配置管理

replaceable

真正让Modelica实现配置管理特性的是replaceable关键字。此关键词是用以标记其类型在之后可以改变(或“重新声明”)的模型部件,。一种理解replaceable的方法是,它允许模型开发人员在模型里定义插孔。这些模型要么一张“白纸”(接口模型就是模型里定义的原始类型),或者最少是“可配置的”。

使用replaceable关键字的优点是,它允许不通过重新接线而创建新模型。这不仅规定了未来模型的结构框架(可以确保命名惯例、共同接口等被遵守),这也有助于减少模型开发过程中的一种潜在错误:即创建连接。

要让组件变成可替换所须要做的唯一事情就是在声明前添加replaceable关键字,即:

replaceable DeclaredType variableName;

其中DeclaredType是名为variableName的变量其初始类型。在这样的声明中,我们可以赋予variableName变量一个新类型(我们即将讨论详细的做法)。但任何variableName使用的新类型必须插件兼容于原类型。

constrainedby

正如我们刚才提到的,默认情况下任何replaceable组件的新类型必须插件兼容于最初的类型。但这种情况其实不一定必须的。我们先前对约束类型的讨论已经指出,我们能够同时为变量指定一个缺省类型,以及一个独立约束类型,令替换的新类型必须与之兼容。

指定另外约束类型需要使用到constrainedby关键字。使用constrainedby关键字语法如下:

replaceable DefaultType variableName constrainedby ConstrainingType;

其中variableName是前面变量声明的名字,DefaultType代表variableName类型。ConstrainingType表示约束类型。如前所述,variableName变量使用的任何新类型必须插件兼容于约束类型。另外,当然DefaultType必须插件兼容于约束类型了。

constrainedby vs. extends

旧版本的Modelica不包含constrainedby关键字。有相同功能的是extends关键字。但有人认为,继承和插件的兼容性截然不同,足矣用一个单独的关键字去减少混乱。所以,如果一段Modelica代码里在本该出现constrainedby关键字的地方(即在replaceable声明的后面)你却看到了extends关键字。

redeclare

所以现在我们知道,通过使用replaceable关键字,可以让我们在之后改变一个变量的类型。改变类型又称为“重新声明”变量(或者说声明为不同的类型)。出于这个原因,用redeclare关键字指示重声明甚为恰当。若我们有以下的系统模型:

model System
  Plant plant;
  Controller controller;
  Actuator actuator;
  replaceable Sensor sensor;
end System;

System内只有传感器是replaceable。因此,其他各子系统(即plantcontrolleractuator)的类型不能改变。

如果我们想在扩展这个模型时使用不同的sensor子系统模型,那我们会以如下方式使用redeclare关键字:

model SystemVariation
  extends System(
    redeclare CheapSensor sensor
  );
end SystemVariation;

这告诉了Modelica语言编译器是在SystemVariation模型的上下文内,sensor子系统应该是CheapSensor模型的一个实例,而非(一般情况下默认的)Sensor模型。但是CheapSensor模型(或重声明时选择的任何其他类型)必须插件兼容于该变量的约束类型

redeclare语句的语法和正常的声明真的几乎完全一样。唯一不同之处在于语句前面的redeclare关键字。很明显,重新声明的任何变量必须事先曾被声明(即你不能使用此语法来声明一个变量,只能重声明它)。

你重定义一个组件时,新的重声明会取代前面的重声明。这点非常重要。例如,下面的重声明之后:

redeclare CheapSensor sensor;

sensor组件不再能被替换。原因是新的声明不包括replaceable关键字。其结果是,此关键词虽然此前在最初的模型有定义,在所有以后的模型却如同不存在了。如果我们希望部件以保持可替换,重新声明将需要写为:

redeclare replaceable CheapSensor sensor;

此外,如果我们重新将变量声明成可替换的,我们还可以选择重新声明约束类型,如下:

redeclare replaceable CheapSensor sensor constrainedby NewSensorType;

但是,原始约束类型哪怕在这种情况下仍然能发挥作用。原因是新类型NewSensorType必须插件兼容于原始约束类型。用编程语言的术语,我们能够收缩类型(减少插件兼容类型的种类),而不能拓宽类型(将此前并非插件兼容的类型变为插件兼容)。

此前在讨论组件数组时,我们指出了重新声明数组中的各个元素并不可能。相反,重声明必须应用到整个数组。换句话说,如果我们最初声明如下:

replaceable Sensor sensors[5];

然后可以重新声明数组,例如:

redeclare CheapSensor sensors[5];

但要注意,重新声明影响sensors数组中的每个元素。仅仅重新定义一个元素是不可能的。

修改

可替换性带来的一个重要问题出现在有重声明时进行修改的情况下。要理解这个问题,考虑下面的例子。

replaceable SampleHoldSensor sensor(sample_rate=0.01)
  constrainedby Sensor;

现在,如果我们要以如下语句重定义sensor,会发生什么情况呢?

redeclare IdealSensor sensor;

sample_rate值会消失么?我们希望如此。因为IdealSensor模型可能不具有名为sample_rateparameter以供修改。

然而,让我们考虑另外一种情况:

replaceable Resistor R1(R=100);

现在想象我们有另外一个电阻器模型SensitiveResistor。而这个模型插件兼容于Resistor(即它有一个名为Rparameter)。但模型也包括一个额外的参数dRdT,以指示电阻的(线性)温度敏感度。我们可能想要做这样的事情:

redeclare SensitiveResistor R1(dRdT=0.1);

R在这种情况下会如何呢?在这里,我们实际上希望保持R的值,让其在整个重声明仍然存在。否则,我们就需要不断重申这点,即:

redeclare SensitiveResistor R1(R=100, dRdT=0.1);

这就会违反DRY原则。其结果将是,在R原始值的任何改变都会被任意的重声明所覆盖。

因此,我们已经看到了两个有效用例。在一种情况下,我们不希望在重声明后保留修改。在另一个例子里,我们则想保留修改。幸好,Modelica语言可以方法可以表达这两种需求。正常的Modelica语义可用于第一种情况。在重定义时,原声明中的所有修改都会被去除。至于第二种情况呢?解决方案是应用约束类型上的修改。因此,以前面的电阻为例,原来的声明将要修改为:

replaceable Resistor R1 constrainedby Resistor(R=100);

在这里我们单独地明确列出默认类型Resistor以及约束类型Resistor(R=100)。原因是约束类型现在包含了修改。这样将修改移动到约束类型后,该修改会自动应用到原来的声明以及随后的重声明里。因此,在这种情况下,电阻器实例R1R值为100。即便修改不直接加在变量名后。而此外,如果我们进行前述的重声明,即:

redeclare SensitiveResistor R1(dRdT=0.1);

R=100修改也会自动应用于此

总之,如果你想修改只应用于特定的声明不影响随后的重声明,那么应该将修改附加在变量名。如果你想让修改持续影响随后的重声明,就应将其附加到约束型后。

重定义

另外,replaceable关键字不仅可以和声明相关联,也可以与定义有关。此功能的主要用途是一次性改变多个组件的类型。举个例子,假设电路模型有数个不同的电阻元件:

model Circuit
  Resistor R1(R=100);
  Resistor R2(R=150);
  Resistor R4(R=45);
  Resistor R5(R=90);
  // ...
equation
  connect(R1.p, R2.n);
  connect(R1.n, R3.p);
  // ...
end Circuit;

现在想象一下,我们希望这个模型有一个版本使用普通的Resistor组件。而另一个版本里,每个电阻均为SensitiveResistor模型的实例。为此,其中一种方法是定义我们的Circuit如下:

model Circuit
  replaceable Resistor R1 constrainedby Resistor(R=100);
  replaceable Resistor R2 constrainedby Resistor(R=150);
  replaceable Resistor R4 constrainedby Resistor(R=45);
  replaceable Resistor R5 constrainedby Resistor(R=90);
  // ...
equation
  connect(R1.p, R2.n);
  connect(R1.n, R3.p);
  // ...
end Circuit;

但在这种情况下,带有SensitiveResistor组件的电路将定义为:

model SensitiveCircuit
  extends Circuit(
    redeclare SensitiveResistor R1(dRdT=0.1),
    redeclare SensitiveResistor R2(dRdT=0.1),
    redeclare SensitiveResistor R3(dRdT=0.1),
    redeclare SensitiveResistor R4(dRdT=0.1)
  );
end SensitiveCircuit;

注意,我们不必另外指定电阻值。原因是电阻的设置是附加在Circuit模型的约束类型上的。但我们必须一而再地改变每个电阻模型然后指定dRdT,即便取值均是一样的。这多少有些无趣。不过, Modelica提供了一种方法去一次性修改所有部件。首先,在模型内定义一个本地类型:

model Circuit
  model ResistorModel = Resistor;
  ResistorModel R1(R=100);
  ResistorModel R2(R=150);
  ResistorModel R4(R=45);
  ResistorModel R5(R=90);
  // ...
equation
  connect(R1.p, R2.n);
  connect(R1.n, R3.p);
  // ...
end Circuit;

这样做是为了帮Resistor创建别名ResistorModel。这本身并不能帮助我们一次性改变每个电阻的类型。但通过将ResistorModel变为replaceable则可以做到这点:

model Circuit
  replaceable model ResistorModel = Resistor;
  ResistorModel R1(R=100);
  ResistorModel R2(R=150);
  ResistorModel R4(R=45);
  ResistorModel R5(R=90);
  // ...
equation
  connect(R1.p, R2.n);
  connect(R1.n, R3.p);
  // ...
end Circuit;

若我们的Circuit模型定义如上,那么就可以创建如下的SensitiveCircuit模型:

model SensitiveCircuit
  extends Circuit(
    redeclare ResistorModel = SensitiveResistor(dRdT=0.1)
  );
end SensitiveCircuit;

所有的电阻元件类型仍为ResistorModel,我们没有必要重声明任何一个元素。通过将ResistorModel类型定义改变为SensitiveResistor(dRdT=0.1),我们做到了重新定义什么是ResistorModel。注意,dRdT=0.1这一更改将应用到类型为ResistorModel的所有组件。从技术上讲,这不是组件类型的重声明,而是一个类型的重定义。但是,我们再次使用了redeclare关键字。

有趣的是,这些重定义仍然有默认类型和约束类型的概念。重定义类型的一般语法为:

replaceable model AliasType = DefaultType(...) constrainedby ConstrainingType(...);

正如可替换组件的情况一样,任何对默认类型DefaultType进行的修改,只会在不重新定义AliasType的情况下有效。不过,任何对约束型ConstrainingType进行的修改,都会在重定义之后会继续有效。此外,AliasType必须总是插件兼容于约束类型。

虽然相对于可更换组件,语言的这个特性使用并不太频繁。但这个特性可以节省时间,并有助于避免在特定情况下的错误。

选择

这部分重点是配置管理。我们已经了解到,约束类型决定redeclare时可选的模型。如果由一个模型开发人员同时创建架构模型以及所有兼容的实现,那么他会知道需要使用什么约束类型匹配潜在的配置。

但如果你在使用的是由别人开发的架构模型呢?你如何确定存在哪些可能模型呢?好在Modelica语言规范包括一些标准标注以解决这一问题。

choices

choices标注允许原模型的开发者将给定声明和一系列修改相关联。在最简单的情况下,用户可以为特定参数指定不同的值:

parameter Modelica.SIunits.Density rho
  annotation(choices(choice=1.1455 "Air",
                     choice=992.2 "Water"));

在本例,模型开发者为rho参数列出了几个用户可能使用的赋值。而上述每个选项均为对rho变量的修改。此信息常用在图形化的Modelica工具里,目的是为用户提供智能化的选项。

这个功能也可以很容易地在配置管理的情况中使用。请看下面的例子:

replaceable IdealSensor sensor constrainedby Sensor
  annotation(
    choices(
      choice=redeclare SampleHoldSensor sensor
             "Sample and hold sensor",
      choice=redeclare IdealSensor sensor
             "An ideal sensor"));

同样,模型开发者可以将一组可能的修改连同声明一起。在使用图形工具时,这些choice值可为用户提供一套合理的选项去配置系统。

choicesAllMatching

不过,这里有一个问题。要明确列出所有这些选择不仅麻烦,而且适合的模型选项也可能会改变。毕竟,其他开发人员(原始模型开发者以外的人)也可能去创建某个特定约束型的实现。为何不让用户在配置自己的系统时可以选择见到所有的合规选项呢?

好在Modelica正正包含了这样的标注。这个标注就是choicesAllMatching。只要在给定的声明(或replaceable定义)将此标注设置为True,Modelica工具就会因此而查找所有合规的选项,并将其展现在用户界面上。例如,

replaceable IdealSensor sensor constrainedby Sensor
  annotation(choicesAllMatching=true);

通过添加此标注,建模工具会在用户用图形界面重新配置他们的模型时找到所有合规的重声明模型。这大大可以增加架构模型的实用性。原因是,此标注在稍微增加模型开发者的工作量的前提下为用户提供了全方位的选择。

结论

在本节中,我们讨论了Modelica的配置管理功能。本功能与Modelica其他的方面有着相同目标:促进重用,提高生产效率,并确保正确性。Modelica的包括许多功能强大的选项去进行组件重声明以及类型重定义。将这点与choicesAllMatching标注结合,模型可以用一些明确的选项去支持大量的配置。