The high-level API — mosaik 2.0a2 documentation

# The high-level API

Currently, there is only a high-level API for Python. Once there are implementations for other languages available, this document will be updated.

## Installation

The Python implementation of the mosaik API is available as a separate package an can easily be installed via pip:

    pip install mosaik-api

It supports Python 2.7, >= 3.3 and PyPy.

## Usage

You create a subclass of `mosaik_api.Simulator` which implements the four API calls `init()`, `create()`, `step()` and `get_data()`. You can optionally override `configure()` and `finalize()`. The former allows you to handle additional command line arguments that your simulator may need. The latter is called just before the simulator terminates and allows you to perform some clean-up.

You then call `mosaik_api.start_simulation()` from your main() function to get everything set-up and running. That function handles the networking as well as serialization and de-serialization of messages. Commands from the low-level API are translated to simple function calls. The return value of these functions is used for the reply.

For example, the message

    ["create", [2, "Model", {"param1": 15, "param2": "spam"}]

will result in a call

    create(2, 'Model', param1=15, param2='spam')

### API calls

class `mosaik_api.` `Simulator`(meta)

  This is the base class that you need to inherit from and implement the API calls.

  `meta`

    Meta data describing the simulator (the same that is returned by `init()`).

        {
            "api_version": "x.y.z",
            "models": {
                "ModelName": {
                    "public": true|false,
                    "params": ["param_1", ...],
                    "attrs": ["attr_1", ...],
                },
                ...
            }
        }

    The api_version is a string that defines which version of the mosaik API the simulator implements. The simulator’s major versions (“x”, in the snippet above) has to be the same as mosaik’s. Mosaik will cancel the simulation if a version mismatch occurs.

    models is a dictionary describing the models provided by this simulator. The entry public determines whether a model can be instantiated by a user (`True`) or if it is a sub-model that cannot be created directly (`False`). params is a list of parameter names that can be passed to the model when creating it. attrs is a list of attribute names that can be accessed (reading or writing).

  `mosaik`

    An RPC proxy to mosaik.

  `init`(sid, **sim_params)

    Initialize the simulator with the ID sid and apply additional parameters (sim_params) sent by mosaik. Return the meta data `meta`.

    If your simulator has no sim_params, you don’t need to override this method.

  `create`(num, model, **model_params)

    Create num instances of model using the provided model_params.

    num is an integer for the number of model instances to create.

    model needs to be a public entry in the simulator’s `meta['models']`.

    model_params is a dictionary mapping parameters (from `meta['models'][model]['params']`) to their values.

    Return a (nested) list of dictionaries describing the created model instances (entities). The root list must contain exactly num elements. The number of objects in sub-lists is not constrained:

        [
            {
                'eid': 'eid_1',
                'type': 'model_name',
                'rel': ['eid_2', ...],
                'children': [
                    {'eid': 'child_1', 'type': 'child', 'rel': []},
                    ...
                ],
            },
            ...
        ]

    The entity ID (eid) of an object must be unique within a simulator instance. For entities in the root list, type must be the same as the model parameter. The type for objects in sub-lists may be anything that can be found in `meta['models']`. rel is a list of related entities. The children entry is optional and may contain a sub-list of entities.

  `step`(time, inputs)

    Perform the next simulation step from time time using input values from inputs and return the new simulation time (the time at which `step()` should be called again).

    time and the time retuned are integers. Their unit is seconds (from simulation start).

    inputs is a dict of dicts mapping entity IDs to attributes and lists of values (each simulator has do decide on its own how to reduce that list (e.g., as its sum, average or maximum):

        {
            'eid_1': {
                'attr_1': ['val_1_1', 'val_1_2', ...],
                'attr_2': ['val_2_1', 'val_2_2', ...],
                ...
            },
            ...
        }

  `get_data`(outputs)

    Return the data for the requested attributes in outputs

    outputs is a dict mapping entity IDs to lists of attribute names whose values are requested:

        {
            'eid_1': ['attr_1', 'attr_2', ...],
            ...
        }

    The return value needs to be a dict of dicts mapping entity IDs and attribute names to their values:

        {
            'eid_1: {
                'attr_1': 'val_1',
                'attr_2': 'val_2',
                ...
            },
            ...
        }

  `configure`(args, backend, env)

    This method can be overridden to configure the simulation with the command line args as created by docopt.

    backend and env are the simpy.io backend and environment used for networking. You can use them to start extra processes (e.g., a web server).

    The default implementation simply ignores them.

  `finalize`()

    This method can be overridden to do some clean-up operations after the simulation finished (e.g., shutting down external processes).

The mosaik-api package provides an example simulator that demonstrates how the API can be implemented.

### Asynchronous requests

The asynchronous requests can be called via the `MosaikRemote` proxy `self.mosaik` from within `step()`. They don’t return the actual results but an event (similar to a future of deferred). The event will eventually hold the actual result. To wait for that result to arrive, you simply yield the event, e.g.:

    def step(self, time, inputs):
        progress = yield self.mosaik.get_progress()
        # ...

`MosaikRemote.` `get_progress`()

  Return the current simulation progress from `sim_progress`.

`MosaikRemote.` `get_related_entities`(entities=None)

  Return information about the related entities of entities.

  If entitites omitted (or `None`), return the complete entity graph, e.g.:

      {
          'nodes': {
              'sid_0/eid_0': {'type': 'A'},
              'sid_0/eid_1': {'type': 'B'},
              'sid_1/eid_0': {'type': 'C'},
          },
          'edges': [
              ['sid_0/eid_0', 'sid_1/eid0', {}],
              ['sid_0/eid_1', 'sid_1/eid0', {}],
          ],
      }

  If entities is a single string (e.g., `sid_1/eid_0`), return a dict containing all entities related to that entity:

      {
          'sid_0/eid_0': {'type': 'A'},
          'sid_0/eid_1': {'type': 'B'},
      }

  If entities is a list of entity IDs (e.g., `['sid_0/eid_0', 'sid_0/eid_1']`), return a dict mapping each entity to a dict of related entities:

      {
          'sid_0/eid_0': {
              'sid_0/eid_1': {'type': 'B'},
          },
          'sid_0/eid_1': {
              'sid_0/eid_1': {'type': 'B'},
          },
      }

`MosaikRemote.` `get_data`(attrs)

  Return the data for the requested attributes attrs.

  Attributes is a dict of (fully qualified) entity IDs mapping to lists of attribute names (`{'sid/eid': ['attr1', 'attr2']}`).

  The return value is a dict mapping the input entity IDs to data dictionaries mapping attribute names to there respective values.

`MosaikRemote.` `set_data`(data)

  Set data as input data for all affected simulators.

  data is a dictionary mapping sim_id/entity_id paths to dictionaries of attributes and values (`{'sid/eid': {'attr1': 'val1', 'attr2': 'val2'}}`)

The mosaik-api package provides an example “multi-agent system” that demonstrates how to perform asynchronous requests can be implemented.

### Starting the simulator

To start your simulator, you just need to create an instance of your `Simulator` sub-class and pass it to `start_simulation()`:

`mosaik_api.` `start_simulation`(simulator, description='', extra_options=None)

  Start the simulation process for `simulation`.

  simulation is the instance of your API implementation (see `Simulator`).

  description may override the default description printed with the help on the command line.

  extra_option may be a list of options for docopt (example: `['-e, --example Enable example mode']`). Commandline arguments are passed to `Simulator.configure()` so that your API implementation can handle them.

Here is an example with a bit more context:

    import mosaik_api

    example_meta = {
        'models' {
            'A': {
                  'public': True,
                  'params': ['init_val'],
                  'attrs': ['val_out', 'dummy_out'],
            },
        }
    }

    class ExampleSim(mosaik_api.Simulator):
        def __init__(self):
            super().__init__(example_meta)

        sim_name = 'ExampleSimulation'

        def configure(self, args, backend, env):
            # Here you could handle additional command line arguments

        def init(self):
            # Initialize the simulator
            return self.meta

        # Implement the remaining methods (create, step, get_data, ...)

    def main():
        import sys

        description = 'A simple example simulation for mosaik.'
        extra_options = [
           '--foo       Enable foo',
           '--bar BAR   The bar parameter',
        ]

        return mosaik_api.start_simulation(ExampleSim(), description, extra_options)

    if __name__ == '__main__':
        sys.exit(main())