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
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
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 ...
46 "trigger": [
47 "p_set",
48 "ev_data",
49 ], # input attributes
56 "non-persistent": [
57 "p",
58 "p_min",
59 "p_max",
60 "cs_data",
61 ], # output attributes
Initialization of simulator class
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_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).
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()
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.