Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

when initial() with impure function? #3499

Closed
gkurzbach opened this issue Apr 8, 2024 · 15 comments · Fixed by #3532
Closed

when initial() with impure function? #3499

gkurzbach opened this issue Apr 8, 2024 · 15 comments · Fixed by #3532
Milestone

Comments

@gkurzbach
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator Author

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
Copy link
Collaborator

HansOlsson commented Apr 15, 2024

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.

(Clarified that "In both cases:")

@HansOlsson HansOlsson added this to the 2024-3 milestone May 16, 2024
@HansOlsson
Copy link
Collaborator

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:

  • Does "entire initialization" involve event iterations to ensure pre(x)=x before starting the simulation or not?
  • I would prefer not, which implies that "entire initialization" should be rephrased. Note that several models in MSL seem to be written under this assumption.
  • If it does involve event iterations impure calls in when initial(), initial equations, initial algorithms, and bindings would need even more special care; otherwise it is just a matter of loops.

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
Copy link
Collaborator Author

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
Copy link
Collaborator

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
Copy link
Member

MapleSim returns i = 3 and j =3.

@HansOlsson
Copy link
Collaborator

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:

  • "when initial()" and "when not intial()" will both be active in something that can be seen as one step of the event iteration!
  • This isn't a normal event iteration at all.

But I cannot see any other good solution.

I rejected the following, since they will for e.g., Modelica.StateGraph lead to non-convergence:

  • Solve the initial equations for consistency - including pre(x)=x (for all variables with pre).
  • Event iterations involving also initial equations, until 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.

@henrikt-ma
Copy link
Collaborator

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

@henrikt-ma
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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
Copy link
Collaborator

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:

  • When initial() is true there is no event iteration, and pre(x) and x can be different.
  • 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.

@henrikt-ma
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants