Skip to content

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open

Conversation

Spartan-71
Copy link
Collaborator

@Spartan-71 Spartan-71 commented Mar 30, 2025

Add New Termite Simulation Example

Summary

This PR introduces a new termite simulation example inspired by the classic termite wood chip gathering behavior. The simulation models termites moving randomly on a grid containing scattered wood chips, following simple rules to form piles over time.

Features

  • Termite Agents: Move randomly and interact with wood chips.
  • Wood Chip Gathering: Termites pick up and drop wood chips based on proximity.
  • Emergent Behavior: Simple rules lead to self-organized wood chip piles.
  • Visualization: Grid-based representation of termite movements and chip aggregation.

Implementation Details

  • Uses an OrthogonalMooreGrid for termite movement.
  • A PropertyLayer is utilized to track wood chip locations.
  • Termite agents follow rule-based interactions to create emergent structures.

Visualization

image

Remark

  • Currently, the piles formed by the termite agents are small in size. Moreover, the model struggles to combine these small piles into a larger one, even after 10k+ steps.
  • The termite agent's step function needs refinement. Would love to discuss the logic for the step function with the community.

Notes

  • This example is inspired by the NetLogo Termites model.
  • The simulation demonstrates a decentralized organization without a central controller.

Summary by CodeRabbit

  • New Features

    • Introduced an interactive termite simulation demonstrating how simple agents coordinate to form wood chip piles.
    • Users can adjust parameters like termite quantity and wood chip density via intuitive controls for real-time visualization.
  • Documentation

    • Added comprehensive instructions covering installation, usage, and detailed model behavior for a smooth user experience.

@EwoutH
Copy link
Member

EwoutH commented Apr 10, 2025

Thanks for this PR, I'm terribly sorry we didn't get back to you sooner.

In general, if no one gets back to you within 3 days, please ping us!

@Sahil-Chhoker @sanika-n since most of us are reviewing 100+ proposals, could (one of) you maybe review this example?

@Sahil-Chhoker
Copy link
Collaborator

@Sahil-Chhoker @sanika-n since most of us are reviewing 100+ proposals, could (one of) you maybe review this example?

I can take a look at this tomorrow.

Copy link

coderabbitai bot commented Apr 10, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The changes introduce a termite simulation using the Mesa framework. A new README provides an overview, installation instructions, and simulation details. An interactive visualization is added via a new app.py file that sets up model parameters, visual components, and rendering logic using SolaraViz. Additionally, a new termite agent class with movement and decision logic is implemented along with a model class that orchestrates the grid setup, wood chip distribution, and simulation steps.

Changes

File(s) Change Summary
examples/termites/README.md Added a new README documenting the termite simulation model, installation steps, usage instructions, and model details.
examples/termites/app.py Introduced interactive visualization logic using Mesa and SolaraViz, including agent portrayal and model parameter configuration.
examples/termites/termites/[agents.py, model.py] Added simulation logic: a new Termite agent class (with movement and step functions) in agents.py and a TermiteModel class (setting up a toroidal grid, wood chip distribution, and agent placement) in model.py.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Viz as SolaraViz
    participant Model as TermiteModel
    participant Agent as Termite Agent
    participant Renderer as Visualization Component

    User->>Viz: Start simulation
    Viz->>Model: Initialize termite model with parameters
    Model->>Agent: Create termite agents on grid
    loop Simulation Steps
        Agent->>Model: Execute step (move, pick up/drop wood chip)
        Model->>Renderer: Update visualization
    end
    Renderer-->>User: Display updated simulation view
Loading

Poem

Hopping through fields of code so light,
I spy termites at play in digital sight.
Wood chips align as agents move with grace,
Grids come alive in this cyber space.
A joyful hop for each new, clever trace! 🐇✨


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@Sahil-Chhoker
Copy link
Collaborator

Sahil-Chhoker commented Apr 10, 2025

@coderabbitai full review

Copy link

coderabbitai bot commented Apr 10, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

coderabbitai bot commented Apr 10, 2025

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (17)
examples/termites/termites/model.py (4)

13-24: Improve docstring completeness for __init__

The method docstring has two minor issues:

  1. It's missing documentation for the seed parameter
  2. There's a grammatical error in "Grid heights" (should be "Grid height")
-        """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.
-        """
+        """Initialize the model.
+
+        Args:
+            num_termites: Number of Termite Agents.
+            width: Grid width.
+            height: Grid height.
+            wood_chip_density: Density of wood chips in the grid.
+            seed: Random seed for reproducibility.
+        """

24-26: Store width and height as instance variables

You're using width and height to set up the grid and property layer, but you're not storing these values as instance variables. This might be useful for future reference or for other methods in the class.

        super().__init__(seed=seed)
        self.num_termites = num_termites
        self.wood_chip_density = wood_chip_density
+       self.width = width
+       self.height = height

41-42: Fix comment syntax

The comment has an extra triple quote which should be removed.

-        # Adding PropertyLayer to the grid"""
+        # Adding PropertyLayer to the grid

51-52: Consider adding data collection capabilities

The current model implementation focuses on simulation without data collection. Consider adding a mechanism for collecting metrics over time, such as the number and size of wood chip piles, to help analyze the emergent behavior.

You could add a DataCollector to track metrics like wood chip pile sizes, average pile distance, or termite movement patterns, which would allow for quantitative analysis of the simulation outcomes.

examples/termites/README.md (5)

18-18: Fix abbreviation format

The abbreviation "i.e" should include the second period.

-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.0.dev0`
🧰 Tools
🪛 LanguageTool

[uncategorized] ~18-~18: The abbreviation “i.e.” (= that is) requires two periods.
Context: ... installed the latest version of mesa i.e 3.2.0.dev0 ## Usage To run the simu...

(I_E)


37-37: Fix subject-verb agreement

The verb "interacts" doesn't agree with the plural subject "termites".

-- **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,
🧰 Tools
🪛 LanguageTool

[uncategorized] ~37-~37: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...imensional toroidal grid where termites interacts with the wood chips. The toroidal natur...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)


37-37: Remove trailing comma

There's an unnecessary trailing comma at the end of this sentence.

-- **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.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~37-~37: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...imensional toroidal grid where termites interacts with the wood chips. The toroidal natur...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)


39-39: Fix extra spacing

There are two consecutive spaces between "storing" and "the".

-- **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.

18-26: Enhance installation instructions

The current installation instructions could be more specific about how to install the required version of Mesa.

-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.0.dev0`
+
+```bash
+pip install mesa==3.2.0.dev0
+```
+
 ## Usage
🧰 Tools
🪛 LanguageTool

[uncategorized] ~18-~18: The abbreviation “i.e.” (= that is) requires two periods.
Context: ... installed the latest version of mesa i.e 3.2.0.dev0 ## Usage To run the simu...

(I_E)

examples/termites/termites/agents.py (4)

16-16: Fix typo in docstring

There's a typo in "startin cell" which should be "starting cell".

-            cell: The startin cell (position) of the agent.
+            cell: The starting cell (position) of the agent.

9-9: Fix attribute documentation style

The attribute documentation uses parentheses which is not the conventional Python style. Remove the parentheses for better readability.

-        hasWoodChip(bool): True if the agent is carrying a wood chip.
+        hasWoodChip: bool. True if the agent is carrying a wood chip.

32-32: Fix typo in comment

There's a typo in "Termite agnet" which should be "Termite agent".

-            # Termite agnet is not carrying any woodchip
+            # Termite agent is not carrying any woodchip

41-41: Fix grammatical errors in comment

There are two grammatical errors in this comment:

  1. "a empty space" should be "an empty space"
  2. "it's neighbourhood" should be "its neighbourhood" (possessive form)
-                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
examples/termites/app.py (4)

37-39: Add slider configuration for width and height parameters

Currently, width and height parameters don't have slider configurations like num_termites and wood_chip_density. Consider adding sliders for these parameters to provide users with more control over the simulation environment.

    "wood_chip_density": {
        "type": "SliderFloat",
        "value": 0.1,
        "label": "Wood Chip Density",
        "min": 0.01,
        "max": 1,
        "step": 0.1,
    },
-    "width": 60,
-    "height": 60,
+    "width": {
+        "type": "SliderInt",
+        "value": 60,
+        "label": "Grid Width",
+        "min": 20,
+        "max": 150,
+        "step": 10,
+    },
+    "height": {
+        "type": "SliderInt",
+        "value": 60,
+        "label": "Grid Height",
+        "min": 20,
+        "max": 150,
+        "step": 10,
+    },
}

3-3: Consider using relative import for project structure consistency

You're using an absolute import from termites.model import TermiteModel which might cause issues depending on how the project is installed or structured. Consider using a relative import for better maintainability.

-from termites.model import TermiteModel
+from .termites.model import TermiteModel

Note: The exact relative import pattern might vary depending on your project structure.


16-17: Add docstring to agent_portrayal function

Adding a docstring would improve code documentation and make the function's purpose clearer to other developers.

 def agent_portrayal(agent):
+    """
+    Define the visual representation of termite agents.
+    
+    Args:
+        agent: The termite agent to portray.
+        
+    Returns:
+        dict: A dictionary with marker style, color based on whether the agent
+              is carrying a wood chip, and size.
+    """
     return {"marker": ">", "color": "red" if agent.hasWoodChip else "black", "size": 10}

44-48: Add docstring to post_process function

Adding a docstring would improve code documentation and make the function's purpose clearer to other developers.

 def post_process(ax):
+    """
+    Adjust the visual aspects of the plot.
+    
+    Args:
+        ax: The matplotlib axes object to modify.
+    """
     ax.set_aspect("equal")
     ax.set_xticks([])
     ax.set_yticks([])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cac1a8 and 89b93ba.

📒 Files selected for processing (4)
  • examples/termites/README.md (1 hunks)
  • examples/termites/app.py (1 hunks)
  • examples/termites/termites/agents.py (1 hunks)
  • examples/termites/termites/model.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
examples/termites/termites/model.py (1)
examples/termites/termites/agents.py (2)
  • Termite (4-61)
  • step (26-61)
examples/termites/termites/agents.py (1)
examples/termites/termites/model.py (1)
  • step (51-52)
examples/termites/app.py (1)
examples/termites/termites/model.py (1)
  • TermiteModel (8-52)
🪛 LanguageTool
examples/termites/README.md

[style] ~10-~10: This phrase is redundant. Consider writing “space”.
Context: ...her, place the carried chip in a nearby empty space. 3. If not carrying a chip and encounte...

(EMPTY_HOLE)


[uncategorized] ~18-~18: The abbreviation “i.e.” (= that is) requires two periods.
Context: ... installed the latest version of mesa i.e 3.2.0.dev0 ## Usage To run the simu...

(I_E)


[uncategorized] ~37-~37: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...imensional toroidal grid where termites interacts with the wood chips. The toroidal natur...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

Comment on lines 53 to 61
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
Copy link

@coderabbitai coderabbitai bot Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize wood chip dropping behavior

The current implementation of the behavior when a termite is carrying a wood chip but there's no wood chip at its current location could potentially be improved. Based on the PR objectives, the simulation is intended to form piles of wood chips, but the current logic only checks if there are neighboring cells with wood chips without considering larger pile formation.

Consider enhancing the logic to prefer dropping wood chips near larger existing piles by counting the number of wood chips in the neighborhood:

         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
+                    # 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

This enhancement would encourage termites to drop wood chips where there are already concentrations of wood chips, leading to larger pile formation as mentioned in the PR objectives.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, with the introduction of neighbor_score and logic around it, the termite agents can form larger piles. But they are currently struggling with combining these small/medium-sized piles to form a single larger pile.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Spartan-71 I understand the challenge - forming larger piles locally is now possible, but merging these separate piles into a single large pile remains difficult. Here's an approach that could help with this global pile merging behavior:

def step(self):
    # First decide if we should move randomly or toward a large pile
    if self.hasWoodChip:
        # When carrying a chip, occasionally search for larger piles within extended radius
        if self.random.random()

< 0.3:  # 30% chance to look for larger piles
            self.move_toward_largest_pile(search_radius=5)  # Search in wider radius
        else:
            self.move()  # Random movement
    else:
        self.move()  # Random movement when not carrying a chip
        
    # Check if wood chip is present on the current cell
    if self.cell.woodcell:
        # Determine whether to pick up a chip based on local pile density
        local_density = sum(1 for n in self.cell.neighborhood if n.woodcell)
        
        # More likely to pick up from smaller piles (lower density)
        pickup_probability = 0.9 - (local_density / 10)  # Scales from 0.9 to ~0.0
        
        if not self.hasWoodChip and self.random.random() < pickup_probability:
            # Pick up the wood chip (more likely if it's isolated or in a small pile)
            self.hasWoodChip = True
            self.cell.woodcell = False
        elif self.hasWoodChip:
            # Find a good place to drop near a dense pile
            empty_neighbors = [x for x in self.cell.neighborhood if x.is_empty and not x.woodcell]
            if empty_neighbors:
                # Move to empty cell and drop
                self.cell = self.random.choice(empty_neighbors)
                self.hasWoodChip = False
                self.cell.woodcell = True
    else:
        # Current cell has no wood chip
        if self.hasWoodChip:
            # Check if we're near other wood chips
            wood_chip_neighbors = [x for x in self.cell.neighborhood if x.woodcell]
            if wood_chip_neighbors:
                # Try to find the best location with highest density in an extended area
                best_spot = self.find_best_drop_location(radius=3)
                if best_spot:
                    self.cell = best_spot
                # Drop the wood chip at current location
                self.hasWoodChip = False
                self.cell.woodcell = True

def move_toward_largest_pile(self, search_radius=5):
    """Move toward the largest pile within the given search radius."""
    # Find cells with wood chips in an extended radius
    pile_locations = []
    visited = set()
    queue = [(self.cell, 0)]  # (cell, distance)
    
    while queue:
        current, distance = queue.pop(0)
        if current in visited or distance > search_radius:
            continue
            
        visited.add(current)
        
        if current.woodcell:
            pile_locations.append(current)
            
        if distance < search_radius:
            for neighbor in current.neighborhood:
                if neighbor not in visited:
                    queue.append((neighbor, distance + 1))
    
    if not pile_locations:
        self.move()  # No piles found, move randomly
        return
        
    # Calculate pile sizes by checking density around each pile location
    pile_sizes = {}
    for loc in pile_locations:
        size = 1  # Start with 1 for the cell itself
        for n in loc.neighborhood:
            if n.woodcell:
                size += 1
        pile_sizes[loc] = size
        
    # Move toward a larger pile with some probability (weighted by size)
    weights = [size for size in pile_sizes.values()]
    target = self.random.choices(list(pile_sizes.keys()), weights=weights, k=1)[0]
    
    # Move one step toward the target pile
    path = self.find_path_to(target)
    if path and len(path) > 1:
        self.cell = path[1]  # Move to the next cell in the path

def find_best_drop_location(self, radius=3):
    """Find the best location to drop a wood chip within the given radius."""
    # Similar to move_toward_largest_pile but returns the best cell to drop
    # Implementation would find cells near high-density areas but with an empty spot
    # [Code would be similar to move_toward_largest_pile but with different selection criteria]
    pass

def find_path_to(self, target):
    """Find a path to the target cell using breadth-first search."""
    # Simple BFS implementation to find path
    # [BFS implementation code here]
    pass

This enhanced algorithm introduces several key improvements:

  1. Probability-based wood chip pickup: Termites are more likely to pick up chips from smaller piles and less likely from larger ones
  2. Extended sensing radius: Termites can detect larger piles beyond their immediate neighborhood
  3. Directed movement: Termites carrying wood chips occasionally move toward larger detected piles
  4. Weighted selection: Larger piles have a higher chance of attracting termites with wood chips

You'd need to implement the helper methods like find_path_to() and complete find_best_drop_location(), but this approach should help termites gradually consolidate smaller piles into larger ones over time.

Would you like me to elaborate on any specific part of this solution?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
examples/termites/termites/model.py (1)

44-49: 🛠️ Refactor suggestion

Validate number of termites to prevent errors.

The code doesn't validate that num_termites doesn't exceed the number of available cells in the grid, which could cause an error with random.sample.

+        # Ensure num_termites doesn't exceed available cells
+        total_cells = width * height
+        if self.num_termites > total_cells:
+            self.num_termites = total_cells
+            print(f"Warning: Number of termites limited to total cells: {total_cells}")
+
         # 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),
         )
examples/termites/termites/agents.py (1)

53-61: Optimize wood chip dropping behavior.

The current implementation could be improved to better form wood chip piles as mentioned in the PR objectives.

         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
+                    # 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
examples/termites/app.py (1)

41-41: ⚠️ Potential issue

Model initialization parameters mismatch with model_params.

There's an inconsistency between the default values in model_params (50 termites, 60x60 grid) and the actual model instance (400 termites, 100x100 grid). This could confuse users since changing the sliders won't match the initial state.

-model = TermiteModel(num_termites=400, width=100, height=100, wood_chip_density=0.1)
+model = TermiteModel(
+    num_termites=model_params["num_termites"]["value"],
+    width=model_params["width"],
+    height=model_params["height"], 
+    wood_chip_density=model_params["wood_chip_density"]["value"]
+)
🧹 Nitpick comments (7)
examples/termites/termites/model.py (2)

8-24: The class definition looks good, but the docstring could be more detailed.

The TermiteModel class has appropriate parameters and a good basic docstring. Consider enhancing the docstring with more details about the model's purpose, mechanics, and expected emergent behavior.


41-42: Fix the comment formatting.

There's an extra set of quotes at the end of this comment.

-        # Adding PropertyLayer to the grid"""
+        # Adding PropertyLayer to the grid
examples/termites/README.md (2)

18-18: Fix the abbreviation format.

The abbreviation "i.e." (that is) requires two periods.

-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.0.dev0`
🧰 Tools
🪛 LanguageTool

[uncategorized] ~18-~18: The abbreviation “i.e.” (= that is) requires two periods.
Context: ... installed the latest version of mesa i.e 3.2.0.dev0 ## Usage To run the simu...

(I_E)


37-37: Fix subject-verb agreement.

The verb should agree with the plural subject "termites".

-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,
+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,
🧰 Tools
🪛 LanguageTool

[uncategorized] ~37-~37: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...imensional toroidal grid where termites interacts with the wood chips. The toroidal natur...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

examples/termites/termites/agents.py (3)

12-21: Fix typo in docstring.

There's a typo in the initialization method's docstring.

-            cell: The startin cell (position) of the agent.
+            cell: The starting cell (position) of the agent.

31-38: Fix typo in comment.

There's a misspelling in the comment.

-            # Termite agnet is not carrying any woodchip
+            # Termite agent is not carrying any woodchip

39-52: Fix apostrophe in comment and consider clearer variable naming.

There's an incorrect apostrophe usage in the comment.

-                then search for a empty space (no agent and no woodcell) in it's neighbourhood and drop the wood chip
+                then search for a empty space (no agent and no woodcell) in its neighbourhood and drop the wood chip
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cac1a8 and 89b93ba.

📒 Files selected for processing (4)
  • examples/termites/README.md (1 hunks)
  • examples/termites/app.py (1 hunks)
  • examples/termites/termites/agents.py (1 hunks)
  • examples/termites/termites/model.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
examples/termites/termites/model.py (1)
examples/termites/termites/agents.py (2)
  • Termite (4-61)
  • step (26-61)
examples/termites/termites/agents.py (1)
examples/termites/termites/model.py (1)
  • step (51-52)
examples/termites/app.py (1)
examples/termites/termites/model.py (1)
  • TermiteModel (8-52)
🪛 LanguageTool
examples/termites/README.md

[style] ~10-~10: This phrase is redundant. Consider writing “space”.
Context: ...her, place the carried chip in a nearby empty space. 3. If not carrying a chip and encounte...

(EMPTY_HOLE)


[uncategorized] ~18-~18: The abbreviation “i.e.” (= that is) requires two periods.
Context: ... installed the latest version of mesa i.e 3.2.0.dev0 ## Usage To run the simu...

(I_E)


[uncategorized] ~37-~37: This verb does not appear to agree with the subject. Consider using a different form.
Context: ...imensional toroidal grid where termites interacts with the wood chips. The toroidal natur...

(AI_EN_LECTOR_REPLACEMENT_VERB_AGREEMENT)

🔇 Additional comments (14)
examples/termites/termites/model.py (3)

1-5: LGTM! Imports are appropriate for the model.

The imports correctly bring in the necessary modules for the termite model, including numpy for array operations, Mesa's Model class, grid components, and the local Termite agent class.


28-39: LGTM! Grid and wood chip distribution setup is well-implemented.

The implementation correctly sets up an OrthogonalMooreGrid with 8 neighbors per cell and creates a PropertyLayer for the wood chips, with random distribution based on the specified density.


51-52: LGTM! Step method is appropriately simple.

The step method correctly shuffles and calls the step method of each agent, which handles the core simulation logic.

examples/termites/README.md (2)

1-14: The introduction and summary effectively explain the model's purpose.

The README provides a good overview of the termite simulation, its inspiration from NetLogo, and the emergent behavior of the decentralized organization system.

🧰 Tools
🪛 LanguageTool

[style] ~10-~10: This phrase is redundant. Consider writing “space”.
Context: ...her, place the carried chip in a nearby empty space. 3. If not carrying a chip and encounte...

(EMPTY_HOLE)


42-44: LGTM! References section properly credits the original model.

The references section appropriately cites the NetLogo Termites model that inspired this implementation.

examples/termites/termites/agents.py (2)

4-10: LGTM! The class definition and docstring clearly explain the agent behavior.

The Termite class is well-defined with a clear docstring explaining the agent's behavior and attributes.


22-25: LGTM! Move method is simple and effective.

The move method correctly implements random movement by selecting a random neighboring cell.

examples/termites/app.py (7)

1-4: LGTM! Imports are appropriate for the visualization.

The imports correctly bring in the necessary components from Mesa for visualization.


5-13: LGTM! The wood chip portrayal settings are well-defined.

The portrayal for wood chips has appropriate color, transparency, and other visual settings.


16-17: LGTM! Agent portrayal function is clear and effective.

The function properly differentiates between termites carrying wood chips (red) and those without (black).


20-39: LGTM! Model parameters are well-configured with appropriate ranges.

The sliders for adjusting termite count and wood chip density have reasonable default values and ranges.


44-48: LGTM! Post-processing function creates a clean visualization.

The function correctly sets equal aspect ratio and removes ticks for a cleaner presentation.


50-55: LGTM! Space component setup is well-configured.

The visualization component correctly incorporates agent and property layer portrayals with appropriate settings.


57-64: LGTM! SolaraViz setup is complete with appropriate parameters.

The visualization is properly configured with the model, components, parameters, and render intervals.

@EwoutH
Copy link
Member

EwoutH commented Apr 10, 2025

Thanks Sahil!

@Spartan-71 please let us know if/what you find useful of the coderabbit review! We’re still testing if this is useful for Mesa (and if so, what parts, fine tuning, etc.) (CC @adamamer20)

@Sahil-Chhoker
Copy link
Collaborator

@Spartan-71, this was just an AI review, I will do a proper review tomorrow, in the mean time you can look at the comments.

Copy link
Collaborator

@Sahil-Chhoker Sahil-Chhoker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code itself is good, I have some questions regarding the model itself,

  1. Did you find the wood chip dropping behavior suggested by coderabbit useful or it makes more sense to have simple calculations?
  2. Is there no interaction between the termites?
  3. Does it affect the simulation in any way that many termites can be on the same cell? Or would it make sense if only one termite per cell?
  4. Another thing I noticed that I am unable to replicate the results, are you sure you are not missing anything?

@Spartan-71
Copy link
Collaborator Author

Spartan-71 commented Apr 11, 2025

1. Did you find the wood chip dropping behavior suggested by coderabbit useful or it makes more sense to have simple calculations?

Yeah, it is slightly more optimized since the size of the woodchip piles that are getting formed are larger as compared to the previous one. Though I need to figure out a way to optimize this part a bit more so that it replicates the behaviour found in NetLogo's Model.

2. Is there no interaction between the termites?

Yes, there is no interaction.

3. Does it affect the simulation in any way that many termites can be on the same cell? Or would it make sense if only one termite per cell?

As per my understanding of this example, many termites on one cell don't improve the simulation in any way.

4. Another thing I noticed that I am unable to replicate the results, are you sure you are not missing anything?

As mentioned under the Review section of this PR:

Remark

  • Currently, the piles formed by the termite agents are small in size. Moreover, the model struggles to combine these small piles into a larger one, even after 10k+ steps.
  • The termite agent's step function needs refinement. Would love to discuss the logic for the step function with the community.

Currently, the simulation is not as optimized as the one present on the NetLogo website. Termites can form medium-sized piles but are unable to combine them to ultimately form a single big pile of wood chips, the step function still needs more optimization

@Spartan-71
Copy link
Collaborator Author

Thanks Sahil!

@Spartan-71 please let us know if/what you find useful of the coderabbit review! We’re still testing if this is useful for Mesa (and if so, what parts, fine tuning, etc.) (CC @adamamer20)

I found this useful not only to point out minor typos/grammatical errors and logical errors, but it also did a pretty good job in optimizing the model's behaviour. I believe this will save a lot of maintainers' time.

@Sahil-Chhoker
Copy link
Collaborator

Currently, the simulation is not as optimized as the one present on the NetLogo website. Termites can form medium-sized piles but are unable to combine them to ultimately form a single big pile of wood chips, the step function still needs more optimization

What's your game plan for this? If you want to discuss anything, you can ask (though I may not be the best person for this). First step could be comparing the model to the one present on NetLogo directly and figuring out the differences between the logic.

@Spartan-71
Copy link
Collaborator Author

What's your game plan for this? If you want to discuss anything, you can ask (though I may not be the best person for this). First step could be comparing the model to the one present on NetLogo directly and figuring out the differences between the logic.

Yeah, I'm looking into the Netlogos logic for the termites step function. Will give an update soon.

@EwoutH
Copy link
Member

EwoutH commented Apr 11, 2025

Love the discussion here!

By the way, it's perfectly fine to merge this PR with "simple" behavior, and then modify the behavior in follow-up PRs. That way no PR gets too big to review.

You can apply the principle of Atomic Commits also on PRs!

@Spartan-71
Copy link
Collaborator Author

Hey @Sahil-Chhoker, I have changed the logic for the termites step function, which now mimics the Netlogos logic.
With this new logic, a single pile of wood chips is getting formed in almost 1000 steps.

image

@Sahil-Chhoker
Copy link
Collaborator

I like the new logic, but it feels too computation-heavy. If you look at the image below:

image

You can clearly see the main performance bottlenecks: wiggle, search_for_chip, put_down_chip, and get_away. The biggest hit comes from calculating the neighborhood with a radius of 3.

Another concern I have is that this model is supposed to be simple—where basic calculations lead to interesting emergent behavior. But right now, we’re calculating everything, which defeats that purpose.

@Sahil-Chhoker
Copy link
Collaborator

Sahil-Chhoker commented Apr 12, 2025

Can't the logic be more simplified? One way to reduce the commutations needed by wiggle and search_for_chip is to just choose a random location on the grid than to calculate the accurate position in one neighborhood if that's allowed, using self.cell = self.model.random.choice(self.model.grid.all_cells.cells). It gives better results in both performance and simulation.
image image

This kills the computations for both wiggle and search_for_chip. I don't have the idea how we can simplify the other functions (because I haven't looked at them yet), but I think its a good start.

@Spartan-71
Copy link
Collaborator Author

You can clearly see the main performance bottlenecks: wiggle, search_for_chip, put_down_chip, and get_away. The biggest hit comes from calculating the neighborhood with a radius of 3.

Agreed. I have experimented with different radii, but with radius < 3, the model was heading towards local optimal solution i.e forming small piles, which then aren't getting combined to create a bigger one. So 3 seemed to be an optimal value.

Another concern I have is that this model is supposed to be simple—where basic calculations lead to interesting emergent behavior. But right now, we’re calculating everything, which defeats that purpose.

Initially, I tried the basic logic only, but the model struggled forming even medium-sized piles with the new optimization suggested by coderabbit; medium-sized piles were getting formed, but then again, it struggled with combining those piles. So I feel this is the way to achieve the expected behaviour.
What are your thoughts @EwoutH?

@Spartan-71
Copy link
Collaborator Author

Can't the logic be more simplified? One way to reduce the commutations needed by wiggle and search_for_chip is to just choose a random location on the grid than to calculate the accurate position in one neighborhood if that's allowed, using self.cell = self.model.random.choice(self.model.grid.all_cells.cells). It gives better results in both performance and simulation.

This is good, but then it doesn't simulate the real termites' behaviour, i.e looking around themselves in search of chips rather than randomly travelling to a location in the colony. If simulating the real behaviour is not as important as computation, then we can consider moving to a random position in the grid to reduce the computation and think of more optimizations.

@Sahil-Chhoker
Copy link
Collaborator

This is good, but then it doesn't simulate the real termites' behaviour, i.e looking around themselves in search of chips rather than randomly travelling to a location in the colony. If simulating the real behaviour is not as important as computation, then we can consider moving to a random position in the grid to reduce the computation and think of more optimizations.

I think it does simulate real behavior, because the termites search around randomly for wood chips and return once found. It makes perfect sense to me to just search randomly than to do complicated calculations to keep the termite near the nest.


def wiggle(self):
# Move the agent to a random neighboring cell.
self.cell = self.cell.get_neighborhood(radius=3).select_random_cell()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplify it to cell = self.model.random.choice(self.model.grid.all_cells.cells)

Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

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.

Copy link
Member

Choose a reason for hiding this comment

The 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?

@Sahil-Chhoker
Copy link
Collaborator

Sahil-Chhoker commented Apr 12, 2025

Also just reduce the docstrings and comments a little, they feel too access at some places.

@EwoutH
Copy link
Member

EwoutH commented Apr 12, 2025

While performance is definitely relevant, I think valid behavior is more important in a showcase model. Of course, if you can further optimize it, that’s always useful.

@Spartan-71
Copy link
Collaborator Author

Spartan-71 commented Apr 12, 2025

There is something wrong with the pytests. I'm already using mesa.discrete_space in the example.

image

@Sahil-Chhoker
Copy link
Collaborator

While performance is definitely relevant, I think valid behavior is more important in a showcase model. Of course, if you can further optimize it, that’s always useful.

I can say that the newly recommended behavior is very valid, it may be a little different from that of the netlogo's implementation, but in no way wrong, and also performant, so it's a win-win.

Copy link
Member

@EwoutH EwoutH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this excellent example! Few notes:

self.wood_chips_layer = PropertyLayer(
"woodcell", (width, height), default_value=False, dtype=bool
)
self.wood_chips_layer.data = np.random.choice(
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should document this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestion where?

self.hasWoodChip = True

for _ in range(10):
new_cell = self.cell.neighborhood.select_random_cell()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@quaquel or @tpike3, is there a more elegant way to select a random empty cell?

Copy link
Member

@EwoutH EwoutH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example as is looks great! I’ll give Tom and Jan some time to get back, merge later today.

@Spartan-71 Spartan-71 requested review from quaquel and tpike3 April 21, 2025 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants