Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Remove dependence on model.schedule, add clock to Model #1942

Merged
merged 17 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions mesa/datacollection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -180,11 +178,14 @@ 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.schedule.agents if hasattr(model, "schedule") else model.agents,
)
EwoutH marked this conversation as resolved.
Show resolved Hide resolved
return agent_records

def collect(self, model):
Expand All @@ -207,7 +208,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.
Expand Down
2 changes: 1 addition & 1 deletion mesa/experimental/jupyter_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion mesa/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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: 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

Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions mesa/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -115,6 +117,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)
Expand Down
Loading