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.

Files changed (104) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +114 -406
  3. mesa/batchrunner.py +27 -54
  4. mesa/cookiecutter-mesa/cookiecutter.json +8 -0
  5. mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
  6. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
  7. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  8. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
  9. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
  10. mesa/datacollection.py +29 -140
  11. mesa/experimental/__init__.py +1 -11
  12. mesa/experimental/cell_space/__init__.py +1 -16
  13. mesa/experimental/cell_space/cell.py +23 -93
  14. mesa/experimental/cell_space/cell_agent.py +21 -117
  15. mesa/experimental/cell_space/cell_collection.py +17 -54
  16. mesa/experimental/cell_space/discrete_space.py +8 -92
  17. mesa/experimental/cell_space/grid.py +8 -32
  18. mesa/experimental/cell_space/network.py +7 -12
  19. mesa/experimental/devs/__init__.py +0 -2
  20. mesa/experimental/devs/eventlist.py +14 -52
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
  22. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  23. mesa/experimental/devs/simulator.py +15 -55
  24. mesa/main.py +63 -0
  25. mesa/model.py +83 -211
  26. mesa/space.py +149 -215
  27. mesa/time.py +77 -62
  28. mesa/{experimental → visualization}/UserParam.py +6 -17
  29. mesa/visualization/__init__.py +2 -25
  30. mesa/{experimental → visualization}/components/altair.py +0 -10
  31. mesa/visualization/components/matplotlib.py +134 -0
  32. mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
  33. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
  34. mesa-3.0.0a0.dist-info/RECORD +38 -0
  35. mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
  36. mesa/examples/README.md +0 -37
  37. mesa/examples/__init__.py +0 -21
  38. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
  39. mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
  40. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  41. mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
  42. mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
  43. mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
  44. mesa/examples/advanced/pd_grid/Readme.md +0 -43
  45. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  46. mesa/examples/advanced/pd_grid/agents.py +0 -50
  47. mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
  48. mesa/examples/advanced/pd_grid/app.py +0 -54
  49. mesa/examples/advanced/pd_grid/model.py +0 -71
  50. mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
  51. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  52. mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
  53. mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
  54. mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
  55. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
  56. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  57. mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
  58. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  59. mesa/examples/advanced/wolf_sheep/agents.py +0 -102
  60. mesa/examples/advanced/wolf_sheep/app.py +0 -84
  61. mesa/examples/advanced/wolf_sheep/model.py +0 -137
  62. mesa/examples/basic/__init__.py +0 -0
  63. mesa/examples/basic/boid_flockers/Readme.md +0 -22
  64. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  65. mesa/examples/basic/boid_flockers/agents.py +0 -71
  66. mesa/examples/basic/boid_flockers/app.py +0 -58
  67. mesa/examples/basic/boid_flockers/model.py +0 -69
  68. mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
  69. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  70. mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
  71. mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
  72. mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
  73. mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
  74. mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
  75. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  76. mesa/examples/basic/conways_game_of_life/agents.py +0 -47
  77. mesa/examples/basic/conways_game_of_life/app.py +0 -51
  78. mesa/examples/basic/conways_game_of_life/model.py +0 -31
  79. mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
  80. mesa/examples/basic/schelling/Readme.md +0 -40
  81. mesa/examples/basic/schelling/__init__.py +0 -0
  82. mesa/examples/basic/schelling/agents.py +0 -26
  83. mesa/examples/basic/schelling/analysis.ipynb +0 -205
  84. mesa/examples/basic/schelling/app.py +0 -42
  85. mesa/examples/basic/schelling/model.py +0 -59
  86. mesa/examples/basic/virus_on_network/Readme.md +0 -61
  87. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  88. mesa/examples/basic/virus_on_network/agents.py +0 -69
  89. mesa/examples/basic/virus_on_network/app.py +0 -114
  90. mesa/examples/basic/virus_on_network/model.py +0 -96
  91. mesa/experimental/cell_space/voronoi.py +0 -257
  92. mesa/experimental/components/matplotlib.py +0 -242
  93. mesa/visualization/components/__init__.py +0 -83
  94. mesa/visualization/components/altair_components.py +0 -188
  95. mesa/visualization/components/matplotlib_components.py +0 -175
  96. mesa/visualization/mpl_space_drawing.py +0 -593
  97. mesa/visualization/solara_viz.py +0 -458
  98. mesa/visualization/user_param.py +0 -69
  99. mesa/visualization/utils.py +0 -9
  100. mesa-3.0.0.dist-info/RECORD +0 -95
  101. mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
  102. /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
  103. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
  104. {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 collections.abc import Callable
6
- from functools import cache, cached_property
3
+ from functools import cache
7
4
  from random import Random
8
- from typing import TYPE_CHECKING, Any
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.agent import Agent
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
- "connections",
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: Coordinate,
55
- capacity: int | None = None,
45
+ coordinate: tuple[int, ...],
46
+ capacity: float | None = None,
56
47
  random: Random | None = None,
57
48
  ) -> None:
58
- """Initialise the cell.
49
+ """ "
59
50
 
60
51
  Args:
61
- coordinate: coordinates of the cell
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.connections: dict[Coordinate, Cell] = {}
69
- self.agents: list[
70
- Agent
71
- ] = [] # TODO:: change to AgentSet or weakrefs? (neither is very performant, )
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, key: Coordinate | None = None) -> None:
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
- if key is None:
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
- keys_to_remove = [k for k, v in self.connections.items() if v == other]
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): # noqa
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 get_neighborhood(
150
- self, radius: int = 1, include_center: bool = False
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: dict[Cell, list[Agent]] = {}
190
- for neighbor in self.connections.values():
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, Protocol
3
+ from typing import TYPE_CHECKING
6
4
 
7
- from mesa.agent import Agent
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 FixedCell(HasCell):
63
- """Mixin for agents that are fixed to a cell."""
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
- cell (Cell): The cell the agent is currently in.
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 remove(self):
86
- """Remove the agent from the model."""
87
- super().remove()
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
- direction: The cardinal direction to move in.
124
- distance: The distance to move.
27
+ unique_id (int): A unique identifier for this agent.
28
+ model (Model): The model instance in which the agent exists.
125
29
  """
126
- direction = direction.lower() # Convert direction to lowercase
30
+ super().__init__(unique_id, model)
31
+ self.cell: Cell | None = None
127
32
 
128
- if direction not in self.DIRECTION_MAP:
129
- raise ValueError(f"Invalid direction: {direction}")
130
-
131
- move_vector = self.DIRECTION_MAP[direction]
132
- for _ in range(distance):
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): # noqa
43
+ def __iter__(self):
52
44
  return iter(self._cells)
53
45
 
54
- def __getitem__(self, key: T) -> Iterable[CellAgent]: # noqa
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: # noqa
50
+ def __len__(self) -> int:
59
51
  return len(self._cells)
60
52
 
61
- def __repr__(self): # noqa
53
+ def __repr__(self):
62
54
  return f"CellCollection({self._cells})"
63
55
 
64
56
  @cached_property
65
- def cells(self) -> list[T]: # noqa
57
+ def cells(self) -> list[T]:
66
58
  return list(self._cells.keys())
67
59
 
68
60
  @property
69
- def agents(self) -> Iterable[CellAgent]: # noqa
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
- self,
88
- filter_func: Callable[[T], bool] | None = None,
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
- if at_most <= 1.0 and isinstance(at_most, float):
107
- at_most = int(len(self) * at_most) # Note that it rounds down (floor)
108
-
109
- def cell_generator(filter_func, at_most):
110
- count = 0
111
- for cell in self:
112
- if count >= at_most:
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 Any, Generic, TypeVar
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) : collection of all cells that are empty
27
- property_layers (dict[str, PropertyLayer]): the property layers of the discrete space
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): # noqa
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): # noqa
52
+ def __iter__(self):
73
53
  return iter(self._cells.values())
74
54
 
75
- def __getitem__(self, key: tuple[int, ...]) -> T: # noqa: D105
55
+ def __getitem__(self, key):
76
56
  return self._cells[key]
77
57
 
78
58
  @property
79
- def empties(self) -> CellCollection[T]:
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
- """Select random empty cell."""
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()