Sunteți pe pagina 1din 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
2. A Minimum Simulation
3. Add a Field (a representation of space)
4. Define our Agents
5. Set Up the Agents
6. Build a Minimal GUI
7. Add a Display
8. Make the Agents Do Something
9. Add Local Inspectability
10. Add Global Inspectability
11. Add a Histogram
12. Add a Time Series Chart
13. Speed Up Drawing
14. 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 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 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 non-
GUI 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 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 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(); // 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, 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 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 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 = 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 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-to-
date 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(); // 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 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).

S-ar putea să vă placă și