How to create an interface (a model-artefact) for an existing model implemented in its own simulator

Example with a single Netlogo random walk model


The model used is a simple random walk where, for each simulation step, agents (so-called walkers) choose a random direction and step forward. The Netlogo model specification is available here. The exchanged data will be here the walkers positions (x,y) the same for input and output. In this simplistic case, the model exchange theses data with itself (i.e. its input port correspond to its output port). Since not very useful for simulation results, this example allows to introduce the very first concepts and their implementation.

In this tutorial, we present the implementation of a model-artefact (which role is to allow the model-agent to interact with the simulator).

random walk example 1

Implementation : model-artefact

In order to create a model-artefact, you need to extend the Interface GenericModelArtefact. Then, you need to fill in the methods (be sure that your simulator implements these functions).

init() Initialize the model
run() Execute the model (one simulation event, one simulation step, for a given simulation time interval...)
getOutputData() Return output data from the model output ports
setInputData() Set input data to the model input ports
getCurrentTime() Return the current simulation time
getNextTime() Return the next simulation time
stop() Manage the end of the simulation process (optional)

Functions

init()

The init() function is used to create a simulator instance, to load the model and to set it up with initial parameters. In this examples, this function creates a Netlogo application instance, loads the randomWalk models and call the "setup" command. Once done, we update the simulation time values. In our case the current simulation time ct=0 and, as we execute the model step-by-step, nt=1.

  @Override
  public void init() {

    // create a simulator instance
    createSimulatorInstance();

    try {

      // set up the model
      // the case of the RandomWalk model, create the agents with an
      // initial position (0,0)
      App.app.command("setup");

      // set patches color
      String cmd = "ask patches [ if (pxcor > " (intenvironmentWidth / 
          ") or (pxcor < " (int-environmentWidth / 2
          ") or (pycor > " (intenvironmentHeight / 2
          ") or (pycor < " (int-environmentHeight / 2
          ") [ set pcolor green ] ]";
      App.app.command(cmd);

      cmd = "ask patches [ if (pxcor - pycor = 0) or (pxcor + pycor = 0) [set pcolor white]]";
      App.app.command(cmd);

      // set up current and next simulation time
      currentTime = new NetlogoTime();
      nextTime = new NetlogoTime();

      currentTime.setTimeFromModel(App.app.report("ticks"));
      nextTime.setTimeFromModel(currentTime.getTimeValue() 1);

      // note : we can also call the updateSimulationTime() function.

    catch (CompilerException e) {
      e.printStackTrace();
    }
  }



  @Override
  protected void createSimulatorInstance() {
    // create a new Netlogo instance
    String[] arg = new String[0];
    App.main(arg);

    // open the model file
    try {
      EventQueue.invokeAndWait(new Runnable() {
        public void run() {
          try {
            App.app
                .open("Netlogo_models/My Models/Random Walk.nlogo");
          catch (IOException ex) {
            ex.printStackTrace();
          }
        }
      });
    catch (InterruptedException e) {
      e.printStackTrace();
    catch (InvocationTargetException e) {
      e.printStackTrace();
    }
  }

run()


The run() function is used to execute the model. In our example, the execution policy is step-by-step, so this function only calls the Netlogo command "go".

  @Override
  public void run() {
    try {
      // run the go procedure (here our policy is to run only one-by-one
      App.app.command("go");
    catch (CompilerException e) {
      e.printStackTrace();
    }

    this.updateSimulationTime();
  }

getCurrentTime(), getNextTime() = updateSimulationTime()


We get the current simulation time from the Netlogo model by calling the report("tick") method, as our simulation is processed step-by-step, the getNextTime() function equals to getCurrentTime() + 1. As we need both values after each run() function call, we created the updateSimulationTime() function.

  @Override
  protected void updateSimulationTime() {
    try {
      currentTime.setTimeFromModel(App.app.report("ticks"));        //getCurrentTime() function
    catch (CompilerException e) {
      e.printStackTrace();
    }
    nextTime.setTimeFromModel(currentTime.getTimeValue() 1);    //getNextTime() function
  }

getOutputData()


This function returns the output data (here, the set of walker positions). Here we report all the walkers, get their IDs, positions and return everything in a list of SimulData objects.

  @Override
  public ArrayList<SimulData> getOutputData() {
    ArrayList<SimulData> outputData = new ArrayList<SimulData>();
    outputData.add(getWalkersPositionsFromModel());
    return outputData;
  }


  /**

   * This function returns the output data from the model
   @return the list of walkers positions from Netlogo
   */
  private SimulData getWalkersPositionsFromModel() {
    // the walkers positions
    HashMap<Double, Point2D.Double> myPositionsMap = new HashMap<Double, Point2D.Double>();

    // the list of agents (the walkers)
    AgentSet listOfWalkers;

    try {

      // get the list of walkers from Netlogo
      listOfWalkers = (AgentSetApp.app.report("turtles");

      // check if the agent set is not empty
      if (!listOfWalkers.isEmpty()) {

        // for each walkers get the position and set the
        // WalkersPosition object
        for (int i = 0; i < listOfWalkers.count(); i++) {

          try {
            // get the agent
            Agent agt = listOfWalkers.agent(i);

            // get the agent id, the position values
            Double agtID = (Doubleagt.getVariable(0);
            Double xPos = (Doubleagt.getVariable(3);
            Double yPos = (Doubleagt.getVariable(4);

            // create a Point2D object
            Point2D.Double agtPos = new Point2D.Double(xPos, yPos);

            // put them in to the map
            myPositionsMap.put(agtID, agtPos);

          catch (NullPointerException e) {

          }
        // end for

      else {
        System.err.println("all walkers are dead");
        // System.exit(1300);
      }

    catch (CompilerException e) {
      e.printStackTrace();
    }

    return new TurtlePositions(myPositionsMap);
  }


setInputData()


This function sets the input data to the model (here, the positions).

 @Override
  public void setInputData(SimulData data) {
    // check if the input data sent by the agent is of type TurtlePosition
    if ((TurtlePositions.class).equals(data.getClass())) {

      TurtlePositions pos = (TurtlePositionsdata;
      setTurtlePositions(pos);

    else {
      // if not (since there is no other input data the model can read)
      // exit.
      System.err
          .println("trying to send a non turtle position data to the random walk model model");
      System.exit(666);
    }
  }

  /**
   * Set the turtle positions to Netlogo
   
   @param pos
   *            A TurtlePositions to set into the model
   */
  private void setTurtlePositions(TurtlePositions pos) {
    HashMap<Double, Point2D.Double> positionList = pos.getTurtlePositions();

    // set the sheep positions
    for (Double turtleID : positionList.keySet()) {

      // get the sheep position
      Point2D.Double aPosition = positionList.get(turtleID);

      // Netlogo command
      String cmd = "ask turtle " + turtleID + " [ setxy "
          + aPosition.getX() " " + aPosition.getY() " ]";

      try {
        App.app.command(cmd);
      catch (CompilerException e) {
        e.printStackTrace();
      }
    }
  }