:orphan:
.. _examplesim:
.. role:: raw-html(raw)
:format: html
========================================================
Integrating a simulation model into the mosaik ecosystem
========================================================
.. warning::
This page is not part of the actively maintained documentation anymore.
It is preserved here so that links to it don't break.
We recommend that you read the current tutorial :doc:`here `.
In this section we'll first implement a simple example simulator. We'll then
implement mosaik's Simulator API step-by-step.
.. _the_simulator:
The model
=============
We want to implement a very simple model with the following behavior:
- *val*:sub:`0` = *init_val*
- *val*:sub:`i` = *val*:sub:`i − 1` + *delta* for *i* ∈ **N**, *i* > 0, *delta*
∈ **Z**
That simply means our model has a value *val* to which we add some *delta*
(which is a positive or negative integer) at every simulation step. Our model
has the attribute *delta* (with value 1 by default) which can be changed by
control mechanisms to alter the behavior of the model. And it has the (output)
attribute *val* which is its current value.
.. figure:: /_static/example-model.*
:width: 225
:align: center
:alt: Schematic diagram of our example model.
Schematic diagram of our example model. You can change the *delta* and
collect the *val* as output.
.. _model_python:
Here is a possible implementation of that simulation model in Python:
.. literalinclude:: code/example_model.py
.. _simulator_class:
Setup for the API implementation
================================
So lets start implementing the mosaik simulator API for this model. We can use the
Python :doc:`high-level API ` for this. This package
eases our workload, because it already implements everything necessary for
communicating with mosaik. It provides an abstract base class which we can
sub-class. So we only need to implement four methods and we are done.
If you already :doc:`installed ` mosaik and the demo, you
already have this package installed in your mosaik virtualenv.
We start by creating a new :file:`simulator_mosaik.py` and import the module
containing the simulator API as well as our model:
.. literalinclude:: code/simulator_mosaik.py
:lines: 1-9
.. _meta_data:
Simulator meta data
===================
Next, we prepare the meta data dictionary that tells mosaik which
:ref:`time paradigm ` it follows (*time-based*, *event-based*,
or *hybrid*), which models our simulator implements and which parameters and
attributes it has. Since this data is usually constant, we define this at
module level (which improves readability):
.. literalinclude:: code/simulator_mosaik.py
:lines: 12-21
In this case we create a *hybrid* simulator, because we want to be able to
control it using ``delta`` events later. For now, we won’t use ``delta``,
though.
We added our "ExampleModel" model with the parameter *init_val* and the
attributes *delta* and *val*. At this point we don't care if they are inputs
or outputs. We just list everything we can read or write. The *public* flag
should usually be ``True``. You can read more about it in the :ref:`Sim API
docs `. From this information, mosaik deduces that our model could
be used in the following way:
.. code-block:: python
# Model name and "params" are used for constructing instances:
model = example_model.Model(init_val=42)
# "attrs" are normal attributes:
print(model.val)
print(model.delta)
.. _mosaik_API:
The ``Simulator`` class
=======================
The package ``mosaik_api_v3`` defines a base class ``Simulator`` for which we now
need to write a sub-class:
.. literalinclude:: code/simulator_mosaik.py
:lines: 24-29
In our simulator's ``__init__()`` method (the constructor) we need to call
``Simulator.__init__()`` and pass the meta data dictionary to it.
``Simulator.__init__()`` will add some more information to the meta data and
set it as ``self.meta`` to our instance.
We also set a prefix for our entity IDs and prepare a dictionary which will
hold some information about the entities that we gonna create.
We can now start to implement the four API calls ``init``, ``create``, ``step``
and ``get_data``:
init()
======
This method will be called exactly once while the simulator is being started
via :meth:`World.start `.
It is used for additional initialization tasks (e.g., it can handle parameters
that you pass to a simulator in your scenario definition). It must return the
meta data dictionary ``self.meta``:
.. literalinclude:: code/simulator_mosaik.py
:lines: 31-37
The first argument is the ID that mosaik gave to that simulator instance. The
second argument is the :ref:`time resolution ` of the
scenario. In this example only the default value of *1.* (second per integer
time step) is supported. If you set another value in the scenario, the
simulator would throw an error and stop.
In addition to that, you can define further (optional) parameters which you
can later set in your scenario. In this case, we can optionally overwrite the
``eid_prefix`` that we defined in ``__init__()``.
create()
========
``create()`` is called in order to initialize a number of simulation model
instances *(entities)* within that simulator. It must return a list with some
information about each entity created:
.. literalinclude:: code/simulator_mosaik.py
:lines: 39-49
The first two parameters tell mosaik how many instances of which model you
want to create. As in ``init()``, you can specify additional parameters for
your model. They must also appear in the *params* list in the simulator meta
data or mosaik will reject them. In this case, we allow setting the initial
value *init_val* for the model instances.
For each entity, we create a new entity ID [#]_ and a model instance. We also
create a mapping (``self.entities``) from the entity ID to our model. For each
entity we create we also add a dictionary containing its ID and type to the
``entities`` list which is returned to mosaik. In this example, it has *num*
entries for the model *model*, but it may get more complicated if you have,
e.g., hierarchical models.
.. [#] Although entity IDs can be plain integers, it is advisable to use
something more meaningful to ease debugging and analysis.
step()
======
The ``step()`` method tells your simulator to perform a simulation step. It
receives its current simulation time, a dictionary with input values
from other simulators (if there are any), and the time until the simulator can
safely advance its internal time without creating a causality error. For
time-based simulators (as in our example) it can be safely ignored (it is
equal to the end of the simulation then). The method returns to mosaik the
time at which it wants to do its next step. For event-based and hybrid
simulators a next (self-)step is optional. If there is no next self-step, the
return value is None/null.
.. note::
The *max_advance* value is not necessarily used and is only for special use
cases where simulators can advance in time without expecting new inputs from
other simulators, e.g. for the integration of a communication simulation.
.. literalinclude:: code/simulator_mosaik.py
:lines: 52-64
.. _inputs:
In this example, the *inputs* could be something like this:
.. code-block:: python
{
'Model_0': {
'delta': {'src_id_0': 23},
},
'Model_1':
'delta': {'src_id_1': 42},
'val': {'src_id_1': 20},
},
}
The inner dictionaries containing the actual values may contain multiple
entries if multiple source entities provide input for another entity. In
the case above, we have two source entities, ‘Model_0’ providing the
delta value to the destination entity (object of ExampleSim) and
‘Model_1' providing the delta and val value to the destination entity
(object of ExampleSim). The source entitiy, ‘Model_0’ has the attribute
‘delta’ as the key to another nested dictionary which contains the
simulator id and its corresponding ‘delta’ value. Similarly the source
entity ‘Model_1’ has the attributes ‘delta’ and ‘val’ as the keys to two
other nested dictionaries which contain the simulator id and its
corresponding ‘delta’ and ‘val’ values.
The structure of the *inputs* dictionary created by mosaik is always the
same as depicted above, only the number of source entities (dependent on
the connections in the scenario (‘Model_0’ and ‘Model_1’ in our case))
and the number of attributes passed by the source entity varies. The
first key of the nested dictionary will be the source entity
(‘Model_1’), the following keys will be the attributes passed by this
source entity to the destination entity (‘delta’: {'src_id_1': 42},
‘val’: {‘src_id_1': 20}).
The simulator receiving these inputs is responsible for aggregating them (e.g., by
taking their sum, minimum or maximum. Since we are not interested in the
source's IDs, we convert that dict to a list with ``values.values()`` before we
calculate the sum of all input values.
After we converted the inputs to something that our simulator can work with,
we let it finally perform its next simulation step.
The return value ``time + 1`` tells mosaik that we wish to perform the next
step in one second (in simulation time), as the *time_resolution* is 1.
(second per integer step). Instead of using a fixed (hardcoded) step size you
can easily implement any other stepping behavior.
get_data()
==========
The ``get_data()`` call allows other simulators to get the values of the
``delta`` and ``val`` attributes of our models (the attributes we listed in
the simulator meta data):
.. literalinclude:: code/simulator_mosaik.py
:lines: 66-79
.. _outputs:
The *outputs* parameter contains the query and may in our case look like this:
.. code-block:: python
{
'Model_0': ['delta', 'value'],
'Model_1': ['value'],
}
The Outputs dictionary may contain multiple keys if multiple destination
entities ask for the output from the source entity. In this case we have
two destination entities ‘Model_0’ and ‘Model_1’ which are requesting
for the source attributes. ‘Model_0’ is requesting for the two source
attributes ‘delta’ and ‘value’, whereas ‘Model_1’ is requesting for 1
source attribute ‘value’. The structure of the Outputs dictionary
created by mosaik is always the same as depicted above, only the number
of destination entities (dependent on the connections in the scenario
(‘Model_0’ and ‘Model_1’ in our case)) and the number of attributes
requested by the destination entity varies.
In our implementation we loop over each entity ID for which data is requested.
We then loop over all requested attributes and check if they are valid. If so,
we dynamically get the requested value from our model instance via
``getattr(obj, 'attr')``. We store all values in the ``data`` dictionary and
return it when we are done.
The expected return value would then be:
.. code-block:: python
{
'Model_0': {'delta': 1, 'value': 24},
'Model_1': {'value': 3},
}
Making it executable
====================
The last step is adding a ``main()`` method to make our simulator executable
(e.g., via ``python -m simulator_mosaik HOST:PORT``). The package
``mosaik_api_v3`` contains the method ``start_simulation()`` which creates
a socket, connects to mosaik and listens for requests from it. You just call it
in your ``main()`` and pass an instance of your simulator class to it:
.. literalinclude:: code/simulator_mosaik.py
:lines: 82-87
Simulators running on different nodes than the mosaik instance are supported
explicitly with the mosaik Python-API v2.4 upward via the **remote** flag. A simulator
with the ``start_simulation()`` method in its ``main()`` can then be called e.g. via
.. code-block:: bash
python simulator_mosaik –r HOST:PORT
in the command line. The mosaik scenario, started
independently, can then connect to the simulator via the statement connect: ``HOST:PORT``
in its "*sim_config*"
( :raw-html:`→` `Configuration) `_.
Note that it may make sense to introduce a short waiting
time into your scenario to give you enough time to start both processes. Alternatively,
the remote connection of simulators supports also a timeout (via the **timeout** flag,
e.g. **–t 60** in the command line call will cause your simulator to wait for 60 seconds
for an initial message from mosaik).
Summary
=======
We have now implemented the mosaik simulator API for our simulator. The following
listing combines all the bits explained above:
.. literalinclude:: code/simulator_mosaik.py
We can now start to write our first scenario, which we will do in the next
section.