Skip to content

An FMU

--runtime fmi3 writes target/export/<identifier>.fmu, an FMI 3.0 unit whose identifier is the package’s name as a Rust identifier, an underscore, and the model’s type name in snake case: the GNC example’s AttitudeControl is gnc_attitude_control. The FMU carries one of two interfaces, which --interface picks; one FMU cannot carry both, and the second export overwrites the first.

  • Co-Simulation, the default. The FMU carries its own solver (the model’s fixed-step, discrete step), and the master only chooses communication steps. A timed model declares fixedInternalStepSize, its tick, and a communication step has to be a whole number of ticks; one that is not is refused. An untimed model takes each communication step as its dt.
  • Scheduled Execution (--interface scheduled-execution). The importer is the scheduler: the FMU declares one clock, tick, at the model’s sample time, and every activation of it runs one tick. Inputs and outputs tick with that clock. A model with rated children still has one clock: each child steps behind its gate inside the tick, on the ticks its period falls on, so the outputs are the simulation’s tick for tick. A model with no rate anywhere has no tick to give the clock and is refused.

Two entries, and no sources/:

Entry What it is
modelDescription.xml the variables, the interface and the step size, generated by running the glue so every start value is the real default
binaries/<platform>/<identifier>.* the glue and the model, one shared library for the platform you built on

The library is built for the host and nothing else; there is no cross-compilation. The platforms an FMU can name:

Host <platform> Library
64-bit Linux, x86 x86_64-linux .so
64-bit Linux, ARM aarch64-linux .so
macOS, Apple silicon aarch64-darwin .dylib
macOS, Intel x86_64-darwin .dylib
64-bit Windows x86_64-windows .dll

Any other host is refused by name. The glue crate stays readable at target/codegen/fmi3/<package>/: an fmite model with a field per variable and the model crate behind them.

Every setting, input and output is one FMI variable, in that order after time, which FMI reserves and the FMU owns. Names are the model’s, so a signal named time (or tick, under Scheduled Execution) is refused: rename it.

In the model In the FMU
a params argument causality="parameter", variability="tunable", start = the default; new values apply from the next tick
an input causality="input", start zero
an output causality="output", variability="discrete", no start: zero until the first step
an array of numbers, [f64; 3] one array variable with a Dimension, as FMI 3.0 has them
a composite one variable per field, named with an underscore: p_rate
an array of composites m_0_rate, m_1_rate, …
a mode (an enum) refused: FMI enumerations are not exported yet, wherever the mode sits

Two signals that would map to one name (p.a beside p_a) are refused together, and the error shows both.

Outputs are the last tick’s, held over the communication step: after the k-th step or activation they are the simulation’s row k. No output depends on an input at the same communication point, so a master can close a loop through the FMU without an algebraic one.

Not yet:

  • saving and restoring the FMU’s state (fmi3GetFMUState and its pair answer with an error);
  • enumerations;
  • Model Exchange;
  • a build for another platform than the host’s.

The satellite attitude control example ships a 1 Hz controller, attitude_control::AttitudeControl, inside a 10 Hz simulation. Its params item gives every gain a default, which is what an FMU needs: a parameter has to start somewhere.

@every(1s)
step AttitudeControl(target: f64, th: [f64; 3], wf: [f64; 3], det: f64, fault: f64)
-> (tc: [f64; 3]) { … }
params AttitudeControl(kp: f64 = 0.05, kd: f64 = -1.5, k_detumble: f64 = -2.0) {
Self { kp, kd, k_detumble }
}

From the example’s directory:

Terminal window
magnet build --runtime fmi3 --model attitude_control::AttitudeControl
Exported target/export/gnc_attitude_control.fmu
Terminal window
unzip -l target/export/gnc_attitude_control.fmu
Length Date Time Name
--------- ---------- ----- ----
1967 01-01-1980 00:00 modelDescription.xml
474288 01-01-1980 00:00 binaries/aarch64-darwin/gnc_attitude_control.dylib

The description is the controller’s interface, as emitted:

  • the step’s @every(1s) is the fixedInternalStepSize;
  • the three settings start at their defaults;
  • th, wf and tc are array variables of three;
  • the output depends on no input within a step.
<?xml version="1.0" encoding="UTF-8"?>
<fmiModelDescription fmiVersion="3.0" modelName="gnc_attitude_control" instantiationToken="{871a79f3-3315-5d97-94d0-1ad912f8cd3a}" generationTool="fmite 0.2.0" variableNamingConvention="structured">
<CoSimulation modelIdentifier="gnc_attitude_control" fixedInternalStepSize="1" canHandleVariableCommunicationStepSize="true"/>
<DefaultExperiment stepSize="1"/>
<ModelVariables>
<Float64 name="time" valueReference="0" causality="independent" variability="continuous"/>
<Float64 name="kp" valueReference="1" causality="parameter" variability="tunable" start="0.05"/>
<Float64 name="kd" valueReference="2" causality="parameter" variability="tunable" start="-1.5"/>
<Float64 name="k_detumble" valueReference="3" causality="parameter" variability="tunable" start="-2"/>
<Float64 name="target" valueReference="4" causality="input" variability="continuous" start="0"/>
<Float64 name="th" valueReference="5" causality="input" variability="continuous" start="0 0 0">
<Dimension start="3"/>
</Float64>
<Float64 name="wf" valueReference="6" causality="input" variability="continuous" start="0 0 0">
<Dimension start="3"/>
</Float64>
<Float64 name="det" valueReference="7" causality="input" variability="continuous" start="0"/>
<Float64 name="fault" valueReference="8" causality="input" variability="continuous" start="0"/>
<Float64 name="tc" valueReference="9" causality="output" variability="discrete">
<Dimension start="3"/>
</Float64>
</ModelVariables>
<ModelStructure>
<Output valueReference="9" dependencies=""/>
<InitialUnknown valueReference="9" dependencies="1 2 3 4 5 6 7 8"/>
</ModelStructure>
</fmiModelDescription>

(The LogCategories element is left out above; the FMU logs a refused call under logStatusError and a panic under logStatusFatal.)

Any FMI 3.0 importer drives the FMU the same way, by value reference:

  1. Instantiate for Co-Simulation.
  2. Set the settings you want to change (kp is reference 1, kd 2), or leave them at their start values.
  3. Enter and exit initialization mode. The model is built from the settings here, as Model::init builds it in the simulation.
  4. Each communication step: set the inputs (references 4 to 8; th and wf take three values each), call doStep from the current time for 1.0 s (or 2.0, two ticks with the inputs held), and read tc (reference 9), three values. A step of 0.3 s is refused: it is not a whole number of 1 s ticks.

After the k-th step tc is exactly what magnet simulate prints for the controller on its k-th row given the same inputs. The test suite asserts this for:

  • this controller;
  • a rated motor loop;
  • a composite-ported model.

Change kp between steps and the next tick uses it.

Terminal window
magnet build --runtime fmi3 --model attitude_control::AttitudeControl --interface scheduled-execution

The file name is the same, and the description changes in three places:

  • the interface element is <ScheduledExecution modelIdentifier="gnc_attitude_control"/>;
  • every input and output becomes variability="discrete" with clocks="10";
  • the clock is declared:
<Clock name="tick" valueReference="10" causality="input" variability="discrete"
intervalVariability="constant" intervalDecimal="1" priority="0"/>

The importer activates clock 10 once a second; each activation runs one tick of the controller and the outputs are the simulation’s for that tick.