Source code for experimental.cell_space.discrete_space
"""Base class for building cell-based spatial environments.
DiscreteSpace provides the core functionality needed by all cell-based spaces:
- Cell creation and tracking
- Agent-cell relationship management
- Property layer support
- Random selection capabilities
- Capacity management
This serves as the foundation for specific space implementations like grids
and networks, ensuring consistent behavior and shared functionality across
different space types. All concrete cell space implementations (grids, networks, etc.)
inherit from this class.
"""
from __future__ import annotations
import warnings
from functools import cached_property
from random import Random
from typing import Generic, TypeVar
from mesa.agent import AgentSet
from mesa.experimental.cell_space.cell import Cell
from mesa.experimental.cell_space.cell_collection import CellCollection
T = TypeVar("T", bound=Cell)
[docs]
class DiscreteSpace(Generic[T]):
"""Base class for all discrete spaces.
Attributes:
capacity (int): The capacity of the cells in the discrete space
all_cells (CellCollection): The cells composing the discrete space
random (Random): The random number generator
cell_klass (Type) : the type of cell class
empties (CellCollection) : collection of all cells that are empty
property_layers (dict[str, PropertyLayer]): the property layers of the discrete space
Notes:
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
passing a random number generator. In most cases, this will be the seeded random number
generator in the model. So, you would do `random=self.random` in a `Model` or `Agent` instance.
"""
def __init__(
self,
capacity: int | None = None,
cell_klass: type[T] = Cell,
random: Random | None = None,
):
"""Instantiate a DiscreteSpace.
Args:
capacity: capacity of cells
cell_klass: base class for all cells
random: random number generator
"""
super().__init__()
self.capacity = capacity
self._cells: dict[tuple[int, ...], T] = {}
if random is None:
warnings.warn(
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
UserWarning,
stacklevel=2,
)
random = Random()
self.random = random
self.cell_klass = cell_klass
self._empties: dict[tuple[int, ...], None] = {}
@property
def cutoff_empties(self): # noqa
return 7.953 * len(self._cells) ** 0.384
@property
def agents(self) -> AgentSet:
"""Return an AgentSet with the agents in the space."""
return AgentSet(self.all_cells.agents, random=self.random)
def _connect_cells(self): ...
def _connect_single_cell(self, cell: T): ...
[docs]
@cached_property
def all_cells(self):
"""Return all cells in space."""
return CellCollection(
{cell: cell.agents for cell in self._cells.values()}, random=self.random
)
def __iter__(self): # noqa
return iter(self._cells.values())
def __getitem__(self, key: tuple[int, ...]) -> T: # noqa: D105
return self._cells[key]
@property
def empties(self) -> CellCollection[T]:
"""Return all empty in spaces."""
return self.all_cells.select(lambda cell: cell.is_empty)
[docs]
def select_random_empty_cell(self) -> T:
"""Select random empty cell."""
return self.random.choice(list(self.empties))
def __setstate__(self, state):
"""Set the state of the discrete space and rebuild the connections."""
self.__dict__ = state
self._connect_cells()