2. Modelica语言核心回顾
开发一个可复用的Modelica模型库,本质上是在设计一套“可被反复实例化、组合和继承”的类体系。因此,在动手写库之前,必须把Modelica的语法与语义基础重新梳理清楚:类如何定义、继承如何组织、方程如何声明、连接器如何传递能量、参数与变量如何分工、事件与离散建模如何介入连续系统。本节围绕这六个核心概念展开,为后续库的架构设计奠定基础。
一、类:Modelica的建模单元
Modelica中一切皆类(class)。类通过关键字区分用途:model用于物理系统建模,block用于信号处理,connector用于定义接口,record用于组织参数集合,function用于算法计算,package用于组织命名空间。一个典型的类由三部分组成:声明区(变量、参数、连接器实例)、方程区(equation或algorithm)、以及可选的annotation。
model Resistor "理想电阻"
extends TwoPin;
parameter Real R(unit="Ohm") = 100 "电阻值";
equation
v = R * i;
end Resistor;
这段代码中,extends TwoPin引入了两个引脚,parameter声明了可配置参数,equation区声明了欧姆定律。Modelica的方程是声明式的,不是赋值语句,求解器会自动决定计算方向。这是库开发中必须牢记的语义:写v = R * i与写i = v / R在数学上等价,但前者更符合物理直觉。
二、继承:库复用的骨架
继承(extends)是Modelica库复用的核心机制。通过继承,子类获得父类的所有变量、参数、方程和连接器,并可在此基础上增加或修改内容。库开发中常见的继承模式有三种:
- 接口继承:如
TwoPin定义正负两个引脚,所有两端元件继承它。 - 行为继承:如
PartialModel提供基础方程,子类补充具体特性。 - 参数继承:如
PartialHeatTransfer提供换热系数参数,子类选择具体关联式。
继承的语义是“is-a”关系,而组合(在类中实例化另一个类)是“has-a”关系。库设计中应优先使用组合来搭建系统结构,用继承来复用接口和公共行为。过度继承会导致类层次过深,修改父类时影响面难以控制。
三、方程与连接器:物理系统的声明式描述
Modelica的方程区可以包含等式、if表达式、for循环和when语句。方程数量必须与未知量数量匹配,否则模型欠定或过定。连接器则定义了组件之间的交互接口,其核心语义是势变量相等、流变量求和为零。
connector Pin "电气引脚"
SI.Voltage v "电势";
flow SI.Current i "电流";
end Pin;
当两个Pin通过connect()连接时,Modelica自动生成v1 = v2和i1 + i2 = 0。这一机制使得库开发者只需定义连接器,无需手动编写节点方程。连接器是库接口设计的核心,其变量选择直接决定了模型的物理域和可组合性。
四、参数与变量:配置与状态的分离
参数(parameter)在仿真开始前确定,仿真过程中不变;变量(variable)随仿真时间变化。库开发中,参数是用户配置模型的入口,应尽量提供合理的默认值和单位声明。Modelica支持参数的类型包括Real、Integer、Boolean、String以及枚举和记录。
变量的类型修饰符也很重要:input和output用于block类,discrete用于离散变量,flow用于连接器中的流变量。库开发中应避免在model类中滥用input/output,它们更适合信号处理场景。
五、事件与离散建模:连续系统之外的另一半
Modelica支持连续、离散和混合系统建模。事件分为时间事件(time >= t0)和状态事件(x > x_limit)。当事件触发时,when语句中的赋值生效,离散变量发生跳变。库开发中,事件常用于实现开关、保护逻辑、状态机等。
when v > Vmax then
alarm = true;
end when;
需要注意的是,when语句中的条件只在事件瞬间为真时执行一次,而不是持续为真时反复执行。这与if表达式有本质区别。库中若包含离散逻辑,必须明确事件触发条件和离散变量的初始化值,否则仿真可能因事件抖动而失败。
六、库开发中的语义约束
把上述概念落到库开发实践中,有几条硬性约束必须遵守。第一,方程数量必须与未知量数量一致,否则模型无法求解;第二,连接器必须成对出现势变量和流变量,且流变量的和为零;第三,参数必须在仿真前确定,不能在方程中动态修改;第四,when语句中的条件只在事件瞬间为真时执行一次,不能当作持续条件使用;第五,离散变量必须显式初始化,否则仿真可能从不确定状态开始。
PartialModel定义公共接口和基础方程,再通过继承派生具体组件。这样既能保证接口一致性,又能避免重复代码。对于包含离散逻辑的组件,应单独建立测试模型,验证事件触发条件和离散变量初始化是否正确。