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

2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.

A Minimum Simulation Add a Field (a representation of space) Define our Agents Set Up the Agents Build a Minimal GUI Add a Display Make the Agents Do Something Add Local Inspectability Add Global Inspectability Add a Histogram Add a Time Series Chart Speed Up Drawing 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 employs to draw charts and graphs. The itext-1.2.jar file. This holds the iText PDF document generation library, which MASON uses 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 movie export options if you like. The quaqua-colorchooser-only.jar file. (OS X Users only). Java's color picker is poor for OS X: this library provides a nice replacemen. I have no idea if it works well for Windows or not -- try it and see! Additionally, you'll need to have Java3D installed -- though not used for this tutorial, it'll make compiling MASON much simpler. None of the additional libraries are actually required to run MASON: without them it will simply refuse to make a given operation available (such as generating charts and graphs). However to compile a MASON simulation, these libraries are required unless you go in and manually remove the MASON code which relies 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, stored in 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 of sim.engine.SimState. Our subclass is going to be called Schelling. A SimState has two basic instance variables: an ec.util.MersenneTwisterFast random number generator called random, and a sim.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 number generator. I wrote it :-) and it's used in lots of production code elsewhere, including NetLogo. The generator is 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 of sim.engine.Steppable) on the schedule to be fired and removed at a later date. When the Schedule is stepped, 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 specified relative to one another. Agents with the same timestep and ordering are fired in random order. Agents may also be scheduled to be fired at regular intervals instead of being one-shot. Our agents will be all fired at regular intervals and will not use any particular firing order (they'll be random relative to other agents scheduled at that timestep). start() is called by MASON whenever a new simulation run is begun. You can do whatever you like in this method, 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 call System.exit(0). doLoop(...) is a convenience method which runs a MASON simulation loop. A basic loop -- which you could do yourself in main() if you wanted -- starts a MASON simulation (calling start()), steps its Schedule until the Schedule is out of events to fire, and then cleans up (calling finish()) and quits. doLoop does a little bit more: it also optionally prints out a bit of statistics information, checkpoints to file and/or recovers from checkpoint, handles multiple jobs, and stops the simulation in various situations, among others. It does most of 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. MASON has many representations of space built-in: they're called fields. In our Schelling model, we'll use a simple two-dimensional grid of Objects. MASON can do a lot more representations than this, but it'll serve for our purposes in the tutorial. In the Schelling.java file, add:
public public public public int neighborhood = 1; double threshold = 3; int gridHeight = 100; int gridWidth = 100; // a dummy one to start

public ObjectGrid2D grid = new ObjectGrid2D(gridWidth, gridHeight); 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 we begin 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 our agents find new places to move to rapidly. A Bag is a MASON object that is very similar to an ArrayList or Vector. The difference is that a Bag's underlying array is publicly accessible, so you can do rapid scans over it 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 in which they live. This class is little more than a wrapper for a publicly accessible 2D array of Objects, plus some 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 have different 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 when stepped. It's important to understand that this isn't a requirement. In MASON, the objects that live in Space may have nothing to do with the Steppable objects that live on the Schedule (in "Time", so to speak). We define Agents as those objects which live on the Schedule and exist to manipulate the world. MASON commonly calls them "Steppable"s, since they adhere to the Steppable interface so the Schedule can step them. 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 Int2Ds to store the current location of the agent on the grid. Java already has similar classes, such as java.awt.Point, but Int2D is immutable, meaning that once its x and y values are set, they cannot be changed. 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 interesting things later. Our Red and Blue agents will both subclass from Agent. In this example, our Agent will store its own location 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 a file 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 called when 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 the redProbability and blueProbability variables). 2. If RED or BLUE, create the appropriate agent with its spot location, put it in that spot, and schedule 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 stepped each 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. In each tick, MASON is stepping the Schedule once. When a Schedule is stepped, it advances to the minimally-scheduled timestep, selects all the agents scheduled for that timestep, shuffles their order (if they have 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. We have about 6500 such agents on the (100x100) board, so that comes to just about a million steps per second on 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's visualize 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 facilities are 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 GUIState instance will hold onto your SimState model instance, loading it, checkpointing it, etc. Additionally the GUIState 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 and manipulate 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 called SchellingWithUI.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 attached to 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 the standard constructor with a Schelling model created with a random number seed. You can create a Schelling model 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 call super.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 when the STOP button is pressed, but it's rarely used. By default we call super.start(), which calls start() on our underlying 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's typically 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 inspect certain features of it. The Console can also checkpoint out a simulation in mid-run and restore a simulation from checkpoint. These checkpoints can also be saved and restored on the command line without a GUI, and traded between GUI and nonGUI versions. Notice that the front of the Console contains an

HTML document which you can customize as you like. The document is defined by the index.html file in the same directory as the SchellingWithUI.class file. Additionally, we put an image in the HTML file, defined by the icon.png file. Note: this particular link doesn't work. This is because it's a link to Wikipedia, and this past month Wikipedia started issuing 403 errors to web browsers that don't provide the "user-agent" property. Sun's Java implementation stupidly doesn't provide that 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 import import import import sim.portrayal.*; sim.portrayal.grid.*; sim.portrayal.simple.*; java.awt.*; javax.swing.*;

The portrayal import statements will allow us to create portrayals which draw grid world and its respective objects. 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(); display.repaint(); } public void start() { super.start(); setupPortrayals(); }

// reschedule the displayer // redraw the display

// 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, allowing you to hide and show the Display2D from the Console, and also close the Display2D cleanly when the Console closes. We then attach an ObjectGridPortrayal2D to the display, calling it "Agents". We set the background color of the Display2D to be black, and display it. This sets up the display and grid portrayal to be used for multiple model instantiations. start() and load(...) both have roughly the same code, so we have grouped that code into a method called setupPortrayals(). Here is code called whenever a new model is begun or loaded from checkpoint. This method 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 three simple portrayals that the grid portrayal will call upon to draw objects in the grid. An OvalPortrayal2D draws 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 to reschedule 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 we play the simulation, they don't move. This is expected, as the agents don't do anything yet. They just sit there. Notice that each time you stop and start the simulation, the scene changes: a new model has been constructed. You can zoom in and scroll around if you like. Try increasing the Scale in the display. You can also take pictures and make (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 a Schelling-like behavior. Specifically each agent (in random order) will do the following: 1. Examine the agents' neighborhood to see how many like-colored agents exist (himself excluded). A neighborhood of size N is defined as square 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 certain threshold, the agent will pick a random empty square and move there. Else he'll stay put. If in the unlikely 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 later on -- 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 are defining these two variables to avoid having to recreate them over and over again, creating needless garbage collection. If there are no empty spaces, our agent does nothing. Otherwise, the IntBags are passed into the getNeighborsMaxDistance , 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 every possible location in this neighborhood, and place them into the neighborsX and neighborsY IntBags respectively. 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 is the agents field); but this illustrates one way of getting neighborhood information. Realize that this is trading 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 the next coordinate values in the IntBag. If the agent is similar to us, then we increase val by the linear distance from us to that coordinate. If we exceed the threshold, we're happy and don't do anything. Else it's time to get up and move! Moving is easy. First we set our location in the grid to null. Then we pick a random location from among the spaces in the empty spaces list. Next we swap our current location with the chosen empty space in the list. 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 more interesting. Here's one convergence scenario. Note that the frame rate (pick "rate" from the pop-up menu at bottomright 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 just following along) If you run it without a display, notice the dramatic speed-up: now we have a rate of about 130! Note that this is slower than before: the agents could be sped up a bit (to about 170 fps) by having them quit their for-loops as soon as they detect happiness in excess of the threshold; but we want to compute the happiness for later, so we'll have to stick with the slower 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-only happiness property to our agents to be displayed when the user double-clicks on a given square. Add the following 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 -- and there's happiness! Next to the happiness property is a magnifying glass: this is the tracker menu, showing what tracking options you have for this property. For a double (like happiness), you can plot the value as well. Note that the values shown in the inspector are for the location and not the object. That is, if a new object moves into that location, the inspector will show its properties instead. Usually you'd prefer to track an object as it moves about the world instead. It's expensive to track objects in a simple two-dimensional array like this; you have to scan through the array to find the object. Instead, you should use a different kind of grid: a sim.field.SparseGrid2D, which uses hash tables to store locations rather than a 2D array. Inspectors for 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 entire model. Let's start by adding fields which specify the probability of reds versus blues versus empty, plus the threshold, 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 contain an 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 the neighborhood, threshold, redProbability, and blueProbability variables.
public int getNeighborhood() { return neighborhood; } public void setNeighborhood(int val) { if (val > 0) neighborhood public double getThreshold() { return threshold; } public void setThreshold(double val) { if (val >= 0) threshold = public double getRedProbability() { return redProbability; } public void setRedProbability(double val) { if (val >= 0 && val + blueProbability <= 1) redProbability public double getBlueProbability() { return blueProbability; } public void setBlueProbability(double val) { if (val >= 0 && val + redProbability <= 1) blueProbability = val; } val; } = val; } = 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 the Model 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 values gathered from the population of agents. A DoubleBag is like an IntBag but it stores Doubles. That's easy to do: just scan through the grid and grab the happiness of each non-null agent and stuffs it in the bag. Add the following 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 to redraw faster than every 0.5 seconds. Unpause the simulation and watch the histogram change. Note that this histogram is for this model run only: if you stop and run again, the histogram is frozen and you'll need to 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 happiness distribution. 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 it every 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-todate data. To change this behavior, you need to set the inspector that MASON will receive to be volatile, as follows. 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 a circle; and furthermore MASON must look up the simple portrayal for each object. This allows considerable flexibility, but it's slow. There's a faster grid portrayal, called sim.portrayal.grid.FastObjectGridPortrayal2D which draws all objects as colored squares. Indeed for many "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 a sim.util.SimpleColorMap to map the number to a color, and sets that rectangle to that color. So we need to do two things. First, we need to have each of our agents return numbers, and second, we need to add the map 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 to Red.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(); display.repaint(); } // reschedule the displayer // redraw the display

Now instead of defining portrayals to draw the individuals, we're simply setting a map which maps numbers to colors. Null values will be 0 (which maps to transparent). Red individuals will provide 1, which maps to red. 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 of about 40. Note from the picture at right that now all objects are being displayed as rectangles. No more ovals.

Checkpoint the Simulation
MASON models can be checkpointed ("freeze dried") and restored, with or without a GUI, and often across different platforms and operating systems. This is important for running on back-end servers and visualizing the current results on front-end workstations, or saving recoverable snapshots in case a machine goes 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 just following 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, at timestep 1000. At this point it's probably converged, so not so interesting. But run it a bit and save it out again 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 from there.

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. Or send the MASON development team questions directly at mason-help /-at-/ cs.gmu.edu (send general development questions to the mailing list instead, please).

Sign up to vote on this title
UsefulNot useful