Skip to content

Fix Sugarscape example compatibility with Mesa 3.0 #131

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 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,3 @@ contact_links:
- name: Discussions/Questions etc
url: https://github.com/projectmesa/mesa-frames/discussions
about: Discuss Mesa-Frames, ask questions, do discussions, share ideas, and showcase your projects



8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,10 @@ cython_debug/
.idea/

.vscode/
*.code-workspace
*.code-workspace

# Test result files
test_results.txt
*_test_results.txt
test_output.txt
*_test_output.txt
23 changes: 15 additions & 8 deletions examples/sugarscape_ig/ss_mesa/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ def get_distance(pos_1, pos_2):


class AntMesa(mesa.Agent):
def __init__(self, unique_id, model, moore=False, sugar=0, metabolism=0, vision=0):
super().__init__(unique_id, model)
def __init__(self, model, moore=False, sugar=0, metabolism=0, vision=0):
# unique_id is automatically assigned in Mesa 3.0
super().__init__(model)
self.moore = moore
self.sugar = sugar
self.metabolism = metabolism
Expand Down Expand Up @@ -67,17 +68,23 @@ def step(self):
self.eat()
if self.sugar <= 0:
self.model.space.remove_agent(self)
self.model.agents.remove(self)
# Agent is automatically removed from model.agents in Mesa 3.0


class Sugar(mesa.Agent):
def __init__(self, unique_id, model, max_sugar):
super().__init__(unique_id, model)
def __init__(self, model, max_sugar):
# unique_id is automatically assigned in Mesa 3.0
super().__init__(model)
self.amount = max_sugar
self.max_sugar = max_sugar

def step(self):
if self.model.space.is_cell_empty(self.pos):
"""
Simple regrow rule for sugar: if no ant is present, grow back to max.
"""
# Simple check for ant presence
pos_agents = self.model.space.get_cell_list_contents([self.pos])
has_ant = any(isinstance(agent, AntMesa) for agent in pos_agents)

if not has_ant:
self.amount = self.max_sugar
else:
self.amount = 0
53 changes: 33 additions & 20 deletions examples/sugarscape_ig/ss_mesa/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def __init__(
Create a new Instant Growback model with the given parameters.

"""
super().__init__()
# Initialize the Mesa base class (required in Mesa 3.0)
super().__init__(seed=seed)

# Set parameters
if sugar_grid is None:
Expand All @@ -37,48 +38,60 @@ def __init__(
metabolism = np.random.randint(2, 4, n_agents)
if vision is None:
vision = np.random.randint(1, 6, n_agents)
if seed is not None:
self.reset_randomizer(seed)

self.width, self.height = sugar_grid.shape
self.n_agents = n_agents
self.space = mesa.space.MultiGrid(self.width, self.height, torus=False)
self.agents: list = []

agent_id = 0
self.sugars = []

# Create sugar resources
sugar_count = 0
for _, (x, y) in self.space.coord_iter():
max_sugar = sugar_grid[x, y]
sugar = Sugar(agent_id, self, max_sugar)
agent_id += 1
sugar = Sugar(self, max_sugar)
self.space.place_agent(sugar, (x, y))
self.sugars.append(sugar)
sugar_count += 1

# Create agent:
# Create AntMesa agents
ant_count = 0
for i in range(self.n_agents):
# Determine position
if initial_positions is not None:
x = initial_positions["dim_0"][i]
y = initial_positions["dim_1"][i]
else:
x = self.random.randrange(self.width)
y = self.random.randrange(self.height)
ssa = AntMesa(
agent_id, self, False, initial_sugar[i], metabolism[i], vision[i]

# Create and place agent
ant = AntMesa(
self,
moore=False,
sugar=initial_sugar[i],
metabolism=metabolism[i],
vision=vision[i],
)
agent_id += 1
self.space.place_agent(ssa, (x, y))
self.agents.append(ssa)
self.space.place_agent(ant, (x, y))
ant_count += 1

self.running = True

def step(self):
self.random.shuffle(self.agents)
[agent.step() for agent in self.agents]
[sugar.step() for sugar in self.sugars]
# Get all AntMesa agents
ant_agents = [agent for agent in self.agents if isinstance(agent, AntMesa)]

# Randomize the order
self.random.shuffle(ant_agents)

# Step each ant agent directly
for ant in ant_agents:
ant.step()

# Process Sugar agents directly
for sugar in [agent for agent in self.agents if isinstance(agent, Sugar)]:
sugar.step()

def run_model(self, step_count=200):
for i in range(step_count):
Copy link
Member

Choose a reason for hiding this comment

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

You're not actually adding any agents to the model so everytime you run_model from performance_comparison.py, the number of agents is always 0 and you don't actually do anything.

if len(self.agents) == 0:
if self.agents.count(AntMesa) == 0:
return
self.step()
127 changes: 127 additions & 0 deletions examples/sugarscape_ig/sugarscape_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""
Pytest tests for the Sugarscape example with Mesa 3.x.
"""

import sys
import os
from pathlib import Path

# Add the parent directory to sys.path to allow imports from ss_mesa package
current_dir = Path(__file__).parent
examples_dir = current_dir.parent
root_dir = examples_dir.parent

# Add root directory to sys.path if not already there
if str(root_dir) not in sys.path:
sys.path.insert(0, str(root_dir))

# Add the examples directory to sys.path if not already there
if str(examples_dir) not in sys.path:
sys.path.insert(0, str(examples_dir))

import mesa
import pytest
from ss_mesa.model import SugarscapeMesa
from ss_mesa.agents import AntMesa, Sugar


@pytest.fixture
def sugarscape_model():
"""Create a standard Sugarscape model for testing."""
return SugarscapeMesa(10, width=10, height=10)


def test_model_creation(sugarscape_model):
"""Test that the model can be created properly with Mesa 3.x"""
model = sugarscape_model

# Count agents with isinstance
total_agents = len(model.agents)
ant_count = sum(1 for agent in model.agents if isinstance(agent, AntMesa))
sugar_count = sum(1 for agent in model.agents if isinstance(agent, Sugar))

# Check that we have the expected number of agents
assert total_agents == (10 * 10 + 10), "Unexpected total agent count"
assert ant_count == 10, "Unexpected AntMesa agent count"
assert sugar_count == 10 * 10, "Unexpected Sugar agent count"


def test_model_step(sugarscape_model):
"""Test that the model can be stepped with Mesa 3.x"""
model = sugarscape_model

# Count agents before stepping
ant_count_before = sum(1 for agent in model.agents if isinstance(agent, AntMesa))

# Step the model
model.step()

# Count agents after stepping
ant_count_after = sum(1 for agent in model.agents if isinstance(agent, AntMesa))

# In this basic test, we just verify the step completes without errors
# and the number of ants doesn't unexpectedly change
assert ant_count_after >= 0, "Expected at least some ants to survive"


@pytest.fixture
def simple_model():
"""Create a simplified model with just a few agents to isolate behavior"""

class SimpleModel(mesa.Model):
def __init__(self, seed=None):
super().__init__(seed=seed)
self.space = mesa.space.MultiGrid(5, 5, torus=False)

# Add sugar agents to all cells
self.sugars = []
for x in range(5):
for y in range(5):
sugar = Sugar(self, 5)
self.space.place_agent(sugar, (x, y))
self.sugars.append(sugar)

# Create one ant agent
self.ant = AntMesa(self, False, 10, 2, 3)
self.space.place_agent(self.ant, (2, 2)) # Place in the middle

def step(self):
# Step the sugar agents
for sugar in self.sugars:
sugar.step()

# Step the ant agent
self.ant.step()

return SimpleModel()


def test_simple_model_creation(simple_model):
"""Test that the simple model is created with the correct agents."""
# Check agents
assert len(simple_model.agents) == 26, "Expected 26 total agents (25 sugar + 1 ant)"

ant_count = sum(1 for agent in simple_model.agents if isinstance(agent, AntMesa))
sugar_count = sum(1 for agent in simple_model.agents if isinstance(agent, Sugar))

assert ant_count == 1, "Expected exactly 1 AntMesa agent"
assert sugar_count == 25, "Expected exactly 25 Sugar agents"


def test_sugar_step(simple_model):
"""Test that sugar agents can step without errors."""
for sugar in simple_model.sugars:
sugar.step()
# If we get here without exceptions, the test passes


def test_ant_step(simple_model):
"""Test that ant agents can step without errors."""
simple_model.ant.step()
# If we get here without exceptions, the test passes


def test_simple_model_step(simple_model):
"""Test that the simple model can step without errors."""
simple_model.step()
# If we get here without exceptions, the test passes
14 changes: 13 additions & 1 deletion pyproject.toml
Copy link
Member

Choose a reason for hiding this comment

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

Put >= than the last version of mesa (3.1.4)

Copy link
Author

@suryanshgargbpgc suryanshgargbpgc Mar 25, 2025

Choose a reason for hiding this comment

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

image

@adamamer20, I think mesa does not use python 3.11, so instead of just changing mesa ~=3.0.0 to mesa >=3.1.4
i can implement a conditional dependency.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dependencies = [
"polars>=1.0.0", #polars._typing (see mesa_frames.types) added in 1.0.0
#"geopolars" (currently in pre-alpha)
]
requires-python = ">=3.9"
requires-python = ">=3.11"
dynamic = [
"version"
]
Expand Down Expand Up @@ -83,6 +83,13 @@ test = [
]

dev = [

"mesa_frames[test, docs, numba]",
"mesa>=2.4.0",
"numba>=0.60",
]

numba = [
"mesa_frames[test, docs]",
"mesa~=2.4.0",
"numba>=0.60",
Expand Down Expand Up @@ -120,3 +127,8 @@ dev = [
"mesa-frames[dev]",
]

[tool.pytest.ini_options]
testpaths = ["tests", "examples"]
python_files = ["test_*.py", "*_test.py", "sugarscape_test.py"]
addopts = "--verbose"

Loading