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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ A simple agent-based simulation showing how rumors spread through a population b


## Continuous Space Examples
_No user examples available yet._
### [Brownian Particles Model](https://github.com/mesa/mesa-examples/tree/main/examples/brownian_particles)

A minimal continuous space example — particles doing random walk (Brownian motion) with soft repulsion so they spread out naturally. Colour-coded by local density using a plasma colormap. Good starting point for `mesa.experimental.continuous_space`.


## Network Examples
Expand Down
34 changes: 34 additions & 0 deletions examples/brownian_particles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Brownian Particles

A simple model of particles diffusing through a 2D continuous space.

## What it shows

Each particle moves randomly each step (Brownian/random walk motion) and softly
repels nearby particles to avoid clustering. The colour of each particle reflects
how many neighbours it currently has — blue/purple means isolated, yellow/red
means densely packed.

This is a minimal example of **continuous space** in Mesa. There is no grid;
agents can be anywhere in a bounded region and interact based on Euclidean distance.

## How to run

```bash
cd examples/brownian_particles
solara run app.py
```

## Parameters

| Parameter | What it does |
|---|---|
| Number of Particles | Total agents in the space |
| Diffusion Rate | Size of random jump each step |
| Neighbor Detection Radius | Distance within which particles sense each other |

## Files

- `brownian_particles/agents.py` — Particle agent (Brownian motion + soft repulsion)
- `brownian_particles/model.py` — BrownianModel, handles setup and scheduling
- `app.py` — Solara visualization with interactive sliders
66 changes: 66 additions & 0 deletions examples/brownian_particles/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import matplotlib.cm as cm
import matplotlib.colors as mcolors
from brownian_particles.model import BrownianModel, BrownianScenario
from mesa.visualization import Slider, SolaraViz
from mesa.visualization.components.matplotlib_components import SpaceMatplotlib

# color particles based on how many neighbors they have
# more neighbors = warmer color (yellow/red), fewer = cooler (blue/purple)
_cmap = cm.get_cmap("plasma")
_norm = mcolors.Normalize(vmin=0, vmax=10)


def particle_draw(agent):
# clamp neighbor count to 0-10 range for the colormap
nc = min(agent.n_neighbors, 10)
rgba = _cmap(_norm(nc))
hex_color = mcolors.to_hex(rgba)
return {"color": hex_color, "size": 15}


model_params = {
"seed": {
"type": "InputText",
"value": 42,
"label": "Random Seed",
},
"n_particles": Slider(
label="Number of Particles",
value=80,
min=10,
max=300,
step=10,
),
"diffusion_rate": Slider(
label="Diffusion Rate",
value=0.5,
min=0.1,
max=3.0,
step=0.1,
),
"vision": Slider(
label="Neighbor Detection Radius",
value=4.0,
min=1.0,
max=15.0,
step=0.5,
),
}


def make_model(**kwargs):
scenario = BrownianScenario(
n_particles=kwargs.get("n_particles", 80),
diffusion_rate=kwargs.get("diffusion_rate", 0.5),
vision=kwargs.get("vision", 4.0),
)
return BrownianModel(scenario=scenario)


page = SolaraViz(
make_model,
components=[SpaceMatplotlib(particle_draw)],
model_params=model_params,
name="Brownian Particles",
)
page # noqa
38 changes: 38 additions & 0 deletions examples/brownian_particles/brownian_particles/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import numpy as np
from mesa.experimental.continuous_space import ContinuousSpaceAgent


class Particle(ContinuousSpaceAgent):
"""
A particle that moves around randomly (Brownian motion) and
gently pushes away from nearby particles so they don't pile up.
"""

def __init__(self, model, space, position, diffusion_rate=0.5, vision=3.0):
super().__init__(space, model)
self.position = np.array(position, dtype=float)
self.diffusion_rate = diffusion_rate
self.vision = vision
self.n_neighbors = 0 # how many particles are nearby, used for coloring

def step(self):
# random kick - this is the Brownian part
noise = self.model.rng.uniform(-1, 1, size=2) * self.diffusion_rate

# soft repulsion: push away from particles that are too close
neighbors, distances = self.get_neighbors_in_radius(radius=self.vision)
neighbors = [n for n in neighbors if n is not self]
self.n_neighbors = len(neighbors)

repulsion = np.zeros(2)
if neighbors:
diff = self.space.calculate_difference_vector(
self.position, agents=neighbors
)
# closer particles push harder
for i, d in enumerate(distances[1:]): # skip self (distance=0)
if d > 0:
repulsion -= diff[i] / (d**2 + 0.1)
repulsion *= 0.05

self.position = self.position + noise + repulsion
65 changes: 65 additions & 0 deletions examples/brownian_particles/brownian_particles/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import mesa
import numpy as np
from brownian_particles.agents import Particle
from mesa.experimental.continuous_space import ContinuousSpace
from mesa.experimental.scenarios import Scenario


class BrownianScenario(Scenario):
"""
Parameters for the Brownian particle model.
These show up as sliders in the UI.
"""

n_particles: int = 80
width: float = 50.0
height: float = 50.0
diffusion_rate: float = 0.5 # how much each particle jumps per step
vision: float = 4.0 # radius in which particles sense neighbors


class BrownianModel(mesa.Model):
"""
A simple model where particles diffuse through a 2D continuous space.
They follow Brownian motion with a soft repulsion to avoid overlapping.

This is a basic example of continuous space in Mesa - good for understanding
how agents move in non-grid environments.
"""

def __init__(self, scenario=None):
if scenario is None:
scenario = BrownianScenario()

super().__init__(scenario=scenario)

self.space = ContinuousSpace(
[[0, scenario.width], [0, scenario.height]],
torus=True,
random=self.random,
n_agents=scenario.n_particles,
)

# scatter particles randomly across the space
positions = self.rng.random(size=(scenario.n_particles, 2)) * np.array(
[scenario.width, scenario.height]
)

Particle.create_agents(
self,
scenario.n_particles,
self.space,
position=positions,
diffusion_rate=scenario.diffusion_rate,
vision=scenario.vision,
)

self.datacollector = mesa.DataCollector(
model_reporters={
"Avg Neighbors": lambda m: np.mean([a.n_neighbors for a in m.agents])
}
)

def step(self):
self.agents.shuffle_do("step")
self.datacollector.collect(self)