The Functional Mock-Up Interface (FMI) defines an exchange format that allows models, referred to as Functional Mock-Up Unit (FMU), to be shared between tools supporting the standard. In general, an FMU must be implemented in a programming language that can produce binaries that can be called from C, such as C itself or C++. While this allows efficient execution of a simulation, it is a significant limitation when prototyping models.
UniFMU is a command line tool that facilitates the implementation of FMUs in other popular languages that would otherwise not be able to produce C-compatible binaries. It does this by providing a precompiled binary that is C-compatible, which then dispatches calls to the implementation of the model in the target language.
| Specification Version | FMU Interface | Languages | Binaries |
|---|---|---|---|
| FMI3 | Co-Simulation | Python, C#, Java | win64, linux64, darwin64 |
| FMI2 | Co-Simulation | Python, C#, Java | win64, linux64, darwin64 |
| FMI1 | x | x | x |
Examples of generated FMUs can be found in the unifmu_examples repo.
The tool can be downloaded from releases tab of the repository. It is a single executable that bundles all assets used during FMU generation as part of the binary.
Claudio Gomes is currently responsible for tool maintenance. Feel free to reach out for help.
To display the synopsis use the --help flag.
unifmu 0.14.0
Implement Functional Mock-up units (FMUs) in various source languages.
* Source: https://github.com/INTO-CPS-Association/unifmu
* Examples: https://github.com/INTO-CPS-Association/unifmu_examples
USAGE:
unifmu <SUBCOMMAND>
COMMANDS:
generate Create a new FMU using the specified source language
generate-distributed Generates a pair of FMU/private folder for distributed co-simulation, where the FMU works as the proxy and the folder as the model
help Print this message or the help of the given subcommand(s)
OPTIONS:
-h, --help Print help information
-V, --version Print version information
The command uses git-style subcommands.
Help for the individual commands can be inquired by appending the --help after the name of the subcommand.
Help for the command generate:
Create a new FMU using the specified source language
Usage: unifmu generate [OPTIONS] <LANGUAGE> <OUTPATH> [FMU_VERSION]
Arguments:
<LANGUAGE> Source language of the generated FMU [possible values: python, c-sharp, java]
<OUTPATH> Output directory or name of the FMU archive if "--zipped" is passed
[FMU_VERSION] Version of the FMI specification to target [default: fmi2] [possible values: fmi2, fmi3]
Options:
-z, --zipped Compress the generated FMU as a zip-archive and store with '.fmu' extension
-h, --help Print help
The generate command can be used to create a new FMU:
unifmu generate python model
The command generates a placeholder FMU implemented in the specific language. For example the tree below shows the placeholder FMU generated when implementing an FMU in python using UniFMU:
📦model
┣ 📂binaries
┃ ┣ 📂darwin64
┃ ┃ ┗ 📜unifmu.dylib
┃ ┣ 📂linux64
┃ ┃ ┗ 📜unifmu.so
┃ ┗ 📂win64
┃ ┃ ┗ 📜unifmu.dll
┣ 📂resources
┃ ┣ 📂compilation_resources
┃ ┃ ┃ 📜launch_with_pyinstaller.toml
┃ ┃ ┃ 📜unifmu_pyinstaller_build_script.bat
┃ ┃ ┗ 📜unifmu_pyinstaller_build_script.sh
┃ ┣ 📂schemas
┃ ┃ ┃ 📜fmi2_messages_pb2.py
┃ ┃ ┗ 📜unifmu_handshake_pb2.py
┃ ┣ 📜abstract_backend.py
┃ ┣ 📜backend.py
┃ ┣ 📜launch.toml
┃ ┣ 📜main.py
┃ ┣ 📜model.py
┃ ┣ 📜README.md
┃ ┗ 📜requirements.txt
┗ 📜modelDescription.xml
Help for the command generate-distributed:
Generates a pair of FMU/private folder for distributed co-simulation, where the FMU works as the proxy and the folder as the model
Usage: unifmu generate-distributed [OPTIONS] <LANGUAGE> <OUTPATH> [FMU_VERSION]
Arguments:
<LANGUAGE> Source language of the generated FMU [possible values: python, c-sharp, java]
<OUTPATH> Output directory or name of the FMU archive if "--zipped" is passed
[FMU_VERSION] Version of the FMI specification to target [default: fmi2] [possible values: fmi2, fmi3]
Options:
-e, --endpoint <ENDPOINT> IP address of the host running the proxy FMU [default: 127.0.0.1]
-z, --zipped Compress the generated FMU as a zip-archive and store with '.fmu' extension
-b, --black-box-fmu Additional feature to handle when the private model is an existing black-box FMU with '.fmu' extension. In this case, the private backend always uses Python and its inner FMU requires to have the same name as the output directory or name of the FMU archive
-h, --help Print help
The generate-distributed command can be used to create a new pair FMU (proxy) / folder (model) for distributed co-simulations (FMI2 only):
unifmu generate-distributed python model_distributed
The command generates a proxy FMU suffixed with _proxy and a private model folder suffixed with _private in the specific language.
The proxy FMU differs from the FMU created with the generate command as it does not contain the model file and its dependencies.
For example the trees below show the proxy FMU and folder generated when using the generate-distributed command with python as the language:
📦model_distributed_proxy
┣ 📂binaries
┣ 📂resources
┃ ┣ 📜launch.toml
┃ ┗ 📜README.md
┗ 📜modelDescription.xml
whereas its fellow private model folder contains the model file, the dependencies, and the endpoint.toml file for the connection with the proxy FMU, as follows (NOTE: This is not an FMU):
📦model_distributed_private
┣ 📂schemas
┃ ┃ 📜fmi2_messages_pb2.py
┃ ┗ 📜unifmu_handshake_pb2.py
┣ 📜abstract_backend.py
┣ 📜backend.py
┣ 📜endpoint.toml
┣ 📜launch.toml
┣ 📜main.py
┣ 📜model.py
┣ 📜README.md
┗ 📜requirements.txt
In order for the distributed co-simulation to work, the proxy FMU shall be executed first with a co-simulation master algorithm, and then, the private model shall be executed externally, using the IP address provided in endpoint.toml and the port opened by the proxy FMU as an argument (or after executing as a console input) for example (NOTE: The port number is logged by the proxy FMU after initializing it).
To change the default IP address, either run the command generate-distributed with the option --endpoint=IP_ADDRESS or update the endpoint.toml file directly.
In case the model in the private backend is an existing FMU, use the option --black-box-fmu. This will generate a private folder that interacts with the existing FMU using FMPy and UniFMU's existing capabilities. The FMU in the private folder should have the same name of the private folder without the suffix _private. In this example, the FMU should be called model_distributed.fmu and should be located in path/to/model_distributed_private/.
The private backend can be executed using the launch.toml, which defines the command to execute the model; in the case of python, it would look like:
python path/to/model_distributed_private/backend.py PORT_NUMBER
(Note: the argument PORT_NUMBER is used to pass the port of the proxy directly to the process running the private model)
Like the file structure, the workflow for modifying FMUs varies depending on the implementation language.
Depending on the language a README.md is placed in the root of the generated FMU, which serves as documentation for the particular language.
The readme file, as well as other source files for each backend are in the respective folder:
- Python
- Csharp
- Java
- Matlab - This backend is not supported, but documentation is left in case you wish to implement it.
| Name | Supported | Notes |
|---|---|---|
| fmi2GetTypesPlatform | ✓ | |
| fmi2GetVersion | ✓ | |
| fmi2SetDebugLogging | ✓ | |
| fmi2Instantiate | ✓ | |
| fmi2FreeInstance | ✓ | |
| fmi2SetupExperiment | ✓ | |
| fmi2EnterInitializationMode | ✓ | |
| fmi2ExitInitializationMode | ✓ | |
| fmi2Terminate | ✓ | |
| fmi2Reset | ✓ | |
| fmi2GetReal | ✓ | |
| fmi2GetInteger | ✓ | |
| fmi2GetBoolean | ✓ | |
| fmi2GetString | ✓ | |
| fmi2SetReal | ✓ | |
| fmi2SetInteger | ✓ | |
| fmi2SetBoolean | ✓ | |
| fmi2SetString | ✓ | |
| fmi2GetFMUstate | ✓ | |
| fmi2SetFMUstate | ✓ | |
| fmi2FreeFMUstate | ✓ | |
| fmi2SerializedFMUstateSize | ✓ | |
| fmi2SerializeFMUstate | ✓ | |
| fmi2DeSerializeFMUstate | ✓ | |
| fmi2GetDirectionalDerivative | x | |
| fmi2EnterEventMode | x | |
| fmi2NewDiscreteStates | x | |
| fmi2EnterContinuousTimeMode | x | |
| fmi2CompletedIntegratorStep | x | |
| fmi2SetTime | x | |
| fmi2SetContinuousStates | x | |
| fmi2GetDerivatives | x | |
| fmi2GetEventIndicators | x | |
| fmi2GetContinuousStates | x | |
| fmi2GetNominalsOfContinuousStates | x | |
| fmi2SetRealInputDerivatives | x | |
| fmi2GetRealOutputDerivatives | x | |
| fmi2DoStep | ✓ | |
| fmi2CancelStep | x | |
| fmi2GetStatus | x | |
| fmi2GetRealStatus | x | |
| fmi2GetIntegerStatus | x | |
| fmi2GetBooleanStatus | x | |
| fmi2GetStringStatus | x |
| Name | Supported | Notes |
|---|---|---|
| fmi3GetVersion | ✓ | |
| fmi3SetDebugLogging | ✓ | |
| fmi3InstantiateModelExchange | x | |
| fmi3InstantiateCoSimulation | ✓ | |
| fmi3InstantiateScheduledExecution | x | |
| fmi3FreeInstance | ✓ | |
| fmi3EnterInitializationMode | ✓ | |
| fmi3ExitInitializationMode | ✓ | |
| fmi3EnterEventMode | ✓ | |
| fmi3Terminate | ✓ | |
| fmi3Reset | ✓ | |
| fmi3GetFloat32 | ✓ | |
| fmi3GetFloat64 | ✓ | |
| fmi3GetInt8 | ✓ | |
| fmi3GetUInt8 | ✓ | |
| fmi3GetInt16 | ✓ | |
| fmi3GetUInt16 | ✓ | |
| fmi3GetInt32 | ✓ | |
| fmi3GetUInt32 | ✓ | |
| fmi3GetInt64 |
$ claude mcp add unifmu \
-- python -m otcore.mcp_server <graph>