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 declaresfixedInternalStepSize, 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 itsdt. - 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.
What is in the file
Section titled “What is in the file”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.
Variables
Section titled “Variables”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 (
fmi3GetFMUStateand its pair answer with an error); - enumerations;
- Model Exchange;
- a build for another platform than the host’s.
The GNC controller, exported
Section titled “The GNC controller, exported”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:
magnet build --runtime fmi3 --model attitude_control::AttitudeControlExported target/export/gnc_attitude_control.fmuunzip -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.dylibThe description is the controller’s interface, as emitted:
- the step’s
@every(1s)is thefixedInternalStepSize; - the three settings start at their defaults;
th,wfandtcare 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.)
Stepping it
Section titled “Stepping it”Any FMI 3.0 importer drives the FMU the same way, by value reference:
- Instantiate for Co-Simulation.
- Set the settings you want to change (
kpis reference 1,kd2), or leave them at their start values. - Enter and exit initialization mode. The model is built from the
settings here, as
Model::initbuilds it in the simulation. - Each communication step: set the inputs (references 4 to 8;
thandwftake three values each), calldoStepfrom the current time for1.0s (or2.0, two ticks with the inputs held), and readtc(reference 9), three values. A step of0.3s 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.
The same controller under a scheduler
Section titled “The same controller under a scheduler”magnet build --runtime fmi3 --model attitude_control::AttitudeControl --interface scheduled-executionThe 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"withclocks="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.