Integrating a Model in Java

What do we do if we want to connect a simulator to mosaik which ist written in Java? In this tutorial we will describe how to create a simple model in Java and integrate it into mosaik using the mosaik-Java high level API. We will do this with the help of our simple model from the Python tutorial, i. e. we will try to replicate the first part of the Python tutorial as close as possible.

Getting the Java API

You can add the java package via maven or gradle, following the instructions at the mosaik-java-API package repository.

If you want to compile the jar yourself, you have to get the sources of the mosaik-java-API for Java which is provided on Gitlab. Clone it and put it in the development environment of your choice.

Creating the model

Next we create a Java class for our model. Our example model has exact the same behaviour as our simple model in the Python tutorial. To distinguish it from the Python-model we call it JModel. The only difference to the Python-model is that in Java we need two constructors (with and without init value) and getter and setter methods to access the variables val and delta.

class JModel {
    private float val;
    private float delta = 1;

    public JModel() {
        this.val = 0;
    }

    public JModel(float initVal) {
        this.val = initVal;
    }

    public float get_val() {
        return this.val;
    }
    
    public float get_delta() {
        return this.delta;
    }

    public void set_delta(float delta) {
        this.delta = delta;
    }

    public void step() {
        this.val += this.delta;
    }
}

Creating the simulator

A simulator provides the functionality that is necessary to manages instances of our model and to execute the models. We need a method addModel to create instances of our model and an ArrayList models to store them. The step method executes a simulation for each model instance. To access the values and deltas we need getter and setter methods. In our example the class implementing these functionalities is called JSimulator.

class JSimulator {
    private final ArrayList<JModel> models;

    public JSimulator() {
    	this.models = new ArrayList<JModel>();
    }
    
    public void add_model(Number init_val) {
        JModel model;
        if (init_val == null) {
        	model = new JModel();
        } else {
        	model = new JModel(init_val.floatValue());
        }
        this.models.add(model);
    }
    
    public void step() {
    	for (int i = 0; i < this.models.size(); i++) {
    		JModel model = this.models.get(i);
    		model.step();
    	}	    		
    }
    
    public float get_val(int idx) {
    	JModel model = this.models.get(idx);
    	return model.get_val();
    }
    
    public float get_delta(int idx) {
    	JModel model = this.models.get(idx);
    	return model.get_delta();
    }
    
    public void set_delta(int idx, float delta) {
    	JModel model = this.models.get(idx);
    	model.set_delta(delta);
    }
}

Implementing the mosaik API

Finally we need to implement the mosaik-API methods. In our example this is done in a class called JExampleSim. This class has to extent the abstract class Simulator from mosaik-java-api which is the Java-equivalent to the Simulator class in Python. The class Simulator provides the four mosaik-API-calls init(), create(), step(), and getData() which we have to implement. For a more detailed explanation of the API-calls see the API-documentation.

But first we have to put together the meta-data containing information about models, attributes, and parameters of our simulator. ‘models’ are all models our simulator provides. In our case this is only JModel. ‘public’: true tells mosaik that it is allowed to create models of this class. ‘params’ are parameter that are passed during initialisation, in our case this is init_val. ‘attrs’ is a list of values that can be exchanged.

    private static final JSONObject meta = (JSONObject) JSONValue.parse(("{"
            + "    'api_version': " + Simulator.API_VERSION + ","
            + "    'models': {"
            + "        'JModel': {" 
            + "            'public': true,"
            + "            'params': ['init_val'],"
            + "            'attrs': ['val', 'delta']" 
            + "        }"
            + "    }" 
            + "}").replace("'", "\""));

First method is init() that returns the meta data. In addition it is possible to pass arguments for initialization. In our case there is eid_prefix which will be used to name instances of the models:

    public Map<String, Object> init(String sid, Map<String, Object> simParams) {
    	if (simParams.containsKey("eid_prefix")) {
    		this.eid_prefix = simParams.get("eid_prefix").toString();
    	}
        return JExampleSim.meta;
    }

create() creates new instances of the model JModel by calling the add_model-method of JSimulator. It also assigns ID (eid) to the models, so that it is able to keep track of them. It has to return a list with the name (eid) and type of the models. You can find more details about the return object in the API-documentation.

    @Override
    public List<Map<String, Object>> create(int num, String model, 
    		Map<String, Object> modelParams) {
        JSONArray entities = new JSONArray();
        for (int i = 0; i < num; i++) {
            String eid = this.eid_prefix + (this.idCounter + i);
            if (modelParams.containsKey("init_val")) {
                Number init_val = (Number) modelParams.get("init_val");
                this.simulator.add_model(init_val);
            }
            JSONObject entity = new JSONObject();
            entity.put("eid", eid);
            entity.put("type", model);
            entity.put("rel", new JSONArray());
            entities.add(entity);
            this.entities.put(eid, this.idCounter + i);
        }
        this.idCounter += num;
        return entities;
    }

step() tells the simulator to perform a simulation step. It passes the time, the current simulation time, and inputs, a JSON data object with input data from preceding simulators. The structure of inputs is explained in the API-documentation. If there are new delta-values in inputs they are set in the appropriate model instance. Finally it calls the simulator’s step()-method which, on its part, calls the step()-methods of the individual model-instances.

    public long step(long time, Map<String, Object> inputs) {
    	//go through entities in inputs
        for (Map.Entry<String, Object> entity : inputs.entrySet()) {
            //get attrs from entity
            Map<String, Object> attrs = (Map<String, Object>) entity.getValue();
            //go through attrs of the entity
            for (Map.Entry<String, Object> attr : attrs.entrySet()) {
            	//check if there is a new delta
                String attrName = attr.getKey();
                if (attrName.equals("delta")) {
                    //sum up deltas from different sources
                    Object[] values = ((Map<String, Object>) attr.getValue()).values().toArray();
                    float value = 0;
                    for (int i = 0; i < values.length; i++) {
                        value += ((Number) values[i]).floatValue();
                    }
                    //set delta 
                    String eid = entity.getKey();
                    int idx = this.entities.get(eid);
                    this.simulator.set_delta(idx, value);
                }
            }
        }
        //call step-method
        this.simulator.step();

        return time + this.stepSize;
    }

getData() gets the simulator’s output data from the last simulation step. It passes outputs, a JSON data object that describes which parameters are requested. getData() goes through outputs, retrieves the requested values from the appropriate instances of JModel and puts it in data. The structure of outputs and data is explained in the API-documentation.

    public Map<String, Object> getData(Map<String, List<String>> outputs) {
        Map<String, Object> data = new HashMap<String, Object>();
        //*outputs* lists the models and the output values that are requested
        //go through entities in outputs
        for (Map.Entry<String, List<String>> entity : outputs.entrySet()) {
            String eid = entity.getKey();
            List<String> attrs = entity.getValue();
            HashMap<String, Object> values = new HashMap<String, Object>();
            int idx = this.entities.get(eid);
            //go through attrs of the entity
            for (String attr : attrs) {
                if (attr.equals("val")) {
                	values.put(attr, this.simulator.get_val(idx));
                }
                else if (attr.equals("delta")) {
                	values.put(attr, this.simulator.get_delta(idx));
                }
            }
            data.put(eid, values);
        }
        return data;
    }

Connecting the simulator to mosaik

We use the same scenario as in our Python example demo1. The only thing we have to change is the way we connect our simulator to mosaik. There are two ways to do this:

  • cmd: mosaik calls the Java API by executing the command given in cmd. Mosaik starts the Java-API in a new process and connects it to mosaik. This works only if your simulator runs on the same machine as mosaik.

  • connect: mosaik connects to the Java-API which runs as a TCP server. This works also if mosaik and the simulator are running on different machines.

For more details about how to connect simulators to mosaik see the section about the Sim Manager in the mosaik-documentation.

Connecting the simulator using cmd

We have to give mosaik the command how to start our Java simulator. This is done in SIM_CONFIG. The marked lines show the differences to our Python simulator.

# Sim config. and other parameters
SIM_CONFIG = {
    'JExampleSim': {
        'cmd': 'java -cp JExampleSim.jar de.offis.mosaik.api.JExampleSim %(addr)s',
    },
    'Collector': {
        'cmd': 'python collector.py %(addr)s',
    },
}
END = 10 * 60  # 10 minutes

The placeholder %(addr)s is later replaced with IP address and port by mosaik. If we now execute demo_1.py we get the same output as in our Python-example.

Note

The command how to start the Java simulator may differ depending on your operating system. If the command is complex, e. g. if it contains several libraries, it is usually better to put it in a script and than call the script in cmd.

Connecting the simulator using connect

In this case the Java API acts as TCP server and listens at the given address and port. Let’s say the simulator runs on a computer with the IP-address 1.2.3.4. We can now choose a port that is not assigned by default. In our example we choose port 5678. Make sure that IP-address and port is accessible from the computer that hosts mosaik (firewalls etc.).

Note

Of course you can run mosaik and your simulator on the same machine by using 127.0.0.1:5678 (localhost). You may want to do this for testing and experimenting. Apart from that the connection with cmd (see above) is usually the better alternative because you don’t have to start the Java part separately.

We have to tell mosaik how to connect to the simulator. This is done in SIM_CONFIG in our scenario (demo1):

# Sim config. and other parameters
SIM_CONFIG = {
    'JExampleSim': {
        'connect': '1.2.3.4:5678',
    },
    'Collector': {
        'cmd': 'python collector.py %(addr)s',
    },
}
END = 10 * 60  # 10 minutes

The marked lines show the differences to our Python simulator. Our simulator is now called JExampleSim and we need to give the simulator’s address and port after the connect key word.

Now we start JExampleSim. To tell the mosaik-Java-API to run as TCP-server is done by starting it with “server” as second argument. The first command line argument is IP-address and port. The command line in our example looks like this:

java -cp JExampleSim.jar de.offis.mosaik.api.JExampleSim 1.2.3.4:5678 server

If we now execute demo_1.py we get the same output as in our Python-example.

Note

You can find the source code used in this tutorial in the mosaik-source-files in the folder docs/tutorial/code.