Banner
Aggregation and Flocking


Aggregation and Flocking

Spring 2014

Psychology 120


Introduction

Our goal this quarter is to build an agent-based model of human mate choice, but before we do that, we are going to design and build a model with agents that aggregate and flock (you will actually build the flocking component, which is the hardest part, in the next lab).

Let’s begin with a version that includes what we did in the previous lab.  Download and install:

Aggregate and Flock.zip

Before we build this model, we will add an option to determine whether one or more agents can occupy the same cell.  To do this, let’s add a Boolean variable unoccupied to both the Particle and SimulationEnvironment classes and add set and get methods.  When unoccupied = true, only one agent can occupy a cell at any given time.  When unoccupied = false, more than one agent can occupy the same cell. We will also pass the value of the variable to each particle we create.

If unoccupied is true, then we have to make sure that we initially place agents in unique cells.  To do this, let’s write a new method called placeAgents, which can place them uniquely and schedule them

 


 

/**
* A new method that handles initial placement of agents
*/
public Particle placeAgents(){
    Particle p = new Particle();
    int x,y;
    if(!unoccupied){ //allows more than one agent to occupy a cell
         x = random.nextInt(gridWidth);
         y = random.nextInt(gridHeight);
     }
     else{
         x = random.nextInt(gridWidth);
         y = random.nextInt(gridHeight);
         for(;;){
             Bag b = particleSpace.getObjectsAtLocation(x, y);
             if(b == null || b.numObjs == 0){
                 break;
             }
             x = random.nextInt(gridWidth);
             y = random.nextInt(gridHeight);
             }
        }

      //Now set all the parameters

      int xdir = random.nextInt(3)-1;
      int ydir = random.nextInt(3)-1;
      p.setXYdir(xdir, ydir);
      p.setAvoidCollisions(avoidCollisions);
      p.setBoundaries(boundaries);
      p.setProbabilityOfChange(probabilityOfChange);
      p.setUnoccupied(unoccupied);
      p.setAggregate(aggregate);
      p.setFlock(flock);
      p.setSearchRadius(searchRadius);
      schedule.scheduleRepeating(p);
      particleSpace.setObjectLocation(p, x, y);
      p.setXY(x, y);
      return p;
}

 

and so are start method will look like this:

 


 

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

	if(unoccupied && n <= gridWidth*gridHeight){ 
		for(int i=0;i<n;i++){ //make n particles
		    Particle p = placeAgents();			
		}
	}
	else if(!unoccupied) {
		for(int i=0;i<n;i++){//make n particles 
                    Particle p = placeAgents(); 		
                } 	
         } 	
        else { 	
            System.out.println("An error occurred: unoccupied && n > gridWidth*gridHeight");
		}
	}
}

 


 

In the class Particle, let’s first consolidate all of our walking methods into a method called wander.

 


 

	public void wander(SimState state){
		if(avoidCollisions){
			if(boundaries){
				moveWithCollisionsBoundaries( state);
			}
			else {
				moveWithCollisions(state); //if doCollisions is true
			}
		}
		else {
			if(boundaries){
				moveWithBoundaries(state);
			}
			else {
				move(state); //else move as before
			}
		}
		wander = false;
	}

 


 

and then our step method will look like this:

 


 

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

 


 

Next we must revise our movement methods so that they can implement only one cell occupied. To do this, let’s write a method that handles this problem the same way for each movement method.

 


 

	public void handleCells(SimState state){
	    SimulationEnvironment se =(SimulationEnvironment)state;
	    if(unoccupied){
		Bag b = se.particleSpace.getObjectsAtLocation(x, y);
		if(b == null || b.numObjs == 0){
			se.particleSpace.setObjectLocation(this, x, y);
			}
		else {// change the coordinates back to where they were
			Int2D xy = se.particleSpace.getObjectLocation(this);
			x = xy.x;
                        //reset the coordinates to the objects location
			y = xy.y;
			wander = true;
		}
	    }
	    else {
	    se.particleSpace.setObjectLocation(this, x, y);
	    }
	}

 


Notice that we have a new variable wander. It is a Boolean variable and we should add it and set it to false. We then need to modify our step method.

 


 

	public void step(SimState state) {
		if(wander){
			wander(state);
		}
		else {
			wander(state);
		}
	}

 


 

This looks odd have wander as the option to move in either case, but when we add aggregate and flock, it will make more sense.

Then we just add this method in place of the appropriate code to each of our four movement methods:

 


 

	public void move (SimState state){
		SimulationEnvironment se =(SimulationEnvironment)state;
		if(se.random.nextBoolean(probabilityOfChange)){
			xdir = se.random.nextInt(3)-1;
			ydir = se.random.nextInt(3)-1;
		}
		x+=xdir;
		y+=ydir;
		x = se.particleSpace.stx(x);
		y = se.particleSpace.sty(y);
		handleCells( state);
	}

 


 

	public void moveWithBoundaries(SimState state){
		SimulationEnvironment se =(SimulationEnvironment)state;
		if(se.random.nextBoolean(probabilityOfChange)){
			xdir = se.random.nextInt(3)-1;
			ydir = se.random.nextInt(3)-1;
		}
		x+=xdir;
		y+=ydir;
		x = bxdir(state,x);
		y = bydir(state,y);
		handleCells( state);
	}

 


 

	public void moveWithCollisions(SimState state){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		if(se.random.nextBoolean(probabilityOfChange)){
			xdir = se.random.nextInt(3)-1;
			ydir = se.random.nextInt(3)-1;
		}
		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);
		handleCells( state);
	}

 


 

	public void moveWithCollisionsBoundaries(SimState state){
		SimulationEnvironment se = (SimulationEnvironment)state;
		//convert the SimState to our SimulationEnvironment
		if(se.random.nextBoolean(probabilityOfChange)){
			xdir = se.random.nextInt(3)-1;
			ydir = se.random.nextInt(3)-1;
		}
		int newx = x + xdir; //generate the coordinates of the
		int newy = y + ydir; //new location to move to
		newx = bx(state,newx);
		newy = by(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);
		handleCells( state);
	}

 


 

Aggregation

What rules would allow agents to aggregate together? There are a number of ways to specify rules for how agents aggregate, but let’s first think about the problem and then come up with a solution.

One thing we know is that for agents to aggregate, they move move towards each other.  Let’s look at Dictyostelium discoideum aggregate.

It is clear from watching Dictyostelium discoideum move that they are moving towards each other but in a way that leads to aggregation into a single clump and perhaps more if we had a larger view.

Here is a video of an agent-based model of Dictyostelium discoideum aggregation:

One way to implement aggregation is to introduce a majority rule. Roughly speaking, move in the direction when you “see” the most other agents. There are two problems here: (1) What does it mean to “see” and how far can an agent “see”? (2) How do you move towards the majority of other agents in your view?

Let’s call the “view” of an agent the area around an agent that is defined by its search radius.

View of Agent x

View of Agent x

So, let’s begin by adding two variables. One a Boolean variable for whether to aggregate or not and the second, a search radius, which is the view of an agent. We will also add the appropriate set and get methods and pass these values from the SimulationEnvrionment to Particle agents we create.

Next, in Particles, we should add majority decision rules for each dimension in space.

 


 

		public int decideX (Bag neighbors){
		int posx =0, negx=0;
		for(int i=0; i<neighbors.numObjs;i++){
			Particle p = (Particle)neighbors.objs[i];
			if(p.x > x){
				posx++;
			}
			else if(p.x < x){
				negx++;
			}
		} // end for

		if(posx > negx){
			return 1;
		}
		else if(negx > posx){
			return -1;
		}
		else {
			return 0;
		}
	}

 


 

	public int decideY (Bag neighbors){
		int posy =0, negy=0;
		for(int i=0; i<neighbors.numObjs;i++){
			Particle p = (Particle)neighbors.objs[i];
			if(p.y > y){
				posy++;
			}
			else if(p.y < y){
				negy++;
			}
		} // end for

		if(posy > negy){
			return 1;
		}
		else if(negy > posy){
			return -1;
		}
		else {
			return 0;
		}
	}

 


 

We can then introduce our aggregation method and add it to our step method and we should be done!

 


 

	public void aggregate(SimState state){
		SimulationEnvironment se =(SimulationEnvironment)state;
		Bag neighbors =
                se.particleSpace.getNeighborsMaxDistance(x, y,
                     searchRadius, !boundaries, null, null, null);
		xdir = decideX(neighbors);
		ydir = decideY(neighbors);
		x+=xdir;
		y+=ydir;
		if(!boundaries){
			x = se.particleSpace.stx(x);
			y = se.particleSpace.sty(y);
		}
		else {
			x = bxdir(state,x);
			y = bydir(state,y);
		}
		handleCells( state);
	}

 


 

	public void step(SimState state) {
		if(wander){
			wander(state);
		}
		else if(aggregate){
			aggregate(state);
		}
		else {
			wander(state);
		}