Skip to content

Cells keep cache after removing connections (3.2.0 version) #2811

@MalchuL

Description

@MalchuL

Describe the bug
Hex Grid remove_cell doesn't clear cache on cells.
I have a simple example where I removing diagonal cells and place agents to separate them. After creating agents I added new cells at same places but without connections. When I run script, I found that some cells uses previous connection before removing cells. I can reproduce this bug at HexGrid.

Workaround

create NoCachedCell and use as cell class

To Reproduce

"""
Simple example demonstrating Mesa's OrthogonalMooreGrid with CellAgent.

This example creates a 5x5 grid where agents can move and interact with neighbors
using the Moore neighborhood (8 surrounding cells).
"""

from random import Random
from typing import Optional, List
import mesa
from mesa.discrete_space import CellAgent, OrthogonalMooreGrid, HexGrid
from mesa.discrete_space.cell import Cell, Coordinate
from mesa.discrete_space.cell_collection import CellCollection



class NoCachedCell(Cell):
    
    @property
    def neighborhood(self) -> CellCollection[Cell]:
        return self.get_neighborhood()

class MoneyAgent(CellAgent):
    """An agent with fixed initial wealth that can move around the grid."""
    
    def __init__(self, model: "MoneyModel", cell) -> None:
        """
        Create a new agent.
        
        Args:
            model: Model instance
            cell: The cell where this agent is located
        """
        super().__init__(model)
        self.cell = cell
        self.prev_cell = cell
        self.wealth = 1
    
    def move(self) -> None:
        """Move to a random cell in the Moore neighborhood."""
        try:
            prev_cell = self.cell
            self.cell = self.cell.neighborhood.select_random_cell()
            self.prev_cell = prev_cell
        except IndexError as e:
            print(self.prev_cell.coordinate, self.cell.coordinate, [c.coordinate for c in self.prev_cell.neighborhood.cells])
            raise e
    
    def give_money(self) -> None:
        """Give 1 unit of wealth to another agent in the same cell."""
        # Get all agents in the current cell except self
        cellmates = [a for a in self.cell.agents if a is not self]
        
        if self.wealth > 0 and cellmates:
            other_agent = self.random.choice(cellmates)
            other_agent.wealth += 1
            self.wealth -= 1


class MoneyModel(mesa.Model):
    """A model with agents moving on a grid and exchanging money."""
    
    def __init__(self, n_agents: int = 10, width: int = 5, height: int = 5, seed: Optional[int] = None) -> None:
        """
        Create a new Money Model.
        
        Args:
            n_agents: Number of agents to create
            width: Width of the grid
            height: Height of the grid
            seed: Random seed for reproducibility
        """
        super().__init__(seed=seed)
        self.num_agents = n_agents
        
        # Create grid with Moore neighborhood and allow multiple agents per cell
        self.grid = HexGrid(
            (width, height),
            torus=True,  # Wrap around edges
            capacity=None,  # Max agents per cell
            random=self.random,
            #cell_klass=NoCachedCell
        )
        # Remove and add cells to the grid to test the grid
        for i in range(width):
            self.grid.remove_cell(self.grid[(i, i)])
        
        
        # Create agents and place them randomly
        agents = MoneyAgent.create_agents(
            self,
            self.num_agents,
            # Randomly select cells for each agent
            self.random.choices(self.grid.all_cells.cells, k=self.num_agents)
        )
        # Add cells to the grid to test the grid
        for i in range(width):
            self.grid.add_cell(self.grid.cell_klass((i, i), random=self.random))

    def step(self) -> None:
        """
        Advance the model by one step:
        1. Have agents move randomly
        2. Have agents give money to others in their cell
        """
        self.agents.shuffle_do("move")
        self.agents.do("give_money")


def print_grid_state(model: MoneyModel) -> None:
    """
    Print a simple text representation of the grid.
    
    Args:
        model: The model to display
    """
    print("\nGrid State (number of agents in each cell):")
    print("-" * (model.grid.width * 4 + 3))
    
    num_agents = 0
    for y in range(model.grid.height-1, -1, -1):
        print("|", end=" ")
        for x in range(model.grid.width):
            cell = model.grid[x, y]
            print(f"{len(cell.agents):2d}", end=" ")
            num_agents += len(cell.agents)
        print("|")
    print("-" * (model.grid.width * 4 + 3))
    print(f"Total number of agents: {num_agents}")


def main():
    """Run a demonstration of the money model."""
    # Create model with 20 agents on a 5x5 grid
    model = MoneyModel(n_agents=20, width=5, height=5, seed=42)
    
    # Run for 10 steps
    for step in range(100):
        print(f"\nStep {step}")
        print_grid_state(model)
        
        # Print wealth stats
        wealth_dist = [agent.wealth for agent in model.agents]
        print("\nWealth Distribution:")
        print(f"Min: {min(wealth_dist)}, Max: {max(wealth_dist)}, "
              f"Average: {sum(wealth_dist)/len(wealth_dist):.2f}")
        
        # Step the model
        model.step()


if __name__ == "__main__":
    main() 

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugRelease notes label

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions