# 陈述式建模
本节主要介绍模型的非因果性与模型的确定性。
# 模型的非因果性
谈及陈述式建模,通常能够快速联想到的是模型的非因果性。陈述式模型的非因果性本质上源于模型的构建与模型的求解是相互独立的。模型的非因果性使得其可以适用多种不同使用环境下的求解需要。一般而言,模型的非因果性来自于方程。Modelica 语言本质上是一种基于方程的建模语言。过程式描述作为陈述式描述的一种特例,也被Modelica 语言所支持,这表现在可以在模型中使用函数与算法。函数与算法的过程式特性表现为在仿真计算过程中函数与算法按照其代码描述形式自上而下地顺序执行。
基于函数与算法代码的过程式描述特性,很容易造成一种误解,认为如果模型中只使用算法,就可以构建一种因果模型。或者认为,在计算关系明确的情况下使用算法进行描述,可以省去编译过程中求解关系推导的环节,从而提升模型的编译速度。在过程式的建模环境中,这两种观点是正确的,但在陈述式的建模环境中则不成立,有时还可能带来负面影响。下面结合示例予以解释。
首先,从执行方式上可以将算法等效为一个函数。
model Case3_1
function fn
input Real u;
output Real z;
algorithm
z:=u^2+1;
end fn;
Real x;
Real y1;
Real y2;
equation
x=sin(time);
y1=fn(x);
algorithm
y2:=x^2+1;
end Case3_1;
在上面的示例模型Case3_1 中,模型算法代码等效为函数fn ,故而变量y1 与y2 的结果是一致的。在 Modelica 语言中,函数默认情况下严格遵循数学函数的定义:自变量到因变量的映射,或者说输入到输出的映射。这意味着输出变量完全由输入变量决定。因此,数学意义上的函数与程序语言中的函数(满足某项功能的程序代码块)不能混为一谈。
通过以上阐述可得出结论:Modelica 模型中每个算法,在概念上均可以等效为一个数学函数。
其次,要介绍下非线性方程的定义。非线性方程是指因变量与自变量之间不满足线性关系的方程,非线性方程笼统地可分为代数多项式方程与超越函数(非多项式)方程。
从非线性方程的定义来看,示例模型 Case3_1 中的方程 y1=fn(x) 属于非线性方程。基于方程的非因果特性,就单个方程而言,方程 y1=fn(x) 既可以用于求解变量y1 ,也可以用于求解变量x 。前面已经指出,算法在概念上可以等效为一个数学函数,因此在Modelica 模型求解时,算法既可以用于求解输出变量,也可以用于求解输入变量。这也就是说,在Modelica 模型中算法与方程一样,其求解关系也是非因果的。
由此可见,尽管算法代码是过程式执行的,但其求解关系可以是非因果的。若算法用于求解输入变量,则等效为一个非线性方程。此外,尽管模型中每个组件模型均只使用算法,但算法之间可能因为输入输出变量相互关联而构成环路,即形成代数环,此时等效为一个非线性方程组。此外,算法可以与方程混用,从而使得算法可能用于求解其中的输入变量,例如:
model Case3_2
function fn
input Real u;
output Real z;
algorithm
z:=u^2+1;
end fn;
Real x1;
Real x2;
Real y1;
Real y2;
equation
y1=3+sin(time);
y2=3+sin(time);
y1=fn(x1);
algorithm
y2:=x2^2+1;
end Case3_2;
在示例模型 Case3_2 中,算法用于求解输入变量x2 ,方程y1=fn(x1) 用于求解变量x1 。因此,在 Modelica 模型中,算法与方程一样,均是非因果的。当然,如果算法的输出变量为非实型变量,则被限定为因果的,即只能已知输入变量求解输出变量。
在此还需要指出的是,如果算法只有一个输出变量,那与使用方程一样,对模型编译效率没有影响。但如果算法包含多个输出变量,特别是同时包含实型输出变量与非实型输出变量的情况,那对模型编译效率反而可能造成负面影响。具体原因在此不展开分析,将在后续的函数与算法专题中进行解释。因此,鉴于算法的使用要求较高,并且若使用不恰当则可能带来负面影响,因此笔者曾倡议在方程可以满足表达要求的情况下尽可能避免使用算法。
# 模型的确定性
由于陈述式模型的描述与求解是相互独立的,故而要求模型经过翻译之后可以得到一个确定的数学形式。基于此确定数学形式,进而得到一个确定的仿真求解结果。一个连续系统的 Modelica模型经过翻译之后可表示为如下数学形式。
其中,
在公式中,cat为Modelica数组拼接操作符。状态变量与代数变量均随时间变化,严格来说应表示为时间的函数形式,即
# 注意事项(一)
对于常微分方程模型,状态变量就是模型出现在导数符 der 中的变量,如跳球模型。对于微分代数模型,状态变量只是出现在导数符der 中的变量的一部分,此时就涉及到状态变量的选取问题,需要合理设置变量的stateSelect与start属性值,尽可能确保模型经过翻译可以得到唯一的确定形式模型。如下示例模型的翻译结果是不唯一的。
model Case3_3
Real x;
Real y;
equation
der(x)+der(y)=1;
y=x+0.5;
end Case3_3;
示例模型Case3_3是一个微分代数模型,变量x与y的导数均出现在模型中,但经过翻译之后只能有一个状态变量,这个状态变量是x还是y是不确定的,而选择x还是y作为状态变量,仿真结果是不一样的。此类不确定模型的仿真结果依赖于Modelica编译器的默认状态变量选择策略。由此带来的问题就是:
(1)在不同编译器下仿真结果不一样;
(2)编译器升级了,默认状态变量选择策略(通常与变量声明顺序、变量名、变量在方程中的出现顺等有关)改变了,仿真结果不一样;
(3)变量名修改了或者变量的声明顺序变了,仿真结果不一样。
若要让Case3_3变为一个严格确定的模型,则需要将x或y的内置属性stateSelect显式设置为StateSelect.always,例如:
model Case3_4
Real x(stateSelect=StateSelect.always);
Real y;
equation
der(x)+der(y)=1;
y=x+0.5;
end Case3_4;
模型Case3_4在任何严格遵循Modelica规范的工具中仿真结果应该是完全一致的,状态变量应该为x。就Case3_3而言,根据Modelica规范,只要让x与y的内置属性stateSelect不一样,则均可以得到确定的状态变量。例如:
model Case3_5
Real x;
Real y(stateSelect=StateSelect.prefer);
equation
der(x)+der(y)=1;
y=x+0.5;
end Case3_5;
根据Modelica规范,可以根据内置属性stateSelect的等级高低确定状态变量,在模型Case3_5中y优先于x被选取为状态变量。此外,在内置属性stateSelect相同的情况下,Sysplorer在选择状态变量时会考虑变量是否具有start值,尽管Modelica规范没有规定要求如此处理,但因为模型仿真时需要为状态变量提供初始值,故此若内置属性stateSelect相同的两个变量中,一个显式设置了start值而另一个没有,则可以优先选择设置了start值的变量作为状态变量。例如:
model Case3_6
Real x;
Real y(start=0);
equation
der(x)+der(y)=1;
y=x+0.5;
end Case3_6;
对于Case3_6,Sysplorer会选择变量y作为状态变量,因为变量y具有显式start值,而变量x的start值是缺省的。当然,在选择状态变量时也会考虑变量的fixed属性值,例如:
model Case3_7
Real x(start=0.5,fixed=true);
Real y(start=1);
equation
der(x)+der(y)=1;
y=x+0.5;
end Case3_7;
对于Case3_7,Sysplorer会选择变量x作为状态变量,因为变量y的fixed属性值被设置为true,而变量x的fixed属性值为默认值false。综合以上情况,对于Case3_3,变量x与y的内置属性stateSelect,fixed,start均相同,即建模者没有为状态变量选取提供任何优选线索,故而该模型必定是一个不确定模型。对于该种情况,Sysplorer根据其内部规则进行选取,选取结果与变量名、是否存在变量的非线性项等因素相关。
特别指出,在同元人构建的模型中状态变量不确定的情况还是比较常见,甚至出现在同元库的示例模型中,这是建模有待改进的一个较为突出的问题。
注意
在模型翻译输出的统计信息中,若输出了哑导变量数目,则表明该模型需要选取状态变量。
# 注意事项(二)
基于前面的公式可知,积分过程中在时间变量 Modelica模型需要满足的前提条件。对于如下模型Case3_8,该条件是不满足的。
model Case3_8
Real x;
Real y(start=0.1);
equation
der(x)=sin(time);
algorithm
y:=y+x;
end Case3_8;
对于示例Case3_8,在仿真时间不往前推进情况下,每计算一次模型,代数变量y就变化一次,故而模型求解过程中可能的重复计算将导致仿真结果不一致。特别是在变步长积分算法情况下,积分步长与用户设置的求解精度相关,而积分步长的不同将导致模型的计算次数不同。故当用户选择不同的积分算法或设置不同的求解精度,得到的仿真结果是不一样的。此类仿真结果不一致问题不是求解器的问题,而是模型的问题,此类模型是不确定模型,显然不满足前面给出的公式。基于前面的公式,在时间与状态变量不变情况下,代数变量应该保持不变。此问题的出现,或多或少受到了过程式编程语言的影响。在程序中可以自行约定变量的含义,而在Modelica 模型中,除了出现在某些特定的内置操作符参数中之外,一个变量不管出现多少次,不管出现在等号或赋值号的左边还是右边,均是指同一时间的同一个变量。
为了消除算法使用不当带来的仿真结果不确定的问题,Sysplorer 根据Modelica规范的建议在模型翻译过程中会对算法的输出变量进行分析,对于先引用后赋值,或者赋值不确定一定会执行(例如条件语句中的赋值,在条件不成立情况下不会被执行)的情况,会进行缺省赋值补充。对于Case3_8中的算法,经过模型翻译之后,算法会变为如下形式:
algorithm
y:=0.1;
y:=y+x;
经过缺省赋值补充之后,在时间不变情况下,算法不管被执行多少次,变量y的结果是一致的。此示例表明,算法的使用给模型翻译带来了额外的处理工作,加大了时间开销。
除了以上两类模型不确定性之外,另一类是模型初始值不确定问题,例如Case3_9。
model Case3_9
Real x1(start = 1);
Real x2(start = 1);
parameter Real lambda = 0.3;
equation
der(x1) = x2;
der(x2) = -x1 + lambda * (1 - x1 * x1) * x2;
initial equation
x1 = x2 + 0.5;
end Case3_9;
在Case3_9中有两个状态变量,需要给定两个初始条件,而模型中只有一个初始方程,两个状态变量x1与x2均显式提供了start值,fixed属性均缺省。根据模型表达,既可以选择x1的start值作为初始条件,也可以选择x2的start值作为初始条件,而两种选择方式的仿真结果是不一致的。若要将Case3_9修改为初始条件确定的模型,稳妥的做法是将变量x1或x2的fixed属性设置为true,如Case3_10所示。
model Case3_10
Real x1(start = 1);
Real x2(start = 1,fixed=true);
parameter Real lambda = 0.3;
equation
der(x1) = x2;
der(x2) = -x1 + lambda * (1 - x1 * x1) * x2;
initial equation
x1 = x2 + 0.5;
end Case3_10;
在Case3_10中x2的start值被选为初始条件。另一种做法是只为x1与x2中的某一个变量显式提供start值,如Case3_11所示。
model Case3_11
Real x1;
Real x2(start = 1);
parameter Real lambda = 0.3;
equation
der(x1) = x2;
der(x2) = -x1 + lambda * (1 - x1 * x1) * x2;
initial equation
x1 = x2 + 0.5;
end Case3_11;
对于Case3_11,尽管Modelica规范没有明确规定x2的start值必须作为初始条件,但建模者没有为x1显式提供start值,故从建模意图的角度推断,Sysplorer优先选择x2的start值作为初始条件。因此,尽管Case3_11在Sysplorer不存在初始条件不确定问题,但这种表达式方式终究没有的得到Modelica规范的明确支持,故更为稳妥的方式是同时将x2的fixed属性设置为true。
前已述及,状态变量随时间变化,是时间变量的一个函数,在时间不变情况下状态变量保持不变。唯一的例外情况是,在事件时间可以使用内置操作符reinit对状态变量进行重置,例如下面的跳球模型。
model BouncingBall
Real v(start=0);
Real h(start = 10);
Boolean flying;
parameter Real c = 0.9;
constant Real g = 9.81;
equation
der(h) = v;
der(v) = if flying then -g else 0;
flying = not (h <= 0 and v <= 0);
when h <= 0 then
reinit(v, -c * v);
end when;
when not (flying or pre(flying)) then
terminate("Bouncing Stop!");
end when;
end BouncingBall;
对于reinit操作符,将专题进行更详细的介绍。
# 小结
本专题重点介绍了陈述式建模的两大显著特性:模型的非因果性与模型的确定性。相比于模型的非因果性,模型的确定性理解起来更难一些。在近些年的工程应用过程中,在模型的确定性方面出现过较多的问题。对于状态变量的理解普遍有些不太到位,表现在模型建成之后不知道要考虑状态变量设置,不了解需要合理设置初始条件,对代数变量使用reinit操作,等等。深入理解陈述式模型的确定性问题,对于构建高质量的可靠模型是十分必要的。