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;
}