As we’ve already seen, Modelica includes many useful functions for describing mathematical behavior. But, inevitably, it is necessary to create new functions for specific purposes. Defining such functions is similar, syntactically, to a Model Definition.
A basic Modelica function includes one or more arguments, a return
value and an algorithm
section to compute the return value in
terms of the arguments. The arguments to the function are preceded by
the input
qualifier and the return value is preceded by the
output
qualifier. For example, consider the following simple
function that squares its input argument:
function Square
input Real x;
output Real y;
algorithm
y := x*x;
end Square;
Here the input argument x
has the type Real
. The output
variable y
also has the Real
type. Arguments and return
values can be scalars or arrays (or even records, although we won’t
introduce records until later).
For complex calculations, it is sometimes useful to define variables
to hold intermediate results. Such variables must be clearly
distinguished from arguments and return values. To declare such
intermediate variables, make their declarations protected
. Making
the variables protected
indicates to the Modelica compiler that
these variables are not arguments or return values, but are instead
used internally by the function. For example, if we wished to write a
function to compute the circumference of a circle, we might utilize an
intermediate variable to store the diameter:
function Circumference
input Real radius;
output Real circumference;
protected
Real diameter := radius*2;
algorithm
circumference := 3.14159*diameter;
end Circumference;
Here we see how some intermediate result or common sub-expression can be associated with an internal variable.
In some cases, it makes sense to include default values for some input arguments. In these cases, it is possible to include a default value in the declaration of the input variable. Consider the following function to compute the potential energy of a mass in a gravitational field:
function PotentialEnergy
input Real m "mass";
input Real h "height";
input Real g=9.81 "gravity";
output Real pe "potential energy";
algorithm
pe := m*g*h;
end PotentialEnergy;
By providing a default value for g
, we do not force users of this
function to provide a value for g
each time. Of course, this kind
of approach should only be used when there is a reasonable default
value for a given argument and it should never be used if you want to
force users to provide a value.
These default values have some important effects when Calling Functions that we shall discuss shortly.
Note that a function can have multiple return values (i.e., multiple
declarations with the output
qualifier). For example, to consider
a function that computes both the circumference and area of a circle:
function CircleProperties
input Real radius;
output Real circumference;
output Real area;
protected
Real diameter := radius*2;
algorithm
circumference := 3.14159*diameter;
area := 3.14159*radius^2;
end CircleProperties;
Our upcoming discussion on Calling Functions will cover how to address multiple return values.
So far, we’ve covered how to define new functions. But it is also worth spending some time discussing the various ways of calling functions. In general, functions are invoked in a way that would be expected by both mathematicians and programmers, e.g.,
f(z, t);
Here we see the typical syntax name of the function name followed by a comma separated list of arguments surrounded by parentheses. But there are several interesting cases to discuss.
The syntax above is “positional”. That means that values in the function call are assigned to arguments based on the order. But since Modelica function arguments have names, it is also possible to call functions using named arguments. Consider the following function for computing the volume of a cube:
function CylinderVolume
input Real radius;
input Real length;
output Real volume;
algorithm
volume = 3.14159*radius^2*length;
end CylinderVolume;
When calling this function, it is important not to confuse the radius and the volume. To avoid any possible confusion regarding their order, it is possible to call the function used named arguments. In that case, the function call would look something like:
CylinderVolume(radius=0.5, length=12.0);
Named arguments are particularly useful in conjunction with default
argument values. Recall the PotentialEnergy
function introduced
earlier. It can be invoked in several ways:
PotentialEnergy(1.0, 0.5, 9.79) // m=1.0, h=0.5, g=9.79
PotentialEnergy(m=1.0, h=0.5, g=9.79) // m=1.0, h=0.5, g=9.79
PotentialEnergy(h=0.5, m=1.0, g=9.79) // m=1.0, h=0.5, g=9.79
PotentialEnergy(h=0.5, m=1.0) // m=1.0, h=0.5, g=9.81
PotentialEnergy(0.5, 1.0) // m=0.5, h=1.0, g=9.81
The reason named arguments are so important for arguments with default values is if a function has many arguments with default arguments, you can selectively override values for those arguments by referring to them by name.
Finally, we previously pointed out the fact that it is possible for a
function to have multiple return values. But the question remains,
how do we address multiple return values? To see how this is done in
practice, let us revisit the CircleProperties
function we defined
earlier in this section. The following statement shows how we can
reference both return values:
(c, a) := CircleProperties(radius);
In other words, the left hand side is a comma separated list of the
variables to be assigned to (or equated to, in the case of an
equation
section) wrapped by a pair of parentheses.
As this discussion demonstrates, there are many different ways to call a function in Modelica.
In general, we can perform the same kinds of calculations in functions as we can in models. But there are some important restrictions.
algorithm
section and it cannot contain when
statements.der
,
initial
, terminal
, sample
, pre
, edge
,
change
, reinit
, delay
, cardinality
, inStream
,
actualStream
protected
) variables
cannot be models or blocks.One important thing to note is that functions are not restricted in terms of recursion (i.e., a function is allowed to call itself).
In the Software-in-the-Loop Controller example, we introduced external functions
that had side effects. This means that the value returned by the
function was not strictly a function of its arguments. Such a
function is said to have “side effects”. Functions with
side effects, should be qualified with the impure
keyword. This
tells the Modelica compiler that these functions cannot be treated as
purely mathematical functions.
The use of impure
functions is restricted. They can only be
invoked from within a when
statement or another impure
function.
Taking all of this into account, the following can be considered a generalized function definition:
function FunctionName "A description of the function"
input InputType1 argName1 "description of argument1";
...
input InputTypeN argNameN := defaultValueN "description of argumentN";
output OutputType1 returnName1 "description of return value 1";
...
output OutputTypeN returnNameN "description of return value N";
protected
InterType1 intermedVarName1 "description of intermediate variable 1";
...
InterTypeN intermedVarNameN "description of intermediate variable N";
annotation ...
algorithm
// Statements that use the values of argName1..argNameN
// to compute intermedVarName1..intermedVarNameN
// and ultimately returnName1..returnNameN
end FunctionName;