Open henrikt-ma opened 4 years ago
In my opinion, a better alternative to overriding the start
attribute with the default start-value is to define this as an error. A less pedantic tool could still choose to go against the specification and do something else together with a warning, but this doesn't mean it should be standardized.
When this error occurs, I see two possible resolutions:
start
by something that can be evaluated prior to the algorithm — a constant, for example.Another reason to not fall back to default start values along with a warning is that this seems comparatively complicated in terms of the symbolic processing. One would first need to form the strong components of the initialization problem with the goal of respecting all start
attributes, and when this fails, look for those that can be ignored to see if this can break the strong component apart — one start
at a time to not disregard more of them than necessary (perhaps there are two candidates, where disregarding either one would be sufficient…) — and if that actually resolves the entangling, produce the warnings and proceed.
I'm not saying this because I am afraid of carrying out symbolic processing — in the short term, this is how we make our living after all. I'm saying it because the more complicated processing like this we add to a tool's responsibilities, the harder it becomes for the end user to understand what is going on. Hence, in the long term, it can serve all of us bad.
Let's add some example for illustration.
First one where a strange result would be obtained if q.start
is ignored:
model StartMatters
parameter Boolean b = false;
parameter Boolean p(start = true, fixed = false); /* Should become equal to b, which is false. */
parameter Integer q(start = if p then 1 else 2, fixed = false); /* Should become 2, if anything. */
initial algorithm
if b then
q := 3;
else
p := false;
end if;
end StartMatters;
As explained by the comments in the model, the expected value of q
after initialization, if anything, would be 2.
If q.start
is ignored, one will obtain q
= 0.
If q.start
is evaluated before having determined p
, some sort of uninitialize read will determine the initialization of q
:
p
is set to p.start
while waiting for it to be initialized, one would get q
= 2.p
is set to the default for Boolean
while waiting for its initial algorithm to run, one would obtain q
= 1.p
before it has been initialized by its initial algorithm, meaning q
could really end up depending on the value of uninitialized memory.Then one example where q.start
can be disregarded without affecting the initialization:
model UnusedStart
parameter Boolean b = false;
parameter Boolean p(start = true, fixed = false); /* Should become equal to b, which is false. */
parameter Integer q(start = if p then 1 else 2, fixed = false); /* The 'start' has no effect. Should become 4, if anything. */
initial algorithm
q := 4; /* Assigning to q before first use in algorithm, no need to initialize to q.start. */
if b then
q := 3;
else
p := false;
end if;
end UnusedStart;
Note that it only requires a simple local analysis to figure out that the algorithm in UnusedStart
doesn't depend on q.start
for initialization. This can be done before constructing the incidence graph of the initialization problem, so there is no need to manipulate the initial incidence graph to find a way out.
In case the examples above are confusingly minimalistic in that they are entangled even though they consist of just a single algorithm, one can extend them to also include an equation:
model StartMattersExtended
parameter Boolean b = false;
parameter Boolean p(start = true, fixed = false); /* Should become equal to b, which is false. */
parameter Boolean p2(fixed = false); /* Should become equal to p. */
parameter Integer q(start = if p2 then 1 else 2, fixed = false); /* Should become 2, if anything. */
initial algorithm
if b then
q := 3;
else
p := false;
end if;
initial equation
p2 = p;
end StartMattersExtended;
model UnusedStartExtended
parameter Boolean b = false;
parameter Boolean p(start = true, fixed = false); /* Should become equal to b, which is false. */
parameter Boolean p2(fixed = false); /* Should become equal to p. */
parameter Integer q(start = if p2 then 1 else 2, fixed = false); /* Should become 4, if anything. */
initial algorithm
q := 4; /* Assigning to q before first use in algorithm. */
if b then
q := 3;
else
p := false;
end if;
initial equation
p2 = p;
end UnusedStartExtended;
In my opinion, a better alternative to overriding the
start
attribute with the default start-value is to define this as an error. A less pedantic tool could still choose to go against the specification and do something else together with a warning, but this doesn't mean it should be standardized.
That is also a possibility. The problem is that we then need to define it in more detail.
However, to me these models seem broken in some way.
I know that technically an (initial) algorithm start with p:=pre(p);
and thus they are well-defined and for a normal algorithm it makes sense that the assignment to p
is conditional.
However, I cannot see that having the only assignment to a variable conditional in an initial algorithm is a good idea, and I couldn't find any such examples in MSL.
In my opinion, a better alternative to overriding the
start
attribute with the default start-value is to define this as an error. A less pedantic tool could still choose to go against the specification and do something else together with a warning, but this doesn't mean it should be standardized.That is also a possibility. The problem is that we then need to define it in more detail.
I don't think we have to. We always depend on tools to perform symbolic analysis with error detection and reasonable user feedback in case of errors. Compared to other requirements on doing sane symbolic processing, the processing needed to work out the examples above is really simple. Still, it's a quality of implementation to even do this — a less ambitious tool could ignore the possibility to figure out which non-fixed parameter start values an initial algorithm needs for initialization, treat all as needed, and thereby fail to translate some models that other tools will be able to prove valid.
I would expect that if a model relying on this kind of analysis was added to MSL, and some tools wouldn't be able to see that a start-value is unused, then it would be the library author's (not some tool's) responsibility to explain why the start
ends up unused, so that this reasoning can be implemented in tools. However, an easier way out for the library author is probably going to be to switch to a start
that is trivial to evaluate before the start of the algorithm.
However, to me these models seem broken in some way.
Yes, and I'm just looking for a clean way of rejecting the worst ones while at the same time introducing what I believe is a more useful way to think of initial algorithm initialization (only relevant for initial algorithms; normal algorithms don't assign to non-fixed parameters, and the pre-values of discrete-time variables are always available).
I know that technically an (initial) algorithm start with
p:=pre(p);
and thus they are well-defined and for a normal algorithm it makes sense that the assignment top
is conditional.
Note that these examples only use parameters, which is why we need to avoid reference to pre(p)
for initialization. (However, te comment is still relevant since it would — in my opinion — make sense to also ease up the requirement to initialize discrete-time variables to pre(x)
when it can be proven unused.)
However, I cannot see that having the only assignment to a variable conditional in an initial algorithm is a good idea, and I couldn't find any such examples in MSL.
Yes, it's good if the MSL isn't relying on the use of unavailable start-values.
To me this focus on "initial algorithm" and pre-values for discrete-valued variables misses an important aspect of the overall problem. The "initial algorithm" above together with the guess-value (=start) form a system of equations.
But we don't need an initial algorithm to have guess-values that are implicitly defined as part of the system of equations.
Consider the following problem:
model StartMatters
parameter Real p2 = -2;
parameter Real p(start = r.x, fixed = false);
record R
Real x,y;
end R;
parameter R r=R(p2,p);
initial equation
p^4+p^2=3;
end StartMatters;
Depending on whether r=R(p2,p)
is split into two equations or treated as one "atomic operation" there is either a known guess-value for the parameter p
or not. (Using start=-2
would avoid that ambiguity.)
To me this focus on "initial algorithm" and pre-values for discrete-valued variables misses an important aspect of the overall problem. The "initial algorithm" above together with the guess-value (=start) form a system of equations.
I agree that the particular case of initial algorithm and discrete-valued variables is a special case of the general problem of not having start-values ready when needed. Indeed, saying something about the general problem would be a highly desirable outcome in the end, but particular cases such as the initial algorithms or your record binding equation below will both be necessary as input to the general discussion, could also require us to make up rules for special cases if we don't find the general formulations we come up with precise enough.
But we don't need an initial algorithm to have guess-values that are implicitly defined as part of the system of equations. … Depending on whether
r=R(p2,p)
is split into two equations or treated as one "atomic operation" there is either a known guess-value for the parameterp
or not. (Usingstart=-2
would avoid that ambiguity.)
This is a good example to work on, and here is how I would like to handle it.
p
to not be considered dependent on p.start
, allowing evaluation of r.x
to be optimized away completely.p.start
.Filippo, Gerd, and Martin S. will volunteer to read and hopefully give feedback.
Assuming that there's a clear distinction between the declaration and the initialization sections, one could think at the first being the pre-processing to the second one. I try to add some details in the following, please comment/add details in case you think I'm missing something.
The declaration section contains parameters declarations that are instantiated before the initialization process occurs. Here the start values (both fixed and/or not) and the binding expressions are present and are used to create the input to the initialization phase.
During the instantiation phase, a list of symbols, where each symbol can have a binding expression is formed. Here the binding equations/statements can be derived from the start values or from an assignment (binding expressions in case of a parameter). If both are present the latter is used.
Whenever a parameter is missing such an equation, a warning should be given, something like: "Warning parameter "a" value is unknown at declaration time. Please add a start value / binding expression."? This might already be offered by some Modelica environments.
Alongside and aligned with the previous, whenever a parameter value cannot be computed using the previous system a warning should be thrown, such as: "Warning parameter "a" value cannot be evaluated at declaration time."
For the sake of simplicity and clarity, I think that these initial values should then be available for being used inside the "initial algorithm" section.
Summarizing, given the freedom of the Modelica language in describing things, which results in a high level of complexity that has to be handled by the compiler, I propose to define something as simple and linear as possible.
In #2602, the following was added (merged to master) without prior discussion in the language group, with added emphasis on the controversial part:
Tentatively removing this part is the topic of #2666, and this issue is about putting this, or something else back.