Skip to content

Commit dd5f57d

Browse files
Improve boid flocker model and documentation (#101)
* Improve boid flocker model * Update Readme.md * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update model.py * Update server.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 06c0015 commit dd5f57d

File tree

7 files changed

+193
-146
lines changed

7 files changed

+193
-146
lines changed

examples/boid_flockers/Readme.md

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,47 @@
1-
# Flockers
1+
# Boids Flockers
2+
3+
## Summary
24

35
An implementation of Craig Reynolds's Boids flocker model. Agents (simulated birds) try to fly towards the average position of their neighbors and in the same direction as them, while maintaining a minimum distance. This produces flocking behavior.
46

57
This model tests Mesa's continuous space feature, and uses numpy arrays to represent vectors. It also demonstrates how to create custom visualization components.
68

9+
## Installation
10+
11+
To install the dependencies use pip and the requirements.txt in this directory. e.g.
12+
13+
```
14+
$ pip install -r requirements.txt
15+
```
16+
717
## How to Run
818

9-
Launch the model:
19+
* To launch the visualization interactively, run ``mesa runserver`` in this directory. e.g.
20+
21+
```
22+
$ mesa runserver
23+
```
24+
25+
or
26+
27+
Directly run the file ``run.py`` in the terminal. e.g.
28+
1029
```
11-
$ python Flocker_Server.py
30+
$ python run.py
1231
```
1332

14-
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
33+
* Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
1534

1635
## Files
1736

18-
* [flockers/model.py](flockers/model.py): Core model file; contains the BoidModel class.
19-
* [flockers/boid.py](flockers/boid.py): The Boid agent class.
20-
* [flockers/SimpleContinuousModule.py](flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions.
21-
* [flockers/simple_continuous_canvas.js](flockers/simple_continuous_canvas.js): JavaScript side of the ``SimpleCanvas`` visualization module; takes the output generated by the Python ``SimpleCanvas`` element and draws it in the browser window via HTML5 canvas.
22-
* [flockers/server.py](flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above
37+
* [boid_flockers/model.py](boid_flockers/model.py): Core model file; contains the Boid Model and Boid Agent class.
38+
* [boid_flockers/SimpleContinuousModule.py](boid_flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions.
39+
* [boid_flockers/simple_continuous_canvas.js](boid_flockers/simple_continuous_canvas.js): JavaScript side of the ``SimpleCanvas`` visualization module; takes the output generated by the Python ``SimpleCanvas`` element and draws it in the browser window via HTML5 canvas.
40+
* [boid_flockers/server.py](boid_flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above
2341
* [run.py](run.py) Launches the visualization.
24-
* [Flocker Test.ipynb](Flocker Test.ipynb): Tests the model in a Jupyter notebook.
42+
* [Flocker_Test.ipynb](Flocker_Test.ipynb): Tests the model in a Jupyter notebook.
2543

2644
## Further Reading
2745

28-
=======
29-
* Launch the visualization
30-
```
31-
$ mesa runserver
32-
```
33-
* Visit your browser: http://127.0.0.1:8521/
34-
* In your browser hit *run*
46+
The following link can be visited for more information on the boid flockers model:
47+
https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/modeling-natural-systems/boids.html

examples/boid_flockers/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ def boid_draw(agent):
1616
}
1717

1818
page = JupyterViz(
19-
BoidFlockers,
20-
model_params,
19+
model_class=BoidFlockers,
20+
model_params=model_params,
2121
measures=[],
2222
name="BoidFlockers",
2323
agent_portrayal=boid_draw,

examples/boid_flockers/boid_flockers/SimpleContinuousModule.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33

44
class SimpleCanvas(mesa.visualization.VisualizationElement):
55
local_includes = ["boid_flockers/simple_continuous_canvas.js"]
6-
portrayal_method = None
7-
canvas_height = 500
8-
canvas_width = 500
96

10-
def __init__(self, portrayal_method, canvas_height=500, canvas_width=500):
7+
def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500):
118
"""
129
Instantiate a new SimpleCanvas
1310
"""

examples/boid_flockers/boid_flockers/boid.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

examples/boid_flockers/boid_flockers/model.py

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,108 @@
88
import mesa
99
import numpy as np
1010

11-
from .boid import Boid
11+
12+
class Boid(mesa.Agent):
13+
"""
14+
A Boid-style flocker agent.
15+
16+
The agent follows three behaviors to flock:
17+
- Cohesion: steering towards neighboring agents.
18+
- Separation: avoiding getting too close to any other agent.
19+
- Alignment: try to fly in the same direction as the neighbors.
20+
21+
Boids have a vision that defines the radius in which they look for their
22+
neighbors to flock with. Their speed (a scalar) and direction (a vector)
23+
define their movement. Separation is their desired minimum distance from
24+
any other Boid.
25+
"""
26+
27+
def __init__(
28+
self,
29+
unique_id,
30+
model,
31+
pos,
32+
speed,
33+
direction,
34+
vision,
35+
separation,
36+
cohere=0.025,
37+
separate=0.25,
38+
match=0.04,
39+
):
40+
"""
41+
Create a new Boid flocker agent.
42+
43+
Args:
44+
unique_id: Unique agent identifyer.
45+
pos: Starting position
46+
speed: Distance to move per step.
47+
direction: numpy vector for the Boid's direction of movement.
48+
vision: Radius to look around for nearby Boids.
49+
separation: Minimum distance to maintain from other Boids.
50+
cohere: the relative importance of matching neighbors' positions
51+
separate: the relative importance of avoiding close neighbors
52+
match: the relative importance of matching neighbors' headings
53+
"""
54+
super().__init__(unique_id, model)
55+
self.pos = np.array(pos)
56+
self.speed = speed
57+
self.direction = direction
58+
self.vision = vision
59+
self.separation = separation
60+
self.cohere_factor = cohere
61+
self.separate_factor = separate
62+
self.match_factor = match
63+
self.neighbors = None
64+
65+
def cohere(self):
66+
"""
67+
Return the vector toward the center of mass of the local neighbors.
68+
"""
69+
cohere = np.zeros(2)
70+
if self.neighbors:
71+
for neighbor in self.neighbors:
72+
cohere += self.model.space.get_heading(self.pos, neighbor.pos)
73+
cohere /= len(self.neighbors)
74+
return cohere
75+
76+
def separate(self):
77+
"""
78+
Return a vector away from any neighbors closer than separation dist.
79+
"""
80+
me = self.pos
81+
them = (n.pos for n in self.neighbors)
82+
separation_vector = np.zeros(2)
83+
for other in them:
84+
if self.model.space.get_distance(me, other) < self.separation:
85+
separation_vector -= self.model.space.get_heading(me, other)
86+
return separation_vector
87+
88+
def match_heading(self):
89+
"""
90+
Return a vector of the neighbors' average heading.
91+
"""
92+
match_vector = np.zeros(2)
93+
if self.neighbors:
94+
for neighbor in self.neighbors:
95+
match_vector += neighbor.direction
96+
match_vector /= len(self.neighbors)
97+
return match_vector
98+
99+
def step(self):
100+
"""
101+
Get the Boid's neighbors, compute the new vector, and move accordingly.
102+
"""
103+
104+
self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
105+
self.direction += (
106+
self.cohere() * self.cohere_factor
107+
+ self.separate() * self.separate_factor
108+
+ self.match_heading() * self.match_factor
109+
) / 2
110+
self.direction /= np.linalg.norm(self.direction)
111+
new_pos = self.pos + self.direction * self.speed
112+
self.model.space.move_agent(self, new_pos)
12113

13114

14115
class BoidFlockers(mesa.Model):
@@ -39,7 +140,8 @@ def __init__(
39140
separation: What's the minimum distance each Boid will attempt to
40141
keep from any other
41142
cohere, separate, match: factors for the relative importance of
42-
the three drives."""
143+
the three drives.
144+
"""
43145
super().__init__()
44146
self.population = population
45147
self.vision = vision
@@ -49,7 +151,6 @@ def __init__(
49151
self.space = mesa.space.ContinuousSpace(width, height, True)
50152
self.factors = {"cohere": cohere, "separate": separate, "match": match}
51153
self.make_agents()
52-
self.running = True
53154

54155
def make_agents(self):
55156
"""
@@ -59,15 +160,15 @@ def make_agents(self):
59160
x = self.random.random() * self.space.x_max
60161
y = self.random.random() * self.space.y_max
61162
pos = np.array((x, y))
62-
velocity = np.random.random(2) * 2 - 1
163+
direction = np.random.random(2) * 2 - 1
63164
boid = Boid(
64-
i,
65-
self,
66-
pos,
67-
self.speed,
68-
velocity,
69-
self.vision,
70-
self.separation,
165+
unique_id=i,
166+
model=self,
167+
pos=pos,
168+
speed=self.speed,
169+
direction=direction,
170+
vision=self.vision,
171+
separation=self.separation,
71172
**self.factors,
72173
)
73174
self.space.place_agent(boid, pos)

0 commit comments

Comments
 (0)