Configuration of a Simulator

The simulator of a model, as explained in the glossary, works as out interface to communicate between the orchestrator of the simulation and the model entities. This means, that the simulator for the PV system model sends input values to the pv systems, initiates the model calculations and collects output values to return to the orchestrator (e.g. for sending the power values to the connected energy management systems). As there may be multiple entities of a model connected to a single simulator, you only need one simulator of a model type for a simulation, as this simulator makes the communication for all entities of its model type. Hence, a _simulator.py is always paired with a _model.py.

This page explains the structure of a _simulator.py to enable you to create a simulator for your own model.

Note

All code-blocks derive from charging_station_simulator.py as of (01/24) if not stated otherwise.

Import mosaik and model

Import of the mosaik simulator base class mosaik_api_v3 and the corresponding model + additional imports
1import mosaik_api_v3
2from eelib.core.devices.charging_station import charging_station_model
3import eelib.utils.validation as vld
4from copy import deepcopy

Listing of model META

State of simulator (whether it is time-discrete or event-based/hybrid), see the glossary
17META = {
18   "type": "hybrid",
Listing of ALL attributes for each modeltype: At first, all attributes of the model class…
19   "models": {
20      "ChargingStation": {
21         "public": True,
22         "params": ["init_vals"],
23         "attrs": [
24            "p",
25            "step_size",
26            "time",
27            ...
input attributes also listed in "trigger" list …
46         "trigger": [
47            "p_set",
48            "ev_data",
49         ],  # input attributes
output attributes also listed in ""non-persistent" list.
56         "non-persistent": [
57            "p",
58            "p_min",
59            "p_max",
60            "cs_data",
61         ],  # output attributes

Initialization of simulator class

Constructor __init__ for this simulator and the initialization function init()
72class Sim(mosaik_api_v3.Simulator):
73    """Simulator class for eELib charging station model."""
74
75    def __init__(self):
76        """Constructs an object of the Charging-Station:Sim class."""
77
78        super(Sim, self).__init__(META)
79
80        ...
81
82    def init(self, sid, scenario_config, time_resolution=1.0):
83        """Initializes parameters for an object of the Charging-Station:Sim class."""
84
85        ...

Following are the so called core functions create(), step() and get_data() that are found in every _simulator.py.

Caution

All core functions of the simulators are called by mosaik and need to be existent!


Creation of model entities in create()

Creation of model entities by assigning ID and name, creating it by its model type, and storing the entity data
111 def create(self, num, model_type, init_vals):
112     """Creates entities of the eELib charging station model."""
113
114     # generated next unused ID for entity
115     next_eid = len(self.entities)
116
117     # create empty list for created entities
118     entities_orchestrator = []
119
120     for i in range(next_eid, next_eid + num):
121         # create entity by specified name and ID
122         ename = "%s%s%d" % (model_type, "_", i)
123         full_id = self.sid + "." + ename
124
125         # get class of specific model and create entity with init values after validation
126         entity_cls = getattr(charging_station_model, model_type)
127         vld.validate_init_parameters(entity_cls, init_vals[i])
128         entity = entity_cls(
129             ename,
130             **init_vals[i],
131             step_size=self.scenario_config["step_size"],
132
133         # add info to the simulators entity-list and current entities
134         self.entities[ename] = {
135             "ename": ename,
136             "etype": model_type,
137             "model": entity,
138             "full_id": full_id,
139         }
140         entities_orchestrator.append({"eid": ename, "type": model_type})
141
142     return entities_orchestrator

Stepping of models in step()

Withint the step() method, inputs coming from mosaik are set into the model entity attributes they are intended for. Afterwards, the entities are called and their calculation is executed with the models’ step() method.

This example is coming from the storage_simulator.py (01/24).

Take input data and set values of entities, then call the model calculation
175# assign property values for each entity and attribute with entity ID
176# process input signals: for the entities (eid), attr is a dict for attributes to be set
177for eid, attrs in inputs.items():
178    # for the attributes, setter is a dict for entities with corresponding set values
179    for attr, setter in attrs.items():
180        # set values corresponding to its type
181        ...
182
183# call step function for each entity in the list
184for ename, entity_dict in self.entities.items():
185    entity_dict["model"].step(time)

Note

You might return the next timestep for when this model should be called again. This is needed for time-based models (e.g. when reading data from a csv file, the next value should be read and send out) and optional for event-based models (e.g. the adaption of energy levels for a storage model is needed, but a heatpump could maybe just be triggered by a new set value).


Handling of output data in get_data()

From a defined set of output properties given by the outputs parameter from mosaik, the values of the transmitting entities are read and stored into a data dict that is returned
233for transmitter_ename, attrs in outputs.items():
234    # get name for current entity and create dict field
235    entry = self.entities[transmitter_ename]
236    if transmitter_ename not in self.output_cache:
237        self.output_cache[transmitter_ename] = {}
238
239    # loop over all targeted attributes and check if info is available
240    for attr in attrs:
241        if attr not in self.meta["models"][type(entry["model"]).__name__]["attrs"]:
242            raise ValueError("Unknown output attribute: %s" % attr)
243
244        # create empty field for cache and output data
245        if attr not in self.output_cache[transmitter_ename]:
246            self.output_cache[transmitter_ename][attr] = {}
247
248        output_data_to_save = getattr(entry["model"], attr)
249
250        # check if this value changed
251        ...
252
253    # check if nothing is to be send out - send output 1 step later to avoid waiting for data
254    if not flag_output_changed:
255        if self.time == self.scenario_config["n_steps"] - 1:  # is last time step?
256            data["time"] = self.time + 1
257        else:
258            data = {"time": self.time}
259
260    return data

For each time step, the output data is continuously stored and compared to the lastly sent data (output_cache) such that if nothing new is to be send out, only the time step will be send. In case of the last time step, we need to adjust the signal by transmitting not the current but the next time step to avoid a loop of endless calling between the simulators.