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:
You'll see this architecture in the tutorial's separate Schelling.java (Model) and SchellingWithUI.java (Visualizer) files.
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.
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:
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.
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.
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.
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.
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);
}
}
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.
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. :-)
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.
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!
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:
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.
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.
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 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 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!
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.
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.
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.
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.
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
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.
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
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:
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:
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.
Next we need to add the doubleValue() method to our Red and Blue agents. Add the following method to Red.java:
Likewise add the following method to Blue.java:
In SchellingWithUI.java, change the gridPortrayal variable and setupPortrayals() method like this:
This says to run the simulation no more than 10000 timesteps, writing out a checkpoint every 1000 steps. You'll get something like this:
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).
Notice that MASON on the command line starts right up where you saved it under the GUI and goes from there.
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).
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)
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)
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; }
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)
Add a Histogram
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)
Add a Time Series Chart
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;
}
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)
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.
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();
...
public double doubleValue() { return 1; }
public double doubleValue() { return 2; }
Switch to a FastObjectGridPortrayal2D
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)
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)
Run the simulation ( as java sim.app.wcss.Schelling -checkpoint my.checkpoint )
(Files again in folder 10 if you're just following along)
More Information
Go to the MASON Home Page. From there you can: