Adding a control mechanism to a scenario — mosaik 2.2.0 documentation

# Adding a control mechanism to a scenario

Now that we integrated our first simulator into mosaik and tested it in a simple scenario, we should a control mechanism and mess around with our example simulator a little bit.

As you remember, our example models had a value to which they added something in each step. Eventually, their value will end up to be very high. We’ll use a multi-agent system to keep the values of our models in [-3, 3]. The agents will monitor the current value of their respective models and when it reaches -3/3, they will set delta to 1/-1 for their model.

Implementing the Sim API for control strategies is very similar to implementing it for normal simulators. We start again by importing the `mosaik_api` package and defining the simulator meta data:

    # controller.py
    """
    A simple demo controller.

    """
    import mosaik_api

    META = {
        'models': {
            'Agent': {
                'public': True,
                'params': [],
                'attrs': ['val_in'],
            },
        },
    }

Our control mechanism will use agents to control other entities. The agent has no parameters and only one attribute for incoming values.

Lets continue and implement `mosaik_api.Simulator`:

    class Controller(mosaik_api.Simulator):
        def __init__(self):
            super().__init__(META)
            self.agents = []

Again, nothing special is going on here. We pass our meta data dictionary to our super class and set an empty list for our agents.

Since our agent doesn’t have any parameters, we don’t need to implement `init()`. The default implementation will return the meta data, so there’s nothing we need to do in this case.

Implementing `create()` is also straight forward:

        def create(self, num, model):
            n_agents = len(self.agents)
            entities = []
            for i in range(n_agents, n_agents + num):
                eid = 'Agent_%d' % i
                self.agents.append(eid)
                entities.append({'eid': eid, 'type': model})

            return entities

Every agent gets an ID like “Agent_*<num>*”. Because there might be multiple `create()` calls, we need to keep track of how many agents we already created in order to generate correct entity IDs. We also create a list of {‘eid’: ‘Agent_<num>’, ‘type’: ‘Agent’} dictionaries for mosaik.

You may have noticed that we, in contrast to our example simulator, did not actually instantiate any real simulation models this time. We just pretend to do it. This okay, since we’ll implement the agent’s “intelligence” directly in `step()`:

        def step(self, time, inputs):
            commands = {}
            for agent_eid, attrs in inputs.items():
                values = attrs.get('val_in', {})

The `commands` dict will contain the commands that our control mechanism is going to send to the example simulator. We fill this dict in the following loop. In that, we iterate over all inputs and extract the input values for each agent; so `values` is a dict containing the current values of all models connected to that agent, e.g., values == {‘Model_0’: 1}

We now check the every input value:

                for model_eid, value in values.items():

                    if value >= 3:
                        delta = -1
                    elif value <= -3:
                        delta = 1
                    else:
                        continue

If the value is ≤ -3 or ≥ 3, we have to set a new delta value. Else, we don’t need to do anything and can continue with a new iteration of the loop.

If we have a new delta, we add it to the `commands` dict:

                    if agent_eid not in commands:
                        commands[agent_eid] = {}
                    if model_eid not in commands[agent_eid]:
                        commands[agent_eid][model_eid] = {}
                    commands[agent_eid][model_eid]['delta'] = delta

After finishing the loop, the `commands` dict may look like this:

    {
        'Agent_0': {'Model_0': {'delta': 1}},
        'Agent_2': {'Model_2': {'delta': -1}},
    }

Agent_0 sets for Model_0 the new delta = 1. Agent_2 sets for Model_2 the new delta = -1. Agent_1 did not set a new delta.

So now that we create all commands – how do they get to the example simulator? The way via `get_data()` is not possible since it would require a circular data-flow like `connect(model, agent); connect(agent, model)` which mosaik cannot resolve (You can read the details if you are curious why).

Instead, we use one of the asynchronous requests that you can perform within `step()`, namely `set_data()`. The `Simulator` makes these requests available via the `mosaik` attribute:

            yield self.mosaik.set_data(commands)

            return time + 60 #this works only for Python versions >=3.3.

The `set_data()` actively sends the commands to mosaik which will pass them to the example simulator in its next step.

Note

yield??? If you are new to Python and don’t know what the `yield` keyword does: Don’t worry! In this case, it will just block the execution of `step()` until all commands are sent to mosaik. After that, the method will normally continue its execution.

When all commands are sent to mosaik, we are done with our step and return the time for our next one (which should be in one minute).

That’s it. Since there’s no data to be retrieved, we don’t need to implement `get_data()`. The default implementation will raise an error for us if it should be called accidentally.

Here is the complete code for our (very simple) controller / mutli-agent system:

    # controller.py
    """
    A simple demo controller.

    """
    import mosaik_api

    META = {
        'models': {
            'Agent': {
                'public': True,
                'params': [],
                'attrs': ['val_in'],
            },
        },
    }

    class Controller(mosaik_api.Simulator):
        def __init__(self):
            super().__init__(META)
            self.agents = []

        def create(self, num, model):
            n_agents = len(self.agents)
            entities = []
            for i in range(n_agents, n_agents + num):
                eid = 'Agent_%d' % i
                self.agents.append(eid)
                entities.append({'eid': eid, 'type': model})

            return entities

        def step(self, time, inputs):
            commands = {}
            for agent_eid, attrs in inputs.items():
                values = attrs.get('val_in', {})
                for model_eid, value in values.items():

                    if value >= 3:
                        delta = -1
                    elif value <= -3:
                        delta = 1
                    else:
                        continue

                    if agent_eid not in commands:
                        commands[agent_eid] = {}
                    if model_eid not in commands[agent_eid]:
                        commands[agent_eid][model_eid] = {}
                    commands[agent_eid][model_eid]['delta'] = delta

            yield self.mosaik.set_data(commands)

            return time + 60 #this works only for Python versions >=3.3.
            #For older versions use: raise StopIteration(time + 60)

    def main():
        return mosaik_api.start_simulation(Controller())

    if __name__ == '__main__':
        main()

Next, we’ll create a new scenario to test our controller.