Modelica® Language Specification version 3.6-dev

Chapter 12 Functions

(May 26, 2021)

This chapter describes the Modelica function construct.

12.1 Function Declaration

A Modelica function is a specialized class (section 12.2) using the keyword function. The body of a Modelica function is an algorithm section that contains procedural algorithmic code to be executed when the function is called, or alternatively an external function specifier (section 12.9). Formal parameters are specified using the input keyword, whereas results are denoted using the output keyword. This makes the syntax of function definitions quite close to Modelica class definitions, but using the keyword function instead of class.

[The structure of a typical function declaration is sketched by the following schematic function example:

function 𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑛𝑎𝑚𝑒
  input  TypeI1 in1;
  input  TypeI2 in2;
  input  TypeI3 in3 = 𝑑𝑒𝑓𝑎𝑢𝑙𝑡𝐸𝑥𝑝𝑟1 "Comment" annotation();
  
  output TypeO1 out1;
  output TypeO2 out2 = 𝑑𝑒𝑓𝑎𝑢𝑙𝑡𝐸𝑥𝑝𝑟2;
  
protected
  local variables
  
algorithm
  statements
  
end 𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑛𝑎𝑚𝑒;

]

Optional explicit default values can be associated with any input or output formal parameter through binding equations. Comment strings and annotations can be given for any formal parameter declaration, as usual in Modelica declarations.

[Explicit default values are shown for the third input parameter and the second output parameter in the example above.]

[All internal parts of a function are optional; i.e., the following is also a legal function:

function 𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑛𝑎𝑚𝑒
end 𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑛𝑎𝑚𝑒;

]

12.1.1 Ordering of Formal Parameters

The relative ordering between input formal parameter declarations is significant since that determines the matching between actual arguments and formal parameters at function calls with positional parameter passing. Likewise, the relative ordering between the declarations of the outputs is significant since that determines the matching with receiving variables at function calls of functions with multiple results. However, the declarations of the inputs and outputs can be intermixed as long as these internal orderings are preserved.

[Mixing declarations in this way is not recommended, however, since it makes the code hard to read.]

[Example:

function 𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑛𝑎𝑚𝑒
  output TypeO1 out1; // Intermixed declarations of inputs and outputs
  input TypeI1 in1; // not recommended since code becomes hard to read
  input TypeI2 in2;
  
  output TypeO2 out2;
  input TypeI3 in3;
  
end 𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛𝑛𝑎𝑚𝑒;

]

12.1.2 Function return-statements

The return-statement terminates the current function call, see section 12.4. It can only be used in an algorithm section of a function. It has the following form:

return;

[Example: (Note that this could alternatively use break:)

function findValue "Returns position of val or 0 if not found"
  input Integer x[:];
  input Integer val;
  output Integer index;
algorithm
  for i in 1:size(x,1) loop
    if x[i] == val then
      index := i;
      return;
    end if;
  end for;
  index := 0;
  return;
end findValue;

]

12.1.3 Inheritance of Functions

It is allowed for a function to inherit and/or modify another function following the usual rules for inheritance of classes (chapter 7).

[For example, it is possible to modify and extend a function class to add default values for input variables.]

12.2 Function as a Specialized Class

The function concept in Modelica is a specialized class (section 4.6).

[The syntax and semantics of a function have many similarities to those of the block specialized class. A function has many of the properties of a general class, e.g. being able to inherit other functions, or to redeclare or modify elements of a function declaration.]

Modelica functions have the following restrictions compared to a general Modelica class:

  • Only input and output formal parameters are allowed in the function’s public variable section.

  • Input formal parameters are read-only after being bound to the actual arguments or default values, i.e., they shall not be assigned values in the body of the function.

  • A function shall not be used in connections, shall not have equations, shall not have initial algorithms.

  • A function can have at most one algorithm section or one external function interface (not both), which, if present, is the body of the function.

  • A function may only contain components of the specialized classes type, record, operator record, and function; and it must not contain, e.g., model, block, operator or connector components.

  • A function may not contain components of type Clock.

  • The elements of a function shall not have prefixes inner, or outer.

  • A function may have zero or one external function interface, which, if present, is the external definition of the function.

  • For a function to be called in a simulation model, the function shall not be partial, and the output variables must be assigned inside the function either in binding equations or in an algorithm section, or have an external function interface as its body, or be defined as a function partial derivative. The output variables of a function should be computed.

    [It is a quality of implementation how much analysis a tool performs in order to determine if the output variables are computed.]

    A function cannot contain calls to the Modelica built-in operators der, initial, terminal, sample, pre, edge, change, reinit, delay, cardinality, inStream, actualStream, to the operators of the built-in package Connections, to the operators defined in chapter 16 and chapter 17, and is not allowed to contain when-statements.

  • The dimension sizes not declared with colon (:) of each array result or array local variable (i.e., a non-input component) of a function must be either given by the input formal parameters, or given by constant or parameter expressions, or by expressions containing combinations of those (section 12.4.4).

  • For initialization of local variables of a function see section 12.4.4).

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

Modelica functions have the following enhancements compared to a general Modelica class:

  • Functions can be called, section 12.4.

    • The calls can use a mix of positional and named arguments, see section 12.4.1.

    • Instances of functions have a special meaning, see section 12.4.2.

    • The lookup of the function class to be called is extended, see section 5.3.2.

  • A function can be recursive.

  • A formal parameter or local variable may be initialized through a binding (=) of a default value in its declaration, see section 12.4.4. Using assignment (:=) is deprecated. If a non-input component in the function uses a record class that contain one or more binding equations they are viewed as initialization of those component of the record component.

  • A function is dynamically instantiated when it is called rather than being statically instantiated by an instance declaration, which is the case for other kinds of classes.

  • A function may have an external function interface specifier as its body.

  • A function may have a return-statement in its algorithm section body.

  • A function allows dimension sizes declared with colon (:) to be resized for non-input array variables, see section 12.4.5.

  • A function may be defined in a short function definition to be a function partial derivative.

12.3 Pure Modelica Functions

Modelica functions are normally pure which makes it easy for humans to reason about the code since they behave as mathematical functions, and possible for compilers to optimize.

  • Pure Modelica functions always give the same output values or errors for the same input values and only the output values influence the simulation result, i.e. is seen as equivalent to a mathematical map from input values to output values. Some input values may map to errors. Pure functions are thus allowed to fail by calling assert, or ModelicaError in C code, or dividing by zero. Such errors will only be reported when and if the function is called. Pure Modelica functions are not assumed to be thread-safe.

  • A Modelica function which does not have the pure function properties is impure.

The declaration of functions follow these rules:

  • Functions defined in Modelica (non-external) are normally assumed to be pure (the exception is the deprecated case below), if they are impure they shall be marked with the impure keyword. They can be explicitly marked as pure.

    [However, since functions as default are pure it is not recommended to explicitly declare them as pure.]

  • External functions must be explicitly declared with pure or impure.

  • If a function is declared as impure any function extending from it shall be declared as impure.

  • A deprecated semantics is that external functions (and functions defined in Modelica directly or indirectly calling them) without pure or impure keyword are assumed to be impure, but without any restriction on calling them. Except for the function Modelica.Utilities.Streams.print, diagnostics must be given if called in a simulation model.

Calls of pure functions used inside expression may be skipped if the resulting expression will not depend on the possible returned value; ignoring the possibility of the function generating an error.

A call to a function with no declared outputs is assumed to have desired side-effects or assertion checks.

[A tool shall thus not remove such function calls, with exception of non-triggered assert calls. A pure function, used in an expression or used with a non-empty left hand side, need not be called if the output from the function call do not mathematically influence the simulation result, even if errors would be generated if it were called.]

[Comment 1: This property enables writing declarative specifications using Modelica. It also makes it possible for Modelica compilers to freely perform algebraic manipulation of expressions containing function calls while still preserving their semantics. For example, a tool may use common subexpression elimination to call a pure function just once, if it is called several times with identical input arguments. However, since functions may fail we can e.g. only move a common function call from inside a loop to outside the loop if the loop is run at least once.]

[Comment 2: The Modelica translator is responsible for maintaining this property for pure non-external functions. Regarding external functions, the external function implementor is responsible. Note that external functions can have side-effects as long as they do not influence the internal Modelica simulation state, e.g. caching variables for performance or printing trace output to a log file.]

With the prefix keyword impure it is stated that a Modelica function is impure and it is only allowed to call such a function from within:

  • Another function marked with the prefix impure.

  • A when-equation.

  • A when-statement.

  • pure(impureFunctionCall()) – which allows calling impure functions in any pure context.

  • Initial equations and initial algorithms.

  • Binding equations for components declared as parameter – which is seen as syntactic sugar for having a parameter with fixed=false and the binding as an initial equation.

    [Thus, evaluation of the same function call at a later time during simulation is not guaranteed to result in the same value as when the parameter was initialized, seemingly breaking the declaration equation.]

  • Binding equations for external objects.

For initial equations, initial algorithms, and bindings it is an error if the function calls are part of systems of equations and thus have to be called multiple times.

[A tool is not allowed to perform any optimizations on function calls to an impure function, e.g., reordering calls from different statements in an algorithm or common subexpression elimination is not allowed.]

By section 6.6, it follows that an impure function can only be passed as argument to a function formal parameter of impure type. A function having a formal function parameter that is impure must be marked pure or impure.

[Comment: The semantics are undefined if the function call of an impure function is part of an algebraic loop.]

[Example:

function evaluateLinear // pure function
  input Real a0;
  input Real a1;
  input Real x;
  output Real y;
algorithm
  y := a0 + a1*x;
end evaluateLinear;
impure function receiveRealSignal // impure function
  input HardwareDriverID id;
  output Real y;
external "C"
  y = receiveSignal(id);
end receiveRealSignal;

Examples of allowed optimizations of pure functions:

model M // Assume sin, cos, asin are pure functions with normal derivatives.
  input Real x[2];
  input Real w;
  Real y[2] = [cos(w), sin(w); -sin(w), cos(w)] * x;
  Real z[2] = der(y);
  Real a = 0 * asin(w);
end M;

A tool only needs to generate one call of the pure function cos(w) in the model M – a single call used for both the two elements of the matrix, as well as for the derivative of that matrix. A tool may also skip the possible error for asin(w) and assume that a is zero.

Examples of restrictions on optimizing pure functions:

  Real x =
    if noEvent(abs(x)) < 1 then
      asin(x) // Cannot move asin(x) out of if-branch.
    else
      0;
algorithm
  assertCheck(p, T); // Must call function
algorithm
  if b then
    y := 2 * someOtherFunction(x);
  end if;
  y := y + asin(x);
  y := y + someOtherFunction(x);
  // Cannot evaluate someOtherFunction(x) before asin(x) - unless b is true
  // The reason is that asin(x) may fail and someOtherFunction may hang,
  // and it might be possible to recover from this error.

]

12.4 Function Call

Function classes and record constructors (section 12.6) and enumeration type conversions (section 4.8.5.3) can be called as described in this section.

12.4.1 Positional or Named Input Arguments of Functions

A function call has optional positional arguments followed by zero, one or more named arguments, such as

f(3.5, 5.76, arg3=5, arg6=8.3);

The formal syntax of a function call (simplified by removing reduction expression, section 10.3.4.1):

primary :
   component-reference function-call-args
function-call-args :
   "(" [ function-arguments ] ")"
function-arguments :
   function-argument [ "," function-arguments]
   | named-arguments
named-arguments: named-argument [ "," named-arguments ]
named-argument: IDENT "=" function-argument
function-argument : function-partial-application | expression

The interpretation of a function call is as follows: First, a list of unfilled slots is created for all formal input parameters. If there are N positional arguments, they are placed in the first N slots, where the order of the parameters is given by the order of the component declarations in the function definition. Next, for each named argument identifier = expression, the identifier is used to determine the corresponding slot. The value of the argument is placed in the slot, filling it (it is an error if this slot is already filled). When all arguments have been processed, the slots that are still unfilled are filled with the corresponding default value of the function definition. The default values may depend on other inputs (these dependencies must be acyclical in the function) – the values for those other inputs will then be substituted into the default values (this process may be repeated if the default value for that input depend on another input). The default values for inputs shall not depend on non-input variables in the function. The list of filled slots is used as the argument list for the call (it is an error if any unfilled slots still remain).

Special purpose operators with function syntax defined in the specification shall not be called with named arguments, unless otherwise noted.

The type of each argument must agree with the type of the corresponding parameter, except where the standard type coercion, section 10.6.13, can be used to make the types agree. (See also section 12.4.6 on applying scalar functions to arrays.)

[Example: Assume a function RealToString is defined as follows to convert a Real number to a String:

function RealToString
  input Real number;
  input Real precision = 6 "number of significantdigits";
  input Real length = 0 "minimum length of field";
  output String string "number as string";
  
end RealToString;

Then the following applications are equivalent:

RealToString(2.0);
RealToString(2.0, 6, 0);
RealToString(2.0, 6);
RealToString(2.0, precision=6);
RealToString(2.0, length=0);
RealToString(2.0, 6, precision=6); // error: slot is used twice

]

12.4.2 Functional Input Arguments to Functions

A functional input argument to a function is an argument of function type. The declared type of such an input formal parameter in a function can be the type-specifier of a partial function that has no replaceable elements. It cannot be the type-specifier of a record or enumeration (i.e., record constructor functions and enumeration type conversions are not allowed in this context). Such an input formal parameter of function type can also have an optional functional default value.

[Example:

function quadrature "Integrate function y=integrand(x) from x1 to x2"
  input Real x1;
  input Real x2;
  input Integrand integrand; // Integrand is a partial function,
  see below
  // With default: input Integrand integrand =
  Modelica.Math.sin;
  output Real integral;
algorithm
  integral :=(x2-x1)*(integrand(x1) + integrand(x2))/2;
end quadrature;
partial function Integrand
  input Real u;
  output Real y;
end Integrand;

]

A functional argument can be provided in one of the following forms to be passed to a scalar formal parameter of function type in a function call:

  1. a)

    as a function type-specifier (Parabola example below),

  2. b)

    as a function partial application (section 12.4.2.1 below),

  3. c)

    as a function that is a component (i.e., a formal parameter of function type of the enclosing function),

  4. d)

    as a function partial application of a function that is a component (example in section 12.4.2.1 below).

In all cases the provided function must be function-compatible (definition 6.8) with the corresponding formal parameter of function type.

[Example: A function as a positional input argument according to case (a):

function Parabola
  extends Integrand;
algorithm
  y := x*x;
end Parabola;
area = quadrature(0, 1, Parabola);

The quadrature2 example below uses a function integrand that is a component as input argument according to case (c):

function quadrature2 "Integrate function y=integrand(x) from x1 to x2"
  input Real x1;
  input Real x2;
  input Integrand integrand; // Integrand is a partial function type
  output Real integral;
algorithm
  integral := quadrature(x1, (x1+x2)/2, integrand)+  quadrature((x1+x2)/2, x2, integrand);
end quadrature2;

]

12.4.2.1 Function Partial Application

A function partial application is similar to a function call with certain formal parameters bound to expressions, the specific rules are specified in this section and are not identical to the ones for function call in section 12.4.1. A function partial application returns a partially evaluated function that is also a function, with the remaining not bound formal parameters still present in the same order as in the original function declaration. A function partial application is specified by the function keyword followed by a function call to func_name giving named formal parameter associations for the formal parameters to be bound, e.g.:

function func_name(, formal_parameter_name = expr, )

[Note that the keyword function in a function partial application differentiates the syntax from a normal function call where some parameters have been left out, and instead supplied via default values.]

The function created by the function partial application acts as the original function but with the bound formal input parameters(s) removed, i.e., they cannot be supplied arguments at function call. The binding occurs when the partially evaluated function is created. A partially evaluated function is function-compatible (definition 6.8) with the same function where all bound arguments are removed.

[Thus, for checking function type compatibility, bound formal parameters are ignored.]

[Example: Function partial application as argument, positional argument passing, according to case (b) above:

model Test
  parameter Integer N;
  Real area;
algorithm
  area := 0;
  for i in 1:N loop
    area := area + quadrature(0, 1, function  Sine(A=2, w=i*time));
  end for;
end Test;
function Sine "y = Sine(x,A,w)"
  extends Integrand;
  input Real A;
  input Real w;
algorithm
  y:=A*Modelica.Math.sin(w*x);
end Sine;

Call with function partial application as named input argument:

area := area + quadrature(0, 1, integrand = function Sine(A=2, w=i*time));

]

[Example: Function types are matching after removing the bound arguments A and w in a function partial application:

function Sine2 "y = Sine2(A,w,x)"
  input Real A;
  input Real w;
  input Real x; // Note: x is now last in argument list.
  output Real y;
algorithm
  y:=A*Modelica.Math.sin(w*x);
end Sine2;
area = quadrature(0, 1, integrand = function  Sine2(A=2, w=3));

The partially evaluated Sine2 has only one argument: x – and is thus type compatible with Integrand.]

[Example: Function partial application of a function that is a component, according to case (d) above:

partial function SurfaceIntegrand
  input Real x;
  input Real y;
  output Real z;
end SurfaceIntegrand;
function quadratureOnce
  input Real x;
  input Real y1;
  input Real y2;
  input SurfaceIntegrand integrand;
  output Real z;
algorithm
  z := quadrature(y1, y2, function  integrand(y=x));
  // This is according to case (d) and needs to bind the 2nd argument
end quadratureOnce;
function surfaceQuadrature
  input Real x1;
  input Real x2;
  input Real y1;
  input Real y2;
  input SurfaceIntegrand integrand;
  output Real integral;
algorithm
  integral := quadrature(x1, x2,
  function quadratureOnce(y1=y1, y2=y2, integrand=integrand));
  // Case (b) and (c)
end surfaceQuadrature;

]

12.4.3 Output Formal Parameters of Functions

A function may have more than one output component, corresponding to multiple return values. The only way to use more than the first return value of such a function is to make the function call the right hand side of an equation or assignment. In this case, the left hand side of the equation or assignment shall contain a list of component references within parentheses:

(out1, out2, out3) = f();

The component references are associated with the output components according to their position in the list. Thus output component i is set equal to, or assigned to, component reference i in the list, where the order of the output components is given by the order of the component declarations in the function definition. The type of each component reference in the list must agree with the type of the corresponding output component.

A function application may be used as expression whose value and type is given by the value and type of the first output component, if at least one return result is provided.

It is possible to omit left hand side component references and/or truncate the left hand side list in order to discard outputs from a function call.

[Optimizations to avoid computation of unused output results can be automatically deduced by an optimizing compiler.]

[Example: Function eigen to compute eigenvalues and optionally eigenvectors may be called in the following ways:

ev = eigen(A); // calculate eigenvalues
x = isStable(eigen(A)); // used in an expression
(ev, vr) = eigen(A) // calculate eigenvectors
(ev,vr,vl) = eigen(A) // and also left eigenvectors
(ev,,vl) = eigen(A) // no right eigenvectors

The function may be defined as:

function eigen "calculate eigenvalues and optionally eigenvectors"
  input Real A[:, size(A,1)];
  output Real eigenValues[size(A,1),2];
  output Real rightEigenVectors[size(A,1),size(A,1)];
  output Real leftEigenVectors [size(A,1),size(A,1)];
algorithm
  // The output variables are computed separately (and not, e.g., by one
  // call of a Fortran function) in order that an optimizing compiler can
  // remove unnecessary computations, if one or more output arguments are
  // missing
  //   compute eigenvalues
  //   compute right eigenvectors using the computed eigenvalues
  //   compute left eigenvectors using the computed eigenvalues
end eigen;

]

The only permissible use of an expression in the form of a list of expressions in parentheses, is when it is used as the left hand side of an equation or assignment where the right hand side is an application of a function.

[Example: The following are illegal:

(x+1, 3.0, z/y) = f(1.0, 2.0); // Not a list of component references.
(x, y, z) + (u, v, w) // Not LHS of suitable eqn/assignment.

]

12.4.4 Initialization and Binding Equations of Components in Functions

Components in a function can be divided into three groups:

  • Public components which are input formal parameters.

  • Public components which are output formal parameters.

  • Protected components which are local variables, parameters, or constants.

When a function is called, components of the function do not have start-attributes. However, a binding equation (= expression) with an expression may be present for a component.

[Declaration assignments of the form := expression are deprecated, but otherwise identical to binding equations.]

A binding equation for a non-input component initializes the component to this expression at the start of every function invocation (before executing the algorithm section or calling the external function). These bindings must be executed in an order where a variable is not used before its binding equations has been executed; it is an error if no such order exists (i.e. the binding must be acyclic).

Binding equations can only be used for components of a function. If no binding equation is given for a non-input component the variable is uninitialized (except for record components where modifiers may also initialize that component). It is an error to use (or return) an uninitialized variable in a function. Binding equations for input formal parameters are interpreted as default arguments, as described in section 12.4.1.

[It is recommended to check for use of uninitialized variables statically – if this is not possible a warning is recommended combined with a run-time check.]

[The properties of components in functions described in this section are also briefly described in section 12.2.]

12.4.5 Flexible Array Sizes and Resizing of Arrays in Functions

[Flexible setting of array dimension sizes of arrays in functions is also briefly described in section 12.2.]

A dimension size not specified with colon (:) for a non-input array component of a function must be given by the inputs or be constant.

[Example:

function joinThreeVectors
  input Real v1[:],v2[:],v3[:];
  output Real vres[size(v1,1)+size(v2,1)+size(v3,1)];
algorithm
  vres := cat (1,v1,v2,v3);
end joinThreeVectors;

]

A non-input array component declared in a function with a dimension size specified by colon (:) and no binding equation, can change size according to these special rules:

  • Prior to execution of the function algorithm the dimension size is zero.

  • The entire array (without any subscripts) may be assigned with a corresponding array with arbitrary dimension size (the array variable is re-sized).

These rules also apply if the array component is an element of a record component in a function.

[Example: A function to collect the positive elements in a vector:

function collectPositive
  input Real x[:];
  output Real xpos[:];
algorithm
  for i in 1 : size(x, 1) loop
    if x[i] > 0 then
      xpos := cat(1, xpos, x[i:i]);
    end if;
  end for;
end collectPositive;

]

12.4.6 Scalar Functions Applied to Array Arguments

Functions with one scalar return value can be applied to arrays element-wise, e.g. if A is a vector of reals, then sin(A) is a vector where each element is the result of applying the function sin to the corresponding element in A. Only function classes that are transitively non-replaceable (section 6.3.1 and section 7.1.4) may be called vectorized.

Consider the expression f(arg1, , argn), an application of the function f to the arguments arg1, …, argn. Potential vectorization of this call is defined as follows. For each passed argument, the type of the argument is checked against the type of the corresponding formal parameter of the function:

  1. 1.

    If the types match, nothing is done.

  2. 2.

    If the types do not match, and a type conversion can be applied, it is applied. Continue with step 1.

  3. 3.

    If the types do not match, and no type conversion is applicable, the passed argument type is checked to see if it is an n-dimensional array of the formal parameter type. If it is not, the function call is invalid. If it is, we call this a foreach argument.

  4. 4.

    For all foreach arguments, the number and sizes of dimensions must match. If they do not match, the function call is invalid.

  5. 5.

    If no foreach argument exists, the function is applied in the normal fashion, and the result has the type specified by the function definition.

  6. 6.

    The result of the function call expression is an n-dimensional array e with the same dimension sizes as the foreach arguments. Each element e[i, , j] is the result of applying f to arguments constructed from the original arguments in the following way:

    • If the argument is not a foreach argument, it is used as-is.

    • If the argument is a foreach argument, the element at index [i, , j] is used.

If more than one argument is an array, all of them have to be the same size, and they are traversed in parallel.

[Example:

sin({a, b, c}) = {sin(a), sin(b), sin(c)} // argument is a vector
sin([a, b, c]) = [sin(a), sin(b), sin(c)] // argument may be a matrix
atan2({a, b, c}, {d, e, f}) = {atan2(a, d), atan2(b, e), atan2(c, f)}

This works even if the function is declared to take an array as one of its arguments. If pval is defined as a function that takes one argument that is a Real vector and returns a Real, then it can be used with an actual argument which is a two-dimensional array (a vector of vectors). The result type in this case will be a vector of Real.

pval([1,2;3,4]) = [pval([1,2]); pval([3,4])]
sin([1,2;3,4]) = [sin({1,2}); sin({3,4})]
  = [sin(1), sin(2); sin(3), sin(4)]
function Add
  input Real e1, e2;
  output Real sum1;
algorithm
  sum1 := e1 + e2;
end Add;

Add(1, [1,2,3]) adds one to each of the elements of the second argument giving the result [2,3,4]. However, it is illegal to write 1 + [1,2,3], because the rules for the built-in operators are more restrictive.]

12.4.7 Empty Function Calls

An empty function call is a call that does not return any results.

[An empty call is of limited use in Modelica since a function call without results does not contribute to the simulation, but it is useful to check assertions and in certain cases for desired side-effects, see section 12.3.]

An empty call can occur either as a kind of “null equation” or “null statement”.

[Example: The empty calls to eigen() are examples of a “null equation” and a “null statement”:

equation
  Modelica.Math.Matrices.eigen(A); // Empty function call as an equation
algorithm
  Modelica.Math.Matrices.eigen(A); // Empty function call as a statement

]

12.5 Built-in Functions

There are basically four groups of built-in functions in Modelica:

  • Intrinsic mathematical and conversion functions, see section 3.7.1.

  • Derivative and special operators with function syntax, see section 3.7.4.

  • Event-related operators with function syntax, see section 3.7.5.

  • Built-in array functions, see section 10.3.

    Note that when the specification references a function having the name of a built-in function it references the built-in function, not a user-defined function having the same name.

12.6 Record Constructor Functions

Whenever a record is defined, a record constructor function with the same name and in the same scope as the record class is implicitly defined according to the following rules:

The declaration of the record is partially flattened including inheritance, modifications, redeclarations, and expansion of all names referring to declarations outside of the scope of the record to their fully qualified names.

[The partial flattening is performed in order to remove potentially conflicting import-clauses in the record constructor function due to flattening the inheritance tree.]

All record elements (i.e., components and local class definitions) of the partially flattened record declaration are used as declarations in the record constructor function with the following exceptions:

  • Component declarations which do not allow a modification (such as final parameter Real) are declared as protected components in the record constructor function.

  • Prefixes (constant, parameter, final, discrete, …) of the remaining record components are removed.

  • The prefix input is added to the public components of the record constructor function.

An instance of the record is declared as output parameter using a name not appearing in the record, together with a modification. In the modification, all input parameters are used to set the corresponding record variables.

A record constructor can only be called if the referenced record class is found in the global scope, and thus cannot be modified.

[This allows constructing an instance of a record, with an optional modification, at all places where a function call is allowed.

Examples:

  record Complex "Complex number"
    Real re "real part";
    Real im "imaginary part";
  end Complex;
  function add
    input Complex u, v;
    output Complex w(re=u.re + v.re, im=u.im+v.re);
  end add;
  Complex c1, c2;
equation
  c2 = add(c1, Complex(sin(time), cos(time));

In the following example, a convenient data sheet library of components is built up:

package Motors
  record MotorData "Data sheet of a motor"
    parameter Real inertia;
    parameter Real nominalTorque;
    parameter Real maxTorque;
    parameter Real maxSpeed;
  end MotorData;
  model Motor "Motor model" // using the generic MotorData
    MotorData data;
    
  equation
    
  end Motor;
  record MotorI123 = MotorData( // data of a specific motor
    inertia = 0.001,
    nominalTorque = 10,
    maxTorque = 20,
    maxSpeed = 3600) "Data sheet of motor I123";
  record MotorI145 = MotorData( // data of another specific motor
    inertia = 0.0015,
    nominalTorque = 15,
    maxTorque = 22,
    maxSpeed = 3600) "Data sheet of motor I145";
end Motors
model Robot
  import Motors.*;
  Motor motor1(data = MotorI123()); // just refer to data sheet
  Motor motor2(data = MotorI123(inertia = 0.0012));
  // data can still be modified (if no final declaration in record)
  Motor motor3(data = MotorI145());
  
end Robot;

Example showing most of the situations, which may occur for the implicit record constructor function creation. With the following record definitions:

package Demo;
  record Record1;
    parameter Real r0 = 0;
  end Record1;
  record Record2
    import Modelica.Math.*;
    extends Record1;
    final constant Real c1 = 2.0;
    constant Real c2;
    parameter Integer n1 = 5;
    parameter Integer n2;
    parameter Real r1 "comment";
    parameter Real r2 = sin(c1);
    final parameter Real r3 = cos(r2);
    Real r4;
    Real r5 = 5.0;
    Real r6[n1];
    Real r7[n2];
  end Record2;
end Demo;

The following record constructor functions are implicitly defined (the name of the output, given in italic below, is not defined; it should be chosen to not cause any conflict):

package Demo;
  function Record1
    input Real r0 = 0;
    output Record1 𝑟𝑒𝑠𝑢𝑙𝑡(r0 = r0);
  end Record1;
  function Record2
    input Real r0 = 0;
    input Real c2;
    input Integer n1 = 5;
    input Integer n2;
    input Real r1 "comment"; // the comment also copied from record
    input Real r2 = Modelica.Math.sin(c1);
    input Real r4;
    input Real r5 = 5.0;
    input Real r6[n1];
    input Real r7[n2];
    output Record2 𝑟𝑒𝑠𝑢𝑙𝑡(r0=r0,c2=c2,n1=n1,n2=n2,r1=r1,r2=r2,r4=r4,r5=r5,r6=r6,r7=r7);
  protected
    final constant Real c1 = 2.0; // referenced from r2
    final parameter Real r3 = Modelica.Math.cos(r2);
  end Record2;
end Demo;

and can be applied in the following way

Demo.Record2 r1 = Demo.Record2(r0=1, c2=2, n1=2, n2=3, r1=1, r2=2,r4=5, r5=5, r6={1,2}, r7={1,2,3});
Demo.Record2 r2 = Demo.Record2(1,2,2,3,1,2,5,5,{1,2},{1,2,3});
parameter Demo.Record2 r3 = Demo.Record2(c2=2, n2=1, r1=1,r4=4, r6=1:5, r7={1});

The above example is only used to show the different variants appearing with prefixes, but it is not very meaningful, because it is simpler to just use a direct modifier.]

12.6.1 Casting to Record

A constructor of a record R can be used to cast an instance m of a model, block, connector class M to a value of type R, provided that for each component defined in R (that do not have a default value) there is also a public component defined in M with identical name and type. A nested record component of R is handled as follows, if the corresponding component of M is a model/block/connector a nested record constructor is called – otherwise the component is used directly; and the resulting call/component is used as argument to the record constructor R. If the corresponding component of R in M is a conditional component, it is an error. The instance m is given as single (un-named) argument to the record constructor of R. The interpretation is that R(m) is replaced by a record constructor of type R where all public components of M that are present in R are assigned to the corresponding components of R. The record cast can be used in vectorized form according to section 12.4.6.

[The problem if R would be a conditional component is that the corresponding binding would be illegal since it is not a connect-equation.]

[The record cast operation is uniquely distinguished from a record constructor call, because an argument of the record constructor cannot be a model, block or connector instance.]

[Example:

connector Flange
  Real phi;
  flow Real tau;
end Flange;
model Model1
  Real m1;
  Boolean b1;
  Flange flange;
end Model1;
model Model2
  Real r1;
  Real r2;
  Integer i2
  Pin p1, p2;
  Model1 sub1;
  protected
  Integer i1;
  
end Model2;
record MyFlange
  Real tau;
end MyFlange;
record MyRecord1
  Boolean b1;
  MyFlange flange;
end MyRecord1;
record MyRecord2
  Real r1;
  Integer i2;
  MyRecord1 sub1;
end MyRecord2;
model Model
  Model2 s1;
  Model2 s2[2];
  MyRecord2 rec1 = MyRecord2(s1);
  MyRecord2 rec2[2] = MyRecord2(s2);
  
end Model;
// Model is conceptually mapped to
model ModelExpanded
  Model2 s1;
  Model2 s2[2];
  MyRecord2 rec1 = MyRecord2(r1=s1.r1, i2=s1.i2,
  sub1 = MyRecord1(b1=s1.sub1.b1,
  flange = MyFlange(tau=s1.sub1.flange.tau));
  MyRecord2 rec2[2] = {MyRecord2(r1=s2[1].r1, i2=s2[1].i2,
  sub1 = MyRecord1(b1=s2[1].sub1.b1,
  flange = MyFlange(tau=s1[1].sub1.flange.tau)),
  MyRecord2(r1=s2[2].r1, i2=s2[2].i2,
  sub1 = MyRecord1(b1=s2[2].sub1.b1,
  flange = MyFlange(tau=s2[2].sub1.flange.tau)};
  
end ModelExpanded;

]

12.7 Derivatives and Inverses of Functions

The annotations listed below are related to differentiation and closed-form inverses of functions. A function declaration can have derivative annotations specifying derivative functions or preferably, for a function written in Modelica, use the smoothOrder annotation to indicate that the tool can construct the derivative function automatically. Partial derivatives are not provided via annotations, but using a certain type of short function definition described in section 12.7.2.

Annotation Description Details
smoothOrder Function smoothness guarantee Annotation 12.1
derivative Provide function derivative Annotation 12.2
inverse Provide closed-form inverses Annotation 12.3
Annotation 12.1 smoothOrder
"smoothOrder" "=" UNSIGNED-NUMBER
"smoothOrder"
   "("
      "normallyConstant" "=" IDENT
      { "," "normallyConstant" "=" IDENT }
   ")"
   "=" UNSIGNED-NUMBER
  • This annotation has only an effect within a function declaration.

    smoothOrder defines the number of differentiations of the function, in order that all of the differentiated outputs are continuous provided all input arguments and their derivatives up to order smoothOrder are continuous.

    [This means that the function is at least CsmoothOrder. smoothOrder = 1 means that the function can be differentiated at least once in order that all output arguments are still continuous, provided the input arguments are continuous. If a tool needs the derivative of a function, e.g. for index reduction or to compute an analytic Jacobian, the function can be differentiated analytically at least smoothOrder times.]

    The optional argument normallyConstant of smoothOrder defines that the function argument IDENT is usually constant.

    [A tool might check whether the actual argument to IDENT is a parameter expression at the place where the function is called. If this is the case, the derivative of the function might be constructed under the assumption that the corresponding argument is constant, to enhance efficiency. Typically, a tool would generate at most two different derivative functions of a function: One, under the assumption that all normallyConstant arguments are actually constant. And one, under the assumption that all input arguments are time varying. Based on the actual arguments of the function call either of the two derivative functions is used.

    This annotation is used by many functions of the Modelica.Fluid library, such as Modelica.Fluid.Dissipation.PressureLoss.StraightPipe.dp_laminar_DP, since geometric arguments to these functions are usually constant.]

Annotation 12.2 derivative
"derivative" [ derivative-constraints ] "=" name
derivative-constraints :
   "(" derivative-constraint { "," derivative-constraint } ")"
derivative-constraint :
   "order" = UNSIGNED-NUMBER
   | "noDerivative" = IDENT
   | "zeroDerivative" = IDENT
  • This annotation has only an effect within a function declaration.

    The derivative annotation can influence simulation time and accuracy, can be applied to both functions written in Modelica and to external functions, and may be used several times for the same function declaration.

    Each use of the derivative annotation points to another derivative-function that expresses a derivative of the declared function, and the annotation can state that it is only valid under certain restrictions on the input arguments. These restrictions are defined using the optional attributes order, noDerivative, and zeroDerivative. The order may be specified at most once for each derivative annotation, must be at least 1, and defaults to 1. Specifying order is only considered a restriction if order>1.

    For details abouts using the derivative annotation, see section 12.7.1.

Annotation 12.3 inverse
"inverse" "(" function-inverse { "," function-inverse } ")"
function-inverse :
   IDENT "=" type-specifier function-call-args""
  • A function with one output formal parameter may have one or more inverse annotations to define inverses of this function.

    For details abouts using the inverse annotation, see section 12.7.3.

12.7.1 Using the Derivative Annotation

The given derivative-function must be a valid derivative if the derivative annotation restrictions are satisfied, and can thus be used to compute the derivative in those cases. There may be multiple restrictions on the derivative, in which case they must all be satisfied. The restrictions also imply that some derivatives of some inputs are excluded from the call of the derivative (since they are not necessary). When a function supplies multiple derivative-functions subject to different restrictions, the first one that can be used (i.e., satisfying the restrictions) will be used for each call.

[This means that the most restrictive derivatives should be written first.]

[Example: The following model illustrates the requirement that a provided derivative must be valid. That fder is a valid derivative of f means that it can be used safely to compute x2 by numeric integration: the function value, x1, will up to numerical precision be matched by the integral of the derivative, x2.

function f
  input Real x;
  output Real y;
  annotation(derivative = fder);
  external "C";
end f;
model M
  input Real u;
  Real x1 "Directly from function";
  Real x2 "Integrated from derivative";
equation
  x1 = f(u);
  der(x2) = der(x1);
initial equation
  x2 = x1;
end M;

Note that tools are not required to use the provided derivative, and might solve the equations completely without numeric integration.]

[Example: Use of order to specify a second order derivative:

function foo0 annotation(derivative = foo1);
end foo0;
function foo1 annotation(derivative(order=2) = foo2);
end foo1;
function foo2 end foo2;

]

The inputs to the derivative function of order 1 are constructed as follows:

  • First are all inputs to the original function, and after all them we will in order append one derivative for each input containing reals. These common inputs must have the same name, type, and declaration order for the function and its derivative.

  • The outputs are constructed by starting with an empty list and then in order appending one derivative for each output containing reals. The outputs must have the same type and declaration order for the function and its derivative.

If the Modelica function call is a nth derivative (n1), i.e. this function call has been derived from an (n-1)th derivative by differentiation inside the tool, an annotation(derivative(order=n+1) = ), specifies the (n+1)th derivative, and the (n+1)th derivative call is constructed as follows:

  • The input arguments are appended with the (n+1)th derivative, which are constructed in order from the nth order derivatives.

  • The output arguments are similar to the output argument for the nth derivative, but each output is one higher in derivative order. The outputs must have the same type and declaration order for the function and its derivative.

[The restriction that only the result of differentiation can use higher-order derivatives ensures that the derivatives x, der_x, … are in fact derivatives of each other. Without that restriction we would have both der(x) and x_der as inputs (or perform advanced tests to verify that they are the same).]

[Example: Given the declarations

function foo0
  
  input Real x;
  input Boolean linear;
  input ;
  output Real y;
  
  annotation(derivative = foo1);
end foo0;
function foo1
  
  input Real x;
  input Boolean linear;
  input ;
  input Real der_x;
  
  output Real der_y;
  
  annotation(derivative(order=2) = foo2);
end foo1;
function foo2
  
  input Real x;
  input Boolean linear;
  input ;
  input Real der_x;
  ;
  input Real der_2_x;
  
  output Real der_2_y;
  

the equation

(,y(t),) =foo0(,x(t),b,)
implies that:
(,dy(t)dt,) =foo1(,x(t),b,,,dx(t)dt,)
(,d2y(t)dt2,) =foo2(,x(t),b,,dx(t)dt,,,d2x(t)dt2,)

]

An input or output to the function may be any simple type (Real, Boolean, Integer, String and enumeration types) or a record. For a record containing Real values, the corresponding derivative uses a derivative record that only contains the real-predefined types and sub-records containing reals (handled recursively) from the original record. When using smoothOrder, then the derivative record is automatically constructed. The function must have at least one input containing reals. The output list of the derivative function shall not be empty.

[Example: Here is one example use case with records mixing Real and non-Real as inputs and outputs:

record ThermodynamicState "Thermodynamic state"
  SpecificEnthalpy h "Specific enthalpy";
  AbsolutePressure p "Pressure";
  Integer phase(min=1, max=2, start=1);
end ThermodynamicState;
record ThermoDynamicState_der "Derivative"
  SpecificEnthalpyDerivative h "Specific enthalphy derivative";
  PressureDerivative p "Pressure derivative";
  // Integer input is skipped
end ThermodynamicState_der;
function density
  input ThermodynamicState state "Thermodynamic state";
  output Density d "Density";
algorithm
  
  annotation(derivative = density_der);
end density;
function density_der
  input ThermodynamicState state "Thermodynamic state";
  input ThermodynamicState_der state_der;
  output DensityDerivative d "Density derivative";
algorithm
  
end density_der;
function setState_ph
  input Pressure p;
  input SpecificEnthalpy h;
  input Integer phase = 0;
  output ThermodynamicState state;
algorithm
  
  annotation(derivative = setState_ph_der);
end setState_ph;
function setState_ph_der
  input Pressure p;
  input SpecificEnthalpy h;
  input Integer phase;
  input PressureDerivative p_der;
  input SpecificEnthalpyDerivative h_der;
  output ThermodynamicState_der state_der;
algorithm
  
end setState_ph_der;
ThermodynamicState state1 = setState_ph(p=, h=, phase=);
Density rho1 = density(state1);
DensityDerivative d_rho1 = der(rho1);
Density rho2 = density(setState_ph(p=, h=, phase=));
DensityDerivative d_rho2 = der(rho2);

]

  • zeroDerivative = 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟1 { , zeroDerivative = 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟2 }

The derivative function is only valid if 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟1 (and 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟2 etc.) are independent of the variables the function call is differentiated with respect to (i.e. that the derivative of 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟1 is zero). The derivative of 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟1 (and 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟2 etc.) are excluded from the argument list of the derivative-function. If the derivative-function also specifies a derivative the common variables should have consistent zeroDerivative.

[Assume that function f takes a matrix and a scalar. Since the matrix argument is usually a parameter expression it is then useful to define the function as follows (the additional derivative = fGeneralDer is optional and can be used when the derivative of the matrix or offset is non-zero). Note that the derivative annotation of fDer must specify zeroDerivative for both y and offset as below, but the derivative annotation of fGeneralDer shall not have zeroDerivative for either of them (it may specify zeroDerivative for x_der, y_der, or offset_der).

function f "Simple table lookup"
  input Real x;
  input Real y[:, 2];
  input Real offset "Shortened to o below";
  output Real z;
algorithm
  
  annotation(derivative(zeroDerivative=y, zeroDerivative=offset) = fDer,
             derivative = fGeneralDer);
end f;
function fDer "Derivative of simple table lookup"
  input Real x;
  input Real y[:, 2];
  input Real offset;
  input Real x_der;
  output Real z_der;
algorithm
  
  annotation(
    derivative(zeroDerivative=y, zeroDerivative=offset, order=2) = fDer2);
end fDer;
function fDer2 "Second derivative of simple table lookup"
  input Real x;
  input Real y[:, 2];
  input Real offset;
  input Real x_der;
  input Real x_der2;
  output Real z_der2;
algorithm
  
end fDer2;
function fGeneralDer "Derivative of table lookup taking
into account varying tables"
  input Real x;
  input Real y[:, 2];
  input Real offset;
  input Real x_der;
  input Real y_der[:, 2];
  input Real offset_der;
  output Real z_der;
algorithm
  
  //annotation(derivative(order=2) = fGeneralDer2);
end fGeneralDer;

In the example above zeroDerivative=y and zeroDerivative=offset imply that

ddtf(x(t),y(t),o(t)) =fxdxdt+fydydt+fododt
=fxdxdt+fy0+fo0
=fxdxdt
=fDerdxdt

]

  • noDerivative = 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟1

The derivative of 𝑖𝑛𝑝𝑢𝑡𝑉𝑎𝑟1 is excluded from the argument list of the derivative-function. This relies on assumptions on the arguments to the function; and the function should document these assumptions (it is not always straightforward to verify them). In many cases even the undifferentiated function will only behave correctly under these assumptions.

The inputs excluded using zeroDerivative or noDerivative may be of any type (including types not containing reals).

[Assume that function fg is defined as a composition f(x, g(x)). When differentiating f it is useful to give the derivative under the assumption that the second argument is defined in this way:

function fg
  input Real x;
  output Real z;
algorithm
  z := f(x, g(x));
end fg;
function f
  input Real x;
  input Real y;
  output Real z;
algorithm
  
  annotation(derivative(noDerivative=y) = h);
end f;
function h
  input Real x;
  input Real y;
  input Real x_der;
  output Real z_der;
algorithm
  
end h;

This is useful if g represents the major computational effort of fg.

Therefore h indirectly includes the derivative with respect to y as follows:

ddtfg(x(t)) =ddtf(x(t),g(x(t)))
=fxdxdt+fygxdxdt
=(fx+fygx)dxdt
=h(x(t),y(t)))dxdt

]

12.7.2 Partial Derivatives of Functions

A class defined as:

IDENT "=" der "(" name "," IDENT { "," IDENT } ")" comment

is the partial derivative of a function, and may only be used as declarations of functions.

The semantics is that a function (and only a function) can be specified in this form, defining that it is the partial derivative of the function to the right of the equal sign (looked up in the same way as a short class definition, and the looked up name must be a function), and partially differentiated with respect to each IDENT in order (starting from the first one). The IDENT must be Real inputs to the function.

The comment allows a user to comment the function (in the info-layer and as one-line description, and as icon).

[Example: The specific enthalpy can be computed from a Gibbs-function as follows:

function Gibbs
  input Real p, T;
  output Real g;
algorithm
  
end Gibbs;
function Gibbs_T = der(Gibbs, T);
function specificEnthalpy
  input Real p, T;
  output Real h;
algorithm
  h := Gibbs(p, T) - T * Gibbs_T(p, T);
end specificEnthalpy;

]

12.7.3 Using the Inverse Annotation

If a function f1 with one output formal parameter y can be restricted to an informally defined domain and codomain, such that the mapping of the input formal parameter uk to y is bijective for any fixed assignment to the other input formal parameters in the domain (see examples below), then it can be given an inverse annotation to provide an explicit inverse f2 to this mapping, provided that the function is only applied on this domain:

The inverse annotation takes the following form in a function declaration:

function f1
  input A1 u1;
  
  input T1 uk;
  
  input Am um = am;
  
  input An un;
  output T2 y;
algorithm
  
  annotation(inverse(uk = f2(, y, ));
end f1;

In addition to y, the formal call to f2 in the annotation shall also pass the other formal parameters (excluding uk) needed determine the inverse, see below. The function f2 must be an actual inverse, meaning that if uk is calculated as uk=f2(,y,), then the equality y=f1(,uk,) is satisfied up to a certain precision, for all values of the input arguments of f2(,y,) in the range and informal domain of f1.

More than one inverse can be defined within the same inverse annotation, separated by commas:

annotation(inverse(uk = f2(, y, ), ui = f3(, y, ), ));

Function f1 can have any number and types of formal parameters with and without default value. The restriction is that the number of unknown variables (see section 4.7) in the output formal parameter of both f1 and f2 must be the same and that f2 should have a union of output and formal parameters that is the same or a subset of that union for f1, but the order of the formal parameters may be permuted.

[Example: Inverse function with same union of formal parameters:

function h_pTX
  input Real p "pressure";
  input Real T "temperature";
  input Real X[:] "mass fractions";
  output Real h "specific enthalpy";
algorithm
  
  annotation(inverse(T = T_phX(p, h, X)));
end h_pTX;
function T_phX
  input Real p "pressure";
  input Real h "specific enthalpy";
  input Real X[:] "mass fractions";
  output Real T "temperature";
algorithm
  
end T_phX;

]

The subset case is useful if f1 computes the inverse of f2 within a region, or up to a certain tolerance. Then, f1 may specify f2 as inverse with fewer arguments, skipping the arguments for tolerance and/or the region.

[Example: Inverse function with subset of formal parameters:

function inv_sine
  input Real x;
  input Real angleOrig;
  output Real angle;
  // Finds sine(angle) = x with angle closest to angleOrig.
algorithm
  
  annotation(inverse(x = sine(angle)));
end inv_sine;
function sine
  input Real angle;
  output Real x;
algorithm
  x := sin(angle);
  // Note: No inverse.
end sine;

]

Tools are not expected to verify the bijectiveness requirement, meaning that it is the user’s responsibility to ensure that this requirement is fulfilled, and that tools can rely on the requirement as an assumption for symbolic manipulations when an inverse function is provided.

There is no guarantee that a provided inverse will be used, and no rule for at which stage of symbolic processing it could be applied. Inlining a function means that the possibility to apply provided inverses is lost. Hence, the recommended inlining annotations – if any – for use together with the inverse-annotation is either Inline = false or LateInline = true.

[Example: If an inverse is provided, but the injectiveness part of the bijectiveness requirement is not fulfilled, this may introduce additional ambiguity to the solution of equations with multiple solutions. Consider the following invalid use of the inverse annotation:

model NotInjective
  function square
    input Real x;
    output Real y = x^2;
    annotation(inverse(x = sqrt(y))); // Invalid!
  end square;
  parameter Real y0 = -1.0;
  Real y(start = y0, fixed = true);
  Real x(start = sign(y0) * sqrt(abs(y0))); // Good guess with same sign as y.
equation
  der(y) = -y;
  square(x) = abs(y); // Expecting continuous solution for x.
end NotInjective;

That the parameter y0 may have any sign means the sign of x cannot be restricted in the informal domain of square, and hence that the injectiveness requirement cannot be fulfilled. Without the inverse annotation, the nonlinear equation in x and y has an ambiguity, but it is generally expected that this is handled so that a continuous solution for x is obtained, meaning that it will keep the same sign as y throughout the simulation. The additional ambiguity introduced by the inverse annotation is that if the provided inverse is used to solve the nonlinear equation instead of using a generic nonlinear equation solver based on local search, then the solution with positive sign is always obtained. The lack of guarantees that a provided inverse will be used thus implies a worse ambiguity than what was present in the model before introducing the inverse annotation.]

[Example: If an inverse is provided, but the surjectiveness part of the bijectiveness requirement is not fulfilled, this may introduce an invalid solution to equations that do not have a solution at all. Consider the following invalid use of the inverse annotation:

model NotSurjective
  function cube
    input Real x;
    output Real y = x ^ 3;
  end cube;
  function cbrtPos "Cubic root of positive number"
    input Real y;
    output Real x;
  algorithm
    assert(y > 0, "Argument must be positive.");
    x := exp(log(y) / 3);
    annotation(inverse(y = cube(x))); // Invalid!
  end cbrtPos;
  Real x = 0.5 + sin(time);
  Real y;
equation
  cbrtPos(y) = x; // Calling cbrtPos requires y > 0.
  annotation(experiment(StopTime = 10.0));
end NotSurjective;

As the value of x varies over the interval [-1, 1], but the range of cbrtPos is only (0,), the informal codomain of cbrtPos cannot be restricted such that the surjectiveness is fulfilled. A valid solution to the equation in x and y must satisfy y>0, and when no inverse annotation is given, a violation will be detected by a nonlinear solver applied directly to the equation. When the (invalid) inverse provided by the inverse annotation is used, however, the equation gets transformed into

  y = cube(x);

where the requirement y>0 can no longer be detected, resulting in a simulation result that does not fulfill the original model equations.]

12.8 Function Inlining and Event Generation

The annotations listed below affect inlining of functions and the related topic of event generation inside functions.

Annotation Description Details
Inline Inline function Annotation 12.4
LateInline Inline after all symbolic transformations Annotation 12.5
InlineAfterIndexReduction Inline after index reduction Annotation 12.6
GenerateEvents Generate events for zero crossings in function Annotation 12.7

Inlining a function makes the statements of the function body accessible to symbolic operations, potentially leading to expression simplifications and more efficient solution of equations. At the same time, another important consequence of inlining a function is that any annotations for derivatives or inverses are lost. Hence, one needs to find the right balance between inlining too early (loss of provided derivatives and inverses) and too late (earlier stages of symbolic processing cannot benefit from symbolic simplifications).

Annotation 12.4 Inline
"Inline" "=" ( false | true )
  • Has only an effect within a function declaration.

    If Inline = true, the model developer proposes to inline the function. This means, that the body of the function is included at all places where the function is called.

    If Inline = false, the model developer proposes to not inline the function.

    [Inline = true is for example used in Modelica.Mechanics.MultiBody.Frames and in functions of Modelica.Media to have no overhead for function calls such as resolving a vector in a different coordinate system and at the same time the function can be analytically differentiated, e.g., for index reduction needed for mechanical systems.]

Annotation 12.5 LateInline
"LateInline" "=" ( false | true )
  • Has only an effect within a function declaration.

    If LateInline = true, the model developer proposes to inline the function after all symbolic transformations have been performed.

    [Late inlining is especially useful for differentiation and inversion of functions; for efficiency reasons it is then useful to replace all function calls with identical input arguments by one function call, before the inlining.]

    If LateInline = false, the model developer proposes to not inline the function after symbolic transformations have been performed.

    Inline = true, LateInline = false is identical to Inline = true.

    Inline = true, LateInline = true is identical to LateInline = true.

    Inline = false, LateInline = true is identical to LateInline = true.

    [This annotation is for example used in Modelica.Media.Water.IF97_Utilities.T_props_ph to provide in combination with common subexpression elimination the automatic caching of function calls. Furthermore, it is used in order that a tool is able to propagate specific enthalpy over connectors in the Modelica.Fluid library.]

Annotation 12.6 InlineAfterIndexReduction
"InlineAfterIndexReduction" "=" ( false | true )
  • Has only an effect within a function declaration.

    If true, the model developer proposes to inline the function after the function is differentiated for index reduction, and before any other symbolic transformations are performed. This annotation cannot be combined with annotations Inline and LateInline.

Annotation 12.7 GenerateEvents
"GenerateEvents" "=" ( false | true )
  • Has only an effect within a function declaration

    If GenerateEvents = true, the model developer proposes that crossing functions in the function should generate events (one possibility of doing this is to inline the function and generate events for the inlined function).

12.9 External Function Interface

Here, the word function is used to refer to an arbitrary external routine, whether or not the routine has a return value or returns its result via output parameters (or both). The Modelica external function call interface provides the following:

  • Support for external functions written in C (specifically C89) and FORTRAN 77. Other languages, e.g. C++ and Fortran 90, may be supported in the future, and provided the function is link-compatible with C89 or FORTRAN 77 it can be written in any language.

  • Mapping of argument types from Modelica to the target language and back.

  • Natural type conversion rules in the sense that there is a mapping from Modelica to standard libraries of the target language.

  • Handling arbitrary parameter order for the external function.

  • Passing arrays to and from external functions where the dimension sizes are passed as explicit integer parameters.

  • Handling of external function parameters which are used both for input and output, by passing an output that has a binding equation to the external function.

    [Binding equations are executed prior to calling the external function.]

The format of an external function declaration is as follows.

function IDENT description-string
  { component-clause ";" }
  [ protected { component-clause ";" } ]
external [ language-specification ]
  [ external-function-call ]
    [annotation ] ";"
  [ annotation ";" ]
end IDENT;

Components in the public part of an external function declaration shall be declared either as input or output.

[This is just as for any other function. The components in the protected part allow local variables for temporary storage to be declared.]

The language-specification must currently be one of "builtin" (deprecated), "C", "C" (for one of the specific C standards like C89, C99, and C11 – specifying that it relies on the C standard library of that version) or "FORTRAN 77". Unless the external language is specified, it is assumed to be "C".

[The intended use of e.g. C99 is to detect if the user tries to link with a C99-function using a C89 compiler.]

The deprecated "builtin" specification is only used for the elementary mathematical functions described in section 3.7.3. The external function call mechanism for "builtin" functions is implementation-defined.

[Typically, for functions from the standard C library, the prototype of the function is provided but no Library annotation. Currently, there are no other builtin functions defined in Modelica.]

[Example:

package Modelica
  package Math
    function sin
      input Real x;
      output Real y;
    external "builtin"
      y = sin(x);
    end sin;
  end Math;
end Modelica;
model UserModel
  parameter Real p = Modelica.Math.sin(2);
end UserModel;

]

The external-function-call specification allows functions whose prototypes do not match the default assumptions as defined below to be called. It also gives the name used to call the external function. If the external call is not given explicitly, this name is assumed to be the same as the Modelica name.

The only permissible kinds of expressions in the argument list are component references, scalar constants, and the function size applied to an array and a constant dimension number. The annotations are used to pass additional information to the compiler when necessary.

A component reference to a component that is part of an input or output is treated the same way as a top-level input or output in the external call.

If the function has annotation(Include="includeDirective"), section 12.9.4 it is assumed that it contains an actual prototype and no prototype shall be automatically generated. In that case the input argument pointers shall be const pointers (indicating that they do not modify the inputs), and non-const pointers are deprecated. The optional external-function-call is still used for determining the name of the function, and order of arguments, as described below.

12.9.1 Argument type Mapping

The arguments of the external function are declared in the same order as in the Modelica declaration, unless specified otherwise in an explicit external function call. Protected variables (i.e. temporaries) are passed in the same way as outputs, whereas constants and size calls are passed as inputs.

12.9.1.1 Simple Types

Arguments of simple types are by default mapped as follows for C:

Modelica C
Input Output
Real double double *
Integer int int *
Boolean int int *
String const char * const char **
Enumeration type int int *

An exception is made when the argument is of the form size(, ). In this case the corresponding C type is size_t.

Strings are nul-terminated (i.e., terminated by ’\0’) to facilitate calling of C functions. The valid return values for an external function returning a String are:

  • A string given as String input to the external function.

  • A pointer to a C string literal.

  • A pointer returned by one of the string allocation functions in section 12.9.6.2.

[The reason why it is not allowed to return a string allocated with, for instance, malloc is that there is no transfer of ownership when a string is returned from the external function. The external code would remain the owner of such a string, and would be responsible for eventually releasing the memory at some point. Consequently, the Modelica simulation environment would not be able to assume that only its own string deallocation routines could invalidate any of the strings returned by external functions.]

Boolean values are mapped to C such that false in Modelica is 0 in C and true in Modelica is 1 in C. If the returned value from C is 0 it is treated as false in Modelica; otherwise as true.

[It is recommended that the C function should interpret any non-zero value as true.]

Arguments of simple types are by default mapped as follows for FORTRAN 77:

Modelica FORTRAN 77
Input Output
Real DOUBLE PRECISION DOUBLE PRECISION
Integer INTEGER INTEGER
Boolean LOGICAL LOGICAL
String Special Not available
Enumeration type INTEGER INTEGER

Sending string literals to FORTRAN 77 subroutines/functions is supported for Lapack/Blas-routines, and the strings are nul-terminated for compatibility with C. Returning strings from FORTRAN 77 subroutines/functions is currently not supported.

Enumeration types used as arguments are mapped to type int when calling an external C function, and to type INTEGER when calling an external FORTRAN function. The ith enumeration literal is mapped to integer value i, starting at 1.

Return values are mapped to enumeration types analogously: integer value 1 is mapped to the first enumeration literal, 2 to the second, etc. Returning a value which does not map to an existing enumeration literal for the specified enumeration type is an error.

12.9.1.2 Arrays

Unless an explicit function call is present in the external-clause, an array is passed by its address followed by n arguments of type size_t with the corresponding array dimension sizes, where n is the number of dimensions.

[The type size_t is a C unsigned integer type.]

Arrays are by default stored in row-major order when calling C functions and in column-major order when calling FORTRAN 77 functions. These defaults can be overridden by the arrayLayout annotation. See the example below.

The table below shows the mapping of an array argument in the absence of an explicit external function call when calling a C function. The type T is allowed to be any of the simple types which can be passed to C as defined in section 12.9.1.1 or a record type as defined in section 12.9.1.3 and it is mapped to the type T as defined in these sections for input arguments. Array inputs to C-functions are const-pointers, indicating that the arrays shall not be changed.

Modelica C
Input Output
T[𝑑𝑖𝑚1] const T *, size_t 𝑑𝑖𝑚1 T *, size_t 𝑑𝑖𝑚1
T[𝑑𝑖𝑚1, 𝑑𝑖𝑚2] const T *, size_t 𝑑𝑖𝑚1, size_t 𝑑𝑖𝑚2 T *, size_t 𝑑𝑖𝑚1, size_t 𝑑𝑖𝑚2
T[, 𝑑𝑖𝑚n] const T *, , size_t 𝑑𝑖𝑚n T *, , size_t 𝑑𝑖𝑚n

The method used to pass array arguments to FORTRAN 77 functions in the absence of an explicit external function call is similar to the one defined above for C: first the address of the array, then the dimension sizes as integers. See the table below. The type T is allowed to be any of the simple types which can be passed to FORTRAN 77 as defined in section 12.9.1.1 and it is mapped to the type T as defined in that section.

Modelica FORTRAN 77
Input and output
T[𝑑𝑖𝑚1] T, INTEGER 𝑑𝑖𝑚1
T[𝑑𝑖𝑚1, 𝑑𝑖𝑚2] T, INTEGER 𝑑𝑖𝑚1, INTEGER 𝑑𝑖𝑚2
T[𝑑𝑖𝑚1, , 𝑑𝑖𝑚n] T, INTEGER 𝑑𝑖𝑚1, , INTEGER 𝑑𝑖𝑚n

[Example: The following two examples illustrate the default mapping of array arguments to external C and FORTRAN 77 functions.

function foo
  input Real a[:,:,:];
  output Real x;
  external;
end foo;

The corresponding C prototype is as follows:

double foo(const double *, size_t, size_t, size_t);

If the external function is written in FORTRAN 77, i.e.:

function foo
  input Real a[:,:,:];
  output Real x;
external "FORTRAN 77";
end foo;

the default assumptions correspond to a FORTRAN 77 function defined as follows:

FUNCTION foo(a, d1, d2, d3)
  DOUBLE PRECISION(d1, d2, d3) a
  INTEGER                      d1
  INTEGER                      d2
  INTEGER                      d3
  DOUBLE PRECISION             foo
  
END

]

When an explicit call to the external function is present, the array and the sizes of its dimensions must be passed explicitly.

[Example: This example shows how arrays can be passed explicitly to an external FORTRAN 77 function when the default assumptions are unsuitable.

function foo
  input Real x[:];
  input Real y[size(x,1),:];
  input Integer i;
  output Real u1[size(y,1)];
  output Integer u2[size(y,2)];
external "FORTRAN 77"
  myfoo(x, y, size(x,1), size(y,2), u1, i, u2);
end foo;

The corresponding FORTRAN 77 subroutine would be declared as follows:

SUBROUTINE myfoo(x, y, n, m, u1, i, u2)
  DOUBLE PRECISION(n) x
  DOUBLE PRECISION(n,m) y
  INTEGER n
  INTEGER m
  DOUBLE PRECISION(n) u1
  INTEGER i
  DOUBLE PRECISION(m) u2
  
END

This example shows how to pass an array in column major order to a C function.

function fie
  input Real[:,:] a;
  output Real b;
  external;
  annotation(arrayLayout = "columnMajor");
end fie;

This corresponds to the following C prototype:

double fie(const double *, size_t, size_t);

]

12.9.1.3 Records

Mapping of record types is only supported for C. A Modelica record class that contains simple types, other record elements, is mapped as follows:

  • The record class is represented by a struct in C.

  • Each element of the Modelica record is mapped to its corresponding C representation.

  • The elements of the Modelica record class are declared in the same order in the C struct.

  • Arrays cannot be mapped.

Records are passed by reference (i.e. a pointer to the record is being passed).

[Example:

record R
  Real x;
  Real z;
end R;

is mapped to:

struct R {
  double x;
  double z;
};

]

12.9.2 Return Type Mapping

If there is a single output parameter and no explicit call of the external function, or if there is an explicit external call in the form of an equation, in which case the LHS must be one of the output parameters, the external routine is assumed to be a value-returning function. Otherwise the external function is assumed not to return anything; i.e., it is really a procedure or, in C, a void-function.

Mapping of the return type of functions is performed as indicated in the table below. Storage for arrays as return values is allocated by the calling routine, so the dimensions of the returned array are fixed at call time. See section 12.9.1.1 regarding returning of String values.

[In the case of an external function not returning anything, argument type mapping according to section 12.9.1.1 is performed in the absence of any explicit external function call.]

Return types are by default mapped as follows for C and FORTRAN 77:

Modelica C FORTRAN 77
Real double DOUBLE PRECISION
Integer int INTEGER
Boolean int LOGICAL
String const char* Not allowed
T[𝑑𝑖𝑚1, , 𝑑𝑖𝑚n] Not allowed Not allowed
Enumeration type int INTEGER
Record See section 12.9.1.3 Not allowed

The element type T of an array can be any simple type as defined in section 12.9.1.1 or, for C, a record type is returned as a value of the record type defined in section 12.9.1.3.

12.9.3 Aliasing

Any potential aliasing in the external function is the responsibility of the tool and not the user. An external function is not allowed to internally change the inputs (even if they are restored before the end of the function).

[Example:

function foo
  input Real x;
  input Real y;
  output Real z = x;
external "FORTRAN 77"
  myfoo(x, y, z);
end foo;

The following Modelica function:

function f
  input Real a;
  output Real b;
algorithm
  b := foo(a, a);
  b := foo(b, 2 * b);
end f;

can on most systems be transformed into the following C function:

double f(double a) {
  extern void myfoo_(double*, double*, double*);
  double b, temp1, temp2;
  myfoo_(&a, &a, &b);
  temp1 = 2 * b;
  temp2 = b;
  myfoo_(&b, &temp1, &temp2);
  return temp2;
}

The reason for not allowing the external function to change the inputs is to ensure that inputs can be stored in static memory and to avoid superfluous copying (especially of matrices). If the routine does not satisfy the requirements the interface must copy the input argument to a temporary. This is rare but occurs e.g. in dormlq in some Lapack implementations. In those special cases the writer of the external interface have to copy the input to a temporary. If the first input was changed internally in myfoo the designer of the interface would have to change the interface function foo to:

function foo
  input Real x;
  protected Real xtemp = x; // Temporary used because myfoo changes its input
  public input Real y;
  output Real z;
external "FORTRAN 77"
  myfoo(xtemp, y, z);
end foo;

Note that we discuss input arguments for Fortran-routines even though FORTRAN 77 does not formally have input arguments and forbid aliasing between any pair of arguments to a function (Section 15.9.3.6 of X3J3/90.4). For the few (if any) FORTRAN 77 compilers that strictly follow the standard and are unable to handle aliasing between input variables the tool must transform the first call of foo into:

temp1 = a; /* Temporary to avoid aliasing */
myfoo_(&a, &temp1, &b);

The use of the function foo in Modelica is uninfluenced by these considerations.]

12.9.4 Annotations for External Libraries and Include Files

The following annotations are useful in the context of calling external functions from Modelica, and they should occur on the external-clause and no other standard annotations should occur on the external-clause. They can all specify either a scalar value or an array of values as indicated below for the Library annotation:

  • The annotation(Library="libraryName"), used by the linker to include the library file where the compiled external function is available.

  • The annotation(Library=("libraryName1", "libraryName2")), used by the linker to include the library files where the compiled external function is available and additional libraries used to implement it. For shared libraries it is recommended to include all non-system libraries in this list.

  • The annotation(Include="includeDirective"), used to include source files needed for calling the external function in the code generated by the Modelica compiler. The included code should be valid C89 code.

    [Examples of files that can be included are header files or source files that contain the functions referenced in the external function declaration.]

  • The annotation(IncludeDirectory="modelica://ModelicaLibraryName/Resources/Include"), used to specify a location for header files. The preceding one is the default and need not be specified; but another location could be specified by using an URI name for the include directory, see section 13.5.

  • The annotation(LibraryDirectory="modelica://ModelicaLibraryName/Resources/Library"), used to specify a location for library files. The preceding one is the default and need not be specified; but another location could be specified by using an URI name for the library directory, see section 13.5. Different versions of one object library can be provided (e.g. for Windows and for Linux) by providing a platform directory below the LibraryDirectory. If no platform directory is present, the object library must be present in the LibraryDirectory. The following platform names are standardized:

    • "win32" (Microsoft Windows 32 bit)

    • "win64" (Microsoft Windows 64 bit)

    • "linux32" (Linux Intel 32 bit)

    • "linux64" (Linux Intel 64 bit)

  • The annotation(SourceDirectory="modelica://ModelicaLibraryName/Resources/Source"), gives the location for source files. The preceding one is the default and need not be specified; but another location could be specified by using an URI name for the source directory, see section 13.5. It is not specified how they are built.

The win32 or win64 directories may contain gcc47, vs2010, vs2012 for specific versions of these compilers and these are used instead of the general win32 or win64 directories, and similarly for other platforms.

The library on Windows may refer to a lib-file (static library), both a lib- and dll-file (in this case the lib-file is an import-library), or just a dll-file. It shall not refer to an obj-file.

If the directory for the specific compiler version is missing the platform specific directory is used.

[A tool may give diagnostics if the directory corresponding to the selected compiler version is missing. The directories may use symbolic links or use a text-file as described below: e.g. a text-file vs2008 containing the text ../win32/vs2005 (or vs2005) suggesting that it is compatible with vs2005.]

The ModelicaLibraryName used for IncludeDirectory, LibraryDirectory, and SourceDirectory indicates the top-level class where the annotation is found in the Modelica source code.

[Example: Use of external functions and of object libraries:

package ExternalFunctions
  model Example
    Real x(start = 1.0), y(start = 2.0);
  equation
    der(x) = -ExternalFunc1(x);
    der(y) = -ExternalFunc2(y);
  end Example;
  model OtherExample
    Real x(start = 1.0);
  equation
    der(x) = -ExternalFunc3(x);
  end OtherExample;
  function ExternalFunc1
    input Real x;
    output Real y;
  external "C"
    y = ExternalFunc1_ext(x)
      annotation(Library = "ExternalLib1",
                 Include = "#include \"ExternalFunc1.h\"",
                 SourceDirectory =
                  "modelica://ExternalFunctions/Resources/Source");
      // The SourceDirectory is the default and thus redundant.
  end ExternalFunc1;
  function ExternalFunc2
    input Real x;
    output Real y;
  external "C"
      annotation(Library = "ExternalLib2",
                 Include = "#include \"ExternalFunc2.h\"");
  end ExternalFunc2;
  function ExternalFunc3
    input Real x;
    output Real y;
  external "C"
      annotation(Include = "#include \"ExternalFunc3.c\"");
  end ExternalFunc3;
end ExternalFunctions;
package MyExternalFunctions
  extends ExternalFunctions;
end MyExternalFunctions;

Directory structure:

ExternalFunctions
  package.mo  // contains the Modelica code from above
  Resources
    Include        // contains the include files
      ExternalFunc1.h // C header file
      ExternalFunc2.h // C header file
      ExternalFunc3.c // C source file (not ideal)
    Library    // contains the object libraries for different platforms
       win32
         ExternalLib1.lib // static link library for VisualStudio
         ExternalLib2.lib // statically linking the dynamic link library
         ExternalLib2.dll // dynamic link library (with manifest)
       linux32
         libExternalLib1.a  // static link library
         libExternalLib2.so // shared library
    Source // Sources for library
      Func1.c         // C source for ExternalLib1.lib
      Func2.c         // C source for ExternalLib2.lib
      HelperFunc.c    // C source also included in ExternalLib2.lib
MyExternalFunctions
   package.mo

Note that calling the function MyExternalFunctions.ExternalFunc1 will use the header and library files from ExternalFunction, the ExternalFunctions.Example will not use ExternalFunc3.c, and one library file may contain multiple functions.

The C-source ExternalFunc3.c will be included fully, and is not part of any library. That is not ideal for C-code, but it works for small functions.

It is not specified how the C-sources in Source will be used to build the libraries.

Header file for the function in the dynamic link / shared library ExternalLib2 so that the desired functions are defined to be exported for Microsoft VisualStudio and for GNU C compiler (note, for Linux it is recommended to use the compiler option -fPIC to build shared libraries or object libraries that are later transformed to a shared library):

// File ExternalFunc2.h
#ifdef __cplusplus
extern "C" {
#endif
#ifdef _MSC_VER
#ifdef EXTERNAL_FUNCTION_EXPORT
#  define EXTLIB2_EXPORT __declspec( dllexport )
#else
#  define EXTLIB2_EXPORT __declspec( dllimport )
#endif
#elif  __GNUC__ >= 4
  /* In gnuc, all symbols are by default exported. It is still often useful,
  to not export all symbols but only the needed ones */
#  define EXTLIB2_EXPORT __attribute__ ((visibility("default")))
#else
#  define EXTLIB2_EXPORT
#endif
EXTLIB2_EXPORT void ExternalFunc2(<function arguments>);
#ifdef __cplusplus
}
#endif

]

The Library name and the LibraryDirectory name in the function annotation are mapped to a linkage directive in a compiler-dependent way thereby selecting the object library suited for the respective computer platform.

12.9.5 Examples

12.9.5.1 Input Parameters, Function Value

[Example: Here all parameters to the external function are input parameters. One function value is returned. If the external language is not specified, the default is "C", as below.

function foo
  input Real x;
  input Integer y;
  output Real w;
  external;
end foo;

This corresponds to the following C prototype:

double foo(double, int);

Example call in Modelica:

z = foo(2.4, 3);

Translated call in C:

z = foo(2.4, 3);

]

12.9.5.2 Arbitrary Placement of Output Parameters, No External Function Value

[Example: In the following example, the external function call is given explicitly which allows passing the arguments in a different order than in the Modelica version.

function foo
  input Real x;
  input Integer y;
  output Real u1;
  output Integer u2;
external "C"
  myfoo(x, u1, y, u2);
end foo;

This corresponds to the following C prototype:

void myfoo(double, double *, int, int *);

Example call in Modelica:

(z1,i2) = foo(2.4, 3);

Translated call in C:

myfoo(2.4, &z1, 3, &i2);

]

12.9.5.3 External Function with Both Function Value and Output Variable

[Example: The following external function returns two results: one function value and one output parameter value. Both are mapped to Modelica output parameters.

function foo
  input Real x;
  input Integer y;
  output Real funcvalue;
  output Integer out1;
external "C"
  funcvalue = myfoo(x, y, out1);
end foo;

This corresponds to the following C prototype:

double myfoo(double, int, int *);

Example call in Modelica:

(z1,i2) = foo(2.4, 3);

Translated call in C:

z1 = myfoo(2.4, 3, &i2);

]

12.9.6 Utility Functions

This section describes the utility functions declared in ModelicaUtilities.h, which can be called in external Modelica functions written in C.

12.9.6.1 Utility Functions for Reporting Errors

The functions listed below produce a message in different ways.

Expression Description Details
ModelicaMessage(𝑠𝑡𝑟𝑖𝑛𝑔) Message with fixed string Function 12.1
ModelicaWarning(𝑠𝑡𝑟𝑖𝑛𝑔) Warning with fixed string
ModelicaError(𝑠𝑡𝑟𝑖𝑛𝑔) Error with fixed string
ModelicaFormatMessage(𝑓𝑜𝑟𝑚𝑎𝑡, ) Message with printf style formatting Function 12.2
ModelicaFormatWarning(𝑓𝑜𝑟𝑚𝑎𝑡, ) Warning with printf style formatting
ModelicaFormatError(𝑓𝑜𝑟𝑚𝑎𝑡, ) Error with printf style formatting
ModelicaVFormatMessage(𝑓𝑜𝑟𝑚𝑎𝑡, 𝑎𝑝) Message with vprintf style formatting Function 12.3
ModelicaVFormatWarning(𝑓𝑜𝑟𝑚𝑎𝑡, 𝑎𝑝) Warning with vprintf style formatting
ModelicaVFormatError(𝑓𝑜𝑟𝑚𝑎𝑡, 𝑎𝑝) Error with vprintf style formatting

The Message-functions only produce the message, but the Warning- and Error-functions combine this with error handling as follows.

The Warning-functions view the message as a warning and can skip duplicated messages similarly as an assert with level = AssertionLevel.Warning in the Modelica code.

The Error-functions never return to the calling function, but handle the error similarly to an assert with level = AssertionLevel.Error in the Modelica code.

Function 12.1 ModelicaMessage, ModelicaWarning, ModelicaError
void ModelicaMessage(const char* 𝑠𝑡𝑟𝑖𝑛𝑔)
void ModelicaWarning(const char* 𝑠𝑡𝑟𝑖𝑛𝑔)
void ModelicaError(const char* 𝑠𝑡𝑟𝑖𝑛𝑔)
  • Output the fixed message string (no format control).

Function 12.2 ModelicaFormatMessage, ModelicaFormatWarning, ModelicaFormatError
void ModelicaFormatMessage(const char* 𝑓𝑜𝑟𝑚𝑎𝑡, …)
void ModelicaFormatWarning(const char* 𝑓𝑜𝑟𝑚𝑎𝑡, …)
void ModelicaFormatError(const char* 𝑓𝑜𝑟𝑚𝑎𝑡, …)
  • Output the message under the same format control as the C function printf.

Function 12.3 ModelicaVFormatMessage, ModelicaVFormatWarning, ModelicaVFormatError
void ModelicaVFormatMessage(const char* 𝑓𝑜𝑟𝑚𝑎𝑡, va_list 𝑎𝑝)
void ModelicaVFormatWarning(const char* 𝑓𝑜𝑟𝑚𝑎𝑡, va_list 𝑎𝑝)
void ModelicaVFormatError(const char* 𝑓𝑜𝑟𝑚𝑎𝑡, va_list 𝑎𝑝)
  • Output the message under the same format control as the C function vprintf.

12.9.6.2 Utility Functions for Allocating Strings

The functions listed below are related to string allocation.

Expression Description Details
ModelicaAllocateString(𝑙𝑒𝑛) Allocate or error Function 12.4
ModelicaAllocateStringWithErrorReturn(𝑙𝑒𝑛) Allocate or null Function 12.5
ModelicaDuplicateString(𝑠𝑡𝑟) Duplicate or error Function 12.6
ModelicaDuplicateStringWithErrorReturn(𝑠𝑡𝑟) Duplicate or null Function 12.7

As described in section 12.9.1.1, an external function wanting to return a newly constructed string must allocate this string with one of the string allocation functions in this section. The allocated memory is owned by the Modelica simulation environment, and may only be accessed by the external function during the currently executing external function call. The string allocation functions can also be used to allocate temporary strings that are not returned from the external function, with the convenience of the Modelica simulation environment being responsible for deallocation after the return of the external function. (This is particularly convenient for avoiding memory leaks in the event of abnormal termination of the external function, for example, via ModelicaError).

[Memory that is not passed to the Modelica simulation environment, such as memory that is freed before leaving the function, or in an ExternalObject, see section 12.9.7, may be allocated with the standard C mechanisms, like malloc.]

Function 12.4 ModelicaAllocateString
char* ModelicaAllocateString(size_t 𝑙𝑒𝑛)
  • Allocates 𝑙𝑒𝑛+1 characters, and sets the last one to nul. If an error occurs, this function does not return, but calls ModelicaError.

Function 12.5 ModelicaAllocateStringWithErrorReturn
char* ModelicaAllocateStringWithErrorReturn(size_t 𝑙𝑒𝑛)
  • Same as ModelicaAllocateString, except that in case of error, the function returns 0. This allows the external function to close files and free other open resources in case of error. After cleaning up resources, use ModelicaError or ModelicaFormatError to signal the error.

Function 12.6 ModelicaDuplicateString
char* ModelicaDuplicateString(const char* 𝑠𝑡𝑟)
  • Returns a writeable duplicate of the nul-terminated string 𝑠𝑡𝑟. If an error occurs, this function does not return, but calls ModelicaError.

Function 12.7 ModelicaDuplicateStringWithErrorReturn
char* ModelicaDuplicateStringWithErrorReturn(const char* 𝑠𝑡𝑟)
  • Same as ModelicaDuplicateString, except that in case of error, the function returns 0. This allows the external function to close files and free other open resources in case of error. After cleaning up resources, use ModelicaError or ModelicaFormatError to signal the error.

12.9.7 External Objects

External functions may need to store their internal memory between function calls. Within Modelica this memory is defined as instance of the predefined class ExternalObject according to the following rules:

  • There is a predefined partial class ExternalObject.

    [Since the class is partial, it is not possible to define an instance of this class.]

  • An external object class shall be directly extended from ExternalObject, shall have exactly two function definitions, called constructor and destructor, and shall not contain other elements. The functions constructor and destructor shall not be replaceable. It is not legal to call the constructor and destructor functions explicitly.

  • The constructor function is called exactly once before the first use of the object. The constructor shall have exactly one output argument in which the constructed instance derived from ExternalObject is returned. The arguments to the constructor must not – directly nor indirectly – depend on the external object being constructed. The constructor shall initialize the object, and must not require any other calls to be made for the initialization to be complete (e.g., from an initial algorithm or initial equation).

    The constructor shall not assume that pointers sent to the external object will remain valid for the life-time of the external object. An exception is that if the pointer to another external object is given as argument to the constructor, that pointer will remain valid as long as the other external object lives.

  • For each completely constructed object, the destructor is called exactly once, after the last use of the object, even if an error occurs. The destructor shall have no output arguments and the only input argument of the destructor shall be of the type derived from ExternalObject. The destructor shall delete the object, and must not require any other calls to be made for the deletion to be complete (e.g., from a when terminal() clause).

    [External objects may be a protected component (or part of one) in a function. The constructor is in that case called at the start of the function call, and the destructor when the function returns, or when recovering from errors in the function.]

    [External objects may be an input (or part of an input) to a function, in that case the destructor is not called (since the external object is active before and after the function call). Normally this is an external function, but it could be a non-external function as well (e.g. calling external functions one or more times). The function input shall not have a default value using the constructor.]

  • An external object class shall be of the specialized class class.

    [This is the only use of class.]

  • Classes derived from ExternalObject can neither be used in an extends-clause nor in a short class definition.

  • Only the constructor may return external objects and an external object can only be bound in component declarations and neither modified later nor assigned to.

    [It follows that a function cannot return a component containing an external object, since only the constructor may return an external object and the constructor exactly returns the external object.]

  • External functions may be defined which operate on the internal memory of an ExternalObject. An ExternalObject used as input argument or return value of an external C function is mapped to the C type void*.

[Example: A user-defined table may be defined in the following way as an ExternalObject (the table is read in a user-defined format from file and has memory for the last used table interval):

class MyTable
  extends ExternalObject;
  function constructor
    input String fileName = "";
    input String tableName = "";
    output MyTable table;
  external "C"
    table = initMyTable(fileName, tableName);
  end constructor;
  function destructor "Release storage of table"
    input MyTable table;
  external "C"
    closeMyTable(table);
  end destructor;
end MyTable;

and used in the following way:

model test "Define a new table and interpolate in it"
  MyTable table=MyTable(fileName ="testTables.txt",
    tableName="table1"); // call initMyTable
  Real y;
equation
  y = interpolateMyTable(table, time);
end test;

This requires to provide the following Modelica function:

function interpolateMyTable "Interpolate in table"
  input MyTable table;
  input Real u;
  output Real y;
external "C"
  y = interpolateMyTable(table, u);
end interpolateTable;

The external C functions may be defined in the following way:

typedef struct { /* User-defined datastructure of the table */
  double* array; /* nrow*ncolumn vector */
  int nrow; /* number of rows */
  int ncol; /* number of columns */
  int type; /* interpolation type */
  int lastIndex; /* last row index for search */
} MyTable;
void* initMyTable(const char* fileName, const char* tableName) {
  MyTable* table = malloc(sizeof(MyTable));
  if ( table == NULL ) ModelicaError("Not enough memory");
  // read table from file and store all data in *table
  return (void*) table;
};
void closeMyTable(void* object) { /* Release table storage */
  MyTable* table = (MyTable*) object;
  if ( object == NULL ) return;
  free(table->array);
  free(table);
}
double interpolateMyTable(void* object, double u) {
  MyTable* table = (MyTable*) object;
  double y;
  // Interpolate using "table" data (compute y)
  return y;
};

]