PyFMU is a library that allows FMUs to be implemented using in Python 3. Its goal is to enable rapid prototyping of a wide of FMUs for a wide range of use cases.
Its highligts include:
- Supports FMI 2.0.
- Write FMUs in high-level langauge.
- Use the extensive collection of standard and third party libraries.
- Model description automatically generated.
- Model can be changed without re-compilation.
PyFMU is still work in progress. Only Linux is supported currently.
The project consists of two parts:
- C/C++ shared library which acts which serves as an 'wrapper' around the Python code.
- Set of tools for generating and exporting projects.
The wrapper is build using CMake as follows:
mkdir build
cd build
cmake ..
This will download external dependencies and try to locate Python 3 development headers. Building the wrapper automatically copies it to the resources of the PyFMU tool.
To install the tools from source pip can be invoked from the root dir.
pip install -e.
The library comes with a command line tool py2fmu which eases the creation of projects and the subsequent export as FMUs
As an example a two input Adder is used.
To create a new project the generate command is used:
python py2fmu.py generate --path /someDir/Adder
This generates a empty project containing the necessary resources and configuration files. By default a template of the main class is generated. In this case a file adder.py defining the class Adder is created.
This subclasses the Fmi2Slave class which provides the methods necessary to define the FMU.
class Adder(Fmi2Slave):
def __init__(self):
# programatically register variables here
...
def do_step(self, current_time: float, step_size: float) -> bool:
return True
...
To define inputs, outputs and parameters the register_variable function is used. For a two input adder the inputs and outputs can be specified as:
def __init__(self):
...
self.register_variable("s", data_type=Fmi2DataTypes.real, causality=Fmi2Causality.output)
self.register_variable("a", data_type=Fmi2DataTypes.real, causality=Fmi2Causality.input, start=0)
self.register_variable("b", data_type=Fmi2DataTypes.real, causality=Fmi2Causality.input, start=0)
Note that the variables MUST be defined either in the __init__ function or as part of a call chain resulting from it. This requirement is related to how model descriptions are extracted.
To implement the dynamics of the FMU the functions of the baseclass must be overwritten. For the adder we define the do_step and exit_initialization_mode of the Adder class.
def exit_initialization_mode(self):
self.s = self.a + self.b
return True
def do_step(self, current_time: float, step_size: float) -> bool:
self.s = self.a + self.b
return True
It is not necessary to implement the set_xxx and get_xxx functions. By default these are mapped directly to instance variables.
To export an project as an FMU the export subcommand is used:
python py2fmu.py export --project /someDir/Adder --output /myFMUs/Adder
The result of this command is an FMU containing the Python that was just written.
Under the hood a few things happen:
- Wrapper is copied to binaries
- Resources are copied into the archive
- A model description is generated.
The model description for the adder project looks like:
<?xml version="1.0" ?>
<fmiModelDescription author="" fmiVersion="2.0" generationDateAndTime="2020-02-23T09:30:00Z" generationTool="pyfmu" guid="221df7a6-36d3-41f7-bc35-8489663bb7ae" modelName="Adder" variableNamingConvention="structured">
<CoSimulation modelIdentifier="pyfmu" needsExecutionTool="true"/>
<ModelVariables>
<!--Index of variable = "1"-->
<ScalarVariable causality="output" initial="calculated" name="s" valueReference="0" variability="continuous">
<Real/>
</ScalarVariable>
<!--Index of variable = "2"-->
<ScalarVariable causality="input" name="a" valueReference="1" variability="continuous">
<Real start="0"/>
</ScalarVariable>
<!--Index of variable = "3"-->
<ScalarVariable causality="input" name="b" valueReference="2" variability="continuous">
<Real start="0"/>
</ScalarVariable>
</ModelVariables>
<ModelStructure>
<Outputs>
<Unknown dependencies="" index="1"/>
</Outputs>
<InitialUnknowns>
<Unknown dependencies="" index="1"/>
</InitialUnknowns>
</ModelStructure>
</fmiModelDescription>
See the tests/examples/projects folder.
- Lars Ivar Hatledal: For his implementation of PythonFMU which was the initial starting point for PyFMU.