Array Functions

There are a great many functions in Modelica that are related to arrays. In this section, we’ll go through different categories of functions and describe how they are used.

Array Construction Functions

We already talked about Array Construction. We saw the different syntactic constructs that can be used to build vectors and matrices. Furthermore, we saw how matrices can be built from other matrices. There are several functions in Modelica that can be used for constructing vectors, matrices and higher-dimension arrays as both an alternative or complement to those previous presented.

fill

The fill function is used to create an array where each element in the array has the same value. The arguments for fill are:

fill(v, d1, d2, ..., dN)

where v is the value to be given to each element in the array and the remaining arguments are the sizes of each dimension. The elements in the resulting array will have the same type as v. So, to fill a 5x7 array of reals with the value 1.7, we could use the following:

parameter Real x[5,7] = fill(1.7, 5, 7);

This would result in a matrix filled as follows:

\[\begin{split}\left[ \begin{array}{ccccccc} 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 \\ 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 \\ 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 \\ 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 \\ 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 & 1.7 \end{array} \right]\end{split}\]

zeros

When working with arrays, a common use case is to create an array that contains only zero elements. This is essentially the same functionality as the fill function, but since the value is known it is only necessary to specify the sizes. Using zeros we can initialize an array as follows:

parameter Real y[2,3,5] = zeros(2, 3, 5);

ones

The ones function is identical to the zeros function except that every element in the resulting array has the value \(1\). So, for example:

parameter Real z[3,5] = ones(3, 5);

This would result in a matrix filled as follows:

\[\begin{split}\left[ \begin{array}{ccccc} 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \\ 1 & 1 & 1 & 1 & 1 \end{array} \right]\end{split}\]

identity

Another common need is to easily build an identity matrix, one whose diagonal elements are all \(1\) while all other elements are \(0\). This can be done very easily with the identity. The identity function takes a single integer argument. This argument determines the number of rows and columns in the resulting matrix. So, invoking identity as:

identity(5);

would produce the following matrix:

\[\begin{split}\left[ \begin{array}{ccccc} 1 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 & 1 \\ \end{array} \right]\end{split}\]

diagonal

The diagonal function is used to create a matrix where all non-diagonal elements are \(0\). The only argument to diagonal is an array containing the values of the diagonal elements. So, to create the following diagonal matrix:

\[\begin{split}\left[ \begin{array}{cccc} 2.0 & 0 & 0 & 0 \\ 0 & 3.0 & 0 & 0 \\ 0 & 0 & 4.0 & 0 \\ 0 & 0 & 0 & 5.0 \end{array} \right]\end{split}\]

one could use the following Modelica code:

diagonal({2.0, 3.0, 4.0, 5.0});

linspace

The linspace function builds a vector where the values of the elements are all linearly distributed over an interval. The linspace function is invoked as follows:

linspace(v0, v1, n);

where v0 is the value of the first elements in the vector, v1 is the last element in the vector and n is the total number of values in the vector. So, for example, invoking linspace as:

linspace(1.0, 5.0, 9);

would yield the vector:

{1.0, 1.5, 2.0, 3.5, 3.0, 3.5, 4.0, 4.5, 5.0}

Conversion Functions

The following functions provide a means to transform arrays into other arrays.

scalar

The scalar function is invoked as follows:

scalar(A)

where A is an array with an arbitrary number of dimensions as long as each dimension is of size \(1\). The scalar function returns the (only) scalar value contained in the array. For example,

scalar([5]) // Argument is a two-dimensional array (matrix)

and

scalar({5}) // Argument is a one-dimensional array (vector)

would both give the scalar value 5.

vector

The vector function is invoked as follows:

vector(A)

where A is an array with an arbitrary number of dimensions as long as only one dimension has a size greater than \(1\). The vector function returns the contents of the array as a vector (i.e., an array with only a single dimension). So, for example, if we passed a column or row matrix, e.g.,

vector([1;2;3;4]) // Argument is a column matrix

or

vector([1,2,3,4]) // Argument is a row matrix

we would get back:

{1,2,3,4}

matrix

The matrix function is invoked as follows:

matrix(A)

where A is an array with an arbitrary number of dimensions as long as only two dimension have a size greater than \(1\). The matrix function returns the contents of the array as a matrix (i.e., an array with only two dimensions).

Mathematical Operations

In linear algebra, there are many different types of mathematical operations that are commonly performed on vectors and matrices. Modelica provides functions to perform most of these operations. In this way, Modelica equations can be made to look very much like their mathematical counterparts in linear algebra.

Let’s start with operations like addition, subtraction, multiplication, division and exponentiation. For the most part, these operations work just as they do in mathematics when applied to the various combinations of scalars, vectors and matrices. However, for completeness and reference, the following tables summarize how these operations are defined.

Explanation of Notation

Each of the operations described below involves two arguments, \(a\) and \(b\), and a result, \(c\). If an argument represents a scalar, it will have no subscripts. If it is a vector, it will have one subscript. If it is a matrix, it will have two subscripts. If the operation is defined for arbitrary arrays, a case will be included with three subscripts. If a given combination is not shown, then it is not allowed.

Addition (+)

Expression Result
\(a + b\) \(c = a + b\)
\(a_{i} + b_{i}\) \(c_{i} = a_{i} + b_{i}\)
\(a_{ij} + b_{ij}\) \(c_{ij} = a_{ij} + b_{ij}\)
\(a_{ijk} + b_{ijk}\) \(c_{ijk} = a_{ijk} + b_{ijk}\)

Subtraction (-)

Expression Result
\(a - b\) \(c = a - b\)
\(a_{i} - b_{i}\) \(c_{i} = a_{i} - b_{i}\)
\(a_{ij} - b_{ij}\) \(c_{ij} = a_{ij} - b_{ij}\)
\(a_{ijk} - b_{ijk}\) \(c_{ijk} = a_{ijk} - b_{ijk}\)

Multiplication (* and .*)

There are two types of multiplication operators. The first is the normal multiplication operator, *, that follows the usual mathematical conventions of linear algebra that matrix-vector products, etc.. The behavior of the * operator is summarized in the following table:

Expression Result
\(a * b\) \(c = a * b\)
\(a * b_i\) \(c_i = a * b_i\)
\(a * b_{ij}\) \(c_{ij} = a * b_{ij}\)
\(a * b_{ijk}\) \(c_{ijk} = a * b_{ijk}\)
\(a_i * b\) \(c_i = a_i * b\)
\(a_{ij} * b\) \(c_{ij} = a_{ij} * b\)
\(a_{ijk} * b\) \(c_{ijk} = a_{ijk} * b\)
\(a_{i} * b_{i}\) \(c = \sum_i a_{i} * b_{i}\)
\(a_{i} * b_{ij}\) \(c_j = \sum_i a_{i} * b_{ij}\)
\(a_{ij} * b_{j}\) \(c_i = \sum_j a_{ij} * b_{j}\)
\(a_{ik} * b_{kj}\) \(c_{ij} = \sum_k a_{ik} * b_{kj}\)

The second type of multiplication operator is a special element-wise version, .*, which doesn’t perform any summations and simply applies the operator element-wise to all array elements.

Expression Result
\(a\) .* \(b\) \(c = a * b\)
\(a_{i}\) .* \(b_{i}\) \(c_{i} = a_{i} * b_{i}\)
\(a_{ij}\) .* \(b_{ij}\) \(c_{ij} = a_{ij} * b_{ij}\)
\(a_{ijk}\) .* \(b_{ijk}\) \(c_{ijk} = a_{ijk} * b_{ijk}\)

Division (/ and ./)

As with Multiplication (* and .*), there are two division operators. The first is the normal division operator, /, which can be used to divide arrays by a scalar value. The following table summarizes its behavior:

Expression Result
\(a / b\) \(c = a / b\)
\(a_i / b\) \(c_i = a_i / b\)
\(a_{ij} / b\) \(c_{ij} = a_{ij} / b\)
\(a_{ijk} / b\) \(c_{ijk} = a_{ijk} / b\)

In addition, there is also an element-wise version of the division operator, ./, whose behavior is summarized in the following table:

Expression Result
\(a\) ./ \(b\) \(c = a / b\)
\(a_{i}\) ./ \(b_{i}\) \(c_{i} = a_{i} / b_{i}\)
\(a_{ij}\) ./ \(b_{ij}\) \(c_{ij} = a_{ij} / b_{ij}\)
\(a_{ijk}\) ./ \(b_{ijk}\) \(c_{ijk} = a_{ijk} / b_{ijk}\)

Exponentiation (^ and .^)

As with Multiplication (* and .*) and Division (/ and ./), the exponentiation operator comes in two forms. The first is the standard exponentiation operator, ^. The standard version can be used in two different ways. The first is to raise one scalar to the power of another (i.e., \(a\) ^ \(b\)). The other is to raise a square matrix to a scalar power (i.e., \(a_{ij}\) ^ \(b\)).

The other form of exponentiation is the element-wise form indicated with the .^ operator. Its behavior is summarized in the following table:

Expression Result
\(a\) .^ \(b\) \(c = a^b\)
\(a_{i}\) .^ \(b_{i}\) \(c_{i} = a_{i}^{b_{i}}\)
\(a_{ij}\) .^ \(b_{ij}\) \(c_{ij} = a_{ij}^{b_{ij}}\)
\(a_{ijk}\) .^ \(b_{ijk}\) \(c_{ijk} = a_{ijk}^{b_{ijk}}\)

Equality (=)

The equality operator, = used to construct equations can be used with scalars as well as arrays as long as the left hand side and right hand side have the same number of dimensions and the sizes of each dimension are the same. Assuming this requirement is met, then the operator is simply applied element-wise. This means that the operator is applied between each element on the left hand side and its counterpart on the right hand side.

Assignment (:=)

The := (assignment) operator is applied in the same element-wise way as the Equality (=) operator.

Relational Operators

All relational operators (and, or, not, >, >=, <=, <) are applied in the same element-wise way as the Equality (=) operator.

transpose

The transpose function takes a matrix as an argument and returns a transposed version of that matrix.

outerProduct

The outerProduct function takes two arguments. Each argument must be a vector and they must have the same size. The function returns a matrix which represents the outer product of the two vectors. Mathematically speaking, assume \(a\) and \(b\) are vectors of the same size. Invoking outerProduct(a,b) will return a matrix, \(c\), whose elements are defined as:

\[c_{ij} = a_i * b_j\]

symmetric

The symmetric function takes a square matrix as an argument. It returns a matrix of the same size where all the elements below the diagonal of the original matrix have been replaced by elements transposed from above the diagonal. In other words,

\[\begin{split}b_{ij} = \mathtt{symmetric(a)} = \left\{ \begin{array}{c} a_{ij}\ \ \mathrm{if}\ i<=j \\ a_{ji}\ \ \mathrm{otherwise} \end{array} \right.\end{split}\]

skew

The skew function takes a vector with three components and returns the following skew-symmetric matrix:

\[\begin{split}\mathtt{skew(x)} &= \left[ \begin{array}{ccc} 0 & -x_3 & x_2 \\ x_3 & 0 & -x_1 \\ -x_2 & x_1 & 0 \end{array} \right]\end{split}\]

cross

The cross function takes two vectors (each with 3 components) and returns the following vector (with three components):

\[\begin{split}\mathtt{cross(x,y)} = \left\{ \begin{array}{c} x_2 y_3 - x_3 y_2 \\ x_3 y_1 - x_1 y_3 \\ x_1 y_2 - x_2 y_1 \end{array} \right\}\end{split}\]

Reduction Operators

Reduction operators are ones that reduce arrays down to scalar values.

min

The min function takes an array and returns the smallest value in the array. For example:

min({10, 7, 2, 11})  // 2
min([1, 2; 3, -4])   // -4

max

The max function takes an array and returns the largest value in the array. For example:

max({10, 7, 2, 11})  // 11
max([1, 2; 3, -4])   // 3

sum

The sum function takes an array and returns the sum of all elements in the array. For example:

sum({10, 7, 2, 11})  // 30
sum([1, 2; 3, -4])   // 2

product

The product function takes an array and returns the product of all elements in the array. For example:

product({10, 7, 2, 11})  // 1540
product([1, 2; 3, -4])   // -24

Miscellaneous Functions

ndims

The ndims function takes an array as its argument and returns the number of dimensions in that array. For example:

ndims({10, 7, 2, 11})  // 1
ndims([1, 2; 3, -4])   // 2

size

The size function can be invoked two different ways. The first way is with a single argument that is an array. In this case, size returns a vector where each component in the vector corresponds to the size of the corresponding dimension in the array. For example:

size({10, 7, 2, 11})       // {4}
size([1, 2, 3; 3, -4, 5])  // {2, 3}

It is also possible to call size with an optional additional argument indicating a specific dimension number. In that case, it will return the size of that specific dimension as a scalar integer. For example,

size({10, 7, 2, 11}, 1)       // 4
size([1, 2, 3; 3, -4, 5], 1)  // 2
size([1, 2, 3; 3, -4, 5], 2)  // 3

Vectorization

In this section, we’ve discussed the numerous functions in Modelica that are designed to work with arguments that are arrays. But a very common use case is to apply a function element-wise to every element in a vector. Modelica supports this use case through a feature called “vectorization”. If a function is designed to take a scalar, but is passed an array instead, the Modelica compiler will automatically apply that function to each element in the vector.

To understand how this works, first consider a normal evaluation using the abs function:

abs(-1.5)   // 1.5

Obviously, abs is normally meant to accept a scalar argument and return a scalar. But in Modelica, we can also do this:

abs({0, -1, 1, -2, 2})  // {0, 1, 1, 2, 2}

Since this function is designed for scalar, the Modelica compiler will transform:

abs({0, -1, 1, -2, 2})

into

{abs(0), abs(-1), abs(1), abs(-2), abs(2)}

In other words, it transforms the function applies to a vector of scalars into a vector a functions applied to scalar.

This feature also works functions that take multiple arguments as long as only one of the expected scalar arguments is a vector. To understand this slightly more complex functionality, consider the modulo function, mod. If applied to scalar arguments we get the following behavior:

mod(5, 2)  // 1

If we turn the first argument into a vector, we get:

mod({5, 6, 7, 8}, 2)  // {1, 0, 1, 0}

In other words, it transforms:

mod({5, 6, 7, 8}, 2)

into

{mod(5,2), mod(6,2), mod(7,2), mod(8,2)}

However, this vectorization does not apply if more than one scalar arguments is presented as a vector. For example, the following expression will be an error:

mod({5, 6, 7, 8}, {2, 3}) // Illegal

because mod expects two scalar arguments, but it was passed two vector arguments.