From 3b6bcbbefbbaa07d80d67ffda37cba90c17228ee Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Sun, 30 Mar 2025 17:52:32 +0530 Subject: [PATCH 01/13] feat: draft for the new termite model example --- examples/termites/README.md | 47 +++++++++++++++++++++++++++ examples/termites/__init__.py | 0 examples/termites/agents.py | 59 +++++++++++++++++++++++++++++++++ examples/termites/app.py | 61 +++++++++++++++++++++++++++++++++++ examples/termites/model.py | 46 ++++++++++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 examples/termites/README.md create mode 100644 examples/termites/__init__.py create mode 100644 examples/termites/agents.py create mode 100644 examples/termites/app.py create mode 100644 examples/termites/model.py diff --git a/examples/termites/README.md b/examples/termites/README.md new file mode 100644 index 00000000..eed6c53a --- /dev/null +++ b/examples/termites/README.md @@ -0,0 +1,47 @@ +# 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. Move randomly. +2. If carrying a wood chip and encountering another, place the carried chip in a nearby empty space. +3. If not carrying a chip and encountering one, pick it up. + +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.0.dev0` + +## 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. + +### Environment + +- **Grid:** A two-dimensional toroidal grid where termites interacts 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. + +## 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) +--- + + + diff --git a/examples/termites/__init__.py b/examples/termites/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/termites/agents.py b/examples/termites/agents.py new file mode 100644 index 00000000..660a35dd --- /dev/null +++ b/examples/termites/agents.py @@ -0,0 +1,59 @@ +from mesa.discrete_space import CellAgent + + +class Termite(CellAgent): + """A Termite agent starts wandering randomly. + If it bumps into a wood chip, it finds a nearby empty space and puts its wood chip down. + + Attributes: + hasWoodChip(bool): True if the agent is carrying a wood chip. + """ + + def __init__(self,model,cell): + """ + Args: + model: The model instance. + cell: The startin cell (position) of the agent. + """ + super().__init__(model) + self.cell = cell + self.hasWoodChip = False + + def move(self): + # Move the agent to a random neighboring cell. + self.cell = self.cell.neighborhood.select_random_cell() + + def step(self): + # Move to a random neighboring cell + self.move() + + # Check if Woodchip is present on the cell + if self.cell.woodcell: + # Termite agnet is not carrying any woodchip + if not self.hasWoodChip: + # Pick up the woodchip + self.hasWoodChip = True + # Remove the wood chip from the cell + self.cell.woodcell = False + else: + """ + Termite agent is already carrying a woodchip and has bumped into another wood chip + then search for a empty space (no agent and no woodcell) in it's neighbourhood and drop the wood chip + """ + empty_cell_neighbors = [x for x in self.cell.neighborhood if x.is_empty and not x.woodcell] + + if empty_cell_neighbors: + # Moving to random empty cell + self.cell = self.cell.random.choice(empty_cell_neighbors) + # Drop the woodchip + self.hasWoodChip = False + self.cell.woodcell = True + else: + # Termite agent has a wood chip + if self.hasWoodChip: + # search for neighbors + wood_chip_neighbors = [x for x in self.cell.neighborhood if x.woodcell] + if wood_chip_neighbors: + # drop the Wood chip + self.hasWoodChip = False + self.cell.woodcell = True diff --git a/examples/termites/app.py b/examples/termites/app.py new file mode 100644 index 00000000..3f60cd29 --- /dev/null +++ b/examples/termites/app.py @@ -0,0 +1,61 @@ +from mesa.visualization import SolaraViz +from mesa.visualization.components.matplotlib_components import make_mpl_space_component +from 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": 50, + "label": "No. of Termites", + "min": 10, + "max": 100, + "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, +} + +model = TermiteModel(num_termites=400,width=100,height=100,wood_chip_density=0.1) + +woodchips_space = make_mpl_space_component( + agent_portrayal = agent_portrayal, + propertylayer_portrayal = wood_chip_portrayal, + post_process = None, + draw_grid=False, + ) + +page = SolaraViz( + model, + components = [woodchips_space], + model_params = model_params, + name = "Termites Model", + play_interval=1, + render_interval=15, +) + diff --git a/examples/termites/model.py b/examples/termites/model.py new file mode 100644 index 00000000..88c2df42 --- /dev/null +++ b/examples/termites/model.py @@ -0,0 +1,46 @@ +import numpy as np +from agents import Termite +from mesa import Model +from mesa.discrete_space import OrthogonalMooreGrid, PropertyLayer + + +class TermiteModel(Model): + """ + A simulation that depicts behavior of termite agents gathering wood chips into piles. + """ + + def __init__(self, num_termites=50, width=60, height=60, wood_chip_density=0.4, 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. + """ + super().__init__(seed=seed) + self.num_termites = num_termites + self.wood_chip_density = wood_chip_density + + # Initializing a OrthogonalMooreGrid: each cell has 8 neighbors + self.grid = OrthogonalMooreGrid((width, height), torus=True) + + # Initializing up a PropertyLayer(woodcell) for wood_chips + self.wood_chips_layer = PropertyLayer("woodcell",(width, height), default_value=False, dtype=bool) + self.wood_chips_layer.data = np.random.choice([True,False],size=(width,height),p=[self.wood_chip_density, 1-self.wood_chip_density]) + + # Adding PropertyLayer to the grid""" + self.grid.add_property_layer(self.wood_chips_layer) + + # Creating and adding termite agents to the grid + Termite.create_agents( + self, + self.num_termites, + self.random.sample(self.grid.all_cells.cells, k=self.num_termites), + ) + + + def step(self): + self.agents.shuffle_do("step") + + From f7dfc41231c5f7a502b8db6de47446ce45c03382 Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Sun, 30 Mar 2025 23:04:53 +0530 Subject: [PATCH 02/13] style: remove x, y axis scale --- examples/termites/app.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/termites/app.py b/examples/termites/app.py index 3f60cd29..d21ae1dc 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -43,10 +43,15 @@ def agent_portrayal(agent): model = TermiteModel(num_termites=400,width=100,height=100,wood_chip_density=0.1) +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 = None, + post_process = post_process, draw_grid=False, ) From 2e286603ea9cbc25e7c14070c4a397f5efba97ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 30 Mar 2025 17:37:25 +0000 Subject: [PATCH 03/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/termites/agents.py | 6 ++++-- examples/termites/app.py | 36 +++++++++++++++++------------------- examples/termites/model.py | 17 +++++++++++------ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/examples/termites/agents.py b/examples/termites/agents.py index 660a35dd..aeccf7d2 100644 --- a/examples/termites/agents.py +++ b/examples/termites/agents.py @@ -9,7 +9,7 @@ class Termite(CellAgent): hasWoodChip(bool): True if the agent is carrying a wood chip. """ - def __init__(self,model,cell): + def __init__(self, model, cell): """ Args: model: The model instance. @@ -40,7 +40,9 @@ def step(self): Termite agent is already carrying a woodchip and has bumped into another wood chip then search for a empty space (no agent and no woodcell) in it's neighbourhood and drop the wood chip """ - empty_cell_neighbors = [x for x in self.cell.neighborhood if x.is_empty and not x.woodcell] + empty_cell_neighbors = [ + x for x in self.cell.neighborhood if x.is_empty and not x.woodcell + ] if empty_cell_neighbors: # Moving to random empty cell diff --git a/examples/termites/app.py b/examples/termites/app.py index d21ae1dc..bcaa896d 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -3,25 +3,22 @@ from model import TermiteModel wood_chip_portrayal = { - "woodcell":{ + "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 - } + return {"marker": ">", "color": "red" if agent.hasWoodChip else "black", "size": 10} + model_params = { - "num_termites":{ + "num_termites": { "type": "SliderInt", "value": 50, "label": "No. of Termites", @@ -29,38 +26,39 @@ def agent_portrayal(agent): "max": 100, "step": 1, }, - "wood_chip_density":{ + "wood_chip_density": { "type": "SliderFloat", "value": 0.1, "label": "Wood Chip Density", "min": 0.01, "max": 1, - "step": 0.1 + "step": 0.1, }, "width": 60, "height": 60, } -model = TermiteModel(num_termites=400,width=100,height=100,wood_chip_density=0.1) +model = TermiteModel(num_termites=400, width=100, height=100, wood_chip_density=0.1) + 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, + 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", + components=[woodchips_space], + model_params=model_params, + name="Termites Model", play_interval=1, render_interval=15, ) - diff --git a/examples/termites/model.py b/examples/termites/model.py index 88c2df42..ddb0645b 100644 --- a/examples/termites/model.py +++ b/examples/termites/model.py @@ -9,7 +9,9 @@ class TermiteModel(Model): A simulation that depicts behavior of termite agents gathering wood chips into piles. """ - def __init__(self, num_termites=50, width=60, height=60, wood_chip_density=0.4, seed=None): + def __init__( + self, num_termites=50, width=60, height=60, wood_chip_density=0.4, seed=None + ): """Initialize the model. Args: @@ -26,8 +28,14 @@ def __init__(self, num_termites=50, width=60, height=60, wood_chip_density=0.4, self.grid = OrthogonalMooreGrid((width, height), torus=True) # Initializing up a PropertyLayer(woodcell) for wood_chips - self.wood_chips_layer = PropertyLayer("woodcell",(width, height), default_value=False, dtype=bool) - self.wood_chips_layer.data = np.random.choice([True,False],size=(width,height),p=[self.wood_chip_density, 1-self.wood_chip_density]) + self.wood_chips_layer = PropertyLayer( + "woodcell", (width, height), default_value=False, dtype=bool + ) + self.wood_chips_layer.data = np.random.choice( + [True, False], + size=(width, height), + p=[self.wood_chip_density, 1 - self.wood_chip_density], + ) # Adding PropertyLayer to the grid""" self.grid.add_property_layer(self.wood_chips_layer) @@ -39,8 +47,5 @@ def __init__(self, num_termites=50, width=60, height=60, wood_chip_density=0.4, self.random.sample(self.grid.all_cells.cells, k=self.num_termites), ) - def step(self): self.agents.shuffle_do("step") - - From 89b93ba7df8d9b9fb7104cd695d86bf8da67b01a Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Thu, 10 Apr 2025 22:01:12 +0530 Subject: [PATCH 04/13] chore: restructured --- examples/termites/app.py | 2 +- examples/termites/{ => termites}/__init__.py | 0 examples/termites/{ => termites}/agents.py | 0 examples/termites/{ => termites}/model.py | 3 ++- 4 files changed, 3 insertions(+), 2 deletions(-) rename examples/termites/{ => termites}/__init__.py (100%) rename examples/termites/{ => termites}/agents.py (100%) rename examples/termites/{ => termites}/model.py (98%) diff --git a/examples/termites/app.py b/examples/termites/app.py index bcaa896d..910de808 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -1,6 +1,6 @@ from mesa.visualization import SolaraViz from mesa.visualization.components.matplotlib_components import make_mpl_space_component -from model import TermiteModel +from termites.model import TermiteModel wood_chip_portrayal = { "woodcell": { diff --git a/examples/termites/__init__.py b/examples/termites/termites/__init__.py similarity index 100% rename from examples/termites/__init__.py rename to examples/termites/termites/__init__.py diff --git a/examples/termites/agents.py b/examples/termites/termites/agents.py similarity index 100% rename from examples/termites/agents.py rename to examples/termites/termites/agents.py diff --git a/examples/termites/model.py b/examples/termites/termites/model.py similarity index 98% rename from examples/termites/model.py rename to examples/termites/termites/model.py index ddb0645b..3c80f32e 100644 --- a/examples/termites/model.py +++ b/examples/termites/termites/model.py @@ -1,8 +1,9 @@ import numpy as np -from agents import Termite from mesa import Model from mesa.discrete_space import OrthogonalMooreGrid, PropertyLayer +from .agents import Termite + class TermiteModel(Model): """ From df85824a2a4b50c0173f7306df7041c26c3ca3e8 Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Fri, 11 Apr 2025 16:44:09 +0530 Subject: [PATCH 05/13] chore: optimized the step function --- examples/termites/app.py | 2 +- examples/termites/termites/agents.py | 30 +++++++++++++++++++++------- examples/termites/termites/model.py | 7 ++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/examples/termites/app.py b/examples/termites/app.py index 910de808..5ed65916 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -38,7 +38,7 @@ def agent_portrayal(agent): "height": 60, } -model = TermiteModel(num_termites=400, width=100, height=100, wood_chip_density=0.1) +model = TermiteModel() def post_process(ax): diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index aeccf7d2..9417614a 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -13,7 +13,7 @@ def __init__(self, model, cell): """ Args: model: The model instance. - cell: The startin cell (position) of the agent. + cell: The starting cell (position) of the agent. """ super().__init__(model) self.cell = cell @@ -29,7 +29,7 @@ def step(self): # Check if Woodchip is present on the cell if self.cell.woodcell: - # Termite agnet is not carrying any woodchip + # Termite agent is not carrying any woodchip if not self.hasWoodChip: # Pick up the woodchip self.hasWoodChip = True @@ -38,14 +38,14 @@ def step(self): else: """ Termite agent is already carrying a woodchip and has bumped into another wood chip - then search for a empty space (no agent and no woodcell) in it's neighbourhood and drop the wood chip + then search for an empty space (no agent and no woodcell) in its neighbourhood and drop the wood chip """ empty_cell_neighbors = [ x for x in self.cell.neighborhood if x.is_empty and not x.woodcell ] if empty_cell_neighbors: - # Moving to random empty cell + # Move to random empty cell self.cell = self.cell.random.choice(empty_cell_neighbors) # Drop the woodchip self.hasWoodChip = False @@ -56,6 +56,22 @@ def step(self): # search for neighbors wood_chip_neighbors = [x for x in self.cell.neighborhood if x.woodcell] if wood_chip_neighbors: - # drop the Wood chip - self.hasWoodChip = False - self.cell.woodcell = True + # Count wood chips in each neighbor's neighborhood to find denser clusters + neighbor_scores = {} + for neighbor in self.cell.neighborhood: + if not neighbor.woodcell and neighbor.is_empty: + # Count wood chips in this neighbor's neighborhood + count = sum(1 for n in neighbor.neighborhood if n.woodcell) + if count > 0: # Only consider cells with at least one wood chip nearby + neighbor_scores[neighbor] = count + + if neighbor_scores: + # Choose the cell with the highest wood chip density in its neighborhood + best_cell = max(neighbor_scores.items(), key=lambda x: x[1])[0] + self.cell = best_cell + self.hasWoodChip = False + self.cell.woodcell = True + else: + # If no good location found, use the original method + self.hasWoodChip = False + self.cell.woodcell = True diff --git a/examples/termites/termites/model.py b/examples/termites/termites/model.py index 3c80f32e..ed8f58de 100644 --- a/examples/termites/termites/model.py +++ b/examples/termites/termites/model.py @@ -11,7 +11,7 @@ class TermiteModel(Model): """ def __init__( - self, num_termites=50, width=60, height=60, wood_chip_density=0.4, seed=None + self, num_termites=50, width=60, height=60, wood_chip_density=0.1, seed=None ): """Initialize the model. @@ -20,15 +20,14 @@ def __init__( 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 - # Initializing a OrthogonalMooreGrid: each cell has 8 neighbors self.grid = OrthogonalMooreGrid((width, height), torus=True) - # Initializing up a PropertyLayer(woodcell) for wood_chips self.wood_chips_layer = PropertyLayer( "woodcell", (width, height), default_value=False, dtype=bool ) @@ -38,10 +37,8 @@ def __init__( p=[self.wood_chip_density, 1 - self.wood_chip_density], ) - # Adding PropertyLayer to the grid""" self.grid.add_property_layer(self.wood_chips_layer) - # Creating and adding termite agents to the grid Termite.create_agents( self, self.num_termites, From 35c6068404fb55246e3a131226a653ee5bdc05f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 11 Apr 2025 11:15:19 +0000 Subject: [PATCH 06/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/termites/termites/agents.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index 9417614a..6d03cf10 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -62,7 +62,9 @@ def step(self): if not neighbor.woodcell and neighbor.is_empty: # Count wood chips in this neighbor's neighborhood count = sum(1 for n in neighbor.neighborhood if n.woodcell) - if count > 0: # Only consider cells with at least one wood chip nearby + if ( + count > 0 + ): # Only consider cells with at least one wood chip nearby neighbor_scores[neighbor] = count if neighbor_scores: From 996e495078c8c2be7aacfe84d494a4bc65eaf7cb Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Sat, 12 Apr 2025 10:21:00 +0530 Subject: [PATCH 07/13] feat: changed agents step function logic --- examples/termites/README.md | 33 +++--- examples/termites/app.py | 6 +- examples/termites/termites/agents.py | 151 +++++++++++++++++---------- examples/termites/termites/model.py | 2 +- 4 files changed, 116 insertions(+), 76 deletions(-) diff --git a/examples/termites/README.md b/examples/termites/README.md index eed6c53a..e419b7bf 100644 --- a/examples/termites/README.md +++ b/examples/termites/README.md @@ -1,47 +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. Move randomly. -2. If carrying a wood chip and encountering another, place the carried chip in a nearby empty space. -3. If not carrying a chip and encountering one, pick it up. +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.0.dev0` +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. +- **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 interacts with the wood chips. The toroidal nature means agents exiting one edge re-enter from the opposite edge, +- **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. -- **PropertyLayer:** A data structure overlaying the grid, storing the presence of wood chips at each cell. - -## 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) ---- +### 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) \ No newline at end of file diff --git a/examples/termites/app.py b/examples/termites/app.py index 5ed65916..a4f7aa6f 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -20,10 +20,10 @@ def agent_portrayal(agent): model_params = { "num_termites": { "type": "SliderInt", - "value": 50, + "value": 100, "label": "No. of Termites", "min": 10, - "max": 100, + "max": 500, "step": 1, }, "wood_chip_density": { @@ -60,5 +60,5 @@ def post_process(ax): model_params=model_params, name="Termites Model", play_interval=1, - render_interval=15, + render_interval=10, ) diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index 6d03cf10..1405d4f9 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -2,11 +2,16 @@ class Termite(CellAgent): - """A Termite agent starts wandering randomly. - If it bumps into a wood chip, it finds a nearby empty space and puts its wood chip down. + """ + 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): @@ -19,61 +24,95 @@ def __init__(self, model, cell): self.cell = cell self.hasWoodChip = False - def move(self): + def wiggle(self): # Move the agent to a random neighboring cell. - self.cell = self.cell.neighborhood.select_random_cell() - - def step(self): - # Move to a random neighboring cell - self.move() + self.cell = self.cell.get_neighborhood(radius=3).select_random_cell() - # Check if Woodchip is present on the cell + 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: - # Termite agent is not carrying any woodchip - if not self.hasWoodChip: - # Pick up the woodchip - self.hasWoodChip = True - # Remove the wood chip from the cell - self.cell.woodcell = False - else: - """ - Termite agent is already carrying a woodchip and has bumped into another wood chip - then search for an empty space (no agent and no woodcell) in its neighbourhood and drop the wood chip - """ - empty_cell_neighbors = [ - x for x in self.cell.neighborhood if x.is_empty and not x.woodcell - ] - - if empty_cell_neighbors: - # Move to random empty cell - self.cell = self.cell.random.choice(empty_cell_neighbors) - # Drop the woodchip - self.hasWoodChip = False - self.cell.woodcell = True + # 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() + if new_cell.is_empty: + self.cell = new_cell + break + return True else: - # Termite agent has a wood chip - if self.hasWoodChip: - # search for neighbors - wood_chip_neighbors = [x for x in self.cell.neighborhood if x.woodcell] - if wood_chip_neighbors: - # Count wood chips in each neighbor's neighborhood to find denser clusters - neighbor_scores = {} - for neighbor in self.cell.neighborhood: - if not neighbor.woodcell and neighbor.is_empty: - # Count wood chips in this neighbor's neighborhood - count = sum(1 for n in neighbor.neighborhood if n.woodcell) - if ( - count > 0 - ): # Only consider cells with at least one wood chip nearby - neighbor_scores[neighbor] = count - - if neighbor_scores: - # Choose the cell with the highest wood chip density in its neighborhood - best_cell = max(neighbor_scores.items(), key=lambda x: x[1])[0] - self.cell = best_cell - self.hasWoodChip = False - self.cell.woodcell = True - else: - # If no good location found, use the original method - self.hasWoodChip = False - self.cell.woodcell = True + # 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 + + 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 diff --git a/examples/termites/termites/model.py b/examples/termites/termites/model.py index ed8f58de..f0b3a293 100644 --- a/examples/termites/termites/model.py +++ b/examples/termites/termites/model.py @@ -11,7 +11,7 @@ class TermiteModel(Model): """ def __init__( - self, num_termites=50, width=60, height=60, wood_chip_density=0.1, seed=None + self, num_termites=100, width=60, height=60, wood_chip_density=0.1, seed=None ): """Initialize the model. From de11caa1477d4ca7d6f028b5f5b5c91a9683f773 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 04:56:23 +0000 Subject: [PATCH 08/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/termites/termites/agents.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index 1405d4f9..73b5065c 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -82,9 +82,10 @@ def put_down_chip(self): 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] + 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) From 048b26a92aac70dc6fe627f969923ce37e226a7c Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Sat, 12 Apr 2025 22:39:46 +0530 Subject: [PATCH 09/13] feat: optimized the performance --- examples/termites/app.py | 27 ++++++++++++++---- examples/termites/termites/agents.py | 42 ++++------------------------ examples/termites/termites/model.py | 2 +- 3 files changed, 29 insertions(+), 42 deletions(-) diff --git a/examples/termites/app.py b/examples/termites/app.py index a4f7aa6f..2c6d8f4c 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -18,12 +18,17 @@ def agent_portrayal(agent): model_params = { + "seed":{ + "type":"InputText", + "value":42, + "label":"Seed", + }, "num_termites": { "type": "SliderInt", "value": 100, "label": "No. of Termites", "min": 10, - "max": 500, + "max": 1000, "step": 1, }, "wood_chip_density": { @@ -34,8 +39,22 @@ def agent_portrayal(agent): "max": 1, "step": 0.1, }, - "width": 60, - "height": 60, + "width": { + "type": "SliderInt", + "value": 100, + "label": "Width", + "min": 10, + "max": 500, + "step": 1, + }, + "height": { + "type": "SliderInt", + "value": 100, + "label": "Height", + "min": 10, + "max": 500, + "step": 1, + }, } model = TermiteModel() @@ -59,6 +78,4 @@ def post_process(ax): components=[woodchips_space], model_params=model_params, name="Termites Model", - play_interval=1, - render_interval=10, ) diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index 73b5065c..cc47a8ad 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -7,11 +7,6 @@ class Termite(CellAgent): 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): @@ -25,22 +20,14 @@ def __init__(self, model, cell): self.hasWoodChip = False def wiggle(self): - # Move the agent to a random neighboring cell. - self.cell = self.cell.get_neighborhood(radius=3).select_random_cell() + self.cell = self.model.random.choice(self.model.grid.all_cells.cells) 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): + for _ in range(10): new_cell = self.cell.neighborhood.select_random_cell() if new_cell.is_empty: self.cell = new_cell @@ -59,61 +46,44 @@ def find_new_pile(self): 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 + return True 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): + + for _ in range(10): 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 def step(self): """ - Protocol which termite follows: + Protocol which termite agent 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 diff --git a/examples/termites/termites/model.py b/examples/termites/termites/model.py index f0b3a293..a6db8a53 100644 --- a/examples/termites/termites/model.py +++ b/examples/termites/termites/model.py @@ -11,7 +11,7 @@ class TermiteModel(Model): """ def __init__( - self, num_termites=100, width=60, height=60, wood_chip_density=0.1, seed=None + self, num_termites=100, width=100, height=100, wood_chip_density=0.1, seed=42 ): """Initialize the model. From 612787e8f4130f15d7f04f2a1634a69a7191d7d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 12 Apr 2025 17:12:05 +0000 Subject: [PATCH 10/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/termites/app.py | 8 ++++---- examples/termites/termites/agents.py | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/termites/app.py b/examples/termites/app.py index 2c6d8f4c..3390d78d 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -18,10 +18,10 @@ def agent_portrayal(agent): model_params = { - "seed":{ - "type":"InputText", - "value":42, - "label":"Seed", + "seed": { + "type": "InputText", + "value": 42, + "label": "Seed", }, "num_termites": { "type": "SliderInt", diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index cc47a8ad..46843de1 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -62,7 +62,6 @@ def put_down_chip(self): return False def get_away(self): - for _ in range(10): new_cell = self.cell.neighborhood.select_random_cell() if new_cell.is_empty: From a1be110b891ae082bc78a64daf5753f1a8ee177d Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Mon, 14 Apr 2025 10:16:43 +0530 Subject: [PATCH 11/13] chore: improved readability --- examples/termites/README.md | 2 +- examples/termites/app.py | 2 +- examples/termites/termites/agents.py | 12 ++++++------ examples/termites/termites/model.py | 11 +++++++---- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/examples/termites/README.md b/examples/termites/README.md index e419b7bf..302bce01 100644 --- a/examples/termites/README.md +++ b/examples/termites/README.md @@ -15,7 +15,7 @@ Over time, these simple interactions lead to the formation of wood chip piles, i ## Installation -Make sure that you have installed the `latest` version of mesa i.e `3.2` onwards. +Make sure that you have installed the `latest` version of mesa. ## Usage diff --git a/examples/termites/app.py b/examples/termites/app.py index 3390d78d..35455a72 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -14,7 +14,7 @@ def agent_portrayal(agent): - return {"marker": ">", "color": "red" if agent.hasWoodChip else "black", "size": 10} + return {"marker": ">", "color": "red" if agent.has_woodchip else "black", "size": 10} model_params = { diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index 46843de1..6bec68ea 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -6,7 +6,7 @@ class Termite(CellAgent): A Termite agent that has ability to carry woodchip. Attributes: - hasWoodChip(bool): True if the agent is carrying a wood chip. + has_woodchip(bool): True if the agent is carrying a wood chip. """ def __init__(self, model, cell): @@ -17,7 +17,7 @@ def __init__(self, model, cell): """ super().__init__(model) self.cell = cell - self.hasWoodChip = False + self.has_woodchip = False def wiggle(self): self.cell = self.model.random.choice(self.model.grid.all_cells.cells) @@ -25,7 +25,7 @@ def wiggle(self): def search_for_chip(self): if self.cell.woodcell: self.cell.woodcell = False - self.hasWoodChip = True + self.has_woodchip = True for _ in range(10): new_cell = self.cell.neighborhood.select_random_cell() @@ -46,12 +46,12 @@ def find_new_pile(self): return True def put_down_chip(self): - if not self.hasWoodChip: + if not self.has_woodchip: return True if not self.cell.woodcell: self.cell.woodcell = True - self.hasWoodChip = False + self.has_woodchip = False self.get_away() return True @@ -77,7 +77,7 @@ def step(self): 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: + if not self.has_woodchip: while not self.search_for_chip(): pass diff --git a/examples/termites/termites/model.py b/examples/termites/termites/model.py index a6db8a53..e4903bfe 100644 --- a/examples/termites/termites/model.py +++ b/examples/termites/termites/model.py @@ -7,7 +7,7 @@ class TermiteModel(Model): """ - A simulation that depicts behavior of termite agents gathering wood chips into piles. + A simulation that shows behavior of termite agents gathering wood chips into piles. """ def __init__( @@ -31,6 +31,8 @@ def __init__( self.wood_chips_layer = PropertyLayer( "woodcell", (width, height), default_value=False, dtype=bool ) + + # Randomly distribute wood chips, by directly modifying the layer's underlying ndarray self.wood_chips_layer.data = np.random.choice( [True, False], size=(width, height), @@ -39,10 +41,11 @@ def __init__( self.grid.add_property_layer(self.wood_chips_layer) + # Create agents and randomly distribute them over the grid Termite.create_agents( - self, - self.num_termites, - self.random.sample(self.grid.all_cells.cells, k=self.num_termites), + model = self, + n = self.num_termites, + cell = self.random.sample(self.grid.all_cells.cells, k=self.num_termites), ) def step(self): From 0706d6b15fd0a4d64334f40b1932752a9cd40eea Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Mon, 14 Apr 2025 11:11:19 +0530 Subject: [PATCH 12/13] style: formatted --- examples/termites/app.py | 6 +++++- examples/termites/termites/model.py | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/termites/app.py b/examples/termites/app.py index 35455a72..ac736ec0 100644 --- a/examples/termites/app.py +++ b/examples/termites/app.py @@ -14,7 +14,11 @@ def agent_portrayal(agent): - return {"marker": ">", "color": "red" if agent.has_woodchip else "black", "size": 10} + return { + "marker": ">", + "color": "red" if agent.has_woodchip else "black", + "size": 10, + } model_params = { diff --git a/examples/termites/termites/model.py b/examples/termites/termites/model.py index e4903bfe..8df39b9a 100644 --- a/examples/termites/termites/model.py +++ b/examples/termites/termites/model.py @@ -43,9 +43,9 @@ def __init__( # Create agents and randomly distribute them over the grid Termite.create_agents( - model = self, - n = self.num_termites, - cell = self.random.sample(self.grid.all_cells.cells, k=self.num_termites), + model=self, + n=self.num_termites, + cell=self.random.sample(self.grid.all_cells.cells, k=self.num_termites), ) def step(self): From b1c8d30d7dd778233018968f22b251a0cf70d2c5 Mon Sep 17 00:00:00 2001 From: Spartan-71 Date: Mon, 14 Apr 2025 12:04:15 +0530 Subject: [PATCH 13/13] fix: imports --- examples/termites/termites/agents.py | 2 +- examples/termites/termites/model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/termites/termites/agents.py b/examples/termites/termites/agents.py index 6bec68ea..4bcdcaa3 100644 --- a/examples/termites/termites/agents.py +++ b/examples/termites/termites/agents.py @@ -1,4 +1,4 @@ -from mesa.discrete_space import CellAgent +from mesa.experimental.cell_space import CellAgent class Termite(CellAgent): diff --git a/examples/termites/termites/model.py b/examples/termites/termites/model.py index 8df39b9a..71a1bd06 100644 --- a/examples/termites/termites/model.py +++ b/examples/termites/termites/model.py @@ -1,6 +1,6 @@ import numpy as np from mesa import Model -from mesa.discrete_space import OrthogonalMooreGrid, PropertyLayer +from mesa.experimental.cell_space import OrthogonalMooreGrid, PropertyLayer from .agents import Termite