Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,4 @@ examples/shifting_baseline/
examples/water_quota/
examples/South-China-Livehood-Evolution/
examples/yr-water-quota/
.aim
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ repos:
rev: 'v1.15.0' # Use the sha / tag you want to point at
hooks:
- id: mypy
args: [--ignore-missing-imports, --follow-imports=skip]
# Use same config as CI - read from pyproject.toml
# CI uses: uv run mypy abses/
# This ensures local and CI use identical mypy settings
- repo: https://github.com/econchick/interrogate
rev: '1.7.0' # Check for the latest version
hooks:
Expand Down
33 changes: 31 additions & 2 deletions abses/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,18 @@
from abses.human.human import BaseHuman
from abses.space.nature import BaseNature
from abses.utils.args import merge_parameters
from abses.utils.config import apply_validation, normalize_config
from abses.utils.datacollector import ABSESpyDataCollector
from abses.utils.logging import (
log_session,
logger,
setup_logger_info,
setup_model_logger,
)
from abses.utils.tracker.factory import (
create_tracker,
prepare_collector_config,
)

if TYPE_CHECKING:
from mesa.model import RNGLike, SeedLike
Expand Down Expand Up @@ -113,14 +118,22 @@ def __init__(
self._run_id: Optional[int] = run_id
# Filter out None values from kwargs for type safety
clean_kwargs = {k: v for k, v in kwargs.items() if v is not None}
self._settings = merge_parameters(parameters, **clean_kwargs)
normalized_params = normalize_config(parameters)
apply_validation(normalized_params)

self._settings = merge_parameters(normalized_params, **clean_kwargs)
self._time = TimeDriver(model=self)
self._steps: int = 0 # Initialize steps counter for type checking
self._setup_subsystems(human_class, nature_class)
self._agents_handler = _ModelAgentsContainer(
model=self, max_len=kwargs.get("max_agents", None)
)

tracker_cfg = self._settings.get("tracker", {})
tracker_backend = create_tracker(tracker_cfg, model=self)
collector_cfg = prepare_collector_config(tracker_cfg)
self.datacollector: ABSESpyDataCollector = ABSESpyDataCollector(
parameters.get("reports", {})
reports=collector_cfg, tracker=tracker_backend
)

# Call initialize on model first
Expand Down Expand Up @@ -403,6 +416,19 @@ def time(self) -> TimeDriver:
"""The time driver & controller"""
return self._time

@time.setter
def time(self, value: Any) -> None:
"""Set the time driver (no-op for safety, prevents Mesa from overwriting).

Mesa 3.4.0+ tries to set self.time = 0.0 in __init__ and self.time += 1 in step().
This setter ignores those attempts to preserve our TimeDriver.

Args:
value: Value to set (ignored, can be TimeDriver, float, or any type).
"""
# Ignore all attempts to set time - we manage it ourselves via _time
pass

@property
def params(self) -> DictConfig:
"""The global parameters of this model."""
Expand Down Expand Up @@ -458,6 +484,9 @@ def step(self) -> None:

def end(self) -> None:
"""Users can custom what to do when the model is end."""
# End tracker run if available
if self.datacollector.tracker is not None:
self.datacollector.tracker.end_run()

# def summary(self, verbose: bool = False) -> pd.DataFrame:
# """Generates a summary report of the model's current state.
Expand Down
31 changes: 31 additions & 0 deletions abses/core/time_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from functools import cached_property, total_ordering, wraps
from typing import (
TYPE_CHECKING,
Any,
Callable,
Deque,
Dict,
Expand Down Expand Up @@ -149,6 +150,36 @@ def __lt__(self, other) -> bool:
return self.dt < other_dt
raise TypeError(f"Cannot compare {type(self)} with {type(other)}.")

def __add__(self, other: Any) -> "TimeDriver":
"""Support addition with numbers (for Mesa 3.4.0+ compatibility).

Mesa 3.4.0+ tries to execute self.time += 1 in its step() wrapper.
We ignore this and return self, as our time management is independent.

Args:
other: Value to add (ignored).

Returns:
Self, unchanged.
"""
# Ignore Mesa's automatic time increment - we manage time ourselves
return self

def __iadd__(self, other: Any) -> "TimeDriver":
"""Support in-place addition with numbers (for Mesa 3.4.0+ compatibility).

Mesa 3.4.0+ tries to execute self.time += 1 in its step() wrapper.
We ignore this and return self, as our time management is independent.

Args:
other: Value to add (ignored).

Returns:
Self, unchanged.
"""
# Ignore Mesa's automatic time increment - we manage time ourselves
return self

def __deepcopy__(self, memo):
return self

Expand Down
Loading
Loading