Search code examples
agent-based-modelingrepast-simphony

Repast Java: scheduling agent and global behaviors in a structural way


I am previously working with Netlogo for years and I am very much getting used to developing the agent-based model based on a set of procedures. An example of supply chain simulation model structure looks like below:

;;the main simulation loop
@ScheduledMethod(start = 1, interval = 1)
public void step() {       
    place-order-to-suppliers() ;;procedures involving customer agent behaviors (a number of methods)
    receive-shipment-from-suppliers() ;;procedures involving both supplier and customer agents and their behaviors (methods)
    receive-order-from-customers()  ;;procedures involving supplier agent only 
    ship-order-to-customers() ;;procedures involving supplier agent only
    summarize()  ;;procedures involving global summary behaviors independent of any agents, as well as local summary behaviors per each type of agents (customer and supplier)
}

The above structure is very useful and intuitive to develop a simulation model. We first cut the simulation world into several key parts (procedures), within which we further develop specific methods related to associated agents and behaviors. The essential part is to establish a higher level procedure (like a package) which could be useful to integrate (pack) the different types of agents and their behaviors/interactions altogether in one place and execute the model in a desired sequential order based on these procedures.

Are there any hints/examples to implement such modular modelling strategy in Repast?

Update: Below is a simple model I wrote which is about how boy and girl interacts in the party (the full reference can be found https://ccl.northwestern.edu/netlogo/models/Party). Below is the code for Boy Class (the girl is the same so not pasted again).

package party;

import java.util.ArrayList;
import java.util.List;

import repast.simphony.context.Context;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.parameter.Parameters;
import repast.simphony.query.PropertyGreaterThan;
import repast.simphony.query.PropertyEquals;
import repast.simphony.query.Query;
import repast.simphony.random.RandomHelper;
import repast.simphony.space.continuous.ContinuousSpace;
import repast.simphony.space.grid.Grid;
import repast.simphony.space.grid.GridPoint;
import repast.simphony.util.ContextUtils;

public class Boy {
    private ContinuousSpace<Object> space;
    private Grid<Object> grid;
    private boolean happy;
    private int id, x, y,tolerance;
    private boolean over;

    Boy (Grid<Object> grid, int id, int x, int y) {
        this.grid = grid;
        this.id = id;
        this.x = x;
        this.y = y;
        Parameters p = RunEnvironment.getInstance().getParameters();
        int get_tolerance = (Integer) p.getValue("tolerance");
        this.tolerance = get_tolerance;
        }

//  @ScheduledMethod(start = 1, interval = 1,shuffle=true)
//  public void step() {
//      relocation();
//      update_happiness();
//      endRun();
//      
//  }

    public void endRun( ) {
        Context<Object> context = ContextUtils.getContext(this);
        Query<Object> query = new PropertyEquals<Object>(context, "happy", true);
        int end_count = 0;
        for (Object o : query.query()) {
           if (o instanceof Boy) {
               end_count ++;               
           }
           if (o instanceof Girl) {
               end_count ++;               
           }
        }
        if (end_count == 70) {
            RunEnvironment.getInstance().endRun();
        }
    }



    public void update_happiness() {
        over = false;
        Context<Object> context = ContextUtils.getContext(this);
        Parameters p = RunEnvironment.getInstance().getParameters();
        int tolerance = (Integer) p.getValue("tolerance");
        GridPoint pt = grid.getLocation(this);
        int my_x = this.getX();
        int boy_count = 0;
        int girl_count = 0;
        Query<Object> query = new PropertyEquals<Object>(context, "x", my_x);
        for (Object o : query.query()) {
            if (o instanceof Boy) {
                boy_count++;
            }
            else {
                girl_count++;
            }
        }
        int total = boy_count + girl_count;
        double ratio = (girl_count / (double)total);
//      System.out.println((girl_count / (double)total));
        if (ratio <= (tolerance / (double)100)) {
            happy = true;
//          System.out.println("yes");
        }
        else {
            happy = false;
//          System.out.println("no");
        }
        over = true;
//      System.out.println(over);
    }

    public void relocation() {
        if (!happy) {
            List<Integer> x_list = new ArrayList<Integer>();
            for (int i = 5; i <= 50; i = i + 5) {
                x_list.add(i);
            }   
            int index = RandomHelper.nextIntFromTo(0, 9);
            int group_x = x_list.get(index);
            while(group_x == this.getX()){
                index = RandomHelper.nextIntFromTo(0, 9);
                group_x = x_list.get(index);
            }
            int group_y = 35;
            while (grid.getObjectAt(group_x,group_y) != null) {
                group_y = group_y + 1;
            }
            this.setX(group_x);
            grid.moveTo(this, group_x,group_y);
        }
    }

    public int getTolerance() {
        return tolerance;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public int getID() {
        return id;
    }

    public boolean getHappy() {
        return happy;
    }

    public boolean getOver() {
        return over;
    }


    public void setTolerance(int tolerance) {
        this.tolerance = tolerance;
    }
}

---------------------------------------------------------------------------------

The running of above code can follow the standard Repast Annotated scheduling method. However, since I want do some integration of the different agents and their methods altogether to allow the creation of bigger procedures(methods), I managed to create a Global Scheduler Agent Class to manage this modeling strategy. Below is the code:

package party;

import java.util.ArrayList;
import java.util.List;

import repast.simphony.context.Context;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ScheduleParameters;
import repast.simphony.engine.schedule.ScheduledMethod;
import repast.simphony.engine.schedule.Schedule;
import repast.simphony.query.PropertyEquals;
import repast.simphony.query.Query;
import repast.simphony.util.ContextUtils;
import repast.simphony.util.collections.IndexedIterable;

public class Global_Scheduler {


    @ScheduledMethod(start = 1, interval = 1,shuffle=true)
    public void updateHappiness() {
        Context<Object> context = ContextUtils.getContext(this);
        IndexedIterable<Object> boy_agents = context.getObjects(Boy.class);
        IndexedIterable<Object> girl_agents = context.getObjects(Girl.class);

        for (Object b: boy_agents) {
            ((Boy) b).update_happiness();
        }
        for (Object g: girl_agents) {
            ((Girl) g).update_happiness();
        }
    }

    @ScheduledMethod(start = 1, interval = 1,shuffle=true)
    public void relocate() {
        Context<Object> context = ContextUtils.getContext(this);
        IndexedIterable<Object> boy_agents = context.getObjects(Boy.class);
        IndexedIterable<Object> girl_agents = context.getObjects(Girl.class);

        for (Object b: boy_agents) {
            ((Boy) b).relocation();
        }
        for (Object g: girl_agents) {
            ((Girl) g).relocation();
        }

    }


    @ScheduledMethod(start = 1, interval = 1,shuffle=true)
    public void summary() {
        Context<Object> context = ContextUtils.getContext(this);
        Query<Object> query = new PropertyEquals<Object>(context, "happy", true);
        int total_count = 0;
        int boy_count = 0;
        int girl_count = 0;
        for (Object o : query.query()) {
           if (o instanceof Boy) {
               total_count ++;  
               boy_count++;
           }
           if (o instanceof Girl) {
               total_count ++;  
               girl_count++;
           }
        }
        System.out.println("Total happy person: " + total_count);
        System.out.println("Total happy boys: " + boy_count);
        System.out.println("Total happy girls: " + girl_count);     
    }

    @ScheduledMethod(start = 1, interval = 1,shuffle=true)
    public void endRun( ) {
        Context<Object> context = ContextUtils.getContext(this);
        Query<Object> query = new PropertyEquals<Object>(context, "happy", true);
        int end_count = 0;
        for (Object o : query.query()) {
           if (o instanceof Boy) {
               end_count ++;               
           }
           if (o instanceof Girl) {
               end_count ++;               
           }
        }
        if (end_count == 70) {
            RunEnvironment.getInstance().endRun();
        }
    }
}

The above code using the global scheduler agent to run the model is working fine and the outcome should behave the same. However, I am not sure if the execution of the model really follows the sequence (i.e. update_happiness() -> relocate() -> summary() -> end_run(). I would also like to know if there are better and simpler way to achieve such modeling strategy?


Solution

  • You can use priorities in your @ScheduledMethod annotations, e.g.,

    @ScheduledMethod(start = 1, interval = 1, shuffle=true, priority=1)
    

    where a higher priority will run before a lower priority.