Mesa 2.3.4__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +3 -5
- mesa/agent.py +393 -116
- mesa/batchrunner.py +58 -31
- mesa/datacollection.py +141 -30
- mesa/examples/README.md +37 -0
- mesa/examples/__init__.py +21 -0
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
- mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +164 -0
- mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
- mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
- mesa/examples/advanced/pd_grid/Readme.md +43 -0
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +50 -0
- mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
- mesa/examples/advanced/pd_grid/app.py +54 -0
- mesa/examples/advanced/pd_grid/model.py +71 -0
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
- mesa/examples/advanced/sugarscape_g1mt/app.py +62 -0
- mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
- mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
- mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +102 -0
- mesa/examples/advanced/wolf_sheep/app.py +84 -0
- mesa/examples/advanced/wolf_sheep/model.py +137 -0
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +22 -0
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +71 -0
- mesa/examples/basic/boid_flockers/app.py +58 -0
- mesa/examples/basic/boid_flockers/model.py +69 -0
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
- mesa/examples/basic/boltzmann_wealth_model/app.py +74 -0
- mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
- mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +47 -0
- mesa/examples/basic/conways_game_of_life/app.py +51 -0
- mesa/examples/basic/conways_game_of_life/model.py +31 -0
- mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
- mesa/examples/basic/schelling/Readme.md +40 -0
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +26 -0
- mesa/examples/basic/schelling/analysis.ipynb +205 -0
- mesa/examples/basic/schelling/app.py +42 -0
- mesa/examples/basic/schelling/model.py +59 -0
- mesa/examples/basic/virus_on_network/Readme.md +61 -0
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +69 -0
- mesa/examples/basic/virus_on_network/app.py +114 -0
- mesa/examples/basic/virus_on_network/model.py +96 -0
- mesa/experimental/UserParam.py +18 -7
- mesa/experimental/__init__.py +10 -2
- mesa/experimental/cell_space/__init__.py +16 -1
- mesa/experimental/cell_space/cell.py +93 -23
- mesa/experimental/cell_space/cell_agent.py +117 -21
- mesa/experimental/cell_space/cell_collection.py +56 -19
- mesa/experimental/cell_space/discrete_space.py +92 -8
- mesa/experimental/cell_space/grid.py +33 -9
- mesa/experimental/cell_space/network.py +15 -10
- mesa/experimental/cell_space/voronoi.py +257 -0
- mesa/experimental/components/altair.py +11 -2
- mesa/experimental/components/matplotlib.py +132 -26
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +54 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +57 -16
- mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -98
- mesa/model.py +212 -84
- mesa/space.py +217 -151
- mesa/time.py +63 -80
- mesa/visualization/__init__.py +25 -6
- mesa/visualization/components/__init__.py +83 -0
- mesa/visualization/components/altair_components.py +188 -0
- mesa/visualization/components/matplotlib_components.py +175 -0
- mesa/visualization/mpl_space_drawing.py +593 -0
- mesa/visualization/solara_viz.py +458 -0
- mesa/visualization/user_param.py +69 -0
- mesa/visualization/utils.py +9 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/METADATA +65 -19
- mesa-3.0.0.dist-info/RECORD +95 -0
- mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
- mesa-2.3.4.dist-info/licenses/LICENSE → mesa-3.0.0.dist-info/licenses/NOTICE +2 -2
- mesa/cookiecutter-mesa/cookiecutter.json +0 -8
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
- mesa/flat/__init__.py +0 -6
- mesa/flat/visualization.py +0 -5
- mesa/main.py +0 -63
- mesa/visualization/ModularVisualization.py +0 -1
- mesa/visualization/TextVisualization.py +0 -1
- mesa/visualization/UserParam.py +0 -1
- mesa/visualization/modules.py +0 -1
- mesa-2.3.4.dist-info/RECORD +0 -45
- /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
mesa/space.py
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Mesa Space Module
|
|
3
|
-
=================
|
|
1
|
+
"""Mesa Space Module.
|
|
4
2
|
|
|
5
3
|
Objects used to add a spatial component to a model.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
.. note::
|
|
6
|
+
All Grid classes (:class:`_Grid`, :class:`SingleGrid`, :class:`MultiGrid`,
|
|
7
|
+
:class:`HexGrid`, etc.) are now in maintenance-only mode. While these classes remain
|
|
8
|
+
fully supported, new development occurs in the experimental cell space module
|
|
9
|
+
(:mod:`mesa.experimental.cell_space`).
|
|
10
|
+
|
|
11
|
+
The :class:`PropertyLayer` and :class:`ContinuousSpace` classes remain fully supported
|
|
12
|
+
and actively developed.
|
|
13
|
+
|
|
14
|
+
Classes
|
|
15
|
+
-------
|
|
16
|
+
* PropertyLayer: A data layer that can be added to Grids to store cell properties
|
|
17
|
+
* SingleGrid: a Grid which strictly enforces one agent per cell.
|
|
18
|
+
* MultiGrid: a Grid where each cell can contain a set of agents.
|
|
19
|
+
* HexGrid: a Grid to handle hexagonal neighbors.
|
|
20
|
+
* ContinuousSpace: a two-dimensional space where each agent has an arbitrary position of `float`'s.
|
|
21
|
+
* NetworkGrid: a network where each node contains zero or more agents.
|
|
14
22
|
"""
|
|
15
23
|
|
|
16
24
|
# Mypy; for the `|` operator purpose
|
|
@@ -23,9 +31,9 @@ import inspect
|
|
|
23
31
|
import itertools
|
|
24
32
|
import math
|
|
25
33
|
import warnings
|
|
26
|
-
from collections.abc import Iterable, Iterator, Sequence
|
|
34
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
|
27
35
|
from numbers import Real
|
|
28
|
-
from typing import Any,
|
|
36
|
+
from typing import Any, TypeVar, cast, overload
|
|
29
37
|
from warnings import warn
|
|
30
38
|
|
|
31
39
|
with contextlib.suppress(ImportError):
|
|
@@ -35,28 +43,29 @@ import numpy as np
|
|
|
35
43
|
import numpy.typing as npt
|
|
36
44
|
|
|
37
45
|
# For Mypy
|
|
38
|
-
from .agent import Agent
|
|
46
|
+
from .agent import Agent, AgentSet
|
|
39
47
|
|
|
40
48
|
# for better performance, we calculate the tuple to use in the is_integer function
|
|
41
49
|
_types_integer = (int, np.integer)
|
|
42
50
|
|
|
43
51
|
Coordinate = tuple[int, int]
|
|
44
52
|
# used in ContinuousSpace
|
|
45
|
-
FloatCoordinate =
|
|
53
|
+
FloatCoordinate = tuple[float, float] | npt.NDArray[float]
|
|
46
54
|
NetworkCoordinate = int
|
|
47
55
|
|
|
48
|
-
Position =
|
|
56
|
+
Position = Coordinate | FloatCoordinate | NetworkCoordinate
|
|
49
57
|
|
|
50
|
-
GridContent =
|
|
58
|
+
GridContent = Agent | None
|
|
51
59
|
MultiGridContent = list[Agent]
|
|
52
60
|
|
|
53
61
|
F = TypeVar("F", bound=Callable[..., Any])
|
|
54
62
|
|
|
55
63
|
|
|
56
64
|
def accept_tuple_argument(wrapped_function: F) -> F:
|
|
57
|
-
"""Decorator to allow grid methods that take a list of (x, y) coord tuples
|
|
58
|
-
|
|
59
|
-
single-item list rather than forcing user to do it.
|
|
65
|
+
"""Decorator to allow grid methods that take a list of (x, y) coord tuples to also handle a single position.
|
|
66
|
+
|
|
67
|
+
Tuples are wrapped in a single-item list rather than forcing user to do it.
|
|
68
|
+
"""
|
|
60
69
|
|
|
61
70
|
def wrapper(grid_instance, positions) -> Any:
|
|
62
71
|
if len(positions) == 2 and not isinstance(positions[0], tuple):
|
|
@@ -67,11 +76,13 @@ def accept_tuple_argument(wrapped_function: F) -> F:
|
|
|
67
76
|
|
|
68
77
|
|
|
69
78
|
def is_integer(x: Real) -> bool:
|
|
70
|
-
|
|
79
|
+
"""Check if x is either a CPython integer or Numpy integer."""
|
|
71
80
|
return isinstance(x, _types_integer)
|
|
72
81
|
|
|
73
82
|
|
|
74
83
|
def warn_if_agent_has_position_already(placement_func):
|
|
84
|
+
"""Decorator to give warning if agent has position already set."""
|
|
85
|
+
|
|
75
86
|
def wrapper(self, agent, *args, **kwargs):
|
|
76
87
|
if agent.pos is not None:
|
|
77
88
|
warnings.warn(
|
|
@@ -102,7 +113,8 @@ class _Grid:
|
|
|
102
113
|
"""Create a new grid.
|
|
103
114
|
|
|
104
115
|
Args:
|
|
105
|
-
width
|
|
116
|
+
width: The grid's width.
|
|
117
|
+
height: The grid's height.
|
|
106
118
|
torus: Boolean whether the grid wraps or not.
|
|
107
119
|
"""
|
|
108
120
|
self.height = height
|
|
@@ -152,6 +164,26 @@ class _Grid:
|
|
|
152
164
|
@overload
|
|
153
165
|
def __getitem__(self, index: int | Sequence[Coordinate]) -> list[GridContent]: ...
|
|
154
166
|
|
|
167
|
+
@property
|
|
168
|
+
def agents(self) -> AgentSet:
|
|
169
|
+
"""Return an AgentSet with the agents in the space."""
|
|
170
|
+
agents = []
|
|
171
|
+
for entry in self:
|
|
172
|
+
if not entry:
|
|
173
|
+
continue
|
|
174
|
+
if not isinstance(entry, list):
|
|
175
|
+
entry = [entry] # noqa PLW2901
|
|
176
|
+
for agent in entry:
|
|
177
|
+
agents.append(agent)
|
|
178
|
+
|
|
179
|
+
# getting the rng is a bit hacky because old style spaces don't have the rng
|
|
180
|
+
try:
|
|
181
|
+
rng = agents[0].random
|
|
182
|
+
except IndexError:
|
|
183
|
+
# there are no agents in the space
|
|
184
|
+
rng = None
|
|
185
|
+
return AgentSet(agents, random=rng)
|
|
186
|
+
|
|
155
187
|
@overload
|
|
156
188
|
def __getitem__(
|
|
157
189
|
self, index: tuple[int | slice, int | slice]
|
|
@@ -159,7 +191,6 @@ class _Grid:
|
|
|
159
191
|
|
|
160
192
|
def __getitem__(self, index):
|
|
161
193
|
"""Access contents from the grid."""
|
|
162
|
-
|
|
163
194
|
if isinstance(index, int):
|
|
164
195
|
# grid[x]
|
|
165
196
|
return self._grid[index]
|
|
@@ -192,8 +223,7 @@ class _Grid:
|
|
|
192
223
|
return [cell for rows in self._grid[x] for cell in rows[y]]
|
|
193
224
|
|
|
194
225
|
def __iter__(self) -> Iterator[GridContent]:
|
|
195
|
-
"""Create an iterator that chains the rows of the grid together
|
|
196
|
-
as if it is one list:"""
|
|
226
|
+
"""Create an iterator that chains the rows of the grid together as if it is one list."""
|
|
197
227
|
return itertools.chain(*self._grid)
|
|
198
228
|
|
|
199
229
|
def coord_iter(self) -> Iterator[tuple[GridContent, Coordinate]]:
|
|
@@ -209,8 +239,7 @@ class _Grid:
|
|
|
209
239
|
include_center: bool = False,
|
|
210
240
|
radius: int = 1,
|
|
211
241
|
) -> Iterator[Coordinate]:
|
|
212
|
-
"""Return an iterator over cell coordinates that are in the
|
|
213
|
-
neighborhood of a certain point.
|
|
242
|
+
"""Return an iterator over cell coordinates that are in the neighborhood of a certain point.
|
|
214
243
|
|
|
215
244
|
Args:
|
|
216
245
|
pos: Coordinate tuple for the neighborhood to get.
|
|
@@ -237,8 +266,7 @@ class _Grid:
|
|
|
237
266
|
include_center: bool = False,
|
|
238
267
|
radius: int = 1,
|
|
239
268
|
) -> Sequence[Coordinate]:
|
|
240
|
-
"""Return a list of cells that are in the neighborhood of a
|
|
241
|
-
certain point.
|
|
269
|
+
"""Return a list of cells that are in the neighborhood of a certain point.
|
|
242
270
|
|
|
243
271
|
Args:
|
|
244
272
|
pos: Coordinate tuple for the neighborhood to get.
|
|
@@ -381,8 +409,7 @@ class _Grid:
|
|
|
381
409
|
return pos[0] % self.width, pos[1] % self.height
|
|
382
410
|
|
|
383
411
|
def out_of_bounds(self, pos: Coordinate) -> bool:
|
|
384
|
-
"""Determines whether position is off the grid, returns the out of
|
|
385
|
-
bounds coordinate."""
|
|
412
|
+
"""Determines whether position is off the grid, returns the out of bounds coordinate."""
|
|
386
413
|
x, y = pos
|
|
387
414
|
return x < 0 or x >= self.width or y < 0 or y >= self.height
|
|
388
415
|
|
|
@@ -390,8 +417,7 @@ class _Grid:
|
|
|
390
417
|
def iter_cell_list_contents(
|
|
391
418
|
self, cell_list: Iterable[Coordinate]
|
|
392
419
|
) -> Iterator[Agent]:
|
|
393
|
-
"""Returns an iterator of the agents contained in the cells identified
|
|
394
|
-
in `cell_list`; cells with empty content are excluded.
|
|
420
|
+
"""Returns an iterator of the agents contained in the cells identified in `cell_list`; cells with empty content are excluded.
|
|
395
421
|
|
|
396
422
|
Args:
|
|
397
423
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
@@ -407,8 +433,7 @@ class _Grid:
|
|
|
407
433
|
|
|
408
434
|
@accept_tuple_argument
|
|
409
435
|
def get_cell_list_contents(self, cell_list: Iterable[Coordinate]) -> list[Agent]:
|
|
410
|
-
"""Returns an iterator of the agents contained in the cells identified
|
|
411
|
-
in `cell_list`; cells with empty content are excluded.
|
|
436
|
+
"""Returns an iterator of the agents contained in the cells identified in `cell_list`; cells with empty content are excluded.
|
|
412
437
|
|
|
413
438
|
Args:
|
|
414
439
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
@@ -441,8 +466,7 @@ class _Grid:
|
|
|
441
466
|
selection: str = "random",
|
|
442
467
|
handle_empty: str | None = None,
|
|
443
468
|
) -> None:
|
|
444
|
-
"""
|
|
445
|
-
Move an agent to one of the given positions.
|
|
469
|
+
"""Move an agent to one of the given positions.
|
|
446
470
|
|
|
447
471
|
Args:
|
|
448
472
|
agent: Agent object to move. Assumed to have its current location stored in a 'pos' tuple.
|
|
@@ -459,15 +483,21 @@ class _Grid:
|
|
|
459
483
|
elif selection == "closest":
|
|
460
484
|
current_pos = agent.pos
|
|
461
485
|
# Find the closest position without sorting all positions
|
|
462
|
-
|
|
486
|
+
# TODO: See if this method can be optimized further
|
|
487
|
+
closest_pos = []
|
|
463
488
|
min_distance = float("inf")
|
|
464
489
|
agent.random.shuffle(pos)
|
|
465
490
|
for p in pos:
|
|
466
491
|
distance = self._distance_squared(p, current_pos)
|
|
467
492
|
if distance < min_distance:
|
|
468
493
|
min_distance = distance
|
|
469
|
-
closest_pos
|
|
470
|
-
|
|
494
|
+
closest_pos.clear()
|
|
495
|
+
closest_pos.append(p)
|
|
496
|
+
elif distance == min_distance:
|
|
497
|
+
closest_pos.append(p)
|
|
498
|
+
|
|
499
|
+
chosen_pos = agent.random.choice(closest_pos)
|
|
500
|
+
|
|
471
501
|
else:
|
|
472
502
|
raise ValueError(
|
|
473
503
|
f"Invalid selection method {selection}. Choose 'random' or 'closest'."
|
|
@@ -488,9 +518,7 @@ class _Grid:
|
|
|
488
518
|
)
|
|
489
519
|
|
|
490
520
|
def _distance_squared(self, pos1: Coordinate, pos2: Coordinate) -> float:
|
|
491
|
-
"""
|
|
492
|
-
Calculate the squared Euclidean distance between two points for performance.
|
|
493
|
-
"""
|
|
521
|
+
"""Calculate the squared Euclidean distance between two points for performance."""
|
|
494
522
|
# Use squared Euclidean distance to avoid sqrt operation
|
|
495
523
|
dx, dy = abs(pos1[0] - pos2[0]), abs(pos1[1] - pos2[1])
|
|
496
524
|
if self.torus:
|
|
@@ -499,7 +527,7 @@ class _Grid:
|
|
|
499
527
|
return dx**2 + dy**2
|
|
500
528
|
|
|
501
529
|
def swap_pos(self, agent_a: Agent, agent_b: Agent) -> None:
|
|
502
|
-
"""Swap agents positions"""
|
|
530
|
+
"""Swap agents positions."""
|
|
503
531
|
agents_no_pos = []
|
|
504
532
|
if (pos_a := agent_a.pos) is None:
|
|
505
533
|
agents_no_pos.append(agent_a)
|
|
@@ -560,16 +588,16 @@ def is_single_argument_function(function):
|
|
|
560
588
|
)
|
|
561
589
|
|
|
562
590
|
|
|
563
|
-
def ufunc_requires_additional_input(ufunc):
|
|
591
|
+
def ufunc_requires_additional_input(ufunc): # noqa: D103
|
|
564
592
|
# NumPy ufuncs have a 'nargs' attribute indicating the number of input arguments
|
|
565
593
|
# For binary ufuncs (like np.add), nargs is 2
|
|
566
594
|
return ufunc.nargs > 1
|
|
567
595
|
|
|
568
596
|
|
|
569
597
|
class PropertyLayer:
|
|
570
|
-
"""
|
|
571
|
-
|
|
572
|
-
can store a value of a specified data type.
|
|
598
|
+
"""A class representing a layer of properties in a two-dimensional grid.
|
|
599
|
+
|
|
600
|
+
Each cell in the grid can store a value of a specified data type.
|
|
573
601
|
|
|
574
602
|
Attributes:
|
|
575
603
|
name (str): The name of the property layer.
|
|
@@ -577,13 +605,6 @@ class PropertyLayer:
|
|
|
577
605
|
height (int): The height of the grid (number of rows).
|
|
578
606
|
data (numpy.ndarray): A NumPy array representing the grid data.
|
|
579
607
|
|
|
580
|
-
Methods:
|
|
581
|
-
set_cell(position, value): Sets the value of a single cell.
|
|
582
|
-
set_cells(value, condition=None): Sets the values of multiple cells, optionally based on a condition.
|
|
583
|
-
modify_cell(position, operation, value): Modifies the value of a single cell using an operation.
|
|
584
|
-
modify_cells(operation, value, condition_function): Modifies the values of multiple cells using an operation.
|
|
585
|
-
select_cells(condition, return_list): Selects cells that meet a specified condition.
|
|
586
|
-
aggregate_property(operation): Performs an aggregate operation over all cells.
|
|
587
608
|
"""
|
|
588
609
|
|
|
589
610
|
propertylayer_experimental_warning_given = False
|
|
@@ -591,8 +612,7 @@ class PropertyLayer:
|
|
|
591
612
|
def __init__(
|
|
592
613
|
self, name: str, width: int, height: int, default_value, dtype=np.float64
|
|
593
614
|
):
|
|
594
|
-
"""
|
|
595
|
-
Initializes a new PropertyLayer instance.
|
|
615
|
+
"""Initializes a new PropertyLayer instance.
|
|
596
616
|
|
|
597
617
|
Args:
|
|
598
618
|
name (str): The name of the property layer.
|
|
@@ -643,14 +663,11 @@ class PropertyLayer:
|
|
|
643
663
|
self.__class__.propertylayer_experimental_warning_given = True
|
|
644
664
|
|
|
645
665
|
def set_cell(self, position: Coordinate, value):
|
|
646
|
-
"""
|
|
647
|
-
Update a single cell's value in-place.
|
|
648
|
-
"""
|
|
666
|
+
"""Update a single cell's value in-place."""
|
|
649
667
|
self.data[position] = value
|
|
650
668
|
|
|
651
669
|
def set_cells(self, value, condition=None):
|
|
652
|
-
"""
|
|
653
|
-
Perform a batch update either on the entire grid or conditionally, in-place.
|
|
670
|
+
"""Perform a batch update either on the entire grid or conditionally, in-place.
|
|
654
671
|
|
|
655
672
|
Args:
|
|
656
673
|
value: The value to be used for the update.
|
|
@@ -679,8 +696,8 @@ class PropertyLayer:
|
|
|
679
696
|
np.copyto(self.data, value, where=condition_result)
|
|
680
697
|
|
|
681
698
|
def modify_cell(self, position: Coordinate, operation, value=None):
|
|
682
|
-
"""
|
|
683
|
-
|
|
699
|
+
"""Modify a single cell using an operation, which can be a lambda function or a NumPy ufunc.
|
|
700
|
+
|
|
684
701
|
If a NumPy ufunc is used, an additional value should be provided.
|
|
685
702
|
|
|
686
703
|
Args:
|
|
@@ -701,8 +718,8 @@ class PropertyLayer:
|
|
|
701
718
|
raise ValueError("Invalid operation or missing value for NumPy ufunc.")
|
|
702
719
|
|
|
703
720
|
def modify_cells(self, operation, value=None, condition_function=None):
|
|
704
|
-
"""
|
|
705
|
-
|
|
721
|
+
"""Modify cells using an operation, which can be a lambda function or a NumPy ufunc.
|
|
722
|
+
|
|
706
723
|
If a NumPy ufunc is used, an additional value should be provided.
|
|
707
724
|
|
|
708
725
|
Args:
|
|
@@ -736,8 +753,7 @@ class PropertyLayer:
|
|
|
736
753
|
self.data = np.where(condition_array, modified_data, self.data)
|
|
737
754
|
|
|
738
755
|
def select_cells(self, condition, return_list=True):
|
|
739
|
-
"""
|
|
740
|
-
Find cells that meet a specified condition using NumPy's boolean indexing, in-place.
|
|
756
|
+
"""Find cells that meet a specified condition using NumPy's boolean indexing, in-place.
|
|
741
757
|
|
|
742
758
|
Args:
|
|
743
759
|
condition: A callable that returns a boolean array when applied to the data.
|
|
@@ -762,9 +778,9 @@ class PropertyLayer:
|
|
|
762
778
|
|
|
763
779
|
|
|
764
780
|
class _PropertyGrid(_Grid):
|
|
765
|
-
"""
|
|
766
|
-
|
|
767
|
-
the representation and manipulation of additional data layers on the grid. This class is
|
|
781
|
+
"""A private subclass of _Grid that supports the addition of property layers.
|
|
782
|
+
|
|
783
|
+
This enables the representation and manipulation of additional data layers on the grid. This class is
|
|
768
784
|
intended for internal use within the Mesa framework and is currently utilized by SingleGrid
|
|
769
785
|
and MultiGrid classes to provide enhanced grid functionality.
|
|
770
786
|
|
|
@@ -777,22 +793,15 @@ class _PropertyGrid(_Grid):
|
|
|
777
793
|
properties (dict): A dictionary mapping property layer names to PropertyLayer instances.
|
|
778
794
|
empty_mask (np.ndarray): A boolean array indicating empty cells on the grid.
|
|
779
795
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
specifies the cells to be considered (True) or ignored (False) in operations. Users can create custom masks,
|
|
790
|
-
including neighborhood masks, to apply specific conditions or constraints. Additionally, methods that deal with
|
|
791
|
-
cell selection or agent movement can return either a list of cell coordinates or a mask, based on the 'return_list'
|
|
792
|
-
parameter. This flexibility allows for more nuanced control and customization of grid operations, catering to a wide
|
|
793
|
-
range of modeling requirements and scenarios.
|
|
794
|
-
|
|
795
|
-
Note:
|
|
796
|
+
|
|
797
|
+
Several methods in this class accept a mask as an input, which is a NumPy ndarray of boolean values. This mask
|
|
798
|
+
specifies the cells to be considered (True) or ignored (False) in operations. Users can create custom masks,
|
|
799
|
+
including neighborhood masks, to apply specific conditions or constraints. Additionally, methods that deal with
|
|
800
|
+
cell selection or agent movement can return either a list of cell coordinates or a mask, based on the 'return_list'
|
|
801
|
+
parameter. This flexibility allows for more nuanced control and customization of grid operations, catering to a wide
|
|
802
|
+
range of modeling requirements and scenarios.
|
|
803
|
+
|
|
804
|
+
Notes:
|
|
796
805
|
This class is not intended for direct use in user models but is currently used by the SingleGrid and MultiGrid.
|
|
797
806
|
"""
|
|
798
807
|
|
|
@@ -803,8 +812,7 @@ class _PropertyGrid(_Grid):
|
|
|
803
812
|
torus: bool,
|
|
804
813
|
property_layers: None | PropertyLayer | list[PropertyLayer] = None,
|
|
805
814
|
):
|
|
806
|
-
"""
|
|
807
|
-
Initializes a new _PropertyGrid instance with specified dimensions and optional property layers.
|
|
815
|
+
"""Initializes a new _PropertyGrid instance with specified dimensions and optional property layers.
|
|
808
816
|
|
|
809
817
|
Args:
|
|
810
818
|
width (int): The width of the grid (number of columns).
|
|
@@ -833,15 +841,12 @@ class _PropertyGrid(_Grid):
|
|
|
833
841
|
|
|
834
842
|
@property
|
|
835
843
|
def empty_mask(self) -> np.ndarray:
|
|
836
|
-
"""
|
|
837
|
-
Returns a boolean mask indicating empty cells on the grid.
|
|
838
|
-
"""
|
|
844
|
+
"""Returns a boolean mask indicating empty cells on the grid."""
|
|
839
845
|
return self._empty_mask
|
|
840
846
|
|
|
841
847
|
# Add and remove properties to the grid
|
|
842
848
|
def add_property_layer(self, property_layer: PropertyLayer):
|
|
843
|
-
"""
|
|
844
|
-
Adds a new property layer to the grid.
|
|
849
|
+
"""Adds a new property layer to the grid.
|
|
845
850
|
|
|
846
851
|
Args:
|
|
847
852
|
property_layer (PropertyLayer): The PropertyLayer instance to be added to the grid.
|
|
@@ -859,8 +864,7 @@ class _PropertyGrid(_Grid):
|
|
|
859
864
|
self.properties[property_layer.name] = property_layer
|
|
860
865
|
|
|
861
866
|
def remove_property_layer(self, property_name: str):
|
|
862
|
-
"""
|
|
863
|
-
Removes a property layer from the grid by its name.
|
|
867
|
+
"""Removes a property layer from the grid by its name.
|
|
864
868
|
|
|
865
869
|
Args:
|
|
866
870
|
property_name (str): The name of the property layer to be removed.
|
|
@@ -875,8 +879,8 @@ class _PropertyGrid(_Grid):
|
|
|
875
879
|
def get_neighborhood_mask(
|
|
876
880
|
self, pos: Coordinate, moore: bool, include_center: bool, radius: int
|
|
877
881
|
) -> np.ndarray:
|
|
878
|
-
"""
|
|
879
|
-
|
|
882
|
+
"""Generate a boolean mask representing the neighborhood.
|
|
883
|
+
|
|
880
884
|
Helper method for select_cells_multi_properties() and move_agent_to_random_cell()
|
|
881
885
|
|
|
882
886
|
Args:
|
|
@@ -904,8 +908,7 @@ class _PropertyGrid(_Grid):
|
|
|
904
908
|
only_empty: bool = False,
|
|
905
909
|
return_list: bool = True,
|
|
906
910
|
) -> list[Coordinate] | np.ndarray:
|
|
907
|
-
"""
|
|
908
|
-
Select cells based on property conditions, extreme values, and/or masks, with an option to only select empty cells.
|
|
911
|
+
"""Select cells based on property conditions, extreme values, and/or masks, with an option to only select empty cells.
|
|
909
912
|
|
|
910
913
|
Args:
|
|
911
914
|
conditions (dict): A dictionary where keys are property names and values are callables that return a boolean when applied.
|
|
@@ -1058,7 +1061,7 @@ class MultiGrid(_PropertyGrid):
|
|
|
1058
1061
|
self._empty_mask[agent.pos] = False
|
|
1059
1062
|
agent.pos = None
|
|
1060
1063
|
|
|
1061
|
-
def iter_neighbors(
|
|
1064
|
+
def iter_neighbors( # noqa: D102
|
|
1062
1065
|
self,
|
|
1063
1066
|
pos: Coordinate,
|
|
1064
1067
|
moore: bool,
|
|
@@ -1073,8 +1076,9 @@ class MultiGrid(_PropertyGrid):
|
|
|
1073
1076
|
def iter_cell_list_contents(
|
|
1074
1077
|
self, cell_list: Iterable[Coordinate]
|
|
1075
1078
|
) -> Iterator[Agent]:
|
|
1076
|
-
"""Returns an iterator of the agents contained in the cells identified
|
|
1077
|
-
|
|
1079
|
+
"""Returns an iterator of the agents contained in the cells identified in `cell_list`.
|
|
1080
|
+
|
|
1081
|
+
Cells with empty content are excluded.
|
|
1078
1082
|
|
|
1079
1083
|
Args:
|
|
1080
1084
|
cell_list: Array-like of (x, y) tuples, or single tuple.
|
|
@@ -1112,9 +1116,9 @@ class _HexGrid:
|
|
|
1112
1116
|
def get_neighborhood(
|
|
1113
1117
|
self, pos: Coordinate, include_center: bool = False, radius: int = 1
|
|
1114
1118
|
) -> list[Coordinate]:
|
|
1115
|
-
"""Return a list of coordinates that are in the
|
|
1116
|
-
|
|
1117
|
-
for a HexGrid the parity of the x coordinate of the point is
|
|
1119
|
+
"""Return a list of coordinates that are in the neighborhood of a certain point.
|
|
1120
|
+
|
|
1121
|
+
To calculate the neighborhood for a HexGrid the parity of the x coordinate of the point is
|
|
1118
1122
|
important, the neighborhood can be sketched as:
|
|
1119
1123
|
|
|
1120
1124
|
Always: (0,-), (0,+)
|
|
@@ -1200,8 +1204,7 @@ class _HexGrid:
|
|
|
1200
1204
|
def iter_neighborhood(
|
|
1201
1205
|
self, pos: Coordinate, include_center: bool = False, radius: int = 1
|
|
1202
1206
|
) -> Iterator[Coordinate]:
|
|
1203
|
-
"""Return an iterator over cell coordinates that are in the
|
|
1204
|
-
neighborhood of a certain point.
|
|
1207
|
+
"""Return an iterator over cell coordinates that are in the neighborhood of a certain point.
|
|
1205
1208
|
|
|
1206
1209
|
Args:
|
|
1207
1210
|
pos: Coordinate tuple for the neighborhood to get.
|
|
@@ -1251,8 +1254,7 @@ class _HexGrid:
|
|
|
1251
1254
|
|
|
1252
1255
|
|
|
1253
1256
|
class HexSingleGrid(_HexGrid, SingleGrid):
|
|
1254
|
-
"""Hexagonal SingleGrid: a SingleGrid where neighbors are computed
|
|
1255
|
-
according to a hexagonal tiling of the grid.
|
|
1257
|
+
"""Hexagonal SingleGrid: a SingleGrid where neighbors are computed according to a hexagonal tiling of the grid.
|
|
1256
1258
|
|
|
1257
1259
|
Functions according to odd-q rules.
|
|
1258
1260
|
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
@@ -1268,8 +1270,7 @@ class HexSingleGrid(_HexGrid, SingleGrid):
|
|
|
1268
1270
|
|
|
1269
1271
|
|
|
1270
1272
|
class HexMultiGrid(_HexGrid, MultiGrid):
|
|
1271
|
-
"""Hexagonal MultiGrid: a MultiGrid where neighbors are computed
|
|
1272
|
-
according to a hexagonal tiling of the grid.
|
|
1273
|
+
"""Hexagonal MultiGrid: a MultiGrid where neighbors are computed according to a hexagonal tiling of the grid.
|
|
1273
1274
|
|
|
1274
1275
|
Functions according to odd-q rules.
|
|
1275
1276
|
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
@@ -1286,30 +1287,6 @@ class HexMultiGrid(_HexGrid, MultiGrid):
|
|
|
1286
1287
|
"""
|
|
1287
1288
|
|
|
1288
1289
|
|
|
1289
|
-
class HexGrid(HexSingleGrid):
|
|
1290
|
-
"""Hexagonal Grid: a Grid where neighbors are computed
|
|
1291
|
-
according to a hexagonal tiling of the grid.
|
|
1292
|
-
|
|
1293
|
-
Functions according to odd-q rules.
|
|
1294
|
-
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
1295
|
-
|
|
1296
|
-
Properties:
|
|
1297
|
-
width, height: The grid's width and height.
|
|
1298
|
-
torus: Boolean which determines whether to treat the grid as a torus.
|
|
1299
|
-
"""
|
|
1300
|
-
|
|
1301
|
-
def __init__(self, width: int, height: int, torus: bool) -> None:
|
|
1302
|
-
super().__init__(width, height, torus)
|
|
1303
|
-
warn(
|
|
1304
|
-
(
|
|
1305
|
-
"HexGrid is being deprecated; use instead HexSingleGrid or HexMultiGrid "
|
|
1306
|
-
"depending on your use case."
|
|
1307
|
-
),
|
|
1308
|
-
DeprecationWarning,
|
|
1309
|
-
stacklevel=2,
|
|
1310
|
-
)
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
1290
|
class ContinuousSpace:
|
|
1314
1291
|
"""Continuous space where each agent can have an arbitrary position.
|
|
1315
1292
|
|
|
@@ -1335,11 +1312,13 @@ class ContinuousSpace:
|
|
|
1335
1312
|
"""Create a new continuous space.
|
|
1336
1313
|
|
|
1337
1314
|
Args:
|
|
1338
|
-
x_max
|
|
1315
|
+
x_max: the maximum x-coordinate
|
|
1316
|
+
y_max: the maximum y-coordinate.
|
|
1339
1317
|
torus: Boolean for whether the edges loop around.
|
|
1340
|
-
x_min
|
|
1341
|
-
|
|
1342
|
-
|
|
1318
|
+
x_min: (default 0) If provided, set the minimum x -coordinate for the space. Below them, values loop to
|
|
1319
|
+
the other edge (if torus=True) or raise an exception.
|
|
1320
|
+
y_min: (default 0) If provided, set the minimum y -coordinate for the space. Below them, values loop to
|
|
1321
|
+
the other edge (if torus=True) or raise an exception.
|
|
1343
1322
|
"""
|
|
1344
1323
|
self.x_min = x_min
|
|
1345
1324
|
self.x_max = x_max
|
|
@@ -1355,6 +1334,19 @@ class ContinuousSpace:
|
|
|
1355
1334
|
self._index_to_agent: dict[int, Agent] = {}
|
|
1356
1335
|
self._agent_to_index: dict[Agent, int | None] = {}
|
|
1357
1336
|
|
|
1337
|
+
@property
|
|
1338
|
+
def agents(self) -> AgentSet:
|
|
1339
|
+
"""Return an AgentSet with the agents in the space."""
|
|
1340
|
+
agents = list(self._agent_to_index)
|
|
1341
|
+
|
|
1342
|
+
# getting the rng is a bit hacky because old style spaces don't have the rng
|
|
1343
|
+
try:
|
|
1344
|
+
rng = agents[0].random
|
|
1345
|
+
except IndexError:
|
|
1346
|
+
# there are no agents in the space
|
|
1347
|
+
rng = None
|
|
1348
|
+
return AgentSet(agents, random=rng)
|
|
1349
|
+
|
|
1358
1350
|
def _build_agent_cache(self):
|
|
1359
1351
|
"""Cache agents positions to speed up neighbors calculations."""
|
|
1360
1352
|
self._index_to_agent = {}
|
|
@@ -1442,11 +1434,13 @@ class ContinuousSpace:
|
|
|
1442
1434
|
self, pos_1: FloatCoordinate, pos_2: FloatCoordinate
|
|
1443
1435
|
) -> FloatCoordinate:
|
|
1444
1436
|
"""Get the heading vector between two points, accounting for toroidal space.
|
|
1437
|
+
|
|
1445
1438
|
It is possible to calculate the heading angle by applying the atan2 function to the
|
|
1446
1439
|
result.
|
|
1447
1440
|
|
|
1448
1441
|
Args:
|
|
1449
|
-
pos_1
|
|
1442
|
+
pos_1: Coordinate tuples for both points.
|
|
1443
|
+
pos_2: Coordinate tuples for both points.
|
|
1450
1444
|
"""
|
|
1451
1445
|
one = np.array(pos_1)
|
|
1452
1446
|
two = np.array(pos_2)
|
|
@@ -1472,7 +1466,8 @@ class ContinuousSpace:
|
|
|
1472
1466
|
"""Get the distance between two point, accounting for toroidal space.
|
|
1473
1467
|
|
|
1474
1468
|
Args:
|
|
1475
|
-
pos_1
|
|
1469
|
+
pos_1: Coordinate tuples for point1.
|
|
1470
|
+
pos_2: Coordinate tuples for point2.
|
|
1476
1471
|
"""
|
|
1477
1472
|
x1, y1 = pos_1
|
|
1478
1473
|
x2, y2 = pos_2
|
|
@@ -1519,12 +1514,33 @@ class NetworkGrid:
|
|
|
1519
1514
|
"""Create a new network.
|
|
1520
1515
|
|
|
1521
1516
|
Args:
|
|
1522
|
-
|
|
1517
|
+
g: a NetworkX graph instance.
|
|
1523
1518
|
"""
|
|
1524
1519
|
self.G = g
|
|
1525
1520
|
for node_id in self.G.nodes:
|
|
1526
1521
|
g.nodes[node_id]["agent"] = self.default_val()
|
|
1527
1522
|
|
|
1523
|
+
@property
|
|
1524
|
+
def agents(self) -> AgentSet:
|
|
1525
|
+
"""Return an AgentSet with the agents in the space."""
|
|
1526
|
+
agents = []
|
|
1527
|
+
for node_id in self.G.nodes:
|
|
1528
|
+
entry = self.G.nodes[node_id]["agent"]
|
|
1529
|
+
if not entry:
|
|
1530
|
+
continue
|
|
1531
|
+
if not isinstance(entry, list):
|
|
1532
|
+
entry = [entry]
|
|
1533
|
+
for agent in entry:
|
|
1534
|
+
agents.append(agent)
|
|
1535
|
+
|
|
1536
|
+
# getting the rng is a bit hacky because old style spaces don't have the rng
|
|
1537
|
+
try:
|
|
1538
|
+
rng = agents[0].random
|
|
1539
|
+
except IndexError:
|
|
1540
|
+
# there are no agents in the space
|
|
1541
|
+
rng = None
|
|
1542
|
+
return AgentSet(agents, random=rng)
|
|
1543
|
+
|
|
1528
1544
|
@staticmethod
|
|
1529
1545
|
def default_val() -> list:
|
|
1530
1546
|
"""Default value for a new node."""
|
|
@@ -1539,7 +1555,16 @@ class NetworkGrid:
|
|
|
1539
1555
|
def get_neighborhood(
|
|
1540
1556
|
self, node_id: int, include_center: bool = False, radius: int = 1
|
|
1541
1557
|
) -> list[int]:
|
|
1542
|
-
"""Get all adjacent nodes within a certain radius
|
|
1558
|
+
"""Get all adjacent nodes within a certain radius.
|
|
1559
|
+
|
|
1560
|
+
Args:
|
|
1561
|
+
node_id: node id for which to get neighborhood
|
|
1562
|
+
include_center: boolean to include node itself or not
|
|
1563
|
+
radius: size of neighborhood
|
|
1564
|
+
|
|
1565
|
+
Returns:
|
|
1566
|
+
a list
|
|
1567
|
+
"""
|
|
1543
1568
|
if radius == 1:
|
|
1544
1569
|
neighborhood = list(self.G.neighbors(node_id))
|
|
1545
1570
|
if include_center:
|
|
@@ -1556,28 +1581,61 @@ class NetworkGrid:
|
|
|
1556
1581
|
def get_neighbors(
|
|
1557
1582
|
self, node_id: int, include_center: bool = False, radius: int = 1
|
|
1558
1583
|
) -> list[Agent]:
|
|
1559
|
-
"""Get all agents in adjacent nodes (within a certain radius).
|
|
1584
|
+
"""Get all agents in adjacent nodes (within a certain radius).
|
|
1585
|
+
|
|
1586
|
+
Args:
|
|
1587
|
+
node_id: node id for which to get neighbors
|
|
1588
|
+
include_center: whether to include node itself or not
|
|
1589
|
+
radius: size of neighborhood in which to find neighbors
|
|
1590
|
+
|
|
1591
|
+
Returns:
|
|
1592
|
+
list of agents in neighborhood.
|
|
1593
|
+
"""
|
|
1560
1594
|
neighborhood = self.get_neighborhood(node_id, include_center, radius)
|
|
1561
1595
|
return self.get_cell_list_contents(neighborhood)
|
|
1562
1596
|
|
|
1563
1597
|
def move_agent(self, agent: Agent, node_id: int) -> None:
|
|
1564
|
-
"""Move an agent from its current node to a new node.
|
|
1598
|
+
"""Move an agent from its current node to a new node.
|
|
1599
|
+
|
|
1600
|
+
Args:
|
|
1601
|
+
agent: agent instance
|
|
1602
|
+
node_id: id of node
|
|
1603
|
+
|
|
1604
|
+
"""
|
|
1565
1605
|
self.remove_agent(agent)
|
|
1566
1606
|
self.place_agent(agent, node_id)
|
|
1567
1607
|
|
|
1568
1608
|
def remove_agent(self, agent: Agent) -> None:
|
|
1569
|
-
"""Remove the agent from the network and set its pos attribute to None.
|
|
1609
|
+
"""Remove the agent from the network and set its pos attribute to None.
|
|
1610
|
+
|
|
1611
|
+
Args:
|
|
1612
|
+
agent: agent instance
|
|
1613
|
+
|
|
1614
|
+
"""
|
|
1570
1615
|
node_id = agent.pos
|
|
1571
1616
|
self.G.nodes[node_id]["agent"].remove(agent)
|
|
1572
1617
|
agent.pos = None
|
|
1573
1618
|
|
|
1574
1619
|
def is_cell_empty(self, node_id: int) -> bool:
|
|
1575
|
-
"""Returns a bool of the contents of a cell.
|
|
1620
|
+
"""Returns a bool of the contents of a cell.
|
|
1621
|
+
|
|
1622
|
+
Args:
|
|
1623
|
+
node_id: id of node
|
|
1624
|
+
|
|
1625
|
+
"""
|
|
1576
1626
|
return self.G.nodes[node_id]["agent"] == self.default_val()
|
|
1577
1627
|
|
|
1578
1628
|
def get_cell_list_contents(self, cell_list: list[int]) -> list[Agent]:
|
|
1579
|
-
"""Returns a list of the agents contained in the nodes identified
|
|
1580
|
-
|
|
1629
|
+
"""Returns a list of the agents contained in the nodes identified in `cell_list`.
|
|
1630
|
+
|
|
1631
|
+
Nodes with empty content are excluded.
|
|
1632
|
+
|
|
1633
|
+
Args:
|
|
1634
|
+
cell_list: list of cell ids.
|
|
1635
|
+
|
|
1636
|
+
Returns:
|
|
1637
|
+
list of the agents contained in the nodes identified in `cell_list`.
|
|
1638
|
+
|
|
1581
1639
|
"""
|
|
1582
1640
|
return list(self.iter_cell_list_contents(cell_list))
|
|
1583
1641
|
|
|
@@ -1586,8 +1644,16 @@ class NetworkGrid:
|
|
|
1586
1644
|
return self.get_cell_list_contents(self.G)
|
|
1587
1645
|
|
|
1588
1646
|
def iter_cell_list_contents(self, cell_list: list[int]) -> Iterator[Agent]:
|
|
1589
|
-
"""Returns an iterator of the agents contained in the nodes identified
|
|
1590
|
-
|
|
1647
|
+
"""Returns an iterator of the agents contained in the nodes identified in `cell_list`.
|
|
1648
|
+
|
|
1649
|
+
Nodes with empty content are excluded.
|
|
1650
|
+
|
|
1651
|
+
Args:
|
|
1652
|
+
cell_list: list of cell ids.
|
|
1653
|
+
|
|
1654
|
+
Returns:
|
|
1655
|
+
iterator of the agents contained in the nodes identified in `cell_list`.
|
|
1656
|
+
|
|
1591
1657
|
"""
|
|
1592
1658
|
return itertools.chain.from_iterable(
|
|
1593
1659
|
self.G.nodes[node_id]["agent"]
|