Skip to content

Schelling's Segregation Model

An implementation of Schelling's segregation model [7], which is traditionally considered to be an agent-based as opposed to a microsimulation, model. However, the distinction is somewhat vague and subjective.

Schelling

Examples Source Code

The code for all the examples can be obtained by either:

  • pulling the docker image virgesmith/neworder:latest (more here), or
  • installing neworder, downloading the source code archive from the neworder releases page and extracting the examples subdirectory.

Inputs

In this example, the similarity threshold is 60% and the cells states are: 36% empty, 12% red, 12% blue and 40% green, on a 480 x 360 grid. The initial population is randomly constructed using the model's Monte-Carlo engine, the process of moving agents randomly swaps unsatisfied agents with empty cells. The boundaries are "sinks", i.e. there are no neighbouring cells

Implementation

The key features used in this example are the StateGrid class for efficient neighbour counting and the use of a conditional halting: an open-ended timeline and a call to the Model.halt() method when a certain state is achieved.

Since the key output for this model is graphical, the visualisation code sits within the model. The model reaches a steady state when there are no unsatisfied agents remaining and there is nothing to be gained by continuing, so when this happens the neworder.Model.halt() method is called, at the end of the step() implementation:

    # finish early if everyone satisfied
    if n_unsat == 0:
      # set the halt flag in the runtime
      self.halt()
      # since the timeline is open-ended we need to explicitly call finalise
      self.finalise()
[file: examples/schelling/schelling.py]

Note that calling the halt() method doesn't immediately halt the model, it flags the neworder runtime to not execute any further timesteps. Thus the remainder of the step method, and the check method (if implemented) will still be called.

The StateGrid.count_neighbours takes a function argument that filters the states of the neighbours. By default it will count cells with a state of 1 (the default value is lambda x: x==1). In this model we use it to count any occupied cells, and cells with a specific state:

    # count all neighbours, scaling by acceptable similarity ratio
    n_any = self.domain.count_neighbours(lambda x: x>0) * self.similarity

    for c in range(1,self.ncategories):
      # count neighbour with a specific state
      n_cat = self.domain.count_neighbours(lambda x: x==c)
      self.sat = np.logical_or(self.sat, np.logical_and(n_cat > n_any, self.domain.state == c))
[file: examples/schelling/schelling.py]

Outputs

The output is an animation as shown above. Log messages also record the timestep and the proportion of the population that remains unsatisfied:

[py 0/1] step 0 43.1493% unsatisfied
[py 0/1] step 1 39.1400% unsatisfied
[py 0/1] step 2 36.9196% unsatisfied
[py 0/1] step 3 35.3113% unsatisfied
[py 0/1] step 4 33.9259% unsatisfied
...
[py 0/1] step 133 0.0017% unsatisfied
[py 0/1] step 134 0.0012% unsatisfied
[py 0/1] step 135 0.0012% unsatisfied
[py 0/1] step 136 0.0006% unsatisfied
[py 0/1] step 137 0.0000% unsatisfied