Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 23 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ This project is a fork of the original PythonFMU repository available at https:/

### Support:

Please take a look at the examples to see the supported features.
We currently support both the Model exchange and Cosimulation Interfaces.
Both the Model Exchange and Co-simulation interfaces are supported, with Float64, Int32,
Int64, UInt64, Boolean, String and Enumeration variables (plus NumPy arrays of the numeric
types). See the [documentation](https://pythonfmu3.readthedocs.io/) for the full list of
supported features and current limitations, and the `examples` folder for more models.

See the [changelog](docs/changelog.md) for what changed in each release.

Expand Down Expand Up @@ -46,35 +48,37 @@ pip install pythonfmu3
```

2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
Keep a single FMU class per script.
3. Run `pythonfmu3 build` to create the fmu.

```
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS_FILE] [--no-external-tool]
[--no-variable-step] [--interpolate-inputs] [--only-one-per-process] [--handle-state]
[--serialize-state] [--use-memory-management]
[Project files [Project files ...]]
usage: pythonfmu3 build [-h] -f SCRIPT_FILE [-d DEST] [--doc DOCUMENTATION_FOLDER] [--terminals TERMINALS]
[--no-external-tool] [--only-one-per-process] [--handle-state] [--serialize-state]
[--no-variable-step] [--needs-completed-step]
[Project files ...]

Build an FMU from a Python script.

positional arguments:
Project files Additional project files required by the Python script.

optional arguments:
options:
-h, --help show this help message and exit
-f SCRIPT_FILE, --file SCRIPT_FILE
Path to the Python script.
-d DEST, --dest DEST Where to save the FMU.
--doc DOCUMENTATION_FOLDER
Documentation folder to include in the FMU.
--terminals TERMINALS_FILE
--terminals TERMINALS
Terminals file (terminalsAndIcons.xml) to include in the FMU.
--no-external-tool If given, needsExecutionTool=false
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
--interpolate-inputs If given, canInterpolateInputs=true
--only-one-per-process
If given, canBeInstantiatedOnlyOncePerProcess=true
--handle-state If given, canGetAndSetFMUstate=true
--serialize-state If given, canSerializeFMUstate=true
--handle-state If given, canGetAndSetFMUState=true
--serialize-state If given, canSerializeFMUState=true
--no-variable-step If given, canHandleVariableCommunicationStepSize=false
--needs-completed-step
If given, needsCompletedIntegratorStep=true
```

### How do I build an FMU from python code with third-party dependencies?
Expand All @@ -84,7 +88,7 @@ _PythonFMU_ does not package a full environment within the FMU.
However, you can package a `requirements.txt` or `environment.yml` file within your FMU following these steps:

1. Install _pythonfmu_ package: `pip install pythonfmu3`
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see the examples below).
3. Create a `requirements.txt` file (to use _pip_ manager) and/or a `environment.yml` file (to use _conda_ manager) that defines your dependencies.
4. Run `pythonfmu3 build -f myscript.py requirements.txt` to create the fmu including the dependencies file.

Expand Down Expand Up @@ -126,6 +130,8 @@ class PythonSlave(Fmi3Slave):
self.author = "John Doe"
self.description = "A simple description"

# Create attributes before registering them: register_variable only adds a setter
# (needed for inputs and parameters) for attributes that already exist.
self.time = 0.0
self.intOut = 1
self.realOut = 3.0
Expand Down Expand Up @@ -165,7 +171,7 @@ To create a model exchange FMU you must inherit from `Fmi3SlaveBase` and one of

```python

from pythonfmu3 import Fmi3Causality, Fmi3SlaveBase, Float64, ModelExchange
from pythonfmu3 import Fmi3Causality, Fmi3Initial, Fmi3SlaveBase, Fmi3Variability, Float64, ModelExchange
from typing import List


Expand Down Expand Up @@ -200,7 +206,9 @@ class PythonSlaveMX(Fmi3SlaveBase, ModelExchange):

```

Note: There is a hard requirement that we have a `self.time` member in the derived class.
Note: Model Exchange classes must define a `self.time` member. Derivative variables give the
value reference of their state with `derivative=`; value references are assigned in
registration order starting at 0 (here `time` = 0, `x0` = 1, `x1` = 2).

#### Create the FMU

Expand Down
12 changes: 12 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
`resources/pythonfmu_libpython.txt` records the building interpreter's Python library.
- The builder finds the model class by importing the script, so class definitions whose
base classes span several lines work. A script must define a single FMU class.
- `Fmi3UpdateDiscreteStatesResult` fields renamed to the FMI names
`discreteStatesNeedUpdate` and `nominalContinuousStatesChanged`. The old names
(`discreteStateNeedsUpdate`, `nominalsOfContinuousStatesChanged`) still work as aliases.
- Python 3.9 or later is required; the package is built from `pyproject.toml`.

### Fixed
- Crash (use-after-free) when freeing an instance after a Python exception.
Expand All @@ -30,3 +34,11 @@
building from Python (e.g. `canHandleVariableCommunicationStepSize` came out `false`).
- `--terminals` packed the wrong file as `terminalsAndIcons.xml`.
- Array variable setters on NumPy 2.5 or later.
- `discreteStatesNeedUpdate` and `nominalContinuousStatesChanged` returned from
`update_discrete_states` were ignored because of the field-name mismatch above.
- FMUs built on Apple Silicon and ARM Linux use the correct platform folders
(`aarch64-darwin`, `aarch64-linux`).
- Documentation: corrected the PID controller and quick-start examples (their inputs and
parameters could not be set), the `pythonfmu3 build` options, the list of supported
types, and added a limitations section. Documentation examples are now checked to
produce working models in the test suite.
2 changes: 1 addition & 1 deletion docs/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ _PythonFMU3_ does not package a full environment within the FMU.
However, you can package a `requirements.txt` or `environment.yml` file within your FMU following these steps:

1. Install _pythonfmu3_ package: `pip install pythonfmu3`
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see below for an example).
2. Create a new class extending the `Fmi3Slave` class declared in the `pythonfmu3.fmi3slave` module (see the [Co-simulation examples](usage.md)).
3. Create a `requirements.txt` file (to use _pip_ manager) and/or a `environment.yml` file (to use _conda_ manager) that defines your dependencies.
4. Run `pythonfmu3 build -f myscript.py requirements.txt` to create the fmu including the dependencies file.

Expand Down
77 changes: 47 additions & 30 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ PythonFMU3 bridges the gap between Python's rich ecosystem and the Functional Mo
- **🔄 Dual FMU Support**: Create both Co-simulation and Model Exchange FMUs
- **🐍 Pure Python**: Write your models in familiar Python syntax
- **📦 Easy Packaging**: Simple CLI tool to build FMUs from Python scripts
- **🔧 Rich Variable Types**: Support for Float64, Int32, Boolean, String, and arrays
- **⚡ High Performance**: Optimized C++ runtime with embedded Python interpreter
- **🌐 Cross-Platform**: Windows, Linux, and macOS support
- **🔧 Variable Types**: Float64, Int32, Int64, UInt64, Boolean, String and Enumeration, plus NumPy arrays of the numeric types
- **⚡ Native FMU binaries**: a C++ FMI 3.0 implementation that runs your Python code in an embedded interpreter
- **🌐 Cross-Platform**: prebuilt for Windows (x86_64) and Linux (x86_64); macOS and ARM build from source
- **📊 NumPy Integration**: Native support for array operations and scientific computing
- **🎛️ Parameter Management**: Configurable parameters with units and constraints
- **🔍 Debugging Support**: Built-in logging and error handling
Expand Down Expand Up @@ -60,13 +60,14 @@ class MyModel(Fmi3Slave):
def __init__(self, **kwargs):
super().__init__(**kwargs)

# Register variables
self.register_variable(Float64("input", causality=Fmi3Causality.input))
self.register_variable(Float64("output", causality=Fmi3Causality.output))

# Initialize values
# Create the attributes first: register_variable only adds a setter
# (needed for inputs and parameters) for attributes that already exist
self.input = 0.0
self.output = 0.0

# Register variables
self.register_variable(Float64("input", causality=Fmi3Causality.input, start=0.0))
self.register_variable(Float64("output", causality=Fmi3Causality.output))

def do_step(self, current_time, step_size):
# Your model logic here
Expand All @@ -80,6 +81,7 @@ pythonfmu3 build -f my_model.py
```

**3. Use in simulation:**
<!-- skip-test -->
```python
import fmpy
result = fmpy.simulate_fmu('MyModel.fmu', stop_time=10.0)
Expand All @@ -91,15 +93,17 @@ packages; see [Running FMUs outside Python](install.md#running-fmus-outside-pyth

## Architecture

PythonFMU3 uses a hybrid architecture combining the performance of C++ with the flexibility of Python:
An FMU built by PythonFMU3 contains your script, a copy of the `pythonfmu3` package and a
native library implementing the FMI 3.0 C API:

- **C++ Runtime**: High-performance FMI interface implementation
- **Embedded Python**: Isolated Python interpreter for model execution
- **C++ wrapper**: implements the FMI functions and forwards each call to your Python
class (`do_step`, the variable getters and setters, ...)
- **Python interpreter**: the host's interpreter when the importer is itself Python (e.g.
FMPy), otherwise one started inside the simulation process. Your model's packages must
be installed in that Python; see [Dependencies](dependencies.md)
- **Python loader (Linux/macOS)**: the FMU's library has no link-time dependency on
Python. It uses the host's interpreter when there is one, and otherwise loads
`libpython` on first instantiation, so native (non-Python) importers can load the FMU
- **Smart Bridging**: Efficient data exchange between C++ and Python
- **Memory Management**: Automatic resource cleanup and garbage collection
Python, and loads `libpython` on first instantiation when the host has none, so native
(non-Python) importers can load the FMU

This project is a fork of the original PythonFMU repository available at https://github.com/NTNU-IHB/PythonFMU, which was used as the basis for adding support for FMI 3.0. While we have made efforts to expand the functionality of this project, it currently has some limitations and does not support all the features of FMI 3.0. We would like to acknowledge and give credit to the original PythonFMU project for their contributions to this work.

Expand All @@ -108,40 +112,53 @@ This project is a fork of the original PythonFMU repository available at https:/
### FMI 3.0 Features
- ✅ **Co-simulation FMUs** with `do_step()` method
- ✅ **Model Exchange FMUs** with derivative functions
- ✅ **Variable types**: Float64, Int32, UInt64, Boolean, String
- ✅ **Array variables** with dimensions and structural parameters
- ✅ **Variable types**: Float64, Int32, Int64, UInt64, Boolean, String, Enumeration
- ✅ **Array variables** (NumPy) of the numeric and Boolean types, with fixed sizes or
sizes given by a structural parameter
- ✅ **Parameters** with fixed, tunable, and constant variability
- ✅ **Units and display units** for physical quantities
- ✅ **Units** for physical quantities
- ✅ **Initial values** and causality declarations
- ✅ **Event handling** and discrete state updates
- ✅ **State serialization** and deserialization
- ✅ **Logging** and error reporting
- ✅ **Event indicators** and discrete state updates (Model Exchange)
- ✅ **Getting, setting and serializing FMU state** (opt-in with `--handle-state` and
`--serialize-state`; see the limitations below)
- ✅ **Logging**, with Python exceptions reported with their traceback

### Advanced Capabilities
- ✅ **NumPy integration** for scientific computing
- ✅ **Custom getters/setters** for complex variable access
- ✅ **Nested variables** with structured naming
- ✅ **Resource management** for external files and dependencies
- ✅ **Multi-platform support** (Windows, Linux, macOS)
- ✅ **Runs in any FMI 3.0 importer**: Python-based (FMPy) and native (OMSimulator,
`fmusim`, Simulink, ...)
- ✅ **Memory-efficient** variable handling

### Limitations
- ⚠️ **Binary variables** not yet supported
- ⚠️ **Clock variables** and scheduled execution not implemented
- ⚠️ **Some FMI 3.0 advanced features** are still in development
- ⚠️ **Variable types** Float32, Int8, Int16, UInt8, UInt16, UInt32, Binary and Clock are not
supported, nor are String arrays
- ⚠️ **Scheduled Execution** is not supported, and Co-simulation ignores Event Mode and
early return
- ⚠️ **Directional and adjoint derivatives** are not available
- ⚠️ **Array sizes** are fixed once the FMU is instantiated: changing a structural
parameter in Configuration Mode does not resize arrays
- ⚠️ **FMU state** only covers registered variables, and serialization uses JSON (no NumPy
arrays). Override `_get_fmu_state`/`_set_fmu_state` if your model has other internal state
- ⚠️ **Prebuilt binaries** exist for Windows x86_64 and Linux x86_64 only; on macOS and ARM,
build the wrapper from source (see [Installation](install.md))

## Examples and Documentation

Explore comprehensive examples and documentation:

- **[Co-simulation Examples](usage.md)**: PID controllers, filters, state machines
- **[Model Exchange Examples](usageMX.md)**: Van der Pol oscillator, Robertson problem
- **[API Reference](api.md)**: Complete class and method documentation
- **[Installation Guide](install.md)**: Setup instructions for all platforms
- **[Co-simulation Examples](usage.md)**: PID controller, units, arrays, step results
- **[Model Exchange Examples](usageMX.md)**: Van der Pol oscillator, bouncing ball with events
- **[Installation Guide](install.md)**: installing, building from source, and running FMUs
outside Python
- **[Dependencies](dependencies.md)**: third-party packages and which Python an FMU uses
- **[Changelog](changelog.md)**

## Community and Support

- 🐛 **Bug Reports**: [GitHub Issues](https://github.com/StephenSmith25/PythonFMU3/issues)
- 💡 **Feature Requests**: [GitHub Discussions](https://github.com/StephenSmith25/PythonFMU3/discussions)
- 💡 **Feature Requests**: [GitHub Issues](https://github.com/StephenSmith25/PythonFMU3/issues)
- 📖 **Documentation**: [Read the Docs](https://pythonfmu3.readthedocs.io/)
- 📦 **PyPI Package**: [pythonfmu3](https://pypi.org/project/pythonfmu3/)
16 changes: 11 additions & 5 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,19 @@ Alternatively, the project can installed from source. First clone the repo,
git clone https://github.com/StephenSmith25/PythonFMU3.git
```

Next build the C++ code,
Next build the C++ wrapper. You need CMake (3.10 or later), a C++17 compiler and the
Python development headers (e.g. `python3-dev` on Debian/Ubuntu). The library is written
to `pythonfmu3/resources/binaries/<platform>/`.

```bash
mkdir build
cd build
cmake -S ../pythonfmu3/pythonfmu-export/
make
cd PythonFMU3
cmake -S pythonfmu3/pythonfmu-export -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
```

On Windows, add `-A x64` to the first command for a 64-bit build. Prebuilt libraries in the
PyPI package cover Windows x86_64 and Linux x86_64; on macOS (Intel or Apple Silicon) and
ARM Linux, install from source this way.
Next the python project may be installed with either,

```bash
Expand Down
Loading
Loading