Skip to content

Note

Click here to download the full example code

A follow-up example: mixing an FMU with a Python proxy FMU

This tutorial is a continuation of the "A first example: an AC voltage source and a resistor". The goal here is to show how CoFMPy allows you to mix compiled FMUs and Python proxy FMUs (fmuproxy) in the same co-simulation.

  • The AC voltage source is provided as a compiled FMU (source.fmu)
  • The resistor is defined as a Python proxy FMU (resistor.py)

This setup illustrates a common workflow: you can rapidly prototype some parts of your system in Python (for example to test an AI model or a simple block), while keeping others as standard FMUs.

Downloading and preparing resources

As before, we first download the resources (the FMU and the Python proxy file) from a shared repository and unzip them locally.

import os
import urllib.request
import zipfile

url = "https://share-is.pf.irt-saintexupery.com/s/39zaG9HkQWnePbi/download"
resources_path = "example1.zip"

urllib.request.urlretrieve(url, resources_path)
with zipfile.ZipFile(resources_path, "r") as zip_ref:
    zip_ref.extractall(".")
os.remove(resources_path)

print("Resources unzipped in example1 folder!")
Traceback (most recent call last):
  File "/home/cofri/dev/cofmpy-github/docs/examples/plot_01_fmu_proxy.py", line 54, in <module>
    urllib.request.urlretrieve(url, resources_path)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 214, in urlretrieve
    with contextlib.closing(urlopen(url, data)) as fp:
                            ~~~~~~~^^^^^^^^^^^
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 189, in urlopen
    return opener.open(url, data, timeout)
           ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 489, in open
    response = self._open(req, data)
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 506, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
                              '_open', req)
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 466, in _call_chain
    result = func(*args)
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 1367, in https_open
    return self.do_open(http.client.HTTPSConnection, req,
           ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                        context=self._context)
                        ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/cofri/.local/share/uv/python/cpython-3.13.2-linux-x86_64-gnu/lib/python3.13/urllib/request.py", line 1322, in do_open
    raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1028)>

Creating the Coordinator

As in the previous tutorial, the base object is the Coordinator. It manages the master algorithm, the FMUs, the proxies, the data handlers, etc.

from cofmpy import Coordinator

coordinator = Coordinator()

The JSON configuration file

The configuration file describes the system: - one FMU (source.fmu) for the AC voltage source - one Python proxy (resistor.py) implementing a resistor - the connection between the source output and the resistor input

Let’s open the configuration file to inspect it.

config_path = "example1/config_with_fmu_proxy.json"
with open(config_path, "r") as f:
    print(f.read())

Starting the simulation system

The coordinator loads the FMUs and proxies, starts the services, and prepares the simulation.

coordinator.start(config_path)

Once loaded, we can inspect the FMUs/proxies and the cosimulation method used.

print("FMUs and proxies in Master:", list(coordinator.master.fmu_handlers.keys()))
print(f"Cosimulation method: {coordinator.master.cosim_method}")

Running the simulation step by step

Just like before, we can step through the simulation manually.

print(f"Initial simulation time: {coordinator.master.current_time}")

time_step = 0.01
coordinator.do_step(time_step)

print(f"Simulation time after one step: {coordinator.master.current_time}")

# Run N steps
N = 100
for _ in range(N):
    coordinator.do_step(time_step)

print(f"Simulation time after {N+1} steps: {coordinator.master.current_time:.2f}")

Running the full simulation

Alternatively, we can run until a specific end time in one command.

coordinator.save_results("simulation_results.csv")

Visualizing results

Results are stored in a CSV file. We can load them into pandas and plot.

import pandas as pd

results = pd.read_csv("simulation_results.csv")
print(results.head(10))
# Results can be accessible directly in the Master object or in the CSV file we just
# saved.
results.plot(x="time", grid=True)

Conclusion

This example shows how easy it is to integrate a Python proxy FMU (fmuproxy) alongside compiled FMUs in CoFMPy.

This workflow is ideal when: - you want to test new logic (e.g., AI model) quickly in Python - you don’t want to package everything as an FMU yet - you still need interoperability with other FMUs

Later, the Python proxy can be exported as a true FMU (using PythonFMU for example), making the system fully portable across different tools.

Total running time of the script: ( 0 minutes 0.079 seconds)

Download Python source code: plot_01_fmu_proxy.py

Download Jupyter notebook: plot_01_fmu_proxy.ipynb

Gallery generated by mkdocs-gallery