Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7c123aa
initial commit
quaquel Nov 18, 2025
e35ec91
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
bf7a3c4
additional warnings
quaquel Nov 18, 2025
c5e6224
shift agentset to rng
quaquel Nov 18, 2025
1305c82
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
f8c13a0
fixes for unit tests
quaquel Nov 18, 2025
6806049
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
8702cdf
Update model.py
quaquel Nov 18, 2025
fe75c58
Update util.py
quaquel Nov 18, 2025
f528b9d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
f788e0f
test fixes
quaquel Nov 18, 2025
4a4ae9a
update benchmark examples to use rng instead of seed
quaquel Nov 18, 2025
ec6d8ea
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
6402f0a
fix other examples and tests
quaquel Nov 18, 2025
930d405
Update model.py
quaquel Nov 18, 2025
831e5c5
Update test_examples_viz.py
quaquel Nov 18, 2025
6aefc7a
replace random/seed with rng in all spaces and some other places
quaquel Nov 18, 2025
08710e0
Update continuous_space.py
quaquel Nov 18, 2025
9915d9d
replace rng.choice with list[rng.integers(0, len(list)]
quaquel Nov 18, 2025
f9133ce
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 18, 2025
27b48ac
ongoing work
quaquel Nov 19, 2025
88f3999
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 19, 2025
5bdfa7e
some further tweaks to the example models
quaquel Nov 19, 2025
9c293ba
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 19, 2025
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
4 changes: 2 additions & 2 deletions benchmarks/global_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def run_model(model_class, seed, parameters):
start_init = timeit.default_timer()
if model_class.__name__ in uses_simulator:
simulator = ABMSimulator()
model = model_class(simulator=simulator, seed=seed, **parameters)
model = model_class(simulator=simulator, rng=seed, **parameters)
else:
model = model_class(seed=seed, **parameters)
model = model_class(rng=seed, **parameters)

end_init_start_run = timeit.default_timer()

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/9_batch_run.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
"version": "3.12.2"
},
"widgets": {
"state": {},
Expand Down
51 changes: 34 additions & 17 deletions mesa/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@
from typing import TYPE_CHECKING, Any, Literal, overload

import numpy as np
from numpy.random import BitGenerator, Generator, RandomState, SeedSequence

if TYPE_CHECKING:
# We ensure that these are not imported during runtime to prevent cyclic
# dependency.
from mesa.model import Model
from mesa.space import Position

from mesa.util import deprecate_kwarg

SeedLike = int | np.ndarray[int] | SeedSequence | BitGenerator | Generator | RandomState


class Agent:
"""Base class for a model agent in Mesa.
Expand Down Expand Up @@ -133,11 +138,16 @@ def __getitem__(self, i):
instance_kwargs = {k: v[i] for k, v in listlike_kwargs.items()}
agent = cls(model, *instance_args, **instance_kwargs)
agents.append(agent)
return AgentSet(agents, random=model.random)
return AgentSet(agents, rng=model.rng)

@property
def random(self) -> Random:
"""Return a seeded stdlib rng."""
warnings.warn(
"the use of random is deprecated, please use rng instead",
FutureWarning,
stacklevel=2,
)
return self.model.random

@property
Expand Down Expand Up @@ -169,32 +179,39 @@ class AgentSet(MutableSet, Sequence):

"""

@deprecate_kwarg("random")
def __init__(
self,
agents: Iterable[Agent],
random: Random | None = None,
rng: SeedLike | None = None,
):
"""Initializes the AgentSet with a collection of agents and a reference to the model.

Args:
agents (Iterable[Agent]): An iterable of Agent objects to be included in the set.
random (Random | np.random.Generator | None): the random number generator
rng (SeedLike | None): the random number generator
"""
self._agents = weakref.WeakKeyDictionary(dict.fromkeys(agents))
if (len(self._agents) == 0) and random is None:
if (len(self._agents) == 0) and (random is None and rng is None):
warnings.warn(
"No Agents specified in creation of AgentSet and no random number generator specified. "
"This can make models non-reproducible. Please pass a random number generator explicitly",
UserWarning,
stacklevel=2,
)
random = Random()
rng = np.random.default_rng()

if random is not None:
self.random = random
else:
# all agents in an AgentSet should share the same model, just take it from first
self.random = self._agents.keys().__next__().model.random
rng = np.random.default_rng(random.getstate()[1])

if rng is None:
rng = self._agents.keys().__next__().model.rng

# if rng is a np.random.Generator, it will just return it,
# if rng can be used to seed a generator, a new generator is created with this seed
self.rng = np.random.default_rng(rng)

def __len__(self) -> int:
"""Return the number of agents in the AgentSet."""
Expand Down Expand Up @@ -254,7 +271,7 @@ def agent_generator(filter_func, agent_type, at_most):

agents = agent_generator(filter_func, agent_type, at_most)

return AgentSet(agents, self.random) if not inplace else self._update(agents)
return AgentSet(agents, rng=self.rng) if not inplace else self._update(agents)

def shuffle(self, inplace: bool = False) -> AgentSet:
"""Randomly shuffle the order of agents in the AgentSet.
Expand All @@ -270,14 +287,16 @@ def shuffle(self, inplace: bool = False) -> AgentSet:

"""
weakrefs = list(self._agents.keyrefs())
self.random.shuffle(weakrefs)

self.rng.shuffle(weakrefs)

if inplace:
self._agents.data = dict.fromkeys(weakrefs)
return self
else:
return AgentSet(
(agent for ref in weakrefs if (agent := ref()) is not None), self.random
(agent for ref in weakrefs if (agent := ref()) is not None),
rng=self.rng,
)

def sort(
Expand All @@ -302,7 +321,7 @@ def sort(
sorted_agents = sorted(self._agents.keys(), key=key, reverse=not ascending)

return (
AgentSet(sorted_agents, self.random)
AgentSet(sorted_agents, rng=self.rng)
if not inplace
else self._update(sorted_agents)
)
Expand Down Expand Up @@ -348,7 +367,7 @@ def shuffle_do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
It's a fast, optimized version of calling shuffle() followed by do().
"""
weakrefs = list(self._agents.keyrefs())
self.random.shuffle(weakrefs)
self.rng.shuffle(weakrefs)

if isinstance(method, str):
for ref in weakrefs:
Expand Down Expand Up @@ -557,15 +576,15 @@ def __getstate__(self):
Returns:
dict: A dictionary representing the state of the AgentSet.
"""
return {"agents": list(self._agents.keys()), "random": self.random}
return {"agents": list(self._agents.keys()), "rng": self.rng}

def __setstate__(self, state):
"""Set the state of the AgentSet during deserialization.

Args:
state (dict): A dictionary representing the state to restore.
"""
self.random = state["random"]
self.rng = state["rng"]
self._update(state["agents"])

def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
Expand Down Expand Up @@ -599,9 +618,7 @@ def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
groups[getattr(agent, by)].append(agent)

if result_type == "agentset":
return GroupBy(
{k: AgentSet(v, random=self.random) for k, v in groups.items()}
)
return GroupBy({k: AgentSet(v, rng=self.rng) for k, v in groups.items()})
else:
return GroupBy(groups)

Expand Down
26 changes: 23 additions & 3 deletions mesa/discrete_space/cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@

from __future__ import annotations

import warnings
from functools import cache, cached_property
from random import Random
from typing import TYPE_CHECKING

import numpy as np
from numpy.random import BitGenerator, Generator, RandomState, SeedSequence

from mesa.discrete_space.cell_agent import CellAgent
from mesa.discrete_space.cell_collection import CellCollection
from mesa.util import deprecate_kwarg

if TYPE_CHECKING:
from mesa.agent import Agent

SeedLike = int | np.ndarray[int] | SeedSequence | BitGenerator | Generator | RandomState
Coordinate = tuple[int, ...]


Expand All @@ -45,21 +51,24 @@ class Cell:
"connections",
"coordinate",
"properties",
"random",
"rng",
]

@deprecate_kwarg("random")
def __init__(
self,
coordinate: Coordinate,
capacity: int | None = None,
random: Random | None = None,
rng: SeedLike | None = None,
) -> None:
"""Initialise the cell.

Args:
coordinate: coordinates of the cell
capacity (int) : the capacity of the cell. If None, the capacity is infinite
random (Random) : the random number generator to use
rng (SeedLike | None): the random number generator

"""
super().__init__()
Expand All @@ -72,7 +81,18 @@ def __init__(
self.properties: dict[
Coordinate, object
] = {} # fixme still used by voronoi mesh
self.random = random

if random is None and rng 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,
)
rng = np.random.default_rng()
if random is not None:
rng = np.random.default_rng(random.getstate()[1])

self.rng = np.random.default_rng(rng)

def connect(self, other: Cell, key: Coordinate | None = None) -> None:
"""Connects this cell to another cell.
Expand Down Expand Up @@ -173,7 +193,7 @@ def get_neighborhood(
"""
return CellCollection[Cell](
self._neighborhood(radius=radius, include_center=include_center),
random=self.random,
rng=self.rng,
)

# FIXME: Revisit caching strategy on methods
Expand Down
29 changes: 22 additions & 7 deletions mesa/discrete_space/cell_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@
from random import Random
from typing import TYPE_CHECKING, TypeVar

import numpy as np
from numpy.random import BitGenerator, Generator, RandomState, SeedSequence

from mesa.util import deprecate_kwarg

if TYPE_CHECKING:
from mesa.discrete_space.cell import Cell
from mesa.discrete_space.cell_agent import CellAgent

SeedLike = int | np.ndarray[int] | SeedSequence | BitGenerator | Generator | RandomState

T = TypeVar("T", bound="Cell")


Expand All @@ -35,7 +42,8 @@ class CellCollection[T: Cell]:
Attributes:
cells (List[Cell]): The list of cells this collection represents
agents (List[CellAgent]) : List of agents occupying the cells in this collection
random (Random) : The random number generator
random (Random) : The random number generator, deprecated
rng (Random) : The random number generator

Notes:
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
Expand All @@ -45,16 +53,19 @@ class CellCollection[T: Cell]:

"""

@deprecate_kwarg("random")
def __init__(
self,
cells: Mapping[T, list[CellAgent]] | Iterable[T],
random: Random | None = None,
rng: SeedLike | None = None,
) -> None:
"""Initialize a CellCollection.

Args:
cells: cells to add to the collection
random: a seeded random number generator.
rng (SeedLike | None): the random number generator
"""
if isinstance(cells, dict):
self._cells = cells
Expand All @@ -66,14 +77,17 @@ def __init__(
next(iter(self._cells.keys())).capacity if self._cells else None
)

if random is None:
if random is None and rng 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
rng = np.random.default_rng()
if random is not None:
rng = np.random.default_rng(random.getstate()[1])

self.rng = np.random.default_rng(rng)

def __iter__(self): # noqa
return iter(self._cells)
Expand All @@ -98,7 +112,7 @@ def agents(self) -> Iterable[CellAgent]: # noqa

def select_random_cell(self) -> T:
"""Select a random cell."""
return self.random.choice(self.cells)
return self.cells[self.rng.integers(0, len(self.cells))]

def select_random_agent(self) -> CellAgent:
"""Select a random agent.
Expand All @@ -108,7 +122,8 @@ def select_random_agent(self) -> CellAgent:


"""
return self.random.choice(list(self.agents))
agents = list(self.agents)
return agents[self.rng.integers(0, len(agents))]

def select(
self,
Expand Down Expand Up @@ -142,4 +157,4 @@ def cell_generator(filter_func, at_most):
yield cell
count += 1

return CellCollection(cell_generator(filter_func, at_most), random=self.random)
return CellCollection(cell_generator(filter_func, at_most), rng=self.rng)
Loading