SchellingTutorial

16
WCSS 2006 Introduction to MASON MASON is a discrete-event simulator designed for very large "swarm-" style simulations. It is written in Java and is free open source, available at the MASON home page . MASON has grids, continuous regions, networks, hexagonal spaces, etc., all in 2D or 3D, plus the flexibility to create your own "space" data structures. Any object can be inspected ("probed") in 2D or in 3D, and tracked with histograms, time series charts, etc. Among the simulators you may be familiar with, MASON is most similar to RePast. MASON was designed from first principles for large numbers of simulations on back-end clusters. To this end it has a number of architecture features which make it somewhat different from other simulation packages: MASON's models are separated from its visualizers. You can dynamically attach visualizers, detach them and run on the command line, or attach different ones. This allows us to checkpoint (serialize, or "freeze-dry") a running MASON simulation to a file, move it to a different architecture, and continue to run it. For example, we can run a MASON simulation with no visualization tools at all on a back-end Linux machine, checkpoint it, and examine/tweak the simulation mid-run on a front-end Mac under visualization. We can then put it back and continue to run from there. Among compiled-code multiagent simulators, MASON is unique in this respect. You'll see this architecture in the tutorial's separate Schelling.java (Model) and SchellingWithUI.java (Visualizer) files. MASON is written in highly portable Java, backward-compatible with Java 1.3.1, so as to run on as many legacy cluster systems as possible. MASON pays very careful attention to speed. It has special data structures which are designed to be hacked at a very low (and potentially unsafe) level if speed is called for. In this tutorial, such structures include Bag, IntBag, DoubleBag, and ObjectGrid2D. MASON also uses a highly efficient implementation of the Mersenne Twister random number generator, and has both "fast" and "slow but flexible" versions of much of its visualization toolkit. In the tutorial, we will be using these data structures and visualizers but will not be taking advantage of their faster procedures; rather we'll stick with slow-but-safe approaches. As a result, our simulation model will run at about 1/3 (or less) the speed it could run, and our visualizer will likewise run at about 1/5 the speed. But it will make the tutorial easier to understand. MASON is compatible with ECJ, our open-source evolutionary computation and stochastic search library, and presently among the very best available. MASON is highly modular. MASON was designed to be easily modified, extended, and otherwise bent any which way. We have tried hard to make the simulator core cleanly structured. A great many elements of MASON can be separated and used by themselves independent of the reset of the simulation package. About the Tutorial We will develop a simple version of the Schelling Segregation model. In this version of Schelling, Red and Blue agents live in a non-toroidal grid world. Each agent computes its happiness as a function of the sum of like-colored agents around it, weighted by distance from those agents. If an agent is not happy enough, it picks a random empty location to move to. All agents have an opportunity to move once per time tick. As mentioned above, our example tutorial has emphasized tutorial brevity and simplicity over speed; thus we use slow-but-safe mechanisms whenever. At the very end of the simulation, we do show how to use a "faster" drawing procedure, which you may be interested in. Here's the tutorial: 1. Get MASON Running (not in the tutorial -- we'll do this by hand) 2. A Minimum Simulation

Transcript of SchellingTutorial

WCSS 2006 Introduction to MASON

MASON is a discrete-event simulator designed for very large "swarm-" style simulations. It is written inJava and is free open source, available at the MASON home page. MASON has grids, continuous regions,networks, hexagonal spaces, etc., all in 2D or 3D, plus the flexibility to create your own "space" datastructures. Any object can be inspected ("probed") in 2D or in 3D, and tracked with histograms, time seriescharts, etc. Among the simulators you may be familiar with, MASON is most similar to RePast.

MASON was designed from first principles for large numbers of simulations on back-end clusters. To thisend it has a number of architecture features which make it somewhat different from other simulationpackages:

MASON's models are separated from its visualizers. You can dynamically attach visualizers,detach them and run on the command line, or attach different ones. This allows us to checkpoint(serialize, or "freeze-dry") a running MASON simulation to a file, move it to a different architecture,and continue to run it. For example, we can run a MASON simulation with no visualization tools atall on a back-end Linux machine, checkpoint it, and examine/tweak the simulation mid-run on afront-end Mac under visualization. We can then put it back and continue to run from there. Amongcompiled-code multiagent simulators, MASON is unique in this respect.

You'll see this architecture in the tutorial's separate Schelling.java (Model) andSchellingWithUI.java (Visualizer) files.

MASON is written in highly portable Java, backward-compatible with Java 1.3.1, so as to run onas many legacy cluster systems as possible.

MASON pays very careful attention to speed. It has special data structures which are designed tobe hacked at a very low (and potentially unsafe) level if speed is called for. In this tutorial, suchstructures include Bag, IntBag, DoubleBag, and ObjectGrid2D. MASON also uses a highly efficientimplementation of the Mersenne Twister random number generator, and has both "fast" and "slowbut flexible" versions of much of its visualization toolkit.

In the tutorial, we will be using these data structures and visualizers but will not be takingadvantage of their faster procedures; rather we'll stick with slow-but-safe approaches. As aresult, our simulation model will run at about 1/3 (or less) the speed it could run, and our visualizerwill likewise run at about 1/5 the speed. But it will make the tutorial easier to understand.

MASON is compatible with ECJ, our open-source evolutionary computation and stochastic searchlibrary, and presently among the very best available.

MASON is highly modular. MASON was designed to be easily modified, extended, and otherwisebent any which way. We have tried hard to make the simulator core cleanly structured. A great manyelements of MASON can be separated and used by themselves independent of the reset of thesimulation package.

About the Tutorial

We will develop a simple version of the Schelling Segregation model. In this version of Schelling, Red andBlue agents live in a non-toroidal grid world. Each agent computes its happiness as a function of the sum oflike-colored agents around it, weighted by distance from those agents. If an agent is not happy enough, itpicks a random empty location to move to. All agents have an opportunity to move once per time tick.

As mentioned above, our example tutorial has emphasized tutorial brevity and simplicity over speed; thuswe use slow-but-safe mechanisms whenever. At the very end of the simulation, we do show how to use a"faster" drawing procedure, which you may be interested in.

Here's the tutorial:

1. Get MASON Running (not in the tutorial -- we'll do this by hand)2. A Minimum Simulation

2. A Minimum Simulation3. Add a Field (a representation of space)4. Define our Agents5. Set Up the Agents6. Build a Minimal GUI7. Add a Display8. Make the Agents Do Something9. Add Local Inspectability

10. Add Global Inspectability11. Add a Histogram12. Add a Time Series Chart13. Speed Up Drawing14. Checkpoint the Simulation

Get MASON Running

We'll get this set up by hand. But in short, you'll need to have the following in your CLASSPATH:

The mason directory. This holds the MASON code and documentation.The jcommon-1.0.0.jar file. This holds utility code used by JFreeChart.The jfreechart-1.0.1.jar file. This is the primary code for JFreeChart, which MASON employsto draw charts and graphs.The itext-1.2.jar file. This holds the iText PDF document generation library, which MASONuses to output charts and graphs in publication-quality fashion.The jmf.jar file. This holds Sun's Java Media Framework, which MASON uses to produce moves.You can also download an operating system-specific version of the framework with extra movieexport options if you like.The quaqua-colorchooser-only.jar file. (OS X Users only). Java's color picker is poor for OSX: this library provides a nice replacemen. I have no idea if it works well for Windows or not -- try itand see!

Additionally, you'll need to have Java3D installed -- though not used for this tutorial, it'll make compilingMASON much simpler.

None of the additional libraries are actually required to run MASON: without them it will simply refuse tomake a given operation available (such as generating charts and graphs). However to compile a MASONsimulation, these libraries are required unless you go in and manually remove the MASON code whichrelies on them. This can be reasonably easily done, but it's inconvenient.

A Minimum Simulation

We begin with a minimum simulation that does nothing at all. Create a file called Schelling.java, storedin a directory called wcss located in the sim/app directory of MASON. The file looks like this:

package sim.app.wcss;

import sim.engine.*;

import ec.util.*;

import sim.util.*;

public class Schelling extends SimState

{

public Schelling(long seed)

{

super(new MersenneTwisterFast(seed), new Schedule());

}

/** Resets and starts a simulation */

public void start()

{

super.start(); // clear out the schedule

}

public static void main(String[] args)

{

doLoop(Schelling.class, args);

System.exit(0);

}

}

An entire MASON simulation model is hung somewhere off of a single instance of a subclass ofsim.engine.SimState. Our subclass is going to be called Schelling. A SimState has two basic instancevariables: an ec.util.MersenneTwisterFast random number generator called random, and asim.engine.Schedule called schedule. We create these and give them to SimState through super().

MersenneTwisterFast is the fastest existing Java implementation of the Mersenne Twister random numbergenerator. I wrote it :-) and it's used in lots of production code elsewhere, including NetLogo. The generatoris essentially identical to java.util.Random in its API, except that it is not threadsafe.

Schedule is MASON's event schedule. You can schedule "agents" (implementations ofsim.engine.Steppable) on the schedule to be fired and removed at a later date. When the Schedule isstepped, it advances to the minimum-scheduled agent and fires it (calling its step(SimState) method).Agents may be scheduled for the same timestep. Within a timestep, agents' firing order may be specifiedrelative to one another. Agents with the same timestep and ordering are fired in random order. Agents mayalso be scheduled to be fired at regular intervals instead of being one-shot. Our agents will be all fired atregular intervals and will not use any particular firing order (they'll be random relative to other agentsscheduled at that timestep).

start() is called by MASON whenever a new simulation run is begun. You can do whatever you like in thismethod, but be sure to call super.start() first. There's also a finish().

MASON models typically can be run both from the command line and also visualized under a GUI.Command-line model runs are usually done by calling main(String[] args) on your SimState subclass.Usually all that needs to be done here is to call the convenience method doLoop and then callSystem.exit(0).

doLoop(...) is a convenience method which runs a MASON simulation loop. A basic loop -- which youcould do yourself in main() if you wanted -- starts a MASON simulation (calling start()), steps its Scheduleuntil the Schedule is out of events to fire, and then cleans up (calling finish()) and quits. doLoop does a littlebit more: it also optionally prints out a bit of statistics information, checkpoints to file and/or recovers fromcheckpoint, handles multiple jobs, and stops the simulation in various situations, among others. It does mostof what you'd want.

Compile and run the simulation (as java sim.app.wcss.Schelling)

(Files in folder 1 if you're just following along)

You'll get back something like this:

MASON Version 12. For further options, try adding ' -help' at end.

Job: 0 Seed: 1155246793094

Starting sim.app.wcss.Schelling

Exhausted

Very exciting.

Add a Field

So far our model only has a representation of time (Schedule). Let's add a representation of space. MASONhas many representations of space built-in: they're called fields. In our Schelling model, we'll use a simpletwo-dimensional grid of Objects. MASON can do a lot more representations than this, but it'll serve for ourpurposes in the tutorial. In the Schelling.java file, add:

public int neighborhood = 1;

public double threshold = 3;

public int gridHeight = 100;

public int gridWidth = 100;

public ObjectGrid2D grid = new ObjectGrid2D(gridWidth, gridHeight); // a dummy one to start

public Bag emptySpaces = new Bag();

You will also need to add a new import statement:

import sim.field.grid.*;

neighborhood is our Schelling neighborhood.

threshold is the minimum number of like-us agents (ourselves excluded) in our neighborhood before webegin to feel "comfortable".

gridHeight and gridWidth are going to be the width and height of our Schelling grid world.

emptySpaces is a sim.util.Bag of locations in the grid world where no one is located. This will help ouragents find new places to move to rapidly. A Bag is a MASON object that is very similar to an ArrayList orVector. The difference is that a Bag's underlying array is publicly accessible, so you can do rapid scans overit and changes to it. This allows Bag to be three to five times the speed of ArrayList.

grid is a sim.field.grid.ObjectGrid2D which will store our Schelling agents. It'll represent the world inwhich they live. This class is little more than a wrapper for a publicly accessible 2D array of Objects, plussome very convenient methods. We initialize it with a width and a height.

Define our Agents

We're going to create two kinds of agents: Red and Blue. They operate identically, except that they havedifferent opinions as to what a "similar to me" agent is.

Each agent will both live in the grid world and be scheduled on the Schedule to move itself about whenstepped. It's important to understand that this isn't a requirement. In MASON, the objects that live in Spacemay have nothing to do with the Steppable objects that live on the Schedule (in "Time", so to speak). Wedefine Agents as those objects which live on the Schedule and exist to manipulate the world. MASONcommonly calls them "Steppable"s, since they adhere to the Steppable interface so the Schedule can stepthem. Agents may not actually have a physical presence at all: though ours will in this example.

Create a new file next to Schelling.java called Agent.java. In this file, add:

package sim.app.wcss;

import sim.util.*;

import sim.engine.*;

public abstract class Agent implements Steppable

{

Int2D loc;

public Agent(Int2D loc)

{

this.loc = loc;

}

public abstract boolean isInMyGroup(Agent obj);

public void step( final SimState state )

{

}

}

loc is a sim.util.Int2D. This is a simple class which holds two integers (x and y). Our agent will use Int2Dsto store the current location of the agent on the grid. Java already has similar classes, such asjava.awt.Point, but Int2D is immutable, meaning that once its x and y values are set, they cannot bechanged. This makes Int2D appropriate for storing in hash tables -- which MASON uses extensively --unlike Point. Non-mutable objects used as keys in hash tables are dangerous: they can break hash tables.

step(SimState) is called by the Schedule when this agent's time has come. We'll fill it in with interestingthings later.

Our Red and Blue agents will both subclass from Agent. In this example, our Agent will store its ownlocation in the world so it doesn't have to scan the world every time to find it out (an expensive prospect).Additionally, each Agent will define a method called isInMyGroup(Agent) which returns true if the

provided agent is of the same "kind" as the original Agent. Let's make the Red and Blue agents. Create afile called Blue.java and add the following code:

package sim.app.wcss;

import sim.util.*;

import sim.engine.*;

public class Blue extends Agent

{

public Blue(Int2D loc)

{

super(loc);

}

public boolean isInMyGroup(Agent obj)

{

return (obj!=null && obj instanceof Blue);

}

}

Likewise create a file called Red.java with similar code:

package sim.app.wcss;

import sim.util.*;

import sim.engine.*;

public class Red extends Agent

{

public Red(Int2D loc)

{

super(loc);

}

public boolean isInMyGroup(Agent obj)

{

return (obj!=null && obj instanceof Red);

}

}

Set up the Agents

Now we're going to define the start() method in the Schelling.java file. Recall that this method is calledwhen MASON is stating a brand new simulation. In this method we do several things:

1. Call super.start(). This lets MASON reset the schedule.2. Create a fresh empty space list and grid.3. For each spot in the grid

1. Determine if the spot should be filled with RED, BLUE, or empty (randomly chosen using theredProbability and blueProbability variables).

2. If RED or BLUE, create the appropriate agent with its spot location, put it in that spot, andschedule the agent to be fired on each time step.

3. If empty, add that spot to our empty space list.

Here's the code to add to the Schelling.java file, replacing the existing empty start() method.

public double redProbability = 0.333;

public double blueProbability = 0.333;

public void start()

{

super.start(); // clear out the schedule

// clear out the grid and empty space list

emptySpaces = new Bag();

grid = new ObjectGrid2D(gridWidth, gridHeight); // first, all are null ("EMPTY")

// add the agents to the grid and schedule them in the schedule

for(int x=0 ; x<gridWidth ; x++)

for(int y=0 ; y<gridHeight ; y++)

{

Steppable agent = null;

double d = random.nextDouble();

if (d < redProbability)

{

agent = new Red(new Int2D(x,y));

schedule.scheduleRepeating(agent);

}

else if (d < redProbability + blueProbability)

{

agent = new Blue(new Int2D(x,y));

schedule.scheduleRepeating(agent);

}

else // add this location to empty spaces list

{

emptySpaces.add(new Int2D(x,y));

}

grid.set(x,y,agent);

}

}

Now, at the beginning of the simulation, our agents are placed into the grid and scheduled to be steppedeach timestep.

Compile and run the simulation ( as java sim.app.wcss.Schelling -until 2000 )

(Files in folder 2 if you're just following along)

You'll get back something like this:

MASON Version 12. For further options, try adding ' -help' at end.

Job: 0 Seed: 1155675734985

Starting sim.app.wcss.Schelling

Steps: 500 Time: 499 Rate: 412.20115

Steps: 1000 Time: 999 Rate: 425.17007

Steps: 1500 Time: 1499 Rate: 423.72881

Steps: 2000 Time: 1999 Rate: 424.44822

Quit

This tells us that MASON (on my laptop) is running Schelling at a rate of about 150 ticks per second. Ineach tick, MASON is stepping the Schedule once. When a Schedule is stepped, it advances to theminimally-scheduled timestep, selects all the agents scheduled for that timestep, shuffles their order (if theyhave no user-defined orderings among them) and steps each one.

In our simulation, all the red and blue agents are being shuffled and stepped once per Schedule tick. Wehave about 6500 such agents on the (100x100) board, so that comes to just about a million steps per secondon my very slow laptop.

Of course, our agents aren't yet doing anything when they're stepped. We'll get to that in a bit. But first, let'svisualize the agents not doing anything. :-)

Build a Minimal GUI

So far we've been only building the model and trying it out on the command line. MASON's GUI facilitiesare entirely separate from its model, and can be hooked to it or unhooked in real time.

A typical MASON GUI centers on a subclass you will write of sim.display.GUIState. The GUIStateinstance will hold onto your SimState model instance, loading it, checkpointing it, etc. Additionally theGUIState will house a sim.display.Console, the GUI widget that allows you to manipulate the Schedule,and one or more displays which describe GUI windows displaying your fields. The displays draw andmanipulate the fields using portrayal objects designed for the various fields.

We begin with a minimal GUI that doesn't have any displays at all. Create a new file calledSchellingWithUI.java and add to it the following:

package sim.app.wcss;

import sim.engine.*;

import sim.display.*;

public class SchellingWithUI extends GUIState

{

public SchellingWithUI() { super(new Schelling(System.currentTimeMillis())); }

public SchellingWithUI(SimState state) { super(state); }

public static String getName() { return "Schelling Segregation WCSS2006 Tutorial"; }

public void init(Controller c)

{

super.init(c);

}

public void start()

{

super.start();

}

public void load(SimState state)

{

super.load(state);

}

public static void main(String[] args)

{

SchellingWithUI schUI = new SchellingWithUI();

Console c = new Console(schUI);

c.setVisible(true);

}

}

When we run main(...), it creates an instance of the SchellingWithUI class, then creates a Console attachedto the class. The Console is then set visible (it's a window).

Some further explanation.

Constructors. The standard constructor is passed a SimState object. This is your model (in our case,Schelling), and it will be stored in the instance variable state. Our default empty constructor calls thestandard constructor with a Schelling model created with a random number seed. You can create a Schellingmodel in some other way if you wish.

init() This method is called by the Console when it attaches itself to the GUIState (in our case,SchellingWithUI). The Controller is the Console itself (it's a subclass of Controller). By default we just callsuper.init(...). We'll flesh out this method to set up the displays etc. later.

start() This method is called when the user presses the PLAY button. There's also a finish() method whenthe STOP button is pressed, but it's rarely used. By default we call super.start(), which calls start() on ourunderlying Schelling model. We'll flesh out this method to reset the portrayals drawing the model later on.

load(SimState) This method is called when the user loads a previously checkpointed simulation. It'stypically more or less the same code as start(), as we'll soon see.

getName() returns a short name for the simulation, which appears in the title bar of the Console.

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 3 if you're just following along)

You'll get a window pop up like the one at right.This is the Console. It's typically used to play,pause, and stop a simulation, as well as inspectcertain features of it. The Console can alsocheckpoint out a simulation in mid-run and restore asimulation from checkpoint. These checkpoints canalso be saved and restored on the command linewithout a GUI, and traded between GUI and non-GUI versions.

Notice that the front of the Console contains an

HTML document which you can customize as youlike. The document is defined by the index.htmlfile in the same directory as theSchellingWithUI.class file. Additionally, weput an image in the HTML file, defined by theicon.png file.

Note: this particular link doesn't work. This isbecause it's a link to Wikipedia, and this past monthWikipedia started issuing 403 errors to webbrowsers that don't provide the "user-agent" property. Sun's Java implementation stupidly doesn't providethat property. We're looking into how to fix it.

If you press PLAY, you'll see the simulation running but nothing being displayed. Exciting!

Add a Display

First we need to add a few new import statements to SchellingWithUI.java:

import sim.portrayal.*;

import sim.portrayal.grid.*;

import sim.portrayal.simple.*;

import java.awt.*;

import javax.swing.*;

The portrayal import statements will allow us to create portrayals which draw grid world and its respectiveobjects. In general, here's what we're going to set up:

1. A sim.display.Display2D will provide the window and scrollable, zoomable drawing surface.2. A sim.portrayal.grid.ObjectGridPortrayal2D, designed to draw ObjectGrid2Ds, will be attached

to the Display2D to display our model's world.3. The ObjectGridPortrayal2D will rely on various Simple Portrayals to draw each of the objects in the

world. Specifically:1. A red sim.portrayal.simple.OvalPortrayal2D will draw the Red objects.2. A blue sim.portrayal.simple.RectanglePortrayal2D will draw the Blue objects.3. An empty sim.portrayal.SimplePortrayal2D will draw the null spaces.

Here's the revised versions of init, start, and load, plus some extra code:

public Display2D display;

public JFrame displayFrame;

ObjectGridPortrayal2D gridPortrayal = new ObjectGridPortrayal2D();

public void init(Controller c)

{

super.init(c);

// Make the Display2D. We'll have it display stuff later.

Schelling sch = (Schelling)state;

display = new Display2D(sch.gridWidth * 4, sch.gridHeight * 4,this,1);

displayFrame = display.createFrame();

c.registerFrame(displayFrame); // register the frame so it appears in the "Display" list

// attach the portrayals

display.attach(gridPortrayal,"Agents");

// specify the backdrop color -- what gets painted behind the displays

display.setBackdrop(Color.black);

displayFrame.setVisible(true);

}

public void setupPortrayals()

{

// tell the portrayals what to portray and how to portray them

gridPortrayal.setField(((Schelling)state).grid);

gridPortrayal.setPortrayalForClass(Red.class, new OvalPortrayal2D(Color.red));

gridPortrayal.setPortrayalForClass(Blue.class, new RectanglePortrayal2D(Color.blue));

gridPortrayal.setPortrayalForNull(new SimplePortrayal2D()); // empty

display.reset(); // reschedule the displayer

display.repaint(); // redraw the display

}

public void start()

{

super.start();

setupPortrayals(); // set up our portrayals

}

public void load(SimState state)

{

super.load(state);

setupPortrayals(); // we now have new grids. Set up the portrayals to reflect this

}

Here's what's going on.

init(Controller c) has been extended to create a Display2D object and register it with the Console, allowingyou to hide and show the Display2D from the Console, and also close the Display2D cleanly when theConsole closes. We then attach an ObjectGridPortrayal2D to the display, calling it "Agents". We set thebackground color of the Display2D to be black, and display it. This sets up the display and grid portrayal tobe used for multiple model instantiations.

start() and load(...) both have roughly the same code, so we have grouped that code into a method calledsetupPortrayals(). Here is code called whenever a new model is begun or loaded from checkpoint. Thismethod tells the grid portrayal that it's supposed to portray the grid we created in our Schelling model.Notice that we can access our Schelling model through the state variable. The method then attaches threesimple portrayals that the grid portrayal will call upon to draw objects in the grid. An OvalPortrayal2Ddraws our Red instances, a RectanglePortrayal2D draws our Blue instances, and a SimplePortrayal2D(which does nothing) "draws" our null regions. Last, we tell the display to reset it self (which causes it toreschedule itself to be updated each timestep) and draws it one time.

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 4 if you're just following along)

This time we can see the agents: but if weplay the simulation, they don't move.This is expected, as the agents don't doanything yet. They just sit there. Noticethat each time you stop and start thesimulation, the scene changes: a newmodel has been constructed.

You can zoom in and scroll around if youlike. Try increasing the Scale in thedisplay. You can also take pictures andmake (at present pretty boring) movies.

Make the Agents

Do Something

Now we get to the crux of the simulation.We're going to modify the agents'step(...) method so they perform aSchelling-like behavior. Specifically eachagent (in random order) will do thefollowing:

1. Examine the agents' neighborhoodto see how many like-colored agents exist (himself excluded). A neighborhood of size N is defined assquare 2N-1 on a side, centered on the agent.

2. If the number of like-colored agents (weighted by distance to the agent) does not exceed a certainthreshold, the agent will pick a random empty square and move there. Else he'll stay put. If in theunlikely chance there are no random squares, the agent will not move.

To do this, we change the step(...) method and add a few variables to the Agent.java file:

IntBag neighborsX = new IntBag(9);

IntBag neighborsY = new IntBag(9);

double happiness = 0;

public void step( final SimState state )

{

Schelling sch = (Schelling)state;

if (sch.emptySpaces.size() == 0) return; // nowhere to move to!

// get all the places I can go. This will be slow as we have to rely on grabbing neighbors.

sch.grid.getNeighborsMaxDistance(loc.x,loc.y,sch.neighborhood,false,neighborsX,neighborsY);

// compute happiness

happiness = 0;

int len = neighborsX.size();

for(int i = 0; i < len; i++)

{

int x = neighborsX.get(i);

int y = neighborsY.get(i);

Agent agent = (Agent)sch.grid.get(x,y);

if (agent != this && isInMyGroup(agent)) // if he's just like me, but he's NOT me

{

// increment happiness by the linear distance to the guy

happiness += 1.0/Math.sqrt((loc.x-x)*(loc.x-x) + (loc.y-y)*(loc.y-y));

}

}

if (happiness >= sch.threshold) return; // I'm happy -- we're not moving

// Okay, so I'm unhappy. First let's pull up roots.

sch.grid.set(loc.x,loc.y,null);

// Now where to go? Pick a random spot from the empty spaces list.

int newLocIndex = state.random.nextInt(sch.emptySpaces.size());

// Swap the location in the list with my internal location

Int2D newLoc = (Int2D)(sch.emptySpaces.get(newLocIndex));

sch.emptySpaces.set(newLocIndex,loc);

loc = newLoc;

// Last, put down roots again

sch.grid.set(loc.x,loc.y,this);

}

This method will require some explanation.

happiness will hold the agent's happiness. It's an instance variable rather than a local variable because lateron -- not now -- we'd like to tap into it.

neighborsX and neighborsY are IntBags. An IntBag is like a Bag, but it stores ints, not Objects. We aredefining these two variables to avoid having to recreate them over and over again, creating needless garbagecollection.

If there are no empty spaces, our agent does nothing. Otherwise, the IntBags are passed into thegetNeighborsMaxDistance , along with the x and y location of the agent and his desired neighborhood.("false" states that the environment is non-toroidal). This method will compute integer pairs for everypossible location in this neighborhood, and place them into the neighborsX and neighborsY IntBagsrespectively. We can then scan through these bags and test each such location.

Obviously, it's not too complicated to just manually wander through the grid squares ourselves (the array isthe agents field); but this illustrates one way of getting neighborhood information. Realize that this istrading convenience for speed: we could be significantly faster if we just dug through the grid manually.The HeatBugs example in MASON shows how to do this.

The for-loop in our step method is extracting the agent (if any) located at position , where x and y are thenext coordinate values in the IntBag. If the agent is similar to us, then we increase val by the linear distancefrom us to that coordinate. If we exceed the threshold, we're happy and don't do anything. Else it's time toget up and move!

Moving is easy. First we set our location in the grid to null. Then we pick a random location from amongthe spaces in the empty spaces list. Next we swap our current location with the chosen empty space in thelist. This removes the empty space from the list and marks our old location as empty (vacant) in the list.Last, we place ourselves in the chosen space on the grid. And we've moved!

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 5 if you're just following along)

This time pressing PLAY should be moreinteresting. Here's one convergencescenario. Note that the frame rate (pick"rate" from the pop-up menu at bottom-right of the Console) is pretty crummy(on my laptop, it's about 12 fps).

Run the simulation on the

command line ( as java

sim.app.wcss.Schelling -until

2000 )

(Files again in folder 5 if you're justfollowing along)

If you run it without a display, notice thedramatic speed-up: now we have a rate ofabout 130! Note that this is slower thanbefore: the agents could be sped up a bit(to about 170 fps) by having them quittheir for-loops as soon as they detecthappiness in excess of the threshold; butwe want to compute the happiness forlater, so we'll have to stick with theslower version for now.

MASON Version 12. For further options, try adding ' -help' at end.

Job: 0 Seed: 1155675932470

Starting sim.app.wcss.Schelling

Steps: 250 Time: 249 Rate: 124.13108

Steps: 500 Time: 499 Rate: 126.83917

Steps: 750 Time: 749 Rate: 126.6464

Steps: 1000 Time: 999 Rate: 126.83917

Steps: 1250 Time: 1249 Rate: 126.19889

Steps: 1500 Time: 1499 Rate: 124.62612

Steps: 1750 Time: 1749 Rate: 120.36591

Steps: 2000 Time: 1999 Rate: 121.83236

Quit

Add Local Inspectability

The MASON GUI can also inspect object properties. For example, we can add a simple read-onlyhappiness property to our agents to be displayed when the user double-clicks on a given square. Add thefollowing method to Agent.java:

public double getHappiness() { return happiness; }

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 6 if you're just following along)

When you double-click on a red square, the Console will switch to showing its inspected properties -- andthere's happiness! Next to the happiness property is a magnifying glass: this is the tracker menu, showingwhat tracking options you have for this property. For a double (like happiness), you can plot the value aswell.

Note that the values shown in the inspector are for the location and not the object. That is, if a new objectmoves into that location, the inspector will show its properties instead. Usually you'd prefer to track anobject as it moves about the world instead. It's expensive to track objects in a simple two-dimensional arraylike this; you have to scan through the array to find the object. Instead, you should use a different kind ofgrid: a sim.field.SparseGrid2D, which uses hash tables to store locations rather than a 2D array. Inspectorsfor a SparseGrid2D will automatically track objects.

Add Global Inspectability

In addition to inspecting individual objects in a field, you can also add inspectable properties to the entiremodel. Let's start by adding fields which specify the probability of reds versus blues versus empty, plus thethreshold, plus the neighborhood. In the SchellingWithUI.java, add:

public Object getSimulationInspectedObject() { return state; }

This tells MASON that it should add a global model inspector for the model. Now the Console will containan additional Model tab for inspecting the entire model.

We don't have anything interesting to inspect in the model yet. Let's add some Java Properties to the model.Add to Schelling.java the following read/write properties, which allow us to inspect and modify theneighborhood, threshold, redProbability, and blueProbability variables.

public int getNeighborhood() { return neighborhood; }

public void setNeighborhood(int val) { if (val > 0) neighborhood = val; }

public double getThreshold() { return threshold; }

public void setThreshold(double val) { if (val >= 0) threshold = val; }

public double getRedProbability() { return redProbability; }

public void setRedProbability(double val)

{ if (val >= 0 && val + blueProbability <= 1) redProbability = val; }

public double getBlueProbability() { return blueProbability; }

public void setBlueProbability(double val)

{ if (val >= 0 && val + redProbability <= 1) blueProbability = val; }

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 7 if you're just following along)

The properties we created were read/write (they had both "get" and "set" version). Now if you select theModel tab, you can view and change those features of the model.

Add a Histogram

Let's add a read-only Java Property which returns an array or sim.util.DoubleBag of happiness valuesgathered from the population of agents. A DoubleBag is like an IntBag but it stores Doubles. That's easy todo: just scan through the grid and grab the happiness of each non-null agent and stuffs it in the bag. Add thefollowing to Schelling.java

public DoubleBag getHappinessDistribution()

{

DoubleBag happinesses = new DoubleBag();

for(int x=0;x < gridWidth;x++)

for(int y=0;y < gridHeight;y++)

if (grid.get(x,y)!=null)

happinesses.add(((Agent)(grid.get(x,y))).getHappiness());

return happinesses;

}

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 8 if you're just following along)

Pause the simulation. Then go to the Models tab and click on the magnifying glass icon next to"HappinessDistribution". Choose "Make Histogram", and a histogram pops up. You might want to set it toredraw faster than every 0.5 seconds. Unpause the simulation and watch the histogram change. Note thatthis histogram is for this model run only: if you stop and run again, the histogram is frozen and you'll needto make a new histogram.

You can have multiple histograms overlaid on one another if you like.

Add a Time Series Chart

Let's add a read-only Java Property which can be plotted in time series: the mean of the happinessdistribution. Add the following to Schelling.java

public double getMeanHappiness()

{

int count = 0;

double totalHappiness = 0;

for(int x=0;x < gridWidth;x++)

for(int y=0;y < gridHeight;y++)

if (grid.get(x,y)!=null)

{

count++;

totalHappiness += ((Agent)(grid.get(x,y))).getHappiness();

}

if (count==0) return 0;

else return totalHappiness / count;

}

You will probably want your model inspector to also be volatile. This means that MASON should update itevery time tick. Since updating the model inspector every time tick is potentially expensive, MASON

doesn't do it by default, and instead relies on you to press the "refresh" button when you want to see up-to-date data. To change this behavior, you need to set the inspector that MASON will receive to be volatile, asfollows. In SchellingWithUI.java, add:

public Inspector getInspector()

{

Inspector i = super.getInspector();

i.setVolatile(true);

return i;

}

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 9 if you're just following along)

Follow the same procedure for getting a Histogram: except instead choose to make a chart from the"MeanHappiness" property. Unpause the simulation and something like this will occur:

Speed Up Drawing

The reason that drawing is slow is because each object is independently being drawn with a rectangle or acircle; and furthermore MASON must look up the simple portrayal for each object. This allows considerableflexibility, but it's slow. There's a faster grid portrayal, calledsim.portrayal.grid.FastObjectGridPortrayal2D which draws all objects as colored squares. Indeed formany "slow" MASON portrayals, they're often a "fast" one.

The fast portrayal works by querying each object to extract a "number" from it. It then uses asim.util.SimpleColorMap to map the number to a color, and sets that rectangle to that color. So we need todo two things. First, we need to have each of our agents return numbers, and second, we need to add themap and change the portrayal.

Set the Agents to Return Numbers

We begin by defining Agent to be sim.util.Valuable, requiring it to implement the method doubleValue().Change Agent.java to look like this:

public abstract class Agent implements Steppable, Valuable

{

...

public abstract double doubleValue();

...

Next we need to add the doubleValue() method to our Red and Blue agents. Add the following method toRed.java:

public double doubleValue() { return 1; }

Likewise add the following method to Blue.java:

public double doubleValue() { return 2; }

Switch to a FastObjectGridPortrayal2D

In SchellingWithUI.java, change the gridPortrayal variable and setupPortrayals() method like this:

FastObjectGridPortrayal2D gridPortrayal = new FastObjectGridPortrayal2D();

public void setupPortrayals()

{

// tell the portrayals what to portray and how to portray them

gridPortrayal.setField(((Schelling)state).grid);

gridPortrayal.setMap(new sim.util.gui.SimpleColorMap(

new Color[]{new Color(0,0,0,0), Color.red, Color.blue}));

display.reset(); // reschedule the displayer

display.repaint(); // redraw the display

}

Now instead of defining portrayals to draw the individuals, we're simply setting a map which maps numbersto colors. Null values will be 0 (which maps to transparent). Red individuals will provide 1, which maps tored. Blue individuals will provide 2, which maps to blue.

Compile and run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files in folder 10 if you're just following along)

That's a bit faster. I get a frame rate ofabout 40. Note from the picture at rightthat now all objects are being displayedas rectangles. No more ovals.

Checkpoint the

Simulation

MASON models can be checkpointed("freeze dried") and restored, with orwithout a GUI, and often across differentplatforms and operating systems. This isimportant for running on back-endservers and visualizing the current resultson front-end workstations, or savingrecoverable snapshots in case a machinegoes down. Let's try it.

Run the simulation ( as java

sim.app.wcss.Schelling -until

10000 -docheckpoint 1000 )

(Files again in folder 10 if you're justfollowing along)

This says to run the simulation no more than 10000 timesteps, writing out a checkpoint every 1000 steps.You'll get something like this:

MASON Version 12. For further options, try adding ' -help' at end.

Job: 0 Seed: 1155676001239

Starting sim.app.wcss.Schelling

Steps: 250 Time: 249 Rate: 117.53644

Steps: 500 Time: 499 Rate: 122.3092

Steps: 750 Time: 749 Rate: 120.71463

Steps: 1000 Time: 999 Rate: 121.24151

Checkpointing to file: 1000.0.Schelling.checkpoint

Steps: 1250 Time: 1249 Rate: 88.55827

Steps: 1500 Time: 1499 Rate: 124.06948

Steps: 1750 Time: 1749 Rate: 121.6545

Steps: 2000 Time: 1999 Rate: 123.57884

Checkpointing to file: 2000.0.Schelling.checkpoint

Steps: 2250 Time: 2249 Rate: 101.0101

Steps: 2500 Time: 2499 Rate: 123.88503

Steps: 2750 Time: 2749 Rate: 124.93753

Steps: 3000 Time: 2999 Rate: 124.62612

Checkpointing to file: 3000.0.Schelling.checkpoint

Steps: 3250 Time: 3249 Rate: 99.04913

Steps: 3500 Time: 3499 Rate: 122.54902

...

...and so on. Each 1000 timesteps, a checkpoint file is written out.

Run the simulation ( as java sim.app.wcss.SchellingWithUI )

(Files again in folder 10 if you're just following along)

Choose "Open..." from the File menu. Select 1000.0.Schelling.checkpoint. And there's the model, attimestep 1000. At this point it's probably converged, so not so interesting. But run it a bit and save it outagain as my.checkpoint ("Save As..." from the File menu).

Run the simulation ( as java sim.app.wcss.Schelling -checkpoint my.checkpoint )

(Files again in folder 10 if you're just following along)

Notice that MASON on the command line starts right up where you saved it under the GUI and goes fromthere.

More Information

Go to the MASON Home Page. From there you can:

Join the mailing list and ask questions.Download the latest version of MASON (including seven more tutorials!).Access online documentation.

Further questions? See Sean Luke during the conference and he'll gladly work through them with you. Orsend the MASON development team questions directly at mason-help /-at-/ cs.gmu.edu (send generaldevelopment questions to the mailing list instead, please).