Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve boid flocker model and documentation #101

Merged
merged 6 commits into from
Feb 24, 2024
Merged
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
47 changes: 30 additions & 17 deletions examples/boid_flockers/Readme.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,47 @@
# Flockers
# Boids Flockers

## Summary

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.

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

## Installation

To install the dependencies use pip and the requirements.txt in this directory. e.g.

```
$ pip install -r requirements.txt
```

## How to Run

Launch the model:
* To launch the visualization interactively, run ``mesa runserver`` in this directory. e.g.

```
$ mesa runserver
```

or

Directly run the file ``run.py`` in the terminal. e.g.

```
$ python Flocker_Server.py
$ python run.py
```

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

## Files

* [flockers/model.py](flockers/model.py): Core model file; contains the BoidModel class.
* [flockers/boid.py](flockers/boid.py): The Boid agent class.
* [flockers/SimpleContinuousModule.py](flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions.
* [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.
* [flockers/server.py](flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above
* [boid_flockers/model.py](boid_flockers/model.py): Core model file; contains the Boid Model and Boid Agent class.
* [boid_flockers/SimpleContinuousModule.py](boid_flockers/SimpleContinuousModule.py): Defines ``SimpleCanvas``, the Python side of a custom visualization module for drawing agents with continuous positions.
* [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.
* [boid_flockers/server.py](boid_flockers/server.py): Sets up the visualization; uses the SimpleCanvas element defined above
* [run.py](run.py) Launches the visualization.
* [Flocker Test.ipynb](Flocker Test.ipynb): Tests the model in a Jupyter notebook.
* [Flocker_Test.ipynb](Flocker_Test.ipynb): Tests the model in a Jupyter notebook.

## Further Reading

=======
* Launch the visualization
```
$ mesa runserver
```
* Visit your browser: http://127.0.0.1:8521/
* In your browser hit *run*
The following link can be visited for more information on the boid flockers model:
https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/modeling-natural-systems/boids.html
4 changes: 2 additions & 2 deletions examples/boid_flockers/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def boid_draw(agent):
}

page = JupyterViz(
BoidFlockers,
model_params,
model_class=BoidFlockers,
model_params=model_params,
measures=[],
name="BoidFlockers",
agent_portrayal=boid_draw,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@

class SimpleCanvas(mesa.visualization.VisualizationElement):
local_includes = ["boid_flockers/simple_continuous_canvas.js"]
portrayal_method = None
canvas_height = 500
canvas_width = 500

def __init__(self, portrayal_method, canvas_height=500, canvas_width=500):
def __init__(self, portrayal_method=None, canvas_height=500, canvas_width=500):
"""
Instantiate a new SimpleCanvas
"""
Expand Down
104 changes: 0 additions & 104 deletions examples/boid_flockers/boid_flockers/boid.py

This file was deleted.

123 changes: 112 additions & 11 deletions examples/boid_flockers/boid_flockers/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,108 @@
import mesa
import numpy as np

from .boid import Boid

class Boid(mesa.Agent):
"""
A Boid-style flocker agent.

The agent follows three behaviors to flock:
- Cohesion: steering towards neighboring agents.
- Separation: avoiding getting too close to any other agent.
- Alignment: try to fly in the same direction as the neighbors.

Boids have a vision that defines the radius in which they look for their
neighbors to flock with. Their speed (a scalar) and direction (a vector)
define their movement. Separation is their desired minimum distance from
any other Boid.
"""

def __init__(
self,
unique_id,
model,
pos,
speed,
direction,
vision,
separation,
cohere=0.025,
separate=0.25,
match=0.04,
):
"""
Create a new Boid flocker agent.

Args:
unique_id: Unique agent identifyer.
pos: Starting position
speed: Distance to move per step.
direction: numpy vector for the Boid's direction of movement.
vision: Radius to look around for nearby Boids.
separation: Minimum distance to maintain from other Boids.
cohere: the relative importance of matching neighbors' positions
separate: the relative importance of avoiding close neighbors
match: the relative importance of matching neighbors' headings
"""
super().__init__(unique_id, model)
self.pos = np.array(pos)
self.speed = speed
self.direction = direction
self.vision = vision
self.separation = separation
self.cohere_factor = cohere
self.separate_factor = separate
self.match_factor = match
self.neighbors = None

def cohere(self):
"""
Return the vector toward the center of mass of the local neighbors.
"""
cohere = np.zeros(2)
if self.neighbors:
for neighbor in self.neighbors:
cohere += self.model.space.get_heading(self.pos, neighbor.pos)
cohere /= len(self.neighbors)
return cohere

def separate(self):
"""
Return a vector away from any neighbors closer than separation dist.
"""
me = self.pos
them = (n.pos for n in self.neighbors)
separation_vector = np.zeros(2)
for other in them:
if self.model.space.get_distance(me, other) < self.separation:
separation_vector -= self.model.space.get_heading(me, other)
return separation_vector

def match_heading(self):
"""
Return a vector of the neighbors' average heading.
"""
match_vector = np.zeros(2)
if self.neighbors:
for neighbor in self.neighbors:
match_vector += neighbor.direction
match_vector /= len(self.neighbors)
return match_vector

def step(self):
"""
Get the Boid's neighbors, compute the new vector, and move accordingly.
"""

self.neighbors = self.model.space.get_neighbors(self.pos, self.vision, False)
self.direction += (
self.cohere() * self.cohere_factor
+ self.separate() * self.separate_factor
+ self.match_heading() * self.match_factor
) / 2
self.direction /= np.linalg.norm(self.direction)
new_pos = self.pos + self.direction * self.speed
self.model.space.move_agent(self, new_pos)


class BoidFlockers(mesa.Model):
Expand Down Expand Up @@ -39,7 +140,8 @@ def __init__(
separation: What's the minimum distance each Boid will attempt to
keep from any other
cohere, separate, match: factors for the relative importance of
the three drives."""
the three drives.
"""
super().__init__()
self.population = population
self.vision = vision
Expand All @@ -49,7 +151,6 @@ def __init__(
self.space = mesa.space.ContinuousSpace(width, height, True)
self.factors = {"cohere": cohere, "separate": separate, "match": match}
self.make_agents()
self.running = True

def make_agents(self):
"""
Expand All @@ -59,15 +160,15 @@ def make_agents(self):
x = self.random.random() * self.space.x_max
y = self.random.random() * self.space.y_max
pos = np.array((x, y))
velocity = np.random.random(2) * 2 - 1
direction = np.random.random(2) * 2 - 1
boid = Boid(
i,
self,
pos,
self.speed,
velocity,
self.vision,
self.separation,
unique_id=i,
model=self,
pos=pos,
speed=self.speed,
direction=direction,
vision=self.vision,
separation=self.separation,
**self.factors,
)
self.space.place_agent(boid, pos)
Expand Down
Loading