Fix: Correct FixedAgent state after removal#3100
Conversation
FixedAgent state after removal
|
Performance benchmarks:
|
This is not correct. The moment you call So, in short, technically this PR is cleaner than the current code, so I am fine with merging it after review. However, the claimed bug is simply not there. |
Right, makes sense. |
Exactly, which is unlikely to cause a bug because the agent has been removed, so no reference to it inside Mesa itself exists anymore. If you can refine the PR description, I'll merge this asap. |
Is the new description appropriate? |
|
I think this was an intended feature (albeit implemented via a "side effect") rather than a bug. It appears the author(@Corvince ) intentionally left the To Reproduce: from mesa.model import Model
from mesa.discrete_space import OrthogonalMooreGrid
from mesa.discrete_space.cell_agent import FixedAgent, CellAgent
class Walker(CellAgent):
"""A commuter trying to walk East."""
def step(self):
# Try to move East (0, 1) relative to current position
print(f"Walker at {self.cell.coordinate} trying to move East...")
# Get the cell to the East
current_x, current_y = self.cell.coordinate
target_cell = self.model.grid._cells.get((current_x, current_y + 1))
if target_cell and target_cell.is_empty:
self.cell = target_cell
print(f" -> Success! Moved to {self.cell.coordinate}")
else:
# If blocked, report WHO is blocking us
blockers = target_cell.agents if target_cell else []
print(f" -> BLOCKED! Path obstructed by: {blockers}")
class MockModel(Model):
def __init__(self):
super().__init__()
self.grid = OrthogonalMooreGrid((1, 3), random=self.random)
self.walker = Walker(self)
self.walker.cell = self.grid[(0, 0)]
self.building = FixedAgent(self)
self.building.cell = self.grid[(0, 2)]
def step(self):
self.agents.shuffle_do("step")
city = MockModel()
print(f"1. Initial State:")
print(f" Walker is at {city.walker.cell.coordinate}")
print(f" Building is at {city.building.cell.coordinate}")
print(f" Grid (0,1) is empty: {city.grid[(0,1)].is_empty}")
print("\n2. EVENT: Demolishing the Building...")
# Correctly remove from scheduler (Time)
city.building.remove()
print("\n3. EVENT: The Exploit (Teleportation)")
# We move the 'dead' building to (0, 1) - right in the Walker's path
# This should NOT be allowed for a FixedAgent
city.building.cell = city.grid[(0, 1)]
print(f" Zombie Building successfully placed at {city.building.cell.coordinate}")
print(f"\n4. VERIFICATION: Is the building in the Model?")
print(f" In Scheduler? {'Yes' if city.building in city.agents else 'NO'}")
print(f" (The model thinks the building is gone!)")
print("\n5. STEP: Walker tries to move")
# The walker steps. It should be blocked by the "ghost" building.
city.step()output Before: after While it looks like a bug, retaining the Please let me know if I am wrong in assuming it! |
|
I built the discrete spaces in close interaction with Corvince. I also added To understand why this PR is fine and was already merged, please carefully check what happens in Your example works, because you have done |
|
I just asked this out of curiosity(seeing it merged), so I wanted to dig deeper to see what would happen. Thanks for the explanation, really appreciate it :) |
|
What your example highlights, but what is not well-documented yet in the Mesa docs, is that you have to be careful with hard references to agents. I ran into this myself with a PhD student I was advising. She had a model where agents had a social network. Basically, each agent had friends, and all this was implemented using a normal dict. When running a large parameter sweep on this model, we encountered a massive memory issue. The social network of hard references was so large that the garbage collector was unable to detect that it was, in fact, a cyclical network and so could be cleared. If |
|
I think it’s always helpful to learn from real-world experiences like that, it definitely gave me some good perspective. |
|
After going through the discussion above, I want to clarify the interpretation of the two viewpoints. @codebreaker32 — As I understand, the idea was that the current “broken” setter was effectively acting as a guardrail. @quaquel — your perspective seems to be that once an agent is removed, it should simply be considered dead. However, the main issue is that the Spatial API itself becomes inconsistent. A common Mesa operation is: agent.cell = Nonewhich is the standard way to remove an agent from the grid. For The goal of the fix is simply to restore consistent spatial API behavior, without requiring users to bypass the public API or modify private attributes. |
Summary
This pull request refactors the removal logic for
FixedAgentto ensure its internal state is consistent after being removed. Theremove()method now guarantees that the agent's.cellattribute is set toNone.Bug / Issue
This PR addresses an inconsistency in the state of a
FixedAgentafterremove()is called.Fixes #3099
Implementation
Testing
To verify the fix, a new test case,
test_fixed_agent_removal_state, was added totests/discrete_space/test_discrete_space.py.Additional Notes
This change improves the internal consistency of the
FixedAgent's state afteragent.remove()has been called.