FhSim  3.1.0
Marine systems simulation
Loading...
Searching...
No Matches
FMU tools

The FhSim FMU (Functional Mock-up Unit) tool makes it possible to export an FhSim model as a stand-alone FMU, see https://fmi-standard.org/. In particular, you can define a set of SimObjects in an FhSim configuration file and export the model, which then bundles necessary libraries and resources in an FMU. The exported FMU implements the Co-Simulation interface of either FMI 2.0 or FMI 3.0; --fmi-version {2,3} picks which, and the default is 2. What each version can and cannot do is set out in What the exported FMU can and cannot do.

FhFmuExport

FhFmuExport is the program that exports an FhSim model file as a stand-alone FMU.

Overview

FhFmuExport is a tool to pack an fhsim model file into an FMU with the required binaries to run the model in an FMU Co-Simulation environment. An FMU is a collection of binaries and resources needed to execute a simulation and communicate over a predefined interface. The folder hierarchy of an FMU is as follows:

.
├─ modelDescription.xml <--- Definition of the IO interface of the FMU, name and author information
├─ resources <--- Resource directory with non-executable files needed for operation
└─ binaries <--- Binary directory with executable files needed for operation
└─ <platform> <--- One directory per platform the FMU carries binaries for

The two FMI versions name that platform directory differently, and the exporter follows whichever version you asked for:

FMI version Directory naming Examples
2.0 operating system plus word size linux64, win64, linux32, win32, darwin64
3.0 processor architecture and operating system, joined by a hyphen x86_64-linux, x86_64-windows, aarch64-linux, x86_64-darwin

There is no requirement for a complete set of binaries as long as binaries for the platform in use is present. Missing binaries will only influence the ability to exchange FMUs with users of different platforms. This tool only export binaries for the platform it was compiled for. Note that we only actively maintain the 64 bit version of FhSim.

Note
An FMU carries one FMI version's layout, not both: the two versions differ in the platform directory name and in which slave module is bundled, so a model needed in both versions is exported twice, into two different output directories.

The tool parses the FhSim input file and collects a list of all dynamic libraries used (in the LibName attribute of the Lib elements in the input file). The tool creates the folder structure for the FMU and creates an installation directory for FhSim, which includes the base executables and libraries for simulation and visualization. Everything executable lands under binaries/<platform>/:

What Where it lands When
The FMI slave module (fhFMUDll for FMI 2.0, fhFMU3Dll for FMI 3.0) binaries/<platform>/ always
The engine libraries fhSimDll and, with visualization, fhVisDll binaries/<platform>/ always
The Ogre rendering libraries and their plugins binaries/<platform>/, flat unless --no-bundled-vis
The Assimp mesh-importer plugin Codec_Assimp beside the other Ogre plugins only with --with-codec-assimp
The SimObject libraries the model actually uses binaries/<platform>/SimObjectLibraries/ always
A license file named license.lic binaries/<platform>/license.lic with --include-license-file

Beside them, every FMU carries its own documentation:

What Where it lands When
interface.html, the model's inputs, outputs and parameters as a table documentation/interface.html always — written by the exporter, unless --documentation supplied one
index.html, the entry point an FMI importer offers the reader documentation/index.html always — a placeholder pointing at the table, unless --documentation supplied one
Whatever else --documentation names: prose, images, further pages documentation/, keeping its own tree with --documentation

FMI 2.0 §2.2.1 and FMI 3.0 §2.5.1.1 both reserve documentation/ for the human-readable description of a model and name documentation/index.html as its entry point. The interface table is written here because this is what knows the interface; everything a reader wants beyond it — what the model is for, who publishes it, under what licence — is not in the model file, so it comes in through --documentation. A file whose name begins with a dot is not copied, because an FMU that carries one fails tests/fmu/CheckFmuArchive.cmake, and neither is a symbolic link. See The documentation an FMU carries.

Only the SimObject libraries the model refers to are copied, not the whole playpen. The copy is not best-effort: if any library named by a LibName attribute cannot be found in the playpen's SimObjectLibraries directory, the export fails rather than producing an FMU that would break on the host's first Instantiate — see Exit codes.

Note
Which Ogre plugins travel and which the FMU's resources/plugins.cfg names are decided together. Ogre throws when it cannot load a plugin the file lists, so leaving Codec_Assimp out of the archive also comments its Plugin=Codec_Assimp line out of the bundled plugins.cfg, and --with-codec-assimp restores both. The plugins.cfg inside the FMU is a rewritten copy in either case — its PluginFolder is repointed at the bundled binaries/<platform> directory, which Ogre reads relative to the resources/ directory the file sits in — so editing the playpen's own copy after an export changes nothing.
The plugins are bundled flat in binaries/<platform>/, beside the Ogre libraries, and not in an OGRE/ subdirectory as the playpen keeps them. A SimObject module bundled at binaries/<platform>/SimObjectLibraries/ was linked in the playpen, with the playpen's RUNPATH, and the one entry of that RUNPATH which resolves inside an FMU is $ORIGIN/.. — the platform directory. The ogre3d package puts the Ogre plugins on the link line, so a module can carry a DT_NEEDED on one; the dynamic loader resolves that entry before anything in the module runs, and it never reads plugins.cfg. A plugin in a subdirectory is therefore invisible to it, however the config file spells the path and whether or not the file is in the archive (FHSIM-0009).

The tool then collects all SimObjects of type ExternalLinkStandard used for data exchange during simulation.

Warning
The causality is inverted relative to FhSim. An ExternalLinkStandard object's input ports are inputs to the object and therefore outputs of FhSim, so they become FMU outputs; its output ports are outputs of the object, inputs to FhSim, and become FMU inputs. Reading the attribute names as if they were FMI causalities is the single most common source of confusion when first inspecting a generated modelDescription.xml.

Those ports become FMU variables of the version's real type — Real in FMI 2.0, Float64 in FMI 3.0. They are not the only variables the FMU carries: the built-in and model-supplied parameters described in Parameters the exported FMU carries add Boolean, String and real-typed variables too. How the variables are named and numbered is set out in Variables, names and value references.

The mapping between the FMU value reference and the FhSim ‘'object’:'port'pair is stored in the fileiomapping.xmlin the FMU resources folder, and is what the FhSim Co-Simulation implementation —fhFMUDllfor FMI 2.0,fhFMU3Dllfor FMI 3.0 — reads at run time to decide which FhSim port a value reference refers to. That file also carries an *instantiation token*, a generated identifier the FMI 3.0 model description repeats: it lets the runtime refuse aresourcesdirectory that does not belong to the modelDescription.xml‘ the host read, which is what a stale unpacked FMU or two exports’ files mixed by hand look like.

The FhSim model file is stored as model.xml in the FMU resources folder, and the appropriate FMU modelDescription.xml file is written to the FMU folder hierarchy.

Finally, the tool compresses the folder into an FMU (a zipped archive with the .fmu extension). The archive is written in-process with the bundled miniz library, so no external zip program is needed. Pass --no-compression to store the entries uncompressed, which is faster and useful in CI.

Note
FhFmuExport finds the FhSim installation to bundle from the directory its own executable sits in — the SimObjectLibraries beside it, and the lib/ (or bin/) and resources/ directories one level up. The working directory plays no part in that search; it only decides where the output directory and the .fmu file are written. Running the exporter from inside a playpen's bin therefore works because that is where the executable is, not because of the cd.

Program Options

The table below provides a short description of available command line options for FhFmuExport.

Program option Short Default Description
--input-file -i required The FhSim input file defining the model to export as an FMU
--model-name -n fhsimexport The name of the FMU. Also the name of the output directory and of <name>.fmu
--fmi-version — 2 FMI version of the exported FMU: 2 or 3. Picks the modelDescription.xml format, the binaries/<platform> directory name and which slave module is bundled. Any other value is an error
--author-name -a SINTEF Ocean The author of the FMU
--description -d Model exported from FhSim with FhFMUExport Description of the FMU
--include-license-file -l none Path to a license file to include in the FMU. The file is copied and renamed to binaries/<platform>/license.lic, whatever it was called
--documentation — none A directory copied into the FMU's documentation/ folder, keeping its own tree. Supply an index.html here and it becomes the entry point an importer offers the reader, in place of the placeholder the exporter would write. Files whose name begins with a dot are left behind, and so are symbolic links, which are not followed. A path that is not a directory is an error, because an export asked for documentation that quietly produced none is the mistake this flag exists to prevent
--add-dir-to-resources, --add-res-dir -p none Copy a directory — or a single file — into the FMU resource folder to make it available for the FMU. A directory keeps its own leaf name, so data/textures lands as resources/textures. May be repeated
--autocopy -c off Auto copy files and folders referenced in the FhSim model attributes into the FMU. Every relative path in an attribute is resolved against the FhSim installation the exporter runs from — the bin its own executable sits in, the directory FhSim runs a model from — and not against the working directory, so the same model exports the same FMU from anywhere. The copies go into resources/auto_copied/, each under its own last path component, and references in the FMU's own fhsim model file are rewritten to point at the bundled copies. A file already inside the playpen's resources/ is bundled too: only part of that tree travels into the FMU, so a reference left in place would not have reached a bundled copy
--debug -D off Build the FMU from debug binaries, useful for debugging in single threaded mode in Common Simulation Platform.
--no-compression — off Store the FMU archive uncompressed. Faster packaging; useful for CI and testing.
--no-bundled-vis — off Skip bundling the visualization/Ogre binaries in the FMU, and export the visualization parameter with a start value of false, so the FMU never declares a renderer it does not carry. Useful for CI and testing where visualization is disabled
--with-codec-assimp — off Bundle the Assimp mesh-importer Ogre plugin. It is left out by default because it is the largest single file the renderer carries — about 26 MB of an export, counting the versioned file and the symlink beside it, both of which are stored in full. Pass it when a model's visualization loads meshes in a format only Assimp reads
--guess-input-interface — off Use constant valued source ports (example: "0,0,1") as input to the FMU. A new ExternalLinkStandard object named fmiInput is created for input to the FMU and connected to the previously constant ports. Initial values are preserved, and the generated variables are named after that object
--guess-output-interface — none Point to a previously generated fhsim output file to make outputs for the FMU. A new ExternalLinkStandard object named fmiOutput is created for outputs and the ports in the output file are connected to it, so the generated variables are named after that object. This requires that only port outputs are used in the example output file; no state outputs are allowed
--verbosity -v off Print progress detail while exporting. This is not a version flag: FhFmuExport has no --version.
--help -h — Display the help menu
Note
--input-file is the only required option, and it may be given at most once, as may every other option except --add-dir-to-resources.
Remarks
In the Default column, off is a switch that is simply absent unless you pass it, while none is an option that takes a value and contributes nothing when no value is given.
Warning
The names the two interface-guessing options synthesise, fmiInput and fmiOutput, become the object part of every variable name they generate — so --guess-input-interface produces FMU inputs called fmiInput.<port>[<i>]. There is no option to choose a different name.

To print a help message type: FhFmuExport --help and you will get:

./FhFmuExport {OPTIONS}
FMU exporter for FhSim.
OPTIONS:
-h, --help Display this help menu
-i[input-file],
--input-file=[input-file] FhSim input file
-n[model-name],
--model-name=[model-name] Model name in FMU
-c, --autocopy Auto copy files and folders referenced
in model attributes into the FMU. Paths
are resolved against the FhSim
installation the exporter runs from, not
the working directory, and references in
the FMU fhsim model file are rewritten
to point to the bundled copies
-p[add-dir-to-resources...],
--add-res-dir=[add-dir-to-resources...],
--add-dir-to-resources=[add-dir-to-resources...]
Paths to be added to fMU resource folder
-D, --debug Build debug-fmu
--no-compression Disable compression for faster FMU
packaging (useful for CI/testing)
--no-bundled-vis Skip bundling visualization/Ogre
binaries in FMU (useful for CI/testing
where vis is disabled)
--with-codec-assimp Bundle the Assimp mesh-importer Ogre
plugin, which is left out by default
-a[author-name],
--author-name=[author-name] Author name in FMU
-d[description],
--description=[description] Model description
-v, --verbosity Turn on verbosity?
--guess-input-interface Guess input interface from constant
values in the interconnections (keep
initial values)
--guess-output-interface=[guess-output-interface]
FhSim output file to guess additional
FMI output from
-l[include-license-file],
--include-license-file=[include-license-file]
Path to license file to include in FMU
--documentation=[documentation] Directory copied into the FMU's
documentation/ folder. FMI reserves that
folder for the human-readable
description of the model, with
documentation/index.html as its entry
point; the exporter writes
documentation/interface.html unless this
supplies one. Symbolic links in it are
not followed
--fmi-version=[fmi-version] FMI version of the exported FMU: 2 or 3
(default 2)
--print-build-key Print the build key of the FMU these
options would produce and exit,
exporting nothing. The key changes when
the model file, a bundled binary or a
content-affecting option changes, so a
pipeline can compare it with the key
stored beside an already published FMU
and skip the export and the upload when
they match
--build-key-extra=[build-key-extra...]
Extra identity to fold into the build
key, repeatable. For what this process
cannot see: a Conan package reference
and revision, a git commit. Without it a
library's identity is the digest of the
file, which changes whenever the library
is rebuilt from unchanged sources
FhSim © 2006-2024 Sintef Ocean - Visit www.fhsim.no for more information
Remarks
If you need an FMU to support running SimObject libraries either with, or without visualization, you need to export from a playpen that contains files for both variants. Note that the fhFMUDll must come from a configuration with visualization. A functional procedure is thus to unzip a playpen with visualization enabled into a non-visualization playpen.

Exit codes

FhFmuExport reports every failure through its exit status, so a build script or CI job does not have to parse its output.

Code Meaning What to do
0 The FMU was written. —
1 Bad command line, an FhSim input file that could not be loaded or parsed, a malformed port size or initial value, a refusal to overwrite the output directory, or a failure while writing the archive. The message on standard error says which. Read the message; it names the file and the reason.
10 The FhSim installation beside the exporter is incomplete: the slave module, or both the visualization and the non-visualization engine library, could not be found. Export from a complete playpen, and remember that the search starts from the exporter's own directory.
11 The SimObject library list is incomplete — at least one library named by a LibName attribute is not in the playpen's SimObjectLibraries directory. The preceding message names the two file names that were tried. Build or copy that library into the playpen.
Note
The FMI 3.0 slave module is built behind the CMake option FHSIM_FMI3_FMU, which is on by default. In a build configured with -DFHSIM_FMI3_FMU=OFF the module does not exist, so --fmi-version 3 would package an FMU with no slave in it. Nothing in the exporter detects that, so check the option before trusting an FMI 3.0 export from an unfamiliar build.

Skipping an export that would change nothing

Exporting an FMU and uploading it is slow, and most builds change nothing about it. --print-build-key prints a digest of everything that decides the FMU's content and exports nothing:

FhFmuExport -i model.xml -n MyModel --print-build-key
a0bb5d63b4c3c952e274efc47662abd6bfc42438e090c462b0d0990afb327676

Every export also writes that digest beside the archive, as <model>.fmu.buildkey. Upload the two together, and the next build can ask whether there is anything new to publish:

key=$(FhFmuExport -i model.xml -n MyModel --print-build-key) || exit 1
if [ "$key" = "$(fetch_published MyModel.fmu.buildkey)" ]; then
echo "MyModel is already published; nothing to export"
else
FhFmuExport -i model.xml -n MyModel
upload MyModel.fmu MyModel.fmu.buildkey
fi

What the key covers

Covered Not covered
The model input file, byte for byte The author (-a), the description (-d), the verbosity
The name and content of every binary the FMU would carry: the slave module, the engine libraries, the Ogre libraries and plugins, and each SimObject library the model names, and of every file --documentation would bundle The compression flag (--no-compression), which changes the archive's encoding and not its content
The options that change what is bundled: --fmi-version, -D/--debug, --no-bundled-vis, --with-codec-assimp, -c/--autocopy, -p/--add-res-dir, -n/--model-name, -l/--include-license-file, --documentation The content of the playpen resources/ tree, of an --add-res-dir directory, or of a file --autocopy pulls in — only the fact that they were asked for

A change to something in the right-hand column does not change the key, which is the point: a build that alters only a description string should not cost an upload.

When a library is rebuilt

A bundled library's identity is the digest of the file. Nothing records a SimObject library's version — a module exports only getSimObject, and neither the playpen nor the build keeps a manifest — so the file is the only identity there is. That is exact, and it is stable as long as the libraries come from a package cache. A pipeline that recompiles them from unchanged sources gets a new file, and therefore a new key, for no real change.

Where a stable identity is known, fold it in with --build-key-extra, which is repeatable:

FhFmuExport -i model.xml -n MyModel --print-build-key \
--build-key-extra "fhsim/3.1.0#a1b2c3" \
--build-key-extra "fhsim_base/2.4.0#d4e5f6"

The key is then whatever those strings and the model file say, and survives a rebuild.

Publishing both FMI versions

--fmi-version is part of the key, and the two versions bundle different slave modules, so an FMI 2.0 and an FMI 3.0 export of the same model always have different keys and are skipped or re-exported independently. They do collide on disk: both write <model>.fmu and <model>.fmu.buildkey from -n, so export each version under its own name or into its own directory, exactly as the test exports do.

Note
The key says nothing about whether two FMUs are byte-identical — they never are, as each export stamps a fresh UUID and the current time into modelDescription.xml. It answers only the question it exists for: would exporting again produce a different FMU?

Variables, names and value references

A host sees the FMU only through its modelDescription.xml: a flat list of variables, each with a name it can show a user and a numeric value reference it uses in every get and set call. Both are generated, and knowing the rules is what lets you predict a variable's name before you have exported anything — which you need, for instance, to write the headless start values in Running an FhSim FMU in an automated host.

Names. Every variable name ends in a bracketed element index, because even a single-element signal is exported as a one-element array.

  • An input or an output is named <Object>.<Port>[<i>], with the FhSim SimObject name, the port name on that object, and the 0-based element index within the signal.
  • A parameter is named <Object>[<i>] — there is no port segment, because a parameter maps to no port.

Value references. There is a single 0-based counter, shared by every data type. It is not one counter per type, as some FMI tooling leads one to expect: value reference 0 is one particular variable in the whole FMU, not "the first Real" and also "the first Boolean". The counter is allocated in port-mapping order — the three built-in parameters first, then the model's VARIABLES entries, then each ExternalLinkStandard object's FMU outputs and then its FMU inputs — and each mapping consumes as many consecutive numbers as its signal has elements. In FMI 3.0 the required time variable takes the next free number after every mapped scalar.

The two files must agree. The runtime resolves a value reference to an FhSim port through resources/iomapping.xml alone; modelDescription.xml is what the host reads. Editing either file by hand therefore breaks the FMU, and does so silently: the host would read and write ports other than the ones it named, with no diagnostic. Re-export instead.

**variableNamingConvention** is "structured" only when every generated name conforms to the structured-name grammar the FMI specifications define — which means every object and port name it concatenates must start with a non-digit and contain only letters, digits and underscores. Nothing validates the names your input file supplies, so one SimObject called Net 1 or main-body makes the whole model description fall back to "flat". Hosts that offer to group variables into a tree rely on this attribute, so name objects and ports conservatively if that matters to you.

A worked example

The FirstOrder test model in this repository is small enough to enumerate completely. It holds one first-order SimObject and one ExternalLinkStandard for data exchange:

<Contents>
<OBJECTS>
<Lib LibName="fhsim_test_objects" SimObject="Math/FirstOrder" Name="Sys1" A="1.0" B="1.0"/>
<Lib LibName="fhsim_test_objects" SimObject="System/ExternalLinkStandard" Name="ExtLink"
outputPortNames="u" Initial_u="2.0"
inputPortNames="y" Size_y="1"/>
</OBJECTS>
<INTERCONNECTIONS>
<Connection Sys1.In="ExtLink.u"/>
<Connection ExtLink.y="Sys1.Out"/>
</INTERCONNECTIONS>
<INITIALIZATION>
<InitialCondition/>
</INITIALIZATION>
<SIMULATION>
<Timing TStart="0" TEnd="1"/>
<Integrator Method="RK45_i" NumCores="1">
<StepControl Step="0.01"/>
</Integrator>
</SIMULATION>
</Contents>

Exported, it becomes exactly six variables — five in FMI 2.0, and those five plus time in FMI 3.0:

Value reference Variable name FMI 2.0 type FMI 3.0 type Causality Where it comes from
0 visualization[0] Boolean Boolean parameter, fixed — or local, constant, when the renderer is bundled built in
1 state_file[0] String String parameter, fixed built in
2 integrator_timestep[0] Real Float64 parameter, fixed <StepControl Step="0.01">, so its start value is 0.01
3 ExtLink.y[0] Real Float64 output ExtLink's inputPortNames="y" — an input to the object is an output of the FMU
4 ExtLink.u[0] Real Float64 input ExtLink's outputPortNames="u", with Initial_u="2.0" as its start value
5 time — Float64 independent FMI 3.0 only; the first number no mapping covers

Note that the object name ExtLink and the port names are reproduced verbatim, that y and u swap sides relative to the FhSim attribute they came from, and that the parameters carry no port segment in their names.

The documentation an FMU carries

An FMU is a file somebody downloads. Everything else in it is written for a program — modelDescription.xml for the importer, resources/iomapping.xml for the runtime, binaries/ for the loader — so documentation/ is the only part of an archive a person reads. FMI reserves it, and names documentation/index.html as the entry point an importer offers (FMI 2.0 §2.2.1, FMI 3.0 §2.5.1.1).

What the exporter writes by itself

documentation/interface.html lists every variable the FMU exposes, grouped into inputs, outputs and parameters, with the type, the value reference and the start value the model states. The names and types are the ones modelDescription.xml uses, because both are written from the same port mappings — a table that renamed a port would send a reader looking for something no host can address.

documentation/index.html is the entry point. Without --documentation it is a placeholder that says the archive carries no description and links to the interface table, so that the standard's entry point is always present and always leads somewhere.

What you supply

FhFmuExport -i NetCage.xml -n NetCage --documentation doc/NetCage

Everything under doc/NetCage is copied into the archive's documentation/, keeping its own tree. An index.html in it becomes the entry point, in place of the placeholder, and an interface.html in it is kept in place of the generated table. Write it for a reader who has unpacked the archive on a machine with no network: an absolute URL still works where there is one, but a stylesheet, a font or an image fetched from elsewhere leaves the page broken exactly where it is meant to be read. Reference images by relative path and let them travel in the same directory.

Two rules are worth knowing before you fill the directory:

  • A file whose name begins with a dot does not travel. An editor's backup or a .git directory left behind would otherwise fail tests/fmu/CheckFmuArchive.cmake, which refuses an archive carrying a build-harness artefact.
  • A symbolic link does not travel, whether it points at a file or at a directory. Copy the content into the directory instead. The build key and the archive both leave links out, so nothing outside the directory can change the FMU without changing its key.
  • The documentation is part of the build key (Skipping an export that would change nothing), so correcting a page is enough to make a pipeline export and republish the FMU. That is deliberate: the page is inside the archive, and an uncorrected copy would otherwise stay published.

tests/fmu/CheckFmuDocumentation.cmake holds an exported archive to both halves of this: the entry point is there, and every relative src/href in a bundled page names a file the archive really carries. That second check is the one that catches the realistic mistake — whether an image was copied into the archive is decided by the export, not by the author, so a page that renders perfectly in the source tree can arrive with every image broken.

Parameters the exported FMU carries

Every FMU FhFmuExport writes carries three built-in parameters, whatever the model. They are declared fixed, which in FMI means they may be written while the instance is in Instantiated or Initialization Mode and not afterwards; the FMU reads all three once, at ExitInitializationMode, on its way to building the engine.

The exception is visualization in an FMU that bundles the renderer. There it is written as a local constant and cannot be set at all, because such an archive carries only the Vis-suffixed SimObject modules: the engine a false would select cannot resolve them, and initialisation fails with Simobject library file not found. Rather than offer a setting that cannot work, the export states the fact. Running such an FMU without a window is visible=false at instantiation, which works and is described below.

Name Type Start value Effect
visualization[0] Boolean true when the renderer was bundled, false for --no-bundled-vis or a playpen without fhVisDll Whether the FMU loads the visualization engine and opens a render window. Settable only when it is false, i.e. when there is no renderer to turn off; see Running an FhSim FMU in an automated host
state_file[0] String the model's InitialStatesFile, or empty The initial-states file to load, overriding whatever the model named
integrator_timestep[0] Real / Float64 the model's <StepControl Step="..."> The integrator step size the run uses. It is not the communication step size, which the master chooses independently

In addition, every <Var> entry in the model's <VARIABLES> block becomes a fixed FMU parameter. Its name is the variable's Name attribute, and its type is decided by its Value: Real/Float64 when every comma-separated token parses as a number, String otherwise. A value with several comma-separated tokens becomes a parameter with that many elements, named <Name>[0], <Name>[1] and so on.

This makes the VARIABLES block the model author's control over the FMU's parameter surface: anything you want a co-simulation host to be able to set, express as a $Variable in the model and give it a <Var> entry. Anything you leave as a literal attribute value is baked into the exported model.xml and cannot be changed from outside.

Note
These parameters are fixed rather than tunable for a reason a host cannot see: FhSim applies a VARIABLES value by patching the model file it is about to load, which happens once, at ExitInitializationMode. A tunable declaration would invite a host to write one in Step Mode and the write would never reach the engine. The FMU refuses such a write outright instead — see FMU export and co-simulation.

What the exported FMU can and cannot do

Interface and instances

The FMU implements Co-Simulation only. There is no Model Exchange export, and FMI 3.0's Event Mode is not offered either: the FMU declares hasEventMode="false".

FhSim also has no FMU import. Nothing in FhSim can load a third-party FMU as a SimObject; the traffic is one-way, out of FhSim into someone else's master.

The FMU declares canBeInstantiatedOnlyOncePerProcess="true", and it means it. Loading the FMU twice in one process is not supported, and neither is loading two FhSim FMUs there: the engine reads several of its inputs relative to the process working directory, so the slave changes the whole process's working directory while it builds the model. A second instance initializing at the same time would see the first one's directory. Run each FhSim FMU in its own process — with Open Simulation Platform that means proxyfmu, as in the cosim example further down this page. (The FMU says so itself: it logs not running in proxyfmu, could cause segfaults/crashes due to library conflicts! whenever the process that loaded it is not proxyfmu. Outside OSP, with one FhSim FMU in the process, that warning is expected and harmless.)

Saving and restoring state

GetFMUState, SetFMUState and the serialize/deserialize pair are all supported and declared. Three caveats apply:

  • The serialized blob is format version 2, and a version-1 blob from an older FhSim is refused with a clear error rather than being read as if the layout had not changed. Blobs do not survive an FhSim upgrade across that boundary.
  • The string buffer is not part of the state. A snapshot carries the integrator state vector, the time, the integrator step size and the real, integer and boolean IO buffers. String-valued variables are not restored.
  • FMU state does not round-trip a Sundials integrator. The round trip is exact for the FhIntegrator single-step family — including the adaptive RK45_i — whose next step depends on nothing beyond the time, the state vector and the step size, all three of which the snapshot carries. CVode and ARKODE keep adaptive internal step and multistep order/history that the snapshot cannot reach, and restoring a time discards whatever history existed. If you need state rollback, configure a single-step method.

Derivatives

Both versions provide directional derivatives; FMI 3.0 additionally provides adjoint derivatives, which FMI 2.0 has no entry point for.

Warning
The quantity returned is the direct-feedthrough partial derivative ∂y/∂u with the states held, not a step response. That is what both FMI specifications define at a communication point, and it has the consequence that surprises people most: an output that an input reaches only through a state correctly reports zero. The model equations at that instant have no dependence of that output on that input; the dependence appears only after time advances, and no step size appears anywhere in the FMI definition. This is the specified answer, not a missing feature — please do not report it as a bug.

Both derivative calls are Step Mode only. A query in Initialization Mode is refused, because the engine does not build the model until ExitInitializationMode returns and there is nothing to differentiate before that.

Types served

An FMI 3.0 host gets Float64, Int32, Boolean and String — the four type families FhSim exchanges. Every other family, Float32 and the 8-, 16- and 64-bit integers, the unsigned integers, Binary and Clock, is refused with fmi3Error. A host that probes for them will see an error rather than a crash, and the instance stays usable.

Steps, time and the experiment

The model description carries a <DefaultExperiment> read from the model's own <Timing TStart TEnd> and <Integrator>/<StepControl Step RelTol>. A model written in the legacy pre-v3 form has neither element, so its FMU carries an empty <DefaultExperiment> and the host must be told the horizon itself.

  • A zero, negative or NaN communication step size is refused with an error, and the instance stays in Step Mode, so the next well-formed step still runs.
  • A step that would run past the declared stop time is discarded at the stop time, reported with fmi2Discard/fmi3Discard and a last-successful time at the stop time. A master that walks past stopTime therefore gets a refusal it can act on, not a success at a point that was never simulated.
  • Off-grid communication points overshoot by up to one integrator step. The engine stops at the first accepted integrator step at or past the time it was asked for, and it does not shorten that last step to land exactly on a communication point that is not a multiple of the integrator step size. An output can therefore belong to a time up to one integrator step later than the point you asked for. Choose a communication step size that is a whole multiple of integrator_timestep[0] and the question does not arise.

Writing variables

All parameters are fixed, so a parameter write is refused once Step Mode has been entered, with an error that names the value reference. Inputs stay writable throughout, as co-simulation requires.

An unknown value reference is a clean error: the call fails, the instance is not poisoned, and the run continues. So is visible=false at instantiation, which suppresses the render window even when the visualization parameter is true — FMI defines visible as "open no windows", and an instantiation-time argument is not something a bundled default should override.

Example usage

Suppose you have created an FhSim configure file awesomeness.xml and want to export an FMU for this model. You have a working FhSim installation (also known as FhSim playpen) located at /opt/fhsim_awesome/playpen/bin. This installation is FhSim with visualization, where your license.lic file is located in the bin folder. The following commands will create an FMU:

cd /opt/fhsim_awesome/playpen/bin
./FhFmuExport \
--input-file /path/to/awesomeness.xml \
--model-name awesomeness \
--author-name "John Doe" \
--description "John's FhSim awesomeness as FMU" \
--include-license-file license.lic

On Windows the same invocation is a cmd session, where the line continuation is ^ and not \:

cd C:\Users\john\fhsim_awesome\playpen\bin
FhFmuExport.exe ^
--input-file path\to\awesomeness.xml ^
--model-name awesomeness ^
--author-name "John Doe" ^
--description "John's FhSim awesomeness as FMU" ^
--include-license-file license.lic

This will produce a folder awesomeness and awesomeness.fmu next to each other in the directory you ran from. The cd chooses where the output lands and nothing else: the playpen the FMU is built from is the one beside the FhFmuExport executable.

Add --fmi-version 3 to get an FMI 3.0 FMU instead. A model needed in both versions is exported twice, under two different --model-name values, because one output directory cannot hold both layouts.

Note
If a SimObject library referenced in awesomeness.xml needs further third-party libraries beyond those FhSim itself brings, those transitive dependencies are not discovered and not bundled; you must add them to the awesomeness folder and re-compress it into an FMU yourself. (Tip: .fmu is really a .zip.) A missing SimObject library is a different matter — that is detected and fails the export with exit code 11, rather than producing a broken FMU.

Running an FhSim FMU in an automated host

An exported FMU defaults to visualization on whenever the renderer was bundled with it. In a CI job, a container, a batch sweep or any host with no display that is not what you want: the FMU tries to open a render window, and ExitInitializationMode fails if it cannot.

Instantiate with visible=false. FMI defines this as "open no windows", and the FMU honours it whatever visualization says: the rendering engine is loaded, no window is opened, and the run proceeds. This works for every FhSim FMU, it needs no special export, and it is what fmpy does by default — see Running an FhSim FMU with fmpy. It is the measure to reach for when you are handed an FMU you did not export yourself.

Setting visualization[0] false is not an alternative for an FMU that bundles the renderer. The exporter writes the variable as a constant there, so an importer refuses the write; the reason is that such an archive carries only the Vis-suffixed SimObject modules, and the engine a false would select cannot resolve them. Where the renderer was not bundled — --no-bundled-vis, or an install without fhVisDll — the variable is a settable fixed parameter whose start value is already false, so a host that sets nothing runs headless anyway.

Running an FhSim FMU with fmpy

fmpy is a Python FMI host that reads the model description, resolves the whole function set and runs the co-simulation lifecycle itself, so it is a good first check that an exported FMU is well formed and gives the right numbers. This walkthrough uses the FirstOrder model enumerated in Variables, names and value references, whose solution is known in closed form: with A = B = 1 and the input held at its start value u = 2, the output is

\[ y(t) = u \left( 1 - e^{-t} \right). \]

Install fmpy (pip install fmpy) and export the model, once per FMI version:

./FhFmuExport --input-file FirstOrder_in.xml --model-name FirstOrderTest --fmi-version 2
./FhFmuExport --input-file FirstOrder_in.xml --model-name FirstOrderTest3 --fmi-version 3

First, validate. validate_fmu returns a list of problems and an empty list means fmpy found nothing to complain about:

python -c "from fmpy.validation import validate_fmu; print(validate_fmu('FirstOrderTest.fmu'))"
[]

Then simulate. fmpy instantiates with visible=False, which is what keeps the run headless; no start_values entry is needed for it. Where the FMU was exported without the renderer, visualization[0] is a settable parameter and is already false.

import numpy as np
from fmpy import simulate_fmu
res = simulate_fmu('FirstOrderTest.fmu',
start_time=0.0, stop_time=1.0, output_interval=0.1)
for row in res:
t, y = float(row['time']), float(row['ExtLink.y[0]'])
print('%.1f %.6f %.6f' % (t, y, 2.0 * (1.0 - np.exp(-t))))

The run prints the FMU's own reminder that it is not inside proxyfmu — expected with a single FhSim FMU in the process — and then the trajectory, which agrees with the closed form to about 1e-7:

[WARNING] not running in proxyfmu, could cause segfaults/crashes due to library conflicts!
0.0 0.000000 0.000000
0.1 0.190325 0.190325
0.2 0.362538 0.362538
0.3 0.518364 0.518364
0.4 0.659360 0.659360
0.5 0.786939 0.786939
0.6 0.902377 0.902377
0.7 1.006829 1.006829
0.8 1.101342 1.101342
0.9 1.186861 1.186861
1.0 1.264241 1.264241

Pointing the same two commands at FirstOrderTest3.fmu gives the same numbers through the FMI 3.0 interface: the model description, the platform directory and the slave module differ, the trajectory does not.

Note the two names you could not have guessed from the model file alone and that the rules in Variables, names and value references give you: visualization[0], the built-in headless switch, and ExtLink.y[0], whose y was an input port name in the FhSim input file.

Remarks
Other hosts worth knowing about: fmusim, the Modelica Association's reference command-line simulator, and the FMI Compliance Checker (fmu-compliance-checker/2.0.4@sintef/stable as a Conan package), which covers FMI 1 and 2.

Running an FhSim FMU with cosim

You can use an FMU simulation engine to run your exported FhSim FMU awesomeness. We recommend Open Simulation Platform Software. For instance, you can use cosim to run awesomeness.fmu.

cosim run-single awesomeness.fmu
Warning
An FhSim FMU may be instantiated only once per process, and the FMU declares as much with canBeInstantiatedOnlyOncePerProcess="true". Running two FhSim FMUs in one co-simulation, or one FhSim FMU with visualization alongside cosim itself, therefore needs each of them in its own process: use proxyfmu in the source attribute for each afflicted Simulator, as below. The cause is not leaking library symbols — the FMU module exports only the fmi2*/fmi3* C entry points and nothing else — but the process-wide state the engine depends on, chiefly the working directory the slave changes while it builds the model. That is a property of the engine, not a packaging accident, so proxyfmu is the answer rather than a workaround.
Running FMU with a system structure definition file

Go ahead an create a OspSystemStructure.xml next to your awesomeness.fmu with the following content:

<OspSystemStructure xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://opensimulationplatform.com/MSMI/OSPSystemStructure" xsi:schemaLocation="http://opensimulationplatform.com/MSMI/OSPSystemStructure ../../../src/cpp/xsd/OspSystemStructure.xsd" version="0.1">
<StartTime>0.0</StartTime>
<Simulators>
<Simulator name="awesome" source="proxyfmu://localhost?file=file:///awesomeness.fmu"/>
</Simulators>
</OspSystemStructure>

Now, run the simulation scenario for 10 seconds:

cosim run . --duration 10

When something goes wrong

Symptoms specific to exporting and running an FhSim FMU — a render window in CI, two FMUs that will not coexist, a refused parameter write, an export that stops with exit code 11 — are catalogued with their causes and fixes in FMU export and co-simulation.