modelica / ModelicaSpecification

Specification of the Modelica Language
https://specification.modelica.org
Creative Commons Attribution Share Alike 4.0 International
98 stars 42 forks source link

when initial() with impure function? #3499

Open gkurzbach opened 5 months ago

gkurzbach commented 5 months ago

E.g. the MSL sample Modelica.Blocks.Examples.Noise.ImpureGenerator uses Modelica.Blocks.Examples.Noise.Utilities.ImpureRandom which contains:

when {initial(), sample(samplePeriod,samplePeriod)} then
   y = Modelica.Math.Random.Utilities.impureRandom(globalSeed.id_impure);
end when;

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?

HansOlsson commented 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.

HansOlsson commented 5 months ago

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.

gkurzbach commented 5 months ago

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?

HansOlsson commented 5 months ago

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:")

HansOlsson commented 4 months ago

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:

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.

gkurzbach commented 4 months ago

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:

  1. The solution of the initial equation systems, containing also equations of when initial(), and then
  2. the event iteration at start time.

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)

HansOlsson commented 4 months ago

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

eshmoylova commented 4 months ago

MapleSim returns i = 3 and j =3.

HansOlsson commented 3 months ago

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:

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.

henrikt-ma commented 3 months ago

For the record, System Modeler starts with i = 3, j = 1 (same as Dymola).

henrikt-ma commented 3 months ago

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;
HansOlsson commented 3 months ago

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.

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).

henrikt-ma commented 3 months ago

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.

HansOlsson commented 3 months ago

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:

henrikt-ma commented 2 months ago

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.