From d90b0f72cd65d5875eb9e4012990699cfe886c5c Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 21 Jan 2024 18:57:57 +0100 Subject: [PATCH 01/21] further updates --- benchmarks/WolfSheep/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/benchmarks/WolfSheep/__init__.py b/benchmarks/WolfSheep/__init__.py index e69de29bb2d..18b86ab19ba 100644 --- a/benchmarks/WolfSheep/__init__.py +++ b/benchmarks/WolfSheep/__init__.py @@ -0,0 +1,14 @@ +from wolf_sheep import WolfSheep + +if __name__ == "__main__": + # for profiling this benchmark model + import time + + # model = WolfSheep(15, 25, 25, 60, 40, 0.2, 0.1, 20) + model = WolfSheep(15, 100, 100, 1000, 500, 0.4, 0.2, 20) + + start_time = time.perf_counter() + for _ in range(100): + model.step() + + print(time.perf_counter() - start_time) From 95864900d9e7981c8ea7240e34672f3fe792d681 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sun, 21 Jan 2024 19:13:00 +0100 Subject: [PATCH 02/21] Update benchmarks/WolfSheep/__init__.py --- benchmarks/WolfSheep/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/WolfSheep/__init__.py b/benchmarks/WolfSheep/__init__.py index 18b86ab19ba..98b1e9fdfed 100644 --- a/benchmarks/WolfSheep/__init__.py +++ b/benchmarks/WolfSheep/__init__.py @@ -1,4 +1,4 @@ -from wolf_sheep import WolfSheep +from .wolf_sheep import WolfSheep if __name__ == "__main__": # for profiling this benchmark model From 2759244eb350ce8e38b31b08cbea46e40998893a Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Tue, 27 Aug 2024 14:01:46 +0200 Subject: [PATCH 03/21] Update __init__.py --- benchmarks/WolfSheep/__init__.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/benchmarks/WolfSheep/__init__.py b/benchmarks/WolfSheep/__init__.py index 98b1e9fdfed..e69de29bb2d 100644 --- a/benchmarks/WolfSheep/__init__.py +++ b/benchmarks/WolfSheep/__init__.py @@ -1,14 +0,0 @@ -from .wolf_sheep import WolfSheep - -if __name__ == "__main__": - # for profiling this benchmark model - import time - - # model = WolfSheep(15, 25, 25, 60, 40, 0.2, 0.1, 20) - model = WolfSheep(15, 100, 100, 1000, 500, 0.4, 0.2, 20) - - start_time = time.perf_counter() - for _ in range(100): - model.step() - - print(time.perf_counter() - start_time) From 8ef5794f63b06e861584f069bcf985b67ab796f0 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Fri, 30 Aug 2024 14:11:08 +0200 Subject: [PATCH 04/21] track unique_id automatically --- mesa/agent.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/mesa/agent.py b/mesa/agent.py index 8d04df9e30f..78ffe4ad0ef 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -10,6 +10,7 @@ import contextlib import copy +import itertools import operator import warnings import weakref @@ -36,8 +37,9 @@ class Agent: model (Model): A reference to the model instance. self.pos: Position | None = None """ + _ids = itertools.count() - def __init__(self, unique_id: int, model: Model) -> None: + def __init__(self, *args, **kwargs) -> None: """ Create a new agent. @@ -45,6 +47,16 @@ def __init__(self, unique_id: int, model: Model) -> None: unique_id (int): A unique identifier for this agent. model (Model): The model instance in which the agent exists. """ + if len(args) == 1: + model = args[0] + unique_id = next(self._ids) + else: + warnings.warn("unique ids are assigned automatically in MESA 3", + DeprecationWarning, stacklevel=2) + unique_id, model, *args = args + # if *args is not empty, what should we do? + + self.unique_id = unique_id self.model = model self.pos: Position | None = None From 8d24591f56eda27dee8534896a311e9a7966c396 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:14:10 +0000 Subject: [PATCH 05/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/agent.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mesa/agent.py b/mesa/agent.py index 78ffe4ad0ef..0a4b46864a4 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -37,6 +37,7 @@ class Agent: model (Model): A reference to the model instance. self.pos: Position | None = None """ + _ids = itertools.count() def __init__(self, *args, **kwargs) -> None: @@ -51,12 +52,14 @@ def __init__(self, *args, **kwargs) -> None: model = args[0] unique_id = next(self._ids) else: - warnings.warn("unique ids are assigned automatically in MESA 3", - DeprecationWarning, stacklevel=2) + warnings.warn( + "unique ids are assigned automatically in MESA 3", + DeprecationWarning, + stacklevel=2, + ) unique_id, model, *args = args # if *args is not empty, what should we do? - self.unique_id = unique_id self.model = model self.pos: Position | None = None From a113e24f7a57006919632df28cc3c27df404785b Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sat, 31 Aug 2024 19:55:29 +0200 Subject: [PATCH 06/21] remove model.next_id and fix tests --- benchmarks/WolfSheep/wolf_sheep.py | 2 +- docs/tutorials/MoneyModel.py | 4 +- mesa/agent.py | 13 ++-- mesa/experimental/cell_space/cell_agent.py | 4 +- mesa/experimental/devs/examples/wolf_sheep.py | 1 - tests/test_agent.py | 63 +++++++++---------- tests/test_cell_space.py | 14 ++--- tests/test_model.py | 2 +- tests/test_time.py | 20 +++--- 9 files changed, 62 insertions(+), 61 deletions(-) diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py index 655d51bba63..be4806b55e2 100644 --- a/benchmarks/WolfSheep/wolf_sheep.py +++ b/benchmarks/WolfSheep/wolf_sheep.py @@ -29,7 +29,7 @@ def random_move(self): def spawn_offspring(self): self.energy /= 2 offspring = self.__class__( - self.model.next_id(), + self. self.model, self.energy, self.p_reproduce, diff --git a/docs/tutorials/MoneyModel.py b/docs/tutorials/MoneyModel.py index 94555f98035..93e19dec11f 100644 --- a/docs/tutorials/MoneyModel.py +++ b/docs/tutorials/MoneyModel.py @@ -15,8 +15,8 @@ def compute_gini(model): class MoneyAgent(mesa.Agent): """An agent with fixed initial wealth.""" - def __init__(self, unique_id, model): - super().__init__(unique_id, model) + def __init__(self, model): + super().__init__(model) self.wealth = 1 def move(self): diff --git a/mesa/agent.py b/mesa/agent.py index 0a4b46864a4..b2128e38ca6 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -28,29 +28,31 @@ from mesa.space import Position +class TestClassDict(dict): + def __missing__(self, key): + res = self[key] = itertools.count(1) + return res + class Agent: """ Base class for a model agent in Mesa. Attributes: - unique_id (int): A unique identifier for this agent. model (Model): A reference to the model instance. self.pos: Position | None = None """ - - _ids = itertools.count() + _ids = TestClassDict() def __init__(self, *args, **kwargs) -> None: """ Create a new agent. Args: - unique_id (int): A unique identifier for this agent. model (Model): The model instance in which the agent exists. """ if len(args) == 1: model = args[0] - unique_id = next(self._ids) + unique_id = next(self._ids[model]) else: warnings.warn( "unique ids are assigned automatically in MESA 3", @@ -59,6 +61,7 @@ def __init__(self, *args, **kwargs) -> None: ) unique_id, model, *args = args # if *args is not empty, what should we do? + # raise ValueError self.unique_id = unique_id self.model = model diff --git a/mesa/experimental/cell_space/cell_agent.py b/mesa/experimental/cell_space/cell_agent.py index abc5155a670..5f1cca5cbcc 100644 --- a/mesa/experimental/cell_space/cell_agent.py +++ b/mesa/experimental/cell_space/cell_agent.py @@ -19,7 +19,7 @@ class CellAgent(Agent): cell: (Cell | None): the cell which the agent occupies """ - def __init__(self, unique_id: int, model: Model) -> None: + def __init__(self, model: Model) -> None: """ Create a new agent. @@ -27,7 +27,7 @@ def __init__(self, unique_id: int, model: Model) -> None: unique_id (int): A unique identifier for this agent. model (Model): The model instance in which the agent exists. """ - super().__init__(unique_id, model) + super().__init__(model) self.cell: Cell | None = None def move_to(self, cell) -> None: diff --git a/mesa/experimental/devs/examples/wolf_sheep.py b/mesa/experimental/devs/examples/wolf_sheep.py index 99fde6a9c8f..f8524db02e9 100644 --- a/mesa/experimental/devs/examples/wolf_sheep.py +++ b/mesa/experimental/devs/examples/wolf_sheep.py @@ -30,7 +30,6 @@ def random_move(self): def spawn_offspring(self): self.energy /= 2 offspring = self.__class__( - self.model.next_id(), self.model, self.moore, self.energy, diff --git a/tests/test_agent.py b/tests/test_agent.py index bb61ae1c1bd..4a0333458d7 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -15,17 +15,16 @@ def get_unique_identifier(self): class TestAgentDo(Agent): def __init__( self, - unique_id, model, ): - super().__init__(unique_id, model) + super().__init__(model) self.agent_set = None def get_unique_identifier(self): return self.unique_id def do_add(self): - agent = TestAgentDo(self.model.next_id(), self.model) + agent = TestAgentDo(self.model) self.agent_set.add(agent) def do_remove(self): @@ -34,7 +33,7 @@ def do_remove(self): def test_agent_removal(): model = Model() - agent = TestAgent(model.next_id(), model) + agent = TestAgent(model) # Check if the agent is added assert agent in model.agents @@ -46,7 +45,7 @@ def test_agent_removal(): def test_agentset(): # create agentset model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) @@ -117,14 +116,14 @@ def test_agentset_initialization(): empty_agentset = AgentSet([], model) assert len(empty_agentset) == 0 - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) assert len(agentset) == 10 def test_agentset_serialization(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(5)] + agents = [TestAgent(model) for _ in range(5)] agentset = AgentSet(agents, model) serialized = pickle.dumps(agentset) @@ -138,16 +137,16 @@ def test_agentset_serialization(): def test_agent_membership(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(5)] + agents = [TestAgent(model) for _ in range(5)] agentset = AgentSet(agents, model) assert agents[0] in agentset - assert TestAgent(model.next_id(), model) not in agentset + assert TestAgent( model) not in agentset def test_agent_add_remove_discard(): model = Model() - agent = TestAgent(model.next_id(), model) + agent = TestAgent( model) agentset = AgentSet([], model) agentset.add(agent) @@ -166,7 +165,7 @@ def test_agent_add_remove_discard(): def test_agentset_get_item(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent( model) for _ in range(10)] agentset = AgentSet(agents, model) assert agentset[0] == agents[0] @@ -179,7 +178,7 @@ def test_agentset_get_item(): def test_agentset_do_str(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent( model) for _ in range(10)] agentset = AgentSet(agents, model) with pytest.raises(AttributeError): @@ -192,7 +191,7 @@ def test_agentset_do_str(): # setup n = 10 model = Model() - agents = [TestAgentDo(model.next_id(), model) for _ in range(n)] + agents = [TestAgentDo(model) for _ in range(n)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -202,7 +201,7 @@ def test_agentset_do_str(): # setup model = Model() - agents = [TestAgentDo(model.next_id(), model) for _ in range(10)] + agents = [TestAgentDo( model) for _ in range(10)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -213,7 +212,7 @@ def test_agentset_do_str(): def test_agentset_do_callable(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent( model) for _ in range(10)] agentset = AgentSet(agents, model) # Test callable with non-existent function @@ -227,7 +226,7 @@ def test_agentset_do_callable(): # setup for lambda function tests n = 10 model = Model() - agents = [TestAgentDo(model.next_id(), model) for _ in range(n)] + agents = [TestAgentDo( model) for _ in range(n)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -238,7 +237,7 @@ def test_agentset_do_callable(): # setup again for lambda function tests model = Model() - agents = [TestAgentDo(model.next_id(), model) for _ in range(10)] + agents = [TestAgentDo( model) for _ in range(10)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -256,7 +255,7 @@ def remove_function(agent): # setup again for actual function tests model = Model() - agents = [TestAgentDo(model.next_id(), model) for _ in range(n)] + agents = [TestAgentDo( model) for _ in range(n)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -267,7 +266,7 @@ def remove_function(agent): # setup again for actual function tests model = Model() - agents = [TestAgentDo(model.next_id(), model) for _ in range(10)] + agents = [TestAgentDo( model) for _ in range(10)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -315,12 +314,12 @@ def custom_func(values): def test_agentset_set_method(): # Initialize the model and agents with and without existing attributes class TestAgentWithAttribute(Agent): - def __init__(self, unique_id, model, age=None): - super().__init__(unique_id, model) + def __init__(self, model, age=None): + super().__init__(model) self.age = age model = Model() - agents = [TestAgentWithAttribute(model.next_id(), model, age=i) for i in range(5)] + agents = [TestAgentWithAttribute(model, age=i) for i in range(5)] agentset = AgentSet(agents, model) # Set a new attribute "health" and an existing attribute "age" for all agents @@ -338,7 +337,7 @@ def __init__(self, unique_id, model, age=None): def test_agentset_map_str(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) with pytest.raises(AttributeError): @@ -350,7 +349,7 @@ def test_agentset_map_str(): def test_agentset_map_callable(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent( model) for _ in range(10)] agentset = AgentSet(agents, model) # Test callable with non-existent function @@ -367,7 +366,7 @@ def test_agentset_map_callable(): def test_agentset_get_attribute(): model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent( model) for _ in range(10)] agentset = AgentSet(agents, model) unique_ids = agentset.get("unique_id") @@ -379,7 +378,7 @@ def test_agentset_get_attribute(): model = Model() agents = [] for i in range(10): - agent = TestAgent(model.next_id(), model) + agent = TestAgent( model) agent.i = i**2 agents.append(agent) agentset = AgentSet(agents, model) @@ -403,8 +402,8 @@ def get_unique_identifier(self): def test_agentset_select_by_type(): model = Model() # Create a mix of agents of two different types - test_agents = [TestAgent(model.next_id(), model) for _ in range(4)] - other_agents = [OtherAgentType(model.next_id(), model) for _ in range(6)] + test_agents = [TestAgent( model) for _ in range(4)] + other_agents = [OtherAgentType( model) for _ in range(6)] # Combine the two types of agents mixed_agents = test_agents + other_agents @@ -428,7 +427,7 @@ def test_agentset_select_by_type(): def test_agentset_shuffle(): model = Model() - test_agents = [TestAgent(model.next_id(), model) for _ in range(12)] + test_agents = [TestAgent( model) for _ in range(12)] agentset = AgentSet(test_agents, model=model) agentset = agentset.shuffle() @@ -441,15 +440,15 @@ def test_agentset_shuffle(): def test_agentset_groupby(): class TestAgent(Agent): - def __init__(self, unique_id, model): - super().__init__(unique_id, model) + def __init__(self, model): + super().__init__(model) self.even = self.unique_id % 2 == 0 def get_unique_identifier(self): return self.unique_id model = Model() - agents = [TestAgent(model.next_id(), model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) groups = agentset.groupby("even") diff --git a/tests/test_cell_space.py b/tests/test_cell_space.py index a918f59e53b..4127847d17b 100644 --- a/tests/test_cell_space.py +++ b/tests/test_cell_space.py @@ -408,7 +408,7 @@ def test_empties_space(): model = Model() for i in range(8): - grid._cells[i].add_agent(CellAgent(i, model)) + grid._cells[i].add_agent(CellAgent(model)) cell = grid.select_random_empty_cell() assert cell.coordinate in {8, 9} @@ -432,7 +432,7 @@ def test_cell(): # add_agent model = Model() - agent = CellAgent(1, model) + agent = CellAgent(model) cell1.add_agent(agent) assert agent in cell1.agents @@ -445,11 +445,11 @@ def test_cell(): cell1.remove_agent(agent) cell1 = Cell((1,), capacity=1, random=random.Random()) - cell1.add_agent(CellAgent(1, model)) + cell1.add_agent(CellAgent(model)) assert cell1.is_full with pytest.raises(Exception): - cell1.add_agent(CellAgent(2, model)) + cell1.add_agent(CellAgent(model)) def test_cell_collection(): @@ -475,9 +475,9 @@ def test_cell_collection(): cells = collection.cells model = Model() - cells[0].add_agent(CellAgent(1, model)) - cells[3].add_agent(CellAgent(2, model)) - cells[7].add_agent(CellAgent(3, model)) + cells[0].add_agent(CellAgent(model)) + cells[3].add_agent(CellAgent(model)) + cells[7].add_agent(CellAgent(model)) agents = collection.agents assert len(list(agents)) == 3 diff --git a/tests/test_model.py b/tests/test_model.py index c92fcb2e4a9..88f68287e0a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -50,7 +50,7 @@ class TestAgent(Agent): pass model = Model() - test_agent = TestAgent(model.next_id(), model) + test_agent = TestAgent( model) assert test_agent in model.agents assert type(test_agent) in model.agent_types diff --git a/tests/test_time.py b/tests/test_time.py index 4203ca5d222..38e03abef0c 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -25,8 +25,8 @@ class MockAgent(Agent): Minimalistic agent for testing purposes. """ - def __init__(self, unique_id, model): - super().__init__(unique_id, model) + def __init__(self, model): + super().__init__(model) self.steps = 0 self.advances = 0 @@ -38,10 +38,10 @@ def kill_other_agent(self): def stage_one(self): if self.model.enable_kill_other_agent: self.kill_other_agent() - self.model.log.append(self.unique_id + "_1") + self.model.log.append(f"{self.unique_id}_1") def stage_two(self): - self.model.log.append(self.unique_id + "_2") + self.model.log.append(f"{self.unique_id}_2") def advance(self): self.advances += 1 @@ -90,7 +90,7 @@ def __init__(self, shuffle=False, activation=STAGED, enable_kill_other_agent=Fal # Make agents for name in ["A", "B"]: - agent = MockAgent(name, self) + agent = MockAgent(self) self.schedule.add(agent) def step(self): @@ -105,7 +105,7 @@ class TestStagedActivation(TestCase): Test the staged activation. """ - expected_output = ["A_1", "B_1", "model_stage", "A_2", "B_2"] + expected_output = ["1_1", "1_1", "model_stage", "1_2", "1_2"] def test_no_shuffle(self): """ @@ -168,7 +168,7 @@ class TestRandomActivation(TestCase): def test_init(self): model = Model() - agents = [MockAgent(model.next_id(), model) for _ in range(10)] + agents = [MockAgent( model) for _ in range(10)] scheduler = RandomActivation(model, agents) assert all(agent in scheduler.agents for agent in agents) @@ -227,7 +227,7 @@ def test_not_sequential(self): model = MockModel(activation=RANDOM) # Create 10 agents for _ in range(10): - model.schedule.add(MockAgent(model.next_id(), model)) + model.schedule.add(MockAgent( model)) # Run 3 steps for _ in range(3): model.step() @@ -273,8 +273,8 @@ class TestRandomActivationByType(TestCase): def test_init(self): model = Model() - agents = [MockAgent(model.next_id(), model) for _ in range(10)] - agents += [Agent(model.next_id(), model) for _ in range(10)] + agents = [MockAgent( model) for _ in range(10)] + agents += [Agent( model) for _ in range(10)] scheduler = RandomActivationByType(model, agents) assert all(agent in scheduler.agents for agent in agents) From 3bc29e11f8b843f761969f37bc0d77f7c5fba9a1 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Sat, 31 Aug 2024 20:01:57 +0200 Subject: [PATCH 07/21] remove model. next_id --- mesa/model.py | 5 ----- tests/test_lifespan.py | 8 ++++---- tests/test_model.py | 3 --- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/mesa/model.py b/mesa/model.py index f276cc82284..8ac3cd1c31b 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -189,11 +189,6 @@ def run_model(self) -> None: def step(self) -> None: """A single step. Fill in here.""" - def next_id(self) -> int: - """Return the next unique ID for agents, increment current_id""" - self.current_id += 1 - return self.current_id - def reset_randomizer(self, seed: int | None = None) -> None: """Reset the model random number generator. diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py index cfd60cdeb74..f9d98799194 100644 --- a/tests/test_lifespan.py +++ b/tests/test_lifespan.py @@ -30,7 +30,7 @@ def __init__(self, agent_lifetime=1, n_agents=10): for _ in range(n_agents): self.schedule.add( - FiniteLifeAgent(self.next_id(), self.agent_lifetime, self) + FiniteLifeAgent(self.agent_lifetime, self) ) def step(self): @@ -41,7 +41,7 @@ def step(self): if len(self.schedule.agents) < self.n_agents: for _ in range(self.n_agents - len(self.schedule.agents)): self.schedule.add( - FiniteLifeAgent(self.next_id(), self.agent_lifetime, self) + FiniteLifeAgent(self.agent_lifetime, self) ) def run_model(self, step_count=100): @@ -54,8 +54,8 @@ class FiniteLifeAgent(Agent): Also has a 10% chance of dying in each tick. """ - def __init__(self, unique_id, lifetime, model): - super().__init__(unique_id, model) + def __init__(self, lifetime, model): + super().__init__(model) self.remaining_life = lifetime self.steps = 0 self.model = model diff --git a/tests/test_model.py b/tests/test_model.py index 88f68287e0a..14f57bccc56 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -6,9 +6,6 @@ def test_model_set_up(): model = Model() assert model.running is True assert model.schedule is None - assert model.current_id == 0 - assert model.current_id + 1 == model.next_id() - assert model.current_id == 1 assert model.steps == 0 model.step() assert model.steps == 1 From 27d54eb430b173eb142af9141f3877c3f336a29b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 31 Aug 2024 18:02:13 +0000 Subject: [PATCH 08/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- benchmarks/WolfSheep/wolf_sheep.py | 3 +-- mesa/agent.py | 2 ++ tests/test_agent.py | 32 +++++++++++++++--------------- tests/test_lifespan.py | 8 ++------ tests/test_model.py | 2 +- tests/test_time.py | 8 ++++---- 6 files changed, 26 insertions(+), 29 deletions(-) diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py index be4806b55e2..c7202dd1385 100644 --- a/benchmarks/WolfSheep/wolf_sheep.py +++ b/benchmarks/WolfSheep/wolf_sheep.py @@ -29,8 +29,7 @@ def random_move(self): def spawn_offspring(self): self.energy /= 2 offspring = self.__class__( - self. - self.model, + self.self.model, self.energy, self.p_reproduce, self.energy_from_food, diff --git a/mesa/agent.py b/mesa/agent.py index b2128e38ca6..63a15c3ada6 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -33,6 +33,7 @@ def __missing__(self, key): res = self[key] = itertools.count(1) return res + class Agent: """ Base class for a model agent in Mesa. @@ -41,6 +42,7 @@ class Agent: model (Model): A reference to the model instance. self.pos: Position | None = None """ + _ids = TestClassDict() def __init__(self, *args, **kwargs) -> None: diff --git a/tests/test_agent.py b/tests/test_agent.py index 4a0333458d7..3b6b601970a 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -141,12 +141,12 @@ def test_agent_membership(): agentset = AgentSet(agents, model) assert agents[0] in agentset - assert TestAgent( model) not in agentset + assert TestAgent(model) not in agentset def test_agent_add_remove_discard(): model = Model() - agent = TestAgent( model) + agent = TestAgent(model) agentset = AgentSet([], model) agentset.add(agent) @@ -165,7 +165,7 @@ def test_agent_add_remove_discard(): def test_agentset_get_item(): model = Model() - agents = [TestAgent( model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) assert agentset[0] == agents[0] @@ -178,7 +178,7 @@ def test_agentset_get_item(): def test_agentset_do_str(): model = Model() - agents = [TestAgent( model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) with pytest.raises(AttributeError): @@ -201,7 +201,7 @@ def test_agentset_do_str(): # setup model = Model() - agents = [TestAgentDo( model) for _ in range(10)] + agents = [TestAgentDo(model) for _ in range(10)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -212,7 +212,7 @@ def test_agentset_do_str(): def test_agentset_do_callable(): model = Model() - agents = [TestAgent( model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) # Test callable with non-existent function @@ -226,7 +226,7 @@ def test_agentset_do_callable(): # setup for lambda function tests n = 10 model = Model() - agents = [TestAgentDo( model) for _ in range(n)] + agents = [TestAgentDo(model) for _ in range(n)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -237,7 +237,7 @@ def test_agentset_do_callable(): # setup again for lambda function tests model = Model() - agents = [TestAgentDo( model) for _ in range(10)] + agents = [TestAgentDo(model) for _ in range(10)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -255,7 +255,7 @@ def remove_function(agent): # setup again for actual function tests model = Model() - agents = [TestAgentDo( model) for _ in range(n)] + agents = [TestAgentDo(model) for _ in range(n)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -266,7 +266,7 @@ def remove_function(agent): # setup again for actual function tests model = Model() - agents = [TestAgentDo( model) for _ in range(10)] + agents = [TestAgentDo(model) for _ in range(10)] agentset = AgentSet(agents, model) for agent in agents: agent.agent_set = agentset @@ -349,7 +349,7 @@ def test_agentset_map_str(): def test_agentset_map_callable(): model = Model() - agents = [TestAgent( model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) # Test callable with non-existent function @@ -366,7 +366,7 @@ def test_agentset_map_callable(): def test_agentset_get_attribute(): model = Model() - agents = [TestAgent( model) for _ in range(10)] + agents = [TestAgent(model) for _ in range(10)] agentset = AgentSet(agents, model) unique_ids = agentset.get("unique_id") @@ -378,7 +378,7 @@ def test_agentset_get_attribute(): model = Model() agents = [] for i in range(10): - agent = TestAgent( model) + agent = TestAgent(model) agent.i = i**2 agents.append(agent) agentset = AgentSet(agents, model) @@ -402,8 +402,8 @@ def get_unique_identifier(self): def test_agentset_select_by_type(): model = Model() # Create a mix of agents of two different types - test_agents = [TestAgent( model) for _ in range(4)] - other_agents = [OtherAgentType( model) for _ in range(6)] + test_agents = [TestAgent(model) for _ in range(4)] + other_agents = [OtherAgentType(model) for _ in range(6)] # Combine the two types of agents mixed_agents = test_agents + other_agents @@ -427,7 +427,7 @@ def test_agentset_select_by_type(): def test_agentset_shuffle(): model = Model() - test_agents = [TestAgent( model) for _ in range(12)] + test_agents = [TestAgent(model) for _ in range(12)] agentset = AgentSet(test_agents, model=model) agentset = agentset.shuffle() diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py index f9d98799194..00dd878c8e9 100644 --- a/tests/test_lifespan.py +++ b/tests/test_lifespan.py @@ -29,9 +29,7 @@ def __init__(self, agent_lifetime=1, n_agents=10): self.schedule = RandomActivation(self) for _ in range(n_agents): - self.schedule.add( - FiniteLifeAgent(self.agent_lifetime, self) - ) + self.schedule.add(FiniteLifeAgent(self.agent_lifetime, self)) def step(self): """Add agents back to n_agents in each step""" @@ -40,9 +38,7 @@ def step(self): if len(self.schedule.agents) < self.n_agents: for _ in range(self.n_agents - len(self.schedule.agents)): - self.schedule.add( - FiniteLifeAgent(self.agent_lifetime, self) - ) + self.schedule.add(FiniteLifeAgent(self.agent_lifetime, self)) def run_model(self, step_count=100): for _ in range(step_count): diff --git a/tests/test_model.py b/tests/test_model.py index 14f57bccc56..b2dfe6ed266 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -47,7 +47,7 @@ class TestAgent(Agent): pass model = Model() - test_agent = TestAgent( model) + test_agent = TestAgent(model) assert test_agent in model.agents assert type(test_agent) in model.agent_types diff --git a/tests/test_time.py b/tests/test_time.py index 38e03abef0c..d349b1eaaaf 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -168,7 +168,7 @@ class TestRandomActivation(TestCase): def test_init(self): model = Model() - agents = [MockAgent( model) for _ in range(10)] + agents = [MockAgent(model) for _ in range(10)] scheduler = RandomActivation(model, agents) assert all(agent in scheduler.agents for agent in agents) @@ -227,7 +227,7 @@ def test_not_sequential(self): model = MockModel(activation=RANDOM) # Create 10 agents for _ in range(10): - model.schedule.add(MockAgent( model)) + model.schedule.add(MockAgent(model)) # Run 3 steps for _ in range(3): model.step() @@ -273,8 +273,8 @@ class TestRandomActivationByType(TestCase): def test_init(self): model = Model() - agents = [MockAgent( model) for _ in range(10)] - agents += [Agent( model) for _ in range(10)] + agents = [MockAgent(model) for _ in range(10)] + agents += [Agent(model) for _ in range(10)] scheduler = RandomActivationByType(model, agents) assert all(agent in scheduler.agents for agent in agents) From 5a3c4ce8bcc32e506501bbaa58697b251a9a3604 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 08:20:48 +0200 Subject: [PATCH 09/21] remove next_id from benchmarks --- benchmarks/BoltzmannWealth/boltzmann_wealth.py | 8 ++++---- benchmarks/Flocking/flocking.py | 7 ++----- benchmarks/Schelling/schelling.py | 9 +++------ benchmarks/WolfSheep/wolf_sheep.py | 14 ++++++-------- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/benchmarks/BoltzmannWealth/boltzmann_wealth.py b/benchmarks/BoltzmannWealth/boltzmann_wealth.py index f6b1bf3d6f9..56443c6f476 100644 --- a/benchmarks/BoltzmannWealth/boltzmann_wealth.py +++ b/benchmarks/BoltzmannWealth/boltzmann_wealth.py @@ -27,8 +27,8 @@ def __init__(self, seed=None, n=100, width=10, height=10): model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": "wealth"} ) # Create agents - for i in range(self.num_agents): - a = MoneyAgent(i, self) + for _ in range(self.num_agents): + a = MoneyAgent(self) # Add the agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) @@ -50,8 +50,8 @@ def run_model(self, n): class MoneyAgent(mesa.Agent): """An agent with fixed initial wealth.""" - def __init__(self, unique_id, model): - super().__init__(unique_id, model) + def __init__(self, model): + super().__init__(model) self.wealth = 1 def move(self): diff --git a/benchmarks/Flocking/flocking.py b/benchmarks/Flocking/flocking.py index ff9f97c0c96..284d831850c 100644 --- a/benchmarks/Flocking/flocking.py +++ b/benchmarks/Flocking/flocking.py @@ -27,7 +27,6 @@ class Boid(mesa.Agent): def __init__( self, - unique_id, model, speed, direction, @@ -41,7 +40,6 @@ def __init__( Create a new Boid flocker agent. Args: - unique_id: Unique agent identifier. speed: Distance to move per step. direction: numpy vector for the Boid's direction of movement. vision: Radius to look around for nearby Boids. @@ -51,7 +49,7 @@ def __init__( match: the relative importance of matching neighbors' directions """ - super().__init__(unique_id, model) + super().__init__(model) self.speed = speed self.direction = direction self.vision = vision @@ -131,13 +129,12 @@ def __init__( "match": match, } - for i in range(self.population): + for _ in range(self.population): x = self.random.random() * self.space.x_max y = self.random.random() * self.space.y_max pos = np.array((x, y)) direction = np.random.random(2) * 2 - 1 boid = Boid( - unique_id=i, model=self, speed=speed, direction=direction, diff --git a/benchmarks/Schelling/schelling.py b/benchmarks/Schelling/schelling.py index b7702a84bed..b582e7af585 100644 --- a/benchmarks/Schelling/schelling.py +++ b/benchmarks/Schelling/schelling.py @@ -8,15 +8,14 @@ class SchellingAgent(CellAgent): Schelling segregation agent """ - def __init__(self, unique_id, model, agent_type, radius, homophily): + def __init__(self, model, agent_type, radius, homophily): """ Create a new Schelling agent. Args: - unique_id: Unique identifier for the agent. x, y: Agent initial location. agent_type: Indicator for the agent's type (minority=1, majority=0) """ - super().__init__(unique_id, model) + super().__init__(model) self.type = agent_type self.radius = radius self.homophily = homophily @@ -81,9 +80,7 @@ def __init__( for cell in self.grid: if self.random.random() < density: agent_type = 1 if self.random.random() < self.minority_pc else 0 - agent = SchellingAgent( - self.next_id(), self, agent_type, radius, homophily - ) + agent = SchellingAgent(self, agent_type, radius, homophily) agent.move_to(cell) self.schedule.add(agent) diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py index c7202dd1385..1baff28aed7 100644 --- a/benchmarks/WolfSheep/wolf_sheep.py +++ b/benchmarks/WolfSheep/wolf_sheep.py @@ -17,8 +17,8 @@ class Animal(CellAgent): - def __init__(self, unique_id, model, energy, p_reproduce, energy_from_food): - super().__init__(unique_id, model) + def __init__(self, model, energy, p_reproduce, energy_from_food): + super().__init__(model) self.energy = energy self.p_reproduce = p_reproduce self.energy_from_food = energy_from_food @@ -29,7 +29,7 @@ def random_move(self): def spawn_offspring(self): self.energy /= 2 offspring = self.__class__( - self.self.model, + self.model, self.energy, self.p_reproduce, self.energy_from_food, @@ -106,7 +106,7 @@ def fully_grown(self, value: bool) -> None: function_args=[self, "fully_grown", True], ) - def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time): + def __init__(self, model, fully_grown, countdown, grass_regrowth_time): """ TODO:: fully grown can just be an int --> so one less param (i.e. countdown) @@ -118,7 +118,7 @@ def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time grass_regrowth_time : time to fully regrow grass countdown : Time for the patch of grass to be fully regrown if fully grown is False """ - super().__init__(unique_id, model) + super().__init__(model) self._fully_grown = fully_grown self.grass_regrowth_time = grass_regrowth_time @@ -188,7 +188,6 @@ def __init__( ) energy = self.random.randrange(2 * sheep_gain_from_food) sheep = Sheep( - self.next_id(), self, energy, sheep_reproduce, @@ -204,7 +203,6 @@ def __init__( ) energy = self.random.randrange(2 * wolf_gain_from_food) wolf = Wolf( - self.next_id(), self, energy, wolf_reproduce, @@ -221,7 +219,7 @@ def __init__( else: countdown = self.random.randrange(grass_regrowth_time) patch = GrassPatch( - self.next_id(), self, fully_grown, countdown, grass_regrowth_time + self, fully_grown, countdown, grass_regrowth_time ) patch.move_to(cell) From 7e8952d51bbe64f1e141c17f970ef06da2e1b005 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 06:21:04 +0000 Subject: [PATCH 10/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- benchmarks/WolfSheep/wolf_sheep.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/benchmarks/WolfSheep/wolf_sheep.py b/benchmarks/WolfSheep/wolf_sheep.py index 1baff28aed7..ceb6efef8b8 100644 --- a/benchmarks/WolfSheep/wolf_sheep.py +++ b/benchmarks/WolfSheep/wolf_sheep.py @@ -218,9 +218,7 @@ def __init__( countdown = grass_regrowth_time else: countdown = self.random.randrange(grass_regrowth_time) - patch = GrassPatch( - self, fully_grown, countdown, grass_regrowth_time - ) + patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time) patch.move_to(cell) def step(self): From 5ddf9109b7653575f698b7e38688ddbf0845aa85 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 09:17:27 +0200 Subject: [PATCH 11/21] remove next_id from devs examples --- .../devs/examples/epstein_civil_violence.py | 16 ++++++---------- mesa/experimental/devs/examples/wolf_sheep.py | 12 +++++------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/mesa/experimental/devs/examples/epstein_civil_violence.py b/mesa/experimental/devs/examples/epstein_civil_violence.py index 9a7314e0ff3..c976bb73898 100644 --- a/mesa/experimental/devs/examples/epstein_civil_violence.py +++ b/mesa/experimental/devs/examples/epstein_civil_violence.py @@ -7,8 +7,8 @@ class EpsteinAgent(Agent): - def __init__(self, unique_id, model, vision, movement): - super().__init__(unique_id, model) + def __init__(self, model, vision, movement): + super().__init__(model) self.vision = vision self.movement = movement @@ -46,7 +46,6 @@ class Citizen(EpsteinAgent): def __init__( self, - unique_id, model, vision, movement, @@ -59,7 +58,6 @@ def __init__( """ Create a new Citizen. Args: - unique_id: unique int model : model instance hardship: Agent's 'perceived hardship (i.e., physical or economic privation).' Exogenous, drawn from U(0,1). @@ -71,7 +69,7 @@ def __init__( vision: number of cells in each direction (N, S, E and W) that agent can inspect. Exogenous. """ - super().__init__(unique_id, model, vision, movement) + super().__init__(model, vision, movement) self.hardship = hardship self.regime_legitimacy = regime_legitimacy self.risk_aversion = risk_aversion @@ -144,8 +142,8 @@ class Cop(EpsteinAgent): able to inspect """ - def __init__(self, unique_id, model, vision, movement, max_jail_term): - super().__init__(unique_id, model, vision, movement) + def __init__(self, model, vision, movement, max_jail_term): + super().__init__(model, vision, movement) self.max_jail_term = max_jail_term def step(self): @@ -236,7 +234,6 @@ def __init__( for _, pos in self.grid.coord_iter(): if self.random.random() < self.cop_density: agent = Cop( - self.next_id(), self, cop_vision, movement, @@ -244,7 +241,6 @@ def __init__( ) elif self.random.random() < (self.cop_density + self.citizen_density): agent = Citizen( - self.next_id(), self, citizen_vision, movement, @@ -270,4 +266,4 @@ def step(self): simulator.setup(model) - simulator.run(time_delta=100) + simulator.run_for(time_delta=100) diff --git a/mesa/experimental/devs/examples/wolf_sheep.py b/mesa/experimental/devs/examples/wolf_sheep.py index f8524db02e9..288a4b592c8 100644 --- a/mesa/experimental/devs/examples/wolf_sheep.py +++ b/mesa/experimental/devs/examples/wolf_sheep.py @@ -14,8 +14,8 @@ class Animal(mesa.Agent): - def __init__(self, unique_id, model, moore, energy, p_reproduce, energy_from_food): - super().__init__(unique_id, model) + def __init__(self, model, moore, energy, p_reproduce, energy_from_food): + super().__init__(model) self.energy = energy self.p_reproduce = p_reproduce self.energy_from_food = energy_from_food @@ -108,7 +108,7 @@ def fully_grown(self, value: bool): function_args=[self, "fully_grown", True], ) - def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time): + def __init__(self, model, fully_grown, countdown, grass_regrowth_time): """ Creates a new patch of grass @@ -116,7 +116,7 @@ def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time grown: (boolean) Whether the patch of grass is fully grown or not countdown: Time for the patch of grass to be fully grown again """ - super().__init__(unique_id, model) + super().__init__(model) self._fully_grown = fully_grown self.grass_regrowth_time = grass_regrowth_time @@ -190,7 +190,6 @@ def __init__( ) energy = self.random.randrange(2 * sheep_gain_from_food) sheep = Sheep( - self.next_id(), self, moore, energy, @@ -207,7 +206,6 @@ def __init__( ) energy = self.random.randrange(2 * wolf_gain_from_food) wolf = Wolf( - self.next_id(), self, moore, energy, @@ -225,7 +223,7 @@ def __init__( else: countdown = self.random.randrange(grass_regrowth_time) patch = GrassPatch( - self.next_id(), self, fully_grown, countdown, grass_regrowth_time + self, fully_grown, countdown, grass_regrowth_time ) self.grid.place_agent(patch, pos) From 2a762a71cf69fa441340d5e6f66bbd97f081449a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 07:17:36 +0000 Subject: [PATCH 12/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/experimental/devs/examples/wolf_sheep.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mesa/experimental/devs/examples/wolf_sheep.py b/mesa/experimental/devs/examples/wolf_sheep.py index 288a4b592c8..cd81cb2ff04 100644 --- a/mesa/experimental/devs/examples/wolf_sheep.py +++ b/mesa/experimental/devs/examples/wolf_sheep.py @@ -222,9 +222,7 @@ def __init__( countdown = grass_regrowth_time else: countdown = self.random.randrange(grass_regrowth_time) - patch = GrassPatch( - self, fully_grown, countdown, grass_regrowth_time - ) + patch = GrassPatch(self, fully_grown, countdown, grass_regrowth_time) self.grid.place_agent(patch, pos) def step(self): From d4eeb0df3bc989e13501e73ef8003f33f57dae4b Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 10:32:38 +0200 Subject: [PATCH 13/21] enforce internal unique_id makes this consistent with #2226 --- mesa/agent.py | 14 ++++++-------- tests/test_datacollector.py | 17 ++++------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/mesa/agent.py b/mesa/agent.py index 63a15c3ada6..aacdeaae079 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -10,6 +10,7 @@ import contextlib import copy +import functools import itertools import operator import warnings @@ -28,12 +29,6 @@ from mesa.space import Position -class TestClassDict(dict): - def __missing__(self, key): - res = self[key] = itertools.count(1) - return res - - class Agent: """ Base class for a model agent in Mesa. @@ -42,8 +37,10 @@ class Agent: model (Model): A reference to the model instance. self.pos: Position | None = None """ - - _ids = TestClassDict() + # this is a class level attribute + # it is a dictionary, indexed by model instance + # so, unique_id is unique relative to a model, and counting starts from 1 + _ids = defaultdict(functools.partial(itertools.count, 1)) def __init__(self, *args, **kwargs) -> None: """ @@ -64,6 +61,7 @@ def __init__(self, *args, **kwargs) -> None: unique_id, model, *args = args # if *args is not empty, what should we do? # raise ValueError + unique_id = next(self._ids[model]) self.unique_id = unique_id self.model = model diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py index bf74e3790d8..11e1c040921 100644 --- a/tests/test_datacollector.py +++ b/tests/test_datacollector.py @@ -13,8 +13,8 @@ class MockAgent(Agent): Minimalistic agent for testing purposes. """ - def __init__(self, unique_id, model, val=0): - super().__init__(unique_id, model) + def __init__(self, model, val=0): + super().__init__(model) self.val = val self.val2 = val @@ -39,15 +39,6 @@ def write_final_values(self): def agent_function_with_params(agent, multiplier, offset): return (agent.val * multiplier) + offset - -class DifferentMockAgent(MockAgent): - # We define a different MockAgent to test for attributes that are present - # only in 1 type of agent, but not the other. - def __init__(self, unique_id, model, val=0): - super().__init__(unique_id, model, val=val) - self.val3 = val + 42 - - class MockModel(Model): """ Minimalistic model for testing purposes. @@ -61,8 +52,8 @@ def __init__(self): self.model_val = 100 self.n = 10 - for i in range(self.n): - self.schedule.add(MockAgent(i, self, val=i)) + for i in range(1, self.n+1): + self.schedule.add(MockAgent(self, val=i)) self.initialize_data_collector( model_reporters={ "total_agents": lambda m: m.schedule.get_agent_count(), From 08b885c5359eb2aa6f5cbb16c805449609fe0b95 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 08:32:52 +0000 Subject: [PATCH 14/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/agent.py | 1 + tests/test_datacollector.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mesa/agent.py b/mesa/agent.py index aacdeaae079..0aa44834f9c 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -37,6 +37,7 @@ class Agent: model (Model): A reference to the model instance. self.pos: Position | None = None """ + # this is a class level attribute # it is a dictionary, indexed by model instance # so, unique_id is unique relative to a model, and counting starts from 1 diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py index 11e1c040921..94e7742afab 100644 --- a/tests/test_datacollector.py +++ b/tests/test_datacollector.py @@ -39,6 +39,7 @@ def write_final_values(self): def agent_function_with_params(agent, multiplier, offset): return (agent.val * multiplier) + offset + class MockModel(Model): """ Minimalistic model for testing purposes. @@ -52,7 +53,7 @@ def __init__(self): self.model_val = 100 self.n = 10 - for i in range(1, self.n+1): + for i in range(1, self.n + 1): self.schedule.add(MockAgent(self, val=i)) self.initialize_data_collector( model_reporters={ From 15a384b1f3c4c55b45427e366916c10b92465519 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 10:40:43 +0200 Subject: [PATCH 15/21] ensure all tests are consistent with internal assignment of unique_id --- mesa/agent.py | 3 ++- tests/test_agent.py | 2 +- tests/test_batch_run.py | 35 +++++++++++++++++------------------ tests/test_model.py | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mesa/agent.py b/mesa/agent.py index 0aa44834f9c..cac5b68ec08 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -50,9 +50,10 @@ def __init__(self, *args, **kwargs) -> None: Args: model (Model): The model instance in which the agent exists. """ + # FIXME, at some future point, turn *args back into model and remove this if else if len(args) == 1: model = args[0] - unique_id = next(self._ids[model]) + # unique_id = next(self._ids[model]) else: warnings.warn( "unique ids are assigned automatically in MESA 3", diff --git a/tests/test_agent.py b/tests/test_agent.py index 3b6b601970a..c4a253e524a 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -278,7 +278,7 @@ def remove_function(agent): def test_agentset_agg(): model = Model() - agents = [TestAgent(i, model) for i in range(10)] + agents = [TestAgent(model) for i in range(10)] # Assign some values to attributes for i, agent in enumerate(agents): diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index 2de7c44735f..fcf996f6925 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -28,9 +28,8 @@ class MockAgent(Agent): Minimalistic agent implementation for testing purposes """ - def __init__(self, unique_id, model, val): - super().__init__(unique_id, model) - self.unique_id = unique_id + def __init__(self, model, val): + super().__init__(model) self.val = val self.local = 0 @@ -77,7 +76,7 @@ def init_agents(self): else: agent_val = self.variable_agent_param for i in range(self.n_agents): - self.schedule.add(MockAgent(i, self, agent_val)) + self.schedule.add(MockAgent(self, agent_val)) def get_local_model_param(self): return 42 @@ -95,8 +94,8 @@ def test_batch_run(): "iteration": 0, "Step": 1000, "reported_model_param": 42, - "AgentID": 0, - "agent_id": 0, + "AgentID": 1, + "agent_id": 1, "agent_local": 250.0, }, { @@ -104,8 +103,8 @@ def test_batch_run(): "iteration": 0, "Step": 1000, "reported_model_param": 42, - "AgentID": 1, - "agent_id": 1, + "AgentID": 2, + "agent_id": 2, "agent_local": 250.0, }, { @@ -113,8 +112,8 @@ def test_batch_run(): "iteration": 0, "Step": 1000, "reported_model_param": 42, - "AgentID": 2, - "agent_id": 2, + "AgentID": 3, + "agent_id": 3, "agent_local": 250.0, }, ] @@ -172,29 +171,29 @@ def test_batch_run_unhashable_param(): { "RunId": 0, "iteration": 0, - "AgentID": 0, - "agent_id": 0, + "AgentID": 1, + "agent_id": 1, **template, }, { "RunId": 0, "iteration": 0, - "AgentID": 1, - "agent_id": 1, + "AgentID": 2, + "agent_id": 2, **template, }, { "RunId": 1, "iteration": 1, - "AgentID": 0, - "agent_id": 0, + "AgentID": 1, + "agent_id": 1, **template, }, { "RunId": 1, "iteration": 1, - "AgentID": 1, - "agent_id": 1, + "AgentID": 2, + "agent_id": 2, **template, }, ] diff --git a/tests/test_model.py b/tests/test_model.py index b2dfe6ed266..016185e6084 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -60,8 +60,8 @@ class Sheep(Agent): pass model = Model() - wolf = Wolf(1, model) - sheep = Sheep(2, model) + wolf = Wolf(model) + sheep = Sheep(model) assert model.agents_by_type[Wolf] == AgentSet([wolf], model) assert model.agents_by_type[Sheep] == AgentSet([sheep], model) From e7ca6de3fcd1e65800ffce0b248b3d48c59e5135 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 10:53:28 +0200 Subject: [PATCH 16/21] Update agent.py --- mesa/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/agent.py b/mesa/agent.py index cac5b68ec08..ca9d96b2a86 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -53,7 +53,7 @@ def __init__(self, *args, **kwargs) -> None: # FIXME, at some future point, turn *args back into model and remove this if else if len(args) == 1: model = args[0] - # unique_id = next(self._ids[model]) + unique_id = next(self._ids[model]) else: warnings.warn( "unique ids are assigned automatically in MESA 3", From ffe1a6c6ee8dc8156686587392d5a1d8cd7f92b3 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 14:29:02 +0200 Subject: [PATCH 17/21] additional test --- mesa/agent.py | 3 ++- tests/test_agent.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mesa/agent.py b/mesa/agent.py index ca9d96b2a86..00b40c907d3 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -35,7 +35,8 @@ class Agent: Attributes: model (Model): A reference to the model instance. - self.pos: Position | None = None + unique_id (int): A unique identifier for this agent. + pos (Position): A reference to the position where this agent is located. """ # this is a class level attribute diff --git a/tests/test_agent.py b/tests/test_agent.py index c4a253e524a..3b07e3eff8a 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -478,3 +478,14 @@ def get_unique_identifier(self): groups = agentset.groupby("even", result_type="agentset") another_ref_to_groups = groups.do(lambda x: x.do("step")) assert groups == another_ref_to_groups + + +def test_oldstyle_agent_instantiation(): + """Old behavior of Agent creation with unique_id and model as positional arguments. + Can be removed/updated in the future.""" + model = Model() + agent = Agent("some weird unique id", model) + assert isinstance(agent.unique_id, int) + assert agent.model == model + assert isinstance(agent.model, Model) + assert agent.unique_id == 1 # test that we ignore unique ID that is passed From e4b887fd48fb586d7d702abe9cd60352a97b4eba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:29:15 +0000 Subject: [PATCH 18/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_agent.py b/tests/test_agent.py index 3b07e3eff8a..e78a4dcec93 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -488,4 +488,4 @@ def test_oldstyle_agent_instantiation(): assert isinstance(agent.unique_id, int) assert agent.model == model assert isinstance(agent.model, Model) - assert agent.unique_id == 1 # test that we ignore unique ID that is passed + assert agent.unique_id == 1 # test that we ignore unique ID that is passed From bdfa09433df8cb80f398ad77a6649029ab456e2a Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 14:31:14 +0200 Subject: [PATCH 19/21] ruff related fixes --- tests/test_batch_run.py | 2 +- tests/test_time.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index fcf996f6925..784c041679a 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -75,7 +75,7 @@ def init_agents(self): agent_val = 1 else: agent_val = self.variable_agent_param - for i in range(self.n_agents): + for _ in range(self.n_agents): self.schedule.add(MockAgent(self, agent_val)) def get_local_model_param(self): diff --git a/tests/test_time.py b/tests/test_time.py index d349b1eaaaf..13d71d22eb0 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -89,7 +89,7 @@ def __init__(self, shuffle=False, activation=STAGED, enable_kill_other_agent=Fal self.schedule = BaseScheduler(self) # Make agents - for name in ["A", "B"]: + for _ in range(2): agent = MockAgent(self) self.schedule.add(agent) From 32ee2d352e94b4266a9c732d488b83302ab3071d Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Wed, 4 Sep 2024 15:32:41 +0200 Subject: [PATCH 20/21] requested modifications --- mesa/agent.py | 41 +++++++++++++++++++++++++---------------- mesa/model.py | 7 +++++-- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/mesa/agent.py b/mesa/agent.py index 00b40c907d3..c7dab8b2b2e 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -37,6 +37,10 @@ class Agent: model (Model): A reference to the model instance. unique_id (int): A unique identifier for this agent. pos (Position): A reference to the position where this agent is located. + + Notes: + unique_id is unique relative to a model instance and starts from 1 + """ # this is a class level attribute @@ -51,23 +55,28 @@ def __init__(self, *args, **kwargs) -> None: Args: model (Model): The model instance in which the agent exists. """ - # FIXME, at some future point, turn *args back into model and remove this if else - if len(args) == 1: - model = args[0] - unique_id = next(self._ids[model]) - else: - warnings.warn( - "unique ids are assigned automatically in MESA 3", - DeprecationWarning, - stacklevel=2, - ) - unique_id, model, *args = args - # if *args is not empty, what should we do? - # raise ValueError - unique_id = next(self._ids[model]) + # TODO: Cleanup in future Mesa version (3.1+) + match args: + # Case 1: Only the model is provided. The new correct behavior. + case [model]: + self.model = model + self.unique_id = next(self._ids[model]) + # Case 2: Both unique_id and model are provided, deprecated + case [_, model]: + warnings.warn( + "unique ids are assigned automatically to Agents in Mesa 3. The use of custom unique_id is " + "deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.", + DeprecationWarning, + stacklevel=2 + ) + self.model = model + self.unique_id = next(self._ids[model]) + # Case 3: Anything else, raise an error + case _: + raise ValueError( + "Invalid arguments provided to initialize the Agent. Only input a model: `super()__init__(model)`." + ) - self.unique_id = unique_id - self.model = model self.pos: Position | None = None self.model.register_agent(self) diff --git a/mesa/model.py b/mesa/model.py index 8ac3cd1c31b..1f3dd072565 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -28,7 +28,6 @@ class Model: Attributes: running: A boolean indicating if the model should continue running. schedule: An object to manage the order and execution of agent steps. - current_id: A counter for assigning unique IDs to agents. Properties: agents: An AgentSet containing all agents in the model @@ -74,7 +73,6 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.running = True self.schedule = None - self.current_id = 0 self.steps: int = 0 self._setup_agent_registration() @@ -90,6 +88,11 @@ def _wrapped_step(self, *args: Any, **kwargs: Any) -> None: # Call the original user-defined step method self._user_step(*args, **kwargs) + def next_id(self) -> int: + warnings.warn("using model.next_id() is deprecated. Agents track their unique ID automatically", + DeprecationWarning, stacklevel=2) + return 0 + @property def agents(self) -> AgentSet: """Provides an AgentSet of all agents in the model, combining agents from all types.""" From d3947ac853fad15e08434671cea1a55357758fcb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:32:49 +0000 Subject: [PATCH 21/21] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mesa/agent.py | 4 ++-- mesa/model.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mesa/agent.py b/mesa/agent.py index c7dab8b2b2e..225e89cda79 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -65,9 +65,9 @@ def __init__(self, *args, **kwargs) -> None: case [_, model]: warnings.warn( "unique ids are assigned automatically to Agents in Mesa 3. The use of custom unique_id is " - "deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.", + "deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) self.model = model self.unique_id = next(self._ids[model]) diff --git a/mesa/model.py b/mesa/model.py index 1f3dd072565..f859aea314f 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -89,8 +89,11 @@ def _wrapped_step(self, *args: Any, **kwargs: Any) -> None: self._user_step(*args, **kwargs) def next_id(self) -> int: - warnings.warn("using model.next_id() is deprecated. Agents track their unique ID automatically", - DeprecationWarning, stacklevel=2) + warnings.warn( + "using model.next_id() is deprecated. Agents track their unique ID automatically", + DeprecationWarning, + stacklevel=2, + ) return 0 @property