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
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
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 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.,
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:
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.
algorithmsection and it cannot contain
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
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;