Exporting and importing an FMU
The export dialog covered on "Exporting your simulation as standalone code" offers a second format almost in passing: a real, ready-to-use .fmu instead of a .cpp or .py file you compile yourself. This page picks that up: what's actually inside the file, how to check it matches the model it came from with no Konjugate anywhere in the loop, and the mirror direction, bringing an FMU, Konjugate's own or someone else's, into a project as a node.
The example continues the same hot plate and room air model that page built: Hot plate fixed at 90, Room air starting at 20, a Cooling edge with rate constant k = 0.05.
Exporting as an FMU
Choosing FMU (FMI 2.0 Co-Simulation) as the export dialog's Format collapses Language to C++ only (an FMU is a compiled binary by definition) and hides Execution entirely (an FMU always runs sequentially):
Only Co-Simulation is offered, never Model Exchange. Model Exchange would hand the integrator to whatever host loads the FMU, which would pick its own solver, likely adaptive, not Explicit Euler, breaking the exact numerical-fidelity guarantee this whole feature is built on. Co-Simulation keeps Konjugate's own fixed-step solver fully in control: the host calls fmi2DoStep at whatever cadence it likes, and internally that steps Konjugate's real globalTimeStep however many whole ticks the requested step covers.
Every parameter gets real fidelity here, in a way a plain source export can't offer. A standalone program has no runtime control stream at all, so it bakes every parameter, live or constant, to a literal. An FMI host's fmi2SetReal calls are exactly that control stream: a live parameter becomes a genuine causality="input" variable, and a constant one becomes causality="parameter", variability="tunable", still defaulting to its stored value, so a host that never touches it reproduces today's behavior exactly, but one that wants to sweep or tune it can.
What's actually in the file
Unzip the .fmu and modelDescription.xml is plain, readable text: this is the real file this export produced, not a sketch of one.
<?xml version="1.0" encoding="UTF-8"?>
<fmiModelDescription fmiVersion="2.0" modelName="coolingModel" guid="7fd55f4e-4ad5-cb62-cd19-c6423342d25d" generationTool="Konjugate">
<CoSimulation modelIdentifier="coolingModel" canHandleVariableCommunicationStepSize="true"
canGetAndSetFMUstate="true" canSerializeFMUstate="true" providesDirectionalDerivative="false"/>
<ModelVariables>
<ScalarVariable name="Hot plate.Temperature" valueReference="0" causality="output" variability="continuous" initial="exact">
<Real start="90"/>
</ScalarVariable>
<ScalarVariable name="Room air.Temperature" valueReference="1" causality="output" variability="continuous" initial="exact">
<Real start="20"/>
</ScalarVariable>
<ScalarVariable name="k" valueReference="2" causality="parameter" variability="tunable">
<Real start="0.05"/>
</ScalarVariable>
</ModelVariables>
<ModelStructure>
<Outputs><Unknown index="1"/><Unknown index="2"/></Outputs>
</ModelStructure>
</fmiModelDescription>
Every node's every state becomes its own output variable, named <node name>.<state name>. k, the Cooling edge's one parameter, appears too, at its stored value. canGetAndSetFMUstate="true" means this specific FMU supports rollback: capture a state snapshot mid-run, step further, restore it. That's conditional: a model with an embedded C++ or Python provider declines rollback instead ("false"), since a provider can carry arbitrary internal state across evaluations with no generic way to serialize it. This model is plain equations only, so it qualifies.
The guid is deterministic, not random, a hash of the model's identifier and its generated C++ source, not randomUUID(). Exporting this identical project again, even on a different machine, lands on this exact same guid. That's what makes merging platform builds possible: Merge platform FMUs…, in the same dialog, combines single-platform exports (Konjugate never cross-compiles: each export only ever contains the binary for the platform it ran on) into one multi-platform .fmu, by confirming their guids agree before trusting their binaries together, then unioning each file's binaries/<platform>/ entry around one shared modelDescription.xml. Pure zip surgery, no compiler involved.
Verifying it independently
konjugateEngine's runFmu command loads a .fmu's own compiled binary directly through the real FMI2 C API. No Konjugate project is involved, just the shared library FMI tooling would load:
$ konjugateEngine runFmu coolingModel/binaries/darwin64/coolingModel.dylib \
--state-count 2 --configuration run.json --output rollback.csv --verify-rollback
Rollback check passed: fmi2Get/SetFMUstate round-tripped correctly.
$ tail -1 rollback.csv
40,90,80.5312673030786
80.5312673030786 is the same number the app itself, and the plain C++/Python exports, produced from this identical model. The FMU reproduces the identical run, and its rollback machinery round-trips correctly on top of that.
Installing one
An FMU installs the same way an add-on or plugin does, from the Extensions dialog's own FMUs tab, covered in "Installing add-ons and plugins." Unlike an add-on or plugin, installing an FMU takes effect immediately: no restart needed before a project can reference it.
Referencing an installed FMU from a node
This is the one corner of the feature with no editor UI yet. A node's implementation references an installed FMU by hand-edited project JSON, the same way an inline computational-node provider is still authored today:
{
"kind": "fmi",
"fmuId": "7fd55f4e-4ad5-cb62-cd19-c6423342d25d",
"fmuVersion": "1.0.0",
"bindings": [
{ "key": "k", "kind": "state", "stateId": 11 }
],
"outputs": [
{ "key": "hotPlateTemperature", "stateId": 12 },
{ "key": "roomAirTemperature", "stateId": 13 }
]
}
fmuId and fmuVersion pin the exact installed FMU: its modelDescription.xml guid, and the version Konjugate assigned it at install time (FMI itself has no package-version concept). Every FMU input or parameter needs a binding, and every output needs an entry in outputs, no partial coverage, since there's no sensible default for an arbitrary vendor variable. A key is derived from the FMU's own variable name by the same rule Konjugate applies automatically: strip everything that isn't a letter or digit, camel-case what's left. Hot plate.Temperature becomes hotPlateTemperature; a bare k stays k.
An FMU output is a value, not a rate. Konjugate marks every one of them setsValue: true behind the scenes, the same algebraic-state mechanism a setsValue source term uses, so the FMU's reported value is written directly into the target state every substep, with zero approximation error and no dependency on Explicit Euler's own arithmetic.
Resolution happens before validation and a run ever start, for the app, the CLI or a script calling the engine adapter directly. By the time the native engine sees this node, it's an ordinary computational-node provider, with no FMI-specific knowledge anywhere in the engine itself.
Proving the import
Building a second, separate project (one node, three states: k, hotPlateTemperature, roomAirTemperature, the JSON above as its whole implementation) and running it for the same 40 seconds:
Two of that node's states are declared with deliberately wrong placeholder values first: hotPlateTemperature starts at 0 instead of the real 90. At the very first sample after simulation begins, it reads 90. The FMU's own reported value overwrites the placeholder immediately, not gradually. From there on, comparing every one of the 400 samples against the original model's own run of the same 40 seconds: roomAirTemperature matches to the full precision either run reports, at every single sample, a maximum difference of exactly 0, not just "close." The entire two-node, one-edge model really did collapse into one opaque, correct, drop-in component.
Where to go from here
"Exporting your simulation as standalone code" covers the same dialog's other format: a program you compile and own outright, rather than a component another tool drives. "Installing add-ons and plugins" covers the Extensions dialog an FMU installs through.
Follow Konjugate on LinkedIn for updates, or subscribe via RSS.
Comments
Post a Comment