Schelling Segregation Model#
Summary#
The Schelling segregation model is a classic agent-based model, demonstrating how even a mild preference for similar neighbors can lead to a much higher degree of segregation than we would intuitively expect. The model consists of agents on a square grid, where each grid cell can contain at most one agent. Agents come in two colors: red and blue. They are happy if a certain number of their eight possible neighbors are of the same color, and unhappy otherwise. Unhappy agents will pick a random empty cell to move to each step, until they are happy. The model keeps running until there are no unhappy agents.
By default, the number of similar neighbors the agents need to be happy is set to 3. That means the agents would be perfectly happy with a majority of their neighbors being of a different color (e.g. a Blue agent would be happy with five Red neighbors and three Blue ones). Despite this, the model consistently leads to a high degree of segregation, with most agents ending up with no neighbors of a different color.
How to Run#
To run the model interactively, in this directory, run the following command
$ solara run app.py
Then open your browser to http://127.0.0.1:8765/ and click the Play button.
To view and run some example model analyses, launch the IPython Notebook and open analysis.ipynb
. Visualizing the analysis also requires matplotlib.
How to Run without the GUI#
To run the model with the grid displayed as an ASCII text, run python run_ascii.py
in this directory.
Files#
model.py
: Contains the Schelling model classagents.py
: Contains the Schelling agent classapp.py
: Code for the interactive visualization.analysis.ipynb
: Notebook demonstrating how to run experiments and parameter sweeps on the model.
Further Reading#
Schelling’s original paper describing the model:
An interactive, browser-based explanation and implementation:
Parable of the Polygons, by Vi Hart and Nicky Case.
Agents#
from mesa import Agent
class SchellingAgent(Agent):
"""Schelling segregation agent."""
def __init__(self, model, agent_type: int) -> None:
"""Create a new Schelling agent.
Args:
model: The model instance the agent belongs to
agent_type: Indicator for the agent's type (minority=1, majority=0)
"""
super().__init__(model)
self.type = agent_type
def step(self) -> None:
"""Determine if agent is happy and move if necessary."""
neighbors = self.model.grid.get_neighbors(
self.pos, moore=True, radius=self.model.radius
)
# Count similar neighbors
similar_neighbors = len([n for n in neighbors if n.type == self.type])
# Calculate the fraction of similar neighbors
if (valid_neighbors := len(neighbors)) > 0:
similarity_fraction = similar_neighbors / valid_neighbors
else:
# If there are no neighbors, the similarity fraction is 0
similarity_fraction = 0.0
# Move if unhappy
if similarity_fraction < self.model.homophily:
self.model.grid.move_to_empty(self)
else:
self.model.happy += 1
Model#
from mesa import Model
from mesa.datacollection import DataCollector
from mesa.examples.basic.schelling.agents import SchellingAgent
from mesa.space import SingleGrid
class Schelling(Model):
"""Model class for the Schelling segregation model."""
def __init__(
self,
height: int = 20,
width: int = 20,
density: float = 0.8,
minority_pc: float = 0.5,
homophily: float = 0.4,
radius: int = 1,
seed=None,
):
"""Create a new Schelling model.
Args:
width: Width of the grid
height: Height of the grid
density: Initial chance for a cell to be populated (0-1)
minority_pc: Chance for an agent to be in minority class (0-1)
homophily: Minimum number of similar neighbors needed for happiness
radius: Search radius for checking neighbor similarity
seed: Seed for reproducibility
"""
super().__init__(seed=seed)
# Model parameters
self.height = height
self.width = width
self.density = density
self.minority_pc = minority_pc
self.homophily = homophily
self.radius = radius
# Initialize grid
self.grid = SingleGrid(width, height, torus=True)
# Track happiness
self.happy = 0
# Set up data collection
self.datacollector = DataCollector(
model_reporters={
"happy": "happy",
"pct_happy": lambda m: (m.happy / len(m.agents)) * 100
if len(m.agents) > 0
else 0,
"population": lambda m: len(m.agents),
"minority_pct": lambda m: (
sum(1 for agent in m.agents if agent.type == 1)
/ len(m.agents)
* 100
if len(m.agents) > 0
else 0
),
},
agent_reporters={"agent_type": "type"},
)
# Create agents and place them on the grid
for _, pos in self.grid.coord_iter():
if self.random.random() < self.density:
agent_type = 1 if self.random.random() < minority_pc else 0
agent = SchellingAgent(self, agent_type)
self.grid.place_agent(agent, pos)
# Collect initial state
self.datacollector.collect(self)
def step(self):
"""Run one step of the model."""
self.happy = 0 # Reset counter of happy agents
self.agents.shuffle_do("step") # Activate all agents in random order
self.datacollector.collect(self) # Collect data
self.running = self.happy < len(self.agents) # Continue until everyone is happy
App#
import solara
from mesa.examples.basic.schelling.model import Schelling
from mesa.visualization import (
Slider,
SolaraViz,
make_plot_component,
make_space_component,
)
def get_happy_agents(model):
"""Display a text count of how many happy agents there are."""
return solara.Markdown(f"**Happy agents: {model.happy}**")
def agent_portrayal(agent):
return {"color": "tab:orange" if agent.type == 0 else "tab:blue"}
model_params = {
"seed": {
"type": "InputText",
"value": 42,
"label": "Random Seed",
},
"density": Slider("Agent density", 0.8, 0.1, 1.0, 0.1),
"minority_pc": Slider("Fraction minority", 0.2, 0.0, 1.0, 0.05),
"homophily": Slider("Homophily", 0.4, 0.0, 1.0, 0.125),
"width": 20,
"height": 20,
}
model1 = Schelling()
HappyPlot = make_plot_component({"happy": "tab:green"})
page = SolaraViz(
model1,
components=[
make_space_component(agent_portrayal),
HappyPlot,
get_happy_agents,
],
model_params=model_params,
)
page # noqa