组件模型

本节我们将总结组件模型与此前创建的模型有何不同。这里讨论将分为两部分。第一部分将着眼于非因果建模。要如何才能为基于概要、面向组件的建模提供一个框架。这样,模型才能自动生成以及遵从守恒方程。第二部分将概述本章的主题会如何(主要是语法上)影响组件模型的定义。。

不过,我们在深入讨论前,值得花些时间讨论术语。本章我们已经创建了两种不同类型的模型。第一种表示单独的效果(例如电阻、电容、弹簧、阻尼器)。另一种代表更复杂的组件(如电路、机制) 。

而在讨论这些不同类模型间的差异前,下面我们来介绍一些术语以便能进行准确表述。组件模型是以可重用形式封装方程的模型。通过建立这样的模型,组件的实例就代替其所封装方程用在同样的位置。子系统模型是由组件或其他子系统组成的模型。换句话说,子系统模型(一般)不包括方程。相反,它表示的是其它部件的总成。通常情况下,这些子系统模型通过在示意图内拖拽、连接部件模型以及子系统模型而创建的。组件模型是“无层级的”(不包含其他组件或子系统,只有方程)。相对地,子系统模型则是分层的。

我们会经常会将子系统模型称作为系统模型。系统模型是预期用于进行仿真的模型。在仿真时,Modelica语言编译器会遍历模型的层次结构。然后,编译器会记下层次结构内的所有变量和方程式。这些都是将在仿真中使用的变量和方程式。当然,为了使其存在于唯一解,该系统模型(如同任何非partial模型一样)必须是平衡的

请注意,子系统模型可以包括方程式。Modelica并没有规则禁止这样做。但是,大部分时候,模型倾向于或由方程,或由其他部件/子系统组成。避免在包含子组件或子系统的模型里放入方程其实是个好主意。因为这样做意味着在看子系统的示意图时,有关模型的某些信息将“看不见”。规则的其中一个例外是子系统中的initial equation区域。

现在我们完成了对术语的讨论。那么,我们开始深入讨论组件模型。

非因果建模

首先,我们会讨论非因果建模。我们在连接器一章简单谈到了这个话题。在这里,我们将更全面地讨论非因果建模。

可组合性

非因果建模有两个非常大的优势。第一个是可组合性。在这里,可组合性是指我们能够将组件实例拖放并连接成几乎任何想要的配置。同时,在此过程中我们也无需担心“兼容性”。原因在于,非因果连接器的设计基础是物理兼容性,而非因果兼容性。这是可能的。因为非因果连接器的定义着眼于物理信息的交换,而非信息流的方向。其结果是,我们可以创建一个基于物理相互作用的组件模型,而无需任何关于信息交流性质(即方向性)的先验知识。

但可组合性也有其他的影响。我们不仅可以轻松通过通过拖放、连接组件去创建系统,但同时也可以很容易进行重新配置。将电路中的电流源替换为电压源会对系统的数学产生深远的影响(例如在系统表示框图时)。但在使用非因果方法建模时,这样的变化不会带来显著的影响。虽然底层的数学表达仍会改变,乃至会大量改变。但是,这对用户却无任何影响。因为数学表达是自动的编译过程中自动产生的。

最后,可组合的另一个特性在对于多领域系统的支持。事实上,Modelica语言不仅支持不同工程领域(电、热、液压),它也支持多种建模的形式。模型开发者创建了框图、状态机、Petri网等不同的模型苦库相对于在不同情况下使用特殊工具或编辑器,所有这些不同的领域和表述均可在Modelica里自由组合。

核算

连接器

非因果建模的另一个优点在于它会自动进行大量的核算。要明白这里进行了什么核算,让我们考虑以下Modelica标准库的旋转connector定义:

connector Flange_a "1-dim. rotational flange of a shaft (filled square icon)"
  Modelica.SIunits.Angle phi "Absolute rotation angle of flange";
  flow Modelica.SIunits.Torque tau "Cut torque in the flange";
  annotation(Icon(/* Filled gray circle */));
end Flange_a;

connector Flange_b "1-dim. rotational flange of a shaft (filled square icon)"
  Modelica.SIunits.Angle phi "Absolute rotation angle of flange";
  flow Modelica.SIunits.Torque tau "Cut torque in the flange";
  annotation(Icon(/* Gray circular outline */));
end Flange_b;

正如我们前面所讨论的,一个非因果连接器包括两种不同类型的变量:横跨变量及穿越变量。穿越变量前带有flow限定词。对于Rotational连接器而言,横跨变量是角位置phi,而穿越变量则是扭矩tau

正负号规则

另外,回想前面讨论Modelica模型应遵守以下约定:连接器里穿越变量的正值表示物理量在流与连接器相连的组件。这是一个重要的正负号惯例。不仅因为惯例确保了所有核算的正确性。而且该惯例会增加可组合性。因为这可以令(本质上对称的)组件,如弹簧、减震器等,在进行翻转后仍然有相同的功能。

连接集合

在我们介绍编译器所执行核算的细节前,我们需要引入一个连接集的概念。而为了介绍什么是连接集,请考虑以下的示意图:

请注意,该模型有8条连接:

equation
  connect(ground.flange_a, damper2.flange_b);
  connect(ground.flange_a, spring2.flange_b);
  connect(damper2.flange_a, inertia2.flange_b);
  connect(spring2.flange_a, inertia2.flange_b);
  connect(inertia2.flange_a, damper1.flange_b);
  connect(inertia2.flange_a, spring1.flange_b);
  connect(damper1.flange_a, inertia1.flange_b);
  connect(spring1.flange_a, inertia1.flange_b);

如果两条连接语句内有一个共同的连接器,它们就属于相同的连接集。如果一个连接器未连接到任何其他连接器,那么它就属于一个仅包含其本身的连接集。使用这个规则,我们可以组织连接器插入连接集如下:

  • 连接集#1

    • ground.flange_a
    • damper2.flange_b
    • spring2.flange_b
  • 连接集#2

    • damper2.flange_a
    • spring2.flange_a
    • inertia2.flange_b
  • 连接集#3

    • inertia2.flange_a
    • damper1.flange_b
    • spring1.flange_b
  • 连接集#4

    • inertia1.flange_b
    • damper1.flange_a
    • spring1.flange_a
  • 连接集#5

    • inertia1.flange_a

请注意,这些连接集在图中由右至左出现。为了直观理解连接集,大家或者可以花时间将连接集内部件与模型简图一一对应。需要注意,flange_a连接器是实心圆,而flange_b则为空心圆。

生成方程

这就是“核算”的开始。每个连接都会自动生成特殊的公式。第一组自动方程都涉及到横跨变量。我们需要施加约束。从数学上讲,所有的横跨变量必须具有相同的值。此外,我们还会引入了一个公式。该公式会指出,连接集内所有穿越变量的总和必须为零。

对应上述连接集,有下列的自动生成公式:

// Connection Set #1
//   Equality Equations:
ground.flange_a.phi = damper2.flange_b.phi;
damper2.flange_b.phi = spring2.flange_b.phi;
//   Conservation Equation:
ground.flange_a.tau + damper2.flange_b.tau + spring2.flange_b.tau = 0;

// Connection Set #2
//   Equality Equations:
damper2.flange_a.phi = spring2.flange_a.phi;
spring2.flange_a.phi = inertia2.flange_b.phi;
//   Conservation Equation:
damper2.flange_a.tau + spring2.flange_a.tau + inertia2.flange_b.tau = 0;

// Connection Set #3
//   Equality Equations:
inertia2.flange_a.phi = damper1.flange_b.phi;
damper1.flange_b.phi = spring1.flange_b.phi;
//   Conservation Equation:
inertia2.flange_a.tau + damper1.flange_b.tau + spring1.flange_b.tau = 0;

// Connection Set #4
//   Equality Equations:
inertia1.flange_b.phi = damper1.flange_a.phi;
damper1.flange_a.phi = spring1.flange_a.phi;
//   Conservation Equation:
inertia1.flange_b.tau + damper1.flange_a.tau + spring1.flange_a.tau = 0;

// Connection Set #5
//   Equality Equations: NONE
//   Conservation Equation:
inertia1.flange_a.tau = 0;

请注意,对于一个空连接集(即连接集#5),由于集合中只有一个横跨变量,所以不产生描述相等关系的方程。守恒方程仍会产生。但方程仅包含一项。因此,方程相当于声明了一点。亦即,一个未连接的连接器内不会有任何量流出来。这也符合我们的物理直觉。

这一切有何物理意义?对于电路连接,这意味着每个连接都可以被视为连接器之间的“完美短路”。对于机械系统,连接则可被视为零惯性的完全刚性轴。总而言之,连接意味着每个连接器上的横跨变量均相等。而且,离开任何一个组件的守恒量必须进入另一个组件。没有任何守恒量会在组件间消失或者被储存。

守恒

这些方程造成两个重要的结果。首先,flow变量自然而然守恒。典型的flow变量是电流、扭矩、质量流率等等。因为这些量全部都是守恒量(即各为电荷、角动量和质量)的时间导数,上述公式令自动这些量自动守恒了。

但是,还有其他的量隐式地保持了守恒。具体来说,我们可以保证能量守恒。对于所有这些领域,通过连接器的功率流都可以表示为穿越变量与横跨变量或其导数之积。其结果是,对于每个领域,利用由连接集自动生成的方程,我们可以很容易地推导出功率守恒方程。从上面的例子中我们知道,对第一个连接集我们有以下公式:

ground.flange_a.phi = damper2.flange_b.phi;
damper2.flange_b.phi = spring2.flange_b.phi;
ground.flange_a.tau + damper2.flange_b.tau + spring2.flange_b.tau = 0;

如果我们将最后一条方程与ground.flange_a连接器的角速度der(ground.flange_a.phi)相乘,我们得到:

der(ground.flange_a.phi)*ground.flange_a.tau
+ der(ground.flange_a.phi)*damper2.flange_b.tau
+ der(ground.flange_a.phi)*spring2.flange_b.tau = 0;

不过,我们也知道,连接集内的所有跨越变量是相等的。所以,其导数也必定相等。这意味着我们可以用其中一个替换其他的任何一个。做两次这样的替换,我们得到:

der(ground.flange_a.phi)*ground.flange_a.tau
+ der(damper2.flange_b.phi)*damper2.flange_b.tau
+ der(spring2.flange_b.phi)*spring2.flange_b.tau = 0;

上式中的第一项就是通过flange_a流入ground组件的功率。第二项是通过flange_b流入damper2组件的功率。最后一项是通过flange_b流入spring2组件的功率。由于这些代表了连接集内所有连接器中流动的功率, 这意味着功率在连接集内守恒(即流出一个组件的所有功率必须流入另一个,没有丢失或被存储)。

平衡的组件

如果仔细观察前面的讨论,我们会在连接集内所生成的涉及非因果变量的方程式里发现一些有趣的点。但要看到这些点,我们首先需要重温我们此前对连接器和连接集的几个认识:

  1. 一条连接只能从属于一个连接集。

  2. 我们在前面讨论了非因果变量。所以我们知道,每一个连接器内的穿越变量(即声明时带有flow限定词的变量),必须匹配一个横跨变量(即没有任何限定词的变量)。

  3. 连接集产生的方程数等于连接组的连接器数乘以穿越-横跨变量对的数目。

记得非因果变量是成对出现的。这些变量所需的一半方程会自动通过连接产生(每对变量会生成一个方程)。这意味着变量所需的一半方程必须来自组件模型本身。

请记住,这里的讨论仅仅是关于连接器内的非因果变量。我们还需要考虑两种其他情况:

  1. 组件模型内声明的变量(而不是在连接器上的)。

  2. 连接器上的因果变量(即前面带有inputoutput限定词的变量)。

Modelica的要求任何非partial模型均为平衡的。但这是什么意思?这意味着组件应该提供正确数量的方程(不比需要的更多,也不更少)。现在的问题是,如何计算所需方程的数目?

我们在对非因果变量的讨论里已经有了一定的基础。由于非因果变量所需的一半方程来自自动生成的方程,另一半方程必须来自含有这些连接器的组件模型之内。具体来说,该组件必须为其连接器的穿越-横跨变量对提供一个方程式。此外,组件还应该为其连接器具有output限定词的变量提供一个方程式(注意,该组件不需为其连接器具有input限定词的变量提供方程)。其理由是,组件可以假定所有input信号均为已知(由外部指定)。另外,组件要负责计算其声明的任何output信号。最后,组件内声明的任何(非parameter)变量也必须对应一个公式。

总之,组件必须提供的方程式数总和为:

  1. 其所有连接器内穿越-横跨变量对的数目

  2. 声明中的组件模型的非parameter变量的数目。

  3. 其所有连接器内output变量的数目。

请注意,这些方程可以(而且经常确实是)来自其继承的partial模型。

如果由组件所提供的方程数等于所需方程数,则该组件模型就成为是平衡的

组件定义

本章里,我们讨论了如何创建组件模型。我们此前讨论了模型定义应该包括的内容。而这一切从一开始就都没有改变,但关于组件模型有几件事情值得强调。

模块

首先,在讨论框图组件的时候,我们介绍了block这种建模方法。block是一种特殊的model。这种模型的连接器只包含inputoutput的信号。

有条件出现的变量/连接器

我们对可选地面连接器的讨论中看到了另外一点。这就是条件声明。条件声明的条件表达式不能为时间的函数(即变量不能出现并在模拟期间消失)。相反,表达式必须是参数和常量的函数。这样编译器或仿真程序可以在仿真运行前判断变量是否应该存在。正如我们所看到的,这样的声明语法如下:

VariableType variableName(/* modifications /*) if conditional_expression;

换句话说,通过紧接着变量名称(以及添加到变量上的所有修改语句)添加if关键字条件表达式,就可以使该变量声明变为有条件出现的。当条件表达式为true,有条件出现的变量才会存在。当条件表达式为false时,该变量就不会存在。

模型的适用范围

assert

要了解如何防止模型离开其适用范围,首先我们要解释一下assert函数。调用assert函数的语法是:

assert(conditional_expression, "Explanation of failure", assertLevel);

其中conditional_expression是产生两种值,truefalse,的表达式。false值表示断言失败。我们马上会讨论失败的后果。第二个参数必须是String,用以描述断言失败的原因。最后一个参数assertLevel的类型为AssertionLevel(这在此前对enumerations的讨论中定义了) 。这最后一个参数为可选。其默认值则为AssertionalLevel.error

现在我们知道了如何使用assert函数。让我们来看看断言对模拟过程的影响。由此,我们可以明白断言的作用。

定义模型的适用范围

在创建组件model(或者说任何model)时,最好直接将公式使用范围添加到模型内。这可以通过添加assert调用来完成。assert调用既可以加在equation区域,也可以algorithm区域。正如其名称所暗示的一样,这些断言语句要求某些条件必须始终为真。

如果一个模型中的公式仅在一定条件下准确或者适用,那么有一点很重要。这就是,上述限制必须包括在通过断言包含在模型内。否则,模型会默默地产生一个不正确的解。如果这个错误没有被发现,模型的仿真结果可能导致错误的决定。如果这个错误被发现了,这会破坏大家对模型的信任。所以,无论如何都要试图在模型中明示其局限性。

这样的断言语句会对模拟有什么影响?这值得花些考虑。模拟过程中会生成所谓的候选解。这些解有可能会就是最终的解。但也可能不是。求解器会提出不同的解。然后它检查以确保解的精确度满足一定数值容差。上述候选解就是在这个过程中产生的。而不够精确的候选解一般而言会以某种方法进一步被改良,直到求解器找到足够精确的解。

如果候选解违反了断言,那么该解会自动被认定为不准确。被违反的断言语句会自动触发解的改善过程。求解器会试图找到一个更准确、且希望不违反断言的新解。但是,如果上述改善过程找到一个足够精确的解(即精度在要求的可接受容差范围内),但该解仍违反系统中的某个断言,那么模拟环境将有两个选择。如果assert调用内的level参数是AssertionLevel.error,那么仿真会终止。但如果assert调用内的level参数是AssertionLevel.warning,那么用户会得到一条警告信息。警告内容就是断言内的描述。消息的传递在不同的仿真环境内可能会有所不同。要注意,level参数的默认值(未在调用assert时提供level的话)是 AssertionLevel.error