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

Add variability change for these functions. #3610

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

HansOlsson
Copy link
Collaborator

Based on discussion in modelica/ModelicaStandardLibrary#4495

I created it as a PR in order to have something concrete to discuss.
It is a draft PR since there are a number of issues to resolve (in addition to the obvious issues of an annotation having such an impact):

  • This corresponds to the intent of GenerateEvents, right?
  • No need for a separate annotation/construct for this, right?
  • What to do if the output is a record with both Real and non-Real members?
  • What to do if there are Real and non-Real outputs as separate outputs?

The reason it is a separate item is to make it easier to discuss.
I have slightly changed my mind on these items and will update the PR

So, an Integer output will be discrete-time, but a Real output still non-discrete time (assuming the inputs are non-discrete time).
@HansOlsson HansOlsson added the discussion Indicates that there's a discussion; not clear if bug, enhancement, or working as intended label Nov 12, 2024
Copy link
Collaborator

@henrikt-ma henrikt-ma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would only have a chance of working if GenerateEvents = true was applied recursively also to all function calls in the function body. We need to avoid loopholes like this:

model DiscreteValuedFunctions
  function f
    input Real u;
    output Integer y = integer(u + 0.5); /* Non-discrete-time Integer output. */
    annotation(Inline = false); // Avoid SystemModeler issue.
  end f;

  function g
    input Real u;
    output Integer y = if u < 2 then 0 else f(u);
    annotation(GenerateEvents = true);
  end g;

  Real a = g(time);    // Fine, result with discontinuity at time 2.0
//Integer b = g(time); // Bad: b is assigned a non-discrete-time solution
  annotation(experiment(StopTime = 5.0, Interval = 0.1));
end DiscreteValuedFunctions;

Edit: Improved example.

@HansOlsson
Copy link
Collaborator Author

I think this would only have a chance of working if GenerateEvents = true was applied recursively also to all function calls in the function body.

A good point, and it's possible that such a recursive solution would work, but I would rather propose to rethink this in another way that gives a similar result in this case.

A function with GenerateEvents=true will sort of behave as a block and not a function in terms of events.
The draft proposal realizes that we need to extend this to having non-Real outputs behave as in a block, i.e. be discrete variables.
The new point is that inside such function we also need to have similar restrictions as in a block, so that Integer y = if u < 2 then 0 else f(u) triggers a variability error; as if it had been used in a block.
The solution for that error is then to apply the annotation in called functions, but nothing is directly applying the annotation recursively.

The problem with applying it recursively is that it must be done with care.
In MSL master we have e.g.,: Modelica.Media.R134a.R134a_ph.getPhase_ph

function getPhase_ph "Number of phases by pressure and specific enthalpy"
  extends Modelica.Icons.Function;
  input AbsolutePressure p "Pressure";
  input SpecificEnthalpy h "Specific enthalpy";

  output Integer phase "Number of phases";

protected 
  SaturationProperties sat(psat=p, Tsat=0)
    "Saturation temperature and pressure";
  SI.SpecificEnthalpy hl=bubbleEnthalpy(sat)
    "Liquid enthalpy";
  SI.SpecificEnthalpy hv=dewEnthalpy(sat) "Vapor enthalpy";

algorithm 
  phase := if ((h < hl) or (h > hv) or (p > R134aData.data.FPCRIT)) then 1
     else 2;

  annotation (GenerateEvents=true, Inline=true, Documentation(info="<html>
This function computes the number of phases for R134a depending on the inputs for absolute pressure and specific enthalpy. It makes use of cubic spline functions for liquid and vapor specific enthalpy.
</html>"));
end getPhase_ph;

Naively generating events would imply that bubbleEnthalpy should generate events, which would be a complete mess (and forcing users to write noEvent around it would be odd). However, if we just use the "as a block" rules there is no problem with that function call - so we can leave it as is.

We need to avoid loopholes like this:
...

Totally agree.

@HansOlsson
Copy link
Collaborator Author

I have now added some text for that. We could add an example showing how this requires that some called functions also use the annotation.

\item
The functions \lstinline!pre!, \lstinline!edge!, and \lstinline!change! result in discrete-time expressions.
\item
Expressions in functions behave as though they were discrete-time expressions.
Expressions in functions not having annotation \lstinline!GenerateEvents = true! (\Cref{modelica:GenerateEvents}), behave as though they were discrete-time expressions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need more specification of variability inside functions that do have GenerateEvents = true? We don't want all expressions inside such function bodies to get non-discrete-time variability, right?

Copy link
Collaborator

@henrikt-ma henrikt-ma Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a start…

Suggested change
Expressions in functions not having annotation \lstinline!GenerateEvents = true! (\Cref{modelica:GenerateEvents}), behave as though they were discrete-time expressions.
The variability of expressions inside functions depends upon the \lstinline!GenerateEvents! annotation (\Cref{modelica:GenerateEvents}).
With \lstinline!GenerateEvents = true!, expression variability is determined according to the same rules as outside of functions.
Otherwise, all expressions have at most discrete-time variability (some expressions, such as function inputs and literals have lower variability).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was the intent of the text I wrote (ignoring the misplaced trailing quotes).

The overall structure of that secition is that it specifies when expressions have lower variability. Stating that functions having GenerateEvents=true use the same rule as non-function is thus not needed, and similarly there is no need to say "at most discrete-time" as the less variable cases should already be handled.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, makes sense. The formulation is a bit unusual, though, shouldn't we simply say:

Suggested change
Expressions in functions not having annotation \lstinline!GenerateEvents = true! (\Cref{modelica:GenerateEvents}), behave as though they were discrete-time expressions.
Expressions in functions not having annotation \lstinline!GenerateEvents = true! (\Cref{modelica:GenerateEvents}).

This item would then look better before or after Expressions in a clocked discrete-time partition.

I also think there is a missing \cref{discrete-time-expressions} link to this section from this item in functions.tex, and the item would need to be updated according to whatever semantics we come up with here:

Components of a function will inside the function behave as though they had discrete-time variability.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "behave as though they were discrete-time expressions." have been present for quite some time on some of the entries. I don't want to change them as part of this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And added the other reference.

@henrikt-ma
Copy link
Collaborator

henrikt-ma commented Nov 29, 2024

I think we'll have to rethink the variabilities of function inputs when GenerateEvents = true. Currently, when passing a non-discrete-time Real expression to a function, this expression is considered having evaluable variability inside of the function body.

It would be great if setting GenerateEvents = true on a function would not make it less useful for computing discrete-valued results. Consider this valid model:

model M1
  function f
    input Real u;
    output Boolean y = u > 0.5;
  end f;
  Boolean b = f(1.0);
end M1;

It would seem unfortunate if the following would be a variability error inside the function:

model M2
  function f
    input Real u;
    output Boolean y = u > 0.5; /* Needs discrete-time variability for u. */
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2;

So the question is whether it would be possible to utilize the expression variability of arguments passed to the function, instead of only using the worst-case variability for the type of the input.

@henrikt-ma
Copy link
Collaborator

henrikt-ma commented Nov 29, 2024

I actually believe there is a natural and elegant solution to the problem with M2 above:

model M2Fix
  function f
    input discrete Real u;
    output Boolean y = u > 0.5;
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2Fix;

That is, declared variability of function inputs. It would primarily be useful for functions with GenerateEvents = true, but could also be considered useful for other functions, as a way of making the function ready for GenerateEvents = true in the future.

Some details:

  • Inside functions discrete would simply mean restricting variability to discrete-time.
  • When calling a function having an input declared constant, the passed expression only needs to have evaluable variability, but has to be evaluated. (That is, we use constant as a way to of saying `/* evaluated */ parameter.)
  • Declared variability of function outputs seems somewhat less useful, and potentially confusing in combination with GenerateEvents = false (since variability must be determined from the argument expression variabilities in this case). It would, however, still be useful in the case of output discrete Real y when GenerateEvents = true.

@HansOlsson
Copy link
Collaborator Author

I think we'll have to rethink the variabilities of function inputs when GenerateEvents = true. Currently, when passing a non-discrete-time Real expression to a function, this expression is considered having evaluable variability inside of the function body.

It would be great if setting GenerateEvents = true on a function would not make it less useful for computing discrete-valued results. Consider this valid model:

model M1
  function f
    input Real u;
    output Boolean y = u > 0.5;
  end f;
  Boolean b = f(1.0);
end M1;

It would seem unfortunate if the following would be a variability error inside the function:

model M2
  function f
    input Real u;
    output Boolean y = u > 0.5; /* Needs discrete-time variability for u. */
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2;

So the question is whether it would be possible to utilize the expression variability of arguments passed to the function, instead of only using the worst-case variability for the type of the input.

I see the point, and agree to an extent - and will return to that.

However, it doesn't need discrete-time variability for u if we use the worst-case variability for the input, as u > 0.5 normally generates an event, a non-discrete-time Real.

I can agree that it is inefficient to generate an event for a literal (or parameter) input, but I don't see that it is an error.

It is also a bit confusing that the outputs from functions have two variabilities:

  • Inside the function the Boolean output behave as a discrete-time expression.
  • Outside the function the function-call-result (which corresponds to the output) is a constant expression.

But that isn't a new problem, and I don't see that it generates problems.

In practice the usual implementation of GenerateEvents=true is to simply inline the function (in contrast to the complicated inlining for other functions that need to handle events), and after that the expression variability of the arguments will be used to avoid generating events.
And that's how I see the functions using the variability of the expression argument.

@henrikt-ma
Copy link
Collaborator

henrikt-ma commented Nov 29, 2024

However, it doesn't need discrete-time variability for u if we use the worst-case variability for the input, as u > 0.5 normally generates an event, a non-discrete-time Real.

Sorry, my example was bad. What I meant was rather something like this:

model M2b
  function g
    input Real u;
    output Boolean y = u > 0.5;
  end g;
  function f
    input Real u;
    output Boolean y = g(u); /* Needs discrete-time variability for u. */
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2b;

Again, the elegant solution to this would be the following:

model M2bFix
  function g
    input Real u;
    output Boolean y = u > 0.5;
  end g;
  function f
    input discrete Real u;
    output Boolean y = g(u); /* Fine; u, and hence g(u), have discrete-time variability. */
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2bFix;

@HansOlsson
Copy link
Collaborator Author

However, it doesn't need discrete-time variability for u if we use the worst-case variability for the input, as u > 0.5 normally generates an event, a non-discrete-time Real.

Sorry, my example was bad. What I meant was rather something like this:

model M2b
  function g
    input Real u;
    output Boolean y = u > 0.5;
  end g;
  function f
    input Real u;
    output Boolean y = g(u); /* Needs discrete-time variability for u. */
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2b;

To me the function f would be illegal since g is constructing a non-discrete-time Boolean (regardless of whether the specific function call would work).

Again, the elegant solution to this would be the following:

model M2bFix
  function g
    input Real u;
    output Boolean y = u > 0.5;
  end g;
  function f
    input discrete Real u;
    output Boolean y = g(u); /* Fine; u, and hence g(u), have discrete-time variability. */
    annotation(GenerateEvents = true);
  end f;
  Boolean b = f(1.0);
end M2bFix;

I have a bit of problem seeing the use-case for this; having a function generating events and then declaring that the inputs are discrete mean that function will not generate events. And remember that the simple implementation of GenerateEvents is to inline it, so the case of a function having multiple real inputs - some discrete and some non-discrete - seems quite esoteric.

@henrikt-ma
Copy link
Collaborator

I have a bit of problem seeing the use-case for this; having a function generating events and then declaring that the inputs are discrete mean that function will not generate events. And remember that the simple implementation of GenerateEvents is to inline it, so the case of a function having multiple real inputs - some discrete and some non-discrete - seems quite esoteric.

The point would be to have rules for variability checking that allow for separate checking of the function body and the expressions where the function is called. This is something we (System Modeler) rely heavily upon when GenerateEvents = true is used in combination with InlineAfterIndexReduction = true, since we perform variability checking way ahead of the stage at which inlining due to InlineAfterIndexReduction = true is performed.

Separate variability analysis of the function body also makes the function a much better abstraction, and allows errors to be reported in a simpler way than when the error is a joint effect of the body of the function and the arguments passed.

@HansOlsson
Copy link
Collaborator Author

I have a bit of problem seeing the use-case for this; having a function generating events and then declaring that the inputs are discrete mean that function will not generate events. And remember that the simple implementation of GenerateEvents is to inline it, so the case of a function having multiple real inputs - some discrete and some non-discrete - seems quite esoteric.

The point would be to have rules for variability checking that allow for separate checking of the function body and the expressions where the function is called.

I agree that such a separation is a good thing, but I don't see why it is necessary to declare some inputs as discrete for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Indicates that there's a discussion; not clear if bug, enhancement, or working as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants