# 数组
数组是一组同类型变量的集合。数组元素通过简单的整数下标来访问,范围从 1 到对应的维数的大小。在 Modelica 中,数组是整齐的,以矩阵为例,每行的长度是相同的,每列的长度也是相同的。该特性保证了数值计算应用的高效性与方便性。
尽管标量本质上不是数组,但仍可被看做是 0 维的数组。向量是 1 维数组,矩阵是 2 维数组。Modelica 不区分行向量与列向量,要想区分二者,需要用行矩阵或列矩阵来表示。但在应用 Modelica 建模过程中很少需要对二者进行区分。
# 数组声明
Modelica 类型系统包含标量、向量、矩阵以及大于 2 维的数组。下表展示了各种数组声明方式:
| 形式1 | 形式2 | 维度 | 名称 | 说明 |
|---|---|---|---|---|
| C x; | C x; | 0 | 标量 | 标量 |
| C[n] x; | C x[n]; | 1 | 向量 | 长度为 n 的向量 |
| C[E] x; | C x[E]; | 1 | 向量 | 枚举下标定义的向量 |
| C[n, m] x; | C x[n, m]; | 2 | 矩阵 | n´m 的矩阵 |
| c[n1, n2, …, nk] x; | c x[n1, n2, …, nk]; | k | 数组 | k 维数组(k>=0) |
在数组声明中,其维度信息可以在类型之后(变量之前),也可以在变量之后。形式 1 的声明方式能够清晰的展示数组的类型,形式 2 则是类似于 Fortran、C 等语言的传统声明方式。
方括号中逗号分隔的元素列表用来描述各个维度的大小,元素可以是整型子类型,也可以是冒号“:”,还可以是布尔类型或枚举类型。
冒号“:”表示不定长维度的数组。这种数组声明可以提高模型的灵活性,适应不同规模问题的求解。当数组被绑定到确切值的时候,冒号“:”所代表的维度的长度被推导出来,例如数组赋初值时:
Real A[:] = {1, 2, 3}; // size(A, 1) 被推导为3
Real M[:, size(M, 1)]; // 要求M是一个方阵,但大小不定
下面是一些数组声明的例子:
class ArrayDim
Real n = 1, m = 4, k = 5;
type Voltage = Real;
// 3-dimensional position vector
Real[3] positionvector = {1, 2, 3};
// transformation matrix
Real[3,3] identitymatrix = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
// A 3-dimensional array
Integer[n,m,k] arr3d;
// A boolean vector
Boolean[2] truthvalues = {false, true};
// A vector of voltage values
Voltage[10] voltagevector;
end ArrayDim;
特别地,布尔和枚举下标的数组声明方式如下:
type Colors = enumeration(red, green, blue);
Real e[Colors] = {1, 3, 5};
// e[E.red] = 1, e[E.green] = 3, e[E.blue] = 5
Real b[Boolean] = {2.2, 3.3};
// b[false] = 2.2, b[true] = 3.3
# 数组下标的下界和上界
由整型、布尔类型或枚举类型作为下标时,其下界和上界分别定义如下:
整型作下标时,下标下界是 1,上界是该维的维度大小;
布尔类型作下标时,下标下界是 false,上界是 true;
枚举类型作下标时,例如 type E = enumeration(e1, e2, …, en),下标下界是 E.e1,上界是 E.en。
# 数组类型和类型检查
用户可以定义数组类型,例如类型 ColorPixel 定义为实型数组类型,包含 3 个元素,可用于表示 3 基色像素。可用 ColorPixel 声明数组变量 image,如下:
type ColorPixel = Real[3];
ColorPixel[512, 512] image;
变量 image 表示 512´512 的像素矩阵,每个像素均为三基色。以数组类型声明的变量在计算时会被展开,展开时变量维数排在类型维数之前。例如变量 image 展开后的最终类型是 Real[512, 512, 3]。变量类型展开后,基本类型是内置类型或用户定义的非数组类型。
变量之间的赋值合法性检查要求类型匹配,包括维数匹配。两个数组变量,只要其最终展开类型相等即类型匹配。根据变量类型展开规则,下面的变量声明是合法的:
Real[512, 512, 3] image2 = image;
# 数组构造
数组构造器提供了简便的方式来生成数组。数组构造器实质上是一个函数,实参为标量或数组,返回一个数组。数组构造器函数为:
array(A, B, C, … )
等价的简写形式为:
{A, B, C, … }
其构造数组的规则为:每调用数组构造器一次,参数维度的左侧被加1后,参数作为结果而返回,新增维度的长度等于参数的个数。例如:{1, 2} 构造一个 1 维数组,该维长度为 2;{ {1, 2} } 构造一个 2 维数组,维度及其大小为 12。该规则可以用一个公式来描述:ndims(array(A)) = ndims(A) + 1。ndims(A) 表示数组 A 的维度。
建议用户在构造多维数组时尽量使用简写形式的数组构造器{},以增强代码的可读性。
使用数组构造器还必须满足以下几个条件:
每个参数的维度必须相等,即每个参数具有相同的维数,并且每一维的长度均相等;
每个参数的类型必须等价,实型与整型子类型可以混合使用,例如 {1.2, 3};
参数个数至少为 1。
例如:
Real v1 = {1, 2, 3}; // 长度为3的向量,类型为Integer[3]
Real v2 = {1.0, 2.3, 4}; // 长度为3的向量,类型为Real[3]
Real m1 = {{1,2,3}} ; // 1´3的矩阵,类型为Integer[1, 3]
Real m2 = {{1,2,3}, v2}; // 2´3的矩阵,类型为Real[2, 3]
# 范围向量构造
范围向量是指向量元素取值于一个数值区间内的固定间距点。比如 {1, 3, 5, 7, 9} 是数值 1 到 9 的区间内、以间距 2 进行取值后的集合。这种范围向量非常有用,例如在 for 循环中用作迭代范围等,因此 Modelica 规范提供了一种便利的方式来构造范围向量:
startexpr [: deltaexpr] : endexpr
该表达式称为“范围向量构造表达式”,简称“范围表达式”。其中步长表达式 deltaexpr 是可选的,如果不指定步长,则缺省为 1。范围表达式可用于构造整型、布尔类型以及枚举类型的范围向量,使用规则定义为:
表达式 j : k,如果 j 和 k 是整型,则表示整型向量 {j, j+1, … , k};如果是实型,则表示实型向量 {j, j+1.0, …, j+n},其中 n = floor(k-j);
表达式 j : d : k,如果是整型,则表示整型向量 {j, j+d, …, j+nd},其中 n=(k-j)/d;如果是实型,则表示实型向量 {j, j+d, …, j+nd},其中 n=floor((k-j)/d);
表达式 false : true 表示布尔类型向量 {false, true};
表达式 j : j 表示 {j},j 可以是整型、实型、布尔类型或枚举类型;
表达式 E.ei : E.ej 表示枚举类型向量 {E.ei, .., E.ej},其中,E.ej 和 E.ei 均为枚举类型 E 中定义的元素,并且要求 E.ej > E.ei。
例如:
Real v1[3] = 1.2 : 3.7; // {1.2, 2.2, 3.2}
Integer v2[3] = 1 : 3; // {1, 2, 3}
Integer v3Empty = 1 : 0; // 空数组
Real v4[7] = 2.1 : 1.2 : 10;
// {2.1, 3.3, 4.5, 5.7, 6.9, 8.1, 9.3}
Real v5[4] = 3 : -1.1 : -1; // {3, 1.9, 0.8, -0.3}
Boolean vb[2] = false : true; // { false, true }
type Size = enumeration(s, m, l, xl);
Size A[4] = Size.s : Size.xl;
// { Size.s, Size.m, Size.l, Size.xl }
# 带迭代器的数组构造
迭代器用于表示数学中“集合”的概念。如下数学格式描述的集合:
{expr(i) | i ∈A }
在 Modelica 中可用下面的语句表示:
{expr_i for i in A}
或
array( expr_i for i in A )
带两个迭代器的数组构造器可如下表示:
{expr_ij for i in A, j in B}
其通用语法格式为:
{expression for iterators}
其中,“iterators”表示多个迭代器,迭代器之间用逗号分隔,格式如下:
ident in range_expression{, ident2 in range_expression2}
迭代器中的范围表达式必须是向量,并且必须保证向量的每个元素都能够在编译时被估值。迭代表达式中的迭代变量 ident 的作用域仅限于表达式该表达式内部。标识符 ident 会覆盖其封闭作用域中可能存在的同名标识符。
仅带单个迭代器的数组构造表达式,其结果为向量,是通过对迭代表达式中的每个元素值依次代入上述表达式 expression 中进行运算构造的,例如:
{r/2 for r in {2, 4, 6, 8}} // {1, 2, 3, 4}
{r for r in 1.3 : 1.1 : 5} // {1.3, 2.4, 3.5, 4.6}
如果迭代器中的迭代变量作为 expression 中的下标索引,迭代器中的范围表达式可以省略不写,迭代范围可被自动推导。
Real x[3] = {1.2, 2.0, 3.8};
Real r1[3] = {x[i]*2 for i in 1 : size(x, 1)};
// {2.4, 4.0, 7.6}
Real r2[3] = {x[i]*2 for i};
// 等价于r1,i的取值范围被自动推导为1 : 3
带多个迭代器的数组构造是矩阵或多维数组构造的简化记法。其转化方法是先将多个迭代器按声明顺序反向,然后将迭代器之间的“,”替换为“} for”,并在整个数组构造器前面补充“{”。例如:
Real hilb[:,:] = { (1/(i+j-1)) for i in 1 : n, j in 1 : n };
Real hilb2[:,:] = { {(1/(i+j-1)) for j in 1 : n} for i in 1 : n };
// hilb2 == hilb
带迭代器的数组构造在定义一些特殊的矩阵时非常有用,例如下列语句构造了一个对角线元素为 1 的矩阵:
{if i = j then 1 else i + j for i in 1 : size(A, 1), j in 1 : size(A, 2)}
# 数组连接
数组可以通过函数 cat(k, A, B, C, …) 执行连接操作。例如:
cat(1, {1, 2}, {3, 4, 5}) // {1, 2, 3, 4, 5}
cat(2, {{1, 2}, {{3, 4}, {{5}, {6}}}}) // {{1, 2, 3}, {4, 5, 6}}
函数 cat(k, A, B, C, ...) 按照下列规则沿维数 k 连接数组 A, B, C, ...:
数组 A, B, C, ...必须具有相同数目的维数,即 ndims(A) = ndims(B) = ...;
数组 A, B, C, ...必须类型等价。结果数组的数据类型是这些实参的最大扩展类型。最大扩展类型应该是等价的。Real 和 Integer 子类型可以混用,产生一个 Real 结果数组,其中 Integer 数值已被转换为 Real 数值;
k 必须是(这些实参数组)存在的维数,即,1 <= k <= ndims(A) = ndims(B) = ndims(C);k 应为整数;
大小匹配:除了第 k 维的大小之外,数组 A, B, C, ... 必须具有相同的数组大小,即,对于 1 <= j <= ndims(A) 且 j <> k,size(A,j) = size(B,j)。
有一种特殊的语法用于沿第一维和第二维的连接:
沿第一维的连接 [A; B; C; …];
沿第二维的连接 [A, B, C, …];
这两种方式可以混用。[…, …] 优先级高于 […; …],例如 [a, b; c, d] 解析为 [[a, b]; [c, d]]。
需要注意的是,在执行沿第一维或第二维的数组连接之前,将所有元素提升为矩阵,这样矩阵可通过标量或者向量来构造。例如:
Real m2[3, 3] = [1, 2, 3; 4, 5, 6; 7, 8, 9];
Real m1[3, 3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
上面两种形式是等价的,显然第一种形式更为简洁易读。
更多例子:
Real s1, s2, v1[n1], v2[n2], M1[m1, n], M2[m2, n], M3[n, m1], M4[n, m2], K1[m1, n, k], K2[m2, n, k];
[s1]是1´1矩阵
[v1]是n1´1矩阵
[v1; v2]是(n1+n2)´1矩阵
[M1; M2]是(m1+m2)´n矩阵
[M3, M4]是n´(m1+m2)矩阵
[K1; K2]是(m1+m2) ´ n ´ k数组
[s1; s2]是2´1矩阵
[s1, s1]是1´2矩阵
Real[3] v1 = array(1, 2, 3);
Real[3] v2 = {4, 5, 6};
Real[3, 2] m1 = [v1, v2];
Real[3, 2] m2 = [v1, [4;5;6]]; // m1 = m2
Real[2, 3] m3 = [1, 2, 3; 4, 5, 6];
Real[1, 3] m4 = [1, 2, 3];
Real[3, 1] m5 = [1; 2; 3];
# 数组索引与切片
数组索引操作符“[…]”用来访问数组元素的值。通过索引既可以访问相应数组元素的值,也可以修改它们的值。数组索引操作所耗费的时间为常量,跟数组大小无关。
数组索引的语法形式如下:
arrayname[indexexpr1, indexexpr2, …]
例如:
Real M[2, 3] = [1, 2, 3; 4, 5, 6];
Real M2[2];
Real x = M[1, 1]; // 取矩阵M的第1行第1列
Real y = M[2, 3]; // 取矩阵M的第2行第3列
M2[1] = x; // 对向量M2的第1个元素赋值
M2[2] = y; // 对向量M2的第2个元素赋值
索引表达式可以是整型标量,也可以是整型向量表达式。索引还可以是布尔类型和枚举类型,例如:
type Colors = enumeration(red, green, blue);
Real e[Colors];
e[Colors.red] = 1;
e[Colors.green] = 3;
e[Colors.blue] = 5;
Real b[Boolean] = {2.2, 3.3};
Real x = b[false]; // x = 2.2
Real y = b[true]; // y = 3.3
标量索引表达式用来访问单个数组元素,向量索引表达式则用来访问数组的某个划分,故称之为“切片”操作。切片操作能够挑选出向量、矩阵和数组中选定的行、列和元素。冒号用于表示某一维所有下标。表达式 end 只能出现于数组下标中,如果用于数组表达式 A 的第 i 个下标,假设 A 的下标为 Integer 子类型,那么它等价于 size(A, i);如果用于嵌套的数组下标中,则指向最近的嵌套数组。如果下标是向量,赋值按向量下标给定的顺序进行。
数组切片例子:
a[:, j]; // a的第j列向量
a[j : k] ; // {[a[j], a[j+1], ... , a[k]}
a[:, j : k] ; // [a[:,j], a[:,j+1], ... , a[:,k]]
v[2 : 2 : 8] ; // v[ {2, 4, 6, 8} ]
v[{j, k}]:={2, 3}; // 等同于v[j]:=2; v[k]:=3;
v[{1, 1}]:={2, 3}; // 等同于v[1]:=3;
A[end - 1, end]; // A[size(A,1)-1,size(A,2)]
A[v[end], end] ; // A[v[size(v, 1)],size(A,2)],第一个end引用v的end。
如果 x 是向量,则 x[1] 是标量,但是切片 x[1:5] 是向量(矢量值或冒号下标表达式导致一个向量被返回)。
下表说明了数组切片后的结果类型,假设 x[n, m]、v[k]、z[i, j, p] 已声明:
| 表达式 | 维数 | 结果类型 |
|---|---|---|
| x[1, 1] | 0 | 标量 |
| x[:, 1] | 1 | n 维向量 |
| x[1, :] | 1 | m 维向量 |
| v[1 : p] | 1 | p 维向量 |
| x[1: p, :] | 2 | p´m 矩阵 |
| x[1 : 1, :] | 2 | 1´m“行”矩阵 |
| x[{1, 3, 5}, :] | 2 | 3´m 矩阵 |
| x[:, v] | 2 | n´k 矩阵 |
| z[: , 3, :] | 2 | i´p 矩阵 |
| x[{1}, :] | 2 | 1´m“行”矩阵 |
# 数组运算
数组运算时,在所有需要 Real 子类型表达式的上下文中,Integer 子类型的表达式也可以使用;Integer 表达式被自动转换为 Real。若无特别说明,下文中的数值类型指 Real 或 Integer 类型的子类型。
# 等式与赋值
标量、向量、矩阵和数组的等式“a=b”与赋值“a:=b”是基于元素定义的,并且要求两个对象具有相同的维数和匹配的维数长度。操作数要求类型等价。
数组等式与赋值规则
| a 的类型 | b 的类型 | a = b 的结果 | 操作 (j=1:n, k=1:m) |
|---|---|---|---|
| Scalar | Scalar | Scalar | a = b |
| Vector[n] | Vector[n] | Vector[n] | a[j] = b[j] |
| Matrix[n, m] | Matrix[n, m] | Matrix[n, m] | a[j, k] = b[j, k] |
| Array[n, m, ...] | Array[n, m, ...] | Array[n, m, ...] | a[j, k, ...] = b[j, k, ...] |
# 加减
数值标量、向量、矩阵和数组的加“a + b”与减“a - b”是基于元素定义的,并要求 size(a) = size(b),a 和 b 均为数值类型。
字符串标量、向量、矩阵和数组的加“a + b”定义为从 a 到 b 的对应元素逐个字符串连接,并要求 size(a) = size(b)。字符串类型的减法未定义。
数组加减运算规则
| a 的类型 | b 的类型 | a+/-b 的结果 | 操作 c:=a+/-b (j=1:n, k=1:m) |
|---|---|---|---|
| Scalar | Scalar | Scalar | c := a +/- b |
| Vector[n] | Vector[n] | Vector[n] | c[j] := a[j] +/- b[j] |
| Matrix[n, m] | Matrix[n, m] | Matrix[n, m] | c[j, k] := a[j, k] +/- b[j, k] |
| Array[n, m, ...] | Array[n, m, ...] | Array[n, m, ...] | c [j, k, ...] := a[j, k, ...] +/- b[j, k, ...] |
例如:
{1, 2, 3} + 4 // 非法
{1, 2, 3} + {4, 5, 6} // {5, 7, 9}
[1, 2; 3, 4] + [5, 6; 7, 8] // {{6, 8}, {10, 12}}
# 乘法
数值标量 s 与数值标量、向量、矩阵或数组a的标量乘法“s * a”或“a * s”是基于元素定义的:
数值标量与数组之间的乘法运算规则
| s 的类型 | a 的类型 | s*a 和 a*s 的类型 | 操作 c:=s*a或c:=a*s (j=1:n, k=1:m) |
|---|---|---|---|
| Scalar | Scalar | Scalar | c := s * a |
| Scalar | Vector [n] | Vector [n] | c[j] := s* a[j] |
| Scalar | Matrix [n, m] | Matrix [n, m] | c[j, k] := s* a[j, k] |
| Scalar | Array[n, m, ...] | Array [n, m, ...] | c[j, k, ...] := s*a[j, k, ...] |
例如:
{1, 2, 3} * 3 // [3] ´ [0] = [3] à {3, 6, 9}
[1, 2; 3, 4] * 2 // [2, 2] ´ [0] = [2, 2] à {{2, 4}, {6, 8}}
数值向量和矩阵的乘法“a * b”只针对下列组合定义:
数值向量和矩阵的乘法运算规则
| a 的类型 | b 的类型 | a*b 的类型 | 操作 c:=a*b |
|---|---|---|---|
| Vector [n] | Vector [n] | Scalar | c := sumk(a[k]*b[k]), k=1:n |
| Vector [n] | Matrix [n, m] | Vector [m] | c[j] := sumk(a[k]*b[k, j])j=1:m, k=1:n |
| Matrix [n, m] | Vector [m] | Vector [n] | c[j] := sumk(a[j, k]*b[k]) |
| Matrix [n, m] | Matrix [m, p] | Matrix [n, p] | c[i, j] = sumk(a[i, k]*b[k, j])i=1:n, k=1:m, j=1:p |
例如:
[1, 2; 3, 4; 5, 6] * {1, 2} // [3, 2] ´ [2] = [3] à {5, 11, 17}
{1, 2} * [1, 2, 3; 4, 5, 6] // [2] ´ [2, 3] = [3] à {9, 12, 15}
{1, 2} * [1, 2; 3, 4; 5, 6] // [2] ´ [3, 2] 非法
[1, 2; 3, 4] * [3, 2; 1, 3]
// [2, 2] ´ [2, 2] = [2, 2] à {{5, 8}, {13, 18}}
[1, 2; 3, 4; 5, 6] * [1, 2; 3, 4; 5, 6] // [3, 2] ´ [3, 2] 非法
[1, 2; 3, 4; 5, 6] * [1, 2, 3; 4, 5, 6]
// [3, 2] ´ [2, 3] = [3, 3]
à {{9, 12, 15}, {19, 26, 33}, {29, 40, 51}}
# 除法
数值标量、向量、矩阵或数组 a 与数值标量 s 的除法“a/s”是基于元素定义的。结果总是 Real 类型。如果要得到带有截断的整数除法,可使用函数div()。
数组与数值标量的除法运算规则
| a 的类型 | b 的类型 | a/s 的结果 | 操作 c:=a/s(j=1:n, k=1:m) |
|---|---|---|---|
| Scalar | Scalar | Scalar | c := a / s |
| Vector[n] | Scalar | Vector[n] | c[k] := a[k] / s |
| Matrix[n, m] | Scalar | Matrix[n, m] | c[j, k] := a[j, k] / s |
| Array[n, m, ...] | Scalar | Array[n, m, ...] | c[j, k, ...] := a[j, k, ...] / s |
例如:
{3, 6, 9} / 3 // à {1, 2, 3}
3 / {3, 6, 9} // 非法
{1, 2, 3} / {3, 6, 9} // 非法
# 求幂
如果“a”和“b”都是数值类型的标量,求幂“a^b”定义为 C 语言中的函数pow()。
如果“a”是一个数值方阵,“s”是 Integer 子类型的标量,并且 s>=0,求幂“a^s”是有效的。求幂通过反复相乘进行。
例如:
a^3 = a*a*a;
a^0 = identity(size(a,1));
assert(size(a,1)==size(a,2), "矩阵必须是方阵");
a^1 = a;
提示
非整型指数是不允许的,因为这需要计算“a”的特征值和特征向量,不再是一种元素操作。
# 布尔运算
操作符“and”和“or”包括两个 Boolean 类型表达式,可以是标量或维数匹配的数组。操作符“not”包括一个 Boolean 类型的表达式,可以是标量或数组。结果为按位逻辑操作。类似于常规编程语言,“and”和“or”遵循短路计算的原则,即,如果一个表达式的值不影响计算结果,该表达式将不被计算。
例如:
Boolean v[n];
Boolean b;
Integer I;
equation
b = (I>=1 and I<=n) and v[I];
// v[I]保证不会越界,因为前面的and表达式为false时,v[I]不被计算
# 关系运算
关系操作符 <、<=、>、>=、==、<> 仅用于标量,数组之间不允许之间进行关系运算。
# 详细语法介绍
此页面只是对 Modelica 语言进行入门功能介绍,更详细的功能请参考 Modelica 语言规范的相关章节。
| 类型 | 对应 Modelica 规范章节 |
|---|---|
| 数组声明 | 数组声明 |
| 数组下标的下界和上界 | 数组声明 |
| 数组类型和类型检查 | 数组声明 |
| 数组构造 | 向量、矩阵和数组的构造 |
| 范围向量构造 | 向量、矩阵和数组的构造 |
| 带迭代器的数组构造 | 向量、矩阵和数组的构造 |
| 数组连接 | 标量、向量、矩阵和数组操作符函数 |
| 数组索引与切片 | 标量、向量、矩阵和数组操作符函数 |
| 数组运算 | 内置的数组操作函数 |
| 数组运算 | 矩阵和矢量的代数函数 |
| 数组运算 | 标量、向量、矩阵和数组操作符函数 |