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

Don't require discrete-time Real variables to be assigned in when-clauses #3247

Open henrikt-ma opened 1 year ago

henrikt-ma commented 1 year ago

I have a modeling problem where I need to detect changes in a Real variable that is discrete-time by construction, without being assigned in a when-clause. This captures what I'd like the specification to allow:

model M
  discrete Real x(start = 0.0, fixed = true);
  discrete Real y = x + floor(time); /* Currently not allowed */
  Integer count(start = 0);
equation
  when time > 0.5 then
    x = 0.1;
  end when;
  when change(y) then
    count = pre(count) + 1;
  end when;
end M;

This would only require minor changes in the specification:

Note that the following existing rule is key to the semantics of the new discrete-time Real variables:

When determining whether an equation can contribute to solving for a variable v (for instance, when applying the perfect matching rule, see section 8.4), the equation can only be considered contributing if the resulting solution would be at most as variable as v.

Also note that existing variability rules also disallow bad non-sensical things like this:

discrete Real y = x + time; /* Variability error: RHS has higher variability than declared component. */

This has been test-implemented with good results in System Modeler.

HansOlsson commented 1 year ago

That would be a major change; as suddenly tools need to deduce discrete variability of reals. A much simpler change is to just deprecate discrete; similarly as we don't declare variables to be clocked.

Note that for clocked variables the automatic deduction mean that we can re-use blocks in clocked partitions, especially simple blocks like gain, add, etc.

henrikt-ma commented 1 year ago

That would be a major change; as suddenly tools need to deduce discrete variability of reals.

No, this is not what is proposed. A Real variable shall be considered discrete-time for purposes of variability analysis only if assigned in a when-clause (current rule) or if declared with the prefix discrete (new rule). No need to deduce things.

On the contrary, I think that just deprecating discrete would leave us with the need for a lot of inference that will make programs more difficult to reason about.

HansOlsson commented 1 year ago

That would be a major change; as suddenly tools need to deduce discrete variability of reals.

No, this is not what is proposed. A Real variable shall be considered discrete-time for purposes of variability analysis only if assigned in a when-clause (current rule) or if declared with the prefix discrete (new rule). No need to deduce things.

And I'm seeing the implications of that - e.g., people declaring an output in an interface as "discrete Real" to indicate that it should be discrete, and then trying to construct it graphically using e.g., a gain-block - where the gain-block lacks the discrete part - leading to a mismatch, and possibly reduced re-usability.

This is something we have already seen with questions on the interface of Modelica.Blocks.Interfaces.DiscreteSISO.

henrikt-ma commented 1 year ago

And I'm seeing the implications of that - e.g., people declaring an output in an interface as "discrete Real" to indicate that it should be discrete, and then trying to construct it graphically using e.g., a gain-block - where the gain-block lacks the discrete part - leading to a mismatch, and possibly reduced re-usability.

I am aware of this problem, but I don't see why it should prevent us from fixing the problem of being able to apply change to an expression which is obviously discrete-time, without breaking the very nice declarative safety of the current variability analysis. (Turning away from the use of declared variability in favor of inferred variability would be a drastic, and in my opinion unfortunate, change of the language.)

Some other time, we should think about a way to model functional relations between variables in block-oriented modeling, perhaps in some form of "Using functions as blocks". However, that's way beyond the scope of this issue.

HansOlsson commented 1 year ago

And I'm seeing the implications of that - e.g., people declaring an output in an interface as "discrete Real" to indicate that it should be discrete, and then trying to construct it graphically using e.g., a gain-block - where the gain-block lacks the discrete part - leading to a mismatch, and possibly reduced re-usability.

I am aware of this problem, but I don't see why it should prevent us from fixing the problem of being able to apply change to an expression which is obviously discrete-time, without breaking the very nice declarative safety of the current variability analysis.

As explained above: Currently we can only apply change to Real variables given by equations in when-clauses. Extending it to some other discrete-time Real variables creates unrealistic expectations and additional problems, as it cannot be extended to all in a straightforward way.

henrikt-ma commented 1 year ago

As explained above: Currently we can only apply change to Real variables given by equations in when-clauses. Extending it to some other discrete-time Real variables creates unrealistic expectations and additional problems, as it cannot be extended to all in a straightforward way.

I need to repeat that this has been test-implemented in System Modeler with good results. Could you please provide concrete examples of potential pitfalls, so that it can be assessed what cannot be extended to all in a straightforward way really means?

HansOlsson commented 1 year ago

As explained above: Currently we can only apply change to Real variables given by equations in when-clauses. Extending it to some other discrete-time Real variables creates unrealistic expectations and additional problems, as it cannot be extended to all in a straightforward way.

I need to repeat that this has been test-implemented in System Modeler with good results. Could you please provide concrete examples of potential pitfalls, so that it can be assessed what cannot be extended to all in a straightforward way really means?

There are two kinds of pitfalls with it.

First is the discrete-ness if we instead consider doing it more graphically it becomes (note that I assume the original example was plain wrong, it assumedly should use change(y) not change(x) - that's another reason to use graphical modeling).

model M
  discrete Modelica.Blocks.Interfaces.RealOutput x(start = 0.0, fixed = true);
  discrete Modelica.Blocks.Interfaces.RealOutput y;
  Modelica.Blocks.Sources.ContinuousClock cClock;
  Floor floor1;
  Modelica.Blocks.Math.Add add;
  Integer count(start = 0);
equation
  when time > 0.5 then
    x = 0.1;
  end when;
  when change(y) then
    count = pre(count) + 1;
  end when;
  connect(cClock.y, floor1.u);
  connect(floor1.u, add.u1);
  connect(x, add.u1);
  connect(add.y, y);
end M;

That exposes one problem, and one might then have to add 'discrete' in lots of places - which is both distracting and counter to the idea for clocked variables that blocks can be used for both normal and clocked cases with the same declaration. It may also be that it is involving parts of other models where the modification is not possible.

The second problem is that equality for Real numbers is not legal (technically it is legal inside of functions but pre is not allowed in functions); so change(x) is not legal for a real x.

Lifting that restriction for discrete Reals given by when-clauses might be possible, but for these new proposed discrete Reals it would be problematic. Basically a Real given by a when-clause is either given a value in the when-clause or equal to pre(x); so any numeric noise is likely to only trigger them when the when-clause is anyway active. For the proposed discrete Real not given by a when-clause it might be given by a system of equations in general and those solutions may have numerical noise. That means that we could get spuriously get change(x) (especially when some unrelated event is triggered).

henrikt-ma commented 1 year ago

(note that I assume the original example was plain wrong, it assumedly should use change(y) not change(x) - that's another reason to use graphical modeling)

Thanks. I fixed the original example to avoid further confusion.

henrikt-ma commented 1 year ago

The second problem is that equality for Real numbers is not legal (technically it is legal inside of functions but pre is not allowed in functions); so change(x) is not legal for a real x.

To me it looks like something only needing clarification that change(x) isn't affected by the equality-on-Real rule. The key here is that the result is discrete-time, and therefore doesn't involve dealing with zero crossing functions, and it's also pointless to forbid since a user-defined function can the the same job (see example below).

Lifting that restriction for discrete Reals given by when-clauses might be possible, but for these new proposed discrete Reals it would be problematic. Basically a Real given by a when-clause is either given a value in the when-clause or equal to pre(x); so any numeric noise is likely to only trigger them when the when-clause is anyway active. For the proposed discrete Real not given by a when-clause it might be given by a system of equations in general and those solutions may have numerical noise. That means that we could get spuriously get change(x) (especially when some unrelated event is triggered).

This is something we already have in Modelica:

model RealChange
  function realChange "Like 'change', but without risk of violating Real equality rules"
    input Real pre_x;
    input Real x;
    output Boolean y = x <> pre_x;
  end realChange;

  discrete Real z(start = 1.0, fixed = true);
  discrete Real x(start = 0.0, fixed = true);
  Real y = x^2;
  Integer count(start = 0, fixed = true);
equation
  when sample(0.05, 1.0) then
    z = pre(z) * 0.8;
  end when;
  when sample(0.0, 0.2) then
    x = y + z/4;
  end when;
  when realChange(pre(x), x) then /* In System Modeler, change(x) is no different. */
    count = pre(count) + 1;
  end when;
  annotation(experiment(StopTime = 10.0));
end RealChange;

I think of this as a case of bad modeling more than a language defect allowing it: We have rather good rules based on variabilities and discrete-valuedness, allowing us to reject many bad models, but not all.

henrikt-ma commented 1 year ago

That exposes one problem, and one might then have to add 'discrete' in lots of places - which is both distracting and counter to the idea for clocked variables that blocks can be used for both normal and clocked cases with the same declaration.

As I've explained before, I think that the general solution to this is outside the scope of this issue: What we need is a way to connect with the state-less functions as if they were blocks. They would then naturally transfer all variabilities just like a function call does.

It may also be that it is involving parts of other models where the modification is not possible.

I am not claiming that this issue will immediately solve all needs to model with discrete-time variables, but it removes the oddity in the language that discrete-time isn't something that can be declared, and it actually solves a practical modeling problem we have encountered.

henrikt-ma commented 1 year ago

There's also the MA-compatible-standards argument. FMI has both discrete input and discrete output variables, see: https://fmi-standard.org/docs/3.0/#table-allowed-variability-causality-combinations

When importing an FMU with such variables into Modelica, it would make sense that the discrete-time variability of these inputs and outputs were clearly declared on the Modelica FMU wrapper block's input/output variables. Correct use of such an imported FMU in Modelica would then have to ensure that connections to the FMU's discrete-time inputs have the required variability, and it becomes possible to take advantage of the discrete-timeness of the outputs.