Skip to content

Fix memory leak in ContinuousSpace agent removal #3029

@Nithin9585

Description

@Nithin9585

When removing an agent from ContinuousSpace, the internal index mapping (_index_to_agent) is corrupted. The code shifts subsequent agents to fill the gap but fails to delete the old index entry of the last shifted agent. This creates a "Ghost Agent" entry: two indices point to the same agent, and the dictionary size grows larger than the number of active agents.

Expected behavior
The _index_to_agent dictionary should accurately reflect the active agents. When an agent is removed:

  1. The removed agent's entry is deleted.
  2. Subsequent agents are re-indexed.
  3. The old index of the last shifted agent is deleted.
  4. len(_index_to_agent) == len(active_agents).

To Reproduce
Run the following script to reproduce the issue:

from mesa import Agent, Model
from mesa.experimental.continuous_space import ContinuousSpace
from mesa.experimental.continuous_space.continuous_space_agents import ContinuousSpaceAgent
import numpy as np

class DummyAgent(ContinuousSpaceAgent):
    pass

model = Model()
space = ContinuousSpace(dimensions=[[0, 10], [0, 10]], torus=False)

# Create 3 agents
agents = []
for i in range(3):
    agent = DummyAgent(space, model)
    agent.position = np.array([float(i), float(i)])
    agents.append(agent)

print(f"Initial agents: {len(space.active_agents)}")
print(f"Initial index map size: {len(space._index_to_agent)}")

# Remove the middle agent (index 1)
agent_to_remove = agents[1]
print(f"Removing agent at index: {space._agent_to_index[agent_to_remove]}")
space._remove_agent(agent_to_remove) 

print(f"Agents after removal: {len(space.active_agents)}")
print(f"Index map size after removal: {len(space._index_to_agent)}")

# Check for stale index
expected_size = len(space.active_agents)
actual_size = len(space._index_to_agent)

if actual_size > expected_size:
    print("BUG DETECTED: _index_to_agent has stale entries!")
    print(f"Expected size: {expected_size}, Actual size: {actual_size}")
    last_index = actual_size - 1
    print(f"Entry at index {last_index}: {space._index_to_agent.get(last_index)}")

Additional context

  • Severity: High (Memory Leak + Logic Error)
  • Location: mesa/experimental/continuous_space/continuous_space.py
  • Logic Demonstration:
    Imagine a list [A, B, C]. Removing B shifts C to index 1.
    • Correct Result: [A, C]
    • Buggy Result: [A, C, C] (The old C at index 2 remains).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions