Introduction ABM with Mason (continued)
Winter 2017
Psychology 120
How to Make the Agents 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?
First, you can upload and install the zipped project from last lecture:
At the level of the Environment 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 Environment 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 agents; import sim.engine.SimState; import sim.field.grid.SparseGrid2D; public class Environment extends SimState { public int n = 100; //The number of agents public SparseGrid2D space; public int gridWidth = 50; public int gridHeight = 50; public Environment(long seed) { super(seed); // TODO Auto-generated constructor stub } public int getN() { return n; } public void setN(int n) { this.n = n; } public int getGridWidth() { return gridWidth; } public void setGridWidth(int gridWidth) { this.gridWidth = gridWidth; } public int getGridHeight() { return gridHeight; } public void setGridHeight(int gridHeight) { this.gridHeight = gridHeight; } public void makeAgents(int n){ for(int i=0;i<n;i++){ int x = random.nextInt(gridWidth); int y = random.nextInt(gridHeight); int dirx = random.nextInt(3)-1; int diry = random.nextInt(3)-1; Agent a = new Agent(x,y,dirx,diry); schedule.scheduleRepeating(a); space.setObjectLocation(a, x, y); } } public void start(){ super.start(); space = new SparseGrid2D(gridWidth,gridHeight); makeAgents(n); } }
We still can’t run a simulation until the particles set up their internal state and have rules for movement.
Rules of Agent Behavior and Internal state
We have created an agent that can move on a torus as shown below.
package agents; import sim.engine.SimState; import sim.engine.Steppable; public class Agent implements Steppable { int x; int y; int dirx; int diry; public Agent(int x, int y, int dirx, int diry) { super(); this.x = x; this.y = y; this.dirx = dirx; this.diry = diry; } public Agent(int x, int y) { super(); this.x = x; this.y = y; } @Override public void step(SimState state) { x += dirx; y += diry; Environment e = (Environment)state; x = e.space.stx(x); y = e.space.sty(y); e.space.setObjectLocation(this, x, y); } }
Notice that some of the agents are not moving. Why is that?
Collisions
The agents 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?
Let’s create two new methods move and moveWithCollisions.
public void move (SimState state){ x += dirx; y += diry; Environment e = (Environment)state; x = e.space.stx(x); y = e.space.sty(y); e.space.setObjectLocation(this, x, y); } public void moveWithCollisions(SimState state){ Environment se = (Environment)state; //convert the SimState to our Environment int tempx = x; int tempy = y; x += dirx; //generate the coordinates of the y += diry; //new location to move to x = se.space.stx(x); y = se.space.sty(y); Bag b = se.space.getObjectsAtLocation(x, y); if(b != null && b.numObjs > 0){ dirx = state.random.nextInt(3)-1; diry = state.random.nextInt(3)-1; x = tempx; y = tempy; x += dirx; //generate the coordinates of the y += diry; //new location to move to x = se.space.stx(x); y = se.space.sty(y); } se.space.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.
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 write a pairs of methods for handling boundaries and also bouncing off of wall.
public int bxdir(SimState state, int x){ Environment se = (Environment)state; //convert the SimState to our Environment if(x= se.gridWidth) { dirx = -dirx; //reverse direction return x+dirx; //revert to previous } else { return x; } } public int bydir(SimState state, int y){ Environment se = (Environment)state; //convert the SimState to our Environment if(y= se.gridHeight) { diry = -diry; //reverse direction return y + diry; // revert to previous } 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 moveWithCollisionsAndBoundaries(SimState state){ Environment se = (Environment)state; //convert the SimState to our Environment int tempx = x; int tempy = y; x += dirx; //generate the coordinates of the y += diry; //new location to move to x = bxdir(state,x); y = bydir(state,y); Bag b = se.space.getObjectsAtLocation(x, y); if(b != null && b.numObjs > 0){ dirx = state.random.nextInt(3)-1; diry = state.random.nextInt(3)-1; x = tempx; y = tempy; x += dirx; //generate the coordinates of the y += diry; //new location to move to x = bxdir(state,x); y = bydir(state,y); } se.space.setObjectLocation(this, x, y); }
We need to determine whether the particles have boundaries at the time of the simulation, so let’s do three modifications to Environment. First, we will add the boolean variable bounded, second we add get and set methods, and third we pass this value on to each agent particle as it is created. We also need to determine whether we need collisions or not.
public boolean bounded = false; public boolean collisions = false; public boolean isBounded() { return bounded; } public void setBounded(boolean bounded) { this.bounded = bounded; } public boolean isCollisions() { return collisions; } public void setCollisions(boolean collisions) { this.collisions = collisions; }
Next we need to pass these parameters on to the agent. We first add
these parameters to Agent and then set their values.
boolean bounded; boolean collisions; public Agent(SimState state, int x, int y, int dirx, int diry) { super(); this.x = x; this.y = y; this.dirx = dirx; this.diry = diry; Environment e = (Environment)state; this.bounded = e.bounded; this.collisions = e.collisions; }
Finally, let’s put this altogether in the step method and finish the rest of the methods we need.
public void step(SimState state) { if(bounded){ if(collisions){ moveWithCollisionsAndBoundaries(state); } else{ moveWithBoundaries(state); } } else { if(collisions){ moveWithCollisions(state); } else{ move (state); } } }
Let’s run our model and see what happens.
Random Movement
We can make agent that move in straight lines, bounce off of wall, and randomly move with collisions. How do we make them move randomly and control the degree of randomness?
To the environment let’s add:
public double probabilityOfRandomMovement = 0; public double getProbabilityOfRandomMovement() { return probabilityOfRandomMovement; } public void setProbabilityOfRandomMovement(double probabilityOfRandomMovement) { this.probabilityOfRandomMovement = probabilityOfRandomMovement; }
Let’s provide this parameter to the Agent class.
double probabilityOfRandomMovement = 0; public Agent(SimState state, int x, int y, int dirx, int diry) { super(); this.x = x; this.y = y; this.dirx = dirx; this.diry = diry; Environment e = (Environment)state; this.bounded = e.bounded; this.collisions = e.collisions; this.probabilityOfRandomMovement = e.probabilityOfRandomMovement; }
How do we introduce probabilistic movement to each of our methods? We can add the following if then statement at the beginning of each method.
if(state.random.nextBoolean(this.probabilityOfRandomMovement)){ dirx = state.random.nextInt(3)-1; diry = state.random.nextInt(3)-1; }
For example:
public void move (SimState state){ if(state.random.nextBoolean(this.probabilityOfRandomMovement)){ dirx = state.random.nextInt(3)-1; diry = state.random.nextInt(3)-1; } x += dirx; y += diry; Environment e = (Environment)state; x = e.space.stx(x); y = e.space.sty(y); e.space.setObjectLocation(this, x, y); }