Function Annotations

We’ve already discussed Annotations in general. Modelica includes a number of standard annotations that are specifically used in conjunction with functions. These meaning of these annotations is formally defined in the Modelica Specification. In this section, we’ll talk about the three general categories of annotations for functions and provide some discussion of why you need them and how to use them.

Mathematical Annotations

The first class of annotations are ones that provide additional mathematical information about the function. Because functions are written using algorithm sections, it is not generally possible to derive equations for the behavior of the function and many symbolic manipulations are therefore not possible. However, using the annotations in this section allows us to augment the function definition with such information.

derivative

As was saw in the ref:polynomial-evaluation example, there are circumstances where we would like to inform the Modelica compiler how to compute the derivative of a given function. This is done by adding the derivative annotation in the function.

Simple First Derivative

The basic use of the derivative annotation is to specify the name of another Modelica function that computes the first derivative of the function being annotated. For example:

function f
  input Real x;
  input Real y;
  output Real z;
  annotation(derivative=df);
algorithm
  z := // some expression involving x and y
end f;

function df
  input Real x;
  input Real y;
  input Real dx;
  input Real dy;
  output Real dz;
algorithm
  dz := // some expression involving x, y, dx and dy
end df;

Note that the first arguments to the derivative function, df, in this case, are the same as for the original function, f. Those arguments are then followed by the differential versions of the input arguments to the original function. Finally, the output(s) of the derivative function are the differential versions of the output(s) of the original function. It sounds complicated, but hopefully the same code conveys how simple the construction really is.

Given such a Modelica function, the Modelica compiler can use such a function to compute various derivatives, e.g.,

\[\frac{\mathrm{d}f}{\mathrm{d}v}(x,y) = df(x, y, \frac{\partial x}{\partial v}, \frac{\partial y}{\partial v})\]

Insensitivity to Some Arguments

Now consider a case where \(\frac{\partial y}{\partial v}\) is zero. The derivative function will be passed this zero value or an array of zero values, if the argument was an array. That zero value will then be used in several calculations inside the derivative function. Most, if not all, of these will be multiplications and the results of those calculations will therefore be zeros. Those zeros will then be added to the final result, but will have no impact. In other words, there are many calculations that could be skipped because they won’t have any impact on the result.

In such cases, Modelica offers a way to avoid these calculations. If the Modelica compiler knows a priori that one of the differentials is zero, it can check (among the set of derivative annotations) if there are any functions that compute the derivative for that case. These cases are specified using the zeroDerivative argument to the derivative annotation. So, in the case of our example function f, we could add the following annotation:

function f
  input Real x;
  input Real y;
  output Real z;
  annotation(derivative=df, derivative(zeroDerivative=y)=df_onlyx);
algorithm
  z := // some expression involving x and y
end f;

where df_onlyx would then be defined as:

function df_onlyx
  input Real x;
  input Real y;
  input Real dx;
  output Real dz;
algorithm
  dz := // some expression involving x, y, dx
end df_onlyx;

Note that the dy term is not included here. This function is specifically for cases where dy is zero. Because dy doesn’t appear in the arguments, this function includes only those calculations involving dx.

Second Derivatives

There are a few more variations worth covering here. The first is how to specify what the second derivative of a function is. This is done by adding an order argument. Note that a function can have multiple derivative annotations, e.g.,

function f
  input Real x;
  input Real y;
  output Real z;
  annotation(derivative=df, derivative(order=2)=ddf);
algorithm
  z := // some expression involving x and y
end f;

function df
  ...
end df;

function ddf
  input Real x;
  input Real y;
  input Real dx;
  input Real dy;
  input Real ddx;
  input Real ddy;
  output Real ddz;
algorithm
  ddz := // some expression involving x, y, dx, dy,
        // ddx and ddz
end ddf;

Hopefully there are no real surprises here. In order to compute the second derivative, it is necessary to add an additional annotation derivative annotation to the original function, i.e.,

annotation(derivative=df, derivative(order=2)=ddf);

This additional annotation has an additional argument order which indicates which derivative that function computes.

Non-Real Arguments

There is one additional complication to discuss. What if the function has arguments that don’t represent real numbers, e.g.,

function g
  input Real x;
  input Integer y;
  output Real z;
algorithm
  z := // some expression involving x and y
end g;

Here, it makes no sense to take the derivative of this function with respect to the y argument, since it is an integer. Any non-real argument can be ignored when formulating the derivative. So, if we wished to compute the derivative of this function, we would do it as follows:

function g
  input Real x;
  input Integer y;
  output Real z;
  annotation(derivative=dg);
algorithm
  z := // some expression involving x and y
end g;

function dg
  input Real x;
  input Integer y;
  input Real dx;
  output Real dz;
algorithm
  dz := // some expression involving x, y and dx
end dg;

In other words, the differential arguments only apply to arguments that are real.

inverse

During our discussion on Non-Linearities, we showed how the inverse annotation can be used to tell the Modelica compiler how to compute the inverse of a function. The goal of an inverse function is to solve explicitly for one of the current function’s input arguments. As such, the inverse annotation contains an explicit equation involving the input and output variables of the current function, but used in conjunction with another function to explicitly compute one of the input arguments.

For example, for a Modelica function defined as follows:

 function h
   input Real a;
   input Real b;
   output Real c;
   annotation(inverse(b = h_inv_b(a, c)));
algorithm
   c := // some calculation involving a and b
end h;

we see that b can be computed by passing a and c as arguments to the function h_inv_b which would be defined as follows:

 function h_inv_b
   input Real a;
   input Real c;
   output Real b;
algorithm
   b := // some calculation involving a and c
end h_inv_b;

Code Generation

The next class of annotations are related to how function definitions are translated into code for simulation. These annotations allow the model developer to provide hints to the Modelica compiler on how the code generation process should be done.

Inline

The Inline annotation is a hint to the Modelica compiler that the statements in the function should be “inlined”. The value of the annotation is used to suggest whether inlining should be done. The default value (if no Inline annotation is present) is false. The following is a function that uses the Inline annotation:

function SimpleCalculation
  input Real x;
  input Real y;
  output Real z;
  annotation(Inline=true);
algorithm
  z := 2*x-y;
end SimpleCalculation;

Here we see that the Inline annotation suggests that the Modelica compiler should inline the SimpleCalculation function. The function is inlined by replacing invocations of the function with the statements in the function that compute the output result. This is useful for functions that perform very simple calculations. In those cases, the “cost” (in CPU time) of calling the function is on the same order of magnitude as the cost of the work performed by the function. By inlining the function, the cost of the function call can be eliminated while still preserving the purpose of the function.

The Inline function is merely a hint to the Modelica compiler. The compiler is not obligated to inline the function. Also, the compiler’s ability to inline the function will depend on the complexity of the function. It is not necessary possible (or even desirable) to inline a function in general.

LateInline

Much like the Inline annotation, the LateInline function tells the Modelica compiler that it would be more efficient to inline the function. The LateInline annotation is also assigned a Boolean value to specify whether the function should be inlined or not. The difference between the Inline and LateInline annotations is that LateInline indicates that inlining should be performed after symbolic manipulation has been performed. A full discussion of the potential interactions between inlining and other symbolic manipulations is beyond the scope of this book.

It should be noted that the LateInline annotation takes precedence over the Inline annotation if they are both applied to a function, i.e.,

Inline LateInline Interpretation
false false Inline=false
true false Inline=true
false true LateInline=true
true true LateInline=true

External Functions

The final class of annotations are related to functions that are defined as external. Such functions often depend on external include files or libraries. These annotations inform the Modelica compiler of these dependencies and where to locate them.

Include

The Include annotations is used whenever the code generated by a Modelica compiler requires an include statement. Typically this is required when external libraries are being referenced. The value of the Include annotation should be the string that should be inserted into the generated code, e.g.,

annotation(Include="#include \"mydefs.h\"");

Note

The value of the Include annotation is a string. If it included embedded strings, they need to be escaped.

IncludeDirectory

As already discussed, the Include annotation allows include directives to be inserted into generated code. The IncludeDirectory annotation specifies what directory should be searched to find the content specified with the Include annotation.

The value of this annotation is a string. The string can represent a directory or it can be a URL. For example, the default value for the IncludeDirectory annotation is:

IncludeDirectory=modelica://LibraryName/Resources/Include

We’ll explain the meaning of these modelica:// URLs shortly.

Library

The Library annotation is used to specify any compiled libraries that a function might depend on. The value of library can be either a simple string, representing the name of the library, or an array of such strings, i.e.,

annotation(Library="somelib");

or

annotation(Library={"onelib","anotherlib"});

The Modelica compiler will then use this information during the “linking” of the generated code.

LibraryDirectory

We have the same issue with Library that we have with Include. The Library annotation tells us what we need to add, but not where to find it. In this way, the LibraryDirectory annotation serves the same role as the IncludeDirectory annotation. Like the IncludeDirectory annotation, it can also be a URL. It’s default value is:

LibraryDirectory=modelica://LibraryName/Resources/Library