Skip to content

Fix: Cell.get_neighborhood() RecursionError for large radius (#3105)#3106

Merged
quaquel merged 5 commits intomesa:mainfrom
Nithin9585:fix-recursion-error-3105
Jan 12, 2026
Merged

Fix: Cell.get_neighborhood() RecursionError for large radius (#3105)#3106
quaquel merged 5 commits intomesa:mainfrom
Nithin9585:fix-recursion-error-3105

Conversation

@Nithin9585
Copy link
Copy Markdown
Contributor

@Nithin9585 Nithin9585 commented Jan 10, 2026

Fixes #3105

This PR improves Cell.get_neighborhood() by replacing the recursive implementation with an iterative Breadth-First Search (BFS). This change provides two key benefits:

  1. Performance improvement: ~2x faster neighborhood lookups for all radius values
  2. Eliminates edge-case crashes: Prevents RecursionError when radius exceeds Python's recursion limit (~1000)

Problem

The previous implementation used recursion to traverse cell neighborhoods:

for neighbor in self.connections.values():
    neighborhood.update(
        neighbor._neighborhood(radius - 1, include_center=True)  # Recursive call
    )

This had two issues:

  • Performance: Function call overhead from deep recursion slowed down neighborhood queries
  • Edge-case crashes: When radius > sys.getrecursionlimit() (~1000), it caused RecursionError: maximum recursion depth exceeded

Solution

Replaced with iterative BFS using explicit layer-by-layer traversal:

# Use iterative BFS for better performance and no recursion limit
visited: set[Cell] = {self}
current_layer: list[Cell] = list(self.connections.values())
neighborhood: dict[Cell, list[Agent]] = {}

# Add immediate neighbors (radius=1)
for neighbor in current_layer:
    if neighbor not in visited:
        visited.add(neighbor)
        neighborhood[neighbor] = neighbor._agents

# Expand outward for remaining radius levels
for _ in range(radius - 1):
    next_layer: list[Cell] = []
    for cell in current_layer:
        for neighbor in cell.connections.values():
            if neighbor not in visited:
                visited.add(neighbor)
                next_layer.append(neighbor)
                neighborhood[neighbor] = neighbor._agents
    current_layer = next_layer
    if not current_layer:
        break

Benefits

~2x faster neighborhood lookups (confirmed by maintainer benchmarks)
No recursion limit: Can handle any radius value (memory-limited, not stack-limited)
Same behavior: All 37 existing discrete_space tests pass
Same time complexity: O(number of cells in radius)


Changes

  • mesa/discrete_space/cell.py: Replaced recursive _neighborhood() with iterative BFS
  • tests/discrete_space/test_discrete_space.py: Added 3 regression tests:
    • test_large_radius_neighborhood: Tests radius=1500 on a 2000-cell chain
    • test_large_radius_with_include_center: Tests include_center parameter with large radius
    • test_radius_exceeds_reachable_cells: Tests radius > available cells

Testing

$ python -m pytest tests/discrete_space/test_discrete_space.py -v
============================= 37 passed in 13.67s =============================

All existing tests pass + 3 new regression tests added.

@github-actions
Copy link
Copy Markdown

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 +1.5% [+1.2%, +1.8%] 🔵 +1.1% [+0.6%, +1.4%]
BoltzmannWealth large 🔵 +0.1% [-0.4%, +0.5%] 🔴 +5.7% [+4.4%, +7.2%]
Schelling small 🔴 +5.3% [+3.7%, +7.2%] 🔵 +2.9% [+1.7%, +4.1%]
Schelling large 🔵 +0.8% [-1.3%, +4.5%] 🔵 +2.4% [+0.4%, +4.2%]
WolfSheep small 🔵 +0.3% [+0.2%, +0.4%] 🔵 +1.1% [+1.0%, +1.2%]
WolfSheep large 🔵 +2.1% [-3.9%, +10.6%] 🔵 +1.3% [-0.0%, +2.5%]
BoidFlockers small 🔵 -0.0% [-0.4%, +0.3%] 🔵 -0.2% [-0.3%, -0.1%]
BoidFlockers large 🔵 -0.2% [-0.7%, +0.2%] 🔵 -0.1% [-0.3%, +0.0%]

@Nithin9585 Nithin9585 force-pushed the fix-recursion-error-3105 branch from ab6feb1 to 3c210d4 Compare January 10, 2026 06:02
@EwoutH EwoutH requested a review from quaquel January 10, 2026 09:38
@quaquel quaquel added the performance Release notes label label Jan 10, 2026
@quaquel quaquel merged commit ae1c1b2 into mesa:main Jan 12, 2026
14 checks passed
@quaquel quaquel added the enhancement Release notes label label Jan 12, 2026
@Nithin9585 Nithin9585 deleted the fix-recursion-error-3105 branch January 12, 2026 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Release notes label performance Release notes label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cell.get_neighborhood() crashes with RecursionError for large radius values

2 participants