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
67 changes: 67 additions & 0 deletions examples/rumor_spread/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Rumor Spread Model

## Summary
This is a small agent-based model built with Mesa to simulate rumor propagation on a 2D grid.

Each agent occupies one cell in the grid. A small number of agents initially know the rumor. At each step, informed agents may spread the rumor to their neighbors. Each agent also has an individual resistance value, which reduces the probability of becoming informed.

The model includes:
- a spatial visualization of rumor spread
- a time-series plot showing how many agents are informed over time
- interactive controls for the number of initial spreaders and infection strength

## Purpose
I built this model as part of my Mesa GSoC 2026 preparation to gain hands-on experience with:
- Mesa model structure
- cell-based agents and grid spaces
- Solara-based visualization
- parameterized simulation experiments

## Model behavior
- Agents are placed on a toroidal 20x20 grid
- A configurable number of agents start informed
- Informed agents try to spread the rumor to neighboring agents
- Each agent has a resistance value between 0 and 1
- Higher resistance reduces the chance of becoming informed
- Informed agents can forget the rumor with a certain probability (recovery)

## Behavior

The model exhibits three regimes:

- Extinction: rumor dies out
- Equilibrium: stable fluctuations
- Saturation: near-total spread

These regimes depend on infection_strength and recovery_rate.

## Parameters
- **Initial Spreaders**: number of agents that start with the rumor
- **Infection Strength**: base strength of rumor transmission
- **Recovery Rate**: probability of forgetting the rumor each step

## What I learned
While building this model, I learned:
- the difference between generic Mesa agents and `CellAgent`
- how to use `OrthogonalMooreGrid` and assign agents to cells
- how to collect aggregate metrics with `DataCollector`
- how to render a model using `SolaraViz` and `SpaceRenderer`
- Introduced a recovery mechanism to prevent full saturation and allow more realistic steady-state behavior.
- how small design choices strongly affect emergent dynamics

## How to run
From this directory:

```bash
solara run app.py
```

## Notes

This model is intentionally simple. Its goal is not to be a realistic rumor diffusion simulator, but to serve as a compact, reproducible Mesa example for experimentation and learning.

## Quick observations
- Lower infection strength slows down diffusion and makes the growth curve less abrupt.
- A higher number of initial spreaders accelerates saturation.
- Even in a simple model, agent-level heterogeneity (resistance) changes the global diffusion pattern.
- With recovery, the model reaches a steady state instead of full infection, showing realistic epidemic-like dynamics.
46 changes: 46 additions & 0 deletions examples/rumor_spread/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from mesa.visualization import Slider, SolaraViz, SpaceRenderer, make_plot_component
from mesa.visualization.components.portrayal_components import AgentPortrayalStyle
from model import RumorModel


def portrayal(agent):
"""Visual style for rumor agents."""
if agent is None:
return None

style = AgentPortrayalStyle(size=100, marker="s", zorder=1)
if agent.has_rumor:
style.update(("color", "red"))
else:
style.update(("color", "lightgray"))
return style


def post_process_space(ax):
"""Improve readability of the grid plot."""
ax.set_aspect("equal")
ax.set_xticks([])
ax.set_yticks([])


model_params = {
"initial_spreaders": Slider("Initial Spreaders", 2, 1, 20, 1),
"infection_strength": Slider("Infection Strength", 0.2, 0.05, 0.5, 0.05),
"recovery_rate": Slider("Recovery Rate", 0.05, 0.0, 0.3, 0.01),
}

model = RumorModel()

renderer = SpaceRenderer(model, backend="matplotlib").setup_agents(portrayal)
renderer.post_process = post_process_space
renderer.draw_agents()

plot_component = make_plot_component({"Informed": "tab:blue"})

page = SolaraViz(
model,
renderer,
components=[plot_component],
model_params=model_params,
name="Rumor Spread Model",
)
87 changes: 87 additions & 0 deletions examples/rumor_spread/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from mesa import Model
from mesa.datacollection import DataCollector
from mesa.discrete_space import CellAgent
from mesa.discrete_space.grid import OrthogonalMooreGrid


class RumorAgent(CellAgent):
"""A grid-based agent that may or may not know the rumor."""

def __init__(self, model, cell=None, has_rumor=False):
super().__init__(model)
if cell is not None:
self.cell = cell
self.has_rumor = has_rumor
self.resistance = self.random.random()

def step(self):
"""Spread the rumor to neighboring agents probabilistically."""
if not self.has_rumor:
return

for neighbor_cell in self.cell.neighborhood:
for agent in neighbor_cell.agents:
if not agent.has_rumor:
spread_probability = (
self.model.infection_strength * (1 - agent.resistance) * 0.3
)
if self.random.random() < spread_probability:
agent.has_rumor = True

# Recovery (forget rumor)
if self.random.random() < self.model.recovery_rate:
self.has_rumor = False


class RumorModel(Model):
"""A simple rumor diffusion model on a 2D toroidal grid."""

def __init__(
self,
width=20,
height=20,
initial_spreaders=2,
infection_strength=0.2,
recovery_rate=0.05,
rng=None,
):
super().__init__(rng=rng)

self.width = width
self.height = height
self.initial_spreaders = initial_spreaders
self.infection_strength = infection_strength
self.recovery_rate = recovery_rate

self.grid = OrthogonalMooreGrid(
(width, height),
torus=True,
capacity=1,
random=self.random,
)

self.datacollector = DataCollector(
model_reporters={
"Informed": lambda m: sum(agent.has_rumor for agent in m.agents),
}
)

self._initialize_agents()
self.running = True
self.datacollector.collect(self)

def _initialize_agents(self):
"""Create and place agents on the grid."""
all_coords = [(x, y) for x in range(self.width) for y in range(self.height)]
initial_informed = set(self.random.sample(all_coords, self.initial_spreaders))

for x, y in all_coords:
has_rumor = (x, y) in initial_informed
cell = self.grid[(x, y)]
agent = RumorAgent(self, cell=cell, has_rumor=has_rumor)
self.agents.add(agent)

def step(self):
"""Advance the model by one step."""
self.agents.shuffle_do("step")
self.datacollector.collect(self)
Loading