Configuration of a Simulator
The Simulator of a model works as our 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 communicates with
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 helping 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 imports1import 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
17META = {
18 "type": "hybrid",
19 "models": {
20 "ChargingStation": {
21 "public": True,
22 "params": ["init_vals"],
23 "attrs": [
24 "p",
25 "step_size",
26 "time",
27 ...
"trigger" list …46 "trigger": [
47 "p_set",
48 "ev_data",
49 ], # input attributes
""non-persistent" list.56 "non-persistent": [
57 "p",
58 "p_min",
59 "p_max",
60 "cs_data",
61 ], # output attributes
Initialization of simulator class
__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()
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_id = len(self.entities)
116
117 # create empty list for created entities
118 entities_orchestrator = []
119
120 for i in range(next_id, next_id + num):
121 # create entity by specified name and ID
122 eid = "%s%s%d" % (model_type, "_", i)
123 full_id = self.sid + "." + eid
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 eid,
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[eid] = {
135 "eid": eid,
136 "etype": model_type,
137 "model": entity,
138 "full_id": full_id,
139 }
140 entities_orchestrator.append({"eid": eid, "type": model_type})
141
142 return entities_orchestrator
Stepping of models in step()
Within the step() method, inputs 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).
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 eid, 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
outputs parameter from mosaik,
the values of the transmitting entities are read and stored into a data dict that is returned233for 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.timestep == self.scenario_config["n_steps"] - 1: # is last timestep?
256 data["time"] = self.timestep + 1
257 else:
258 data = {"time": self.timestep}
259
260 return data
For each timestep, the output data is continuously stored and compared to the lastly sent data
(output_cache) so if nothing new is to be sent out, only the timestep will be sent. In
case of the last timestep, we need to adjust the signal by transmitting not the current but the
next timestep to avoid a loop of endless calling between the simulators.