SchellingSwarm - Object files
schelling_2.0/DiscreteToroid.m
See header
file
#import "DiscreteToroid.h"
@implementation DiscreteToroid
-getObjectAtX: (int) inX Y: (int) inY {
inX = [self wrapXCoord: inX];
inY = [self wrapYCoord: inY];
return [super getObjectAtX: inX Y: inY];
}
-putObject: (id) anObject atX: (int) inX Y: (int) inY {
inX = [self wrapXCoord: inX];
inY = [self wrapYCoord: inY];
return [super putObject: anObject atX: inX Y: inY];
}
-(int) wrapCoord: (int) inCoord atModulus: (int) inModulus
{
while ((inCoord) < 0) inCoord += inModulus;
return (inCoord % inModulus);
}
-(int) wrapXCoord: (int) inCoord
{
return [self wrapCoord: inCoord atModulus: xsize];
}
-(int) wrapYCoord: (int) inCoord
{
return [self wrapCoord: inCoord atModulus: ysize];
}
@end
schelling_2.0/ModelSwarm.m
See headerfile
// Schellings Segregation Model
// Code by Benedikt Stefansson, .
// First version July 1997
// Second version February 1998
#import "ModelSwarm.h"
#import <simtools.h>
@implementation ModelSwarm
// createBegin: here we set up the default simulation parameters.
+createBegin: (id) aZone {
ModelSwarm * obj;
ProbeMap * probeMap;
// First, call our superclass createBegin. This makes sure that
// the obj(ect) - the ModelSwarm - is created correctly
obj = [super createBegin: aZone];
// Now fill in various simulation parameters with default values.
obj->worldSize = 50;
obj->neighborhood_type = strdup("vonneuman");
obj->fractionVacant = 0.2;
obj->fractionBlue = 0.5;
obj->blueToleranceUpper=0.50;
obj->blueToleranceLower=0.25;
obj->redToleranceUpper=0.50;
obj->redToleranceLower=0.25;
obj->blueToleranceLower=0.25;
obj->randomSeed=882246;
// Then create an EmptyProbeMap to hold the individual
// probes for each simulation parameter
probeMap = [EmptyProbeMap createBegin: aZone];
[probeMap setProbedClass: [self class]];
probeMap = [probeMap createEnd];
// Add in a bunch of probes one per simulation parameter
[probeMap addProbe: [probeLibrary getProbeForVariable: "worldSize"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "neighborhood_type"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "fractionVacant"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "fractionBlue"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "blueToleranceUpper"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "blueToleranceLower"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "redToleranceUpper"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "redToleranceLower"
inClass: [self class]]];
[probeMap addProbe: [probeLibrary getProbeForVariable: "randomSeed"
inClass: [self class]]];
[probeMap addProbe: [[probeLibrary getProbeForMessage: "saveParameters"
inClass: [self class]]
setHideResult: 1]];
[probeMap addProbe: [[probeLibrary getProbeForMessage: "loadParameters"
inClass: [self class]]
setHideResult: 1]];
// Now install our custom probeMap into the probeLibrary.
[probeLibrary setProbeMap: probeMap For: [self class]];
return obj;
}
-createEnd {
return [super createEnd];
}
// Methods associated with probe above, to
// save or load the parameters from a file
-saveParameters {
[ObjectSaver save: self toFileNamed: "model.setup"];
return self;
}
-loadParameters {
[ObjectLoader load: self fromFileNamed: "model.setup"];
return self;
}
-buildObjects {
int x,y;
int color;
double tolerance;
[super buildObjects];
// We need a list for the agents
agentList=[List create:[self getZone]];
// Now create a 2d array to hold their location info
world=[SchellingWorld createBegin: [self getZone]];
[world setSizeX: worldSize Y: worldSize];
[world setModelSwarm: self];
world=[world createEnd];
// Also some random number generators
aGenerator=[PMMLCG1 create: [self getZone] setStateFromSeed: randomSeed];
uniformDouble=[UniformDouble create: [self getZone] setGenerator: aGenerator];
uniformInteger=[UniformInteger create: [self getZone] setGenerator: aGenerator];
// Then proceed to create the people in the world
for(x=0;x<worldSize;x++) {
for(y=0;y<worldSize;y++) {
id person;
id neighborhood;
// With probability 1-fractionVacant put an
// agent on the square and with probability
// fractionBlue make an agent of that color
if([self getRandomDouble]>=fractionVacant) {
if([self getRandomDouble]<=fractionBlue) {
// Here we initialize variables for a blue agent
color=10;
tolerance=[self getRandomDoubleMin: blueToleranceLower
Max: blueToleranceUpper];
}
else {
// Here we initialize variables for a red agent
color=11;
tolerance=[self getRandomDoubleMin: redToleranceLower
Max: redToleranceUpper];
}
// Each agent will have a neighborhood object
// which encapsulates the "neighborhood" rule
neighborhood=[Neighborhood create:[self getZone]];
[neighborhood setX: x Y: y];
[neighborhood setWorld: world];
if(neighborhood_type[0]=='v')
[neighborhood setType: 1];
else
[neighborhood setType: 2];
// Finally we create the actual person
person=[Person create: [self getZone]];
[person setWorld: world];
[person setNeighborhood: neighborhood];
[person setAgentColor: color];
[person setTolerance: (float)tolerance];
// Now that the person has been created and
// initialized we have to add it to the world
// and the agentList which is used by the schedule
[world putObject: person atX: x Y: y];
[agentList addLast: person];
}
}
}
return self;
}
-getAgentList {
return agentList;
}
-getWorld {
return world;
}
-(int)getWorldSize {
return worldSize;
}
-(double)getRandomDoubleMin: (double) min Max: (double) max {
double num;
if(min==max)
num=max;
else
num=[uniformDouble getDoubleWithMin: min withMax: max];
return num;
}
-(double)getRandomDouble {
double num;
num=[uniformDouble getDoubleWithMin: 0.0 withMax: 1.0];
return num;
}
-(int)getRandomIntMin: (int) min Max: (int) max {
int num;
if(min==max)
num=max;
else
num=[uniformInteger getIntegerWithMin: min withMax: max];
return num;
}
-buildActions {
[super buildActions];
modelSchedule = [Schedule createBegin: [self getZone]];
[modelSchedule setRepeatInterval: 1];
modelSchedule = [modelSchedule createEnd];
[modelSchedule at: 0 createActionForEach: agentList message: M(step)];
return self;
}
-activateIn: (id) swarmContext {
// First, activate ourselves via the superclass activateIn: method.
// Just pass along the context: the activity library does the right thing.
[super activateIn: swarmContext];
// Now activate our own schedule.
[modelSchedule activateIn: self];
// Finally, return our activity.
return [self getSwarmActivity];
}
@end
schelling_2.0/Neighborhood.m
See headerfile
// Schellings Segregation Model
// Code by Benedikt Stefansson, .
// First version July 1997
// Second version February 1998
#import "Neighborhood.h"
#import "Person.h"
@implementation Neighborhood
-setX: (int) x Y: (int) y {
myX=x;
myY=y;
return self;
}
-setWorld: w {
myWorld=w;
return self;
}
-setType: (int) t {
myType=t;
return self;
}
-(int)getX {
return myX;
}
-(int)getY {
return myY;
}
-(float)getFractionOf: (int) t {
float fraction=0.0;
int xvals[4]={0,0,1,-1};
int yvals[4]={-1,1,0,0};
int i,j;
Person * neighbor;
if(myType==1) {
// This enforces a VonNeuman neighborhood
for(i=0;i<4;i++)
if((neighbor=[myWorld getObjectAtX: myX+xvals[i] Y: myY+yvals[i]]))
if([neighbor getColor]==t)
fraction+=0.25;
} else {
// This enforces a Moore neighborhood
for(i=-1;i<2;i++)
for(j=-1;j<2;j++)
if((neighbor=[myWorld getObjectAtX: myX+i Y: myY+j]))
if([neighbor getColor]==t)
fraction+=0.125;
}
return fraction;
}
@end
schelling_2.0/ObserverSwarm.m
See headerfile
// Schellings Segregation Model a la Axtell and Epstein
// Code by Benedikt Stefansson, .
// First version July 1997
#import "ObserverSwarm.h"
#import "ModelSwarm.h"
#import <collections.h>
#import <swarmobject.h>
@implementation ObserverSwarm
// createBegin: here we set up the default observation parameters.
+createBegin: (id) aZone {
ObserverSwarm * obj;
ProbeMap * probeMap;
// Superclass createBegin to allocate ourselves.
obj = [super createBegin: aZone];
// Fill in the relevant parameters (only one, in this case).
obj->displayFrequency = 1;
// Also, build a customized probe map. Without a probe map, the default
// is to show all variables and messages. Here we choose to
// customize the appearance of the probe, give a nicer interface.
probeMap = [EmptyProbeMap createBegin: aZone];
[probeMap setProbedClass: [self class]];
probeMap = [probeMap createEnd];
// Add in a bunch of variables, one per simulation parameters
[probeMap addProbe: [probeLibrary getProbeForVariable: "displayFrequency"
inClass: [self class]]];
// Now install our custom probeMap into the probeLibrary.
[probeLibrary setProbeMap: probeMap For: [self class]];
return obj;
}
// createEnd: create objects we know we'll need. In this case, none,
// but you might want to override this.
-createEnd {
return [super createEnd];
}
// Create the objects used in the display of the model.
-buildObjects {
id modelZone; // zone for model.
[super buildObjects];
modelZone = [Zone create: [self getZone]];
modelSwarm = [ModelSwarm create: modelZone];
[probeDisplayManager createProbeDisplayFor: modelSwarm];
[probeDisplayManager createProbeDisplayFor: self];
[controlPanel waitForControlEvent];
// Check now if the user hit the quit button: if so, abort.
if ([controlPanel getState] == ControlStateQuit) return self;
[modelSwarm buildObjects];
colormap = [XColormap create: [self getZone]];
// set up the colormap, it maps colors used
// by the Tk widgets - which have actual names -
// into our internal representation as arbitrary integers
[colormap setColor: 10 ToName: "blue"];
[colormap setColor: 11 ToName: "red"];
[colormap setColor: 1 ToName: "white"];
// create the 2d window which is used to
// display the position of agents on the toroid
worldRaster = [ZoomRaster create: [self getZone]];
[worldRaster setColormap: colormap];
[worldRaster setZoomFactor: 6];
[worldRaster setWidth: [modelSwarm getWorldSize] Height: [modelSwarm getWorldSize]];
[worldRaster setWindowTitle: "Schelling's world"];
[worldRaster pack];
// Create Object2dDisplay which knows how to
// display a 2d space of objects/agents and
// respond to mouseclicks from the raster
worldDisplay = [Object2dDisplay createBegin: [self getZone]];
[worldDisplay setDisplayWidget: worldRaster];
[worldDisplay setDiscrete2dToDisplay: [modelSwarm getWorld]];
[worldDisplay setObjectCollection: [modelSwarm getAgentList]];
[worldDisplay setDisplayMessage: M(drawSelfOn:)];
worldDisplay = [worldDisplay createEnd];
// This allows the user to right-click on the display to probe agents
[worldRaster setButton: ButtonRight Client: worldDisplay Message: M(makeProbeAtX:Y:)];
// Finally add one graph widget to show how many
// people are unhappy and trying to move
moveGraph = [EZGraph createBegin: [self getZone]];
[moveGraph setTitle: "Fraction of people unhappy"];
[moveGraph setAxisLabelsX: "time" Y: "number"];
moveGraph = [moveGraph createEnd];
[moveGraph createAverageSequence: "unhappy"
withFeedFrom: [modelSwarm getAgentList]
andSelector: M(getUnhappy)] ;
return self;
}
-buildActions {
[super buildActions];
// Tell the model Swarm to build it's schedules
[modelSwarm buildActions];
// Then create a group of actions for the observer
displayActions = [ActionGroup create: [self getZone]];
[displayActions createActionTo: probeDisplayManager message: M(update)];
[displayActions createActionTo: controlPanel message: M(doTkEvents)];
[displayActions createActionTo: self message: M(eraseRaster)];
[displayActions createActionTo: worldDisplay message: M(display)];
[displayActions createActionTo: worldRaster message: M(drawSelf)];
[displayActions createActionTo: moveGraph message:M(step)];
// Put these actions on a schedule to be repeated at a certain frequency
displaySchedule = [Schedule createBegin: [self getZone]];
[displaySchedule setRepeatInterval: displayFrequency]; // note frequency!
displaySchedule = [displaySchedule createEnd];
[displaySchedule at: 0 createAction: displayActions];
return self;
}
// activateIn: - activate the schedules so they're ready to run.
// The swarmContext argument has to do with what we were activated *in*.
// Typically the ObserverSwarm is the top-level Swarm, so it's activated
// in "nil". But other Swarms and Schedules and such will be activated
// inside of us.
-activateIn: (id) swarmContext {
// First, activate ourselves (just pass along the context).
[super activateIn: swarmContext];
// Now activate our schedule in ourselves. This arranges for the
// execution of the schedule we built.
[displaySchedule activateIn: self];
// Activate the model swarm in ourselves. The model swarm is a
// subswarm of the observer swarm.
[modelSwarm activateIn: self];
// Activate returns the swarm activity - the thing that's ready to run.
return [self getSwarmActivity];
}
-eraseRaster {
// A method that overrides the default
// black background for raster and paints
// the raster white instead
[worldRaster fillRectangleX0: 0
Y0: 0
X1: [modelSwarm getWorldSize]
Y1: [modelSwarm getWorldSize]
Color: 1];
return self;
}
@end
schelling_2.0/Person.m
See headerfile
// Schelling's Segregation model
// Code by Benedikt Stefansson, .
// First version July 1997
// Second version February 1998
#import "Person.h"
@implementation Person
// A bunch of methods that set
// one parameter or another
-setWorld: w {
myWorld=w;
return self;
}
-setNeighborhood: n {
myNeighborhood=n;
return self;
}
-setAgentColor: (int) c {
myColor=c;
return self;
}
-setTolerance: (float) t {
myTolerance=t;
return self;
}
-step {
// The method that implements the agents
// actual behavior - look around and try
// to move if you are unhappy
unhappy=0;
// Then see if you are happy in your neighborhood
// and if you are not try to move somewhere else
if((myTolerance<[myNeighborhood getFractionOf: (myColor==10)? 11 : 10]))
[self moveToNewLocation];
return self;
}
-moveToNewLocation {
// If we call this method we know we are
// unhappy so we flick this switch here
unhappy=1;
// Find an empty neighbor according to
// the definition of neighborhoood
[myWorld findEmptyLocation: self];
return self;
}
-drawSelfOn: (id) rast {
// A method to draw the agent on the raster
[rast drawPointX: [myNeighborhood getX] Y: [myNeighborhood getY] Color: myColor];
return self;
}
// A bunch of methods to get parameters
-(int)getUnhappy {
return unhappy;
}
-(int)getColor {
return myColor;
}
-(double)getTolerance {
return myTolerance;
}
-getNeighborhood {
return myNeighborhood;
}
@end
schelling_2.0/SchellingWorld.m
See headerfile
// Schellings Segregation Model
// Code by Benedikt Stefansson, .
// First version July 1997
// Second version February 1998
#import "SchellingWorld.h"
@implementation SchellingWorld
-setModelSwarm: m {
modelSwarm=m;
return self;
}
-move: o toX: (int) x Y: (int) y {
id hood;
hood=[o getNeighborhood];
// To move an agent first we erase
// him in the original location
[self putObject: nil atX: [hood getX] Y: [hood getY]];
// Then we move him to the new location
[self putObject: o atX: x Y: y];
// Finally we change the agent's neighborhood
[hood setX: x Y: y];
return self;
}
-findEmptyLocation: o {
int x,y;
int i,j;
// Pick a random starting point
x=(int)[modelSwarm getRandomIntMin: 0 Max: xsize];
y=(int)[modelSwarm getRandomIntMin: 0 Max: ysize];
// Find the first empty spot
for(i=0;i<xsize;i++)
for(j=0;j<ysize;j++)
if(!([self getObjectAtX: x+i Y: y+j]))
goto FoundIt;
// This should never be called - but just in case
// if no empty location is found stay in same place
return [self move: o toX: [[o getNeighborhood] getX] Y: [[o getNeighborhood] getY]];
FoundIt:
return [self move: o toX: x+i Y: y+j];
}
@end
schelling_2.0/Shuffler.m
See headerfile
// Swarm library.
// Copyright (C) 1996 by the Regents of the University of Michigan.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular purpose.
// See file LICENSE for details and terms of copying.
// object to shuffle Swarm List list with num elts, in place
// from Knuth vol 2
// Modified by Nelson Minar from Ted Belding's code.
// Changed the random number distribution used.
// Sun Jul 6 22:42:35 MET DST 1997
#import "Shuffler.h"
#import <simtools.h>
// Defining the methods for a agent.
@implementation Shuffler
// Initialize world for the agent.
-setUniformRandom: (id) rnd {
// Strictly speaking, this check isn't necessary. But we intend these
// parameters to be immutable once set, so to be extrasafe we check:
// it could catch an error later.
if (uniformRandom != nil) {
[InvalidArgument raiseEvent: "You can only set the random number
generator of the shuffler at creation time\n"];
}
uniformRandom = rnd;
return self;
}
-createEnd {
if (uniformRandom == nil) {
[InvalidCombination raiseEvent: "shuffler was created without a random
number generator.\n"];
}
return self; // CRUCIAL!
}
-shuffleList: list {
// shuffle Swarm List list with num elts, in place
// from Knuth vol 2
int j, k;
id temp;
j = [list getCount];
while (j >1) {
k = [uniformRandom getIntegerWithMin: 0 withMax: j-1];
j--;
temp = [list atOffset: k];
[list atOffset: k put: [list atOffset: j]];
[list atOffset: j put: temp];
}
return self;
}
@end
schelling_2.0/main.m
// Schellings Segregation Model a la Axtell and Epstein
// Code by Benedikt Stefansson, .
// First version July 1997
#import "ObserverSwarm.h"
int
main(int argc, char ** argv) {
ObserverSwarm * observerSwarm;
initSwarm(argc, argv);
observerSwarm = [ObserverSwarm create: globalZone];
[observerSwarm buildObjects];
[observerSwarm buildActions];
[observerSwarm activateIn: nil];
[observerSwarm go];
return 0;
}