Main Content

Set Model to Steady-State When Estimating Parameters (Code)

This example shows how to set a model to steady-state in the process of parameter estimation. Setting a model to steady-state is important in many applications such as power systems and aircraft dynamics. This example uses a population dynamics model.

This example requires Simulink® Control Design™ software.

Model Description

The Simulink model sdoPopulationInflux models a simple ecology where an organism population growth is limited by the carrying capacity of the environment.

$$\frac{d y}{dt} = R (1 - \frac{y}{K}) (y + 10)$$

  • $R$ is the inherent growth rate of the organism population.

  • $K$ is the carrying capacity of the environment.

There is also an influx of other members of the organism from a neighboring environment. The model uses normalized units.

Open the model.

modelName = 'sdoPopulationInflux';
open_system(modelName)

The file sdoPopulationInflux_Data.mat contains data of the population in the environment as well as the influx of additional organisms from the neighboring environment.

load sdoPopulationInflux_Data.mat;   % Time series:  Population_ts  Inflow_ts
hFig = figure;
subplot(2,1,1);
plot(Population_ts)
subplot(2,1,2);
plot(Inflow_ts)

The population starts in a steady state. After some time, there is an influx of organisms from the neighboring environment. Based on the measured data, you can estimate values for the model parameters.

The parameter R represents the inherent growth rate of the organism. Use 1 as the initial guess for this parameter. It is non-negative.

R = sdo.getParameterFromModel(modelName, 'R');
R.Value = 1;
R.Minimum = 0;

The parameter K represents the carrying capacity of the environment. Use 2 as the initial guess for this parameter. It is known to be at least 0.1.

K = sdo.getParameterFromModel(modelName, 'K');
K.Value = 2;
K.Minimum = 0.1;

Collect these parameters into a vector.

v = [R K];

Compare Measured Data to Initial Simulated Output

Create an Experiment object.

Exp = sdo.Experiment(modelName);

Associate Population_ts with model output.

Population = Simulink.SimulationData.Signal;
Population.Name = 'Population';
Population.BlockPath = [modelName  '/Integrator'];
Population.PortType = 'outport';
Population.PortIndex = 1;
Population.Values = Population_ts;

Add Population to the experiment.

Exp.OutputData = Population;

Associate Inflow_ts with model input.

Inflow = Simulink.SimulationData.Signal;
Inflow.Name = 'Population';
Inflow.BlockPath = [modelName  '/In1'];
Inflow.PortType = 'outport';
Inflow.PortIndex = 1;
Inflow.Values = Inflow_ts;

Add Inflow to the experiment.

Exp.InputData = Inflow;

Create a simulation scenario using the experiment, and obtain the simulated output.

Exp = setEstimatedValues(Exp, v);   % use vector of parameters/states
Simulator = createSimulator(Exp);
Simulator = sim(Simulator);

Search for the model output signal in the logged simulation data.

SimLog = find(Simulator.LoggedData,  ...
	get_param(modelName, 'SignalLoggingName') );
PopulationSim = find(SimLog, 'Population');

The model output does not match the data very well, indicating a need to compute better estimates of the model parameters.

clf;
plot(PopulationSim.Values, 'r');
hold on;
plot(Population_ts, 'b');
legend('Model Simulation', 'Measured Data',  'Location', 'best');

Estimate Parameters

To estimate parameters, define an objective function to compute the sum squared error between model simulation and measured data.

estFcn = @(v) sdoPopulationInflux_Objective(v, Simulator, Exp);
type sdoPopulationInflux_Objective.m
function vals = sdoPopulationInflux_Objective(v, Simulator, Exp, OpPointSetup)
% Compare model output with data
%
%    Inputs:
%       v            - vector of parameters and/or states
%       Simulator    - used to simulate the model
%       Exp          - Experiment object
%       OpPointSetup - Object to set up computation of steady-state
%                      operating point

% Copyright 2018 The MathWorks, Inc.

%Parse inputs
if nargin < 4
    OpPointSetup = [];
end

% Requirement setup
req = sdo.requirements.SignalTracking;
req.Type = '==';
req.Method = 'Residuals';

% Simulate the model
Exp = setEstimatedValues(Exp, v);   % use vector of parameters/states
Simulator = createSimulator(Exp,Simulator);
strOT = mat2str(Exp.OutputData(1).Values.Time);
if isempty(OpPointSetup)
    Simulator = sim(Simulator, 'OutputOption', 'AdditionalOutputTimes', 'OutputTimes', strOT);
else
    Simulator = sim(Simulator, 'OutputOption', 'AdditionalOutputTimes', 'OutputTimes', strOT, ...
        'OperatingPointSetup', OpPointSetup);
end

% Compare model output with data
SimLog = find(Simulator.LoggedData,  ...
	get_param(Exp.ModelName, 'SignalLoggingName') );
OutputModel = find(SimLog, 'Population');
Model_Error = evalRequirement(req, OutputModel.Values, Exp.OutputData.Values);
vals.F = Model_Error;
%Define options for the optimization.
%
opts = sdo.OptimizeOptions;
opts.Method = 'lsqnonlin';

Estimate the parameters.

vOpt = sdo.optimize(estFcn, v, opts);
disp(vOpt)
 Optimization started 2024-Feb-13, 01:17:57

                                          First-order 
 Iter F-count        f(x)      Step-size  optimality
    0      5       12.485            1
    1     10      1.09824        1.184        0.244
    2     15       0.9873        1.088       0.0259
    3     20     0.952948        1.217      0.00624
    4     25     0.946892       0.9151      0.00197
    5     30     0.946484       0.3541      0.00153
Local minimum possible.

lsqnonlin stopped because the final change in the sum of squares relative to 
its initial value is less than the value of the function tolerance.
 
(1,1) =
 
       Name: 'R'
      Value: 5.5942
    Minimum: 0
    Maximum: Inf
       Free: 1
      Scale: 1
       Info: [1x1 struct]

 
(1,2) =
 
       Name: 'K'
      Value: 3.2061
    Minimum: 0.1000
    Maximum: Inf
       Free: 1
      Scale: 2
       Info: [1x1 struct]

 
1x2 param.Continuous
 

Use the estimated parameter values in the model, and obtain the model response. Search for the model output signal in the logged simulation data.

Exp = setEstimatedValues(Exp, vOpt);
Simulator = createSimulator(Exp,Simulator);
Simulator = sim(Simulator);
SimLog = find(Simulator.LoggedData,  ...
	get_param(modelName, 'SignalLoggingName') );
PopulationSim = find(SimLog, 'Population');

Comparing the measured population data with the optimized model response shows that they still do not match well. There is a transient at the beginning of the model response, where it is markedly different from the measured data.

clf;
plot(PopulationSim.Values, 'r');
hold on;
plot(Population_ts, 'b');
legend('Model Simulation', 'Measured Data',  'Location', 'best');

Put Model in Steady-State During Estimation

To improve the fit between the model and measured data, the model needs to be set to steady-state when parameters are estimated. Define an operating point specification. The input is known from experimental data. Therefore, (1) it should not be treated as a free variable when computing the steady-state operating point, and (2) after the operating point is found, its input should not be used when simulating the model. On the other hand, all the states found when computing the operating point should be used when simulating the model. Create an sdo.OperatingPointSetup object to collect the operating point specification, inputs to use, and states to use, so this information can be passed to the objective function and used when simulating the model. You can also provide a fourth argument to sdo.OperatingPointSetup to specify options for computing the operating point. For example, the option 'graddescent-proj' is often used to find the operating point for systems that use physical modeling.

opSpec = operspec(modelName);
opSpec.Inputs(1).Known = true;
inputsToUse = [];
statesToUse = 1:numel(opSpec.States);
OpPointSetup = sdo.OperatingPointSetup(opSpec, inputsToUse, statesToUse);

Estimate the parameters, setting the model to steady-state in the process.

estFcn = @(v) sdoPopulationInflux_Objective(v, Simulator, Exp, OpPointSetup);
vOpt = sdo.optimize(estFcn, v, opts);
disp(vOpt)
 Optimization started 2024-Feb-13, 01:18:33

                                          First-order 
 Iter F-count        f(x)      Step-size  optimality
    0      5      11.1517            1
    1     10     0.025674       0.5732        0.045
    2     15   0.00239293       0.3451        0.357
    3     20  0.000692478       0.0148      0.00301
    4     25   0.00069236    6.539e-05     1.16e-07
Local minimum found.

Optimization completed because the size of the gradient is less than
the value of the optimality tolerance.
 
(1,1) =
 
       Name: 'R'
      Value: 0.5953
    Minimum: 0
    Maximum: Inf
       Free: 1
      Scale: 1
       Info: [1x1 struct]

 
(1,2) =
 
       Name: 'K'
      Value: 3.0988
    Minimum: 0.1000
    Maximum: Inf
       Free: 1
      Scale: 2
       Info: [1x1 struct]

 
1x2 param.Continuous
 

Use the estimated parameter values in the model, and obtain the model response. Search for the model output signal in the logged simulation data.

Exp = setEstimatedValues(Exp, vOpt);
Simulator = createSimulator(Exp,Simulator);
Simulator = sim(Simulator, 'OperatingPointSetup', OpPointSetup);
SimLog = find(Simulator.LoggedData,  ...
	get_param(modelName, 'SignalLoggingName') );
PopulationSim = find(SimLog, 'Population');

There is no more transient at the beginning of the model response, and there is a much better match between the model response and measured data, which is also reflected by the lower objective/cost function value in the second optimization. All these indicate that a good set of parameter values was found.

clf;
plot(PopulationSim.Values, 'r');
hold on;
plot(Population_ts, 'b');
legend('Model Simulation', 'Measured Data',  'Location', 'best');

Related Examples

To learn how to put models in a steady state using the Parameter Estimator app, see Set Model to Steady-State When Estimating Parameters (GUI).

Close the model and figure.

bdclose(modelName)
close(hFig)