函数定义

如前所述,Modelica语言已经包含了很多有用的函数,可以方便地描述物体的数学特性。但不可避免地,有时候有必要创建用于特殊目的的新函数。定义这些新函数的方式及语法与模型定义相似。

基本语法

基本的Modelica函数都包含一个或多个参数、一个函数返回值以及根据参数计算函数返回值的algorithm。函数的输入参数以限定词input进行声明。函数的返回值以限定词output进行声明。例如,以下述简单函数为例,函数计算输入参数的平方值:

function Square
  input Real x;
  output Real y;
algorithm
  y := x*x;
end Square;

在上述例子中,输入变量x的类型为Real。输出变量y同样也是Real类型。函数的参数和返回值可以是标量或数组(甚至是记录类型,later章节才介绍记录类型)。

中间变量

对于复杂的计算过程,通过定义变量保存中间结果将会非常有帮助。这些变量必须与函数的参数和返回值明确的区分。为了声明这些中间变量,我们以限定词protected进行声明。以protected形式声明这些变量是为了告诉Modelica编译器这些变量既不是函数的参数也不是函数的返回值,而是函数计算过程中内部使用的变量。例如,如果我们希望编写一个函数来计算圆的周长,那就可能需要一个中间变量来储存圆的直径,即:

function Circumference
  input Real radius;
  output Real circumference;
protected
  Real diameter := radius*2;
algorithm
  circumference := 3.14159*diameter;
end Circumference;

在上述例子中,我们可以看到中间结果或公用子表达式可以与某个中间变量相关联。

默认的输入参数

在某些情况下,有必要为某些输入参数设置默认值。这种情况下,在声明输入参数时就可以包含了默认值。考虑如下计算物体势能的函数:

function PotentialEnergy
  input Real m "mass";
  input Real h "height";
  input Real g=9.81 "gravity";
  output Real pe "potential energy";
algorithm
  pe := m*g*h;
end PotentialEnergy;

为参数g设定默认值后,用户调用此函数时就不必每次都为g赋值了。当然,这种情况只适用于给定参数具有合理默认值的情况。如果你期望用户为参数提供数值的话,此种方法不适用。

调用函数时,这些具有默认值的参数将产生重要的影响。我们将稍后讨论这点。

多个返回值

其实,一个函数可以有多个返回值(即多个带有output限定词的变量声明)。例如,我们可以利用一个函数同时计算圆的周长和面积:

function CircleProperties
  input Real radius;
  output Real circumference;
  output Real area;
protected
  Real diameter := radius*2;
algorithm
  circumference := 3.14159*diameter;
  area := 3.14159*radius^2;
end CircleProperties;

我们接下来将在调用函数部分讨论如何设置函数的多个返回值。

调用函数

目前为止,我们已经介绍了如何去定义一个新的函数。接下来,我们将讨论各种调用函数的方法。一般的,函数调用的方式应该同时满足数学工作者和程序员的需求,例如:

f(z, t);

如上,我们可以看到典型语法函数调用语法:函数名称后带有括号,而里面的参数列表则由逗号进行分隔。但是,也有一些有趣的个例需要讨论。

上述语法需注意的是“参数位置”。这意味着,在调用此函数进行参数赋值时,要注意参数顺序。由于Modelica语言定义了函数的参数名称,因此也可以通过参数名调用函数。例如,参考下面计算立方体体积的函数:

function CylinderVolume
  input Real radius;
  input Real length;
  output Real volume;
algorithm
  volume = 3.14159*radius^2*length;
end CylinderVolume;

当调用此函数时,一定要注意不要混淆参数radius和length的顺序。为了避免由于参数顺序引起的混淆,我们可以使用参数名称来调用此函数。以这种方式实现函数调用的写法如下所示:

CylinderVolume(radius=0.5, length=12.0);

当调用具有默认参数值的函数时,使用参数名称调用将尤其方便。对于前面介绍的PotentialEnergy函数,可以有以下多种调用方式:

PotentialEnergy(1.0, 0.5, 9.79)       // m=1.0, h=0.5, g=9.79
PotentialEnergy(m=1.0, h=0.5, g=9.79) // m=1.0, h=0.5, g=9.79
PotentialEnergy(h=0.5, m=1.0, g=9.79) // m=1.0, h=0.5, g=9.79
PotentialEnergy(h=0.5, m=1.0)         // m=1.0, h=0.5, g=9.81
PotentialEnergy(0.5, 1.0)             // m=1.0, h=0.5, g=9.81

之所以对于具有默认参数值的函数,应使用参数名称实现函数调用,最重要的原因是:对于具有多个默认参数值的函数,你可以通过指定这些参数的名称以选择性地覆盖其默认值。

现在,我们讨论一下前面提到的一个函数可能具有多个返回值的情况。但问题是,我们该如何引用函数的多个返回值?让我们回顾一下本节前面部分定义的函数CircleProperties,下面的表达式展现了在实际使用时,我们应如何引用函数的多个返回值:

(c, a) := CircleProperties(radius);

换句话说,等号左边括号内用逗号分隔开的参数值是由函数的相应返回值进行赋值的(或在函数内部使用了equation区域时,等于相应值)。

根据上述讨论的结果,在Modelica语言中,有多种方式可以实现函数调用。

重要的限制条件

一般来说,我们可以利用函数或模型实现相同的计算功能。但是,两者之间存在一些重要的限制条件。

  1. 输入变量都是只读的——不可以对函数的输入变量进行赋值。

  2. 在函数中,不允许引用全局变量time

  3. 不允许存在多个方程或when语句——函数中只允许包含一个algorithm区域,并且不能包含when语句。

  4. 以下功能不能从函数中进行调用:derinitialterminalsamplepreedgechangereinitdelaycardinalityinStreamactualStream

  5. 参数、结果以及中间变量(protected)不能是模型或块。

  6. 数组的大小受限——参数是数组类型的可不指定其维数(请参考未定义维度部分)并且其大小由引用它的函数隐性确定。函数返回值是数组类型的,其大小必须由常数或与其相关联的输入参数的大小来定义。

需要非常注意的一点是,函数能不受限制地实现递归运算( 即函数允许调用自身)。

副作用

软件在环(SiL)控制器例子中,我们对外部函数的副作用进行了介绍。也就是说,函数的返回值并不是其参数在严格意义上的函数。像这类函数就具有所谓的“副作用”。具有副作用的函数应该以关键字impure进行声明。这就告诉Modelica编译器,这些函数不能被视为纯粹的数学函数。

这些以impure函数在使用时有其限制。它们只能被其他impure函数调用或在when语句中使用。

函数模板

考虑上述所有情况,下面的例子可以认为是一个广义函数定义的模板:

function FunctionName "A description of the function"
  input InputType1 argName1 "description of argument1";
  ...
  input InputTypeN argNameN := defaultValueN "description of argumentN";
  output OutputType1 returnName1 "description of return value 1";
  ...
  output OutputTypeN returnNameN "description of return value N";
protected
  InterType1 intermedVarName1 "description of intermediate variable 1";
  ...
  InterTypeN intermedVarNameN "description of intermediate variable N";
  annotation(key1=value1,key2=value2);
algorithm
  // Statements that use the values of argName1..argNameN
  // to compute intermedVarName1..intermedVarNameN
  // and ultimately returnName1..returnNameN
end FunctionName;