ModelicaSpecification icon indicating copy to clipboard operation
ModelicaSpecification copied to clipboard

Annotation noDerivative belongs to function being differentiated

Open henrikt-ma opened this issue 1 year ago • 13 comments

It occurs wrong to me that the noDerivative annotation is attached to the derivative function rather than the function to which the derivative belongs.

To illustrate, I believe this is currently valid:

model NoDerivativeExample
  function f
    input Real x;
    output Real y = if x > 0.0 then 4.0 else 5.0;
    annotation(
    derivative(noDerivative = x) = f_der1,
    derivative = f_der2, /* Derivative is completely "shadowed" by f_der1, but that's not the point here. */
    InlineAfterIndexReduction = true
  );
  end f;

  function f_der1
    input Real x;
    output Real y_der = 0;
  end f_der1;

  function f_der2
    input Real x;
    input Real x_der;
    output Real y_der = 0;
  end f_der2;

  Real x;
equation
  f(x) = 0.0;
  annotation(experiment(StopTime = 1.0));
end NoDerivativeExample;

For symbolic analysis of the model, I'm interested in knowing that f(x) can't be solved for x, and I suppose I can tell this by looking for noDerivative = x in any of the derivative annotations provided for the function. Thus, it seems wrong that this information is attached to the derivative rather than the entire function. This is what I'd like it to look like:

  function f
    input Real x;
    output Real y = if x > 0.0 then 4.0 else 5.0;
    annotation(
    noDerivative = x,
    derivative = f_der2,
    InlineAfterIndexReduction = true
  );
  end f;

Am I missing something, or could we at least open up for allowing this use of noDerivative in addition to the current, misplaced, design?

henrikt-ma avatar Aug 29 '22 09:08 henrikt-ma

Proposed semantics:

A function input component can have the annotation noDerivative = true. When any function input component has noDerivative = true, the derivative annotations for the same function are not allowed to specify their own noDerivative; instead, the input component noDerivative annotations are in effect for every derivative annotation.

Of course, an alternative design would be to allow noDerivative = x as a top level function annotation, instead of having noDerivative = true for the input component x.

henrikt-ma avatar Aug 29 '22 11:08 henrikt-ma

It occurs wrong to me that the noDerivative annotation is attached to the derivative function rather than the function to which the derivative belongs.

To me noDerivative is a sort of hack, and I don't think we should change it as the question of using noDerivative in the indicated way shouldn't really work.

In an ideal world it shouldn't specify more and be a semantic restriction (how is unclear). In one sense the function does something even when the semantic restriction is not satisfied, but the derivative does not make sense. In another sense calling the function without satisfying the semantic restriction may be nonsense.

As an example consider Modelica.Media.Air.ReferenceAir.Air_Utilities.h_props_dT which has derivative(noDerivative=aux) = h_dT_der

The reason we shouldn't differentiate aux in h_props_dT is that it should be called as follows:

function h_dT "Specific enthalpy as function of density and temperature"
  extends Modelica.Icons.Function;
  input SI.Density d "Density";
  input SI.Temperature T "Temperature";
  output SI.SpecificEnthalpy h "Specific enthalpy";
algorithm 
  h := h_props_dT(
          d,
          T,
          Air_Utilities.airBaseProp_dT(d, T));
end h_dT;

So ideally we might replace noDerivative=aux by stating that aux=airBaseProp_dT(d, T) and forbid other calls of h_props_dT; but it is technically complicated.

But based on this I believe the question for this case: "can h_props_dT be solved for aux" doesn't really make sense.

HansOlsson avatar Aug 29 '22 11:08 HansOlsson

First, if noDerivative = x as used in the example shouldn't really work, aren't we missing a way of annotating a function so that it is clear from the outside that the derivatives will not depend on x in a differentiable manner? If there was such an annotation, would its semantics have any practical difference compared to the current noDerivative?

Second, even for the current noDerivative semantics, wouldn't it make more sense to associate this with the function as a whole rather than with a particular derivative annotation?

henrikt-ma avatar Aug 29 '22 12:08 henrikt-ma

First, if noDerivative = x as used in the example shouldn't really work, aren't we missing a way of annotating a function so that it is clear from the outside that the derivatives will not depend on x in a differentiable manner?

Currently I would use zeroDerivative for that. It does require that x has zero derivative in order to compute the derivative, which is necessary if we want to differentiate for index reduction since otherwise the dirac-pulses will be incorrectly handled.

Second, even for the current noDerivative semantics, wouldn't it make more sense to associate this with the function as a whole rather than with a particular derivative annotation?

Possibly, but to me we would still be so far from the goal that it doesn't really make sense to prioritize such a change.

HansOlsson avatar Aug 29 '22 12:08 HansOlsson

First, if noDerivative = x as used in the example shouldn't really work, aren't we missing a way of annotating a function so that it is clear from the outside that the derivatives will not depend on x in a differentiable manner?

Currently I would use zeroDerivative for that. It does require that x has zero derivative in order to compute the derivative, which is necessary if we want to differentiate for index reduction since otherwise the dirac-pulses will be incorrectly handled.

The problem with zeroDerivative is that it is a constraint for the applicability of one of the derivatives; it says nothing about the function in general.

henrikt-ma avatar Aug 29 '22 12:08 henrikt-ma

(On a side note, I encountered this when looking around for noDerivative usage in the MSL: https://github.com/modelica/ModelicaStandardLibrary/issues/4023)

henrikt-ma avatar Aug 29 '22 12:08 henrikt-ma

Please excuse a bit of scope creep instead of opening a new issue for getting my head around noDerivative.

While the example in the specification makes sense to me (the example where the function input with noDerivative is required to be a specific function of another input), I struggle with what seems to be a common usage pattern in the MSL. The simplest variant of this is probably Modelica.Blocks.Interfaces.Adaptors.Functions.state1:

function state1 "Return state (with one derivative)"
  extends Modelica.Icons.Function;
  input Real u[2] "Required values for state and der(s)";
  input Real dummy "Just to have one input signal that should be differentiated to avoid possible problems in the Modelica tool (is not used)";
  output Real s;
algorithm
  s := u[1];
  annotation(derivative(noDerivative = u) = state1der1, InlineAfterIndexReduction = true);
end state1;

function state1der1 "Return 1st derivative (der of state1)"
  extends Modelica.Icons.Function;
  input Real u[2] "Required values for state and der(s)";
  input Real dummy "Just to have one input signal that should be differentiated to avoid possible problems in the Modelica tool (is not used)";
  input Real dummy_der;
  output Real sder1;
algorithm
  sder1 := u[2];
  annotation(InlineAfterIndexReduction = true);
end state1der1;

I understand the description for x ("Required values for state and der(s)") by noting that s = x[1]. That is, I read the description like this:

Vector of {s, der(s)} (where 's' is the "state")

What I can't understand is how to make sense of this when der(s) is the time derivative of s, differentiation in Modelica isn't only about time differentiation. What tells me that the provided state1der1 is going to produce wrong results when computing the partial derivative with respect to the "state"? Are we relying on x[1] actually being a state determined by numeric integration as a guarantee for never differentiating the call partially with respect to this variable? Or is it the intricate interplay with InlineAfterIndexReduction = true that is supposed to guarantee that the noDerivative handling is gone by the time it comes to partial differentiation?

henrikt-ma avatar Aug 31 '22 11:08 henrikt-ma

Are we relying on x[1] actually being a state determined by numeric integration as a guarantee for never differentiating the call partially with respect to this variable?

When using this function it is implicitly assumed that x[1] is "known" so we don't have to solve for it. For the h_dT it is more complicated.

Or is it the intricate interplay with InlineAfterIndexReduction = true that is supposed to guarantee that the noDerivative handling is gone by the time it comes to partial differentiation?

And that as well.

HansOlsson avatar Sep 02 '22 10:09 HansOlsson

We're now talking about two orthogonal matters in this conversation.

  1. The complicated nature of noDerivative. Interested readers are recommended to read this issue for additional background: https://github.com/modelica/ModelicaStandardLibrary/issues/1523 Here's a section I'd like to highlight, and that I don't see reflected by the current specification:

The Move block has the additional feature that the "noDerivative" annotation is used (because the "u" vector argument shall not be differentiated when function position(u) is differentiated). Sven Erik and Hans propose to utilize this feature: If annotation Inline=false is present, then the tool should avoid to inline according to the specification. However, if state selection fails and the function has the "noDerivative" annotation, then this is an indication that a situation as for the Move block is present. It is then proposed to make another try and inline the function before the state selection. However, this approach will not work for the current Move block, because the block has an additional, second dummy argument, "called dummy"). When the Move block is called, actually "time" is given for this dummy argument ("phi = position(u,time)"). I do no longer understand why this dummy argument is needed. Sven Erik and Hans propose to just remove it. If it is removed, then there remains just one argument with the "noDerivative" annotation and the sketched approach can be used.

  1. The observation that the noDerivative information – whatever it means – is a property of the function being differentiated, not just a property of one of the provided derivatives.

henrikt-ma avatar Sep 06 '22 05:09 henrikt-ma

We're now talking about two orthogonal matters in this conversation. ... 2. The observation that the noDerivative information – whatever it means – is a property of the function being differentiated, not just a property of one of the provided derivatives.

That isn't entirely correct.

In one sense it is an assumption(or property) of the function being called, so e.g., h_props_dT assumes that the arguments are d, T, and aux=Air_Utilities.airBaseProp_dT(d, T).

That is assumed for both the function itself and when attempting to differentiate it.

However, if the assumption isn't satisfied the function itself may still "work", but the derivative will not be valid as a derivative for the function call. In that sense it is more a property of the possible differentiation than of the function itself.

HansOlsson avatar Sep 06 '22 13:09 HansOlsson

However, if the assumption isn't satisfied the function itself may still "work", but the derivative will not be valid as a derivative for the function call. In that sense it is more a property of the possible differentiation than of the function itself.

I suppose the ultimate question is whether it could ever make sense to provide two derivatives for the same function, but with different noDerivative characteristics?

henrikt-ma avatar Sep 06 '22 14:09 henrikt-ma

However, if the assumption isn't satisfied the function itself may still "work", but the derivative will not be valid as a derivative for the function call. In that sense it is more a property of the possible differentiation than of the function itself.

I suppose the ultimate question is whether it could ever make sense to provide two derivatives for the same function, but with different noDerivative characteristics?

Currently not. If noDerivative were able to specify the underlying assumption it would have worked (assuming that the assumptions could be verified), similarly as having a function with derivatives with different zeroDerivative.

If we were to associate noDerivative with the function being differentiated it would currently more be a case of stating that it is a non-independent input as in the examples above; but it seems like a quite complicated change for something that is already a hack.

HansOlsson avatar Sep 29 '22 13:09 HansOlsson

I suppose the ultimate question is whether it could ever make sense to provide two derivatives for the same function, but with different noDerivative characteristics?

Currently not. If noDerivative were able to specify the underlying assumption it would have worked (assuming that the assumptions could be verified), similarly as having a function with derivatives with different zeroDerivative.

Well, what a hack…

henrikt-ma avatar Oct 04 '22 05:10 henrikt-ma