Note
Click here to download the full example code
A first example: an AC voltage source and a resistor¶
This is a simple example of how to use CoFMPy to load a co-simulation system (JSON configuration file and FMUs) and run the simulation.
The use case is a simple system with an AC voltage source and a resistor. The AC voltage source generates a sinusoidal voltage signal, and the resistor consumes the power from the source. The resistor has a variable resistance that can be changed during the simulation.

We will first download all necessary resources such as the FMUs (source and resistor) and the configuration file from the public link provided below.
import os
import urllib.request
import zipfile
url = "https://share-is.pf.irt-saintexupery.com/s/39zaG9HkQWnePbi/download"
# Local path to resources folder
resources_path = "example1.zip"
# Download and unzip the file
urllib.request.urlretrieve(url, resources_path)
with zipfile.ZipFile(resources_path, "r") as zip_ref:
zip_ref.extractall(".")
# Remove the zip file
os.remove(resources_path)
print("Resources unzipped in example1 folder!")
Traceback (most recent call last):
File "/home/cofri/dev/cofmpy-github/docs/examples/plot_00_get_started.py", line 53, 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)>
Now that we have all the necessary resources, we can start the example.
The base object in CoFMPy is the Coordinator. It manages all the components of CoFMPy: the Master algorithm, the graph engine, the data stream handlers, etc. In this tutorial, we only deal with the Coordinator that communicates automatically with the different components.
We will first import the Coordinator object from CoFMPy and create an instance of it.
from cofmpy import Coordinator
coordinator = Coordinator()
The JSON configuration file¶
The first step is to create the JSON configuration file based on your simulation system. This file must contain the information about the FMUs, the connections between them, and the simulation settings. For more information, check the page on how to create a JSON configuration file, see this page. The system also requires input data to run the simulation (here, the variable resistor from a CSV file).
Here is the content of the configuration file for this example:
config_path = "example1/config_with_csv.json"
with open(config_path, "r") as f:
print(f.read())
In the JSON configuration file, you can see the following information:
- The 2 FMUs used in the system: an AC voltage source and a resistor
- 2 connections:
- the output of the source is connected to the input of the resistor
- the resistance value of the resistor is set by a CSV file
- The simulation settings: the cosimulation method and the edge separator (used in the graph visualization).
The next step is to load the configuration file via the Coordinator. This will start the multiple components to handle the whole simulation process:
- the Master: the main process that controls the co-simulation
- the data stream handlers: the objects that read and write data from/to the system
- the graph engine
coordinator.start(config_path)
You can access the attributes of the components of the Coordinator object. For
example, you can access the co-simulation method via the
master.cosim_method attribute.
# We can check the list of FMUs in the Master or the cosimulation method used
print("FMUs in Master:", list(coordinator.master.fmu_handlers.keys()))
print(f"Cosimulation method: {coordinator.master.cosim_method}")
# ... and the stream handlers (here, the CSV source). Keys are (fmu_name, var_name)
print("\nCSV data stream handler key:", list(coordinator.stream_handlers.keys())[0])
csv_data_handler = coordinator.stream_handlers[("resistor", "R")]
print("CSV path for resistance value R:", csv_data_handler.path)
print("CSV data for R (as Pandas dataframe):\n", csv_data_handler.data.head())
You can also visualize the graph of the system using the plot_graph method. This
method will plot the connections between the FMUs in the system.
coordinator.graph_engine.plot_graph()
Running the simulation¶
After loading the configuration file, you can run the simulation by calling the
do_step method.
This method will run the simulation for a given time step via the Master algorithm.
The do_step method will save the results in the data storages defined in the
configuration file. You can access the data storages using the data_storages
attribute of the Coordinator object. By default, a data storage for all outputs is
created in the "storage/results.csv" file (see below).
print(f"Current time of the co-simulation: {coordinator.master.current_time}")
time_step = 0.01
coordinator.do_step(time_step)
print(
"Current time of the co-simulation after one step: "
f"{coordinator.master.current_time}"
)
# Run N steps
N = 100
for _ in range(N):
coordinator.do_step(time_step)
print(
f"Current time of the co-simulation after {N+1} steps: "
f"{coordinator.master.current_time:.2f}"
)
It is possible to run the simulation until a specific end time by using the
run_simulation
method. This
method will run the simulation until the end time and return the results of the
simulation. Note that you should recreate a new Coordinator from scratch. It is not
possible to mix both do_step and run_simulation methods in the same Coordinator
object.
At the end of the simulation, you can also manually save results to a CSV file:
coordinator.save_results("simulation_results.csv")
Visualizing the results¶
Results can be accessible directly in the Master object or in the CSV file we just saved.
import pandas as pd
results = pd.read_csv("simulation_results.csv")
print(results.head(10))
results.plot(x="time", grid=True)
We can observe that the change of resistance value at t = 0.42s effectively changes the current \(I = U/R\) flowing through the resistor.
Total running time of the script: ( 0 minutes 0.126 seconds)
Download Python source code: plot_00_get_started.py