Banner
Introduction to ABM continued

Introduction ABM with Mason (continued)

 

Spring 2012

Psychology 120


How to Make the Particles do Something

Up to this point, we can make agents, make a 2D discrete space, place the agents in space, and graphically represent them, but how do we make them do something?

At the level of the SimulationEnvironment class, we first have to schedule each agent to do something  (if it has something to do on each time step) by using the schedule in SimState, which we inherited when we defined SimulationEnvironment as a subclass of SimState, we will schedule each agent to repeat through each step.

The code is simple:

       schedule.scheduleRepeating(p);

and we add this to the code at some point within the construction for loop after each agent as illustrated below:

package paricles;

import sim.engine.*;
import sim.field.grid.SparseGrid2D;

public class SimulationEnvironment extends SimState {

	 public SparseGrid2D particleSpace;  //This is a 2D space in which bags
	                                   //are created for each cell once an
	 //agent or a particle first moves into a cell.  This allows multiple
	 //agents or particles to occupy the same cell.

	 public int gridWidth = 50;  //the width of the grid
         public int gridHeight = 50; //the height of the grid
         public int n = 100; //The number of particles

	 public SimulationEnvironment(long seed) {
	    super(seed);
		// TODO Auto-generated constructor stub
	}

	public void start(){
	    super.start();

	    particleSpace = new SparseGrid2D(gridWidth, gridHeight); //create a 2D
		//space for our agents.

		/*
		 * Now, let's make n particles and put them into random locations in
                 * particleSpace
		 * To do this, we will use the random method built into SimState.
                 * It has lot's of
		 * methods, which make generating random locations easier.
		 */

	     for(int i=0;i<n;i++){
		int x = random.nextInt(gridWidth);
		int y = random.nextInt(gridHeight);
		Particle p = new Particle();
		schedule.scheduleRepeating(p);
		particleSpace.setObjectLocation(p, x, y);
	     }
	}
}

Now the simulation runs through steps until stopped, but the particles still don’t do anything. This is because they have no behavioral rules. We have to specify something for our particle agents to do.

Rules of Particle Behavior

The first thing we have to do is decide what behavior(s) our particles will have.  Let’s start really simple.  Let’s suppose that they move in a straight line.  What happens at the boundary of the space (i.e., when they reach the edges of space)?  There are two clear possibilities.  First, they could bounce off the edges or stop.  Second, they could pass through and end up on the other side as though the space is a torus.  That is, the space is  toroidal. Let’s assume the space is toroidal, then we don’t have to worry about exactly what to do at boundaries, the particles just wrap around and keep on moving.

So, how are we going to define this behavior?

First, let’s go to the Particle class.  We will add two sets of variables, x and y coordinate positions for a particle and the directions along the x and y axises:

	int x,y; //declare the location of the agent
	int xdir,ydir; //declare the directions the agent moves

Now, we also need to add some “set” methods, which are public, so that these variables can be initially set in the simulation environment.  The following methods will do the trick:

	public void setXY(int x, int y){
		this.x = x;
		this.y = y;
	}

	public void setXYdir(int xdir, int ydir){
		this.xdir = xdir;
		this.ydir = ydir;
	}

and finally, we must define a “move” method. The move method will have to do three things. First, it will have to change x and y at each step so that the particle moves. Second, it will have to handle boundaries so that the particle at an edge emerges on the opposite side. Finally, it will have to move the particle in the space. The method below does all three:

	public void move (SimState state){
	    x+=xdir;  //add xdir to x
	    y+=ydir;  //add ydir to y
	    SimulationEnvironment se =(SimulationEnvironment)state; //get the
		                  //simuation environment class
	    x = se.particleSpace.stx(x); //use the built in function stx to move in
		                             //toroidal space
	    y = se.particleSpace.sty(y);//use the built in function sty to move in
            //toroidal space
	    se.particleSpace.setObjectLocation(this, x, y); //move the particle in space
	}

We add the “move” method to the “step” method as illustrated below and then run the model.

package particles;

import sim.engine.*;

public class Particle implements Steppable{

    int x,y; //declare the location of the agent
    int xdir,ydir; //declare the directions the agent moves

    public void step(SimState state) {
	move(state);
    }

    public void setXY(int x, int y){
	this.x = x;
	this.y = y;
    }

    public void setXYdir(int xdir, int ydir){
	this.xdir = xdir;
	this.ydir = ydir;
    }

    public void move (SimState state){
	x+=xdir;
	y+=ydir;
	SimulationEnvironment se =(SimulationEnvironment)state;
	x = se.particleSpace.stx(x);
	y = se.particleSpace.sty(y);
	se.particleSpace.setObjectLocation(this, x, y);
    }
}

Notice that some of the particles are not moving. Why is that?

Collisions

The particles are unaffected  by collisions with each other.  They “pass” through each other and move as though nothing happened.  How can we simulate collisions?  We could program a billiard ball model but it would take some time and why should we suppose our particles merely behave like billiard balls?  Suppose instead that they attempt to avoid collisions by randomly changing directions.  How might they do this?

To start with, let’s write a new method that randomly changes a particles direction of movement if it is going to move to a location where there is already at least one other particle. Let’s call this method moveWithCollisions. Here is one way to write it:

	public void moveWithCollisions(SimState state){
	    SimulationEnvironment se = (SimulationEnvironment)state;
		   //convert the SimState to our SimulationEnvironment
	    int newx = x + xdir; //generate the coordinates of the
	    int newy = y + ydir; //new location to move to
	    Bag b = se.particleSpace.getObjectsAtLocation(newx, newy);
		  //get the bag at that location
	    if(b != null && b.numObjs > 0){  //the bag could be null
		//if not, find out if it is empty or has another particle in it.
		xdir = se.random.nextInt(3)-1; //change direction randomly
		ydir = se.random.nextInt(3)-1;
	    }
	    x += xdir; //set the new direction
            y += ydir;
	    x = se.particleSpace.stx(x);
	    y = se.particleSpace.sty(y);
	    se.particleSpace.setObjectLocation(this, x, y);
	}

Now, we need the particle how it is going to handle collisions–move through them or move randomly. Let’s declare a doCollisions boolean variable and set it to true:

	boolean doCollisions = true;

and then change our step method:

	public void step(SimState state) {
	    if(doCollisions){
		moveWithCollisions(state); //if doCollisions is true
	    }
	    else {
		move(state); //else move as before
            }
	}

Our additions look like this:

package particles;

import sim.engine.*;
import sim.util.Bag;

public class Particle implements Steppable{

    int x,y; //declare the location of the agent
    int xdir,ydir; //declare the directions the agent moves
    boolean doCollisions = true;

    public void step(SimState state) {
	if(doCollisions){
	    moveWithCollisions(state); //if doCollisions is true
	}
	else {
            move(state); //else move as before
            }
      }

    public void setXY(int x, int y){
	this.x = x;
	this.y = y;
    }

    public void setXYdir(int xdir, int ydir){
	this.xdir = xdir;
	this.ydir = ydir;
    }

    public void move (SimState state){
	x+=xdir;
	y+=ydir;
	SimulationEnvironment se =(SimulationEnvironment)state;
	x = se.particleSpace.stx(x);
	y = se.particleSpace.sty(y);
	se.particleSpace.setObjectLocation(this, x, y);
    }

    public void moveWithCollisions(SimState state){
	SimulationEnvironment se = (SimulationEnvironment)state;
		   //convert the SimState to our SimulationEnvironment
        int newx = x + xdir; //generate the coordinates of the
	int newy = y + ydir; //new location to move to
	Bag b = se.particleSpace.getObjectsAtLocation(newx, newy);
		  //get the bag at that location
	if(b != null && b.numObjs > 0){  //the bag could be null
             //if not, find out if it is empty or has another particle in it.
             xdir = se.random.nextInt(3)-1; //change direction randomly
	     ydir = se.random.nextInt(3)-1;
	    }
         x += xdir; //set the new direction
         y += ydir;
	 x = se.particleSpace.stx(x);
	 y = se.particleSpace.sty(y);
	 se.particleSpace.setObjectLocation(this, x, y);
	}
}

The problem is that we really don’t want to go into the program to change values such as whether there are collisions or not every time we run a new simulation.

Get and Set Methods

MASON recognizes methods that start with “get” and “set”.  They allow us to get and set parameter values.  When we put a pair of “get” and “set” methods in the subclass (child) of the SimState class, we can change parameter values while we are running simulations.  For example, suppose we add the parameter “doCollisions” to our SimulationEnvironment class:

       public boolean avoidCollisions = true; //Particles collide?

Next, we add “get” and “set” methods to this class:

    public int getgridWidth(){
    	return gridWidth;
    }

    public void setgridWidth(int i){
    	if(i>0)
    		gridWidth = i;
    }

    public int getgridHeight(){
    	return gridHeight;
    }
   public void setgridHeight(int i){
        if(i>0)
    		gridHeight = i;
    }

public int getn(){
return n;
}

public void setn(int i){
if(i>0)
n = i;
}

public boolean getavoidCollisions(){
return avoidCollisions;
}

public void setavoidCollisions(boolean x){
avoidCollisions = x;
}

Let's run the simulation and try out some parameter changes.

One problem is that we can't yet change whether particles do collisions or not.  We have to do two things.  First add a set method to the Particle class and then pass the value of "doCollisions" from the SimulationEnvironment to each particle.  When we are done, our two classes should look like this:


package particles;

import sim.engine.*;
import sim.util.Bag;

public class Particle implements Steppable{

	int x,y; //declare the location of the agent
	int xdir,ydir; //declare the directions the agent moves
	boolean avoidCollisions = true;

	public void step(SimState state) {
		if(avoidCollisions){
			MoveWithCollisions(state); //if avoidCollisions is true
		}
		else {
			move(state); //else move as before
		}
	}

	public void setXY(int x, int y){
		this.x = x;
		this.y = y;
	}

	public void setXYdir(int xdir, int ydir){
		this.xdir = xdir;
		this.ydir = ydir;
	}

	public void setavoidCollisions(boolean x){
		avoidCollisions = x;
	}

	public void move (SimState state){
		x+=xdir;
		y+=ydir;
		SimulationEnvironment se =(SimulationEnvironment)state;
		x = se.particleSpace.stx(x);
		y = se.particleSpace.sty(y);
		se.particleSpace.setObjectLocation(this, x, y);
	}

	public void MoveWithCollisions(SimState state){
		SimulationEnvironment se = (SimulationEnvironment)state;
		   //convert the SimState to our SimulationEnvironment
		int newx = x + xdir; //generate the coordinates of the
		int newy = y + ydir; //new location to move to
		Bag b = se.particleSpace.getObjectsAtLocation(newx, newy);
		  //get the bag at that location
		if(b != null && b.numObjs > 0){  //the bag could be null
			//if not, find out if it is empty or has another particle in it.
			xdir = se.random.nextInt(3)-1; //change direction randomly
			ydir = se.random.nextInt(3)-1;
		}
		x += xdir; //set the new direction
		y += ydir;
		x = se.particleSpace.stx(x);
		y = se.particleSpace.sty(y);
		se.particleSpace.setObjectLocation(this, x, y);
	}

}

package particles;

import sim.engine.*;
import sim.field.grid.SparseGrid2D;

public class SimulationEnvironment extends SimState {

	 public SparseGrid2D particleSpace;  //This is a 2D space in which bags
	                                   //are created for each cell once an
	 //agent or a particle first moves into a cell.  This allows multiple
	 //agents or particles to occupy the same cell.

     public int gridWidth = 50;  //the width of the grid
     public int gridHeight = 50; //the height of the grid
     public int n = 100; //The number of particles
     public boolean avoidCollisions = true; //Particles collide?

    /*
     * Get and set methods for changing variables
     */

    public int getgridWidth(){
    	return gridWidth;
    }

    public void setgridWidth(int i){
    	if(i>0)
    		gridWidth = i;
    }

     public int getgridHeight(){
    	return gridHeight;
    }

    public void setgridHeight(int i){
    	if(i>0)
    		gridHeight = i;
    }

    public int getn(){
    	return n;
    }

    public void setn(int i){
    	if(i>0)
    		n = i;
    }

    public boolean getavoidCollisions(){
    	return avoidCollisions;
    }

    public void setavoidCollisions(boolean x){
    	avoidCollisions = x;
    }

	public SimulationEnvironment(long seed) {
		super(seed);
		// TODO Auto-generated constructor stub
	}

	public void start(){
		super.start();

		particleSpace = new SparseGrid2D(gridWidth, gridHeight); //create a 2D
		//space for our agents.

		/*
		 * Now, let's make n particles and put them into random locations
                 * in particleSpace
		 * To do this, we will use the random method built into SimState.
                 * It has lot's of
		 * methods, which make generating random locations easier.
		 */

		for(int i=0;i < n;i++){
			int x = random.nextInt(gridWidth);
			int y = random.nextInt(gridHeight);
			Particle p = new Particle();
			schedule.scheduleRepeating(p);
			particleSpace.setObjectLocation(p, x, y);
			p.setXY(x, y);
			int xdir = random.nextInt(3)-1;
			int ydir = random.nextInt(3)-1;
			p.setXYdir(xdir, ydir);
			p.setavoidCollisions(avoidCollisions);
		}
	}
}

Boundaries

So far, we have been working with unbounded spaces, but for real agents, there is always some type of boundary.  This room has boundaries.  We can't simply pass through the walls.  So how do we add boundaries to our space and how do particles interact with boundaries?

There are many approaches to answering this question, but let's implement a simple solution.  Let's suppose that the visible edges of the spaces we create have boundaries that particles cannot pass through them (we could make them permeable to some degree if we like).  Suppose further that particles bounce off walls at the same angle they approach, how should we implement this?

Let's break this problem down into how we handle the x and y coordinates. This turns out to be a little tricky. We just need to write two pairs of methods for handling boundaries and also bouncing off of wall.

	
	protected int bx(SimState state, int x){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		if(x<0 || x>= se.gridWidth) {
			return x-xdir; //change the direction of x
		}
		else {
			return x;
		}
	}

	protected int bxdir(SimState state, int x){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		if(x<0 || x>= se.gridWidth) {
			xdir = -xdir;
			return x+xdir;
		}
		else {
			return x;
		}
	}

	protected int by(SimState state, int y){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		if(y<0 || y>= se.gridHeight) {
			return  y - ydir;
		}
		else {
			return y;
		}

	}

	protected int bydir(SimState state, int y){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		if(y<0 || y>= se.gridHeight) {
			ydir = -ydir;
			return y + ydir;
		}
		else {
			return y;
		}
	}

Next, let's write a new version of a method that handles boundaries with collisions. This is easy, all we need to do is copy our original method, rename it and change it a little bit and we get:

	public void moveWithCollisionsBoundaries(SimState state){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		int newx = x + xdir; //generate the coordinates of the
		int newy = y + ydir; //new location to move to
		newx = by(state,newx);
		newy = bx(state,newy);
		Bag b = se.particleSpace.getObjectsAtLocation(newx, newy);
		//get the bag at that location
		if(b != null && b.numObjs > 0){  //the bag could be null
			//if not, find out if it is empty or has another particle in it.
			xdir = se.random.nextInt(3)-1; //change direction randomly
			ydir = se.random.nextInt(3)-1;
		}
		x += xdir; //set the new direction
		y += ydir;
		x = bxdir(state,x);
		y = bydir(state,y);
		se.particleSpace.setObjectLocation(this, x, y);
	}

Next, let's add the option to have boundaries with collisions. To do this, we will add a boolean variable avoidCollisions, write a set method for it, and update our step method.

	boolean avoidBoundaries = true;

	public void setavoidBoundaries(boolean x){
		avoidCollisions = x;
	}

	public void step(SimState state) {
		if(avoidCollisions){
			if(avoidBoundaries){
				moveWithCollisionsBoundaries( state);
			}
			else {
			moveWithCollisions(state); //if avoidCollisions is true
			}
		}

		else {
			move(state); //else move as before
		}
	}

We need to determine whether the particles have boundaries at the time of the simulation, so let's do three modifications to SimulationEnvironment. First, we will add the boolean variable avoidCollisions, second we add get and set methods, and third we pass this value on to each agent particle as it is created.

     public boolean avoidBoundaries = true; //Do particles have boundaries?

    public boolean getavoidBoundaries(){
    	return avoidBoundaries;
    }

    public void setavoidBoundaries(boolean x){
    	avoidBoundaries = x;
    }

    p.setdoBoundaries(avoidBoundaries);

Let's run our model and see what happens.


Documentation

Let's add some documentation to our project and I will explain what we have added. Download this zip file: documentation.zip. Let's put the three files in our particles package and refresh our project.