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.