From c29f21dda47965111a709f27831aafdd40358b8f Mon Sep 17 00:00:00 2001 From: rht Date: Sat, 6 Jan 2024 22:00:15 -0500 Subject: [PATCH 01/17] refactor: Remove dependence on model.schedule --- mesa/datacollection.py | 14 ++++++-------- mesa/experimental/jupyter_viz.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/mesa/datacollection.py b/mesa/datacollection.py index 88f1863f457..8929821937d 100644 --- a/mesa/datacollection.py +++ b/mesa/datacollection.py @@ -14,8 +14,7 @@ When the collect() method is called, each model-level function is called, with the model as the argument, and the results associated with the relevant -variable. Then the agent-level functions are called on each agent in the model -scheduler. +variable. Then the agent-level functions are called on each agent. Additionally, other objects can write directly to tables by passing in an appropriate dictionary object for a table row. @@ -30,8 +29,7 @@ Finally, DataCollector can create a pandas DataFrame from each collection. The default DataCollector here makes several assumptions: - * The model has a schedule object called 'schedule' - * The schedule has an agent list called agents + * The model has an agent list called agents * For collecting agent-level variables, agents must have a unique_id """ import contextlib @@ -67,7 +65,7 @@ def __init__( Model reporters can take four types of arguments: 1. Lambda function: - {"agent_count": lambda m: m.schedule.get_agent_count()} + {"agent_count": lambda m: len(m.agents)} 2. Method of a class/instance: {"agent_count": self.get_agent_count} # self here is a class instance {"agent_count": Model.get_agent_count} # Model here is a class @@ -180,11 +178,11 @@ def _record_agents(self, model): rep_funcs = self.agent_reporters.values() def get_reports(agent): - _prefix = (agent.model.schedule.steps, agent.unique_id) + _prefix = (agent.model.steps, agent.unique_id) reports = tuple(rep(agent) for rep in rep_funcs) return _prefix + reports - agent_records = map(get_reports, model.schedule.agents) + agent_records = map(get_reports, model.agents) return agent_records def collect(self, model): @@ -207,7 +205,7 @@ def collect(self, model): if self.agent_reporters: agent_records = self._record_agents(model) - self._agent_records[model.schedule.steps] = list(agent_records) + self._agent_records[model.steps] = list(agent_records) def add_table_row(self, table_name, row, ignore_missing=False): """Add a row dictionary to a specific table. diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py index 7a586239058..950723e1e73 100644 --- a/mesa/experimental/jupyter_viz.py +++ b/mesa/experimental/jupyter_viz.py @@ -181,7 +181,7 @@ def on_value_play(change): def do_step(): model.step() previous_step.value = current_step.value - current_step.value += 1 + current_step.value = model.steps def do_play(): model.running = True From 85ec27f8f24b17096b060bed74f50740377c8f0d Mon Sep 17 00:00:00 2001 From: rht Date: Sun, 7 Jan 2024 09:17:51 -0500 Subject: [PATCH 02/17] model: Implement internal clock --- mesa/model.py | 12 +++++++++++- tests/test_batch_run.py | 1 + tests/test_datacollector.py | 1 + tests/test_lifespan.py | 1 + tests/test_time.py | 1 + tests/test_visualization.py | 1 + 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mesa/model.py b/mesa/model.py index 1bd9b3a7548..d20a1cd057d 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -13,11 +13,13 @@ from collections import defaultdict # mypy -from typing import Any +from typing import Any, Union from mesa.agent import Agent, AgentSet from mesa.datacollection import DataCollector +TimeT = Union[float, int] + class Model: """Base class for models in the Mesa ABM library. @@ -68,6 +70,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.current_id = 0 self.agents_: defaultdict[type, dict] = defaultdict(dict) + self.steps = 0 + self.time: TimeT = 0 # the model's clock + # Warning flags for current experimental features. These make sure a warning is only printed once per model. self.agentset_experimental_warning_given = False @@ -112,6 +117,11 @@ def run_model(self) -> None: def step(self) -> None: """A single step. Fill in here.""" + def advance_time(self, deltat: TimeT = 1): + """Increment the model's steps counter and clock.""" + self.steps += 1 + self.time += deltat + def next_id(self) -> int: """Return the next unique ID for agents, increment current_id""" self.current_id += 1 diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index 1557666f34f..bbc05845723 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -85,6 +85,7 @@ def get_local_model_param(self): def step(self): self.datacollector.collect(self) self.schedule.step() + self.advance_time() def test_batch_run(): diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py index a56ca47745c..9b03efdcd2b 100644 --- a/tests/test_datacollector.py +++ b/tests/test_datacollector.py @@ -89,6 +89,7 @@ def test_model_calc_comp(self, input1, input2): def step(self): self.schedule.step() self.datacollector.collect(self) + self.advance_time() class TestDataCollector(unittest.TestCase): diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py index cfd60cdeb74..ee5dc1d108c 100644 --- a/tests/test_lifespan.py +++ b/tests/test_lifespan.py @@ -43,6 +43,7 @@ def step(self): self.schedule.add( FiniteLifeAgent(self.next_id(), self.agent_lifetime, self) ) + self.advance_time() def run_model(self, step_count=100): for _ in range(step_count): diff --git a/tests/test_time.py b/tests/test_time.py index 3cfc2f35078..669cc29044a 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -96,6 +96,7 @@ def __init__(self, shuffle=False, activation=STAGED, enable_kill_other_agent=Fal def step(self): self.schedule.step() + self.advance_time() def model_stage(self): self.log.append("model_stage") diff --git a/tests/test_visualization.py b/tests/test_visualization.py index ca9f0949558..a002c687e6b 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -49,6 +49,7 @@ def __init__(self, width, height, key1=103, key2=104): def step(self): self.schedule.step() + self.advance_time() class TestModularServer(TestCase): From 22c9e8d50fe804669716778c36b18061c87db522 Mon Sep 17 00:00:00 2001 From: rht Date: Tue, 9 Jan 2024 06:51:26 -0500 Subject: [PATCH 03/17] time: Call self.model.advance_time() in step() This ensures that the scheduler's clock and the model's clock are updated and are in sync. --- mesa/time.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mesa/time.py b/mesa/time.py index 87a68949cdd..6f5d66b1af5 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -114,6 +114,7 @@ def step(self) -> None: self.do_each("step") self.steps += 1 self.time += 1 + self.model.advance_time() def get_agent_count(self) -> int: """Returns the current number of agents in the queue.""" @@ -171,6 +172,7 @@ def step(self) -> None: self.do_each("step", shuffle=True) self.steps += 1 self.time += 1 + self.model.advance_time() class SimultaneousActivation(BaseScheduler): @@ -199,6 +201,7 @@ def step(self) -> None: self.do_each("advance") self.steps += 1 self.time += 1 + self.model.advance_time() class StagedActivation(BaseScheduler): @@ -262,6 +265,7 @@ def step(self) -> None: self.time += self.stage_time self.steps += 1 + self.model.advance_time() class RandomActivationByType(BaseScheduler): @@ -362,6 +366,7 @@ def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None: self.step_type(agent_class, shuffle_agents=shuffle_agents) self.steps += 1 self.time += 1 + self.model.advance_time() def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None: """ @@ -487,6 +492,7 @@ def step(self) -> None: # After processing events, advance time by the time_step self.time = end_time self.steps += 1 + self.model.advance_time() def get_next_event_time(self) -> TimeT | None: """Returns the time of the next scheduled event.""" From c37ba71c075820cd3c7945f35e6166fece903fff Mon Sep 17 00:00:00 2001 From: rht Date: Tue, 9 Jan 2024 08:22:32 -0500 Subject: [PATCH 04/17] Ensure advance_time call in schedulers happen only once in a model step --- mesa/time.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mesa/time.py b/mesa/time.py index 6f5d66b1af5..fcb4408ba7f 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -114,7 +114,8 @@ def step(self) -> None: self.do_each("step") self.steps += 1 self.time += 1 - self.model.advance_time() + if self.model.steps < self.steps: + self.model.advance_time() def get_agent_count(self) -> int: """Returns the current number of agents in the queue.""" @@ -172,7 +173,8 @@ def step(self) -> None: self.do_each("step", shuffle=True) self.steps += 1 self.time += 1 - self.model.advance_time() + if self.model.steps < self.steps: + self.model.advance_time() class SimultaneousActivation(BaseScheduler): @@ -201,7 +203,8 @@ def step(self) -> None: self.do_each("advance") self.steps += 1 self.time += 1 - self.model.advance_time() + if self.model.steps < self.steps: + self.model.advance_time() class StagedActivation(BaseScheduler): @@ -265,7 +268,8 @@ def step(self) -> None: self.time += self.stage_time self.steps += 1 - self.model.advance_time() + if self.model.steps < self.steps: + self.model.advance_time() class RandomActivationByType(BaseScheduler): @@ -366,7 +370,8 @@ def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None: self.step_type(agent_class, shuffle_agents=shuffle_agents) self.steps += 1 self.time += 1 - self.model.advance_time() + if self.model.steps < self.steps: + self.model.advance_time() def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None: """ @@ -492,7 +497,8 @@ def step(self) -> None: # After processing events, advance time by the time_step self.time = end_time self.steps += 1 - self.model.advance_time() + if self.model.steps < self.steps: + self.model.advance_time() def get_next_event_time(self) -> TimeT | None: """Returns the time of the next scheduled event.""" From 5a381b8f08090aaea18f21c72902c61b8bce79e0 Mon Sep 17 00:00:00 2001 From: rht Date: Tue, 9 Jan 2024 09:03:23 -0500 Subject: [PATCH 05/17] Turn model steps and time to be private attribute --- mesa/datacollection.py | 4 ++-- mesa/model.py | 8 ++++---- mesa/time.py | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mesa/datacollection.py b/mesa/datacollection.py index 8929821937d..5edbe587c2e 100644 --- a/mesa/datacollection.py +++ b/mesa/datacollection.py @@ -178,7 +178,7 @@ def _record_agents(self, model): rep_funcs = self.agent_reporters.values() def get_reports(agent): - _prefix = (agent.model.steps, agent.unique_id) + _prefix = (agent.model._steps, agent.unique_id) reports = tuple(rep(agent) for rep in rep_funcs) return _prefix + reports @@ -205,7 +205,7 @@ def collect(self, model): if self.agent_reporters: agent_records = self._record_agents(model) - self._agent_records[model.steps] = list(agent_records) + self._agent_records[model._steps] = list(agent_records) def add_table_row(self, table_name, row, ignore_missing=False): """Add a row dictionary to a specific table. diff --git a/mesa/model.py b/mesa/model.py index d20a1cd057d..fbc099298ac 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -70,8 +70,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.current_id = 0 self.agents_: defaultdict[type, dict] = defaultdict(dict) - self.steps = 0 - self.time: TimeT = 0 # the model's clock + self._steps = 0 + self._time: TimeT = 0 # the model's clock # Warning flags for current experimental features. These make sure a warning is only printed once per model. self.agentset_experimental_warning_given = False @@ -119,8 +119,8 @@ def step(self) -> None: def advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" - self.steps += 1 - self.time += deltat + self._steps += 1 + self._time += deltat def next_id(self) -> int: """Return the next unique ID for agents, increment current_id""" diff --git a/mesa/time.py b/mesa/time.py index fcb4408ba7f..3e015b5d645 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -114,7 +114,7 @@ def step(self) -> None: self.do_each("step") self.steps += 1 self.time += 1 - if self.model.steps < self.steps: + if self.model._steps < self.steps: self.model.advance_time() def get_agent_count(self) -> int: @@ -173,7 +173,7 @@ def step(self) -> None: self.do_each("step", shuffle=True) self.steps += 1 self.time += 1 - if self.model.steps < self.steps: + if self.model._steps < self.steps: self.model.advance_time() @@ -203,7 +203,7 @@ def step(self) -> None: self.do_each("advance") self.steps += 1 self.time += 1 - if self.model.steps < self.steps: + if self.model._steps < self.steps: self.model.advance_time() @@ -268,7 +268,7 @@ def step(self) -> None: self.time += self.stage_time self.steps += 1 - if self.model.steps < self.steps: + if self.model._steps < self.steps: self.model.advance_time() @@ -370,7 +370,7 @@ def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None: self.step_type(agent_class, shuffle_agents=shuffle_agents) self.steps += 1 self.time += 1 - if self.model.steps < self.steps: + if self.model._steps < self.steps: self.model.advance_time() def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None: @@ -497,7 +497,7 @@ def step(self) -> None: # After processing events, advance time by the time_step self.time = end_time self.steps += 1 - if self.model.steps < self.steps: + if self.model._steps < self.steps: self.model.advance_time() def get_next_event_time(self) -> TimeT | None: From db3710fc2ab57a2a342d60ac1eabb5580b363744 Mon Sep 17 00:00:00 2001 From: rht Date: Tue, 16 Jan 2024 03:28:06 -0500 Subject: [PATCH 06/17] Rename advance_time to _advance_time --- mesa/model.py | 2 +- mesa/time.py | 12 ++++++------ tests/test_batch_run.py | 2 +- tests/test_datacollector.py | 2 +- tests/test_lifespan.py | 2 +- tests/test_time.py | 2 +- tests/test_visualization.py | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mesa/model.py b/mesa/model.py index fbc099298ac..d0a8063e62c 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -117,7 +117,7 @@ def run_model(self) -> None: def step(self) -> None: """A single step. Fill in here.""" - def advance_time(self, deltat: TimeT = 1): + def _advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" self._steps += 1 self._time += deltat diff --git a/mesa/time.py b/mesa/time.py index 3e015b5d645..d5bd97b32c2 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -115,7 +115,7 @@ def step(self) -> None: self.steps += 1 self.time += 1 if self.model._steps < self.steps: - self.model.advance_time() + self.model._advance_time() def get_agent_count(self) -> int: """Returns the current number of agents in the queue.""" @@ -174,7 +174,7 @@ def step(self) -> None: self.steps += 1 self.time += 1 if self.model._steps < self.steps: - self.model.advance_time() + self.model._advance_time() class SimultaneousActivation(BaseScheduler): @@ -204,7 +204,7 @@ def step(self) -> None: self.steps += 1 self.time += 1 if self.model._steps < self.steps: - self.model.advance_time() + self.model._advance_time() class StagedActivation(BaseScheduler): @@ -269,7 +269,7 @@ def step(self) -> None: self.steps += 1 if self.model._steps < self.steps: - self.model.advance_time() + self.model._advance_time() class RandomActivationByType(BaseScheduler): @@ -371,7 +371,7 @@ def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None: self.steps += 1 self.time += 1 if self.model._steps < self.steps: - self.model.advance_time() + self.model._advance_time() def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None: """ @@ -498,7 +498,7 @@ def step(self) -> None: self.time = end_time self.steps += 1 if self.model._steps < self.steps: - self.model.advance_time() + self.model._advance_time() def get_next_event_time(self) -> TimeT | None: """Returns the time of the next scheduled event.""" diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index bbc05845723..c9dac611aeb 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -85,7 +85,7 @@ def get_local_model_param(self): def step(self): self.datacollector.collect(self) self.schedule.step() - self.advance_time() + self._advance_time() def test_batch_run(): diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py index 9b03efdcd2b..3ec38fc4e9c 100644 --- a/tests/test_datacollector.py +++ b/tests/test_datacollector.py @@ -89,7 +89,7 @@ def test_model_calc_comp(self, input1, input2): def step(self): self.schedule.step() self.datacollector.collect(self) - self.advance_time() + self._advance_time() class TestDataCollector(unittest.TestCase): diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py index ee5dc1d108c..6c5abf30370 100644 --- a/tests/test_lifespan.py +++ b/tests/test_lifespan.py @@ -43,7 +43,7 @@ def step(self): self.schedule.add( FiniteLifeAgent(self.next_id(), self.agent_lifetime, self) ) - self.advance_time() + self._advance_time() def run_model(self, step_count=100): for _ in range(step_count): diff --git a/tests/test_time.py b/tests/test_time.py index 669cc29044a..d37796917cb 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -96,7 +96,7 @@ def __init__(self, shuffle=False, activation=STAGED, enable_kill_other_agent=Fal def step(self): self.schedule.step() - self.advance_time() + self._advance_time() def model_stage(self): self.log.append("model_stage") diff --git a/tests/test_visualization.py b/tests/test_visualization.py index a002c687e6b..b57803004c8 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -49,7 +49,7 @@ def __init__(self, width, height, key1=103, key2=104): def step(self): self.schedule.step() - self.advance_time() + self._advance_time() class TestModularServer(TestCase): From 02cefd8c529fcfad8a8a330dd6ba480c1a15cf0d Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 02:21:44 -0500 Subject: [PATCH 07/17] Annotate model._steps --- mesa/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/model.py b/mesa/model.py index d0a8063e62c..b2b12340ad9 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -70,7 +70,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.current_id = 0 self.agents_: defaultdict[type, dict] = defaultdict(dict) - self._steps = 0 + self._steps: int = 0 self._time: TimeT = 0 # the model's clock # Warning flags for current experimental features. These make sure a warning is only printed once per model. From 3d7c3c15ab0a0d397463b8ce0f2663ccc249f1c4 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 02:23:25 -0500 Subject: [PATCH 08/17] Remove _advance_time from tests This is because schedule.step already calls _advance_time under the hood. --- tests/test_batch_run.py | 1 - tests/test_datacollector.py | 1 - tests/test_lifespan.py | 1 - tests/test_time.py | 1 - tests/test_visualization.py | 1 - 5 files changed, 5 deletions(-) diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index c9dac611aeb..1557666f34f 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -85,7 +85,6 @@ def get_local_model_param(self): def step(self): self.datacollector.collect(self) self.schedule.step() - self._advance_time() def test_batch_run(): diff --git a/tests/test_datacollector.py b/tests/test_datacollector.py index 3ec38fc4e9c..a56ca47745c 100644 --- a/tests/test_datacollector.py +++ b/tests/test_datacollector.py @@ -89,7 +89,6 @@ def test_model_calc_comp(self, input1, input2): def step(self): self.schedule.step() self.datacollector.collect(self) - self._advance_time() class TestDataCollector(unittest.TestCase): diff --git a/tests/test_lifespan.py b/tests/test_lifespan.py index 6c5abf30370..cfd60cdeb74 100644 --- a/tests/test_lifespan.py +++ b/tests/test_lifespan.py @@ -43,7 +43,6 @@ def step(self): self.schedule.add( FiniteLifeAgent(self.next_id(), self.agent_lifetime, self) ) - self._advance_time() def run_model(self, step_count=100): for _ in range(step_count): diff --git a/tests/test_time.py b/tests/test_time.py index d37796917cb..3cfc2f35078 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -96,7 +96,6 @@ def __init__(self, shuffle=False, activation=STAGED, enable_kill_other_agent=Fal def step(self): self.schedule.step() - self._advance_time() def model_stage(self): self.log.append("model_stage") diff --git a/tests/test_visualization.py b/tests/test_visualization.py index b57803004c8..ca9f0949558 100644 --- a/tests/test_visualization.py +++ b/tests/test_visualization.py @@ -49,7 +49,6 @@ def __init__(self, width, height, key1=103, key2=104): def step(self): self.schedule.step() - self._advance_time() class TestModularServer(TestCase): From cf94bc885114f5227f17a9065db3bd4fed852764 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 02:31:40 -0500 Subject: [PATCH 09/17] model: Rename _time to time_ --- mesa/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesa/model.py b/mesa/model.py index b2b12340ad9..5c006340966 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -71,7 +71,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.agents_: defaultdict[type, dict] = defaultdict(dict) self._steps: int = 0 - self._time: TimeT = 0 # the model's clock + self.time_: TimeT = 0 # the model's clock # Warning flags for current experimental features. These make sure a warning is only printed once per model. self.agentset_experimental_warning_given = False @@ -120,7 +120,7 @@ def step(self) -> None: def _advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" self._steps += 1 - self._time += deltat + self.time_ += deltat def next_id(self) -> int: """Return the next unique ID for agents, increment current_id""" From 3355104d2b4d7e96dd30744bd3e14491c79f7ad6 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 02:32:42 -0500 Subject: [PATCH 10/17] Rename _steps to steps_ --- mesa/datacollection.py | 4 ++-- mesa/model.py | 4 ++-- mesa/time.py | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mesa/datacollection.py b/mesa/datacollection.py index 5edbe587c2e..b2f984d88c0 100644 --- a/mesa/datacollection.py +++ b/mesa/datacollection.py @@ -178,7 +178,7 @@ def _record_agents(self, model): rep_funcs = self.agent_reporters.values() def get_reports(agent): - _prefix = (agent.model._steps, agent.unique_id) + _prefix = (agent.model.steps_, agent.unique_id) reports = tuple(rep(agent) for rep in rep_funcs) return _prefix + reports @@ -205,7 +205,7 @@ def collect(self, model): if self.agent_reporters: agent_records = self._record_agents(model) - self._agent_records[model._steps] = list(agent_records) + self._agent_records[model.steps_] = list(agent_records) def add_table_row(self, table_name, row, ignore_missing=False): """Add a row dictionary to a specific table. diff --git a/mesa/model.py b/mesa/model.py index 5c006340966..33084d3b321 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -70,7 +70,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.current_id = 0 self.agents_: defaultdict[type, dict] = defaultdict(dict) - self._steps: int = 0 + self.steps_: int = 0 self.time_: TimeT = 0 # the model's clock # Warning flags for current experimental features. These make sure a warning is only printed once per model. @@ -119,7 +119,7 @@ def step(self) -> None: def _advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" - self._steps += 1 + self.steps_ += 1 self.time_ += deltat def next_id(self) -> int: diff --git a/mesa/time.py b/mesa/time.py index d5bd97b32c2..dbeedd4c24a 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -114,7 +114,7 @@ def step(self) -> None: self.do_each("step") self.steps += 1 self.time += 1 - if self.model._steps < self.steps: + if self.model.steps_ < self.steps: self.model._advance_time() def get_agent_count(self) -> int: @@ -173,7 +173,7 @@ def step(self) -> None: self.do_each("step", shuffle=True) self.steps += 1 self.time += 1 - if self.model._steps < self.steps: + if self.model.steps_ < self.steps: self.model._advance_time() @@ -203,7 +203,7 @@ def step(self) -> None: self.do_each("advance") self.steps += 1 self.time += 1 - if self.model._steps < self.steps: + if self.model.steps_ < self.steps: self.model._advance_time() @@ -268,7 +268,7 @@ def step(self) -> None: self.time += self.stage_time self.steps += 1 - if self.model._steps < self.steps: + if self.model.steps_ < self.steps: self.model._advance_time() @@ -370,7 +370,7 @@ def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None: self.step_type(agent_class, shuffle_agents=shuffle_agents) self.steps += 1 self.time += 1 - if self.model._steps < self.steps: + if self.model.steps_ < self.steps: self.model._advance_time() def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None: @@ -497,7 +497,7 @@ def step(self) -> None: # After processing events, advance time by the time_step self.time = end_time self.steps += 1 - if self.model._steps < self.steps: + if self.model.steps_ < self.steps: self.model._advance_time() def get_next_event_time(self) -> TimeT | None: From 97ce7dfecee4787d77faad6361d172782339c8c5 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 03:00:33 -0500 Subject: [PATCH 11/17] Revert applying _advance_time in schedulers step --- mesa/time.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/mesa/time.py b/mesa/time.py index dbeedd4c24a..87a68949cdd 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -114,8 +114,6 @@ def step(self) -> None: self.do_each("step") self.steps += 1 self.time += 1 - if self.model.steps_ < self.steps: - self.model._advance_time() def get_agent_count(self) -> int: """Returns the current number of agents in the queue.""" @@ -173,8 +171,6 @@ def step(self) -> None: self.do_each("step", shuffle=True) self.steps += 1 self.time += 1 - if self.model.steps_ < self.steps: - self.model._advance_time() class SimultaneousActivation(BaseScheduler): @@ -203,8 +199,6 @@ def step(self) -> None: self.do_each("advance") self.steps += 1 self.time += 1 - if self.model.steps_ < self.steps: - self.model._advance_time() class StagedActivation(BaseScheduler): @@ -268,8 +262,6 @@ def step(self) -> None: self.time += self.stage_time self.steps += 1 - if self.model.steps_ < self.steps: - self.model._advance_time() class RandomActivationByType(BaseScheduler): @@ -370,8 +362,6 @@ def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None: self.step_type(agent_class, shuffle_agents=shuffle_agents) self.steps += 1 self.time += 1 - if self.model.steps_ < self.steps: - self.model._advance_time() def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None: """ @@ -497,8 +487,6 @@ def step(self) -> None: # After processing events, advance time by the time_step self.time = end_time self.steps += 1 - if self.model.steps_ < self.steps: - self.model._advance_time() def get_next_event_time(self) -> TimeT | None: """Returns the time of the next scheduled event.""" From 3663d3e567e81d10fc29e3e982d5bc6fd0a79e60 Mon Sep 17 00:00:00 2001 From: Ewout ter Hoeven Date: Mon, 22 Jan 2024 03:04:47 -0500 Subject: [PATCH 12/17] feat: Automatically call _advance_time right after model step() Solution drafted by and partially attributed to ChatGPT: https://chat.openai.com/share/d9b9c6c6-17d0-4eb9-9eae-484402bed756 --- mesa/model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mesa/model.py b/mesa/model.py index 33084d3b321..fd760bc0565 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -72,6 +72,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.steps_: int = 0 self.time_: TimeT = 0 # the model's clock + self._original_step = self.step + self.step = self._wrapped_step # Warning flags for current experimental features. These make sure a warning is only printed once per model. self.agentset_experimental_warning_given = False @@ -117,6 +119,11 @@ def run_model(self) -> None: def step(self) -> None: """A single step. Fill in here.""" + def _wrapped_step(self): + """Wrapper for the step method to include time and step updating.""" + self._original_step() + self._advance_time() + def _advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" self.steps_ += 1 From f56d34355984b9141a5768465b89d6988a97fe30 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 05:31:22 -0500 Subject: [PATCH 13/17] fix: Make sure agent removes itself in schedule.remove --- mesa/time.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mesa/time.py b/mesa/time.py index 87a68949cdd..5455ec4f926 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -106,6 +106,7 @@ def remove(self, agent: Agent) -> None: agent: An agent object. """ self._agents.remove(agent) + agent.remove() def step(self) -> None: """Execute the step of all the agents, one at a time.""" From 34c40869c491dc2e4a2c8a5fb8de14b5e2496005 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 06:00:43 -0500 Subject: [PATCH 14/17] fix: Do step() wrapping in scheduler instead of model --- mesa/model.py | 7 ------- mesa/time.py | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mesa/model.py b/mesa/model.py index fd760bc0565..33084d3b321 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -72,8 +72,6 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.steps_: int = 0 self.time_: TimeT = 0 # the model's clock - self._original_step = self.step - self.step = self._wrapped_step # Warning flags for current experimental features. These make sure a warning is only printed once per model. self.agentset_experimental_warning_given = False @@ -119,11 +117,6 @@ def run_model(self) -> None: def step(self) -> None: """A single step. Fill in here.""" - def _wrapped_step(self): - """Wrapper for the step method to include time and step updating.""" - self._original_step() - self._advance_time() - def _advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" self.steps_ += 1 diff --git a/mesa/time.py b/mesa/time.py index 5455ec4f926..7fd7a3fc60b 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -73,6 +73,8 @@ def __init__(self, model: Model, agents: Iterable[Agent] | None = None) -> None: self.model = model self.steps = 0 self.time: TimeT = 0 + self._original_step = self.step + self.step = self._wrapped_step if agents is None: agents = [] @@ -116,6 +118,11 @@ def step(self) -> None: self.steps += 1 self.time += 1 + def _wrapped_step(self): + """Wrapper for the step method to include time and step updating.""" + self._original_step() + self.model._advance_time() + def get_agent_count(self) -> int: """Returns the current number of agents in the queue.""" return len(self._agents) From 24233be158176cf3e794d650a5751097986a70a6 Mon Sep 17 00:00:00 2001 From: rht Date: Mon, 22 Jan 2024 07:27:44 -0500 Subject: [PATCH 15/17] fix: JupyterViz: replace model.steps with model.steps_ --- mesa/experimental/jupyter_viz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py index 950723e1e73..5a8dbc2b1ea 100644 --- a/mesa/experimental/jupyter_viz.py +++ b/mesa/experimental/jupyter_viz.py @@ -181,7 +181,7 @@ def on_value_play(change): def do_step(): model.step() previous_step.value = current_step.value - current_step.value = model.steps + current_step.value = model.steps_ def do_play(): model.running = True From 8e7754fd6840bd19d6d1e5b7d5046e4b06c0d8dd Mon Sep 17 00:00:00 2001 From: rht Date: Wed, 24 Jan 2024 19:14:26 -0500 Subject: [PATCH 16/17] Rename steps_ -> _steps, time_ -> _time --- mesa/datacollection.py | 4 ++-- mesa/experimental/jupyter_viz.py | 2 +- mesa/model.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mesa/datacollection.py b/mesa/datacollection.py index b2f984d88c0..5edbe587c2e 100644 --- a/mesa/datacollection.py +++ b/mesa/datacollection.py @@ -178,7 +178,7 @@ def _record_agents(self, model): rep_funcs = self.agent_reporters.values() def get_reports(agent): - _prefix = (agent.model.steps_, agent.unique_id) + _prefix = (agent.model._steps, agent.unique_id) reports = tuple(rep(agent) for rep in rep_funcs) return _prefix + reports @@ -205,7 +205,7 @@ def collect(self, model): if self.agent_reporters: agent_records = self._record_agents(model) - self._agent_records[model.steps_] = list(agent_records) + self._agent_records[model._steps] = list(agent_records) def add_table_row(self, table_name, row, ignore_missing=False): """Add a row dictionary to a specific table. diff --git a/mesa/experimental/jupyter_viz.py b/mesa/experimental/jupyter_viz.py index 5a8dbc2b1ea..eb20ca5f3bd 100644 --- a/mesa/experimental/jupyter_viz.py +++ b/mesa/experimental/jupyter_viz.py @@ -181,7 +181,7 @@ def on_value_play(change): def do_step(): model.step() previous_step.value = current_step.value - current_step.value = model.steps_ + current_step.value = model._steps def do_play(): model.running = True diff --git a/mesa/model.py b/mesa/model.py index 33084d3b321..b2b12340ad9 100644 --- a/mesa/model.py +++ b/mesa/model.py @@ -70,8 +70,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.current_id = 0 self.agents_: defaultdict[type, dict] = defaultdict(dict) - self.steps_: int = 0 - self.time_: TimeT = 0 # the model's clock + self._steps: int = 0 + self._time: TimeT = 0 # the model's clock # Warning flags for current experimental features. These make sure a warning is only printed once per model. self.agentset_experimental_warning_given = False @@ -119,8 +119,8 @@ def step(self) -> None: def _advance_time(self, deltat: TimeT = 1): """Increment the model's steps counter and clock.""" - self.steps_ += 1 - self.time_ += deltat + self._steps += 1 + self._time += deltat def next_id(self) -> int: """Return the next unique ID for agents, increment current_id""" From eae90a685049095a0a30c2e7a3ade42cc83172a5 Mon Sep 17 00:00:00 2001 From: rht Date: Fri, 26 Jan 2024 05:51:10 -0500 Subject: [PATCH 17/17] agent_records: Use model.agents only when model has no scheduler --- mesa/datacollection.py | 5 ++++- mesa/time.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mesa/datacollection.py b/mesa/datacollection.py index 5edbe587c2e..3fcd205c9c5 100644 --- a/mesa/datacollection.py +++ b/mesa/datacollection.py @@ -182,7 +182,10 @@ def get_reports(agent): reports = tuple(rep(agent) for rep in rep_funcs) return _prefix + reports - agent_records = map(get_reports, model.agents) + agent_records = map( + get_reports, + model.schedule.agents if hasattr(model, "schedule") else model.agents, + ) return agent_records def collect(self, model): diff --git a/mesa/time.py b/mesa/time.py index 7fd7a3fc60b..3dcd1708f84 100644 --- a/mesa/time.py +++ b/mesa/time.py @@ -108,7 +108,6 @@ def remove(self, agent: Agent) -> None: agent: An agent object. """ self._agents.remove(agent) - agent.remove() def step(self) -> None: """Execute the step of all the agents, one at a time."""