Simulation#
SOFIRpy allows you to co-simulate multiple FMUs and custom implemented python models.
Arguments to the simulate Function#
The function simulate accepts the
following arguments:
stop_time#
Define the stop_time in seconds.
stop_time = 10 # 10 seconds of simulation will be performed
step_size#
Define the step_size in seconds. Needs to be greater than 0 and smaller than the stop_time.
step_size = 1 # starting from 0, each second a step in each model is performed until the stop_time is reached.
fmu_paths#
Define which FMUs should be simulated by passing a dictionary with the name of the fmu and the file path as value. The name of the FMUs can be chosen arbitrarily, but each name must occur only once.
# simulate one FMU named DC_Motor
fmu_paths = {"<fmu_name>": "<Path/to/fmu>"}
model_classes#
Define the custom implemented python models that should be simulated by passing a
dictionary with the name of the python model and the model as value.
The python models are classes that inherit from the abstract class
SimulationEntity.
This class defines 3 abstract methods that must be implemented by your custom python
models:
SimulationEntity.do_step(time):Define how your models performs a simulation step.
SimulationEntity.set_parameters(parameter_name, parameter_value):Define how the parameters of your models are set.
SimulationEntity.get_parameter_value(parameter_name):Define how to get the value of a parameter of your model.
Additionally 3 optional method can be implemented:
SimulationEntity.initialize(start_values):Define how to set the start values of your model before the simulation starts.
SimulationEntity.get_unit(parameter_name):Define how to get the unit of a parameter.
SimulationEntity.conclude_simulation():Define functionalities that your model should perform after the simulation has finished.
Example:
from sofirpy import SimulationEntity
# First define the class
class Foo(SimulationEntity):
def __init__(self):
self.parameters = {"parameter1": 0, "parameter2": 0}
self.units = {"parameter1": "m", "parameter2": "V"}
self.parameter_dtypes = {"parameter1": float, "parameter2": int}
def do_step(self, time): # mandatory method
self.parameter["parameter1"] += time/100
self.parameter["parameter2"] += 1
def set_parameter(
self, parameter_name, parameter_value
): # mandatory method
self.parameters[parameter_name] = parameter_value
def get_parameter_value(self, output_name): # mandatory method
return self.parameters[output_name]
def initialize(self, start_values): # optional
for name, value in start_values.items():
self.parameters[name] = value
def get_unit(self, parameter_name): #optional
return self.units.get(parameter_name)
def conclude_simulation(self): # optional
print("Concluded simulation!")
def get_dtype_of_parameter(self, parameter_name): # optional
return self.parameter_dtypes[parameter_name]
# simulate one python model called foo
model_classes = {"foo": Foo} # we pass the class not the instance!
Note
A class is passed as the values of the dictionary not an instance of the class.
connections_config#
Define how the inputs and outputs of the systems are connected.
Lets assume we have the following configuration.
Each input of a system must have a corresponding output of another system it is connected to. We define these connections as follows:
connections_config = {
"FMU1": [
{
"parameter_name": "fmu1_input1",
"connect_to_system": "CustomSystem1",
"connect_to_external_parameter": "custom_system1_output1",
}
],
"FMU2": [
{
"parameter_name": "fmu2_input1",
"connect_to_system": "FMU1",
"connect_to_external_parameter": "fmu1_output2",
}
],
"CustomSystem1": [
{
"parameter_name": "custom_system1_input1",
"connect_to_system": "FMU1",
"connect_to_external_parameter": "fmu1_output1",
},
{
"parameter_name": "custom_system1_input2",
"connect_to_system": "CustomSystem2",
"connect_to_external_parameter": "custom_system2_output1",
},
],
The keys of the dictionary are the names of the systems that have at least one input. The values of the dictionary define how a input of the system is connected to the output of another system.
start_values#
Define start_values for your systems. For the fmus you can also pass the unit of the value. The start values for a each system will be passed to the initialize method of the corresponding class.
start_values = {
"<name of system 1>":
{
"<name of parameter 1>": <start value>,
"<name of parameter 2>", (<start value>, unit e.g 'kg.m2')
},
"<name of system 2>":
{
"<name of parameter 1>": <start value>,
"<name of parameter 2>": <start value>
}
}
parameters_to_log#
Define which parameters should be logged during the simulation. If the parameter has a
different dtype than float, the dtype needs to be returned in the method
SimulationEntity.get_dtype_of_parameter.
parameters_to_log = {
"<name of system 1>":
[
"<name of parameter 1>",
"<name of parameter 2>",
],
"<name of system 2>":
[
"<name of parameter 1>",
"<name of parameter 2>",
]
}
logging_step_size#
Define a logging step size. The logging step size must be a multiple of the step size.
get_units#
Define whether to return a dictionary with units of the logged parameters. If the units are not defined inside your implemented classes they will be set to None.
Return values of the simulate function#
The function simulate returns the
following:
results#
The results of the simulation is a pandas DataFrame. The first column is the time. The other columns are are named as follows ‘<system_name>.<parameter_name>’ for all the logged parameters.
units#
Dictionary of units of the logged parameters.