Model Exchange Examples
Model Exchange FMUs are used for continuous-time simulation where an external solver integrates the system of differential equations. The FMU provides the derivative function and state information, while the master algorithm handles time stepping and numerical integration.
Key Concepts
Model Exchange vs Co-Simulation
- Model Exchange: External solver calls your derivative function at each integration step
- Co-Simulation: Your FMU manages its own time stepping with
do_step()
Required Methods
For Model Exchange FMUs, you must implement:
- get_continuous_state_derivatives(): Returns derivatives of continuous states
- get_nominals_of_continuous_states(): Returns nominal values for scaling
- Optional: get_event_indicators(), update_discrete_states()
Key Implementation Details
Variable Registration
Model Exchange FMUs require specific variable declarations:
# Independent variable (time)
self.register_variable(Float64("time",
causality=Fmi3Causality.independent,
variability=Fmi3Variability.continuous))
# States with initial values
self.register_variable(Float64("x0",
causality=Fmi3Causality.output,
start=2.0, # Initial value
variability=Fmi3Variability.continuous,
initial=Fmi3Initial.exact)) # Must be exact
# Derivatives (local variables)
self.register_variable(Float64("derx0",
causality=Fmi3Causality.local,
variability=Fmi3Variability.continuous,
derivative=1)) # Derivative of variable 1 (x0)
Derivative Function
The core of Model Exchange is the derivative function:
def get_continuous_state_derivatives(self):
"""Calculate derivatives of continuous states"""
# Van der Pol oscillator equations:
# dx0/dt = x1
# dx1/dt = μ(1-x0²)x1 - x0
self.derx0 = self.x1
self.derx1 = self.mu * ((1 - self.x0**2) * self.x1) - self.x0
return [self.derx0, self.derx1]
Example
from pythonfmu3 import Fmi3Causality, ModelExchange, Fmi3Variability, Fmi3SlaveBase, Fmi3Status, Float64, Fmi3Initial, Unit, Float64Type, Fmi3StepResult
from typing import List
class VanDerPol(Fmi3SlaveBase, ModelExchange):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.author = ""
self.description = "Van Der Pol oscillator problem for model exchange FMUs"
self.time = 0.0
self.mu = 1.0
self.x0 = 2
self.x1 = 0
self.derx0 = 0.0
self.derx1 = 0.0
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
self.register_variable(Float64("x0", causality=Fmi3Causality.output, start=2, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
self.register_variable(Float64("x1", causality=Fmi3Causality.output, start=0, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
self.register_variable(Float64("derx0", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1))
self.register_variable(Float64("derx1", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=2))
self.register_variable(Float64("mu", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed))
def get_continuous_state_derivatives(self) -> List[float]:
self.derx0 = self.x1
self.derx1 = self.mu * ((1 - self.x0**2) * self.x1) - self.x0
return [self.derx0, self.derx1]
def get_nominals_of_continuous_states(self, nStates: int) -> List[float]:
return [1.0, 1.0]
Build the FMU
Once your Python script is ready, build the FMU:
# Using the CLI
pythonfmu3 build -f van_der_pol.py
Usage Example
The generated FMU can be used with any FMI-compatible tool:
# Example with FMPy
import fmpy
# Load and simulate the FMU
fmu_filename = "VanDerPol.fmu"
result = fmpy.simulate_fmu(
fmu_filename,
stop_time=20.0,
step_size=0.01,
start_values={'mu': 1.0, 'x0': 2.0, 'x1': 0.0}
)
# Plot results
import matplotlib.pyplot as plt
plt.plot(result['time'], result['x0'], label='x0')
plt.plot(result['time'], result['x1'], label='x1')
plt.xlabel('Time')
plt.ylabel('State Values')
plt.legend()
plt.grid(True)
plt.show()
Advanced Features
Event Handling
For systems with discrete events. Firstly, mark the event indicators in register variable with has_event_indicator,
self.register_variable(Float64("h", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact), has_event_indicator=True)
Then define the event methods, get_event_indicators() and update_discrete_states.
def get_event_indicators(self):
return [0.2]
def update_discrete_states(self):
"""Handle discrete state updates after events"""
fdsr = Fmi3UpdateDiscreteStatesResult()
fdsr.valuesOfContinuousStatesChanged = True
Example
from pythonfmu3 import Fmi3Causality, Fmi3Variability, Fmi3SlaveBase, ModelExchange, Float64, Fmi3Initial, Fmi3UpdateDiscreteStatesResult
from typing import List
import sys
EVENT_EPS = 1e-12
class BouncingBall(Fmi3SlaveBase, ModelExchange):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.author = "..."
self.description = "Bouncing Ball"
self.time = 0.0
self.h = 1.0
self.v = 0.0
self.derh = 0.0
self.derv = 0.0
self.g = -9.81
self.e = 0.7
self.v_min = 0.1
self.register_variable(Float64("time", causality=Fmi3Causality.independent, variability=Fmi3Variability.continuous))
self.register_variable(Float64("h", causality=Fmi3Causality.output, start=1, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact),
has_event_indicator=True)
self.register_variable(Float64("derh", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=1))
self.register_variable(Float64("v", causality=Fmi3Causality.output, start=0, variability=Fmi3Variability.continuous, initial=Fmi3Initial.exact))
self.register_variable(Float64("derv", causality=Fmi3Causality.local, variability=Fmi3Variability.continuous, derivative=3))
self.register_variable(Float64("g", causality=Fmi3Causality.parameter, variability=Fmi3Variability.fixed))
self.register_variable(Float64("e", causality=Fmi3Causality.parameter, variability=Fmi3Variability.tunable))
self.register_variable(Float64("v_min", variability=Fmi3Variability.constant, start=0.1))
def get_continuous_state_derivatives(self) -> List[float]:
self.derh = self.v
self.derv = self.g
return [self.derh, self.derv]
def get_event_indicators(self) -> List[float]:
z = [self.h]
if self.h > -EVENT_EPS and self.h <=0 and self.v > 0:
z[0] = -EVENT_EPS
return z
def update_discrete_states(self):
fdsr = Fmi3UpdateDiscreteStatesResult()
if self.h <= 0 and self.v < 0:
self.h = sys.float_info.min
self.v = -self.v * self.e
if self.v < self.v_min:
self.v = 0.0;
self.g = 0.0;
fdsr.valuesOfContinuousStatesChanged = True
return fdsr
Build the FMU
Once your Python script is ready, build the FMU:
# Using the CLI
pythonfmu3 build -f bouncing_ball.py