diff --git a/RationaleMCP/0036/BothControl.png b/RationaleMCP/0036/BothControl.png new file mode 100644 index 000000000..caec8b832 Binary files /dev/null and b/RationaleMCP/0036/BothControl.png differ diff --git a/RationaleMCP/0036/README.md b/RationaleMCP/0036/README.md new file mode 100644 index 000000000..7e4c781f9 --- /dev/null +++ b/RationaleMCP/0036/README.md @@ -0,0 +1,149 @@ +Modelica Change Proposal MCP-0036 +Setting states +Hans Olsson +-- + +# Summary +The main idea is that in many cases we use a model within a model (e.g. in Model-Predictive-Control and Feedback Linearization), +and we need to update those states in the model within the model based on more accurate measurements of them during the simulation, +and have the state-updating as part of the model (and not as some tool-specific solution). + +# Revisions +| Date | Description | +| --- | --- | +| 2020-04-22 | Hans Olsson. Created. | +| 2020-07-08 | Hans Olsson. Updated with reinit. | +| 2020-09-25 | Hans Olsson. Updated with new semantics. | +| 2021-04-29 | Hans Olsson. Added test-cases and more complete proposal. | +| 2022-02-14 | Hans Olsson. More test-cases. | +| 2022-05-03 | Hans Olsson. Update specification text, clarified that ready | +| 2023-08-30 | Hans Olsson. Review comments | +| 2025-04-14 | Hans Olsson. Fix reference | + +# Contributor License Agreement +All authors of this MCP or their organizations have signed the "Modelica Contributor License Agreement". + +# Rationale +See previous discussion https://github.com/modelica/ModelicaSpecification/issues/2285 +and paper ttps://doi.org/10.3384/ecp17132517 + +A specific syntax is proposed. + +# Alternatives +The alternatives considered are: + - stateSelect.Measurement and binding equation + - *special binding equation without stateSelect.Measurement* + - possibly reinit within a Clock – check if possible + - replace "reinit" by "measurement"-operator, or forceState(…) + - the implemented annotation does not seem like a good idea + +The proposal does allow us to hide this "turning a measurement into a state" in a block, and thus many of the details will be less important. + +## Annotation variant +The annotation variant was proposed in the paper (not good since it influences the semantics) and is: + +Real xs annotation(useAsInputForState=x); + +This means that 'x' is first seen as a state, but after index reduction x is no longer a state and x=xs replaces the integration of der(x). +What might seem as odd is that the annotation is associated with the measurement variable, not with the state-variable. +The reason is that this allows setting of states deeply nested in existing models (which can otherwise be solved with an extra variable), but +instead requires work-arounds for complicated measurement expressions as in Kalman filters. + +## Alternatives such as reinit(x, xnew) +As discussed in https://github.com/modelica/ModelicaSpecification/issues/2285#issuecomment-641275627 the conclusion of +https://github.com/modelica/ModelicaSpecification/issues/578 was that all reinits are done at the end of the event iteration using previously computed values. + +Thus the current reinit(v, -v/2); does not lead to a loop with reinit, but first evaluates -v/2 (and other reinit-values) and then sets v (and other reinit-values). +One goal of "Clocked Discretized Continuous-Time Partition" is to preserve the behaviour of a restricted set of continuous-time models; having two different variants of reinit (with _the same syntax_ but different semantics) is counter to that. +In addition the current reinit might possibly in the future be replaced by some form of impulses, indicating that the changes are physical and influence other parts of the system, this does not seem consistent with how states are set in this MCP. +Finally having a conditional setting of states is more complicated, and reinit and other similar operators naturally suggest that - and we either need to determine what it means or forbid it. + +## Proposed alternative +Use a special binding equation: + +Real x=reinit(xs); + +This new use of reinit can be separated from the existing use of reinit. +An alternative would be to introduce a new operator, measurement, only legal in a "Clocked Discretized Continuous-Time Partition" and only in this way and its meaning is discussed in the semantics part. + +The name of the operator can be discussed. The current proposal re-uses "reinit" to avoid introducing new reserved words. + +Key points: +- Associated with state, not measurement variable. +- Allows general expressions, not only variables. +- Unconditional. +- Only clocked. +- Few corner cases. +- Easy to recognize for humans and tools. +- Straightforward to implement. Adapting the existing prototype to the new syntax was straightforward. +- The special binding equation is ignored for equation counting. + +Remaining: +- Decide if the way to go. Proposal: Yes +- Name of operator (measurement, reinit, ...). Proposal: reinit +- Must the partition have a discretization method? Proposal: No +- Write specification text (a few paragraphs) + +The reason the partition doesn't need a discretization method is that if we use reinit for all states there is nothing to integrate with the discretization method making it a meaingless choice. +Obviously it is possible to specify a discretization method even if not used. +However, if the intent is to require that all states are measured the proposed semantics allow a check for that. + +# Semantics +The operator is only legal in a "Clocked Discretized Continuous-Time Partition", but the original semantics was unclear + +Thus we present an alternative below, + +## Original semantics (only of historic interest) +The operator is only legal in a "Clocked Discretized Continuous-Time Partition", and means that 'x' is first seen as a state, but after index reduction x is no longer a state and x=xs replaces the integration of der(x) + +The meaning is that +1. StateSelect.always is defined for variable x. +2. It is treated as a state during the usual index reduction and state selection. +3. Afterwards x is deselected as state and the equation x = xs is added. Its derivative is set as a dummy derivative. + +This is not ideal as it uses words like "treated as", and variables are changed from being states to non-states. + +## Discretization method semantics +The operator is only legal in a "Clocked Discretized Continuous-Time Partition", and only as the entire declaration equation for a variable. +The variable must be a scalar or array variable that is a subtype of Real. + +The meaning of Real x=reinit(xs); is that +1. StateSelect.always is set for the variable x. +2. Therefore it is a state and thus participate in the usual index reduction and state selection. +3. During discretization the state is equal to the new value xs during the entire step, instead of using the discretization method +https://specification.modelica.org/master/synchronous-language-elements.html#solver-methods +4. This declaration equation is ignored for the equation count. + +This makes it clear that the variable is an actual state, just integrated differently. +Note that even if the proposed syntax reuses the `reinit` keyword, it avoids the problems with the alternative `reinit(x, xs)` proposal. + +Step (3) requires that it is a state (guaranteed by (1)), and that it is a continuous variable in a "Clocked Discretized Continuous-Time Partition", as stated at the start. + +# Backwards Compatibility +Will depend on exact syntax. +In the proposed alternative it will depend on the measurement keyword; using "reinit" it is fully backwards compatible. + +# Tool Implementation +Both the preliminary variant with annotation and proposed alternative have been implemented in Dymola. + +## Experience with Prototype +The implementation effort was small and it works; both variants give the same results. + +However, as noted in the paper the procedure for using it is still slightly messy. + - Attempt to translate the model + - For each input add appropriate number of integrators and the state-update-component + - Have parameters for those integrators etc. +That could be automated in tools. + +(Note: The reinit-variant in Dymola 2022 and earlier requires that the argument to reinit must be declared before reinit, that was corrected in Dymola 2022x.) + +## Test-cases +The package [TestSettingStates](TestSettingStates.mo) contain a trivial example showing how the model is supposed to be handled, with relevant details, and the previously constructed test-case. + + + +# Required Patents +At best of my knowledge no patents would be required for implementation of this proposal. + +# References +https://doi.org/10.3384/ecp17132517 diff --git a/RationaleMCP/0036/TestSettingStates.mo b/RationaleMCP/0036/TestSettingStates.mo new file mode 100644 index 000000000..d402803a6 --- /dev/null +++ b/RationaleMCP/0036/TestSettingStates.mo @@ -0,0 +1,667 @@ +within ; +package TestSettingStates + model TrivialDemonstration + model DiscretizedWithReinit + input Real u; + Real x=reinit(u); + Real y=2*x; + equation + der(y)=u-y; + end DiscretizedWithReinit; + + model Discretized + input Real u; + Real x(stateSelect=StateSelect.always); + Real y=2*x; + equation + der(y)=u-y; + end Discretized; + Discretized discretized1(u=sample(time, Clock(Clock(1, 10), "ExplicitEuler"))); + DiscretizedWithReinit discretized2(u=sample(time, Clock(Clock(1, 10), "ExplicitEuler"))); + annotation (Documentation(info=" +
The result of this model will be similar to:
++when sample(1e-1) then + /* without state reset */ + discretized1.u=time; + discretized1.x=discretized1.x+...; // Euler discretization + discretized1.y=2*discretized1.x; + discretized1.der_y=time-discretized1.y; + discretized1.der_x=0.5*discretized1.der_y; + + /* with state reset */ + discretized2.u=time; + discretized2.x=time; // Just using input! + discretized2.y=2*discretized2.x; + discretized2.der_y=time-discretized2.y; + discretized2.der_x=0.5*discretized2.der_y; +end when; ++
In particular note that the relation between der_y and der_x is the same regardless of resetting states, i.e., it only influences the integration of x not the differentation before that.
+")); + end TrivialDemonstration; + model TrialArrayDemonstration + model DiscretizedWithReinit + input Real u[:]; + Real x[size(u,1)]=reinit(u); + Real y[:]=2*x; + equation + der(y)=u-y; + end DiscretizedWithReinit; + + model Discretized + input Real u[:]; + Real x[size(u,1)](each stateSelect=StateSelect.always); + Real y[:]=2*x; + equation + der(y)=u-y; + end Discretized; + Discretized discretized1(u={sample(time, Clock(Clock(1, 10), "ExplicitEuler"))}); + DiscretizedWithReinit discretized2(u={sample(time, Clock(Clock(1, 10), "ExplicitEuler"))}); + annotation (Documentation(info=" +The result of this model will be similar to:
++when sample(1e-1) then + /* without state reset */ + discretized1.u[1]=time; + discretized1.x[1]=discretized1.x[1]+...; // Euler discretization + discretized1.y[1]=2*discretized1.x[1]; + discretized1.der_y[1]=time-discretized1.y[1]; + discretized1.der_x[1]=0.5*discretized1.der_y[1]; + + /* with state reset */ + discretized2.u[1]=time; + discretized2.x[1]=time; // Just using input! + discretized2.y[1]=2*discretized2.x[1]; + discretized2.der_y[1]=time-discretized2.y[1]; + discretized2.der_x[1]=0.5*discretized2.der_y[1]; +end when; ++
In particular note that the relation between der_y and der_x is the same regardless of resetting states, i.e., it only influences the integration of x not the differentation before that.
+")); + end TrialArrayDemonstration; + + package System + model FeedforwardControl + extends Modelica.Clocked.Examples.Systems.ControlledMixingUnit(pro=1.1); + annotation (Documentation(info=" +An example from Modelica Standard Library, but changed to have some difference between plant and plant in controller for easier comparison.
+This does not need the possibility for setting states due to using a different controller.
+")); + end FeedforwardControl; + + model FeedbackControl + "Simple example of a mixing unit where a (discretized) nonlinear inverse plant model is used as feedback linearization controller" + extends Modelica.Icons.Example; + + parameter Modelica.Units.SI.Frequency freq=1/300 + "Critical frequency of filter"; + parameter Real c0(unit="mol/l") = 0.848 "Nominal concentration"; + parameter Modelica.Units.SI.Temperature T0=308.5 "Nominal temperature"; + parameter Real a1_inv = 0.2674 "Process parameter of inverse plant model (see references in help)"; + parameter Real a21_inv = 1.815 "Process parameter of inverse plant model (see references in help)"; + parameter Real a22_inv = 0.4682 "Process parameter of inverse plant model (see references in help)"; + parameter Real b_inv = 1.5476 "Process parameter of inverse plant model (see references in help)"; + parameter Real k0_inv = 1.05e14 "Process parameter of inverse plant model (see references in help)"; + parameter Real eps = 34.2894 "Process parameter (see references in help)"; + parameter Real x10 = 0.42 "Relative offset between nominal concentration and initial concentration"; + parameter Real x20 = 0.01 "Relative offset between nominal temperature and initial temperature"; + parameter Real u0 = -0.0224 "Relative offset between initial cooling temperature and nominal temperature"; + final parameter Real c_start(unit="mol/l") = c0*(1-x10) "Initial concentration"; + final parameter Modelica.Units.SI.Temperature T_start=T0*(1 + x20) + "Initial temperature"; + final parameter Real c_high_start(unit="mol/l") = c0*(1-0.72) "Reference concentration"; + final parameter Real T_c_start = T0*(1+u0) "Initial cooling temperature"; + parameter Real pro=1.1 "Deviations of plant to inverse plant parameters"; + final parameter Real a1=a1_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real a21=a21_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real a22=a22_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real b=b_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real k0=k0_inv*pro "Process parameter of plant model (see references in help)"; + + + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.MixingUnit invMixingUnit( + c0=c0, + T0=T0, + a1=a1_inv, + a21=a21_inv, + a22=a22_inv, + b=b_inv, + k0=k0_inv, + eps=eps, + c(start=c_start, fixed=true), + T(start=T_start, + fixed=true), + T_c(start=T_c_start)) + annotation (Placement(transformation(extent={{10,-10},{-10,10}}, origin={2,26}))); + Modelica.Blocks.Math.InverseBlockConstraints inverseBlockConstraints + annotation (Placement(transformation(extent={{-26,-16},{26,16}}, origin={0,28}))); + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.MixingUnit mixingUnit( + c(start=c_start, fixed=true), + T(start=T_start, fixed=true), + c0=c0, + T0=T0, + a1=a1, + a21=a21, + a22=a22, + b=b, + k0=k0, + eps=eps) annotation (Placement(transformation(extent={{-10,-10},{10,10}}, + origin={86,-16}))); + + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.CriticalDamping + filter( + n=3, + f=freq, + x(start={0.49,0.49,0.49}, fixed={true,false,false})) + annotation (Placement(transformation(extent={{-10,-10},{10,10}}, origin={-78,30}))); + Modelica.Clocked.RealSignals.Sampler.Hold hold1(y_start=300) + annotation (Placement(transformation(extent={{-6,-6},{6,6}}, origin={62,-16}))); + Modelica.Clocked.ClockSignals.Clocks.PeriodicRealClock periodicClock1( + useSolver=true, + period=1, + solverMethod="Rosenbrock2") + annotation (Placement(transformation(extent={{-6,-6},{6,6}}, origin={-130,-22}))); + Modelica.Blocks.Sources.Step step(height=c_high_start - c_start, offset= + c_start, + startTime=0) + annotation (Placement(transformation(extent={{-10,-10},{10,10}}, origin={-130,30}))); + Modelica.Clocked.RealSignals.Sampler.SampleClocked sample2 + annotation (Placement(transformation(extent={{-6,-6},{6,6}}, origin={-106,30}))); + Modelica.Clocked.RealSignals.Sampler.Sample sample_c + annotation (Placement(transformation(extent={{6,-6},{-6,6}}, origin={78,68}))); + Modelica.Clocked.RealSignals.Sampler.SampleClocked sample_T + annotation (Placement(transformation(extent={{6,-6},{-6,6}}, origin={70,-44}))); + Utilities.InputToState inputToState annotation (Placement(transformation( + extent={{-10,-10},{10,10}}, origin={8,-4}))); + Utilities.InputToState inputToState1 annotation (Placement(transformation( + extent={{-10,-10},{10,10}}, origin={-6,56}))); + Utilities.FeedbackController controller( + freq=freq) annotation (Placement(transformation(rotation=0, extent={{-58,20},{-38, + 40}}))); + equation + connect(inverseBlockConstraints.y2, invMixingUnit.T_c) annotation (Line( + points={{22.1,28},{22.1,26},{14,26}}, + color={0,0,127})); + connect(invMixingUnit.c, inverseBlockConstraints.u2) annotation (Line( + points={{-10,32},{-20,32},{-20,28},{-20.8,28}}, + color={0,0,127})); + connect(hold1.y, mixingUnit.T_c) annotation (Line( + points={{68.6,-16},{70,-16},{70,-18},{72,-18},{72,-16},{74,-16}}, + color={0,0,127})); + connect(sample2.u,step. y) annotation (Line( + points={{-113.2,30},{-119,30}}, + color={0,0,127})); + connect(filter.u, sample2.y) annotation (Line( + points={{-90,30},{-99.4,30}}, + color={0,0,127})); + connect(periodicClock1.y, sample2.clock) annotation (Line( + points={{-123.4,-22},{-106,-22},{-106,22.8}}, + color={175,175,175}, + pattern=LinePattern.Dot, + thickness=0.5)); + connect(hold1.u, inverseBlockConstraints.y1) annotation (Line(points={{54.8,-16}, + {36,-16},{36,28},{27.3,28}}, color={0,0,127})); + connect(mixingUnit.c, sample_c.u) annotation (Line(points={{98,-10},{104,-10}, + {104,68},{85.2,68}},color={0,0,127})); + connect(sample_T.u, mixingUnit.T) annotation (Line(points={{77.2,-44},{104,-44}, + {104,-22},{98,-22}}, color={0,0,127})); + connect(sample_T.clock, periodicClock1.y) annotation (Line( + points={{70,-51.2},{78,-51.2},{78,-72},{-126,-72},{-126,-22},{-123.4,-22}}, + color={175,175,175}, + pattern=LinePattern.Dot, + thickness=0.5)); + + connect(sample_T.y, inputToState.measurement) annotation (Line(points={{63.4,-44}, + {26,-44},{26,-4},{18,-4}}, color={0,0,127})); + connect(inputToState.stateToSet, invMixingUnit.T) annotation (Line(points={{-2,-4}, + {-10,-4},{-10,20}}, color={0,0,127})); + connect(inputToState1.measurement, sample_c.y) annotation (Line(points={{4,56},{ + 62,56},{62,68},{71.4,68}}, color={0,0,127})); + connect(controller.y, inverseBlockConstraints.u1) annotation (Line( + points={{-38,28},{-28.6,28}}, color={0,0,127})); + connect(filter.y,controller.u1) + annotation (Line(points={{-67,30},{-58,30}}, + color={0,0,127})); + connect(controller.u2, + sample_c.y) + annotation (Line(points={{-56,40},{-56,72},{62,72},{62,68},{71.4,68}}, + color={0,0,127})); + connect(inputToState1.stateToSet, inverseBlockConstraints.u2) annotation ( + Line(points={{-16,56},{-28,56},{-28,28},{-20.8,28}}, + color={0,0,127})); + annotation (Diagram(coordinateSystem(preserveAspectRatio=false, extent={{-140, + -100},{120,100}}), graphics={Rectangle(extent={{-96,70},{40,-18}}, + lineColor={255,0,0}), Text( + extent={{-84,-4},{-38,-12}}, + textColor={255,0,0}, + fillColor={0,0,255}, + fillPattern=FillPattern.Solid, + textString="feedback linearization")}), + experiment(StopTime=300), + Documentation(info=" +This demonstrates using feedback linearization to improve the control of the plant.
+")); + end FeedbackControl; + + model BothControl + "Simple example of a mixing unit where a (discretized) nonlinear inverse plant model is used as feedback linearization controller and another inverse model as feedforward control" + extends Modelica.Icons.Example; + + parameter Modelica.Units.SI.Frequency freq=1/300 + "Critical frequency of filter"; + parameter Real c0(unit="mol/l") = 0.848 "Nominal concentration"; + parameter Modelica.Units.SI.Temperature T0=308.5 "Nominal temperature"; + parameter Real a1_inv = 0.2674 "Process parameter of inverse plant model (see references in help)"; + parameter Real a21_inv = 1.815 "Process parameter of inverse plant model (see references in help)"; + parameter Real a22_inv = 0.4682 "Process parameter of inverse plant model (see references in help)"; + parameter Real b_inv = 1.5476 "Process parameter of inverse plant model (see references in help)"; + parameter Real k0_inv = 1.05e14 "Process parameter of inverse plant model (see references in help)"; + parameter Real eps = 34.2894 "Process parameter (see references in help)"; + parameter Real x10 = 0.42 "Relative offset between nominal concentration and initial concentration"; + parameter Real x20 = 0.01 "Relative offset between nominal temperature and initial temperature"; + parameter Real u0 = -0.0224 "Relative offset between initial cooling temperature and nominal temperature"; + final parameter Real c_start(unit="mol/l") = c0*(1-x10) "Initial concentration"; + final parameter Modelica.Units.SI.Temperature T_start=T0*(1 + x20) + "Initial temperature"; + final parameter Real c_high_start(unit="mol/l") = c0*(1-0.72) "Reference concentration"; + final parameter Real T_c_start = T0*(1+u0) "Initial cooling temperature"; + parameter Real pro=1.1 "Deviations of plant to inverse plant parameters"; + final parameter Real a1=a1_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real a21=a21_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real a22=a22_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real b=b_inv*pro "Process parameter of plant model (see references in help)"; + final parameter Real k0=k0_inv*pro "Process parameter of plant model (see references in help)"; + + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.MixingUnit invMixingUnit( + c0=c0, + T0=T0, + a1=a1_inv, + a21=a21_inv, + a22=a22_inv, + b=b_inv, + k0=k0_inv, + eps=eps, + c(start=c_start, fixed=true), + T(start=T_start, + fixed=true), + T_c(start=T_c_start)) + annotation (Placement(transformation(extent={{10,-10},{-10,10}}, origin={2,26}))); + Modelica.Blocks.Math.InverseBlockConstraints inverseBlockConstraints + annotation (Placement(transformation(extent={{-26,-16},{26,16}}, origin={0,28}))); + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.MixingUnit mixingUnit( + c(start=c_start, fixed=true), + T(start=T_start, fixed=true), + c0=c0, + T0=T0, + a1=a1, + a21=a21, + a22=a22, + b=b, + k0=k0, + eps=eps) annotation (Placement(transformation(extent={{-10,-10},{10,10}}, + origin={86,-16}))); + + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.CriticalDamping + filter( + n=3, + f=freq, + x(start={0.49,0.49,0.49}, fixed={true,false,false})) + annotation (Placement(transformation(extent={{-10,-10},{10,10}}, origin={-78,30}))); + Modelica.Clocked.RealSignals.Sampler.Hold hold1(y_start=300) + annotation (Placement(transformation(extent={{-6,-6},{6,6}}, origin={62,-16}))); + Modelica.Clocked.ClockSignals.Clocks.PeriodicRealClock periodicClock1( + useSolver=true, + period=1, + solverMethod="Rosenbrock2") + annotation (Placement(transformation(extent={{-6,-6},{6,6}}, origin={-130,-22}))); + Modelica.Blocks.Sources.Step step(height=c_high_start - c_start, offset= + c_start, + startTime=0) + annotation (Placement(transformation(extent={{-10,-10},{10,10}}, origin={-130,30}))); + Modelica.Clocked.RealSignals.Sampler.SampleClocked sample2 + annotation (Placement(transformation(extent={{-6,-6},{6,6}}, origin={-106,30}))); + Modelica.Clocked.RealSignals.Sampler.Sample sample_c + annotation (Placement(transformation(extent={{6,-6},{-6,6}}, origin={78,68}))); + Modelica.Clocked.RealSignals.Sampler.SampleClocked sample_T + annotation (Placement(transformation(extent={{6,-6},{-6,6}}, origin={70,-44}))); + Utilities.InputToState inputToState annotation (Placement(transformation( + extent={{-10,-10},{10,10}}, origin={8,-4}))); + Utilities.InputToState inputToState1 annotation (Placement(transformation( + extent={{-10,-10},{10,10}}, origin={-6,56}))); + Utilities.FeedbackController controller( + freq=freq) annotation (Placement(transformation(rotation=0, extent={{-58,20},{-38, + 40}}))); + Modelica.Clocked.Examples.Systems.Utilities.ComponentsMixingUnit.MixingUnit + invMixingUnit1( + c0=c0, + T0=T0, + a1=a1_inv, + a21=a21_inv, + a22=a22_inv, + b=b_inv, + k0=k0_inv, + eps=eps, + c(start=c_start, fixed=true), + T(start=T_start, + fixed=true, + stateSelect=StateSelect.always), + T_c(start=T_c_start)) + annotation (Placement(transformation(extent={{10,-48},{-10,-28}}))); + Modelica.Blocks.Math.InverseBlockConstraints inverseBlockConstraints1 + annotation (Placement(transformation(extent={{-32,-52},{20,-20}}))); + Modelica.Blocks.Math.Gain gain(k=20) annotation (Placement(transformation( + extent={{28,-68},{48,-48}}))); + Modelica.Blocks.Math.Feedback feedback + annotation (Placement(transformation(extent={{-8,-80},{12,-60}}))); + Modelica.Blocks.Math.Add add + annotation (Placement(transformation(extent={{46,14},{66,34}}))); + equation + connect(inverseBlockConstraints.y2, invMixingUnit.T_c) annotation (Line( + points={{22.1,28},{22.1,26},{14,26}}, + color={0,0,127})); + connect(invMixingUnit.c, inverseBlockConstraints.u2) annotation (Line( + points={{-10,32},{-20,32},{-20,28},{-20.8,28}}, + color={0,0,127})); + connect(hold1.y, mixingUnit.T_c) annotation (Line( + points={{68.6,-16},{70,-16},{70,-18},{72,-18},{72,-16},{74,-16}}, + color={0,0,127})); + connect(sample2.u,step. y) annotation (Line( + points={{-113.2,30},{-119,30}}, + color={0,0,127})); + connect(filter.u, sample2.y) annotation (Line( + points={{-90,30},{-99.4,30}}, + color={0,0,127})); + connect(periodicClock1.y, sample2.clock) annotation (Line( + points={{-123.4,-22},{-106,-22},{-106,22.8}}, + color={175,175,175}, + pattern=LinePattern.Dot, + thickness=0.5)); + connect(mixingUnit.c, sample_c.u) annotation (Line(points={{98,-10},{104,-10}, + {104,68},{85.2,68}},color={0,0,127})); + connect(sample_T.u, mixingUnit.T) annotation (Line(points={{77.2,-44},{104,-44}, + {104,-22},{98,-22}}, color={0,0,127})); + connect(sample_T.clock, periodicClock1.y) annotation (Line( + points={{70,-51.2},{78,-51.2},{78,-72},{-126,-72},{-126,-22},{-123.4,-22}}, + color={175,175,175}, + pattern=LinePattern.Dot, + thickness=0.5)); + + connect(sample_T.y, inputToState.measurement) annotation (Line(points={{63.4,-44}, + {28,-44},{28,-4},{18,-4}}, color={0,0,127})); + connect(inputToState.stateToSet, invMixingUnit.T) annotation (Line(points={{-2,-4}, + {-10,-4},{-10,20}}, color={0,0,127})); + connect(inputToState1.measurement, sample_c.y) annotation (Line(points={{4,56},{ + 62,56},{62,68},{71.4,68}}, color={0,0,127})); + connect(controller.y, inverseBlockConstraints.u1) annotation (Line( + points={{-38,28},{-28.6,28}}, color={0,0,127})); + connect(filter.y,controller.u1) + annotation (Line(points={{-67,30},{-58,30}}, + color={0,0,127})); + connect(controller.u2, + sample_c.y) + annotation (Line(points={{-56,40},{-56,72},{62,72},{62,68},{71.4,68}}, + color={0,0,127})); + connect(inputToState1.stateToSet, inverseBlockConstraints.u2) annotation ( + Line(points={{-16,56},{-28,56},{-28,28},{-20.8,28}}, + color={0,0,127})); + connect(invMixingUnit1.T, feedback.u1) + annotation (Line(points={{-12,-44},{-12,-70},{-6,-70}}, color={0,0,127})); + connect(feedback.y,gain. u) annotation (Line(points={{11,-70},{20,-70},{20,-58}, + {26,-58}}, + color={0,0,127})); + connect(invMixingUnit1.c, inverseBlockConstraints1.u2) annotation (Line( + points={{-12,-32},{-22,-32},{-22,-36},{-26.8,-36}}, color={0,0,127})); + connect(filter.y, inverseBlockConstraints1.u1) annotation (Line(points={{-67,30}, + {-62,30},{-62,-36},{-34.6,-36}}, color={0,0,127})); + connect(feedback.u2, inputToState.measurement) annotation (Line(points={{2,-78}, + {2,-90},{56,-90},{56,-44},{28,-44},{28,-4},{18,-4}}, color={0,0,127})); + connect(inverseBlockConstraints1.y2, invMixingUnit1.T_c) annotation (Line( + points={{16.1,-36},{14.05,-36},{14.05,-38},{12,-38}}, color={0,0,127})); + connect(inverseBlockConstraints.y1, add.u1) annotation (Line(points={{27.3,28}, + {38,28},{38,30},{44,30}}, color={0,0,127})); + connect(gain.y, add.u2) annotation (Line(points={{49,-58},{54,-58},{54,-26},{38, + -26},{38,18},{44,18}}, color={0,0,127})); + connect(add.y, hold1.u) annotation (Line(points={{67,24},{72,24},{72,-4},{48,-4}, + {48,-16},{54.8,-16}}, color={0,0,127})); + annotation (Diagram(coordinateSystem(preserveAspectRatio=false, extent={{-140, + -100},{120,100}}), graphics={Rectangle(extent={{-96,70},{40,-18}}, + lineColor={255,0,0}), Text( + extent={{-82,-6},{-36,-14}}, + textColor={255,0,0}, + fillColor={0,0,255}, + fillPattern=FillPattern.Solid, + textString="feedback linearization"), + Rectangle(extent={{-36,-18},{62,-94}}, + lineColor={255,0,0}), Text( + extent={{-40,-82},{6,-90}}, + textColor={255,0,0}, + fillColor={0,0,255}, + fillPattern=FillPattern.Solid, + textString="controller")}), + experiment(StopTime=300), + Documentation(info=" +This demonstrates using feedback linearization to improve the control of the plant.
+")); + end BothControl; + + package Utilities + model FeedbackController + Modelica.Blocks.Math.Feedback feedback annotation (Placement( + transformation(extent={{-10,10},{10,-10}}, origin={-64,0}))); + Modelica.Blocks.Math.Gain gain1(k=K0) annotation (Placement( + transformation(extent={{-10,-10},{10,10}}, origin={-30,0}))); + Modelica.Blocks.Math.Feedback feedback1 annotation (Placement( + transformation(extent={{-10,10},{10,-10}}, origin={2,0}))); + Modelica.Blocks.Math.Gain gain(k=K1) annotation (Placement( + transformation(extent={{10,-10},{-10,10}}, origin={26,30}))); + SimpleIntegrator simpleIntegrator1(initType=Modelica.Blocks.Types.Init.NoInit) + annotation (Placement(transformation(extent={{-10,-10},{10,10}}, + origin={32,0}))); + SimpleIntegrator simpleIntegrator(initType=Modelica.Blocks.Types.Init.NoInit) + annotation (Placement(transformation(extent={{-10,-10},{10,10}}, + origin={72,0}))); + final parameter Real K0 = (2*pi*freq)^2; + final parameter Real K1 = 2*(2*pi*freq); + parameter Modelica.Units.SI.Frequency freq=1/300 + "Critical frequency of filter"; + constant Real pi = Modelica.Constants.pi; + Modelica.Blocks.Interfaces.RealInput u1 annotation (Placement( + transformation(rotation=0, extent={{-100,-10},{-80,10}}))); + Modelica.Blocks.Interfaces.RealInput u2 annotation (Placement( + transformation(rotation=0, extent={{-80,90},{-60,110}}))); + Modelica.Blocks.Interfaces.RealOutput y(start=simpleIntegrator.y_start) + annotation (Placement(transformation(rotation=0, extent={{100,-30},{ + 120,-10}}))); + equation + connect(simpleIntegrator1.y, simpleIntegrator.u) annotation (Line(points={{43,0},{ + 60,0}}, color={0,0,127})); + connect(feedback1.u2, gain.y) + annotation (Line(points={{2,8},{2,30},{15,30}}, color={0,0,127})); + connect(gain1.y, feedback1.u1) annotation (Line(points={{-19,0},{-6,0}}, + color={0,0,127})); + connect(feedback1.y, simpleIntegrator1.u) annotation (Line(points={{11,0},{ + 20,0}}, color={0,0, + 127})); + connect(simpleIntegrator1.y, gain.u) annotation (Line(points={{43,0},{ + 48,0},{48,30},{38,30}}, + color={0,0,127})); + connect(feedback.y, gain1.u) + annotation (Line(points={{-55,0},{-42,0}}, color={0,0,127})); + connect(u1, feedback.u1) + annotation (Line(points={{-90,0},{-72,0}}, color={0,0,127})); + connect(u2, feedback.u2) annotation (Line(points={{-70,100},{-70,14},{ + -64,14},{-64,8}}, color={0,0,127})); + connect(y, simpleIntegrator.y) annotation (Line(points={{110,-20},{88, + -20},{88,0},{83,0}}, color={0,0,127})); + annotation (Diagram(coordinateSystem(extent={{-90,-100},{110,100}})), + Icon(coordinateSystem(extent={{-90,-100},{110,100}}))); + end FeedbackController; + + block SimpleIntegrator + "Output the integral of the input signal with optional reset" + import Modelica.Blocks.Types.Init; + parameter Real k(unit="1")=1 "Integrator gain"; + /* InitialState is the default, because it was the default in Modelica 2.2 + and therefore this setting is backward compatible + */ + parameter Init initType=Init.InitialState + "Type of initialization (1: no init, 2: steady state, 3,4: initial output)" annotation(Evaluate=true, + Dialog(group="Initialization")); + parameter Real y_start=0 "Initial or guess value of output (= state)" + annotation (Dialog(group="Initialization")); + extends Modelica.Blocks.Interfaces.SISO(y(start=y_start)); + equation + der(y) = k*u; + annotation ( + Documentation(info=" ++This blocks computes output y as +integral of the input u multiplied with +the gain k: +
++ ++ k +y = - u + s +
+It might be difficult to initialize the integrator in steady state. +This is discussed in the description of package +Continuous. +
+ ++If the reset port is enabled, then the output y is reset to set +or to y_start (if the set port is not enabled), whenever the reset +port has a rising edge. +
+"), Icon(coordinateSystem( + preserveAspectRatio=true, + extent={{-100.0,-100.0},{100.0,100.0}}), + graphics={ + Line( + points={{-80.0,78.0},{-80.0,-90.0}}, + color={192,192,192}), + Polygon( + lineColor={192,192,192}, + fillColor={192,192,192}, + fillPattern=FillPattern.Solid, + points={{-80.0,90.0},{-88.0,68.0},{-72.0,68.0},{-80.0,90.0}}), + Line( + points={{-90.0,-80.0},{82.0,-80.0}}, + color={192,192,192}), + Polygon( + lineColor={192,192,192}, + fillColor={192,192,192}, + fillPattern=FillPattern.Solid, + points={{90.0,-80.0},{68.0,-72.0},{68.0,-88.0},{90.0,-80.0}}), + Text( + extent={{-150.0,-150.0},{150.0,-110.0}}, + textString="k=%k"), + Line( + points=DynamicSelect({{-80.0,-80.0},{80.0,80.0}}, if use_reset then {{-80.0,-80.0},{60.0,60.0},{60.0,-80.0},{80.0,-60.0}} else {{-80.0,-80.0},{80.0,80.0}}), + color={0,0,127}), + Line( + visible=use_reset, + points={{60,-100},{60,-80}}, + color={255,0,255}, + pattern=LinePattern.Dot)})); + end SimpleIntegrator; + + block InputToState + Modelica.Blocks.Interfaces.RealInput measurement + annotation (Placement(transformation(extent={{120,-20},{80,20}}))); + RealInputSetState stateToSet=reinit(measurement) + annotation (Placement(transformation(extent={{-120,-20},{-80,20}}))); + annotation (Icon(coordinateSystem(preserveAspectRatio=false)), Diagram( + coordinateSystem(preserveAspectRatio=false))); + end InputToState; + + connector RealInputSetState = + input Real "'input Real' as connector" annotation ( + defaultComponentName="u", + Icon(graphics={ + Polygon( + lineColor={0,0,127}, + fillColor={238,46,47}, + fillPattern=FillPattern.Solid, + points={{-100.0,100.0},{100.0,0.0},{-100.0,-100.0}})}, + coordinateSystem(extent={{-100.0,-100.0},{100.0,100.0}}, + preserveAspectRatio=true, + initialScale=0.2)), + Diagram( + coordinateSystem(preserveAspectRatio=true, + initialScale=0.2, + extent={{-100.0,-100.0},{100.0,100.0}}), + graphics={ + Polygon( + lineColor={0,0,127}, + fillColor={238,46,47}, + fillPattern=FillPattern.Solid, + points={{0.0,50.0},{100.0,0.0},{0.0,-50.0},{0.0,50.0}}), + Text( + textColor={0,0,127}, + extent={{-10.0,60.0},{-10.0,85.0}}, + textString="%name")}), + Documentation(info=" ++Connector with one input signal of type Real. +
+")); + end Utilities; + end System; + package ErrorExamples + model DiscretizedIncorrect1 "Illegal since rhs must just be reinit, use reinit(-u) instead." + input Real u; + Real x=-reinit(u); + equation + when Clock(Clock(1,10), "ExplicitEuler") then + der(x)=u-x; + end when; + end DiscretizedIncorrect1; + model DiscretizedIncorrect2 "Illegal since reinit must be in a binding declaration" + input Real u; + Real x; + equation + when Clock(Clock(1,10), "ExplicitEuler") then + der(x)=u-x; + x=reinit(u); + end when; + end DiscretizedIncorrect2; + model DiscretizedIncorrect3 "Illegal, since cannot have both x and y as states" + input Real u; + Real x=reinit(u); + Real y(stateSelect=StateSelect.always)=2*x; + equation + when Clock(Clock(1,10), "ExplicitEuler") then + der(y)=u-y; + end when; + end DiscretizedIncorrect3; + model DiscretizedIncorrect4 "Using normal reinit is legal, but does not give the desired result for y" + model DiscretizedWithReinitOther "Using normal reinit instead, different result for y" + /* + Specifically the normal reinit semantics imply that x will be updated at the end of the step. + The clocked semantics normally only evaluate y once per step. + Combined this means one step delay in y. + There are also additional minor changes related to der(y) and using y during the calculation. + + (And normally reinit would be in a non-clocked when.) + */ + input Real u; + Real x(stateSelect=StateSelect.always); + Real y=2*x; + equation + der(y)=u-y; + when Clock() then + reinit(x,u); + end when; + end DiscretizedWithReinitOther; + DiscretizedWithReinitOther discretized3(u=sample(time, Clock(Clock(1, 10), "ExplicitEuler"))); + + extends TrivialDemonstration; + end DiscretizedIncorrect4; + end ErrorExamples; + annotation (uses(Modelica(version="4.0.0"))); +end TestSettingStates; diff --git a/chapters/classes.tex b/chapters/classes.tex index fd355dca4..477dc526e 100644 --- a/chapters/classes.tex +++ b/chapters/classes.tex @@ -1037,7 +1037,7 @@ \section{Balanced Models}\label{balanced-models} \end{itemize} \end{definition} -\begin{definition}[Local equation size]\index{local equation size} +\begin{definition}[Local equation size]\index{local equation size}\label{local-equation-size} The local equation size of a \lstinline!model! or \lstinline!block! class is the sum of the following numbers: \begin{itemize} \item diff --git a/chapters/equations.tex b/chapters/equations.tex index 9c3a85c68..5e592b7f1 100644 --- a/chapters/equations.tex +++ b/chapters/equations.tex @@ -436,7 +436,8 @@ \subsubsection{Single Assignment Rule Applied to When-Equations}\label{applicati \subsection{reinit}\label{reinit} -\lstinline!reinit! can only be used in the body of a \lstinline!when!-equation. +The \lstinline!reinit!-equation can only be used in the body of a \lstinline!when!-equation. +(There is also \lstinline!reinit! declaration equation, \cref{setting-states}.) It has the following syntax: \begin{lstlisting}[language=modelica] reinit(x, expr); diff --git a/chapters/operatorsandexpressions.tex b/chapters/operatorsandexpressions.tex index 6f7ea2b3b..ad3831578 100644 --- a/chapters/operatorsandexpressions.tex +++ b/chapters/operatorsandexpressions.tex @@ -1472,6 +1472,7 @@ \subsection{Event-Related Operators with Function Syntax}\label{event-related-op $x$ is a scalar or array \lstinline!Real! variable that is implicitly defined to have \lstinline!StateSelect.always!. \begin{nonnormative} It is an error if the variable cannot be selected as a state. +Note that there is also the \lstinline!reinit! declaration equation, \cref{setting-states}. \end{nonnormative} $\mathit{expr}$ needs to be type-compatible with $x$. \lstinline!reinit! can only be applied once for the same variable -- either as an individual variable or as part of an array of variables. diff --git a/chapters/synchronous.tex b/chapters/synchronous.tex index f5ba319f4..275774c73 100644 --- a/chapters/synchronous.tex +++ b/chapters/synchronous.tex @@ -1125,6 +1125,8 @@ \section{Discretized Sub-Partition}\label{continuous-time-equations-in-clocked-p This feature also allows defining multi-rate systems: Different parts of the continuous-time model are associated to different clocks and are solved with different integration methods between clock ticks, e.g., a very fast sub-system with an implicit solver with a small step-size and a slow sub-system with an explicit solver with a large step-size. + +There is also a special handling for determining states according to measurements instead of the normal integration method, \cref{setting-states}. \end{nonnormative} With the language elements defined in this section, continuous-time equations can be used in clocked partitions. @@ -1185,6 +1187,7 @@ \subsection{Solver Methods}\label{solver-methods} A sub-partition can have an integration method, directly associated (\cref{associating-a-solver-to-a-partition}) or inferred from other sub-partitions (\cref{inferencing-of-solvermethod}). A predefined type \lstinline!ModelicaServices.Types.SolverMethod! defines the methods supported by the respective tool by using the \lstinline!choices! annotation. +See also \cref{setting-states}. \begin{nonnormative} The \lstinline!ModelicaServices! package contains tool specific definitions. @@ -1341,6 +1344,53 @@ \subsection{Solver Methods}\label{solver-methods} This data is tool specific and is typically either defined with a vendor annotation or is given in the simulation environment. \end{nonnormative} +\subsection{Setting states}\label{setting-states} +In model-based control systems, a natural way of incorporating measurements can be to update discretized model states based on the measurements, see \textcite{OlssonEtAl2017ModelBased}. +Note that \lstinline!reinit! also has another definition, see \cref{reinit}. + +A declaration equation can have the form \lstinline!Real x = reinit(expr)!. +The restrictions are that: +\begin{itemize} +\item It is only legal in a discretized sub-partition. +\item The variable (i.e.,\ \lstinline!x!) must be a scalar or array variable that is a subtype of \lstinline!Real!. +\item The right-hand-side must consist solely of a call to \lstinline!reinit!. +\item The argument to \lstinline!reinit! (i.e,\ \lstinline!expr!) must be compatible with the variable. +\end{itemize} + +The semantics are: +\begin{itemize} +\item The variable implicitly has \lstinline!StateSelect.always!. +\item The variable participates in the usual index reduction and state selection (where it must be selected as a state). +\item The discretization of the state is equal to the new value \lstinline!expr! during the entire step, instead of using the discretization method in \cref{solver-methods}. +\item This declaration equation is ignored for the equation count, see \cref{local-equation-size}. +\end{itemize} + +\begin{example} +This example shortly demonstrates the semantics; for realistic use-cases, see \textcite{OlssonEtAl2017ModelBased}. +\begin{lstlisting}[language=modelica] +model DiscretizedWithReinit + Real u = sample(time, Clock(Clock(1, 10), "ExplicitEuler")); + Real x = reinit(u); + Real y = 2 * x; +equation + der(y) = u - y; +end DiscretizedWithReinit; +\end{lstlisting} +This is similar to the following equations: +\begin{lstlisting}[language=modelica] +when sample(0.1) then + u = time; + x = u; // Discretization - just using input! + y = 2 * x; + der_y = time - y; + der_x = 0.5 * der_y; +\end{lstlisting} +Here we see that \lstinline!x! was selected as a state in favor of \lstinline!y!, despite the latter being differentiated. +Without the \lstinline!reinit!-call the equation for \lstinline!x! would be \lstinline!pre(x) + 0.1 * pre(der_x)!. +Note that replacing the discretization for \lstinline!x! does not change the equations for \lstinline!der_y! and \lstinline!der_x! compared to solver method discretization. +\end{example} + + \subsection{Associating a Solver to a Sub-Partition}\label{associating-a-solver-to-a-partition} A \lstinline!SolverMethod! can be associated to a clock with the overloaded \lstinline!Clock! constructor \lstinline!Clock($c$, solverMethod=$\ldots$)!, see \cref{clock-constructors}. diff --git a/mls.bib b/mls.bib index a5de3e9e3..72a42548d 100644 --- a/mls.bib +++ b/mls.bib @@ -65,6 +65,18 @@ @Article{Harel1987Statecharts url = {http://www.inf.ed.ac.uk/teaching/courses/seoc1/2005_2006/resources/statecharts.pdf}, } +@InProceedings{OlssonEtAl2017ModelBased, + author = {Olsson, Hans and Mattsson, Sven Erik and Otter, Martin and Pfeiffer, Andreas and Bürger, Christoff and Henriksson, Dan}, + title = {Model-based Embedded Control using Rosenbrock Integration Methods}, + year = {2017}, + month = may, + day = {15--17}, + booktitle = {Proceedings of the 12\textsuperscript{th} International Modelica Conference}, + pages = {517--526}, + address = {Prague, Czech Republic}, + url = {https://doi.org/10.3384/ecp17132517}, +} + @InProceedings{ThummelEtAl2005InverseModels, author = {Thümmel, Michael and Looye, Gertjan and Kurze, Matthias and Otter, Martin and Bals, Johann}, title = {Nonlinear Inverse Models for Control},