# FMU 导入常见问题


本文档主要针对Co-Simulation类型的FMU使用常见问题进行说明。

# FMU 导入说明

Sysplorer导入FMU的方式是将FMU封装为Modelica模型,模型中包含FMU的输入、输出、参数以及FMU的计算流程,对模型操作等同于对FMU操作。FMU导入的封装模型与一般的Modelica模型性质基本相同,可以单独仿真,也可以与其它模型一起共同构建更复杂的模型;不同之处在于封装模型是对FMU的映射,模型的内容表示的是FMU信息,因此不能随意对模型进行修改。

需要注意的是,FMU导入的封装模型不能随意修改,但不是不能修改,模型中有部分内容是允许用户修改用以调整FMU的调用行为的,本文后续将说明的FMU导入常见问题中,有些就是通过修改模型来解决的。

# 启动仿真失败

# 问题描述

导入FMU进行仿真,提示启动仿真失败,如下:

# 问题分析

1) 平台位数不一致

导入FMU生成的模型在仿真求解时,需要显式加载FMU中动态库,从而可以在仿真过程中调用FMU接口进行FMU模型的仿真。上述问题出现的直接原因是FMU中动态库显式加载失败,首先需要检查FMU的位数与平台的位数是否一致,如下:

打开FMU,在binaries文件夹下,找到平台位数文件夹,如:win64windows 64位),若该文件夹下包含FMU动态库文件,这表示该FMU可以在对应的平台和位数下使用;可以同时存在多个平台位数文件夹,表示该FMU可以同时在不同的平台和位数下使用。当前支持的平台与位数如下:

  • win64:windows 64 位

  • win32:windows 32 位

  • linux64:linux 64 位

  • linux32:linux 32 位

检查Sysplorer的位数,如下(windows 32位):

FMU的位数与Sysplorer当前选择的位数不一致,则会出现上述问题。

2) 缺少依赖的动态库

若确定FMU的位数与Sysplorer的位数一致,则需要检查FMU是否缺少依赖的动态库文件,示例模型如下:

model Case5_1
  function f1
    input Real u;
    output Real y;
  external "C" y = fcn(u) annotation(
    Include = "#include \"test.h\"", 
    IncludeDirectory = "modelica://Model1/resource", 
    Library = "test", 
    LibraryDirectory = "modelica://Model1/resource");  // 外部函数
  end f1;

  input Real u;
  output Real y;
equation 
  y = f1(u);
end Case5_1;

上述模型使用了外部函数f1,依赖动态库test.dll,将该模型导出为FMU时,若resource文件夹下的test.dll丢失或损毁,则导出的FMU导入之后仿真就会出现上述问题,如下:

该问题更常见于其它平台导出的FMU,此时有一个检验方法:用FMI官方提供的工具对该FMU进行检查,如:FMUChecker,如果是因为缺少依赖的动态库或其它资源文件导致的该问题,那么官方的工具也会提示FMU校验失败。

# 解决方案

先按照上述问题分析对问题进行分析定位,确保FMU的位数与当前Sysplorer选择的位数一致;然后,若该FMU是通过Sysplorer导出的,则要确保原模型是可以正常仿真的;如果是其它平台导出的FMU,则可以借助FMI官方提供的工具检查该FMU是否合法。

FMUChecker是一个可执行程序,简单介绍下使用方法:

形式: fmuCheck.win64.exe [options] <model.fmu>
使用示例:fmuCheck.win64.exe -k cs -e test.log -o test.csv -h 0.02 -s 10 Model1.fmu
[参数]
-k cs表示Co-Simulation类型的FMU,me表示ME类型的FMU
-e test.log表示FMU执行过程的错误信息文件
-o test.csv仿真结果文件
-h 0.02仿真步长
-s 10仿真时间
Model1.fmu要仿真的FMU文件

更详细的使用方法可以参考FMUChecker的帮助文档。

# 输入变量初始时刻值不生效

# 问题描述

导入FMU后,给导入生成的封装模型的输入变量赋值,仿真模型,封装模型输出变量初始时刻的值不符合预期(不满足原模型中输出与输入的关系),示例如下:

// 原模型y=p*u;
model Case5_2                                                             
  input Real u;
  output Real y;
  parameter Real p = 1;
  protected
  Real a;
equation
  a = p*u;
  y = a;
end Case5_2;

导出Case5_2.fmu,导入生成封装模型FMU_Case5_2.mo,给封装模型输入赋值,如下:

model Case5_3
  FMU_Case5_2  m1;    // 封装模型实例
  Real x(start=1);
equation
  m1.'u' = x;         // 封装模型输入变量赋值
  der(x) = time;
end Case5_3;

仿真结果曲线如下:

从结果曲线可以看出,初始时刻封装模型的输出y与输入x的值不相等。

# 问题分析

FMU可以看作为一个灰盒模型,模型的所有输出变量依赖于所有输入变量 ,导入 FMU 的封装模型使用过程中若出现输入变量依赖于输出变量 ,则模型输入、输出互相依赖出现代数环,需要求解非线性方程,增加了求解的复杂度(也可能导致求解失败)。

上述封装模型输入、输出互相依赖出现的代数环,从原模型的角度看很可能是不存在的,因为原模型的所有输出不一定依赖于所有输入,示例如下:

原模型输出y1依赖于输入u1,输出y2依赖于输入u2。导入生成封装模型与外部输入输出连接,外部输入u与封装模型u1连接,外部输出y与封装模型y2连接,封装模型y1u2连接,示例如下:

上述模型中,因为封装模型u2依赖于y1,会导致封装模型输入、输出之间互相依赖出现代数环,但是从原模型来看,输入输出之间并无互相依赖,变量计算顺序为:u1 -> y1 -> u2 -> y2

基于上述分析,Sysplorer在导入FMU生成封装模型时,为了避免封装模型输入、输出互相依赖出现代数环,采取了如下措施:

1)单步仿真前,设置FMU输入变量时,取输入变量的pre值,打破可能存在的环路,示例如下:

algorithm
  when sample(fmu_StartTime + fmu_StepSize, fmu_StepSize) then
    // 设置输入变量pre值
    fmuFunctions.fmuSetReal(fmu_Object, {88}, {pre('a')});   
    fmuFunctions.fmuSetInteger(fmu_Object, {216}, {pre('b.')});
    for i in 1:fmu_StepFactor loop
      (fmu_StepSucceed, fmu_Terminated) := fmuFunctions.fmuDoStep(fmu_Object, 
        pre(fmu_Time) + (i - 1) * (time - pre(fmu_Time)) / fmu_StepFactor, 
        (time - pre(fmu_Time)) / fmu_StepFactor);   // 单步仿真
      if fmu_Terminated then
        terminate("FMU Terminated!");
      end if;
    end for;
    fmu_Time := time;
  end when;

2)初始时刻,设置FMU输入变量初始时刻值,取输入变量的start值,打破可能存在的环路,示例如下:

initial algorithm
  if fmu_InputStartForInitialization then
    fmuFunctions.fmuSetReal(fmu_Object, {88}, fmu_Real_u_start);
    fmuFunctions.fmuSetInteger(fmu_Object, {216}, fmu_Integer_u_start);
  else
    fmuFunctions.fmuSetReal(fmu_Object, {88}, {'a'});
    fmuFunctions.fmuSetInteger(fmu_Object, {216}, {'b'});
  end if;

参数fmu_InputStartForInitialization默认值为truefmu_Real_u_startfmu_Integer_u_start为辅助变量表示输入变量的start值,如下:

constant Boolean fmu_InputStartForInitialization = true 
    "Start value of inputs used for initialization";
parameter Real fmu_Real_u_start[fmu_nRealInput] = {0.0};
parameter Integer fmu_Integer_u_start[fmu_nIntegerInput] = {0};

如上所示,若参数fmu_InputStartForInitialization值设置为false,则初始时刻给FMU设置的输入变量值即为封装模型输入变量值本身。

# 解决方案

将封装模型中参数fmu_InputStartForInitialization值改为false(默认为true),如下:

model Case5_4
  FMU_Case5_2  m1;
  FMU_Case5_2  m2(fmu_InputStartForInitialization = false);  // 参数值改为false
  Real x(start=1);
  equation
  m1.'u' = x;
  m2.'u' = x;
  der(x) = time;
end Case5_4;

仿真结果曲线如下:

封装模型实例m2的输出变量y初始时刻值与输入x一致。

导入FMU生成的封装模型中,参数fmu_InputStartForInitialization用于控制初始时刻是否进行破环处理,若确定封装模型在与外部模型集成过程中不存在输入依赖输出的情况,则可以将该参数值改为false,使得输入变量初始时刻值生效。反之,若初始时刻出现环路导致模型求解问题,则将参数值设为true进行破环处理。

# 怎么控制FMU的计算步长

# 问题描述

导入Co-Simulation类型FMU后,怎么调整FMU的计算步长?

# 问题分析

对于Co-Simulation类型的FMU,调用FMUDoStep接口单步计算FMU模型,接口形式如下:

fmiStatus fmiDoStep(fmiComponent c,
                    fmiReal currentCommunicationPoint,
                    fmiReal communicationStepSize,
                    fmiBoolean newStep);

currentCommunicationPoint表示当前仿真的时间,communicationStepSize表示单步计算的步长。

导入生成的封装模型中,通过算法表示FMU的单步计算过程,如下:

algorithm
  when sample(fmu_StartTime + fmu_StepSize, fmu_StepSize) then
    // 设置输入变量pre值
    fmuFunctions.fmuSetReal(fmu_Object, {88}, {pre('a')});   
    fmuFunctions.fmuSetInteger(fmu_Object, {216}, {pre('b.')});
    for i in 1:fmu_StepFactor loop
      (fmu_StepSucceed, fmu_Terminated) := fmuFunctions.fmuDoStep(fmu_Object, 
        pre(fmu_Time) + (i - 1) * (time - pre(fmu_Time)) / fmu_StepFactor, 
        (time - pre(fmu_Time)) / fmu_StepFactor);   // 单步仿真
      if fmu_Terminated then
        terminate("FMU Terminated!");
      end if;
    end for;
    fmu_Time := time;
  end when;

其中,fmu_StepSizeReal​类型参数,默认值为0.002,表示外部模型与FMU模型的通讯步长;fmu_StepFactor为​Integer类型参数,默认值为1,表示一个通讯步长内可以进行几次单步计算,每次单步计算的步长为fmu_StepSize/fmu_StepFactor

# 解决方案

通过修改参数fmu_StepSizefmu_StepFactor来控制FMU的通讯步长和单步计算步长。

# Q-IDENT问题

# 问题描述

某项目中反馈,在Sysplorer中导入FMU生成的模型中,模型变量名都添加了单引号,如下:

// 原模型
model Case5_5
  input Real x;
  output Real y;
  Real a = time;
equation 
  y = a * x;
end Case5_5;

导出FMU后再导入生成模型:

model Case5_6
  ......
    
  Real 'a'(unit = "s");

  Modelica.Blocks.Interfaces.RealInput 'x'(start = fmu_Real_u_start[1]) ;

  Modelica.Blocks.Interfaces.RealOutput 'y'(start = fmu_Real_y_start[1], fixed = true);

  ......
end Case5_6;

导入生成的模型变量名添加了单引号后,导致的问题是:FMU接口规模很大(数千),应客户需求开发接口脚本自动连线功能时,根据FMU中的xml文件开发脚本自动识别连线时,无法与带单引号的变量名匹配上。

# 问题分析

Sysplorer导入FMU生成的模型中,变量名需要符合Modelica规范,如下模型中,若变量名不添加单引号则不合法:

// 原模型
model Case5_7
  model M1
    Real a = time;
  end M1;

  input Real x[2];
  output Real y;
  M1 m;
equation 
  y = m.a * x[1] * x[2];
end Case5_7;

导出FMU后再导入生成模型:

model Case5_8
  ......
    
  Real 'm.a'(unit = "s");

  Modelica.Blocks.Interfaces.RealInput 'x[1]'(start = fmu_Real_u_start[1]) ;

  Modelica.Blocks.Interfaces.RealInput 'x[2]'(start = fmu_Real_u_start[2]) ;

  Modelica.Blocks.Interfaces.RealOutput 'y'(start = fmu_Real_y_start[1], fixed = true);

  ......
end Case5_8;

层次化变量m.a以及数组分量x[1]x[2]若不用单引号包围,则不符合Modelica规范。

为了使层次化变量以及数组分量符合Modelica规范,同时保留变量与原模型变量的对应性,Sysplorer利用ModelicaQ-Ident机制,将导入FMU生成的模型中所有变量用单引号括起来。

# 解决方案

导入FMU生成的模型中,用户一般只关注模型顶层的端口(输入、输出变量),该项目也只需要处理模型顶层端口,所以我们在Sysplorer内核1525版本对FMU导入进行了一个优化处理:对于顶层的非数组端口变量,导入生成的FMU模型中其变量名保持与原模型一致,如下:

model Case5_9
  ......
    
  Real 'm.a'(unit = "s");

  Modelica.Blocks.Interfaces.RealInput x(start = fmu_Real_u_start[1]) ;

  Modelica.Blocks.Interfaces.RealOutput y(start = fmu_Real_y_start[1], fixed = true);

  ......
end Case5_9;

该优化处理也在Sysplorer 2024a中进行了同步支持,要注意的是,对于顶层数组端口,还是会生成带单引号的变量名。