Modelica® - A Unified Object-Oriented Language for Systems Modeling Language Specification Version 3.4

Chapter 12 Functions

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  functionname
  input  TypeI1 in1;
  input  TypeI2 in2;
  input  TypeI3 in3 := default_expr1 "Comment" annotation(...);
  ...
  output TypeO1 out1;
  output TypeO2 out2 :=  default_expr2;
  ...
protected
  <local variables>
  ...
algorithm
  ...
    <statements>
  ...
end functionname;

]

Optional explicit default values can be associated with any input or output formal parameter through declaration assignments. [Such defaults are shown for the third input parameter and the second output parameter in our example.] Comment strings and annotations can be given for any formal parameter declaration, as usual in Modelica declarations.

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

function  functionname
end functionname;

]

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  <functionname>
  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  <functionname>;

]

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 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:

  • Each input formal parameter of the function must be prefixed by the keyword input, and each result formal parameter by the keyword output. All public variables are formal parameters.

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

  • A function may not be used in connections, may not have equations, may 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 restricted classes type, record, operator record, and function; i.e. no model or block components.

  • The elements of a function may 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 may not be partial, and the output variables must be assigned inside the function either in declaration assignments 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 (:) of each array result or array local variable [i.e., a non-input components] 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 (:) 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.

  • 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,

  • in initial equations and initial algorithms,

  • in bindings for variables declared as parameter – which is seen as syntactic sugar for having a parameter with fixed=false and the binding as an initial equation [thus there is no guarantee that parameter is equal to the impure function call after initialization] – and in constructing 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.]

It is possible to mark a function formal parameter as impure. Only if the function formal parameter is marked impure, it is allowed to pass an impure function to it. A function having a formal function parameter marked 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.]

[Examples:

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 – to handle the two elements of the matrix above, and 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)  else 0; // May not move asin(x) out of then
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);
  // May not 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 type-specifier "(" [ named-arguments ] ")" | 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. This slot shall be not filled [otherwise an error occurs] and the value of the argument is placed in the slot, filling it. 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 may not depend on non-input variables in the function. There shall be no remaining unfilled slots [otherwise an error occurs] and the list of filled slots is used as the argument list for the call.

Special purpose operators with function syntax defined in the specification may 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 coercions 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 class-name of a partial function that has no replaceable elements. It cannot be the class-name 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 x;
  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,

  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 type compatible” (section 6.5) to 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” (see section 6.5) to the same function where all bound arguments are removed [thus, for checking function type compatibility, bound formal parameters are ignored].

[Example of 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 showing that 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 of a 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 Declaration Assignments 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 a function do not have start-attributes. However, a declaration assignment (:= expression) with an expression may be present for a component.

A declaration assignment 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 declaration assignment has been executed; it is an error if no such order exists (i.e. the binding must be acyclic).

Declaration assignments can only be used for components of a function. If no declaration assignment 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. [It is recommended to check this statically - if this is not possible a warning is recommended combined with a run-time check.] Declaration assignments for input formal parameters are interpreted as default arguments, as described in section 12.4.1.

[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 declaration assignment, 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.2.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 is defined.

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 with the same dimension sizes as the foreach arguments. Each element ei,..,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.

[Examples:

  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
  atan({a,b,c},{d,e,f}) = {atan(a,d), atan(b,e), atan(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 vector of Reals 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, and is not allowed to have side-effects that influence the simulation state.]

An empty call can occur either as a kind of “null” equation or “null” statement, [e.g. as in the empty calls to eigen() in the example below:

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.2.

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

  • 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 [in order to remove potentially conflicting import statements 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 constant Real c=1 or 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 to construct 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;
    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 result(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 result(r0=r0,c2=c2,n1=n1,n2=n2,r1=r1,r2=r2,r4=r4,r5=r5,r6=r6,r7=r7);
  protected
    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 corresponding binding would be illegal since not a connect-statement.] 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. [Note, this 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 Declaring Derivatives of Functions

Derivatives of functions can be declared explicitly using the derivative annotation, see section 12.7.1, whereas a function can be defined as a partial derivative of another function using the der-operator in a short function definition, see section 12.7.2.

12.7.1 Using the Derivative Annotation

A function declaration can have an annotation derivative specifying the derivative function or preferably, for a function written in Modelica, use the smoothOrder annotation to indicate that the tool can construct the derivative function automatically, section 18.3. The derivative annotation can influence simulation time and accuracy and can be applied to both functions written in Modelica and to external functions. A derivative annotation can state that it is only valid under certain restrictions on the input arguments. These restrictions are defined using the following optional attributes: order (only a restriction if order>1, the default for order is 1), noDerivative, and zeroDerivative. The given derivative-function can only be used to compute the derivative of a function call if these restrictions are satisfied. 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). A function may supply 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:

  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 (n>=1), i.e. this function call has been derived from an (n-1)th derivative by differentiation inside the tool, an annotation(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:

(…,d y(t)/dt,…)=foo1(…,x(t),b,…, …,d x(t)/dt,…);

(…,d^2 y(t)/dt^2,…)=foo2(…,x(t),b,…,d x(t)/dt,…, …,d^2 x(t)/dt^2,…);

]

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 Reals the corresponding derivative uses a derivative record, that only contain 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 may not be empty.

[Here is one example use case with records mixing Reals and non-Reals 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=inputVar1 \{, zeroDerivative=inputVar2 \}

The derivative function is only valid if inputVar1 (and inputVar2 etc.) are independent of the variables the function call is differentiated with respect to (i.e. that the derivative of inputVar1 is “zero”). The derivative of inputVar1 (and inputVar2 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 = f_general_der is optional and can be used when the derivative of the matrix or offset is non-zero). Note that f_der must have zeroDerivative for both y and offset, but f_general_der may not have zeroDerivative for either of them (it may zeroDerivative for x_der, y_der, or offset_der).

function f "Simple table lookup"
  input Real x;
  input Real y[:, 2];
  input Real offset;
  output Real z;
algorithm
  ...
  annotation(derivative(zeroDerivative=y, zeroDerivative=offset)= f_der,
             derivative=f_general_der);
end f;
function f_der "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) = f_der2);
end f_der;
function f_der "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 f_der;
function f_general_der "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) = f_general_der2);
end f_general_der;

]

  • noDerivative=inputVar1

The derivative of inputVar1 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) = f_der);
end f;
function f_der
  input Real x;
  input Real y;
  input Real x_der;
  output Real z_der;
algorithm
  ...
end f_der;

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

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 - 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.8 Declaring Inverses of Functions

Every function with one output formal parameter may have one or more “inverse” annotations to define inverses of this function:

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

The meaning is that function ”f2” is one inverse to function ”f1” where the previous output ”y” is now an input and the previous input ”uk” is now an output. More than one inverse can be defined within the same inverse annotation. Several inverses are separated by commas. [The inverse requires that for all valid values of the input arguments of f2(…,y, …) and uk being calculated as  uk := f2(…, y, …)  implies the equality y = f1(…, uk, …,) up to a certain precision.]

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” must have exactly the same formal parameters as ”f1” (with the same defaults, if a formal parameter ”um” has a default), but the order of the formal parameters may be permuted.

[Example:

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;

]

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 declaration assignment to the external function. [Declaration assignments are executed prior to calling the external function.]

The format of an external function declaration is as follows.

function IDENT string-comment
  { 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", "C", "C..." (for one of the specific C-standards like C89, C99, and C11 – which specifies 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 "builtin" specification is only used for functions that are defined to be built-in in Modelica. 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.

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-expression 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 ’\’) to facilitate calling of C functions. When returning a non-literal string, the memory for this string must be allocated with function ModelicaAllocateString (see section 12.9.6) [It is not suitable to use malloc, because a Modelica simulation environment may have its own allocation scheme, e.g., a special stack for local variables of a function]. After return of the external function, the Modelica environment is responsible for the memory allocated with ModelicaAllocateString (e.g., to free this memory, when appropriate). It is not allowed to access memory that was allocated with ModelicaAllocateString in a previous call of this external function. [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, should be allocated with the standard C-mechanisms, like calloc(..)].

Boolean values are mapped to C such that false in Modelica is 0 in C and true in Modelica is 1 in C. [However, the C-function should interpret any non-zero value as true.] If the returned value from C is zero it is treated as false in Modelica; otherwise 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
Enumeration type INTEGER INTEGER
String Special Not Available

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 i:th enumeration literal is mapped to integer value i, starting at one.

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 declaration, 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 array layout 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.

Modelica C
Input and Output
T[𝑑𝑖𝑚1] T’ *, size_t 𝑑𝑖𝑚1
T[𝑑𝑖𝑚1,𝑑𝑖𝑚2] T’ *, size_t 𝑑𝑖𝑚1, size_t 𝑑𝑖𝑚2
T[𝑑𝑖𝑚1, …, 𝑑𝑖𝑚n] T’ *, size_t 𝑑𝑖𝑚1, …, 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

[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(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.

[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(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).

[For 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. 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. Otherwise the external function is assumed not to return anything; i.e., it is really a procedure or, in C, a void-function. [In this case, 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 annotation (Library=…):

  • 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, [e.g., header files or source files that contain the functions referenced in the external function declaration], needed for calling the external function in the code generated by the Modelica compiler. The included code should be valid C89 code.

  • The annotation(IncludeDirectory="modelica://LibraryName/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.2.3.

  • The annotation(LibraryDirectory="modelica://LibraryName/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.2.3. 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 ”win32”/”win64” directories may contain ”gcc47”, ”vs2010”, ”vs2012” for specific versions of these compilers and these are used instead of the general ”win32”/”win64” directories, and similarly for other platforms.

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 LibraryName used for IncludeDirectory and LibraryDirectory indicates the top-level class where the annotation is found in the Modelica source code.

[Example: to show the 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;
  function ExternalFunc1
    input Real x;
    output Real y;
    external "C"
    y=ExternalFunc1_ext(x) annotation(Library="ExternalLib11",Include="#include \"ExternalFunc1.h\"");
  end ExternalFunc1;
  function ExternalFunc2
    input Real x;
    output Real y;
    external "C" annotation(Include="\#include \"ExternalFunc3.c\"");
  end ExternalFunc2;
  function ExternalFunc3
    input Real x;
    output Real y;
    external
    y=ExternalFunc3_ext(x) annotation(Library="ExternalLib11", Include="#include \"ExternalFunc1.h\"");
  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
    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
MyExternalFunctions
   package.mo

Note that calling MyExternalFunctions.ExternalFunc1 will use header and library files from ExternalFunctions.

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

[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

[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

[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

The following utility functions can be called in external Modelica functions written in C. These functions are defined in file ModelicaUtilities.h:

The following functions produce a message in different ways. 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.

ModelicaMessage

ModelicaWarning

ModelicaError

void Modelica{Message,Warning,Error}(const char* string)

Output the message string (no format control).

ModelicaFormatMessage

ModelicaFormatWarning

ModelicaFormatError

void ModelicaFormat{Message,Warning,Error}(const char* string,…)

Output the message under the same format control as the C-function printf.

ModelicaVFormatMessage

ModelicaVFormatWarning

ModelicaVFormatError

void ModelicaVFormat{Message,Warning,Error}(const char*string, va_list)

Output the message under the same format control as the C-function vprintf.

And then the string handling functions:

ModelicaAllocateString
char* ModelicaAllocateString(size_t len)
Allocate memory for a Modelica string which is used as return argument of an external Modelica function. Note, that the storage for string arrays (= pointer to string array) is still provided by the calling program, as for any other array. If an error occurs, this function does not return, but calls ”ModelicaError”.
ModelicaAllocateStringWithErrorReturn
char*ModelicaAllocateStringWithErrorReturn(size_t len)
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.

12.9.7 External Objects

External functions may have internal memory reported 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.

  • The constructor function is called exactly once before the first use of the object. For each completely constructed object, the destructor is called exactly once, after the last use of the object, even if an error occurs. The constructor shall have exactly one output argument in which the constructed instance derived from ExternalObject is returned. The destructor shall have no output arguments and the only input argument of the destructor shall be of the type derived from ExternalObject. It is not legal to call explicitly the constructor and destructor functions. 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 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). The constructor may 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.]

    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 may not have a default value using the constructor.

  • 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 external object can only be bound in component declarations and neither modified later nor assigned to.

    No function may 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;
};

]