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:

Additionally 3 optional method can be implemented:

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.

_images/connection_diagram.svg

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.