这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战
本文是来自于FMI2.0规范的第三章模型交换的译文,基于机翻与个人理解修订而成。
一、模型交换的数学描述
基础
下图包含了访问C程序动态系统的方程的接口描述,示意图如下
模型交换接口的目标是用数值方法求解由微分、代数和离散时间方程构成的系统。在2.0版本的接口中,需要处理在状态空间中由事件组成的常微分方程(缩写为“hybrid ODE”)。在FMU中可能包含代数方程系统;FMU也可能仅包含离散时间方程(例如描述采样数据控制器)。
自变量时间t属于元祖 t=(tR...ti)。其中tR为实数,ti为{0,1,2,...};
- 实数部分tR是FMU的自变量,用于描述事件之间模型的连续时间行为;
- 而整数部分ti是一个计数器,用于枚举在同一连续时间中发生的事件,该时间定义在文献中也称为“超密集时间”,参见(Lee and Zheng 2007)。
ODE意为常微分方程,hybrid ODE 意为 混合常微分方程 超密集时间(super dense time):按我的理解是在一个时间段中,存在多个时间点,每个时间点的间隔是毫秒甚至微秒级的,可以称之为超密集时间。
相关符号的含义如下
事件(Event)
FMI支持的混合ODE被描述为分段连续时间系统,不连续性可能发生在T0....Ti时刻,其中Ti < Ti+1。这些时刻称为“事件”,事件可以被预先声明(定时器),也可以隐式定义(状态、步长事件)。
在两个事件间,变量的值是连续的或不变的:如果变量仅在事件瞬间更改其值,则该变量称为离散时间;否则称为连续时间。只有实数变量可以是连续时间。以下变量索引用于描述相应变量的时序行为(例如,vd是离散时间变量)
- c:连续时间变量,取值范围为ti+ <= t <= -ti+1
- d:离散时间变量,只在时刻ti改变它的值
- c+d: 连续时间变量以及离散时间变量的集合
相关例子如下:
瞬时事件由以下条件之一定义,这些条件给出了最小的时刻(event instant)ti:
1. 外部事件(external event)
外部事件z指参数变化导致分段不连续
- 离散时间的输入值发生变化
- 连续时间输入变量存在一个非连续的变化
- 可调参数(tunable)发生了变化
这几个被称之为外部事件。【如果A连接到B,当A触发了事件,那么A所有的输出在当前时间点是非连续的。因此,如果A的输出连接到B,也建议为B触发一个外部事件,这意味着在B上调用fmi2EnterEventMode】
2. 时间事件(time event)
时间事件表示当前瞬间取值依赖于前一个取值。
预定义的 时刻 ti =(Tnext(ti-1), 0) 取决于FMU的前一个 时刻 ti-1,这样的事件称之为时间事件。
3. 状态事件
状态事件表示指示器z取值发生变化 事件指示器 zj(t)的取值范围发生类似从zj > 0修改为zj <= 0的变化,这种事件称之为状态事件。所有事件指示器都是分段连续的,并集中在一个实数向量z(t)中。
4. 步进事件(step):
在完成的积分器步进中可能发生的事件。它不是由精确的时间或条件定义的,因此通常不是由用户定义的。程序可以使用它,例如在不同状态之间动态切换。步进事件比状态事件的处理效率高得多,因为在完成积分器步进时执行检查后才触发事件,而状态事件需要一个搜索过程。
在积分器的每个步中,都必须调用 fmi2CompletedIntegratorStep(函数标志ModelDescription.completedIntegratorStepNotNeeded = false);如果返回参数 nextMode = EventMode
,则此时会发生一个事件,称之为步进事件。【步进事件用于动态的修改FMU内部模型(连续)状态,因为以前的状态在数值上不再适用】
积分器(Integrator、数值积分):积分器是软件中的一部分。在一些计算物理的电脑模拟软件中,像是数值天气预报、分子动力学、飞行模拟器、油层模拟法、隔音屏障设计、建筑声学及电子电路仿真等应用中,积分器是可以用离散步骤计算积分的数值方法。
FMI模型交换模型由以下变量描述:
变量 | 描述 |
---|---|
t | 自变量时间T,使用causality = "independent"定义变量 |
v | 所有暴露变量的向量 |
p | 在仿真过程中恒定的参数,不带下标的表示自变量(causality = "parameter");因变量(causality = "calculatedParameter")用pcalculated表示 |
u(t) | 输入变量(causality = "input"、variability= "discrete" or " continuous"),这些变量的值在模式之外定义 |
u(t) | 输出变量(causality = "output"、variability= "discrete" or " continuous"),这些变量的值在FMU中计算,并被设计用于模型连接中。因此,输出变量可以在环境中用作其他FMU或其他子模型的输入值。 |
w(t) | 无法用于FMU连接的FMU的局部变量(causality = "local")。 |
z(t) | 用于定义状态事件的实际连续时间变量的向量 |
xc(t) | 代表连续时间状态的实际连续时间变量的向量.为了符号上的方便,连续时间状态在概念上被视为另一种类型的变量,作为以下数学描述的输出或局部变量。实际上,连续时间状态是FMU的输出y或局部变量w的一部分 |
xd(t) .xd(t) | 第一个符号表示(内部)离散时间变量(任何类型)的向量,表示离散状态;第二个表示前离散时间变量的向量 |
Tnext(ti) | 在初始化或事件瞬间,FMU可以定义下一个瞬间 |
r(ti) | 布尔变量的向量 |
模式(Mode)
1. 模式分类
计算FMI模型的解意味着将求解过程划分为不同的阶段,并且在每个阶段都使用不同的方程式和求解方法,可根据以下模式划分阶段:
-
初始化模式:该模式用于在开始时间t0时,通过其它模式中不存在的额为方程式来计算 连续时间状态xc(t0) 、(内部)前离散时间状态.xd(t0) 的初始值。(例如,用于定义状态或状态导数的起始值的方程式)
-
连续时间模式:通过数值求解常微分方程和代数方程,此模式用于计算事件之间所有(实时)连续时间变量的值。在此阶段中,所有离散时间变量都是固定的,并且不分析相应的离散时间方程。
-
事件模式:此模式用于为所有连续时间变量、所有在当前时刻t以及在前一时刻.t 的离散时间变量计算新值。这是通过求解由所有连续时间方程和所有活动离散时间方程组成的代数方程实现的。在FMI 2.0中,没有对应机制能够提供事件发生时 离散时间变量是否变化的信息。因此,在环境中必须假定某个瞬时事件总是计算所有离散时间变量,尽管在FMU内部只有一个新的子集可能会被计算出来。
2. 代数环
将FMU连接在一起时会出现回路结构,这会加大求解模型的难度。因为在实数变量、布尔变量或整数变量中可能存在线性或非线性代数方程组。为了有效地解决FMU中的这类方程系统,需要声明依赖性信息(例如,其输出直接取决于输入)。在xml文件下的元素中,可以有选择性的提供此数据。如果未提供此数据,则必须假定最坏的情况(例如,所有输出变量都代数依赖于所有输入变量)
在上图中,FMU1和FMU2通过适合的fmi2SetXXX、fmi2GetXXX调用序列来调用可计算的FMU变量,可以计算FMU变量。在下图中,FMU3和FMU4通过存在的“真实”代数环连接。这类循环可以使用牛顿迭代法解决。在每个迭代中,求解器提供的迭代变量u4以及通过fmi2SetXXX 和 fmi2GetXXX的显示序列调用的残差都会被计算并提供给求解器。基于残差,提供u4的新值。当残差趋向于0时,迭代会被终止。
这种类型的代数环会在初始化模式、事件模式、连续-时间模式等模式下发生。由于在每个模式中都会计算不同的变量,并且与其他两个模式相比,在初始化模式中变量计算的因果关系可能会有所不同,因此可能有必要在不同的模式中解决不同种类的循环。
如下表所示,函数fmi2SetXXX是函数fmi2SetReal,fmi2SetBoolean,fmi2SetInteger和fmi2SetString的缩写。函数fmi2GetXXX是函数fmi2GetReal,fmi2GetBoolean,fmi2GetInteger和fmi2GetString的缩写。
在某个瞬时事件中,离散系统的代数方程可以描述为 前一个离散时间状态.xd 以及 离散时间输入 ud的函数。如果FMU被循环连接,则会迭代调用这些代数方程,直到找到解为止。如果实际离散时间状态xd和前离散时间状态.xd 不同,则更新离散时间状态;时间的整数部分将增加,并执行新的事件迭代。
FMU在 初始化模式 下使用init方法进行初始化。该函数的输入参数包括输入变量(causality =“input”)和自变量(causality =“independent”;通常为默认值“time”)。所有的变量都具有一个初始化(initial = ”exact“),这是为了在初始时间 t0 中,计算连续时间状态和输出变量。例如,可通过为状态提供初始值或声明状态导数为零来定义初始化。
初始化本身是一个复杂的话题,而且还要求FMU在初始化模式下解决FMU内部明确定义的初始化问题。调用fmi2ExitInitializationMode
之后,FMU隐式处于事件模式,所有的离散时间变量和连续时间变量都会在初始化时间实例中被计算处理。如果喜欢,还可以依靠代数循环进行迭代。完成后必须调用fmi2NewDiscreteStates,并且根据返回参数的值,FMU要么在初始时刻继续事件迭代、要么切换到连续时间模式。
- 切换到连续时间模式后,开始计算积分;在此阶段中,将计算连续状态的导数。如果FMU和子模型连接在一起,则这些模型的输入是其他模型的输出,因此必须计算相应的FMU输出。每当存储结果值时,通常在模拟开始之前定义的输出点上,都必须调用与所需变量有关的fmi2GetXXX函数。
事件时刻由时间、状态/步进事件/环境触发的外部事件确定。为了确定状态事件,必须在每个完成的积分器步骤中查询事件指示器。一旦事件指示符发出信号指示其域的更改,将在前一个积分器步骤与实际完成的积分器步骤之间执行随时间的迭代,以便确定达到一定精度的域更改的瞬间。
触发事件后,需要将FMU切换到事件模式。在这种模式下,可以解决连接的FMU上的方程组(类似于连续时间模式)。一旦达到收敛,就必须调用fmi2NewDiscreteStates(..)来增加超密集时间。根据离散时间模型,可能需要新的事件迭代(例如,因为FMU在内部描述了状态机,并且转换仍然可以触发,但是必须考虑新的输入)。
二、FMI应用程序编程接口
本节包含接口说明,用于声明C程序中的不同实体部分
提供自变量(Independent Variables)和缓存的重新初始化(Re-initialization)
根据实际情况,需要计算不同的变量;而为了提高效率,需要接口仅用于计算当前上下文中所需的变量。例如,在进行积分迭代的过程中,仅需要计算状态导数,前提是模型的输出未连接。有可能在同一时刻,也需要其它的变量。
如果积分步长已经完成,则还需要计算事件指示(event indicator)函数。为了提高效率,在调用计算事件指示符函数时,如果状态导数已经在之前时刻进行了计算,则不要重新计算状态导数。这意味着状态导数支持重用,此功能被称为“变量缓存”。
当输入参数(例如:时间或状态)发生改变,缓存需要模型评估可以探测到。这是通过用函数调用显式设置它们来实现的,因为每个这样的函数调用调用的信号(signal)恰好是相应变量的变化。因此,本节包含用于设置方程估计函数的输入参数的函数。这对于时间和状态变量来说没有问题,但是参数和输入则涉及更多,因为后者可能具有不同的数据类型。
-- 设置新的时刻,重新初始化依赖时间的变量缓存,新的时间值需要与当前时间不一致(仅依赖于常量或参数的变量不需要在后面重新计算,可以重用先前计算的值)
fmi2Status fmi2SetTime(fmi2Component c, fmi2Realtime);
-- 设置一个新的(连续)状态向量,重新初始化依赖状态的变量缓存
-- nx 表示向量x的长度,目的是用于检测;仅依赖于常量、参数、时间和输入的变量不需要在后续中重新计算,但可以重复使用先前计算的值
-- 连续状态可能在事件模式中更改
fmi2Status fmi2SetContinuousStates(fmi2Component c, constfmi2Realx[], size_tnx);
-- 为(独立)参数、起始值、输入和变量缓存重新初始化 设置新值。
fmi2Status fmi2SetXXX(..)
模型方程评估(Evaluation of Model Equations)
本小节包含核心函数用于估计模型方程。下列函数之一在被调用前,需要调用合适的(上一小节)函数为当前的模型方程设置输入参数。
1. fmi2EnterEventMode(进入事件模式)
-- 模型从连续时间模式进入事件模式,离散时间方程可能变为活动状态(并且关系未“冻结”)。
fmi2Status fmi2EnterEventMode(fmi2Component c);
2. fmi2NewDiscreteStates(递增超密集时间 (tR,tI) ==> tR,tI+1))
-- 模型从连续时间模式进入事件模式,离散时间方程可能被激活(不是冻结状态(frozen),可以与其它fmu交互)
fmi2Status fmi2NewDiscreteStates(fmi2Component c , fmi2EventInfo* fmi2eventInfo);
-- 表示事件信息
typedef struct{
// 需要新的离散状态
fmi2Boolean newDiscreteStatesNeeded;
//终止仿真
fmi2Boolean terminateSimulation;
//连续状态的标称改变
fmi2Boolean nominalsOfContinuousStatesChanged;
//连续状态的标称值改变
fmi2Boolean valuesOfContinuousStatesChanged;
// 如果设置nextEventTimeDefined=fmi2True,则使用nextEventTime显示下一个事件时间
fmi2Boolean nextEventTimeDefined;
fmi2Real nextEventTime;
} fmi2EventInfo;
超密集时间:在一个事件中,使用迭代器计数用于表示事件的精确描述,即 t = (tR,tI)。
fmi2EventInfo 的参数如下:
- FMU 处于事件模式,在这个函数调用中递增超密集时间。在调用
fmi2NewDiscreteStates
函数之前,如果超密集时间的值为(tR,tI);那么调用之后的时间是(tR,tI+1)。如果返回参数(fmi2eventInfo->newDiscreteStatesNeeded=fmi2True
),那么FMU仍然需要保持为事件模式,还需要为FMU设置新的输入、计算和获取输出以及重新调用fmi2NewDiscreteStates
函数。这还取决于其他FMU的连接,环境应当为:- 如果至少有一个FMU返回参数
TerminateSimulation=fmi2True
,即有模块表示可以终止模型交换了;则调用fmi2Terminate
函数; - 如果所有FMU返回
newDiscreteStatesNeeded=fmi2False
,则调用fmi2EnterContinuousTimeMode
。 - 否则,保持事件模式
- 如果至少有一个FMU返回参数
- 如果
nominalsOfContinuousStatesChanged=fmi2True
,则状态的值由于函数调用而发生了变化,可以使用 fmi2GetNominalsOfContinuousStates 进行查询 - 如果
valuesOfContinuousStatesChanged=fmi2True
,则由于函数调用,连续状态向量中至少一个元素已更改其值。可以使用 fmi2GetContinuousStates 查询状态的新值。如果连续状态向量的任何元素均未更改其值,则 valuesOfContinuousStatesChanged 必须返回fmi2False(如果在这种情况下将返回fmi2True,则可能发生无限事件循环)。 - 如果
nextEventTimeDefined=fmi2True
,则模拟将最大次数的进行积分,直到time=nextEventTime
,并且此时应调用fmi2EnterEventMode
。如果由于状态事件而在nextEventTime
之前停止积分,那么nextEventTime的定义将过时。
3. fmi2EnterContinuousTimeMode(进入连续时间模式)
fmi2Status fmi2EnterContinuousTimeMode(fmi2Component c);
模型进入连续时间模式,所有离散时间方程变为非活动状态,所有关系都被“冻结”。从事件模式更改为(在所有涉及的FMU和其他模型的事件模式中的全局事件迭代已收敛之后)连续时间模式时,必须调用此函数。
使用这个函数的目的是为了:
- 将FMU内部结果存储在文件中,可以存储初始化后的结果和已被处理的事件;
- 如果FMU包含动态变化的状态,新状态可能选择这个函数执行。
4. fmi2CompletedIntegratorStep(完成积分器步长)
如果completedIntegratorStepNotNeeded=false
,则需要在每次积分器步长完成后调用这个函数,用于
fmi2Status fmi2CompletedIntegratorStep(
// 表示fmu实例
fmi2Component c,
// 表示在本次模拟运行中,不再对上一个时刻调用 fmi2SetFMUState 函数(FMU可以使用此标志刷新结果缓冲区)
fmi2Boolean noSetFMUStatePriorToCurrentPoint,
// 表示将进入 fmi2EnterEventMode 函数
fmi2Boolean* enterEventMode,
// 表示将调用 terminateSimulation 函数终止模拟
fmi2Boolean* terminateSimulation
);
如果enterEventMode = fmi2False并且 terminateSimulation = fmi2False
,则FMU保持在连续时间模式,而无需再次调用fmi2EnterContinuousTimeMode函数。
当积分器步长完成并且在随后修改状态(例如通过BDF方法进行校正)时,在调用fmi2CompletedIntegratorStep(..)
函数之前,必须调用fmi2SetContinuousStates(..)
函数更新状态。
当积分步长完成且一个或多个事件指示器改变符号(相对于先前完成的积分步长来说)时,则积分器或者环境必须确定符号改变时最接近
的上一个完成时间,这个时间必须具有一定精度(通常是计算机中最小的浮点数的小倍数)。这通常是通过迭代来执行的,其中时间是变化的,并且迭代期间所需的状态变量是通过插值确定的。函数 fmi2CompletedIntegratorStep
必须在此状态事件定位程序之后调用,而不是在积分算法成功计算出时间步长之后调用。该函数调用的预期目的是向FMU指示,在此阶段,所有输入和状态变量都具有有效(接受)值。
调用fmi2CompletedIntegratorStep
之后,仍然允许其返回时间(调用fmi2SetTime)并使用fmi2GetXXX查询前时刻中变量的值(例如,确定输出点处的非状态变量的值):但是,它不是允许在上一个完成的``IntegratorStep或上一个fmi2EnterEventMode调用之前返回时间。
在以下几种情况中,需要调用这个函数:
- 延迟:delay(...)操作中使用的所有变量都存储在适当的缓冲区中,并且函数返回 nextMode = fmi2ContinuousTimeMode
- 动态状态选择:检查动态选择的状态在数值上是否仍然合适。如果合适,则函数返回enterEventMode = fmi2False,否则返回enterEventMode=fmi2True。在第二种情况下,必须调用fmi2EnterEventMode(..),然后通过fmi2NewDiscreteStates(..)函数动态更改状态。
注意:此函数不用于检测时间或状态事件。例如,通过将前一个事件的指示符与fmi2CompletedIntegratorStep(..)
的当前调用进行比较,来检测时间或状态事件。这些类型的事件是在环境中检测到的,在这种情况下,环境必须调用fmi2EnterEventMode(..)
,而与fmi2CompletedIntegratorStep(..)
的返回参数enterEventMode
是fmi2True还是fmi2False无关。
积分步长:积分区间[a,b]等分为n段,积分步长h=(b-a)/n BDF方法:时间步相关的算法
5. fmi2GetDerivatives、fmi2GetEventIndicators(获取导数、事件指示符)
fmi2Status fmi2GetDerivatives (fmi2Component c, fmi2Real derivatives[], size_t nx);
fmi2Status fmi2GetEventIndicators(fmi2Component c, fmi2Real eventIndicators[], size_t ni)
在当前时刻和当前状态下,计算状态导数和事件指示符:
- 导数作为带有“ nx”个元素的向量返回,事件指示符作为带有“ ni”个元素的向量返回。
- 当事件指示器的域从zj > 0变为zj ≤0或相反操作时,将触发状态事件。 FMU必须保证在事件发生时重启zj≠0,例如通过以较小值改变zj来保证。此外,zj应在FMU中使用其标称值进行缩放(因此,返回的矢量“ eventIndicators”的所有元素应按“ one”的顺序排列)。
导数向量中元素的顺序与状态向量的顺序相同(例如,导数[2]是x [2]的导数),而事件指示器不一定与模型描述文件中的变量相关。
注:fmi2Status = fmi2Discard 对于上述两个函数来说都是可能的取值。
6. fmi2GetContinuousStates(连续状态)
fmi2Status fmi2GetContinuousStates(fmi2Component c, fmi2Realx[], size_t nx);
返回新的(连续)状态向量x。如果函数以 eventInfo-> valuesOfContinuousStatesChanged = fmi2True
返回(标识(连续时间)状态向量已更改),则必须在调用函数 fmi2EnterContinuousTimeMode 之后直接调用此函数。
7. fmi2GetNominalsOfContinuousStates
fmi2Status fmi2GetNominalsOfContinuousStates(fmi2Component c, fmi2Realx_nominal[],size_tnx);
返回连续状态的标称值。如果此函数返回eventInfo-> nominalsOfContinuousStatesChanged = fmi2True
,则在调用函数 fmi2NewDiscreteStates
之后应调用此函数,因为连续状态的标称值已更改[例如,因为连续状态与变量的关联由于内部动态状态选择而发生了变化。如果FMU没有与连续状态i有关的标称值信息,则应返回标称值x_nominal [i] = 1.0。注意,要求x_nominal [i]> 0.0。 【通常,连续状态的标称值用于计算积分器所需的绝对公差:absoluteTolerance[i] = 0.01*tolerance*x_nominal[i];
】
基于函数调用顺序的状态机
根据以下状态图,FMI的每个实现都必须支持的函数调用序列(以 UML 2.0 状态机的形式调用模型交换 C函数的序列):
定义状态图的最初目的是为了定义 FMI函数允许的调用顺序:
FMI不支持状态图不接受的调用顺序。对于这样的调用序列,FMU的行为是不确定的。例如,状态图表示当用于模型交换的FMU处于状态“连续时间模式”时,不支持对离散输入的fmi2SetReal调用。如果转换被标记为一个或多个函数名称(例如 fmi2GetReal、fmi2GetInteger),这意味着如果成功调用了这些函数中的任何一个,则会进行转换。注意,由于每个状态都是通过特定的函数调用(例如fmi2EnterEventMode)或特定的返回值(例如fmi2Fatal)输入的,因此FMU始终可以确定它处于哪种状态。
状态机的每个状态对应于仿真的特定阶段,如下所示
- instantiated(实例化)
在这种状态下,可以设置起始值和估计值(initial ="exact/approx"的变量)
- Initialization Mode(初始化模式) 在此状态下,方程式可用于确定所有连续时间状态以及所有输出(可选:导出工具公开的其他变量)。可以通过fmi2GetXXX调用检索的变量为:
- 在xml文件中的 下定义的
- 具有 causality ="output"的变量;可以设置为 initial ="exact" 的变量以及 variability ="input" 的变量。
-
Continuous-Time Mode(连续时间模式) 在这种状态下,连续时间模型方程处于活动状态,并执行了积分器步进。如果在完成积分器步进结束时,至少一个事件指示域被检测到发生了变化,则可以确定状态事件的事件时间。
-
Event Mode(事件模式) 如果在连续时间模式下触发了事件,则通过调用fmi2EnterEventMode进入事件模式。
在这种模式下,所有的连续时间方程和离散时间方程都是有效的,并且可以计算和检索事件中的未知数。事件完全处理后,必须调用fmi2NewDiscreteStates函数并根据返回参数(newDiscreteStatesNeeded)决定保持状态图处于事件模式或切换到连续时间模式。当初始化模式以 fmi2ExitInitializationMode
终止时,将直接进入事件模式,并根据在初始化模式下确定的初始连续时间状态来计算初始时间的连续时间和离散时间变量。
- terminated(终止) 在这种状态下,可以获取模拟的最后一次结果。
注1:仅在连续的时间间隔内才允许在时间上向后仿真。一旦发生事件(调用了fmi2EnterEventMode),就禁止时间向前回溯,因为fmi2EnterEventMode / fmi2NewDiscreteStates只能计算下一个离散状态,而不能计算前一个离散状态。 注2:在初始化,事件和连续时间模式期间,可以根据xml文件中元素下定义的模型结构,使用
fmi2SetXXX
函数设置输入变量,并使用fmi2GetXXX
函数互换获取输出变量。
下表汇总了各个状态下允许的函数调用: (黄色的仅适用于模型交换,而其它函数可使用于模型交换以及联合仿真)
"x"表示:在相应状态下允许调用 数字表示:如果指示的条件成立,则允许调用
- (1)variability ≠"constant"initial = "exact/approx"
- (2)causality = "output/" 或者 continuous-time 状态 或者状态导数
- (3)variability≠"constant" & (hasinitial="exact" || causality="input")
- (4)causality = "input" || (causality = "parameter" & variability = "tunable")
- (5)causality = "input" & variability = "continuous"
- (7)检索到的值仅可用于调试
伪代码
模型交换的伪代码如下:
m = M_fmi2Instantiate("m", ...) // "m" 是实例名称
// "M_" 表示模型标识
// 来自XML文件
nx = ... // 状态数量
nz = ... // 事件标识数量
Tstart = 0 // 开始时间
Tend = 10 // 结束时间
dt = 0.01 // 固定步长为 10 毫秒
// 设置开始时间
time = TStart
// 设置所有变量的初始值 ("ScalarVariable / <type> / start") 以及 设置time = Tstart
M_fmi2SetReal/Integer/Boolean/String(m, ...)
// 初始化
// 确定连续和离散时间状态
M_fmi2SetupExperiment(m, fmi2False, 0.0, Tstart, fmi2True, Tend)
M_fmi2EnterInitializationMode(m)
M_fmi2ExitInitializationMode(m)
initialEventMode = fmi2True
enterEventMode = fmi2False
timeEvent = fmi2False
stateEvent = fmi2False
previous_z = zeros(nz)
// 检索初始状态 x 以及 x的标称值(如果需要绝对公差)
M_fmi2GetContinuousStates(m, x, nx)
M_fmi2GetNominalsOfContinuousStates(m, x_nominal, nx)
// 例如,在 t=Tstart 处检索解,用于输出
M_fmi2GetReal/Integer/Boolean/String(m, ...)
do
// 处理事件
if initialEventMode or enterEventMode or timeEvent or stateEvent then
if not initialEventMode then
M_fmi2EnterEventMode(m)
end if
// 事件迭代
eventInfo.newDiscreteStatesNeeded = fmi2True;
valuesOfContinuousStatesChanged = fmi2False;
nominalsOfContinuousStatesChanged = fmi2False
while eventInfo.newDiscreteStatesNeeded loop
// 在超密集时间点设置输入
M_fmi2SetReal/Integer/Boolean/String(m, ...)
// 更新离散状态
M_fmi2NewDiscreteStates(m, &eventInfo)
// 在超密集时间点获取输出
M_fmi2GetReal/Integer/Boolean/String(m, ...)
valuesOfContinuousStatesChanged = valuesOfContinuousStatesChanged or eventInfo.valuesOfContinuousStatesChanged;
nominalsOfContinuousStatesChanged = nominalsOfContinuousStatesChanged or eventInfo.nominalsOfContinuousStatesChanged;
if eventInfo.terminateSimulation then goto TERMINATE_MODEL
end while
// 进入连续时间模式
M_fmi2EnterContinuousTimeMode(m)
//在模拟(重新)开始时求解
M_fmi2GetReal/Integer/Boolean/String(m, ...)
if initialEventMode or valuesOfContinuousStatesChanged then
//模型发出状态值变化的信号,检索它们
M_fmi2GetContinuousStates(m, x, nx)
end if
if initialEventMode or nominalsOfContinuousStatesChanged then
// 状态的含义发生了变化;检索新的标称值
M_fmi2GetNominalsOfContinuousStates(m, x_nominal, nx)
end if
if eventInfo.nextEventTimeDefined then
tNext = min(eventInfo.nextEventTime, Tend)
else
tNext = Tend
end if
initialEventMode = fmi2False
end if
if time >= Tend then
goto TERMINATE_MODEL
end if
// 计算导数
M_fmi2GetDerivatives(m, der_x, nx)
// 获取时间
h = min(dt, tNext-time)
time = time + h
M_fmi2SetTime(m, time)
// 在 t=time 设置连续输入
M_fmi2SetReal(m, ...)
// 在 t=time 设置状态并执行一步
x = x + h * der_x // forward Euler method
M_fmi2SetContinuousStates(m, x, nx)
// 在 t=time 获取事件指标
M_fmi2GetEventIndicators(m, z, nz)
// 如果有的话,检测事件
timeEvent = time >= tNext
stateEvent = sign(z) <> sign(previous_z) or previous_z != 0 && z == 0
previous_z = z
// 通知模型接收积分步长
M_fmi2CompletedIntegratorStep(m, fmi2True, &enterEventMode, &terminateSimulation)
// 获取连续输出
M_fmi2GetReal(m, ...)
until terminateSimulation
// 终止仿真和获取最终值
TERMINATE_MODEL:
M_fmi2Terminate(m)
M_fmi2GetReal/Integer/Boolean/String(m, ...)
// cleanup
M_fmi2FreeInstance(m)
------------------------------
status = M_fmi2GetDerivatives(m, der_x, nx);
switch ( status ) { case fmi2Discard: ....; break; // reduce step size and try again
case fmi2Error : ....; break; // cleanup and stop simulation
case fmi2Fatal : ....; } // stop using the model
三、FMI描述文件(Schema-ModelExchange)
本节节将定义“模型交换”特定元素“ModelExchange”。
1. 标签相关定义
2. 标签相关结构
modelexchange
- modelIdentifier:根据C语法的简短类名称,例如“ A_B_C”
- needsExecutionTool:如果为true,则需要一个工具来执行模型,并且FMU仅包含与此工具的通信。
- completedIntegratorStepNotNeeded:如果为true,则无需调用函数 fmi2CompletedIntegratorStep(这将使集成效率稍微提高一点)。如果调用该函数,则无效。如果为false(默认值),则必须在完成每个积分器步长后调用该函数,请参见3.2.2节。
- canBeInstantiatedOnlyOncePerProcess:该标志指示情况(尤其是对于嵌入式代码),其中每个FMU只能有一个实例(多个实例默认为false;如果需要多个实例且此标志为true,则必须在不同的进程中实例化FMU)。
- canNotUseMemoryManagementFunctions:如果为true,则FMU使用其自身的函数仅用于内存分配和释放。 fmi2Instantiate中的回调函数allocateMemory和freeMemorygiven被忽略
- canGetAndSetFMUstate:如果为true,则环境可以查询内部FMU状态并可以将其还原。也就是说,FMU支持函数fmi2GetFMUstate、fmi2SetFMUstate和fmi2FreeFMUstate。
- canSerializeFMUstate:如果为true,则环境可以序列化内部FMU状态,换句话说,FMU支持函数fmi2SerializedFMUstateSize、fmi2SerializeFMUstat 、fmi2DeSerializeFMUstate。如果是这种情况,则标记canGetAndSetFMUstate也必须为true
- providesDirectionalDerivative:如果为true,则可以使用fmi2GetDirectionalDerivative(..)计算方程的方向导数。
- sourceFiles
- name
3. 示例XML描述文件
<?xml version="1.0" encoding="UTF8"?>
<fmiModelDescription fmiVersion="2.0" modelName="MyLibrary.SpringMassDamper" guid="{8c4e810f-3df3-4a00-8276-176fa3c9f9e0}" description="Rotational Spring Mass Damper System" version="1.0" generationDateAndTime="2011-09-23T16:57:33Z" variableNamingConvention="structured" numberOfEventIndicators="2">
<ModelExchange modelIdentifier="MyLibrary_SpringMassDamper"/>
<UnitDefinitions>
<Unit name="rad">
<BaseUnit rad="1"/>
<DisplayUnit name="deg" factor="57.2957795130823"/></Unit> <Unit name="rad/s">
<BaseUnit s="-1" rad="1"/>
</Unit>
<Unit name="kg.m2">
<BaseUnit kg="1" m="2"/>
</Unit>
</UnitDefinitions>
<TypeDefinitions>
<SimpleType name="Modelica.SIunits.Inertia">
<Real quantity="MomentOfInertia" unit="kg.m2" min="0.0"/>
</SimpleType>
<SimpleType name="Modelica.SIunits.Torque">
<Real quantity="Torque" unit="N.m"/>
</SimpleType>
<SimpleType name="Modelica.SIunits.AngularVelocity">
<Real quantity="AngularVelocity" unit="rad/s"/>
</SimpleType>
<SimpleType name="Modelica.SIunits.Angle">
<Real quantity="Angle" unit="rad"/>
</SimpleType>
</TypeDefinitions>
<DefaultExperiment startTime="0.0" stopTime="3.0" tolerance="0.0001"/>
<ModelVariables>
<ScalarVariable name="inertia1.J" valueReference="1073741824" description="Moment of load inertia" causality="parameter" variability="fixed">
<Real declaredType="Modelica.SIunits.Inertia" start="1"/>
</ScalarVariable>
<!—index="1" -->
<ScalarVariable name="torque.tau" valueReference="536870912" description="Accelerating torque acting at flange (= -flange.tau)" causality="input">
<Real declaredType="Modelica.SIunits.Torque" start="0" />
</ScalarVariable>
<!—index="2" -->
<ScalarVariable name="inertia1.phi" valueReference="805306368" description="Absolute rotation angle of component" causality="output">
<Real declaredType="Modelica.SIunits.Angle" />
</ScalarVariable>
<!—index="3" -->
<ScalarVariable name="inertia1.w" valueReference="805306369" description="Absolute angular velocity of component (= der(phi))" causality="output">
<Real declaredType="Modelica.SIunits.AngularVelocity" />
</ScalarVariable>
<!—index="4" -->
<ScalarVariable name="x[1]" valueReference="0", initial="exact">
<Real/>
</ScalarVariable>
<!—index="5" -->
<ScalarVariable name="x[2]" valueReference="1", initial="exact">
<Real/>
</ScalarVariable>
<!—index="6" -->
<ScalarVariable name="der(x[1])" valueReference="2">
<Real derivative="5"/>
</ScalarVariable>
<!—index="7" -->
<ScalarVariable name="der(x[2])" valueReference="3">
<Real derivative="6"/>
</ScalarVariable>
<!—index="8" -->
</ModelVariables>
<ModelStructure>
<Outputs>
<Unknown index="3" />
<Unknown index="4" />
</Outputs>
<Derivatives>
<Unknown index="7" />
<Unknown index="8" />
</Derivatives>
<InitialUnknowns>
<Unknown index="3" />
<Unknown index="4" />
<Unknown index="7" dependencies="5 2" />
<Unknown index="8" dependencies="5 6" />
</InitialUnknowns>
</ModelStructure>
</fmiModelDescription>
四、参考资料
《Functional Mock-up Interface forModel Exchange and Co-Simulation v2.0》