Mesa 3.0.0__py3-none-any.whl → 3.0.0a0__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 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
- mesa-3.0.0a0.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/solara_viz.py +0 -458
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
"""The Cell in a cell space."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
|
-
from
|
|
6
|
-
from functools import cache, cached_property
|
|
3
|
+
from functools import cache
|
|
7
4
|
from random import Random
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
9
6
|
|
|
10
|
-
from mesa.experimental.cell_space.cell_agent import CellAgent
|
|
11
7
|
from mesa.experimental.cell_space.cell_collection import CellCollection
|
|
12
|
-
from mesa.space import PropertyLayer
|
|
13
8
|
|
|
14
9
|
if TYPE_CHECKING:
|
|
15
|
-
from mesa.
|
|
16
|
-
|
|
17
|
-
Coordinate = tuple[int, ...]
|
|
10
|
+
from mesa.experimental.cell_space.cell_agent import CellAgent
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
class Cell:
|
|
@@ -31,13 +24,11 @@ class Cell:
|
|
|
31
24
|
|
|
32
25
|
__slots__ = [
|
|
33
26
|
"coordinate",
|
|
34
|
-
"
|
|
27
|
+
"_connections",
|
|
35
28
|
"agents",
|
|
36
29
|
"capacity",
|
|
37
30
|
"properties",
|
|
38
31
|
"random",
|
|
39
|
-
"_mesa_property_layers",
|
|
40
|
-
"__dict__",
|
|
41
32
|
]
|
|
42
33
|
|
|
43
34
|
# def __new__(cls,
|
|
@@ -51,40 +42,34 @@ class Cell:
|
|
|
51
42
|
|
|
52
43
|
def __init__(
|
|
53
44
|
self,
|
|
54
|
-
coordinate:
|
|
55
|
-
capacity:
|
|
45
|
+
coordinate: tuple[int, ...],
|
|
46
|
+
capacity: float | None = None,
|
|
56
47
|
random: Random | None = None,
|
|
57
48
|
) -> None:
|
|
58
|
-
"""
|
|
49
|
+
""" "
|
|
59
50
|
|
|
60
51
|
Args:
|
|
61
|
-
coordinate:
|
|
52
|
+
coordinate:
|
|
62
53
|
capacity (int) : the capacity of the cell. If None, the capacity is infinite
|
|
63
54
|
random (Random) : the random number generator to use
|
|
64
55
|
|
|
65
56
|
"""
|
|
66
57
|
super().__init__()
|
|
67
58
|
self.coordinate = coordinate
|
|
68
|
-
self.
|
|
69
|
-
self.agents
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.capacity: int | None = capacity
|
|
73
|
-
self.properties: dict[Coordinate, object] = {}
|
|
59
|
+
self._connections: list[Cell] = [] # TODO: change to CellCollection?
|
|
60
|
+
self.agents = [] # TODO:: change to AgentSet or weakrefs? (neither is very performant, )
|
|
61
|
+
self.capacity = capacity
|
|
62
|
+
self.properties: dict[str, object] = {}
|
|
74
63
|
self.random = random
|
|
75
|
-
self._mesa_property_layers: dict[str, PropertyLayer] = {}
|
|
76
64
|
|
|
77
|
-
def connect(self, other: Cell
|
|
65
|
+
def connect(self, other: Cell) -> None:
|
|
78
66
|
"""Connects this cell to another cell.
|
|
79
67
|
|
|
80
68
|
Args:
|
|
81
69
|
other (Cell): other cell to connect to
|
|
82
|
-
key (Tuple[int, ...]): key for the connection. Should resemble a relative coordinate
|
|
83
70
|
|
|
84
71
|
"""
|
|
85
|
-
|
|
86
|
-
key = other.coordinate
|
|
87
|
-
self.connections[key] = other
|
|
72
|
+
self._connections.append(other)
|
|
88
73
|
|
|
89
74
|
def disconnect(self, other: Cell) -> None:
|
|
90
75
|
"""Disconnects this cell from another cell.
|
|
@@ -93,9 +78,7 @@ class Cell:
|
|
|
93
78
|
other (Cell): other cell to remove from connections
|
|
94
79
|
|
|
95
80
|
"""
|
|
96
|
-
|
|
97
|
-
for key in keys_to_remove:
|
|
98
|
-
del self.connections[key]
|
|
81
|
+
self._connections.remove(other)
|
|
99
82
|
|
|
100
83
|
def add_agent(self, agent: CellAgent) -> None:
|
|
101
84
|
"""Adds an agent to the cell.
|
|
@@ -121,6 +104,7 @@ class Cell:
|
|
|
121
104
|
|
|
122
105
|
"""
|
|
123
106
|
self.agents.remove(agent)
|
|
107
|
+
agent.cell = None
|
|
124
108
|
|
|
125
109
|
@property
|
|
126
110
|
def is_empty(self) -> bool:
|
|
@@ -132,91 +116,37 @@ class Cell:
|
|
|
132
116
|
"""Returns a bool of the contents of a cell."""
|
|
133
117
|
return len(self.agents) == self.capacity
|
|
134
118
|
|
|
135
|
-
def __repr__(self):
|
|
119
|
+
def __repr__(self):
|
|
136
120
|
return f"Cell({self.coordinate}, {self.agents})"
|
|
137
121
|
|
|
138
|
-
@cached_property
|
|
139
|
-
def neighborhood(self) -> CellCollection[Cell]:
|
|
140
|
-
"""Returns the direct neighborhood of the cell.
|
|
141
|
-
|
|
142
|
-
This is equivalent to cell.get_neighborhood(radius=1)
|
|
143
|
-
|
|
144
|
-
"""
|
|
145
|
-
return self.get_neighborhood()
|
|
146
|
-
|
|
147
122
|
# FIXME: Revisit caching strategy on methods
|
|
148
123
|
@cache # noqa: B019
|
|
149
|
-
def
|
|
150
|
-
|
|
151
|
-
) -> CellCollection[Cell]:
|
|
152
|
-
"""Returns a list of all neighboring cells for the given radius.
|
|
153
|
-
|
|
154
|
-
For getting the direct neighborhood (i.e., radius=1) you can also use
|
|
155
|
-
the `neighborhood` property.
|
|
156
|
-
|
|
157
|
-
Args:
|
|
158
|
-
radius (int): the radius of the neighborhood
|
|
159
|
-
include_center (bool): include the center of the neighborhood
|
|
160
|
-
|
|
161
|
-
Returns:
|
|
162
|
-
a list of all neighboring cells
|
|
163
|
-
|
|
164
|
-
"""
|
|
165
|
-
return CellCollection[Cell](
|
|
124
|
+
def neighborhood(self, radius=1, include_center=False):
|
|
125
|
+
return CellCollection(
|
|
166
126
|
self._neighborhood(radius=radius, include_center=include_center),
|
|
167
127
|
random=self.random,
|
|
168
128
|
)
|
|
169
129
|
|
|
170
130
|
# FIXME: Revisit caching strategy on methods
|
|
171
131
|
@cache # noqa: B019
|
|
172
|
-
def _neighborhood(
|
|
173
|
-
self, radius: int = 1, include_center: bool = False
|
|
174
|
-
) -> dict[Cell, list[Agent]]:
|
|
132
|
+
def _neighborhood(self, radius=1, include_center=False):
|
|
175
133
|
# if radius == 0:
|
|
176
134
|
# return {self: self.agents}
|
|
177
135
|
if radius < 1:
|
|
178
136
|
raise ValueError("radius must be larger than one")
|
|
179
137
|
if radius == 1:
|
|
180
|
-
neighborhood = {
|
|
181
|
-
neighbor: neighbor.agents for neighbor in self.connections.values()
|
|
182
|
-
}
|
|
138
|
+
neighborhood = {neighbor: neighbor.agents for neighbor in self._connections}
|
|
183
139
|
if not include_center:
|
|
184
140
|
return neighborhood
|
|
185
141
|
else:
|
|
186
142
|
neighborhood[self] = self.agents
|
|
187
143
|
return neighborhood
|
|
188
144
|
else:
|
|
189
|
-
neighborhood
|
|
190
|
-
for neighbor in self.
|
|
145
|
+
neighborhood = {}
|
|
146
|
+
for neighbor in self._connections:
|
|
191
147
|
neighborhood.update(
|
|
192
148
|
neighbor._neighborhood(radius - 1, include_center=True)
|
|
193
149
|
)
|
|
194
150
|
if not include_center:
|
|
195
151
|
neighborhood.pop(self, None)
|
|
196
152
|
return neighborhood
|
|
197
|
-
|
|
198
|
-
# PropertyLayer methods
|
|
199
|
-
def get_property(self, property_name: str) -> Any:
|
|
200
|
-
"""Get the value of a property."""
|
|
201
|
-
return self._mesa_property_layers[property_name].data[self.coordinate]
|
|
202
|
-
|
|
203
|
-
def set_property(self, property_name: str, value: Any):
|
|
204
|
-
"""Set the value of a property."""
|
|
205
|
-
self._mesa_property_layers[property_name].set_cell(self.coordinate, value)
|
|
206
|
-
|
|
207
|
-
def modify_property(
|
|
208
|
-
self, property_name: str, operation: Callable, value: Any = None
|
|
209
|
-
):
|
|
210
|
-
"""Modify the value of a property."""
|
|
211
|
-
self._mesa_property_layers[property_name].modify_cell(
|
|
212
|
-
self.coordinate, operation, value
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
def __getstate__(self):
|
|
216
|
-
"""Return state of the Cell with connections set to empty."""
|
|
217
|
-
# fixme, once we shift to 3.11, replace this with super. __getstate__
|
|
218
|
-
state = (self.__dict__, {k: getattr(self, k) for k in self.__slots__})
|
|
219
|
-
state[1][
|
|
220
|
-
"connections"
|
|
221
|
-
] = {} # replace this with empty connections to avoid infinite recursion error in pickle/deepcopy
|
|
222
|
-
return state
|
|
@@ -1,133 +1,37 @@
|
|
|
1
|
-
"""An agent with movement methods for cell spaces."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
6
4
|
|
|
7
|
-
from mesa
|
|
5
|
+
from mesa import Agent, Model
|
|
8
6
|
|
|
9
7
|
if TYPE_CHECKING:
|
|
10
|
-
from mesa.experimental.cell_space import Cell
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class HasCellProtocol(Protocol):
|
|
14
|
-
"""Protocol for discrete space cell holders."""
|
|
15
|
-
|
|
16
|
-
cell: Cell
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class HasCell:
|
|
20
|
-
"""Descriptor for cell movement behavior."""
|
|
21
|
-
|
|
22
|
-
_mesa_cell: Cell | None = None
|
|
23
|
-
|
|
24
|
-
@property
|
|
25
|
-
def cell(self) -> Cell | None: # noqa: D102
|
|
26
|
-
return self._mesa_cell
|
|
27
|
-
|
|
28
|
-
@cell.setter
|
|
29
|
-
def cell(self, cell: Cell | None) -> None:
|
|
30
|
-
# remove from current cell
|
|
31
|
-
if self.cell is not None:
|
|
32
|
-
self.cell.remove_agent(self)
|
|
33
|
-
|
|
34
|
-
# update private attribute
|
|
35
|
-
self._mesa_cell = cell
|
|
36
|
-
|
|
37
|
-
# add to new cell
|
|
38
|
-
if cell is not None:
|
|
39
|
-
cell.add_agent(self)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class BasicMovement:
|
|
43
|
-
"""Mixin for moving agents in discrete space."""
|
|
44
|
-
|
|
45
|
-
def move_to(self: HasCellProtocol, cell: Cell) -> None:
|
|
46
|
-
"""Move to a new cell."""
|
|
47
|
-
self.cell = cell
|
|
48
|
-
|
|
49
|
-
def move_relative(self: HasCellProtocol, direction: tuple[int, ...]):
|
|
50
|
-
"""Move to a cell relative to the current cell.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
direction: The direction to move in.
|
|
54
|
-
"""
|
|
55
|
-
new_cell = self.cell.connections.get(direction)
|
|
56
|
-
if new_cell is not None:
|
|
57
|
-
self.cell = new_cell
|
|
58
|
-
else:
|
|
59
|
-
raise ValueError(f"No cell in direction {direction}")
|
|
8
|
+
from mesa.experimental.cell_space.cell import Cell
|
|
60
9
|
|
|
61
10
|
|
|
62
|
-
class
|
|
63
|
-
"""
|
|
11
|
+
class CellAgent(Agent):
|
|
12
|
+
"""Cell Agent is an extension of the Agent class and adds behavior for moving in discrete spaces
|
|
64
13
|
|
|
65
|
-
@property
|
|
66
|
-
def cell(self) -> Cell | None: # noqa: D102
|
|
67
|
-
return self._mesa_cell
|
|
68
|
-
|
|
69
|
-
@cell.setter
|
|
70
|
-
def cell(self, cell: Cell) -> None:
|
|
71
|
-
if self.cell is not None:
|
|
72
|
-
raise ValueError("Cannot move agent in FixedCell")
|
|
73
|
-
self._mesa_cell = cell
|
|
74
|
-
|
|
75
|
-
cell.add_agent(self)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class CellAgent(Agent, HasCell, BasicMovement):
|
|
79
|
-
"""Cell Agent is an extension of the Agent class and adds behavior for moving in discrete spaces.
|
|
80
14
|
|
|
81
15
|
Attributes:
|
|
82
|
-
|
|
16
|
+
unique_id (int): A unique identifier for this agent.
|
|
17
|
+
model (Model): The model instance to which the agent belongs
|
|
18
|
+
pos: (Position | None): The position of the agent in the space
|
|
19
|
+
cell: (Cell | None): the cell which the agent occupies
|
|
83
20
|
"""
|
|
84
21
|
|
|
85
|
-
def
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
self.cell = None # ensures that we are also removed from cell
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
class FixedAgent(Agent, FixedCell):
|
|
92
|
-
"""A patch in a 2D grid."""
|
|
93
|
-
|
|
94
|
-
def remove(self):
|
|
95
|
-
"""Remove the agent from the model."""
|
|
96
|
-
super().remove()
|
|
97
|
-
|
|
98
|
-
# fixme we leave self._mesa_cell on the original value
|
|
99
|
-
# so you cannot hijack remove() to move patches
|
|
100
|
-
self.cell.remove_agent(self)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class Grid2DMovingAgent(CellAgent):
|
|
104
|
-
"""Mixin for moving agents in 2D grids."""
|
|
105
|
-
|
|
106
|
-
# fmt: off
|
|
107
|
-
DIRECTION_MAP = {
|
|
108
|
-
"n": (-1, 0), "north": (-1, 0), "up": (-1, 0),
|
|
109
|
-
"s": (1, 0), "south": (1, 0), "down": (1, 0),
|
|
110
|
-
"e": (0, 1), "east": (0, 1), "right": (0, 1),
|
|
111
|
-
"w": (0, -1), "west": (0, -1), "left": (0, -1),
|
|
112
|
-
"ne": (-1, 1), "northeast": (-1, 1), "upright": (-1, 1),
|
|
113
|
-
"nw": (-1, -1), "northwest": (-1, -1), "upleft": (-1, -1),
|
|
114
|
-
"se": (1, 1), "southeast": (1, 1), "downright": (1, 1),
|
|
115
|
-
"sw": (1, -1), "southwest": (1, -1), "downleft": (1, -1)
|
|
116
|
-
}
|
|
117
|
-
# fmt: on
|
|
118
|
-
|
|
119
|
-
def move(self, direction: str, distance: int = 1):
|
|
120
|
-
"""Move the agent in a cardinal direction.
|
|
22
|
+
def __init__(self, unique_id: int, model: Model) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Create a new agent.
|
|
121
25
|
|
|
122
26
|
Args:
|
|
123
|
-
|
|
124
|
-
|
|
27
|
+
unique_id (int): A unique identifier for this agent.
|
|
28
|
+
model (Model): The model instance in which the agent exists.
|
|
125
29
|
"""
|
|
126
|
-
|
|
30
|
+
super().__init__(unique_id, model)
|
|
31
|
+
self.cell: Cell | None = None
|
|
127
32
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
self.move_relative(move_vector)
|
|
33
|
+
def move_to(self, cell) -> None:
|
|
34
|
+
if self.cell is not None:
|
|
35
|
+
self.cell.remove_agent(self)
|
|
36
|
+
self.cell = cell
|
|
37
|
+
cell.add_agent(self)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""CellCollection class."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
import itertools
|
|
@@ -16,7 +14,7 @@ T = TypeVar("T", bound="Cell")
|
|
|
16
14
|
|
|
17
15
|
|
|
18
16
|
class CellCollection(Generic[T]):
|
|
19
|
-
"""An immutable collection of cells
|
|
17
|
+
"""An immutable collection of cells
|
|
20
18
|
|
|
21
19
|
Attributes:
|
|
22
20
|
cells (List[Cell]): The list of cells this collection represents
|
|
@@ -30,12 +28,6 @@ class CellCollection(Generic[T]):
|
|
|
30
28
|
cells: Mapping[T, list[CellAgent]] | Iterable[T],
|
|
31
29
|
random: Random | None = None,
|
|
32
30
|
) -> None:
|
|
33
|
-
"""Initialize a CellCollection.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
cells: cells to add to the collection
|
|
37
|
-
random: a seeded random number generator.
|
|
38
|
-
"""
|
|
39
31
|
if isinstance(cells, dict):
|
|
40
32
|
self._cells = cells
|
|
41
33
|
else:
|
|
@@ -48,71 +40,42 @@ class CellCollection(Generic[T]):
|
|
|
48
40
|
random = Random() # FIXME
|
|
49
41
|
self.random = random
|
|
50
42
|
|
|
51
|
-
def __iter__(self):
|
|
43
|
+
def __iter__(self):
|
|
52
44
|
return iter(self._cells)
|
|
53
45
|
|
|
54
|
-
def __getitem__(self, key: T) -> Iterable[CellAgent]:
|
|
46
|
+
def __getitem__(self, key: T) -> Iterable[CellAgent]:
|
|
55
47
|
return self._cells[key]
|
|
56
48
|
|
|
57
49
|
# @cached_property
|
|
58
|
-
def __len__(self) -> int:
|
|
50
|
+
def __len__(self) -> int:
|
|
59
51
|
return len(self._cells)
|
|
60
52
|
|
|
61
|
-
def __repr__(self):
|
|
53
|
+
def __repr__(self):
|
|
62
54
|
return f"CellCollection({self._cells})"
|
|
63
55
|
|
|
64
56
|
@cached_property
|
|
65
|
-
def cells(self) -> list[T]:
|
|
57
|
+
def cells(self) -> list[T]:
|
|
66
58
|
return list(self._cells.keys())
|
|
67
59
|
|
|
68
60
|
@property
|
|
69
|
-
def agents(self) -> Iterable[CellAgent]:
|
|
61
|
+
def agents(self) -> Iterable[CellAgent]:
|
|
70
62
|
return itertools.chain.from_iterable(self._cells.values())
|
|
71
63
|
|
|
72
64
|
def select_random_cell(self) -> T:
|
|
73
|
-
"""Select a random cell."""
|
|
74
65
|
return self.random.choice(self.cells)
|
|
75
66
|
|
|
76
67
|
def select_random_agent(self) -> CellAgent:
|
|
77
|
-
"""Select a random agent.
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
CellAgent instance
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"""
|
|
84
68
|
return self.random.choice(list(self.agents))
|
|
85
69
|
|
|
86
|
-
def select(
|
|
87
|
-
|
|
88
|
-
filter_func
|
|
89
|
-
at_most: int | float = float("inf"),
|
|
90
|
-
):
|
|
91
|
-
"""Select cells based on filter function.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
filter_func: filter function
|
|
95
|
-
at_most: The maximum amount of cells to select. Defaults to infinity.
|
|
96
|
-
- If an integer, at most the first number of matching cells is selected.
|
|
97
|
-
- If a float between 0 and 1, at most that fraction of original number of cells
|
|
98
|
-
|
|
99
|
-
Returns:
|
|
100
|
-
CellCollection
|
|
101
|
-
|
|
102
|
-
"""
|
|
103
|
-
if filter_func is None and at_most == float("inf"):
|
|
70
|
+
def select(self, filter_func: Callable[[T], bool] | None = None, n=0):
|
|
71
|
+
# FIXME: n is not considered
|
|
72
|
+
if filter_func is None and n == 0:
|
|
104
73
|
return self
|
|
105
74
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
break
|
|
114
|
-
if not filter_func or filter_func(cell):
|
|
115
|
-
yield cell
|
|
116
|
-
count += 1
|
|
117
|
-
|
|
118
|
-
return CellCollection(cell_generator(filter_func, at_most))
|
|
75
|
+
return CellCollection(
|
|
76
|
+
{
|
|
77
|
+
cell: agents
|
|
78
|
+
for cell, agents in self._cells.items()
|
|
79
|
+
if filter_func is None or filter_func(cell)
|
|
80
|
+
}
|
|
81
|
+
)
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
"""DiscreteSpace base class."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
|
-
from collections.abc import Callable
|
|
6
3
|
from functools import cached_property
|
|
7
4
|
from random import Random
|
|
8
|
-
from typing import
|
|
5
|
+
from typing import Generic, TypeVar
|
|
9
6
|
|
|
10
|
-
from mesa.agent import AgentSet
|
|
11
7
|
from mesa.experimental.cell_space.cell import Cell
|
|
12
8
|
from mesa.experimental.cell_space.cell_collection import CellCollection
|
|
13
|
-
from mesa.space import PropertyLayer
|
|
14
9
|
|
|
15
10
|
T = TypeVar("T", bound=Cell)
|
|
16
11
|
|
|
@@ -23,8 +18,8 @@ class DiscreteSpace(Generic[T]):
|
|
|
23
18
|
all_cells (CellCollection): The cells composing the discrete space
|
|
24
19
|
random (Random): The random number generator
|
|
25
20
|
cell_klass (Type) : the type of cell class
|
|
26
|
-
empties (CellCollection) :
|
|
27
|
-
|
|
21
|
+
empties (CellCollection) : collecction of all cells that are empty
|
|
22
|
+
|
|
28
23
|
"""
|
|
29
24
|
|
|
30
25
|
def __init__(
|
|
@@ -33,13 +28,6 @@ class DiscreteSpace(Generic[T]):
|
|
|
33
28
|
cell_klass: type[T] = Cell,
|
|
34
29
|
random: Random | None = None,
|
|
35
30
|
):
|
|
36
|
-
"""Instantiate a DiscreteSpace.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
capacity: capacity of cells
|
|
40
|
-
cell_klass: base class for all cells
|
|
41
|
-
random: random number generator
|
|
42
|
-
"""
|
|
43
31
|
super().__init__()
|
|
44
32
|
self.capacity = capacity
|
|
45
33
|
self._cells: dict[tuple[int, ...], T] = {}
|
|
@@ -50,99 +38,27 @@ class DiscreteSpace(Generic[T]):
|
|
|
50
38
|
|
|
51
39
|
self._empties: dict[tuple[int, ...], None] = {}
|
|
52
40
|
self._empties_initialized = False
|
|
53
|
-
self.property_layers: dict[str, PropertyLayer] = {}
|
|
54
41
|
|
|
55
42
|
@property
|
|
56
|
-
def cutoff_empties(self):
|
|
43
|
+
def cutoff_empties(self):
|
|
57
44
|
return 7.953 * len(self._cells) ** 0.384
|
|
58
45
|
|
|
59
|
-
@property
|
|
60
|
-
def agents(self) -> AgentSet:
|
|
61
|
-
"""Return an AgentSet with the agents in the space."""
|
|
62
|
-
return AgentSet(self.all_cells.agents, random=self.random)
|
|
63
|
-
|
|
64
|
-
def _connect_cells(self): ...
|
|
65
46
|
def _connect_single_cell(self, cell: T): ...
|
|
66
47
|
|
|
67
48
|
@cached_property
|
|
68
49
|
def all_cells(self):
|
|
69
|
-
"""Return all cells in space."""
|
|
70
50
|
return CellCollection({cell: cell.agents for cell in self._cells.values()})
|
|
71
51
|
|
|
72
|
-
def __iter__(self):
|
|
52
|
+
def __iter__(self):
|
|
73
53
|
return iter(self._cells.values())
|
|
74
54
|
|
|
75
|
-
def __getitem__(self, key
|
|
55
|
+
def __getitem__(self, key):
|
|
76
56
|
return self._cells[key]
|
|
77
57
|
|
|
78
58
|
@property
|
|
79
|
-
def empties(self) -> CellCollection
|
|
80
|
-
"""Return all empty in spaces."""
|
|
59
|
+
def empties(self) -> CellCollection:
|
|
81
60
|
return self.all_cells.select(lambda cell: cell.is_empty)
|
|
82
61
|
|
|
83
62
|
def select_random_empty_cell(self) -> T:
|
|
84
|
-
"""
|
|
63
|
+
"""select random empty cell"""
|
|
85
64
|
return self.random.choice(list(self.empties))
|
|
86
|
-
|
|
87
|
-
# PropertyLayer methods
|
|
88
|
-
def add_property_layer(
|
|
89
|
-
self, property_layer: PropertyLayer, add_to_cells: bool = True
|
|
90
|
-
):
|
|
91
|
-
"""Add a property layer to the grid.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
property_layer: the property layer to add
|
|
95
|
-
add_to_cells: whether to add the property layer to all cells (default: True)
|
|
96
|
-
"""
|
|
97
|
-
if property_layer.name in self.property_layers:
|
|
98
|
-
raise ValueError(f"Property layer {property_layer.name} already exists.")
|
|
99
|
-
self.property_layers[property_layer.name] = property_layer
|
|
100
|
-
if add_to_cells:
|
|
101
|
-
for cell in self._cells.values():
|
|
102
|
-
cell._mesa_property_layers[property_layer.name] = property_layer
|
|
103
|
-
|
|
104
|
-
def remove_property_layer(self, property_name: str, remove_from_cells: bool = True):
|
|
105
|
-
"""Remove a property layer from the grid.
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
property_name: the name of the property layer to remove
|
|
109
|
-
remove_from_cells: whether to remove the property layer from all cells (default: True)
|
|
110
|
-
"""
|
|
111
|
-
del self.property_layers[property_name]
|
|
112
|
-
if remove_from_cells:
|
|
113
|
-
for cell in self._cells.values():
|
|
114
|
-
del cell._mesa_property_layers[property_name]
|
|
115
|
-
|
|
116
|
-
def set_property(
|
|
117
|
-
self, property_name: str, value, condition: Callable[[T], bool] | None = None
|
|
118
|
-
):
|
|
119
|
-
"""Set the value of a property for all cells in the grid.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
property_name: the name of the property to set
|
|
123
|
-
value: the value to set
|
|
124
|
-
condition: a function that takes a cell and returns a boolean
|
|
125
|
-
"""
|
|
126
|
-
self.property_layers[property_name].set_cells(value, condition)
|
|
127
|
-
|
|
128
|
-
def modify_properties(
|
|
129
|
-
self,
|
|
130
|
-
property_name: str,
|
|
131
|
-
operation: Callable,
|
|
132
|
-
value: Any = None,
|
|
133
|
-
condition: Callable[[T], bool] | None = None,
|
|
134
|
-
):
|
|
135
|
-
"""Modify the values of a specific property for all cells in the grid.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
property_name: the name of the property to modify
|
|
139
|
-
operation: the operation to perform
|
|
140
|
-
value: the value to use in the operation
|
|
141
|
-
condition: a function that takes a cell and returns a boolean (used to filter cells)
|
|
142
|
-
"""
|
|
143
|
-
self.property_layers[property_name].modify_cells(operation, value, condition)
|
|
144
|
-
|
|
145
|
-
def __setstate__(self, state):
|
|
146
|
-
"""Set the state of the discrete space and rebuild the connections."""
|
|
147
|
-
self.__dict__ = state
|
|
148
|
-
self._connect_cells()
|