# 数组


数组是一组同类型变量的集合。数组元素通过简单的整数下标来访问,范围从 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 规范章节
数组声明 数组声明
数组下标的下界和上界 数组声明
数组类型和类型检查 数组声明
数组构造 向量、矩阵和数组的构造
范围向量构造 向量、矩阵和数组的构造
带迭代器的数组构造 向量、矩阵和数组的构造
数组连接 标量、向量、矩阵和数组操作符函数
数组索引与切片 标量、向量、矩阵和数组操作符函数
数组运算 内置的数组操作函数
数组运算 矩阵和矢量的代数函数
数组运算 标量、向量、矩阵和数组操作符函数