Open gkurzbach opened 5 months ago
It is not desired, but in this specific case it isn't that problematic. In other cases that is avoided in an indirect way: https://specification.modelica.org/master/functions.html#pure-modelica-functions
For initial equations, initial algorithms, and bindings it is an error if the function calls are part of systems of equations and thus have to be called multiple times.
It thus seems that the solution for this issue is to specify that the if the function is impure it should only be called once, and that it is an error if the call is part of a system of equations.
Forbidding impure functions in the initial equations is a hard restriction. Especially if you need to initialize external objects outside the constructor, e.g. when implementing an FMI interface.
Would it be possible to specify that an external function might be called multiple times if it is called in when initial() and it has to cope with it?
Forbidding impure functions in the initial equations is a hard restriction. Especially if you need to initialize external objects outside the constructor, e.g. when implementing an FMI interface.
Would it be possible to specify that an external function might be called multiple times if it is called in when initial() and it has to cope with it?
I would consider the following possibilities:
As noted in #3498 similar considerations apply to non-initial when.
In both cases: I would prefer the "only once" interpretation, but I don't know if there are any issues with implementing it.
(Clarified that "In both cases:")
Forbidding impure functions in the initial equations is a hard restriction. Especially if you need to initialize external objects outside the constructor, e.g. when implementing an FMI interface. Would it be possible to specify that an external function might be called multiple times if it is called in when initial() and it has to cope with it?
I would consider the following possibilities:
- Require that tools ensure that an impure function in when-initial is only called once (unless it is actually part of a system of equations - that should be diagnosed).
- Allow it to be called multiple times.
As noted in #3498 similar considerations apply to non-initial when.
In both cases: I would prefer the "only once" interpretation, but I don't know if there are any issues with implementing it.
Do people agree with this idea? Both for loops with when-equations and impure functions call in when-initial.
However, I noted something else that is unclear:
pre(x)=x
before starting the simulation or not?Consider the following simple model:
model Unnamed
output Integer i=if pre(i)<3 then pre(i)+1 else pre(i);
Integer j;
initial equation
Modelica.Utilities.Streams.print("A");
equation
when {initial(),time>=0.5} then
Modelica.Utilities.Streams.print("B");
j=if pre(j)<3 then pre(j)+1 else pre(j);
end when;
end Unnamed;
It deliberately uses pre(...)<3
to avoid infinite loops. In Dymola i starts at 3 and j at 1.
SimulationX starts with i=3 and j=2. So a more detailed description seems to be necessary.
For me the initialization consists of two steps:
At the second point the question is whether code inside when initial()
is excecuted again (initial()
"becomes true") or not. (As I remember in earlier versions of Modelica, before having initial equations, this was the case)
Language group: oversight that we didn't consider all cases where impure functions can be parts of systems of equations. Other case: Please report result for model and try to converge on interpretation. @henrikt-ma @eshmoylova
MapleSim returns i = 3 and j =3.
After some thinking I have come to the following conclusion regarding how to specify the initialization (and a plan for implementing it ...).
A. Solve initial equations (including pre(x)
and x
when needed) and normal equations as in that chapter; treating "initial equation" and "when initial()" the same. No event iteration.
B. Set initial()=false
and disable initial equations, and re-evaluate the model directly using event iterations until convergence of pre(x)=x
. The crucial part is that B doesn't start with a normal new step in the event iteration, since that would involve copying x
to pre(x)
, and additionally it doesn't involve setting x=pre(x)
- but instead keeps current value of x
(see below).
In particular for when-equations it means that if we have:
initial equation
pre(x)=false;
equation
x=...; // Evaluates to true
when x then ...
then when x
will trigger at the start of B, whereas if we copy x
to pre(x)
after solving the initial equations it doesn't. There are cases where this triggering is really desirable.
I'm aware that it is a bit weird since:
But I cannot see any other good solution.
I rejected the following, since they will for e.g., Modelica.StateGraph lead to non-convergence:
pre(x)=x
(for all variables with pre).pre(x)=x
.The reason is that we often have pre(localActive)=false
and localActive=...
with some equation that isn't false at the start; with the rejected ideas that either leads to inconsistent equations, or it leads to an infinite loop.
However, I think we need to have a better explanation for this.
Consider two functions p1(x)
and p2(x)
.
During step A initial equations are active and we use x=p1(x)
and pre(x)=p2(x)
, and solve for p1(x)
and p2(x)
.
At the start of step B we use x:=p1(x)
for inactive when-clauses and at the start of algorithms assigning to discrete variables x
.
For explicit (including edge and when-conditions) use of pre(x)
we use p2(x)
. At subsequent event iterations and during the rest of the simulation we have p1(x)=p2(x)=pre(x)
.
A consequence for Real x(start=0);equation when {initial(),sample(1,1)} x=pre(x)+1; end when;
is that:
A. p2(x)=pre(x)=0
, p1(x)=x=1
.
B. when-clause not active. x:=p1(x)=1
.
A consequence for Real x(start=0);equation when {not initial()} x=pre(x)+1; end when;
is that:
A. p2(x)=pre(x)=0
, p1(x)=x=0
.
B. when-clause active. x=p2(x)+1=1
In the rare case that the when-clause is active both during step A and start of step B it will seem as if there's only one update.
Real x(start=0);equation when {initial(), not initial()} x=pre(x)+1; end when;
gives:
A. p2(x)=pre(x)=0
, p1(x)=x=1
.
B. when-clause active. x=p2(x)+1=1
This case is the part I'm the most unsure about, but I cannot see any good alternative - and it seems quite esoteric.
To simplify things use pre(x)
for p2(x)
, and preOrStart(x)
for p1(x)
- and use preOrStart
in a few places.
As for the actual issue and the previous example:
when {initial(),time>=0.5} then
Modelica.Utilities.Streams.print("X");
j=if pre(j)<3 then pre(j)+1 else pre(j);
end when;
Will start by printing "X" once, and setting j=1
. And any impure function called in "when initial()" will thus only be called once (since no event iteration during initialization), unless it is part of an initial system of equations.
For the record, System Modeler starts with i = 3
, j = 1
(same as Dymola).
This model gives i = 0
in System Modeler, and I would be interested to hear about the situations where it is really desirable that when true then
ever triggers.
model WhenTrue
Integer i(start = 0, fixed = true);
Boolean b;
initial equation
pre(b) = false;
equation
b = true;
when b then
i = 1;
end when;
end WhenTrue;
This model gives
i = 0
in System Modeler, and I would be interested to hear about the situations where it is really desirable thatwhen true then
ever triggers.
Note that it wasn't specifically about "true", but conditions that happen to evaluate to "true" initially. A common situation is when using Modelica.StateGraph (will see if I can construct some examples).
Note that it wasn't specifically about "true", but conditions that happen to evaluate to "true" initially. A common situation is when using Modelica.StateGraph (will see if I can construct some examples).
Sure, the case of a true
literal was just used to illustrate the most basic situation.
Note that it wasn't specifically about "true", but conditions that happen to evaluate to "true" initially. A common situation is when using Modelica.StateGraph (will see if I can construct some examples).
Sure, the case of a
true
literal was just used to illustrate the most basic situation.
After some additional thinking I think I have found a better solution (that I initially rejected).
Step 1 - initialization. initial()=true; Solve initial equations and normal equations for x and pre(x), etc. Do not assume that x=pre(x). Only when-clauses with initial() are active. Step 2 - event iteration after initialization. initial()=false; pre(x):=x; Solve normal equations. if x!=pre(x) go to step 2.
The thing that needs to be clarified is thus:
The thing that needs to be clarified is thus:
- When initial() is true there is no event iteration, and pre(x) and x can be different.
Even though I'm not sure we strictly speaking need to clarify this, I can't see that it would hurt doing it either.
- If a call of an impure function is part of a system of equation it is an error, even if the call occurs in a when-clause.
Sounds good, but should probably be slightly generalized. I think we should also write the text more carefully to deal with the fact that linear systems typically only require a single evaluation.
For initial equations, initial algorithms, and bindings it is an error if the function calls are part of systems of equations (including linear systems), even if called in agreement with the restrictions above.
The reason is that solving systems of equations generally require expressions to be evaluated an unknown number of times.
I'd also like us to clarify this part:
\begin{nonnormative}
Comment: The semantics are undefined if the function call of an
impure function is part of an algebraic loop.
\end{nonnormative}
to make it clear that we are talking about the equation systems of the integration problem here, as we did take care of the equation systems in initialization.
E.g. the MSL sample Modelica.Blocks.Examples.Noise.ImpureGenerator uses Modelica.Blocks.Examples.Noise.Utilities.ImpureRandom which contains:
During initialization the when initial() equations are active, so the result may depend on the number of iterations needed to solve the initial system. Is this desired?