Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,19 @@ This project is an agent-based model implemented using the Mesa framework in Pyt
### [Emperor's Dilemma](https://github.com/mesa/mesa-examples/tree/main/examples/emperor_dilemma)

This project simulates how unpopular norms can dominate a society even when the vast majority of individuals privately reject them. It demonstrates the "illusion of consensus" where agents, driven by a fear of appearing disloyal, not only comply with a rule they hate but also aggressively enforce it on their neighbors. This phenomenon creates a "trap" of False Enforcement, where the loudest defenders of a norm are often its secret opponents.

### [Humanitarian Aid Distribution Model](https://github.com/mesa/mesa-examples/tree/main/examples/humanitarian_aid_distribution)

This model simulates a humanitarian aid distribution scenario using a needs-based behavioral architecture. Beneficiaries have dynamic needs (water, food) and trucks distribute aid using a hybrid triage system.

### [Rumor Mill Model](https://github.com/mesa/mesa-examples/tree/main/examples/rumor_mill)

A simple agent-based simulation showing how rumors spread through a population based on the spread chance and initial knowing percentage, implemented with the Mesa framework and adapted from NetLogo [Rumor mill](https://www.netlogoweb.org/launch#https://www.netlogoweb.org/assets/modelslib/Sample%20Models/Social%20Science/Rumor%20Mill.nlogox).

### [Axelrod Culture Model](https://github.com/mesa/mesa-examples/tree/main/examples/axelrod_culture)

An implementation of Axelrod's model of cultural dissemination. Agents on a grid hold multi-feature cultural profiles and interact with neighbors based on cultural similarity, producing emergent cultural regions. Demonstrates how local convergence and global polarization can coexist.


## Continuous Space Examples
_No user examples available yet._
Expand Down Expand Up @@ -135,4 +141,4 @@ This folder contains an implementation of El Farol restaurant model. Agents (res

### [Schelling Model with Caching and Replay](https://github.com/mesa/mesa-examples/tree/main/examples/caching_and_replay)

This example applies caching on the Mesa [Schelling](https://github.com/mesa/mesa-examples/tree/main/examples/schelling) example. It enables a simulation run to be "cached" or in other words recorded. The recorded simulation run is persisted on the local file system and can be replayed at any later point.
This example applies caching on the Mesa [Schelling](https://github.com/mesa/mesa-examples/tree/main/examples/schelling) example. It enables a simulation run to be "cached" or in other words recorded. The recorded simulation run is persisted on the local file system and can be replayed at any later point.
27 changes: 27 additions & 0 deletions examples/axelrod_culture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Axelrod Culture Model

## Summary

An implementation of Axelrod's model of cultural dissemination. Each agent occupies a cell on a grid and holds a "culture" consisting of **F** features, each taking one of **Q** possible integer traits. At each step, an agent randomly selects a neighbor and interacts with probability equal to their cultural similarity (fraction of shared features). If interaction occurs, the agent copies one of the neighbor's differing traits.

Despite the local tendency toward convergence, stable cultural regions can persist globally — Axelrod's key insight: local homogenization and global polarization can coexist. The number of stable regions increases with Q (more possible traits → more diversity) and decreases with F (more features → more overlap → faster convergence).

## How to Run

To install the dependencies use pip and the requirements.txt in this directory:

$ pip install -r requirements.txt

To run the model interactively, in this directory, run the following command:

$ solara run app.py

## Files

* [agents.py](axelrod_culture/agents.py): Defines `CultureAgent` with a cultural profile and interaction logic
* [model.py](axelrod_culture/model.py): Sets up the grid, initializes random cultures, and tracks cultural regions
* [app.py](app.py): Solara based visualization showing the culture grid and region count over time

## Further Reading

* Axelrod, R. (1997). The dissemination of culture: A model with local convergence and global polarization. *Journal of Conflict Resolution*, 41(2), 203–226. https://doi.org/10.1177/0022002797041002001
77 changes: 77 additions & 0 deletions examples/axelrod_culture/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import hashlib

import numpy as np
import solara
from axelrod_culture.model import AxelrodModel, number_of_cultural_regions
from matplotlib.figure import Figure
from mesa.visualization import SolaraViz, make_plot_component


def culture_to_color(culture):
key = str(culture).encode()
h = hashlib.sha256(key).hexdigest()
return (int(h[0:2], 16) / 255, int(h[2:4], 16) / 255, int(h[4:6], 16) / 255)


def make_culture_grid(model):
fig = Figure(figsize=(5, 5))
fig.subplots_adjust(left=0.05, right=0.95, top=0.92, bottom=0.05)
ax = fig.add_subplot(111)
grid = np.zeros((model.height, model.width, 3))
for agent in model.agents:
x, y = int(agent.cell.coordinate[0]), int(agent.cell.coordinate[1])
grid[y][x] = culture_to_color(agent.culture)
ax.imshow(grid, origin="lower", interpolation="nearest")
ax.set_title(f"Cultural Regions: {number_of_cultural_regions(model)}", fontsize=11)
ax.set_xticks([])
ax.set_yticks([])
return solara.FigureMatplotlib(fig)


RegionsPlot = make_plot_component({"Cultural Regions": "#e63946"})

model_params = {
"rng": {"type": "InputText", "value": 42, "label": "Random Seed"},
"width": {
"type": "SliderInt",
"value": 10,
"label": "Grid Width",
"min": 5,
"max": 20,
"step": 1,
},
"height": {
"type": "SliderInt",
"value": 10,
"label": "Grid Height",
"min": 5,
"max": 20,
"step": 1,
},
"f": {
"type": "SliderInt",
"value": 3,
"label": "Features (F)",
"min": 2,
"max": 10,
"step": 1,
},
"q": {
"type": "SliderInt",
"value": 3,
"label": "Traits per feature (Q)",
"min": 2,
"max": 15,
"step": 1,
},
}

model = AxelrodModel()

page = SolaraViz(
model,
components=[make_culture_grid, RegionsPlot],
model_params=model_params,
name="Axelrod Culture Model",
)
page # noqa
Empty file.
36 changes: 36 additions & 0 deletions examples/axelrod_culture/axelrod_culture/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from mesa import Agent


class CultureAgent(Agent):
"""An agent with a cultural profile made of f features, each with q possible traits.

Two agents interact with probability equal to their cultural similarity
(fraction of features they share). If they interact, the focal agent
copies one randomly chosen differing feature from the neighbor.

Attributes:
culture (list[int]): List of f integers, each in range [0, q)
"""

def __init__(self, model, culture):
super().__init__(model)
self.culture = list(culture)

def similarity(self, other):
"""Return fraction of features shared with another agent (0.0 to 1.0)."""
matches = sum(a == b for a, b in zip(self.culture, other.culture))
return matches / len(self.culture)

def interact_with(self, other):
"""Interact with another agent based on cultural similarity."""
sim = self.similarity(other)
if sim == 0.0 or sim == 1.0:
return
if self.random.random() < sim:
differing = [
i
for i in range(len(self.culture))
if self.culture[i] != other.culture[i]
]
feature = self.random.choice(differing)
self.culture[feature] = other.culture[feature]
105 changes: 105 additions & 0 deletions examples/axelrod_culture/axelrod_culture/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
Axelrod Culture Model
=====================

Models how local cultural interactions can produce global polarization.
Each agent has a "culture" consisting of f features, each taking one of
q possible integer traits. At each step, an agent picks a random neighbor
and interacts with probability equal to their cultural similarity. If they
interact, the agent copies one of the neighbor's differing traits.

Despite the tendency toward local convergence, stable cultural regions
can persist -- a phenomenon Axelrod called the "culture problem":
local homogenization coexisting with global diversity.

Key result: the number of stable cultural regions decreases with f
(more features -> more convergence) and increases with q (more traits
per feature -> more diversity and fragmentation).

Reference:
Axelrod, R. (1997). The dissemination of culture: A model with local
convergence and global polarization. Journal of Conflict Resolution,
41(2), 203-226.
"""

from mesa import Model
from mesa.datacollection import DataCollector
from mesa.discrete_space import OrthogonalVonNeumannGrid

from axelrod_culture.agents import CultureAgent


def number_of_cultural_regions(model):
"""Count distinct stable cultural regions using flood fill on the grid."""
visited = set()
regions = 0
agent_by_pos = {
(int(a.cell.coordinate[0]), int(a.cell.coordinate[1])): a for a in model.agents
}
for pos in agent_by_pos:
if pos in visited:
continue
queue = [pos]
visited.add(pos)
while queue:
cx, cy = queue.pop()
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
npos = (cx + dx, cy + dy)
if (
npos not in visited
and npos in agent_by_pos
and agent_by_pos[npos].culture == agent_by_pos[(cx, cy)].culture
):
visited.add(npos)
queue.append(npos)
regions += 1
return regions


class AxelrodModel(Model):
"""Axelrod's model of cultural dissemination on a grid.

Attributes:
width (int): Grid width
height (int): Grid height
f (int): Number of cultural features per agent
q (int): Number of possible traits per feature
grid: OrthogonalVonNeumannGrid containing agents
"""

def __init__(self, width=10, height=10, f=3, q=3, rng=None):
super().__init__(rng=rng)

self.width = width
self.height = height
self.f = f
self.q = q

self.grid = OrthogonalVonNeumannGrid(
(width, height), torus=False, random=self.random
)

cultures = [
[self.random.randrange(q) for _ in range(f)] for _ in range(width * height)
]

CultureAgent.create_agents(self, width * height, cultures)

for agent, cell in zip(self.agents, self.grid.all_cells.cells):
agent.cell = cell

self.datacollector = DataCollector(
model_reporters={"Cultural Regions": number_of_cultural_regions}
)
self.running = True
self.datacollector.collect(self)

def step(self):
agent_list = list(self.agents)
for _ in range(self.width * self.height):
agent = self.random.choice(agent_list)
neighbors = [a for a in agent.cell.neighborhood.agents if a is not agent]
if neighbors:
neighbor = self.random.choice(neighbors)
agent.interact_with(neighbor)
self.datacollector.collect(self)
3 changes: 3 additions & 0 deletions examples/axelrod_culture/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mesa
matplotlib
solara
Loading