Boid Flocking

This page presents a classic boid flocking model—an archetypal Agent-Based Modeling (ABM) example showing how simple local rules can produce complex collective behavior such as flocking, swarming, or schooling. We cover the theory and origin of Boids, a Mesa 2.4 implementation, the key parameters, and a custom PyQtGraph visualization that colors dynamically detected swarms.

Theory & Origin

The Boids model was introduced by Craig W. Reynolds (1987) to simulate the lifelike motion of flocks using three simple rules applied locally by each agent (boid):

When combined with a limited perception radius, these rules yield emergent, coordinated group motion. See the Boids (Wikipedia) overview, and Mesa’s modern example Boid Flockers (Mesa 3.x).

Key Parameters

ParameterMeaningTypical Effect
population Number of boids (agents) in the environment. Higher values increase density and interaction, often strengthening flock formation.
width, height Continuous space size (environment bounds). Smaller spaces raise encounter frequency; larger spaces disperse flocks.
speed Preferred speed or max velocity per step. Higher speed increases dynamism; may require higher vision/separation to remain stable.
vision Perception radius (how far a boid senses neighbors). Too small → fragmented motion; too large → over-synchronization or “traffic jams”.
separation Repulsion distance / weight to prevent crowding. Higher values spread agents out; too high can fragment flocks.
alignment_weight, cohesion_weight, separation_weight Weights for the three steering forces (if exposed in your model). Balances smoothness vs. responsiveness; tuning is key for realistic flocking.

Mesa 2.4 Implementation (Classic Server)

Below is a minimal sketch of the classic Mesa 2.x server for Boids. It uses the legacy ModularServer + JS canvas components (pre-Solara). If you are on Mesa 3.x+, refer to the Solara example linked above.

# server.py (Mesa 2.4 style)
from mesa.visualization.ModularVisualization import ModularServer
from SimpleContinuousModule import SimpleCanvas
from model import BoidFlockers

def boid_draw(agent):
    return {"Shape": "circle", "r": 2, "Filled": "true", "Color": "Red"}

boid_canvas = SimpleCanvas(boid_draw, 500, 500)
model_params = dict(population=100, width=100, height=100, speed=5, vision=10, separation=2)

server = ModularServer(BoidFlockers, [boid_canvas], "Boid Flockers", model_params)

# run.py
from server import server
server.launch(open_browser=True)

For Mesa ≥ 3.x, visualization is Solara-based; porting is required (no JS injection). See: Mesa 3.x Boid Flockers.

Custom Visualization (PyQtGraph)

To avoid browser dependencies and gain speed, we use a PyQtGraph viewer. It steps the model via a QTimer, draws boids as a scatter, and adds dynamic swarm coloring based on proximity (clusters are recolored each frame).

# run_pyqt.py (launcher)
import sys
from PyQt6 import QtWidgets
from view_pyqt import BoidQtViewer
from model import BoidFlockers

params = dict(population=100, width=100, height=100, speed=5, vision=10, separation=2)
model = BoidFlockers(**params)

app = QtWidgets.QApplication(sys.argv)
viewer = BoidQtViewer(model=model, fps=30, point_size=8.0, swarm_radius=6.0)
viewer.show()
sys.exit(app.exec())
# view_pyqt.py (core viewer: dynamic clustering + coloring)
from PyQt6 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
import random

class BoidQtViewer(QtWidgets.QMainWindow):
    def __init__(self, model, fps=30, point_size=6.0, swarm_radius=5.0, parent=None):
        super().__init__(parent)
        self.model = model
        self.ms_per_frame = int(1000 / max(1, fps))
        self.swarm_radius = swarm_radius

        self.setWindowTitle("Boid Flockers — PyQtGraph (Dynamic Swarm Coloring)")
        self.resize(1000, 800)

        self.canvas = pg.GraphicsLayoutWidget(show=True)
        self.setCentralWidget(self.canvas)
        self.plot = self.canvas.addPlot()
        self.plot.setAspectLocked(True)
        self.plot.showGrid(x=True, y=True, alpha=0.3)

        sxmin = getattr(self.model.space, "x_min", 0.0)
        sxmax = getattr(self.model.space, "x_max", 100.0)
        symin = getattr(self.model.space, "y_min", 0.0)
        symax = getattr(self.model.space, "y_max", 100.0)
        self.plot.setXRange(sxmin, sxmax)
        self.plot.setYRange(symin, symax)

        self.scatter = pg.ScatterPlotItem(pxMode=False, size=point_size, name="Agents")
        self.plot.addItem(self.scatter)

        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self._step_and_render)
        self.timer.start(self.ms_per_frame)

    def _detect_swarms(self, positions, radius=5.0):
        N = len(positions)
        labels = np.full(N, -1, dtype=int)
        cluster_id = 0
        for i in range(N):
            if labels[i] != -1:
                continue
            labels[i] = cluster_id
            stack = [i]
            while stack:
                j = stack.pop()
                dists = np.linalg.norm(positions[j] - positions, axis=1)
                neighbors = np.where((labels == -1) & (dists < radius))[0]
                labels[neighbors] = cluster_id
                stack.extend(neighbors)
            cluster_id += 1
        return labels

    def _make_group_colors(self, n_groups):
        random.seed(42)
        return [(random.randint(50,255), random.randint(50,255), random.randint(50,255))
                for _ in range(n_groups)]

    def _step_and_render(self):
        self.model.step()
        agents = list(getattr(self.model.schedule, "agents", []))
        if not agents:
            return

        positions = np.array([a.pos for a in agents])
        xs, ys = positions[:,0], positions[:,1]

        labels = self._detect_swarms(positions, radius=self.swarm_radius)
        n_clusters = len(np.unique(labels))
        colors = self._make_group_colors(n_clusters)
        brushes = [pg.mkBrush(colors[l % n_clusters]) for l in labels]
        self.scatter.setData(xs, ys, brush=brushes)

Demo Clip

Figure: Boids simulated in Mesa with dynamic swarm coloring using a proximity-based clusterer (Population = 300.0, speed = 3.0, vision = 10.0, radius = 6.0, seperation = 1.0). Over time, the boids self-organize into distinct flocks (swarms) that move cohesively across the environment..

Download / View Source

The full source code of this demo is available on GitHub: 📥 Download / View Source

Further Reading

Craig W. Reynolds (1987). “Flocks, Herds, and Schools: A Distributed Behavioral Model.” SIGGRAPH ’87, 21(4), 25–34. (Wikipedia summary)

Mesa 3.x Boid Flockers: https://mesa.readthedocs.io/stable/examples/basic/boid_flockers.html

Mesa (Framework): https://mesa.readthedocs.io/en/stable/