函数标注¶
在标注章节中,我们已经对标注进行了讨论。Modelica语言包括一些专门用于函数的标准标注。这些标注的含义正式定义在Modelica规范中。在本节中,我们将讨论与函数相关的三大类标注,并讨论这些标注的必要性以及如何使用这些标注。
数学函数标注¶
第一大类标注的主要功能是为数学函数提供一些额外的信息。因为这些数学函数的功能都是在algorithm
区域实现的,因此通常情况下,编译器不能根据Modelica语言内的符号操作得出上述函数方程的行为。但是,本节中的标注可以用来为函数定义补充相关数学信息。
derivative
¶
正如我们在多项式计算示例中讨论的那样,有些情况下,我们需告诉Modelica编译器如何计算给定函数的导数。上述功能主要是通过在函数定义时添加derivative
标注来实现的。
简单的一阶导数¶
derivative
标注的基本用途是为了指定另一个Modelica函数的名称,用来计算被标注函数的一阶导数。例如:
function f
input Real x;
input Real y;
output Real z;
annotation(derivative=df);
algorithm
z := // some expression involving x and y
end f;
function df
input Real x;
input Real y;
input Real dx;
input Real dy;
output Real dz;
algorithm
dz := // some expression involving x, y, dx and dy
end df;
通过上述例子可以看到,在这种情况下,导数函数的第一个参数df
与原始函数f
的第一个参数相同。然后,紧随此参数后面的是原函数输入变量的微分形式。最后,导数函数的输出变量等同于原始函数输出变量的微分。听上去上述过程非常复杂,但读者应该能从上述代码中发现,要构造这样的函数其实是非常简单的。
给定如下的Modelica函数,Modelica编译器就可以使用下列函数来计算函数的一系列导数,例如:
不敏感参数¶
我们考虑这样一种情况,表达式\(\frac{\partial y}{\partial v}\)等于0。导数函数将会输出这个零值(或零值数组,若参数为数组)。上述零值会在导数函数内部被数次调用,其中大多数(如果不是全部)将调用零值进行乘法运所。此时计算的结果也将为零。最终,这些计算结果将会与其他结果相加,但是对函数的最终输出没有任何影响。换句话说,这种情况下,有很多计算其实可以跳过,因为它们对结果不会有任何影响。
在这种情况下,Modelica语言提供了一种避免上述冗余计算的方法。如果Modelica编译器能预先知道参数的微分为0,它就可以(在derivative
标注内)检查是否有函数计算导数。这些情况是通过在derivative
标注使用zeroDerivative
参数进行指定的。因此,对于上述示例中的函数f
,我们增加了如下标注内容:
function f
input Real x;
input Real y;
output Real z;
annotation(derivative=df, derivative(zeroDerivative=y)=df_onlyx);
algorithm
z := // some expression involving x and y
end f;
其中,df_onlyx
函数被定义为如下形式:
function df_onlyx
input Real x;
input Real y;
input Real dx;
output Real dz;
algorithm
dz := // some expression involving x, y, dx
end df_onlyx;
通过比较可以看到,此函数不包括dy
项。因此,函数只适用于dy
为0的情况。而且,因为参数不包括dy
项,该函数只包括那些涉及参数dx
的计算方程。
二阶导数¶
对于二阶导数,这里有一些变化需要说明一下。首先要知道应如何指定函数的二阶导数。这主要是通过添加一个order
变量来实现的。需要注意的是,函数可以包括多个derivative
标注,例如:
function f
input Real x;
input Real y;
output Real z;
annotation(derivative=df, derivative(order=2)=ddf);
algorithm
z := // some expression involving x and y
end f;
function df
...
end df;
function ddf
input Real x;
input Real y;
input Real dx;
input Real dy;
input Real ddx;
input Real ddy;
output Real ddz;
algorithm
ddz := // some expression involving x, y, dx, dy,
// ddx and ddz
end ddf;
希望你对上述内容并不陌生。为了实现二阶导数的计算,需要在原函数内添加额外的derivative
标注,即:
annotation(derivative=df, derivative(order=2)=ddf);
这些额外的标注增加了一个order
变量用于表明函数求解的是第几阶的导数。
非实数参数¶
此外,对于其他一些复杂情况还需讨论。例如,函数内有时会包含一些非实数的参数,即:
function g
input Real x;
input Integer y;
output Real z;
algorithm
z := // some expression involving x and y
end g;
要计算上述函数对参数y
的导数就没有什么意义了,因为参数y
的数据类型是整型。在求解函数导数时,我们可以忽略任何非实数的参数。因此,如果我们希望计算上述函数的导数,可以采用下述方式:
function g
input Real x;
input Integer y;
output Real z;
annotation(derivative=dg);
algorithm
z := // some expression involving x and y
end g;
function dg
input Real x;
input Integer y;
input Real dx;
output Real dz;
algorithm
dz := // some expression involving x, y and dx
end dg;
换句话说,我们只对那些类型为实数的参数进行微分计算。
inverse
(反函数)¶
在非线性章节中,我们讨论了使用inverse
标注。该标注为Modelica编译器提供了如何计算相应函数反函数的信息。逆函数作用在于它能显式地求解函数的某个输入变量。因此,inverse
标注包含一个涉及当前函数输入、输出变量的显式方程。这样,标注可利用另一个函数去直接求解其中一个输入变量。
例如,对于一个定义如下的Modelica函数:
function h
input Real a;
input Real b;
output Real c;
annotation(inverse(b = h_inv_b(a, c)));
algorithm
c := // some calculation involving a and b
end h;
我们可以在根据上述代码里看到,通过向函数h_inv_b
中输入变量a
和c
,就可以计算得到参数b
的表达式,如下所示:
function h_inv_b
input Real a;
input Real c;
output Real b;
algorithm
b := // some calculation involving a and c
end h_inv_b;
代码生成¶
另一大类标注是关于如何将定义的函数转换为仿真用的代码。这些标注允许模型开发者在Modelica编译器生成代码的过程提供一定的帮助。
Inline
¶
Inline
标注用于提示Modelica编译器,函数中的语句是否应被“内联”。此标注的取值用于决定了执行内联与否。标注的默认值为false
(如果函数定义中没有出现Inline
标注)。下面是使用Inline
标注定义的函数示例:
function SimpleCalculation
input Real x;
input Real y;
output Real z;
annotation(Inline=true);
algorithm
z := 2*x-y;
end SimpleCalculation;
在上述例子中,我们可以看到,Inline
标注表明Modelica编译器应内嵌上述定义的SimpleCalculation
函数。函数的内嵌是通过调用计算输出变量的函数进行替换的。这对于执行非常简单计算功能的函数是非常有用的。这些情况下,调用该函数的“成本”(计算机CPU的计算时间)与函数执行的成本基本是一个数量级的。通过内嵌函数,调用函数的成本可以大大消除,同时依然能实现函数的功能。
Inline
标注的功能只是用于提示Modelica编译器,编译器不会自动关联内嵌函数。此外,编译器内嵌函数的能力取决于函数的复杂程度。通常情况下,编译器没有必要(或者期望)去内嵌函数。
LateInline
¶
与Inline标注一样,LateInline
标注的功能是告诉Modelica编译器采用内嵌函数的方式将更有效。LateInline
标注也会分配一个Boolean
值用以指定是否应采用内嵌函数的方式。Inline
和LateInline
标注的区别是,LateInline
标注表明应在符号运算完成后执行内嵌功能。关于函数内嵌以及符号运算之间的交互关系超出了本书的讨论范围。
还有一点需要注意,当一个函数内同时应用了Inline
和LateInline
标注时,LateInline
标注的优先级要高于Inline
标注,即:
Inline |
LateInline |
说明 |
false |
false |
Inline=false |
true |
false |
Inline=true |
false |
true |
LateInline=true |
true |
true |
LateInline=true |
外部函数¶
最后一大类标注是与定义的external
函数相关的。这些外部函数的定义通常都依靠外部文件或库。使用这类标注就是为了通知Modelica编译器这些依赖关系以及如何定位这些外部函数。
Include
¶
通常情况下,Modelica编译器生成代码过程中如果使用了某个外部库,需添加相应的Include
标注,以此标示包含语句。Include
标注的值是字符串,并且会嵌入到生成的代码文件中,例如:
annotation(Include="#include \"mydefs.h\"");
Note
Include
标注的值是一个字符串,如果它包含嵌入的字符串,需进行相应的转义。
IncludeDirectory
¶
在Include标注,我们已经讨论了该标注允许在生成的代码内插入路径。IncludeDirectory
标注指定了Include
标注声明的内容应在哪个目录下搜索。
该标注的值是一个字符串。字符串可以是一个目录或者URL。例如,IncludeDirectory
标注的默认值如下所示:
IncludeDirectory=modelica://LibraryName/Resources/Include
稍后,我们将对modelica:// URL地址的含义进行解释。
Library
¶
Library
标注主要用于指定函数可能依赖的所有编译库,该标注的值可以是一个表示库名字的字符串,也可以是上述字符串组成的数组,即:
annotation(Library="somelib");
或者
annotation(Library={"onelib","anotherlib"});
Modelica编译器在“链接”生成代码时将使用这些信息。
LibraryDirectory
¶
对于Library
和Include
标注,我们有同样的问题。Library
标注告诉我们需要添加那些库文件,但却没有告诉我们去哪里查找。另外,LibraryDirectory
与IncludeDirectory标注具有相同的作用,如同IncludeDirectory
标注,LibraryDirectory
标注的值也可以是一个URL,其默认值如下所示:
LibraryDirectory=modelica://LibraryName/Resources/Library