函数标注

标注章节中,我们已经对标注进行了讨论。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{\mathrm{d}f}{\mathrm{d}v}(x,y) = df(x, y, \frac{\partial x}{\partial v}, \frac{\partial y}{\partial v})\]

不敏感参数

我们考虑这样一种情况,表达式\(\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中输入变量ac,就可以计算得到参数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值用以指定是否应采用内嵌函数的方式。InlineLateInline标注的区别是,LateInline标注表明应在符号运算完成后执行内嵌功能。关于函数内嵌以及符号运算之间的交互关系超出了本书的讨论范围。

还有一点需要注意,当一个函数内同时应用了InlineLateInline标注时,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

对于LibraryInclude标注,我们有同样的问题。Library标注告诉我们需要添加那些库文件,但却没有告诉我们去哪里查找。另外,LibraryDirectoryIncludeDirectory标注具有相同的作用,如同IncludeDirectory标注,LibraryDirectory标注的值也可以是一个URL,其默认值如下所示:

LibraryDirectory=modelica://LibraryName/Resources/Library