-
Notifications
You must be signed in to change notification settings - Fork 180
Add new termite example #251
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
Changes from 8 commits
3b6bcbb
f7dfc41
2e28660
89b93ba
df85824
35c6068
996e495
de11caa
048b26a
612787e
a1be110
0706d6b
b1c8d30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Termite WoodChip Behaviour | ||
|
||
This model simulates termites interacting with wood chips, inspired by the [NetLogo Termites model](https://ccl.northwestern.edu/netlogo/models/Termites). It explores emergent behavior in decentralized systems, demonstrating how simple agents (termites) collectively organize wood chips into piles without centralized coordination. | ||
|
||
## Summary | ||
|
||
In this simulation, multiple termite agents move randomly on a grid containing scattered wood chips. Each termite follows simple rules: | ||
|
||
1. Search for a wood chip. If found, pick it up and move away. | ||
2. When carrying a wood chip, search for a pile (another wood chip). | ||
3. When a pile is found, find a nearby empty space to place the carried chip. | ||
4. After dropping a chip, move away from the pile. | ||
|
||
Over time, these simple interactions lead to the formation of wood chip piles, illustrating decentralized organization without a central coordinator. | ||
|
||
## Installation | ||
|
||
Make sure that you have installed the `latest` version of mesa i.e `3.2` onwards. | ||
|
||
## Usage | ||
|
||
To run the simulation: | ||
```bash | ||
solara run app.py | ||
``` | ||
|
||
## Model Details | ||
|
||
### Agents | ||
|
||
- **Termite:** An agent that moves within the grid environment, capable of carrying a single wood chip at a time. The termite follows the precise logic of the original NetLogo model, with each behavior (searching, finding piles, dropping chips) continuing until successful. | ||
|
||
### Environment | ||
|
||
- **Grid:** A two-dimensional toroidal grid where termites interact with the wood chips. The toroidal nature means agents exiting one edge re-enter from the opposite edge. | ||
- **PropertyLayer:** A data structure overlaying the grid, storing the presence of wood chips at each cell. | ||
|
||
### Agent Behaviors | ||
|
||
- **wiggle():** Simulates random movement by selecting a random neighboring cell. | ||
- **search_for_chip():** Looks for a wood chip. If found, picks it up and moves forward significantly. | ||
- **find_new_pile():** When carrying a chip, searches for a cell that already has a wood chip. | ||
- **put_down_chip():** Attempts to place the carried wood chip in an empty cell near a pile. | ||
- **get_away():** After dropping a chip, moves away from the pile to prevent clustering. | ||
|
||
## References | ||
|
||
- Wilensky, U. (1997). NetLogo Termites model. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL. Available at: [NetLogo Termites Model](https://ccl.northwestern.edu/netlogo/models/Termites) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from mesa.visualization import SolaraViz | ||
from mesa.visualization.components.matplotlib_components import make_mpl_space_component | ||
from termites.model import TermiteModel | ||
|
||
wood_chip_portrayal = { | ||
"woodcell": { | ||
"color": "blue", | ||
"alpha": 0.6, | ||
"colorbar": False, | ||
"vmin": 0, | ||
"vmax": 2, | ||
}, | ||
} | ||
|
||
|
||
def agent_portrayal(agent): | ||
return {"marker": ">", "color": "red" if agent.hasWoodChip else "black", "size": 10} | ||
|
||
|
||
model_params = { | ||
"num_termites": { | ||
"type": "SliderInt", | ||
"value": 100, | ||
"label": "No. of Termites", | ||
"min": 10, | ||
"max": 500, | ||
"step": 1, | ||
}, | ||
"wood_chip_density": { | ||
"type": "SliderFloat", | ||
"value": 0.1, | ||
"label": "Wood Chip Density", | ||
"min": 0.01, | ||
"max": 1, | ||
"step": 0.1, | ||
}, | ||
"width": 60, | ||
"height": 60, | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
model = TermiteModel() | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def post_process(ax): | ||
ax.set_aspect("equal") | ||
ax.set_xticks([]) | ||
ax.set_yticks([]) | ||
|
||
|
||
woodchips_space = make_mpl_space_component( | ||
agent_portrayal=agent_portrayal, | ||
propertylayer_portrayal=wood_chip_portrayal, | ||
post_process=post_process, | ||
draw_grid=False, | ||
) | ||
|
||
page = SolaraViz( | ||
model, | ||
components=[woodchips_space], | ||
model_params=model_params, | ||
name="Termites Model", | ||
play_interval=1, | ||
render_interval=10, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
from mesa.discrete_space import CellAgent | ||
|
||
|
||
class Termite(CellAgent): | ||
""" | ||
A Termite agent that has ability to carry woodchip. | ||
|
||
Attributes: | ||
hasWoodChip(bool): True if the agent is carrying a wood chip. | ||
|
||
The termite will: | ||
1. Search for a cell with a wood chip (search_for_chip). | ||
2. Once it picks up the chip, search for a pile (find_new_pile). | ||
3. Put the chip down on an empty cell (put_down_chip), then move away (get_away). | ||
""" | ||
|
||
def __init__(self, model, cell): | ||
""" | ||
Args: | ||
model: The model instance. | ||
cell: The starting cell (position) of the agent. | ||
""" | ||
super().__init__(model) | ||
self.cell = cell | ||
self.hasWoodChip = False | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def wiggle(self): | ||
# Move the agent to a random neighboring cell. | ||
self.cell = self.cell.get_neighborhood(radius=3).select_random_cell() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I mag ask, why? Isn’t it more logical they choose a cell nearby? Is it performance? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initially tried the same (radius=1), but the model struggled to form even medium-sized piles, and there was no chance of combining these piles even after 10k+ steps. Also, performance was the bottleneck. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I also should add to it, termites are meant to search for woodchips, and its not mentioned anywhere that they should search their vicinity, therefore this approach just makes that search faster. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds valid. @Spartan-71 could you add this in one or two lines of documentation/comment? |
||
|
||
def search_for_chip(self): | ||
""" | ||
If the current cell has a wood chip, pick it up and move forward. | ||
Otherwise, wiggle and continue searching. | ||
""" | ||
# Check if current cell has a wood chip | ||
if self.cell.woodcell: | ||
# Pick up the wood chip | ||
self.cell.woodcell = False | ||
self.hasWoodChip = True | ||
|
||
# Move forward | ||
for _ in range(30): | ||
new_cell = self.cell.neighborhood.select_random_cell() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if new_cell.is_empty: | ||
self.cell = new_cell | ||
break | ||
return True | ||
else: | ||
# No chip found, wiggle and return False to continue searching | ||
self.wiggle() | ||
return False | ||
|
||
def find_new_pile(self): | ||
# Continue wiggling until finding a cell with a wood chip. | ||
if not self.cell.woodcell: | ||
self.wiggle() | ||
return False | ||
return True | ||
|
||
def put_down_chip(self): | ||
""" | ||
If current cell is empty (no wood chip), drop the chip. | ||
Otherwise, move forward, then try again. | ||
""" | ||
if not self.hasWoodChip: | ||
return True # Nothing to put down | ||
|
||
if not self.cell.woodcell: | ||
# Drop the chip | ||
self.cell.woodcell = True | ||
self.hasWoodChip = False | ||
|
||
# Get away from the pile | ||
self.get_away() | ||
return True | ||
else: | ||
# Move to a random neighbor | ||
empty_neighbors = [c for c in self.cell.neighborhood if c.is_empty] | ||
if empty_neighbors: | ||
self.cell = self.model.random.choice(empty_neighbors) | ||
return False | ||
|
||
def get_away(self): | ||
# Move 20 steps forward randomly untill on a cell with no wood chip. | ||
empty_neighbors = [ | ||
c for c in self.cell.get_neighborhood(radius=3) if c.is_empty | ||
] | ||
if empty_neighbors: | ||
self.cell = self.model.random.choice(empty_neighbors) | ||
|
||
for _ in range(20): | ||
new_cell = self.cell.neighborhood.select_random_cell() | ||
if new_cell.is_empty: | ||
self.cell = new_cell | ||
# If still on a wood chip, keep going | ||
if self.cell.woodcell: | ||
return self.get_away() | ||
break | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def step(self): | ||
""" | ||
Protocol which termite follows: | ||
1. Search for a wood chip if not carrying one. | ||
2. Find a new pile (a cell with a wood chip) if carrying a chip. | ||
3. Put down the chip if a suitable location is found. | ||
""" | ||
if not self.hasWoodChip: | ||
# Keep searching until termite find a chip | ||
while not self.search_for_chip(): | ||
pass | ||
|
||
# Keep looking for a pile until termite find one | ||
while not self.find_new_pile(): | ||
pass | ||
|
||
# Keep trying to put down the chip until successful | ||
while not self.put_down_chip(): | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import numpy as np | ||
from mesa import Model | ||
from mesa.discrete_space import OrthogonalMooreGrid, PropertyLayer | ||
|
||
from .agents import Termite | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class TermiteModel(Model): | ||
""" | ||
A simulation that depicts behavior of termite agents gathering wood chips into piles. | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
|
||
def __init__( | ||
self, num_termites=100, width=60, height=60, wood_chip_density=0.1, seed=None | ||
): | ||
"""Initialize the model. | ||
|
||
Args: | ||
num_termites: Number of Termite Agents, | ||
width: Grid width. | ||
height: Grid heights. | ||
wood_chip_density: Density of wood chips in the grid. | ||
seed : Random seed for reproducibility. | ||
""" | ||
super().__init__(seed=seed) | ||
self.num_termites = num_termites | ||
self.wood_chip_density = wood_chip_density | ||
|
||
self.grid = OrthogonalMooreGrid((width, height), torus=True) | ||
|
||
self.wood_chips_layer = PropertyLayer( | ||
"woodcell", (width, height), default_value=False, dtype=bool | ||
) | ||
self.wood_chips_layer.data = np.random.choice( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a smart way to initialize the PropertyLayer! That’s one of the advantages of using a standard data structure underneath, it can directly be accessed and modified. @Spartan-71, was this intuitive, or should we document this possibility better? CC @quaquel basically a two-liner to initialize the PorpertyLayers, really cool. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should document this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any suggestion where?
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
[True, False], | ||
size=(width, height), | ||
p=[self.wood_chip_density, 1 - self.wood_chip_density], | ||
) | ||
|
||
self.grid.add_property_layer(self.wood_chips_layer) | ||
|
||
Termite.create_agents( | ||
Spartan-71 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self, | ||
self.num_termites, | ||
self.random.sample(self.grid.all_cells.cells, k=self.num_termites), | ||
) | ||
|
||
def step(self): | ||
self.agents.shuffle_do("step") |
Uh oh!
There was an error while loading. Please reload this page.