Configuration of a Simulator

This page explains the structure of a simulator.py to enable you to create your own simulators. The simulator.py stores information about the connected models and mediates between them and mosaik (e.g. passing the order to step() as well as associated values). As there may be multiple instances of a model connected to a single simulator, you only need one simulator of a type per scenario, set within the SIM_CONFIG of scenario file. A simulator.py is always paired with a model.py.

Note

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

Introduction and imports

Basic explanation import of relevant packages
 1"""
 2Mosaik interface for the eELib charging station model.
 3Simulator for communication between orchestrator (mosaik) and charging station entities.
 4
 5Author: elenia@TUBS
 6"""
 7
 8import mosaik_api_v3
 9from eelib.core.devices.charging_station import charging_station_model
10import eelib.utils.validation as vld
11from copy import deepcopy

Listing of model META

State of simulator (whether it is time-discrete or event-based/hybrid)
17META = {
18   "type": "hybrid",
Listing of ALL attributes for each modeltype: At first, all attributes …
19   "models": {
20      "ChargingStation": {
21         "public": True,
22         "params": ["init_vals"],
23         "attrs": [
24            "type",
25            "output_type",
26            "step_size",
27            "time",
28            ...
input attributes also listed in "trigger" list …
46         "trigger": [
47            "p_set",
48            "e_bat",
49            "e_bat_max",
50            "p_charge_max",
51            "p_discharge_max",
52            "appearance",
53            "appearance_end_step",
54            "bev_consumption_period",
55         ],  # input attributes
output attributes also listed in ""non-persistent" list.
56         "non-persistent": [
57            "p_min",
58            "p_max",
59            "appearance_end_step",
60            "p_device",
61            "p",
62            "discharge_efficiency",
63            "charge_efficiency",
64            "e_bat",
65            "e_bat_max",
66         ],  # output attributes

Initialization of simulator class

Short explanation as well as 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    Args:
 76        mosaik_api_v3 (module): defines communication between mosaik and simulator
 77
 78    Raises:
 79        ValueError: Unknown output attribute, when not described in META of simulator
 80    """
 81
 82    def __init__(self):
 83        """Constructs an object of the Charging-Station:Sim class."""
 84
 85        super(Sim, self).__init__(META)
 86
 87        # storing of event-based output info (for same-time loop or next time step)
 88        self.output_cache = {}
 89
 90        # initiate empty dict for model entities
 91        self.entities = {}
 92
 93    def init(self, sid, scenario_config, time_resolution=1.0):
 94        """Initializes parameters for an object of the Charging-Station:Sim class.
 95
 96        Args:
 97            sid (str): Id of the created instance of the simulator (e.g. CSSim-0)
 98            scenario_config (dict): scenario configuration data, like resolution or step size
 99            time_resolution (float): Time resolution of current scenario.
100
101        Returns:
102            meta: description of the simulator
103        """
104
105        # assign properties
106        self.sid = sid
107        self.scenario_config = scenario_config
108
109        return self.meta

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 should not be deleted!


Creation of model entities in create()

Core functions should also be labeled in their docstring
111 def create(self, num, model_type, init_vals):
112     """Creates entities of the eELib charging station model.
113     Core function of mosaik.
114
115     Args:
116         num (int): Number of cs models to be created
117         model_type (str): type of created instance (e.g. "charging_station")
118         init_vals (list): list with initial values for each charging_station entity
119
120     Returns:
121         dict: created entities
122     """
Creation of entities by assigning individual entity names and calling the initialization method of that model type
124# generated next unused ID for entity
125next_eid = len(self.entities)
126
127# create empty list for created entities
128entities_orchestrator = []
129
130for i in range(next_eid, next_eid + num):
131    # create entity by specified name and ID
132    ename = "%s%s%d" % (model_type, "_", i)
133    full_id = self.sid + "." + ename
134
135    # get class of specific model and create entity with init values after validation
136    entity_cls = getattr(charging_station_model, model_type)
137    vld.validate_init_parameters(entity_cls, init_vals[i])
138    entity = entity_cls(
139        ename,
140        **init_vals[i],
141        step_size=self.scenario_config["step_size"],
142    )
Simulator stores information about the entities
144    # add info to the simulators entity-list and current entities
145    self.entities[ename] = {
146        "ename": ename,
147        "etype": model_type,
148        "model": entity,
149        "full_id": full_id,
150    }
151    entities_orchestrator.append({"eid": ename, "type": model_type})
152
153return entities_orchestrator

Stepping of models in step()

The step() method of storage_simulator.py (01/24).

First take input data (from mosaik) and set values of entities
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 (attr), setter is a dict for entities with corresponding set values
179    for attr, setter in attrs.items():
180        # for transmitter (eid_setter), value_dict contains set values (with ids, when dict)
181        setting_value_dict = deepcopy(getattr(self.entities[eid]["model"], attr))
182        for eid_setter, value_dict in setter.items():
183            if isinstance(value_dict, dict):
184                # go by each id and search for corresponding entity id
185                for getter_id, value in value_dict.items():
186                    if eid in getter_id:
187                        setting_value_dict[eid_setter] = value
188            # value_dict is not a dict, only a single value -> write directly
189            elif isinstance(value_dict, (float, int)):
190                setting_value_dict[eid_setter] = value_dict
191            else:
192                raise TypeError("Unknown format for value_dict")
193        setattr(self.entities[eid]["model"], attr, setting_value_dict)
194
195        # check if there is more than one power set value - otherwise directly set it
196        if attr == "p_set":
197            if len(setting_value_dict) > 1:
198                raise ValueError("There is more than one power set value for " + eid)
Then simply step each model for this time step
200# call step function for each entity in the list
201for ename, entity_dict in self.entities.items():
202    entity_dict["model"].step(time)

Note

You might return the next timestep for when this model should be called again.


Handling of output data in get_data()

From a defined set of output properties, the values of the transmitting entities are read and stored into data dict
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)
For each time step, the output data is continuously stored and compared to the lastly sent (output_cache) such that if nothing new is to be send out, only the time step will be send
291# check if nothing is to be send out - send output 1 step later to avoid waiting for data
292if not flag_output_changed:
293    if self.time == self.scenario_config["n_steps"] - 1:  # is last time step?
294        data["time"] = self.time + 1
295    else:
296        data = {"time": self.time}