# 运算符与表达式
词法单元可以按照附录 A Modelica 具体语法中的 Modelica 语法的表达式部分的规则组合成更大的构建块。例如,它们可以从操作符、函数引用、组件或组件引用(引用组件)和文字构建。每个表达式都有类型和可变性。
本章描述表达式的计算规则,表达式可变性的概念,内置数学操作符和函数,内置的函数形式的特殊 Modelica 操作符。
表达式可以包含有变量和常量,表达式有类型,包括预定义的或由用户定义的。Modelica 的预定义内置类型是 Real、Integer、Boolean、String 和枚举类型,在预定义类型有详细的介绍。
# 表达式
Modelica 的方程、赋值和声明方程中含有表达式。
表达式可以包含基本的操作符,如 +、-、*、/、^ 等,其优先级定义见操作符的优先级和结合特性的表,语法见附录 A Modelica 具体语法。在标量、向量、矩阵和数组操作符函数,定义了针对标量和数组参数的操作符的语义。
也可以定义函数,并用常见的方式调用它们。函数的位置输入参数或命名输入参数描述了位置参数和命名参数的函数调用语法初始化和方程绑定描述了向量化函数调用的语法。内置的数组函数在数组维的上下界给出,其它的内部操作符在
函数形式的内置固有操作符给出。
# 操作符的优先级和结合特性
操作符优先级决定带有操作符的表达式的隐式子表达式结构。(通过将子表达式括在括号内可以明确表示子表达式结构。)优先级较高的操作符比优先级较低的操作符与其操作数的联系更紧密。例如,“” 的优先级高于 “+”,意味着 1+23 隐式构造为 1+(2*3)。
当操作符属于相同优先级的同一组时,优先级组结合性用于确定隐式子表达式结构。左结合性意味着子表达式是从左到右形成的。例如,二元加法操作符的左结合性意味着 1-2-3 隐式构造为 (1-2)-3。优先级组也可以是非结合性的,这意味着基于结合性没有确定隐式子表达式结构。例如,关系操作符的非结合性意味着 1 < 2 < 3 是无效表达式。请注意,操作符不需要相同才能使结合性发挥作用;1==2<3 也是无效,并且 1-2 +3 被隐式地构造为 (1-2)+3。还需要注意,Modelica 中的非结合数组范围可以与两个或三个由 “:” 分隔的操作数一起使用,这意味着 1:2:5 是该操作符的一种有效三元用法,而不是该操作符的两种无效的二元用法。
在解析阶段——也就是这里定义的操作符优先级和结合性有关的地方——子表达式结构是固定的。由于 Modelica 工具可以自由地以符号方式操作表达式,所以不能期望这个子表达式结构反映求值的顺序,参见求值顺序。
以下表格展示了所有表达式操作符的优先级和结合性,与附录 A Modelica 具体语法中的 Modelica 语法一致并予以补充。
下表按优先级从高到低排列的操作符。具有不同优先级的操作符由水平线分隔。所有操作符都是二元的,除了后缀操作符以及与 expr 一起显示为一元的操作符、条件操作符、数组构造操作符 { } 和连接操作符 [ ] 以及以及既可为二元也可为三元操作符的数组范围构造符。数组构造和连接的结合性指的是分隔符(“,” 或 “;”),而不是封闭的定界符。
| Operator | Assoc. | Operator syntax | Examples |
|---|---|---|---|
| Postfix array index | left | [] | arr[index] |
| Postfix access | left | . | a.b |
| Postfix function call | none | funcName(args) | sin(4.36) |
| Array construction | left† | {expr, expr, …} | {2, 3} |
| Horizontal concatenation | left† | [expr, expr, …] | [5, 6] |
| Vertical concatenation | left† | [expr; expr; …] | [2, 3; 7, 8] |
| Exponentiation | none | ^ | 2 ^ 3 |
| Multiplicative | left | * / | 2 * 3 |
| Elementwise multiplicative | left | .* ./ | {2, 3} .* {4, 5} |
| Additive unary | none | +expr -expr | -0.5 |
| Additive | left | + - | 1 + 2 |
| Elementwise additive | left | .+ .- | {2, 3} .+ {4, 5} |
| Relational | none | < <= > >= == <> | a < b |
| Unary negation | none | not expr | not b1 |
| Logical and | left | and | b1 and b2 |
| Logical or | left | or | b1 or b2 |
| Array range | none | expr : expr | 1 : 5 |
| Array range | none | expr : expr : expr | start : step : stop |
| Conditional | none | if expr then expr else expr | if b then 3 else x |
| Named argument | none | ident = expr | x = 2.26 |
后缀数组索引和后缀访问操作符不是正常意义上的表达式操作符,即 a.b[1] 可以被视为 a.(b[1])。相反,需要联合考虑这些操作符来识别整个组件引用(语法中主要的替代产生式之一),它是本身可以被视为表达式的最小单元。 Postfix 数组索引和 postifx 访问只能立即应用于组件引用;甚至不允许在左操作数周围使用括号。
示例:后缀数组索引和后缀访问的相对优先级。考虑数组变量 a 的以下定义:
record R
Real[2] x;
end R;
R[3] a;
以下是使用后缀数组索引和后缀访问的一些有效和无效方法:
- a[3].x[2] // OK:Real 类型的组件引用
- a[3].x // OK:Real[2] 类型的组件引用
- a.x[2] // OK:Real[3] 类型的组件引用
- a.x // OK:Real 类型的组件引用[3, 2]
- (a.x)[2] // 错误:括号使用无效
- a[3] // OK:R 类型的组件引用
- (a[3]).x // 错误:括号使用无效
a.x、a.x[2] 和 (a.x)[2] 之间的关系说明了给予数组索引比后缀访问更高的优先级的效果。如果优先级相等,这会将 a.x[2] 的含义更改为 (a.x)[2] 试图表达的相同内容,即 Real[2] 类型的组件引用。
示例:非关联求幂和数组范围操作符(请注意,数组范围操作符仅采用标量操作数):
- x ^ y ^ z // 不合法,请使用括号来明确说明。
- a : b : c : d // 不合法,并且括号不能使其合法。
注意
加法一元表达式仅允许出现在和的第一项中,即不能紧邻任何加法或元素加法操作符的右侧。例如,1 + -1 + 1 是无效表达式(根据附录 A Modelica 具体语法 无法解析),而 1 + (-1) + 1 和 -1 + 1 + 1 都可以。
示例:Modelica 中一元减号和加号操作符与 Mathematica 和 MATLAB 中的稍有不同,因为下面的表达式是不合规定的(而在 Mathematica 和MATLAB 中是有效的):
- 2*-2 // 在 Mathematica/MATLAB 中 = -4 in; 在 Modelica 中不合规定
- --2 //在 Mathematica/MATLAB 中 = 2 in; 在 Modelica 中不合规定
- ++2 //在 Mathematica/MATLAB 中 = 2 in; 在 Modelica 中不合规定
- 2--2 //在 Mathematica/MATLAB 中 = 4 in; 在 Modelica 中不合规定
条件操作符还可以包括 elseif 分支。
等号 = 和赋值 := 不是表达式操作符,因为它们只能分别出现在方程和赋值语句中。
(操作符优先级表在生成 Modelica 表达式树的文本表示时非常有用。)
注意
在执行此操作时,一元加法操作符只允许用于和的第一个项。一个简单的实现可能不会为像 1 + (-1)这样的表达式树产生所有所需的括号,因为它可能会认为一元操作符的更高优先级使得括号变得多余。解决此问题的一个技巧是将加法一元操作符视为与二元加法操作符具有相同优先级的左结合。
# 求值顺序
Modelica 工具可以自由地求解方程、重排表达式,或者忽略对某些表达式的计算,只要这不影响计算结果(例如布尔表达式的短路计算)。If- 语句和 if- 表达式保证只有条件为真时才计算相应的子句, 但是,生成状态事件或时间事件的关系操作符,在连续积分期间将保持其在最近的事件中得到的值。
如果数值操作溢出,其计算结果是未定义的。对于文字常数,建议将数字自动转换成为另一种精度更高的类型。
示例:如果要防止表达式求值错误,应该使用 if:
Boolean v[n];
Boolean b;
Integer I;
equation
x=v[I] and (I>=1 and I<=n); // 错误
x=if (I>=1 and I<=n) then v[I] else false; //正确
要防止对负数求平方根,使用 noEvent:
der(h)=if h>0 then -c*sqrt(h) else 0; // 错误
der(h)=if noEvent(h>0) then -c*sqrt(h) else 0; // 正确
# 算术操作符
Modelica 支持 5 种二元操作符,可对任意数值型操作数进行运算:
| 操作符 | 描述 |
|---|---|
| ^ | 求幂 |
| * | 乘法 |
| / | 除法 |
| + | 加法 |
| - | 减法 |
其中某些操作符可以用于标量操作数和数组操作数混合的运算,详见标量、向量、矩阵和数组操作符函数。这些操作符的语法由如下规则定义:
arithmetic-expression :
[ add-operator ] term { add-operator term }
add-operator :
"+" | "-"
term :
factor { mul-operator factor }
mul-operator :
"*" | "/"
factor :
primary [ "^" primary ]
# 等于、关系和逻辑操作符
Modelica 支持标准的关系操作符和逻辑操作符,这些操作符的结果是标准的布尔类型值 ture 或者 false。
| 操作符 | 描述 |
|---|---|
| > | 大于 |
| >= | 大于等于 |
| < | 小于 |
| <= | 小于等于 |
| == | 表达式中的等于 |
| <> | 不等于 |
单个等于符号 = 从不用在关系式中,仅仅用在方程表达式中方程,等式与赋值和函数调用的命名参数传递时。见函数的位置输入参数或命名输入参数。
定义了如下逻辑操作符:
| 操作符 | 描述 |
|---|---|
| not | 否定,一元操作符 |
| and | 逻辑与 |
| or | 逻辑非 |
关系操作符和逻辑操作符的语法定义:
logical-expression :
logical-term { or logical-term }
logical-term :
logical-factor { and logical-factor }
logical-factor :
[ not ] relation
relation :
arithmetic-expression [ relational-operator arithmetic-expression ]
relational-operator :
"<" | "<=" | ">" | ">=" | "==" | "<>"
关系操作符满足:
- 关系操作符 <、<=、>、>=、==、<> 只针对简单类型的标量操作数定义。结果为布尔类型, 根据关系是否满足相应地取 true 或 false。
- 对于 String 类型的操作数,对每个关系操作符 op,str1 op str2 使用 C 函数 strcmp 定义为 strcmp(str1, str2) op 0。
- 对于 Boolean 类型的操作数,false < true。
- 对于枚举类型的操作数,其顺序按照枚举项声明的顺序确定。
- 在形如 v1 == v2 或 v1 <> v2 的关系式中,除非在函数中使用,否则 v1 或 v2 不能为 Real 类型的子类型。(该规则的原因在于,对 Real 型的关系操作将被转换为状态事件见事件与同步, 而这种转换对于 == 和 <> 关系操作符变得无谓的复杂(如,需要两个而不是一个 crossing (交叉)函数,甚至在 event instants(事件时刻)也需要采用 epsilon 策略)。而且,在寄存器与内存中的数值长度不相等的机器中,测试实型变量是否相等是有问题的。)
- “v1 rel_op v2” 形式的关系式被称为基本关系,其中 v1 和 v2 为变量,rel_op 为关系操作符。如果 v1 或 v2 之一或者两者都为实型的子类型,则称为实型基本关系。
- 关系操作符可以生成事件,请参阅离散时间表达式。
# 其它操作符和变量
Modelica 也包含一些内置操作符,不是标准算术、关系或者逻辑操作符。下面对这些操作符进行描述,其中包括 time,它是一个内置变量,不是操作符。
# 字符串连接
字符串的连接附录 A Modelica 具体语法在 Modelica 中用 + 操作符表示 (例如,"a" + "b"的结果是 "ab")。
# 数组构造操作符
数组构造操作符{…}在向量、矩阵和数组的构造中描述。
# 数组连接操作符
数组连接操作符[…]在数组串接中描述。
# 数组范围操作符
数组范围构造操作符:在向量构造中描述。
# If 表达式
表达式:
if expression1 then expression2 else expression3
是 if 表达式的一个例子。expression1 必须为布尔表达式,被首先求值。如果 expression1 为真,则计算 expression2,其值作为 if 表达式的值,否则计算 expression3,其值作为 if 表达式的值。expression2 和 expression3 这两个表达式的类型必须兼容,决定了 if 表达式的类型类型相容的表达式。带 elseif 的 if 表达式, 通过用 else if 替换 elseif 来定义。(注意:elseif 在 if 子句中对称增加。) 对于短路计算,见求值顺序。
(表达式中的 elseif 已添加到 Modelica 语言中,以与 if 方程保持对称。)
示例:
Integer i;
Integer sign_of_i1 = if i < 0 then -1 elseif i == 0 then 0 else 1;
Integer sign_of_i2 = if i < 0 then -1 else if i == 0 then 0 else 1;
# 成员访问操作符
可以使用点操作符 . 访问类实例成员。
(例:用 R1.R 访问电阻 R1 的组件 R。另一个使用点操作符的情况:对于类内部定义的类型或类,当然也可以使用点操作符来访问,但是是对类的名字而不是类的实例使用点操作符。)
# 内置变量 time
所有声明的变量都是变量 time 的函数。变量 time 是内置变量,在所有模型和模块中都可当作输入变量来使用。time 被隐含地定义为:
input Real time (final quantity = "Time",
final unit = "s");
time 的 start 属性的值是仿真的开始时间。
示例:
encapsulated model SineSource
import Modelica.Math.sin;
connector OutPort = output Real;
OutPort y = sin(time); // Uses the built-in variable time.
end SineSource;
# 函数形式的内置固有操作符
Modelica 的某些内置操作符和函数调用有一样的语法,但是,它们的行为不像是数学函数,因为结果不仅与输入有关,还与仿真的状况有关。
也有某些内置函数的输出只依赖于输入,但它们除了返回结果值外,还可以触发事件。“固有 (intrinsic)” 的意思是他们定义在 Modelica 的语言层次而不是定义在库中。内部固有的操作符 / 函数有:
- 数学函数和转换函数,见数值函数和转换函数。
- 导数和函数形式的特殊用途函数,见函数形式的微分和特殊用途操作符。
- 函数形式的事件相关操作符,见函数形式的与事件有关的操作符。
- 数组操作符 / 函数,见数组维的上下界。
请注意,当规范引用一个与内置函数同名的函数时,它引用的是内置函数,而不是一个与之同名的用户定义的函数,也可以参阅内置函数。除了内置的 String 操作符外,本节中的所有操作符只能用位置参数调用。
# 数值函数和转换函数
以下列出的数学函数和转换操作符不会生成事件。
| 表示 | 描述 | 细节 |
|---|---|---|
| abs(v) | 绝对值(无事件) | 函数 3.1 |
| sign(v) | 论证符号(无事件) | 函数 3.2 |
| sqrt(v) | 平方根 | 函数 3.3 |
| Integer(e) | 从枚举到整数的转换 | 操作符 3.1 |
| EnumTypeName(i) | 从整数到枚举的转换 | 操作符 3.2 |
| String(…) | 转换为字符串 | 操作符 3.3 |
根据动态向量,除字符串转换操作符之外都是可矢量化的。其他非事件生成数学函数在内置的数学函数和外部的内置函数中描述,而事件触发数学函数在事件触发数学函数中描述。
函数 3.1 abs
abs(v)
扩展为 no Event( if v>=0 then v else –v )。 要求参数 v 为整型或实型表达式。 通过不生成事件,确保所有 𝑥 的属性 abs( 𝑥 )≥0 的代价是有时会在事件之间产生不连续变化的导数。 需要无事件语义的典型情况是形如 abs(x) * x = y 的流方程。有事件生成时,该方程会在事件发生时,在两种形式 x^2 = y 和 -x^2 = y 之间切换,其中的事件不会恰好与y的符号变化相一致。当y经过零时,方程的两种形式都不会在y的一个开放邻域内有解,因此在 y 足够接近 0 的某个点上求解方程必定失败。另一方面,在没有事件生成的情况下,方程可以很容易地求解 x ,即使 y 为零。请注意,如果没有事件生成,尽管 abs(x) 有一个不连续的导数,但 abs(x) * x 的导数从不突变。
这个方程的逆形式是 x = sign(y) * sqrt(abs(y))。有事件生成时,当寻找 abs(y) 的零交点时,对负数调用 sqrt 会失败,参见事件与同步。另一方面,如果没有事件生成,计算 sqrt(abs(y)) 永远不会失败。
函数 3.2 sign
sign (v)
展开为 noEvent(if v > 0 then 1 else if v < 0 then –1 else 0)。参数 v 需要是一个 Integer 或 Real 表达式。
函数 3.3 sqrt
sqrt (v)
如果 v ≥ 0,返回 v 的平方根,否则会发生错误。参数 v 需要是一个 Integer 或 Real 表达式。
操作符 3.1 Integer
Integer (e)
返回枚举类型的表达式 e 的序数,该表达式评估为枚举值 E.enumvalue,其中 Integer(E.e1) = 1,Integer(E.en) = n,对于一个枚举类型 E = enumeration(e1, ..., en)。请参阅枚举值到 String/Integer 的类型变换。
操作符 3.2 EnumTypeName(i)
EnumTypeName (i)
对于任何枚举类型 EnumTypeName,返回枚举值 EnumTypeName.e,比如 Integer (EnumTypeName.e)=i。请参考上面 Integer 的定义。尝试转换 i 的值,这些值不对应于枚举类型的值,是一个错误。请参见未指定枚举。
操作符 3.3 String
String(b, <options>)
String(i, <options>)
String(i, format=s)
String(r, <options>)
String(r, format=s)
String(e, <options>)
将标量非字符串表达式转换为字符串表示。第一个参数可以是布尔型的 b、整型 i、实型 r 或枚举值 e 见枚举值到 String/Integer 的类型变换。⟨options⟩ 可以选择以下命名参数的零个或多个(这些参数不能作为位置参数传递):
- Integer minimumLength = 0:返回结果字符串的最小长度。如有必要,可用空格填充未用空间。
- Boolean leftJustified = true:如果为真,转换的结果在字符串中左对齐;如果为假,右对齐。
- Integer significantDigits = 6:结果字符串中的有效位数,仅在数据格式为实型时才生效。 在标准类型强制转换描述的标准类型强制转换不适用于 String 的第一个参数。因此,当 String 的第一个参数是一个 Integer 表达式时,指定 significantDigits 是一个错误。
对于 Real 表达式,输出应根据 Modelica 语法。
(使用6个有效数字格式的实数值示例:12.3456, 0.0123456, 12345600, 1.23456E-10。)
与 ⟨options⟩ 对应的格式字符串为:
- 对于实型 Real:
(if leftJustified then "-" else "") + String(minimumLength)+ "." + String(signficantDigits) + "g" - 对于整型 Integer:
(if leftJustified then "-" else "") + String(minimumLength) + "d"
ANSI-C 风格的格式字符串(不能与其他任何命名参数结合使用)由一个前面没有%的单个转换修饰符组成。它不应包含长度修饰符(length modifier),并且不应使用 * 表示宽度和 / 或表示精度。对于实型和整型,允许使用 f、e、E、g、G 转换修饰符。对于整型,还允许使用 d、i、o、x、X、u 和 c 等转换修饰符。对于实数类型使用整型转换修饰符是一个不推荐的特性,Modelica 工具将通过四舍五入、截断或选择实数转换修饰符之一来获的结果。
对于整型的 x、X(十六进制)和 c(字符)给出的结果与 Modelica 语法不一致。
示例:一些值得一提的情况:
- String(4.0, format = "g") 产生的 4 不是一个有效的实型字面量。然而,它是一个整型字面量,可以在 Modelica 代码的几乎任何地方替代实型字面量 4.0(其中 String 的第一个参数是一个显著的例外)。
- String(4, format = ".3f") 使用 String 的整型情况,因为对第一个参数没有自动类型强制转换。一个可能的实现是在内部将该值转换为浮点数,然后返回 format = ".3f" 的实数。
- String(4611686018427387648, format = ".0f")(在具有 64 位 IntegerType 的实现中是有效的整数值)如果应用了内部转换为 64 位双精度浮点数可能会产生 4611686018427387904(与输入值不等)的情况。
# 事件触发数学函数
下面列出的操作符在 when-clause 之外和 clocked 离散时间分区之外使用时会触发事件(参见时钟离散时间和离散化连续时间)。
| 表示 | 描述 | 详情 |
|---|---|---|
| div( x , y ) | 向零截断的除法 | 操作符 3.4 |
| mod( x , y ) | 整数模 | 操作符 3.5 |
| rem( x , y ) | 整余数 | 操作符 3.6 |
| ceil( x ) | 返回不小于 x 的最小整数 | 操作符 3.7 |
| floor( x ) | 返回不大于 x 的最大整数 | 操作符 3.8 |
| integer( x ) | 返回不大于 x 的最大整数 | 操作符 3.9 |
这些表达式,如 div、ceil、floor 和 integer,都是产生事件的表达式。mod(x,y) 的产生事件表达式是 floor(x/y),而 rem(x,y) 的产生事件表达式是 div(x,y) - 也就是说,当 mod 或 rem 在一个区间内连续改变时,不会产生事件,但当它们在从一个区间突然跳到下一个区间时,会产生事件。
提示
如果不希望如此,可以应用 noEvent 操作符。例如,noEvent(integer(v))。
操作符 3.4 div
div(x, y)
返回 x/y 的商,舍弃小数部分(也称为向零截断)。函数的结果和参数为实型或整型,如果其中一个参数为实型,结果就为实型,否则为整型。
注意
这是在 C99 中定义的除号 /;而在 C89 中,负数的结果是由实现定义的,因此必须使用标准函数 div()。
操作符 3.5 mod
mod(x, y)
返回 x/y 的整数模,即 mod(x, y)=x-floor(x/y)*y。函数的结果和参数为实型或整型,如果其中一个参数为实型,结果就为实型,否则为整型。
注意
在 when 子句外部,当返回值的变化不连续时,会触发状态事件。示例: mod(3,1.4) = 0.2, mod(-3,1.4) = 1.2, mod(3,-1.4) = -1.2。
操作符 3.6 rem
rem(x, y)
返回 x/y 的整余数,使 div(x, y) * y + rem(x, y) = x 。函数的结果和参数为实型或整型,如果其中一个参数为实型,结果就为实型,否则为整型。
注意
在 when 子句外部,当返回值的变化不连续时,会触发状态事件。示例, rem(3,1.4) = 0.2, rem(-3,1.4) = -0.2。
操作符 3.7 ceil
ceil (x)
返回不小于 x 的最小整数。结果和参数为实型。
注意
在 when 子句外部,当返回值的变化不连续时,会触发状态事件。
操作符 3.8 floor
floor (x)
返回不大于 x 的最大整数。结果和参数为实型。
注意
在 when 子句外部,当返回值的变化不连续时,会触发状态事件。
操作符 3.9 integer
integer (x)
返回不大于 x 的最大整数。参数为实型,结果为整型。
注意
在 when 子句外部,当返回值的变化不连续时,会触发状态事件。
# 内置的数学函数和外部的内置函数
下面列出的函数是基础数学函数。工具预期会利用这些函数的众所周知的性质(导数、逆等)来对表达式和方程进行符号处理。
| 表示 | 描述 | 详情 |
|---|---|---|
| sin(x) | 正弦 | |
| cos(x) | 余弦 | |
| tan(x) | 正切 ( x 不应为: ..., -π/2, π/2, 3π/2, ...) | |
| asin(x) | 反正弦函数 (-1 ≤ x ≤ 1) | |
| acos(x) | 反余弦函数 (-1 ≤ x ≤ 1) | |
| atan(x) | 反正切函数 | |
| atan2(x,y) | y/x 的反正切的主值 | 函数 3.4 |
| sinh(x) | 双曲正弦函数 | |
| cosh(x) | 双曲余弦函数 | |
| tanh(x) | 双曲正切函数 | |
| exp(x) | 以 e 为底的指数函数 | |
| log(x) | 自然 (底数 e)对数函数(x > 0) | |
| log10(x) | 以 10 为底的对数函数 (x > 0) |
这些函数是唯一可以使用不推荐使用的"内置"外部语言调用的函数,参见外部函数接口。
提示
有关基础数学函数的面向最终用户的信息,可以在 Modelica.Math 包中找到相应函数。
函数 3.4 atan2
atan2 (y, x)
根据两个参数的符号确定结果的象限,得到 y/x 的反正切的主值。结果
# 函数形式的微分和特殊用途操作符
下面列出的操作符包括导数操作符和具有函数语法的特殊用途操作符。
| 表示 | 描述 | 详情 |
|---|---|---|
| der(expr) | 时间导数 | 操作符 3.10 |
| delay(expr, ...) | 延时 | 操作符 3.11 |
| cardinality(c) | 连接方程式中出现的次数 | 操作符 3.12 |
| homotopy(actual,simplified) | 同伦初始化 | 操作符 3.13 |
| semiLinear(x,k+,k-) | 符号相关斜率 | 操作符 3.14 |
| inStream(v) | 流入组件的流变量 | 操作符 3.15 |
| actualStream(v) | 流变量的实际值 | 操作符 3.16 |
| spatialDistribution(...) | 变速度传递 | 操作符 3.17 |
| getInstanceName() | 调用位置的实例名称 | 操作符 3.18 |
以下调用使用命名参数的函数形式的特殊用途操作符可以使用命名参数(使用指定的名称)或位置参数(函数的输入按以下调用中给出的顺序)进行调用。
操作符 3.10 der
der(expr )
expr 的时间导数。表达式 expr 必须为实型的子类型。表达式及其所有子表达式必须是可微分的。如果 expr 是数组,该操作符应用到数组中的所有元素。对于非标量参数,函数可按函数的向量化调用矢量化。
提示
对于实型参数和常量,结果为维数与变量相同的零标量或数组。
操作符 3.11 delay
delay (expr , delayTime , delayMax )
delay (expr , delayTime)
当 time > time.start+delayTime 时 返 回 “expr(time–delayTime)”,当 time <= time.start+delayTime 时返回 “expr(time.start)”。 参数,即 expr, delayTime 和 delayMax,要求为实型的子类型,另外 delayMax 还要求为参数表达式。应满足 0 <= delayTime <= delayMax,否则出错。如果参数表中未提供 delayMax,则 delayTime 必须为参数表达式。对于非标量参数,根据函数的向量化调用,该函数会进行矢量化。更多细节,请参见延迟 delay。
操作符 3.12 cardinality
cardinality (c)
返回连接器实例 c 在一个 connect 语句中(作为内部和外部连接器)出现的次数,为整数类型。参见函数形式的微分和特殊用途操作符。
提示
这是一个要废弃的操作符。不应该再使用它,因为在以后的版本中会将它去掉。
操作符 3.13 homotopy
homotopy ( actual = actual , simplified = simplified )
标量表达式 actual 和 simplified 是 Real 的子类型。Modelica 翻译器应将此操作符映射为以下两种形式之一:
- 返回 actual(简单的实现)。
- 为了求解代数方程组,操作符在解决过程中可能返回两个参数的组合,最终结果为 actual。
[示例:actual · λ + simplified · (1 − λ),其中 λ 是从 0 到 1 的同伦参数。]
该解必须满足返回 actual 的同伦方程。对于非标量自变量,根据动态向量对函数进行矢量化。有关更多详细信息,请参阅同伦 homotopy。
操作符 3.14 semiLinear
semiLinear (x, k+, k−)
返回:smooth(0, if x >= 0 then k+ * x else k− * x)。结果是 Real 类型。对于非标量参数,该函数根据函数的向量化调用进行矢量化。有关更多详细信息,请参阅 semiLinear(尤其是当 x = 0 的情况)。
操作符 3.15 inStream
inStream (v)
instream(v) 只允许用于流连接器中定义的流变量 v ,并且假设流是从连接点到组件的,它是接近连接点的流变量 v 的值。该值是根据流变量和流变量的流连接方程来计算的。该操作符是可矢量化的。有关更多详细信息,请参阅附流操作符 inStream 和连接方程。
操作符 3.16 actualStream
actualStream (v)
actualStream(v) 返回流变量 v 的实际值,适用于任何流向。该操作符是可矢量化的。有关更多详细信息,请参阅附流操作符。
操作符 3.17 spatialDistribution
spatialDistribution (
in0 = in0 , in1 = in1 , x = x,
positiveVelocity = . . .,
initialPoints = . . .,
initialValues = . . .)
patialDistribution 允许对属性的变速传输进行近似。有关更多详细信息,请参阅空间分布 spatialDistribution。
操作符 3.18 getInstanceName
getInstanceName ()
返回一个字符串,该字符串包含正在模拟的模型 / 块的名称,后面附加了调用此函数的实例的完全限定名称。有关更多详细信息,请参阅 getInstanceName。
下面将更详细地描述其中的一些操作符。
# 延迟 delay
操作符 delay() 允许多种合理的数值实现方式,比如在(内部)积分器多项式中插值,更简单的实现方式是在包含表达式 expr 历史值的缓冲器中线性插值。如无更多信息,需要存储延迟信号的所有历史值,因为在仿真过程中延迟时间可能会改变。为避免过量的存储需求并提高效率,必须通过 delayMax 给出允许的最大延迟时间。
这给出了必须存贮的延迟信号值的(数量)上限。对于采用固定步长积分器的实时仿真,该信息足以在仿真开始前确定要分配的内部缓冲区的大小。对于变步长积分器,缓冲区的大小在积分期间是动态变化的。原则上,delay 操作符能够断开代数环,但为了简单起见没有支持这种功能,因为必须通过额外的参数给出最小延迟时间并在编译期间保持不变。另外,为避免在延迟缓冲区中外推插值, 积分器的最大步长受限于最小延迟时间。
# 空间分布 spatialDistribution
许多应用涉及对属性的变速传输的建模。对这个无限维系统进行建模的一种选择是通过 ODE 进行近似,但这需要大量的状态变量,并可能引入数值扩散或数值振荡。另一种选择是使用内置操作符,通过对存储的分布进行适当的采样、插值和移位来跟踪 z(x, t) 的空间分布。在这种情况下,操作符的内部状态对 ODE 求解器是隐藏的。
空间分布允许以良好的精度高效地解决下面的无限维问题:
其中 z(x, t) 是被传输的量,x 是归一化空间坐标,t 是时间,v(t) = der(x) 是归一化传输速度,并且边界条件设置为 x = 0.0 或 x = 1.0,具体取决于速度的符号。调用语法为:
(out0, out1) = spatialDistribution(in0, in1, x, positiveVelocity,
initialPoints = {0.0, 1.0},
initialValues = {0.0, 0.0});
其中 in0、in1、out0、out1 和 x 都是 Real 的子类型,positiveVelocity 是一个布尔值,initialPoints 和 initialValues 是相同大小的 Real 子类型数组,包含描述 z(x, t0) 初始分布的一组点的 x 坐标和 z 值。out0 和 out1 由 z(0.0, t) 和 z(1.0, t) 的解给出;而 in0 和 in1 是 z(0.0, t) 和 z(1.0, t) 的边界条件(在每个时间点,只有 in0 和 in1 中的一个被使用)。initialPoints 数组中的元素必须按非降序排序。根据第12.4.6节描述的矢量化规则,该操作符不能被矢量化。该操作符只能根据 in0 和 in1 的参数(它们必须有相同的大小)进行矢量化,返回相同大小的矢量化后输出 out0 和 out1;initialPoints 和 initialValues 的参数相应地被矢量化。解 z 可以用特征来描述:
这允许基于边界条件的插值直接计算解。空间分布可以用块中给出的伪代码来描述:
block spatialDistribution
input Real in0;
input Real in1;
input Real x;
input Boolean positiveVelocity;
parameter Real initialPoints(each min=0, each max=1)[:] = {0.0, 1.0};
parameter Real initialValues[:] = {0.0, 0.0};
output Real out0;
output Real out1;
protected
Real points[:];
Real values[:];
Real x0;
Integer m;
algorithm
/* The notation
* x <and then> y
* is used below as a shorthand for
* if x then y else false
* also known as "short−circuit evaluation of x and y".
*/
if positiveVelocity then
out1 := interpolate(points, values, 1 - (x - x0));
out0 := values[1]; // Similar to in0 but avoiding algebraic loop.
else
out0 := interpolate(points, values, 0 - (x - x0));
out1 := values[end]; // Similar to in1 but avoiding algebraic loop.
end if;
when <acceptedStep> then
if x > x0 then
m := size(points, 1);
while m > 0 <and then> points[m] + (x - x0) >= 1 loop
m := m - 1;
end while;
values := cat(1,
{in0},
values[1:m],
{interpolate(points, values, 1 - (x - x0))});
points := cat(1, {0}, points[1:m] .+ (x-x0), {1});
elseif x < x0 then
m := 1;
while m < size(points, 1) <and then> points[m] + (x - x0) <= 0 loop
m := m + 1;
end while;
values := cat(1,
{interpolate(points, values, 0 - (x - x0))},
values[m:end],
{in1});
points := cat(1, {0}, points[m:end] .+ (x - x0), {1});
end if;
x0 := x;
end when;
initial algorithm
x0 := x;
points := initialPoints;
values := initialValues;
end spatialDistribution;
注意
该实现有一个内部状态,因此不能在 Modelica 中描述为函数;initialPoints 和 initialValues 被声明为参数,以表示它们仅在初始化期间使用。
上述的无限维问题可以用以下方式来描述:
der(x) = v;
(out0, out1) = spatialDistribution(in0, in1, x, v >= 0,
initialPoints, initialValues);
事件在速度改变符号的确切时刻生成——如果不需要,可以使用 noEvent 来抑制事件生成。如果已知速度始终为正,则可以省略 out0,例如:
der(x) = v;
(, out1) = spatialDistribution(in0, 0, x, true, initialPoints, initialValues);
技术上相关的使用空间分布的案例包括电气传输线的建模、天然气、水和区域供热的管道和管网、洒水系统、在细长体中的脉冲传播、传送带和液压系统。对于在上述示例中的速度 v 传输多个数量的管道,需要进行矢量化。
# 基数(已弃用)cardinality
因为以下的原因,不推荐使用 cardinality 操作符,它将在未来的版本中被移除:
- Reflective 操作符使早期类型检查更困难。
- 几乎总被以奇怪的方式滥用。
- 不被用在键合图 (Bond graphs),尽管这是最初引入它的目的。
操作符 cardinality()允许在模型中定义由连接决定的方程,示例:
connector Pin
Real v;
flow Real i;
end Pin;
model Resistor
Pin p, n;
equation
assert(cardinality(p) > 0 and cardinality(n) > 0,
"Connectors p and n of Resistor must be connected");
// Equations of resistor
...
end Resistor;
基数是在删除条件组件后进行计数的,并且不应用于可扩展连接器、可扩展连接器中的元素或连接器数组(但可以应用于连接器数组的标量元素)。基数应该只在 assert 的条件和不包含 connect 和类似操作符的 if 语句中使用,请参阅时钟离散时间和离散化连续时间。
# 同伦 homotopy
在动态仿真问题的初始化阶段,经常需要通过迭代求解器解决大的非线性方程组。这种求解器的收敛性对未知变量的初值选择至关重要。通过提供模型的一个替代、简化版本,可以使该过程变得更加稳健,即使没有准确的初值,也能达到收敛,然后连续地将简化模型转化为实际模型。这种转换可以使用以下形式的表达式来描述:
在系统方程的表述中,这通常被称为同伦变换。如果精心选择了简化表达式,那么问题的解随λ连续变化,因此只要采取足够小的步骤,就有可能最终获得实际问题的解。该操作符可以用有序参数调用,或者为了提高可读性,最好用命名参数调用。建议在整个模型上执行(概念上的)一次同伦迭代,而不是在各个非线性代数方程组上执行多次同伦迭代。原因是可能存在以下结构:
w = f1(x) // has homotopy
0 = f2(der(x), x, z, w)
在这里,存在一个非线性方程系统 f2。然而,同伦用于作为非线性代数方程系统“输入”的变量,并修改了非线性代数方程系统的特性。唯一有用的方法是将 f1 和 f2 一起进行同伦迭代。建议的方法是“概念性的”,因为可以有更高效的实现,例如,通过确定最小的迭代循环,该循环包含存在同伦的第一个 BLT 块的方程和描述非线性代数方程系统的最后一个 BLT 块的所有方程。通过在全局范围内定义以下函数,可以获得同伦的简单实现:
function homotopy
input Real actual;
input Real simplified;
output Real y;
algorithm
y := actual;
annotation(Inline = true);
end homotopy;
示例 1:在电气系统中,如果开关是代数环的一部分,则通常很难求解非线性代数方程。理想化的二极管模型可以通过以下方式实现,首先从一个“平坦”的二极管特性开始,然后使用同伦方法移动到所需的“陡峭”的特性:
model IdealDiode
...
parameter Real Goff = 1e-5;
protected
Real Goff_flat = max(0.01, Goff);
Real Goff2;
equation
off = s < 0;
Goff2 = homotopy(actual = Goff, simplified = Goff_flat);
u = s * (if off then 1 else Ron2) + Vknee;
i = s * (if off then Goff2 else 1) + Goff2*Vknee;
...
end IdealDiode;
示例 2:在电气系统中,所有电压源均以零电压开始且所有电流源均以零电流开始通常很有用,因为可以轻松获得稳态初始化。典型的电压源定义为:
model ConstantVoltageSource
extends Modelica.Electrical.Analog.Interfaces.OnePort;
parameter Modelica.Units.SI.Voltage V;
equation
v = homotopy(actual = V, simplified = 0.0);
end ConstantVoltageSource;
示例 3:在流体系统建模中,由于二次项以及对流体特性的依赖,压力 / 流量关系是高度非线性的。基于额定负载调整的简化线性模型可以用来使整体模型的非线性度降低,从而更容易在没有准确初始值的情况下求解。这里使用了命名参数以进一步提高可读性。
model PressureLoss
import Modelica.Units.SI;
...
parameter SI.MassFlowRate m_flow_nominal "Nominal mass flow rate";
parameter SI.Pressure dp_nominal "Nominal pressure drop";
SI.Density rho "Upstream density";
SI.DynamicViscosity lambda "Upstream viscosity";
equation
...
m_flow = homotopy(actual = turbulentFlow_dp(dp, rho, lambda),
simplified = dp/dp_nominal*m_flow_nominal);
...
end PressureLoss;
示例4:请注意,同伦不应该用来结合无关的表达式,因为这可以通过结合两个明确定义的系统来生成奇异系统。
model DoNotUse
Real x;
parameter Real x0 = 0;
equation
der(x) = 1 - x;
initial equation
0 = homotopy(der(x), x - x0);
end DoNotUse;
初始方程展开为
你可以求解这两个方程,得到
它在
# semiLinear
在某些情况下,如果 semiLinear()的第一个参数 (x) 为零,那么带 semiLinear()函数的方程变为欠定的,即具有无穷多的解。这时为了选择一个有意义的解,建议在翻译阶段采用以下规则来对方程进行转换:
方程组:
y = semiLinear(x, sa, s1);
y = semiLinear(x, s1, s2);
y = semiLinear(x, s2, s3);
...
y = semiLinear(x, sN, sb);
可被下式替换:
s1 = if x >= 0 then sa else sb
s2 = s1;
s3 = s2;
...
sN = sN - 1;
y = semiLinear(x, sa, sb);
方程组:
x = 0;
y = 0;
y = semiLinear(x, sa, sb);
可被下式替换:
x = 0
y = 0
sa = sb
对于符号转换,下面性质是有用的(由定义得出):
semiLinear(m_flow, port_h, h);
等价于:
-semiLinear(-m_flow, h, port_h);
semiLinear 函数用于处理流体系统中的回流,例如:
H_flow = semiLinear(m_flow, port.h, h);
即,焓流速 H_flow 可以依据流向,由物质流速 m_flow 和返侵比焓来计算。
# getInstanceName
返回一个字符串,其中包含正在仿真的模型/块的名称,并附加了调用此函数的实例的完全限定名称。
示例:
package MyLib
model Vehicle
Engine engine;
...
end Vehicle;
model Engine
Controller controller;
...
end Engine;
model Controller
equation
Modelica.Utilities.Streams.print("Info from: " + getInstanceName());
end Controller;
end MyLib;
如果仿真了 MyLib.Vehicle,调用 getInstanceName() 将返回 "Vehicle.engine.controller"。
如果此函数不是在模型或块内部调用的(例如,该函数在一个 function 中被调用或在 package 的常量中被调用),则不指定返回值。
注意
仿真结果不应该依赖于此函数的返回值。
# 函数形式的与事件有关的操作符
下面列出的操作符是具有函数语法的事件相关操作符。根据动态向量,操作符 noEvent、pre、edge 和 change 可矢量化。
| 表示 | 描述 | 详情 |
|---|---|---|
| initial() | 初始化阶段的谓语 | 操作符 3.19 |
| terminal() | 成功分析结束的谓语 | 操作符 3.20 |
| noEvent(expr) | 在不触发事件的情况下计算 expr | 操作符 3.21 |
| smooth(p, expr) | 将 expr 视为 p 次连续可微 | 操作符 3.22 |
| sample(start,interval) | 周期性触发事件 | 操作符 3.23 |
| pre(y) | 变量 | 操作符 3.24 |
| edge(b) | 展开为 (b 而不是 pre(b)) | 操作符 3.25 |
| change(v) | 展开为 (v <> pre(v)) | 操作符 3.26 |
| reinit(x, expr) | 用 expr 重新初始化 x | 操作符 3.27 |
操作符 3.19 initial
initial ()
初始化阶段返回 true,否则返回 false。(因此,在仿真开始时触发一个时间事件)。
操作符 3.20 terminal
terminal ()
在成功分析的结尾返回 true。(因此,确保有一个事件在仿真成功结束时发生)。
操作符 3.21 noEvent
noEvent (expr )
expr 中的含义按字面意思理解,即不会触发任何状态或时间事件。不应使用过零函数来监视 expr 内任何正常生成事件的子表达式。在函数内部,noEvent 仅与函数注释 GenerateEvents = true 一起使用时产生影响。另请参阅操作符 3.22 smooth 和事件与同步。
操作符 3.22 smooth
smooth (p, expr )
如果 p ≥ 0,smooth(p, expr) 返回 expr 并声明 expr 是 p 次连续可微的,即 expr 在出现在表达式中的所有实型变量中都是连续的,而且关于所有出现的实型变量的所有偏导数都存在并且直到 p 阶都是连续的。参数 p 应该是一个标量整数参数表达式。在 smooth 中允许的 expr 类型仅为:实型表达式、允许表达式的数组以及只包含允许表达式组件的记录。
为了效率原因,应该使用 smooth 而不是 noEvent 来避免事件。工具可以自由地不为 smooth 内部的表达式生成事件。然而,smooth 并不保证不会生成事件,因此在 smooth 内部使用 noEvent 可能是必要的。
注意
如果出现的任何变量不连续变化,smooth 并不能保证平滑输出。
示例:
Real x, y, z;
equation
x = if time < 1 then 2 else time - 2;
z = smooth(0, if time < 0 then 0 else time);
y = smooth(1, if time < 0 then 0 else sqrt(x) * x); // Needs noEvent
操作符 3.23 sample
sample (start , interval )
在时刻 “start + i * interval”(i = 0, 1, ...)返回 true 并触发时间事件。在每个事件的第一次事件迭代之后的事件迭代以及持续积分期间,运算符总是返回 false。起始时间 start 和采样间隔 interval 必须是参数表达式,并且必须是实型或整型的子类型。采样间隔时间必须是正数。
操作符 3.24 pre
pre(y)
返回变量 y(t) 在时间点 t 的左极限
- (a) 变量 y 是简单类型的子类型或记录组件;
- (b) y 是离散时间表达式;
- (c) 操作符不在函数类中应用。
(这也适用于 when-clauses 中的连续时间变量,离散时间表达式的定义参见离散时间表达式。)
pre(y) 的第一个值在初始化阶段确定。
在某事件时刻,计算激活的模型方程后,如果至少有一个变量 v 满足 “pre(v) <> v”,则触发一个新的事件。在此情况下,立即重新计算模型。计算序列被称为“事件迭代”(event iteration)。若所有使用 pre 操作符的变量 v 满足“pre(v) == v”,则重新开始积分。
如果 v 和 pre(v) 只用于 when 子句,那么翻译器可能会屏蔽变量 v 的事件迭代,因为 v 在事件迭代过程中不会发生变化。找到事件迭代的最小循环,即不需要重新评估模型的所有部分,是高质量的实现。
本语言允许出现未知变量为 Real、Integer、Boolean 或枚举等多种类型混合的代数方程系统。这些方程系统可以通过全局定点迭代策略求解,与事件迭代相似,在每步迭代中,让 Boolean、Integer、或枚举类型的未知量保持不变。而且,更高效率地求解该系统是实现的质量问题,如,将定点迭代策略应用于模型方程的一个子集。
操作符 3.25 edge
edge(b)
对于布尔变量 b,扩展为 (b 而非 pre(b))。与 pre 的限制相同 (例如,不能在函数类中使用)。
操作符 3.26 change
change( v)
扩展为(v <> pre(v))。与 pre()操作符有相同的限制。
操作符 3.27 reinit
reinit(x, expr )
在 when 子句的主体中,在事件瞬间用 expr 重新初始化 x。 x 是一个标量或数组 Real 变量,它被隐式定义为具有 StateSelect.always。
注意
如果无法将变量选为状态,则会出错。
expr 需要与 x 的类型兼容,reinit 只能对同一变量使用一次。既可以作为单个变量,也可以作为变量数组的一部分。它只能在方程中的 when 子句中使用。另请参阅 When - 方程。
# 表达式的可变性
表达式可变性的概念指出表达式随时间变化而改变的程度,见条件组件声明关于可变性的概念。表达式可变性有 4 个层次,从最低层(可变性最小)开始依次为:
- 常量可变性
- 参数可变性
- 离散时间可变性
- 连续时间可变性
虽然可以仅根据声明的变量可变性(没有表达式可变性的概念)来规避许多无效模型,但以下规则既有助于确保计算解决方案符合声明的可变性,又对简化推理和错误报告施加了额外的限制:
- 对于赋值 v := expr 或绑定方程 v = expr,必须声明 v 至少与 expr 一样可变。
- 对于多重返回赋值 (x1, ..., xn) := expr(见多个返回值的函数调用赋值),x1, . . . , xn 必须声明为至少与 expr 一样可变。
- 当确定一个方程是否可以用于求解变量 v 时(例如,在应用完全匹配规则时,见同步数据流原理和单一赋值规则,只有在所得到的解最多与变量 v 相同时,才能认为该方程有助于变量 v 的求解。
当确定一个方程是否可以用于求解变量 v 时(例如,当应用完美匹配规则,参见同步数据流原理和单一赋值规则),只有当所得解最多与 v 一样可变时,才可以认为方程有用。
示例:下面的(未确定的)模型测试说明了由于可变性约束而产生的两种后果。首先,它包含声明方程和赋值的可变性错误。其次,它说明了可变性对方程与变量匹配的影响,这可能导致违反完美匹配规则。下文将详细介绍如何确定可变性。下面注释中提到的离散值方程可变性规则是指离散时间表达式中要求布尔方程两边都是离散时间的规则。
model Constants
parameter Real p1 = 1;
constant Real c1 = p1 + 2; // Error, not a constant expression.
parameter Real p2 = p1 + 2; // Fine.
end Constants;
model Test
Constants c1(p1 = 3); // Fine.
Constants c2(p2 = 7); // Fine, declaration equation can be modified.
Real x;
Boolean b1 = noEvent(x > 1); // Error, since b1 is a discrete - time variable
// and noEvent(x > 1) is not discrete - time.
Boolean b2;
Integer i1;
Integer i2;
algorithm
i1 := x; // Error, assignment to variable of lesser variability.
equation
/* The equation below can be rejected for two reasons:
* 1. Discrete - valued equation variability rule requires both sides to be
* discrete - time.
* 2. It violates the perfect matching rule, as no variable can be solved
* with correct variability using this equation.
*/
b2 = noEvent(x > 1); // Error, see above.
i2 = x; // No variability error, and can be matched to x.
end Test;
# 函数可变性
函数调用的可变性既要考虑函数中直接给出的参数的可变性,也要考虑使用的默认参数(如果有)的可变性。这一点对于以短类形式给出的函数尤为重要,参见函数的继承。这对重声明也有影响,参见定义 6.8。函数的纯度(参见 Modelica 纯函数)并不影响函数调用的可变性。
(调用声明为非纯函数的限制与可变性限制具有类似的目的,请参见 Modelica 纯函数,因此在定义可变性时没有必要考虑纯度。考虑一个读取外部文件并从该文件返回某些值的函数。不同的用途可以在仿真前更新文件(作为参数表达式),也可以在仿真过程中更新文件(作为离散时间表达式)。因此,这取决于用例和特定文件,而非函数本身、甚至可以在连续时间内更新文件(作为算法的一部分),并仍然使用相同的函数)。
# 常量表达式
常量表达式是:
- 实型、整型、布尔型、字符串型或枚举型字面量;
- 声明为 constant 的变量,可参阅条件组件声明;
- 除了特定的内置操作符 initial、terminal、der、edge、change、sample、Subtask.activated、Subtask.lastInterval 和 pre 以外,以常量子表达式为实参(函数中没有定义参数)的函数或操作符都是常量表达式。
- 有些函数调用是常量表达式,与参数无关:
- -ndims(A)
# 参数表达式
参数表达式是:
- 常量表达式;
- 声明为 parameter 的变量,请参阅条件组件声明;
- 除了特定的内置操作符 initial、terminal、der、edge、change、sample 和 pre 外,函数或带参数子表达式的操作符为参数表达式;
- 有些函数调用是参数表达式,即使参数不是:
- cardinality(c) ,请参阅基数(已弃用)cardinality中的使用限制。
- -end 在 A[...end...]中,如果 A 是在非函数类中声明的变量。
- size(A)(包括 size(A,j),其中 j 是参数表达式),如果 A 是在非函数类中声明的变量。
- Connections.isRoot(A.R)。
- Connections.rooted(A.R)。
# 离散时间表达式
离散时间表达式是:
- 参数表达式;
- 离散时间变量,参见条件组件声明;
- 所有输入实参为离散时间表达式的函数调用;
- 所有子表达式为离散时间表达式的表达式;
- when 子句、initial equation或initial algorithm 中的表达式;
- 时钟离散时间分区中的表达式,参见时钟离散时间和离散化连续时间;
- 关系操作符(>、<、>=、<=)和事件生成函数 ceil、floor、div 和 integer,在 noEvent 之内的除外。如果有任何一个参数是非离散时间表达式和 Real 的子类型,它们将产生事件,参见事件与同步。(请注意,rem 和 mod 会生成事件,但不是离散时间表达式。换句话说,noEvent 内部的关系(如 noEvent(x > 1))也不是离散时间表达式)
- 函数 pre、edge 和 change 产生离散时间表达式;
- 函数中表达式的行为就像离散时间表达式。
若非位于 when 子句内部,在由非离散时间(即连续时间,但非离散时间)切换表达式控制的 if 表达式、if 子句、while 语句或 for 子句中,对离散时间变量赋值、离散时间表达式之间的等式或应生成事件的实际基本关系/函数都是不合法的。(为了保证所有离散变量方程中的表达式都是离散时间表达式,并确保交叉函数不会在两个事件之间发生作用,这种限制是必要的。)
对于标量或数组方程 expr1 = expr2,如果两个表达式都不是基本的 Real 类型,则要求两个表达式必须都是离散时间表达式。对于记录方程,该规则递归地适用于记录的每个组成部分。这就是离散值方程可变性规则。
对于标量方程,该规则源于这样的观察,即离散值方程无法提供足够的信息来求解连续值变量。因此,根据完全匹配规则(见同步数据流原理和单一赋值规则),这样的方程必须用来求解离散值变量。根据附录 B Modelica 微分代数方程表示 中对 (B.1c) 的解释,expr1 和 expr2 中的一个必须是变量,而另一个表达式必须是它的解。由于离散值变量是离散时间表达式,因此方程另一侧的解必须最多具有离散时间的可变性。也就是说,方程的两边都是离散时间表达式。
例如,该规则表明(在 when 子句之外)noEvent 不能应用于 Boolean、Integer、String 或 enumeration 等式的任一边,因为这将导致非离散时间表达式。
对于数组方程,请注意每个数组只能有一个元素类型,因此如果一个元素是 Real,那么所有其他条目也必须是 Real,这可能需要使用标准类型强制转换标准类型强制转换。因此,如果基本类型不是 Real,那么数组的所有元素都是离散值,这样就可以将上述标量方程的参数按元素顺序应用于数组方程。也就是说,数组方程两边的所有数组元素都具有离散时间可变性,这表明整个数组 expr1 和 expr2 也是离散时间表达式。
对于记录方程来说,记录的各个组成部分都有独立的类型,方程被视为记录各个组成部分的方程集合。为了支持具有混合可变性成分的记录,在确定可变性之前,由记录变量或记录构造函数给出边的记录方程会在概念上被拆分。
示例:应用于记录方程的离散值方程可变性规则。在下面的第一个方程中,方程两边都有一个记录构造函数,方程在概念上被拆分,time 和 true 的可变性被分开考虑。在第二个方程中,makeR 函数调用(无论是否内联)意味着方程无法从概念上分割成记录的各个组成部分。makeR 调用的可变性由于 time 参数而具有连续时间性,这也成为了调用中 b 成员的可变性。
record R
Real x;
Boolean b;
end R;
function makeR "Function wrapper around record constructor"
input Real xx;
input Boolean bb;
output R r = R(xx, bb);
annotation(Inline = true); // Inlining doesn't help.
end makeR;
model Test
R r1, r2;
equation
r1 = R(time, true); // OK: Discrete−time Boolean member.
r2 = makeR(time, true); // Error: Continuous−time Boolean member.
end Test;
# 连续时间表达式和和非离散时间表达式
所有表达式都是连续时间表达式,包括常量表达式、参数表达式和离散时间表达式。非离散时间表达式指的是既不是常量、参数也不是离散时间表达式的表达式。例如,time 是一个连续时间内置变量(见条件组件声明),而 time + 1 是一个非离散时间表达式。需要注意的是,根据上下文,time 可以指连续时间变量或非离散时间表达式。