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
76 changes: 76 additions & 0 deletions examples/crowd_evacuation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Crowd Evacuation — Social Force Model

## Summary
This model simulates how people evacuate a room. It's based on [Helbing & Molnár's Social Force Model](https://doi.org/10.1103/PhysRevE.51.4282) (1995) — the idea that pedestrian movement can be modeled as if people are pushed around by invisible forces:

1. **"I want to get out"** — each person is pulled toward the nearest exit
2. **"Don't crowd me"** — people push away from each other to avoid collisions
3. **"That's a wall"** — people push away from room boundaries

These three simple rules, when combined, produce realistic crowd behavior: bottlenecks form at exits, people slow down when it gets crowded, and wider exits actually help (as you'd expect).

### Observed Behavior
When you run the model, you'll see:
- Agents start scattered randomly across the room
- They quickly orient toward the nearest exit and begin moving
- As agents converge on exits, they slow down due to social repulsion
- A natural bottleneck forms at each exit opening
- Agents closest to exits escape first; those farther away arrive in waves
- With default settings (80 people, 2 exits), full evacuation takes around 1000–2000 steps (~100–200 simulated seconds)

### What It Demonstrates
This is a **Continuous Space** example. It showcases:
- **`ContinuousSpace`** — a bounded 2D room (not a torus)
- **`ContinuousSpaceAgent`** — agents with smooth, real-valued positions
- **Vector arithmetic** — velocity-based movement using `self.position += velocity * dt`
- **`get_neighbors_in_radius()`** — finding nearby agents for social force computation
- **Dynamic agent state** — agents are marked `escaped` when they reach an exit
- **`DataCollector`** — tracking agents remaining, escaped count, and average speed
- **`SolaraViz`** — interactive visualization with parameter sliders

## How to Run
```bash
pip install mesa[viz] numpy matplotlib
```

Interactive visualization:
```bash
solara run app.py
```

Headless (no GUI):
```python
from crowd_evacuation.model import EvacuationModel

model = EvacuationModel(num_people=20, num_exits=2)
model.run_for(500)
print(f"Escaped: {model.agents_escaped} / {model.num_people}")
print(f"Simulated time: {model.time * model.dt:.0f} seconds")
```

Expected output: all 20 agents escape within 500 steps.

## Parameters
| Parameter | Default | What it does |
|-----------|---------|-------------|
| `num_people` | 80 | How many people in the room |
| `width` | 30 | Room width in meters |
| `height` | 20 | Room height in meters |
| `num_exits` | 2 | Number of exit doors (1–4, placed on opposite walls) |
| `exit_width` | 1.5 | How wide each exit is (meters) |
| `desired_speed` | 1.3 | How fast people *want* to walk (m/s) |
| `max_speed` | 2.0 | Hard speed limit (m/s) |

## Files
- **`model.py`** — `EvacuationModel`: sets up the room, exits, and data collection
- **`agents.py`** — `Person`: the social force logic (desired + social + wall forces)
- **`app.py`** — SolaraViz visualization with room layout and evacuation progress chart

## Interesting Things to Try
- Set `num_exits` to 1 and watch the bottleneck form
- Crank up `num_people` to 200 and see how long evacuation takes
- Compare `desired_speed` = 1.0 vs 3.0 — does wanting to go faster actually help? (Spoiler: not always — this is the "faster-is-slower" effect from Helbing's paper)

## References
- Helbing, D., & Molnár, P. (1995). Social force model for pedestrian dynamics. *Physical Review E*, 51(5), 4282.
- Helbing, D., Farkas, I., & Vicsek, T. (2000). Simulating dynamical features of escape panic. *Nature*, 407(6803), 487–490.
218 changes: 218 additions & 0 deletions examples/crowd_evacuation/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
"""Crowd Evacuation visualization using SolaraViz.
Displays the room layout with exits, person agents colored by speed,
and real-time charts tracking evacuation progress.
"""

import matplotlib.pyplot as plt
import numpy as np
import solara
from crowd_evacuation.agents import Person
from crowd_evacuation.model import EvacuationModel
from matplotlib.figure import Figure
from mesa.visualization import SolaraViz, make_plot_component

model_params = {
"num_people": {
"type": "SliderInt",
"value": 80,
"label": "Number of People:",
"min": 10,
"max": 200,
"step": 10,
},
"width": {
"type": "SliderInt",
"value": 30,
"label": "Room Width (m):",
"min": 10,
"max": 50,
"step": 5,
},
"height": {
"type": "SliderInt",
"value": 20,
"label": "Room Height (m):",
"min": 10,
"max": 50,
"step": 5,
},
"num_exits": {
"type": "SliderInt",
"value": 2,
"label": "Number of Exits:",
"min": 1,
"max": 4,
"step": 1,
},
"exit_width": {
"type": "SliderFloat",
"value": 1.5,
"label": "Exit Width (m):",
"min": 0.5,
"max": 3.0,
"step": 0.5,
},
"desired_speed": {
"type": "SliderFloat",
"value": 1.3,
"label": "Desired Speed (m/s):",
"min": 0.5,
"max": 3.0,
"step": 0.1,
},
}


@solara.component
def RoomDrawer(model):
"""Draw the evacuation room with exits and agents."""
fig = Figure(figsize=(10, 7), dpi=100)
ax = fig.subplots()

width = model.width
height = model.height

# Draw room boundary
ax.set_xlim(-1, width + 1)
ax.set_ylim(-1, height + 1)
ax.set_aspect("equal")

# Draw walls (thick lines)
wall_color = "#2c3e50"
wall_lw = 3
ax.plot([0, width], [0, 0], color=wall_color, linewidth=wall_lw) # Bottom
ax.plot([0, width], [height, height], color=wall_color, linewidth=wall_lw) # Top
ax.plot([0, 0], [0, height], color=wall_color, linewidth=wall_lw) # Left
ax.plot([width, width], [0, height], color=wall_color, linewidth=wall_lw) # Right

# Draw exits (green gaps in walls)
for exit_pos, exit_w in model.exits:
ex, ey = exit_pos
ax.plot(
ex,
ey,
marker="s",
markersize=max(8, exit_w * 6),
color="#27ae60",
zorder=5,
markeredgecolor="#1e8449",
markeredgewidth=2,
)
ax.annotate(
"EXIT",
(ex, ey),
fontsize=7,
ha="center",
va="center",
fontweight="bold",
color="white",
zorder=6,
)

# Draw agents
active_agents = [a for a in model.agents if isinstance(a, Person) and not a.escaped]

if active_agents:
positions = np.array([a.position for a in active_agents])
speeds = np.array([np.linalg.norm(a.velocity) for a in active_agents])

# Color by speed: slow=blue, fast=red
max_speed = model_params["desired_speed"]["max"]
norm_speeds = np.clip(speeds / max_speed, 0, 1)

colors = plt.cm.RdYlBu_r(norm_speeds) # Red=fast, Blue=slow

ax.scatter(
positions[:, 0],
positions[:, 1],
c=colors,
s=25,
alpha=0.85,
edgecolors="#333",
linewidths=0.5,
zorder=10,
)

# Add info text
remaining = model.num_people - model.agents_escaped
ax.set_title(
f"Crowd Evacuation — Step {int(model.time)} | "
f"Remaining: {remaining} | Escaped: {model.agents_escaped}",
fontsize=12,
fontweight="bold",
)
ax.set_xlabel("x (meters)")
ax.set_ylabel("y (meters)")

# Color bar for speed
sm = plt.cm.ScalarMappable(
cmap=plt.cm.RdYlBu_r,
norm=plt.Normalize(0, max_speed),
)
sm.set_array([])
cbar = fig.colorbar(sm, ax=ax, shrink=0.6, pad=0.02)
cbar.set_label("Speed (m/s)", fontsize=9)

fig.tight_layout()

return solara.FigureMatplotlib(fig)


def make_evacuation_curve(model):
"""Create the evacuation progress chart."""
fig = Figure(figsize=(8, 4), dpi=100)
ax = fig.subplots()

data = model.datacollector.get_model_vars_dataframe()

if len(data) > 0:
ax.fill_between(
data.index,
data["Agents Remaining"],
alpha=0.3,
color="#e74c3c",
)
ax.plot(
data.index,
data["Agents Remaining"],
color="#e74c3c",
linewidth=2,
label="Remaining",
)
ax.plot(
data.index,
data["Agents Escaped"],
color="#27ae60",
linewidth=2,
label="Escaped",
)

ax.set_title("Evacuation Progress", fontweight="bold")
ax.set_xlabel("Simulation Step")
ax.set_ylabel("Number of Agents")
ax.legend(loc="center right")
ax.set_ylim(0, model.num_people + 5)

fig.tight_layout()

return solara.FigureMatplotlib(fig)


# Create initial model
model1 = EvacuationModel()

# Assemble the visualization
page = SolaraViz(
model1,
components=[
RoomDrawer,
make_evacuation_curve,
make_plot_component("Average Speed"),
],
model_params=model_params,
name="Crowd Evacuation — Social Force Model",
play_interval=100,
)

page # noqa
Empty file.
Loading