# 方程


方程是类定义的一部分。标量方程与标量变量相关,即约束这些变量可以同时取的值。当已知包含 n 个变量的方程的 n-1 个变量时,可以推断(求解)第 n 个变量的值。与算法部分相反,方程部分中的方程之间没有顺序,它们可以单独求解。

# 方程的种类

根据其出现的句法上下文,Modelica 中的方程可以来分为不同的种类:

  • 常规等式方程,出现在方程节,包括连接方程和其他特殊句法形式(方程节中的方程)的方程类型。
  • 声明方程,是变量、参数、或者常量声明的一部分(声明方程)。
  • 修改方程,一般用来修改类的属性(修改)。
  • 绑定方程,包含声明方程,也包括对变量元素值本身的修改。当上述这些出现在函数外部时也会被认为是方程,然后带有绑定方程的组件将其值绑定到某个表达式。(绑定方程也可以出现在函数中,参见初始化和方程绑定)。
  • 初始方程,用来解决初始化问题(初始化,初始方程和初始算法)。

# 方程中的展开和查找

展开的方程和其相应的未展开方程是一样的。

方程中的名字应该通过查找方程的部分展开的包含类中来发现。

# 方程节中的方程

方程部分由关键字 equation 和一系列方程组成。其标准语法如下:

equation-section :
  [ initial ] equation { equation ";" } 

在方程章节中可能会出现以下几种类型的方程。其语法定义如下:

equation :
  ( simple-expression "=" expression
  | if-equation
  | for-equation
  | connect-equation
  | when-equation
  | component-reference function-call-args
  )
  description 

方程部分不允许出现任何语句,包括使用 := 运算符的赋值语句。

# 简单等式方程

简单等式方程是数学中传统的方程类型,用于表达两个表达式之间的相等关系。在 Modelica 语言中,这类方程有两种语法形式:第一种是两表达式之间的等式方程,第二种则用于调用具有多个返回值的函数。简单等式方程的语法如下:

simple-expression "=" expression

方程式的左侧和右侧类型需要保持兼容性,其兼容方式与二元运算符的两个参数相同(参见类型相容的表达式)。

列举三个示例:

  • simple_expr1 = expr2;
  • (if pred then alt1 else alt2) = expr2;
  • (out1, out2, out3) = function_name(inexpr1, inexpr2);

注:根据语法规则,第二个示例中的 if-then-else 表达式需要用括号括起来以避免解析歧义。另请参阅多个返回值的函数调用赋值关于在赋值语句中使用多个结果调用函数的相关内容。

# For-Equations- 重复方程结构

for - 方程的语法如下:

for for-indices loop
  { equation ";" }
end for ";" 

一个 for 循环方程可以选择性地使用多个迭代器(for 索引),更多信息参见嵌套 for 循环和含有多个迭代的归约表达式

for-indices:
  for-index { "," for-index }
for-index:
  IDENT [ in expression ] 

以下是一个 for - 方程前缀的示例:

for IDENT in expression loop

# 显式迭代范围的 For-Equations

for-equation 的表达式应为向量表达式,其中更通用的数组表达式被视为向量的向量或矩阵的向量。该表达式在每次 for-equation 执行时评估一次,并在紧邻包含该 for-equation 的作用域内进行评估。for-equation 的表达式应为参数表达式。for-equation 的迭代范围也可指定为布尔类型或枚举类型,更多信息参见类型作为迭代范围。循环变量(IDENT)在内部作用域中循环结构体不可被赋值。对于向量表达式的每个元素,按正常顺序依次执行:循环变量将获取该元素的值,并用于评估 for 循环体。

示例:

for i in 1:10 loop         // i takes the values 1, 2, 3, ..., 10
for r in 1.0:1.5:5.5 loop  // r takes the values 1.0, 2.5, 4.0, 5.5
for i in {1, 3, 6, 7} loop // i takes the values 1, 3, 6, 7
for i in TwoEnums loop     // i takes the values TwoEnums.one, TwoEnums.two
                          // for TwoEnums = enumeration (one, two) 

循环变量可能会隐藏其他变量,如下例所示。然而,强烈建议为循环变量使用其他名称:

constant Integer j = 4;
Real x[j]
equation
  for j in 1:j loop // j takes the values 1, 2, 3, 4
    x[j] = j;       // Uses the loop-variable j
  end for; 

# For-Equations 的隐式迭代范围

循环变量的迭代范围有时可以通过其作为数组索引的使用情况来推断。更多信息请参见隐式迭代范围

示例:

Real x[n], y[n];
equation
  for i loop         // Same as: for i in 1:size(x, 1) loop
    x[i] = 2 * y[i];
  end for; 

# Connect-Equations

Connect-Equations 具有以下语法:

connect "(" component-reference "," component-reference ")" ";" 

这些语句可以放置在 for 循环方程和 if 条件方程内部;前提是 for 循环的索引和 if 方程的条件必须是参数表达式,且不依赖于基数(cardinality)、根节点(rooted)、连接根节点(Connections.rooted)或是否为根节点(Connections.isRoot)。for 循环方程 / if 条件方程会被展开。连接方程(connect-equations)的具体说明详见连接方程和连接器

同样的限制也适用于 Connections.branch(连接分支)、Connections.root(连接根节点)和 Connections.potentialRoot(连接潜在根节点);这些参数在展开后将按照过度约束的连接的规定进行处理。

# If - 方程

If-Equations 遵循以下语法规则:

if expression then
  { equation ";" }
{ elseif expression then
  { equation ";" }
}
[ else
  { equation ";" }
]
end if ";" 

if 子句或 elseif 子句中的表达式必须是标量布尔表达式。一个 if 子句、零个或多个 elseif 子句以及一个可选的 else 子句共同构成一个分支列表。通过按顺序计算 if 子句和 elseif 子句的条件,直到找到一个计算结果为 true 的条件,从而选择这些 if、elseif 和 else 子句中的一个或零个主体。如果没有条件计算结果为 true,则选择 else 子句的主体(如果存在 else 子句,否则不选择任何主体)。在方程部分,主体中的方程被视为必须满足的方程。未被选择的主体对模型评估没有影响。

在方程部分中,其切换条件并非完全由参数表达式构成的 if 方程,每个分支中的方程数量应相同(缺少 else 子句时计为零个方程,并且在将方程展开为标量方程后确定方程数量)。

(如果违反此条件,单赋值规则将不成立,因为在仿真过程中方程的数量可能会发生变化,尽管未知数的数量保持不变。)

# When - 方程

When-Equations 具有以下语法:

when expression then
  { equation ";" }
{ elsewhen expression then
  { equation ";" }
}
end when ";" 

当方程(when-equation)的表达式应为离散时间布尔标量或向量表达式。若表达式为时钟表达式,则该方程称为时钟当子句(时钟 When 子句)而非当方程,其处理方式亦有所不同。当方程内的各方程仅在标量表达式为真或向量表达式中任一元素为真时被激活。

示例:当方程在 when-equation 中的顺序无关紧要时:

equation
  when x > 2 then
    y3 = 2*x + y1 + y2; // y1和y3方程的顺序无关紧要
    y1 = sin(x);
  end when;
  y2 = sin(y1); 

# 在等式方程中用 if - 方程来定义 when - 方程

When - 方程:

equation
  when x > 2 then
    v1 = expr1;
    v2 = expr2;
  end when; 

概念上等价于下面含有特殊 if - 表达式的方程:

// Not correct Modelica
Boolean b(start = x.start > 2);
equation
  b = x > 2;
  v1 = if edge(b) then expr1 else pre(v1);
  v2 = if edge(b) then expr2 else pre(v2); 

这种等价只是概念上的,因为非离散时间的实型变量或表达式,示例:

/* discrete */ Real x;
input Real u;
output Real y;
equation
  when sample() then
    x = a * pre(x) + b * pre(u);
  end when;
  y = x; 

在这个例子里,x 是一个离散时间变量(不管有没有前缀 discrete),但是 u 和 y 不能是离散时间变量 (因为没有在 when 子句中赋值)。然而,pre(u) 在 when 子句中是合法的,因为 when 子句仅在事件发生时评估,所以所有的表达式都是离散时间表达式

如上,引入的布尔变量 b 的初值通过取 when 条件的初值来定义,b 为参数变量。特殊函数 initial、terminal 和 sample 的初值为 false。

# When - 方程内部方程的约束

  • When - 方程不能出现在初始方程中。
  • When 子句不能嵌套。
  • 如果控制表达式完全是参数表达式,则 when-equation 只能出现在 if - 方程和 for - 方程中。

示例:

when x > 2 then
  when y1 > 3 then
    y2 = sin(x);
  end when;
end when; 

# When - 方程中的等式

when 方程内的等式必须符合以下形式之一:

  • v=expr;
  • (out1, out2, out3, ...) = function_call_name(in1, in2, ...);
  • 操作符 assert, terminate, reinit。
  • 只要 for 循环和 if 条件语句中的方程式满足这些要求,那么这些 for 循环和 if 条件语句中的方程式就符合条件。

此外,

  • 不同的 when/elsewhen 分支中,方程等号左边引用的组件的集合必须具有相同。此处,reinit 的目标变量(包括在用 initial() 激活的 when - 子句中的 when)不被认为是一个左值,因此 reinit 不受此要求的影响(assert 和 terminate 也是如此)。
  • when - 方程中,if-else-then 子句的分支中的方程,其等号左边引用的组件的集合必须具有相同,除非 if-else-then 有专门的参数表达式作为切换条件。
  • 任何作为左值的引用,(v, out1,…),在 when - 子句中必须是组件引用,并且任何下标必须是参数表达式。

示例:when - 方程分支中的方程需要的约束就很明显了:

Real x, y;
equation
  x + y = 5;
  when condition then
    2 * x + y = 7; // error: not valid Modelica
  end when; 

当 when - 方程分支中的方程没被激活时,并不清楚哪个变量应该保持不变,x 和 y 都可以。该例的正确写法为:

Real x,y;
equation
  x + y = 5;
  when condition then
    y = 7 - 2 * x; // fine
  end when; 

这里,当 when 子句没被激活时,将变量 y 视为常量,用前一事件时刻得到的 y 值,从第一个方程计算 x。

示例: if-equation 的限制意味着以下两个变量都是非法的:

Real x, y;
equation
  if time < 1 then
    when sample(1, 2) then
      x = time;
    end when;
  else
    when sample(1, 3) then
      y = time;
    end when;
  end if;

  when sample(1, 2) then
    if time < 1 then
      y = time;
    else
      x = time;
    end if;
  end when; 

而对 parameter-expression 的限制旨在允许以下情况:

parameter Boolean b = true;
parameter Integer n = 3;
Real x[n];
equation
  if b then
    for i in 1:n loop
      when sample(i, i) then
        x[i] = time;
      end when;
    end for;
  end if; 

# 单一赋值规则在 When 方程中的应用

Modelica 的单一赋值规则(见同步数据流原理和单一赋值规则)要求 when 方程 :

  • 两个 when 方程也许并不能定义相同的变量。
    如果没有这一规则,可能会建立下面的错误的模型 DoubleWhenConflict ,因为有两个方程(close = true; close = false;)定义了同一个变量 close。如果在同一时刻,两个条件都为真,则方程之间就出现了冲突。
model DoubleWhenConflict
  Boolean close;  // Erroneous model: close defined by two equations!
equation
  ...
  when condition1 then
    ...
    close = true;
  end when;
  when condition2 then
    close = false;
  end when;
  ...
end DoubleWhenConflict; 

解决这个冲突的一种方法就是让其中一个 when 方程具有更高的优先级。这可以通过把一个 when 改写为 elsewhen 来实现,就如下面的 WhenPriority 模型一样, 另一个方法是使用 when-construct 的 statement 版本,见 when 语句

  • 可以用 when - 方程的 elsewhen 部分来解决赋值冲突,因为前一个 when/elsewhen 的优先级高于后面的。
    下面的例子说明了两个条件同时为真时的情况,条件 condition1 及其关联的方程比 condition2 有更高的优先级。
model WhenPriority
  Boolean close;  // Correct model: close defined by two equations!
equation
  ...
  when condition1 then
    close = true;
  elsewhen condition2 then
    close = false;
  end when;
  ...
end WhenPriority; 

与 elsewhen(在等式或算法中)相比,另一种方法是使用带有多个 when 语句的算法。然而,如果两个条件同时为真,则两个语句都会被执行。因此,它们必须以相反的顺序来保持优先级,并且任何副作用都需要更加小心。

model WhenPriorityAlg
  Boolean close;  // Correct model: close defined by two when—statements!
algorithm
  ...
  when condition2 then
    close := false;
  end when;
  when condition1 then
    close := true;
  end when;
  ...
end WhenPriorityAlg; 

# reinit

reinit 操作符可在 when 方程或 when-statement 中使用,其语法如下:

reinit(x, expr);

该操作符在事件时刻用 expr 初始化变量 x。x 是一个 Real 类型的变量(或者 Real 类型的数组, 其向量化应用参见函数中的大小可变的数组以及数组大小的改变),它必须被选作状态变量 (每个数组元素都被选作状态变量,resp., states),也就是说,reinit 作用于 x 意味着 stateSelect= stateSelect.always。expr 要和 x 类型相容。对于任何给定的变量(可能是数组变量),只能在一个 when-equation 中应用 reinit(可以应用于单个变量或数组变量的一部分)(允许在同一个 when-equation 的几个 when - 或者 else - clause 子句中应用 reinit)。如果在同一个 when - 或者 else - 子句中有多个变量的 reinit,它们必须出现在 If- equation 的不同分支中(以便在任何情况下该变量最多有一个 reinit 是激活的)。如果 reinit 在初始化期间被激活(由于 when initial()),请参见初始化,初始方程和初始算法

reinit 操作符不违背单一赋值规则,因为 reinit(x, expr) 先对 expr 求值,记为 value,然后在当前事件迭代步骤结束时将该 value 赋给 x(这种从值到重新初始化状态的复制是在模型的所有其他求值之后,在将 x 复制到 pre(x) 之前完成的)。

示例:如果出现高指标系统(If a higher index system is present),即状态变量之间有约束,一些状态变量需要被重定义为非状态变量。在仿真中,状态变量是这样选择的:reinit 操作符作用的变量,至少应在 when - 语句被激活时被选为状态变量。否则就可能出现错误,因为 reinit 操作符应用于非状态变量上了。

使用 reinit 操作符的示例: 跳跃小球:

der(h) = v; 
der(v) = if flying then -g else 0; 
flying = not (h<=0 and v<=0);
when h < 0 then
  reinit(v, -e*pre(v));
end when;

# assert

assert 可以是下面形式的方程或者语句:

assert(condition, message); // Uses level=AssertionLevel.error
assert(condition, message, assertionLevel);
assert(condition, message, level = assertionLevel); 

此处, condition 是布尔表达式,message 是字符串表达式,level 是内置枚举类型 AssertionLevel 的可选参数表达式。assert 断言可以被用在方程节或者算法节。

这意味着可以像调用一个带有三个形参的函数一样调用 assert,第三个形式参数具有名称 level 和默认值 AssertionLevel.error。

(level 需要参数表达式,因为它将在编译时计算。)

如果断言语句中的条件 condition 为真,就不对 message 求值,忽略该过程调用。如果条件计算结果为假,就根据 level 的值采取不同的动作:

  • level = AssertionLevel.error: 终止当前计算,仿真可继续进行另一个计算。如果仿真终止了,message 指出错误原因。
    (可以使用较短的步长或者改变迭代变量值作为开始另一次估值的方法)。
    失败的断言优先于成功终止。这意味着如果模型首先通过达到停止时间或显式使用终止命令触发成功分析结束,但以 terminal()=true 进行的评估触发了断言,则该分析视为失败。
  • level = AssertionLevel.warning: 不中止当前计算。message 指出了引起警告的原因。
    (建议仅在条件变为假时报告一次警告,并在条件恢复为真时报告条件不再被违反。该断言语句不应影响模型行为。例如,仅在接受的积分器步骤后评估条件并报告消息。由于若不隐式使用 noEvent 处理条件,可能会触发导致仿真结果轻微变化的事件。)

断言级别错误的情况可用于避免在模型有效性范围之外进行评估;例如,计算饱和液体温度的函数在被调用时,其压力值不能低于三相点值。

AssertionLevel.warning 用例适用于有效性边界非硬性规定的情形:例如,基于多项式插值曲线的流体属性模型可能在 250K 至 400K 温度区间内给出精确结果,但在 200K 至 500K 范围内仍能提供合理结果。当温度超出较小区间但仍处于较大区间时,应向用户发出警告,但模拟应继续执行而无需其他操作。对应代码示例如下:

assert(T > 250 and T < 400, "Medium model outside full accuracy range", 
       AssertionLevel.warning);
assert(T > 200 and T < 500, "Medium model outside feasible region"); 

# terminate

terminate 方程或者语句(使用函数语法)可终止分析,参见 reinit。终止并不会立即在其定义的地方发生,因为成功终止所需的变量也许暂时还没得到。相反,终止实际发生在当前积分步完成之后, 或者发生在事件时刻,这时事件已处理完毕而积分还没再次开始。

终止操作接受一个字符串参数,用于指明成功的原因。

示例:terminate 的目的是可以给出更多的终止原因,而不是简单的某个固定时间:

model ThrowingBall
  Real x(start = 0);
  Real y(start = 1);
equation
  der(x) = ...;
  der(y) = ...;
algorithm
  when y < 0 then
    terminate("The ball touches the ground");
  end when;
end ThrowingBall; 

# 超定的基于连接的方程系统的方程操作符

这个话题的描述见过度约束的连接

# 同步数据流原理和单一赋值规则

同步数据流原理和单一赋值规则是 Modelica 的基础,其定义如下:

  1. 离散时间变量保持它们实际的值,直到这些变量被显式地改变。微分变量有 der(x) 对应于 x 的时间导数,并且 x 在数学上是连续的,除非触发 reinit,参见 When - 方程。变量值可以在连续积分期间的任意时刻和事件时刻被访问。
  2. 在连续积分中的每一时刻,以及在每一个事件时刻,活动方程表达的变量之间的关系,必须同时得到满足。
  3. 在事件时刻的计算和通信不占用时间。
    (如果必须仿真计算或通信时间,必须为这种性质显式建模。)
  4. 平坦化后必须存在变量与方程之间的一个完美匹配,完美匹配意思为一个变量只能与有助于求解变量的方程相匹配(完美匹配规则 - 以前称为单一赋值规则);全局平衡条件见平衡模型

# 事件与同步

事件是指在特定时间或特定条件发生时瞬间发生的事情。例如,事件是由 when 子句、if 方程或 if 表达式中发生的条件定义的。每当事件生成表达式(例如,x> 20 或 floor(x)) 改变其值时,积分将停止并发生事件。事件生成表达式有一个内部缓冲区,表达式的值只能在事件瞬间改变。如果求值的表达式与缓冲区不一致,那将触发一个事件,缓冲区将在事件瞬间用一个新值更新。在持续积分期间,事件生成表达式具有上一个事件瞬间的表达式的恒定值。

(需要一种根查找机制(即零穿越函数的零点)来确定表达式改变其值的极小的时间间隔;事件发生在这个时间间隔的右侧。)

示例:

y = if u > uMax then uMax else if u < uMin then uMin else u;

在连续积分期间,总是计算同一个 if - 分支。只要 u-uMax 或 u-uMin 穿过零,积分就会停止。在事件时刻,选择正确的 if - 分支然后再重新开始积分。

n 阶(n>=1)数值积分方法要求连续模型的方程 n 阶可微。如果不是按字面处理实型元素关系式而是如上定义,这个要求可以得到满足,因为不连续的改变只能出现在事件时刻,而不会出现于连续积分时。

下面特殊关系的处理属于实现质量方面的议题,特殊关系:

time >= discrete expression 
time < discrete expression

在 “time = discrete expression” 时触发一个时间事件,即,事件时刻预先已知,不需要迭代去寻找精确的事件时刻。

如果关系式或关系式所在的表达式是函数 noEvent(…) 的参数,那么在连续积分期间也会按字面处理关系式(are taken literally)。smooth(p, x) 操作符也允许参数中的关系式按字面处理。noEvent 的这种特征被传播到 noEvent 函数作用域内的所有子关系式。对于 smooth,不允许按字面计算的特征被传播到所有子关系,但平滑特征(smooth-property)本身不会被传播。

示例:

x = if noEvent(u > uMax) then uMax elseif noEvent(u < uMin) then uMin else u;
y = noEvent( if u > uMax then uMax elseif u < uMin then uMin else u);
z = smooth(0, if u > uMax then uMax elseif u < uMin then uMin else u); 

在这个例子中,x=y=z,但是某种工具(机制)可能为 z 生成事件。这里 if - 表达式按字面处理而不生成状态事件。

smooth 函数是有用的,例如,如果建模者能够保证所用的 if - 表达式至少满足积分器的连续性要求,在这种情况下仿真速度会提高,因为积分时无状态事件迭代。noEvent 函数用于保证避免“超范围(outside domain)“错误,例如,y = if noEvent(x >= 0) then sqrt(x) else 0。

在 when 子句内的所有方程和赋值语句,以及函数类内的所有赋值语句隐式地使用 noEvent 函数进行处理,即,在这些操作的作用域内的关系式决不会生成状态或时间事件。

(在 when 子句中使用状态事件是没有必要的,因为 when 子句体在连续积分时不被计算。)

示例:由非离散时间表达式引发的两种不同错误:

when noEvent(x1 > 1) or x2 > 10 then // When-condition must be discrete-time
  close = true;
end when;
above1 = noEvent(x1 > 1);  // Boolean equation must be discrete-time 

"当条件规则" 在 If - 方程中阐述,而非实数方程的规则则在离散时间表达式中说明。

Modelica 基于同步数据流原理(同步数据流原理和单一赋值规则)。

同步数据流原理中的规则保证变量总是被唯一的方程集合所定义,不可能一个变量被两个会导致冲突或不确定行为的方程所定义这。而且,模型的连续部分与离散部分总是自动同步。
示例:

equation // Illegal example
  when condition1 then
    close = true;
  end when;
  when condition2 then
    close = false;
  end when; 

这个模型无效,这是由于单个未知变量 close 有两个方程,违背规则 4。如果这个模型有效,那么当同一时刻两个条件都为真时出现冲突,因为两个方程之间并没有优先关系。为了使其有效,模型必须改为:

equation
  when condition1 then
    close = true;
  elsewhen condition2 then
    close = false;
  end when; 

这里,如果在同一时刻两个条件都为真,模型也是定义良好的(condition1 比 condition2 具有更高的优先级)。

不保证两个不同的事件在同一时刻发生。

因此,事件的同步必须在模型中显式编程实现,例如,通过计数器。示例:

Boolean fastSample, slowSample;
Integer ticks(start=0);
equation
  fastSample = sample(0,1);
algorithm
  when fastSample then 
    ticks := if pre(ticks) < 5 then pre(ticks)+1 else 0;
  slowSample := pre(ticks) == 0;
end when;
algorithm
  when fastSample then // fast sampling
    ...
  end when;
algorithm
  when slowSample then // slow sampling (5−times slower)
    ...
  end when; 

在 fastSample when 子句每出现五次时 slowSample when 子句被计算一次。

(单一赋值原则和事件同步要求显式编程,在一定程度上允许编译时的模型验证。)

# 初始化,初始方程和初始算法

在对 Modelica 模型的任何操作之前(例如,仿真或线性化),先进行初始化,为模型中出现的所有变量赋相容的值。在这个阶段,也称为初始化阶段,导数 der(…) 和 pre 变量 pre(…) 翻译为未知的代数变量。初始化阶段将使用所有预期操作(如仿真或线性化)中使用的所有方程和算法。

when - 子句的方程在初始化期间是激活的,当且仅当它们被 initial() 显式启用,并且只能使用 when initial() then 或 when {. . ., initial(),. . .} then 两种形式之一(对于 elsewhen 和算法也类似,见下文)。在这种情况下,when - 子句方程在整个初始化阶段保持激活。如果 reinit(x, expr) 在初始化期间处于激活状态(由于在 when initial() 内部),则将其解释为添加 x=expr (reinit-equation) 作为初始方程。reinit 处理既可以在 when - 子句中直接应用 if,也可以在 when - 子句中的 if - 方程中应用。特别地,为了平衡初始化过程中激活的 when - 子句中的 if - 方程,reinit(x, expr) 需要被计算为方程 x = expr,请参见连接方程

(如果一个 when - 子句方程 v =expr;在初始化阶段未激活,则添加方程 v =pre(v) 进行初始化。这是由 when-clause 方程的映射规则推导出来的。如果 when - 子句的条件包含 initial(),但不是特定形式之一,则 when - 子句在初始化期间不激活: when not initial() then print("simulation started"); end when;)

when - 语句中的算法语句在初始化期间是激活的,当且仅当它们被 initial() 显式启用时,并且只能使用 when initial() then 或 when {. . ., initial(),. . .} then 两种形式之一。在这种情况下,when - 语句中的算法语句在整个初始化阶段保持激活状态。

一个激活的 when - 子句会使下面的 elsewhen 分支失效(类似于仿真过程中的when-子句),但除了第一个 elsewhen initial() then 或 elsewhen{…,initial(),…}then 在初始化过程中与 when initial() then 或 when{…,initial(),…}then 的激活类似。

(这意味着任何后续的 elsewhen initial() 都没有作用,类似于 when false then。)

(在初始化过程中没有对不激活的 when - 语句进行特殊处理,而是在算法体之前使用 v:= pre(v) 初始化 when - 语句中分配的变量(因为它们是离散的),参见 Model 的算法区。)

确定所有变量初始值所需的必要约束(取决于组件可变性,参见条件组件声明的定义),可以用以下两种方式定义:

  1. 通过在 “initial equation” 节的方程或在 “initial algorithm” 节的赋值。在这些初始化节的方程或赋值为纯代数量,描述初始时刻变量之间的约束。在这些部分不允许使用 when 子句。
  2. 对于连续时间变量 vc,在初始化方程中加入方程 pre(vc)= vc。
    (如果 pre(vc) 不存在于平坦模型中,工具可能会选择不引入这个方程,或者如果引入了这个方程可以消除 vc(以避免引入许多虚拟变量pre(vc))。)
  3. 隐式地通过对 fixed= true 的变量使用 start 属性。start 由下列初始值表达式给出:
    • 对于声明为 constant 或 parameter 的变量,不会在初始化方程中添加任何方程。
    • 对于离散时间变量 vd,将方程 pre(vd) =startExpression 添加到初始化方程中。
    • 对于连续 Real 变量 vc,在初始化方程中加入方程 vc =startExpression。

常量应由声明方程确定(参见条件组件声明),不允许设置 fixed = false。对于参数,fixed 默认为 true。对于其他变量,fixed 默认为 false。

具有 fixed= false 的变量的起始值可以用作初始迭代值,以防在初始化阶段使用迭代求解器。

(在迭代求解器失败的情况下,建议专门报告那些求解器需要初始迭代值,但已经应用了回退值(参见预定义类型)的变量,缺乏适当的初始迭代值是求解器失败的可能原因。)

所有作为 parameter 声明为 fixed =false 的变量在初始化阶段都被视为未知数,也就是说,它们必须有额外的方程——初始值可以在初始化期间用作猜测值。

在参数同时具有绑定方程和 fixed = false 的情况下,建议进行诊断,不过参数应该从绑定方程中求解。

连续时间 Real 变量 vc 只有一个初始化值,因为上面的规则保证在初始化过程中 vc= pre(vc)= vc。startExpression(如果 fixed=true)。

在开始积分之前,必须保证对于所有变量 v,v =pre(v)。如果对于某些变量 vi 不是这种情况,则必须设置 pre(vi):= vi,并且必须遵循初始时间的事件迭代,因此重新估值模型,直到满足此条件。

Modelica 翻译器可能首先将模型的连续方程转化为状态空间形式(state space form),至少概念上如此。这可能需要对方程进行微分以缩减指标(index),即,要引入附加的方程,在某些情况下, 还要引入附加的未知变量。整个方程集合,与上面定义的附加约束一起,将产生一个代数方程系统, 其中方程的数量和所有变量(包括 der(…) 和 pre(…) 变量)的数量相等。通常,这是一个非线性方程系统,因而可能需要提供适当的猜测值(即 start 值和 fixed=false)以进行数值计算求解。用户可能很难指出必须添加多少个初始方程,特别是当系统具有高阶指标时。

以下提供一些非标准的思路。可以提供某个工具自动地增加或删除初始方程,以使结果系统在结构上非奇异:

  • 如果离散变量缺少初始值但不影响仿真结果, 可以自动地设置为起动值或缺省值而不通知用户。例如,在 when 子句中赋值的变量,在 when 子句这外没有对它的访问,又没有对其显式使用 pre() 操作符,这种变量就对仿真没有影响。
  • 不固定的 start 属性可能会被分析为固定的。
  • 相同的起始值或初始方程可以在分析后删除。

(目标是能够初始化模型,同时满足初始方程和固定的起始值。)

示例:在稳定状态下初始化的连续时间控制器:

  Real y(fixed = false); // fixed=false is redundant
equation
  der(y) = a*y + b*u;
initial equation 
  der(y) = 0;

初始化时将有下解:

der(y) = 0; 
y = -b/a *u;

示例:连续时间控制器的初始化,既可在稳定进行,也可通过为状态 y 提供起动值来初始化:

parameter Boolean steadyState = true;
parameter Real y0 = 0 "start value for y, if not steadyState";
Real y;
equation
  der(y) = a * y + b * u;
initial equation
  if steadyState then
    der(y) = 0;
  else
    y = y0;
  end if; 

也可写为(这种形式清晰性差一点):

parameter Boolean steadyState = true;
Real y (start = 0, fixed = not steadyState);
Real der_y (start = 0, fixed = steadyState) = der(y);
equation
  der(y) = a * y + b * u; 

示例: 在稳定状态下初始化的离散时间控制器:

discrete Real y;
equation
  when {initial(), sampleTrigger} then
    y = a * pre(y) + b * u;
  end when;
initial equation
  y = pre(y); 

初始化阶段将得出以下方程组:

y = a * pre(y) + b * u;
y = pre(y); 

解决方案:

y := (b * u) / (1 - a);
pre(y) := y; 

示例: 可复位的连续时间控制器在稳态或提供启动时初始化状态变量 y 的值:

parameter Boolean steadyState = true;
parameter Real y0 = 0 "start and reset value for y, if not steadyState";
input Boolean reset "For resetting integrator to y0";
Real y;
equation
  der(y) = a * y + b * u;
  when {initial(), reset} then
    if not (initial() and steadyState) then
      reinit(y, y0);
    end if;
  end when;
initial equation
  if steadyState then
    der(y) = 0;
  end if; 

如果 not steady State 为真这将在初始化时添加 y = y0;反之,则在初始化过程中忽略 reinit,并使用初始方程。这个模型可以用各种方式来写,这种特殊的方式清楚地表明重置等于正常的初始化。

在初始化过程中,这给出了以下等式:

if not steadyState then
  y = y0;
end if;
if steadyState then
  der(y) = 0;
end if; 

如果 steadyState 不是参数表达式,根据连接方程的限制,这两个方程都是非法的。

# 初始化所需的方程个数

通常,对于有 n 个变量和 m 个输出的纯(一阶)常微分方程(ODE)系统,在瞬态分析中,我们有 n+m 个未知变量。ODE 的初始化问题又增加了 n 个未知变量对应于微分变量。ODE 初始化时,我们要确定 2n+m 个变量的初值,而不是在瞬态分析中的 n+m 个变量。

示例:考虑下面的简单方程系统:

der(x1) = f1(x1);
der(x2) = f2(x2);
y = x1+x2+u;

这里,我们有 3 个变量是未知的:有两个动态变量,同时也是状态变量 x1 和 x2,即 n=2;一个输出变量 y,即 m=1;一个输入变量 u 有未知值。解决这个初值问题,需要确定 x1, x2, der(x1), der(x2), 和 y 的初值,因此另外需要 2 个初始化方程才能解决这个初值问题。此外,必须谨慎选择这两个初始方程,以确保它们与动态方程相结合,给出一个确定良好的初始化问题。

至于 DAE,在初始化系统中,最多只需要 n 个额外的方程就能达到 2n+m 个方程。原因是,高指标 DAE 问题中,动态连续时间状态变量的个数可能少于状态变量的个数 n,如初始化,初始方程和初始算法所说,通过适当的诊断,工具可以通过增加 / 删除初始化方程来满足这个要求。

# 起始值推荐优先级

一般来说,许多变量的开始属性不是固定的,选择其中的一个子集可以给出一组接近用户期望的一致的开始值。下面给出了一个寻找这样一个子集的非规范过程。

一个模型有一个分层的组件结构。模型的每个组件都可以被赋予一个唯一的模型组件层次级别号。顶层模型的层次号为 1。在模型组件层次结构中,每向下一层,层数增加 1。模型组件层次结构级别号用于给 start-attribute 一个置信度,其中较小的数字意味着 start-attribute 更有信心。不严谨的说,如果在第i层上设置或修改了 start-attribute,则置信度为i。如果一个 start-attribute 是由一个可能的分层修饰符在顶层设置的,那么这个 start-attribute 具有最高的置信度,即 1,无论在什么级别上,变量本身都被声明。如果 start-attribute 被设置为等于一个参数,该参数可能等于另一个参数(等等),则使用这些绑定的最低置信度。(在几乎所有情况下,这都是链中最后一个参数绑定的置信度。)请注意,这只适用于表达式恰好是参数的情况——而不是依赖于一个或多个参数的表达式。如果考虑参数绑定的置信度被捆绑,如果不相等,则使用 start 属性的置信度来打破这种捆绑。

示例: 显示 start-values 优先级的简化示例。示例 M3 表明直接使用参数置信度很重要,而不仅仅是当其他优先级被捆绑时:

model M1
  Real x(start = 4.0);
  Real y(start = 5.0);
equation
  x = y;
end M1;
model M2
  parameter Real xStart = 4.0;
  parameter Real yStart = 5.0;
  Real x(start = xStart);
  Real y(start = yStart);
equation
  x = y;
end M2;
model M3
  model MLocal
    parameter Real xStart = 4.0;
    Real x(start = xStart);
  end MLocal;
  model MLocalWrapped
    parameter Real xStart = 4.0;
    MLocal m(xStart = xStart);
  end MLocalWrapped;
  MLocal mx;
  MLocalWrapped my(xStart = 3.0);
equation
  mx.x = my.y;
end M3;
M1 m1(x(start = 3.0));
// Using m1.x.start = 3.0 with confidence number 1
// over m1.y.start = 5.0 with confidence number 2
M2 m2(xStart = 3.0);
// Using m2.x.start = m2.xStart = 3.0 with confidence number 1
// over m2.y.start = m2.yStart = 5.0 with confidence number 2
M3 m3(mx.x = 3.0);
// Using m3.mx.x = m3.mx.xStart = 3.0 with confidence number 1
// over m3.my.x = m3.my.xStart = 4.0 with confidence number 2