Source code for mesa.model

"""
The model class for Mesa framework.

Core Objects: Model
"""

# Mypy; for the `|` operator purpose
# Remove this __future__ import once the oldest supported Python is 3.10
from __future__ import annotations

import itertools
import random
import warnings
from collections import defaultdict

# mypy
from typing import Any, Union

from mesa.agent import Agent, AgentSet
from mesa.datacollection import DataCollector

TimeT = Union[float, int]


[docs] class Model: """Base class for models in the Mesa ABM library. This class serves as a foundational structure for creating agent-based models. It includes the basic attributes and methods necessary for initializing and running a simulation 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. agents_: A defaultdict mapping each agent type to a dict of its instances. This private attribute is used internally to manage agents. Properties: agents: An AgentSet containing all agents in the model, generated from the _agents attribute. agent_types: A list of different agent types present in the model. Methods: get_agents_of_type: Returns an AgentSet of agents of the specified type. run_model: Runs the model's simulation until a defined end condition is reached. step: Executes a single step of the model's simulation process. next_id: Generates and returns the next unique identifier for an agent. reset_randomizer: Resets the model's random number generator with a new or existing seed. initialize_data_collector: Sets up the data collector for the model, requiring an initialized scheduler and agents. """ def __new__(cls, *args: Any, **kwargs: Any) -> Any: """Create a new model object and instantiate its RNG automatically.""" obj = object.__new__(cls) obj._seed = kwargs.get("seed") if obj._seed is None: # We explicitly specify the seed here so that we know its value in # advance. obj._seed = random.random() obj.random = random.Random(obj._seed) # TODO: Remove these 2 lines just before Mesa 3.0 obj._steps = 0 obj._time = 0 return obj def __init__(self, *args: Any, **kwargs: Any) -> None: """Create a new model. Overload this method with the actual code to start the model. Always start with super().__init__() to initialize the model object properly. """ self.running = True self.schedule = None self.current_id = 0 self.agents_: defaultdict[type, dict] = defaultdict(dict) self._steps: int = 0 self._time: TimeT = 0 # the model's clock @property def agents(self) -> AgentSet: """Provides an AgentSet of all agents in the model, combining agents from all types.""" if hasattr(self, "_agents"): return self._agents else: all_agents = itertools.chain.from_iterable(self.agents_.values()) return AgentSet(all_agents, self) @agents.setter def agents(self, agents: Any) -> None: warnings.warn( "You are trying to set model.agents. In a next release, this attribute is used " "by MESA itself so you cannot use it directly anymore." "Please adjust your code to use a different attribute name for custom agent storage", UserWarning, stacklevel=2, ) self._agents = agents @property def agent_types(self) -> list[type]: """Return a list of different agent types.""" return list(self.agents_.keys())
[docs] def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet: """Retrieves an AgentSet containing all agents of the specified type.""" return AgentSet(self.agents_[agenttype].keys(), self)
[docs] def run_model(self) -> None: """Run the model until the end condition is reached. Overload as needed. """ while self.running: self.step()
[docs] 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
[docs] def next_id(self) -> int: """Return the next unique ID for agents, increment current_id""" self.current_id += 1 return self.current_id
[docs] def reset_randomizer(self, seed: int | None = None) -> None: """Reset the model random number generator. Args: seed: A new seed for the RNG; if None, reset using the current seed """ if seed is None: seed = self._seed self.random.seed(seed) self._seed = seed
[docs] def initialize_data_collector( self, model_reporters=None, agent_reporters=None, tables=None, ) -> None: if not hasattr(self, "schedule") or self.schedule is None: raise RuntimeError( "You must initialize the scheduler (self.schedule) before initializing the data collector." ) if self.schedule.get_agent_count() == 0: raise RuntimeError( "You must add agents to the scheduler before initializing the data collector." ) self.datacollector = DataCollector( model_reporters=model_reporters, agent_reporters=agent_reporters, tables=tables, ) # Collect data for the first time during initialization. self.datacollector.collect(self)