Mesa 3.1.0.dev0__py3-none-any.whl → 3.1.1__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 +48 -0
- mesa/batchrunner.py +14 -1
- mesa/datacollection.py +1 -6
- mesa/examples/__init__.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/app.py +5 -0
- mesa/examples/advanced/pd_grid/agents.py +2 -1
- mesa/examples/advanced/pd_grid/app.py +5 -0
- mesa/examples/advanced/pd_grid/model.py +3 -5
- mesa/examples/advanced/sugarscape_g1mt/agents.py +12 -65
- mesa/examples/advanced/sugarscape_g1mt/app.py +24 -19
- mesa/examples/advanced/sugarscape_g1mt/model.py +45 -52
- mesa/examples/advanced/wolf_sheep/agents.py +3 -1
- mesa/examples/advanced/wolf_sheep/model.py +17 -16
- mesa/examples/basic/boid_flockers/app.py +5 -0
- mesa/examples/basic/boltzmann_wealth_model/app.py +8 -5
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
- mesa/examples/basic/conways_game_of_life/app.py +5 -0
- mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
- mesa/examples/basic/schelling/agents.py +11 -5
- mesa/examples/basic/schelling/app.py +6 -1
- mesa/examples/basic/virus_on_network/app.py +5 -0
- mesa/experimental/__init__.py +17 -10
- mesa/experimental/cell_space/__init__.py +19 -7
- mesa/experimental/cell_space/cell.py +22 -37
- mesa/experimental/cell_space/cell_agent.py +12 -1
- mesa/experimental/cell_space/cell_collection.py +18 -3
- mesa/experimental/cell_space/discrete_space.py +15 -64
- mesa/experimental/cell_space/grid.py +74 -4
- mesa/experimental/cell_space/network.py +13 -1
- mesa/experimental/cell_space/property_layer.py +444 -0
- mesa/experimental/cell_space/voronoi.py +13 -1
- mesa/experimental/devs/__init__.py +20 -2
- mesa/experimental/devs/eventlist.py +19 -1
- mesa/experimental/devs/simulator.py +24 -8
- mesa/experimental/mesa_signals/__init__.py +23 -0
- mesa/experimental/mesa_signals/mesa_signal.py +485 -0
- mesa/experimental/mesa_signals/observable_collections.py +133 -0
- mesa/experimental/mesa_signals/signals_util.py +52 -0
- mesa/mesa_logging.py +190 -0
- mesa/model.py +17 -23
- mesa/visualization/__init__.py +2 -2
- mesa/visualization/mpl_space_drawing.py +8 -5
- mesa/visualization/solara_viz.py +49 -11
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/METADATA +1 -1
- mesa-3.1.1.dist-info/RECORD +94 -0
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/WHEEL +1 -1
- mesa/experimental/UserParam.py +0 -67
- mesa/experimental/components/altair.py +0 -81
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/devs/examples/epstein_civil_violence.py +0 -305
- mesa/experimental/devs/examples/wolf_sheep.py +0 -250
- mesa/experimental/solara_viz.py +0 -453
- mesa-3.1.0.dev0.dist-info/RECORD +0 -94
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/entry_points.txt +0 -0
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -49,9 +49,9 @@ if run:
|
|
|
49
49
|
for i in range(num_ticks):
|
|
50
50
|
model.step()
|
|
51
51
|
my_bar.progress((i / num_ticks), text="Simulation progress")
|
|
52
|
-
placeholder.text("Step =
|
|
52
|
+
placeholder.text(f"Step = {i}")
|
|
53
53
|
for contents, (x, y) in model.grid.coord_iter():
|
|
54
|
-
# print(
|
|
54
|
+
# print(f"x: {x}, y: {y}, state: {contents}")
|
|
55
55
|
selected_row = df_grid[(df_grid["x"] == x) & (df_grid["y"] == y)]
|
|
56
56
|
df_grid.loc[selected_row.index, "state"] = (
|
|
57
57
|
contents.state
|
|
@@ -6,7 +6,6 @@ class SchellingAgent(Agent):
|
|
|
6
6
|
|
|
7
7
|
def __init__(self, model, agent_type: int) -> None:
|
|
8
8
|
"""Create a new Schelling agent.
|
|
9
|
-
|
|
10
9
|
Args:
|
|
11
10
|
model: The model instance the agent belongs to
|
|
12
11
|
agent_type: Indicator for the agent's type (minority=1, majority=0)
|
|
@@ -16,15 +15,22 @@ class SchellingAgent(Agent):
|
|
|
16
15
|
|
|
17
16
|
def step(self) -> None:
|
|
18
17
|
"""Determine if agent is happy and move if necessary."""
|
|
19
|
-
neighbors = self.model.grid.
|
|
18
|
+
neighbors = self.model.grid.get_neighbors(
|
|
20
19
|
self.pos, moore=True, radius=self.model.radius
|
|
21
20
|
)
|
|
22
21
|
|
|
23
22
|
# Count similar neighbors
|
|
24
|
-
|
|
23
|
+
similar_neighbors = len([n for n in neighbors if n.type == self.type])
|
|
24
|
+
|
|
25
|
+
# Calculate the fraction of similar neighbors
|
|
26
|
+
if (valid_neighbors := len(neighbors)) > 0:
|
|
27
|
+
similarity_fraction = similar_neighbors / valid_neighbors
|
|
28
|
+
else:
|
|
29
|
+
# If there are no neighbors, the similarity fraction is 0
|
|
30
|
+
similarity_fraction = 0.0
|
|
25
31
|
|
|
26
|
-
#
|
|
27
|
-
if
|
|
32
|
+
# Move if unhappy
|
|
33
|
+
if similarity_fraction < self.model.homophily:
|
|
28
34
|
self.model.grid.move_to_empty(self)
|
|
29
35
|
else:
|
|
30
36
|
self.model.happy += 1
|
|
@@ -19,9 +19,14 @@ def agent_portrayal(agent):
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
model_params = {
|
|
22
|
+
"seed": {
|
|
23
|
+
"type": "InputText",
|
|
24
|
+
"value": 42,
|
|
25
|
+
"label": "Random Seed",
|
|
26
|
+
},
|
|
22
27
|
"density": Slider("Agent density", 0.8, 0.1, 1.0, 0.1),
|
|
23
28
|
"minority_pc": Slider("Fraction minority", 0.2, 0.0, 1.0, 0.05),
|
|
24
|
-
"homophily": Slider("Homophily", 3, 0, 8, 1),
|
|
29
|
+
"homophily": Slider("Homophily", 0.3, 0.0, 0.8, 0.1),
|
|
25
30
|
"width": 20,
|
|
26
31
|
"height": 20,
|
|
27
32
|
}
|
mesa/experimental/__init__.py
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
"""Experimental
|
|
1
|
+
"""Experimental features package for Mesa.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This package contains modules that are under active development and testing. These
|
|
4
|
+
features are provided to allow early access and feedback from the Mesa community, but
|
|
5
|
+
their APIs may change between releases without following semantic versioning.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
Current experimental modules:
|
|
8
|
+
cell_space: Alternative API for discrete spaces with cell-centric functionality
|
|
9
|
+
devs: Discrete event simulation system for scheduling events at arbitrary times
|
|
10
|
+
mesa_signals: Reactive programming capabilities for tracking state changes
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Notes:
|
|
13
|
+
- Features in this package may be changed or removed without notice
|
|
14
|
+
- APIs are not guaranteed to be stable between releases
|
|
15
|
+
- Features graduate from experimental status once their APIs are stabilized
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from mesa.experimental import cell_space, devs, mesa_signals
|
|
19
|
+
|
|
20
|
+
__all__ = ["cell_space", "devs", "mesa_signals"]
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
"""Cell spaces.
|
|
1
|
+
"""Cell spaces for active, property-rich spatial modeling in Mesa.
|
|
2
2
|
|
|
3
|
-
Cell spaces
|
|
4
|
-
|
|
3
|
+
Cell spaces extend Mesa's spatial modeling capabilities by making the space itself active -
|
|
4
|
+
each position (cell) can have properties and behaviors rather than just containing agents.
|
|
5
|
+
This enables more sophisticated environmental modeling and agent-environment interactions.
|
|
5
6
|
|
|
7
|
+
Key components:
|
|
8
|
+
- Cells: Active positions that can have properties and contain agents
|
|
9
|
+
- CellAgents: Agents that understand how to interact with cells
|
|
10
|
+
- Spaces: Different cell organization patterns (grids, networks, etc.)
|
|
11
|
+
- PropertyLayers: Efficient property storage and manipulation
|
|
12
|
+
|
|
13
|
+
This is particularly useful for models where the environment plays an active role,
|
|
14
|
+
like resource growth, pollution diffusion, or infrastructure networks. The cell
|
|
15
|
+
space system is experimental and under active development.
|
|
6
16
|
"""
|
|
7
17
|
|
|
8
18
|
from mesa.experimental.cell_space.cell import Cell
|
|
@@ -20,19 +30,21 @@ from mesa.experimental.cell_space.grid import (
|
|
|
20
30
|
OrthogonalVonNeumannGrid,
|
|
21
31
|
)
|
|
22
32
|
from mesa.experimental.cell_space.network import Network
|
|
33
|
+
from mesa.experimental.cell_space.property_layer import PropertyLayer
|
|
23
34
|
from mesa.experimental.cell_space.voronoi import VoronoiGrid
|
|
24
35
|
|
|
25
36
|
__all__ = [
|
|
26
|
-
"CellCollection",
|
|
27
37
|
"Cell",
|
|
28
38
|
"CellAgent",
|
|
29
|
-
"
|
|
30
|
-
"FixedAgent",
|
|
39
|
+
"CellCollection",
|
|
31
40
|
"DiscreteSpace",
|
|
41
|
+
"FixedAgent",
|
|
32
42
|
"Grid",
|
|
43
|
+
"Grid2DMovingAgent",
|
|
33
44
|
"HexGrid",
|
|
45
|
+
"Network",
|
|
34
46
|
"OrthogonalMooreGrid",
|
|
35
47
|
"OrthogonalVonNeumannGrid",
|
|
36
|
-
"
|
|
48
|
+
"PropertyLayer",
|
|
37
49
|
"VoronoiGrid",
|
|
38
50
|
]
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Cells are positions in space that can have properties and contain agents.
|
|
2
|
+
|
|
3
|
+
A cell represents a location that can:
|
|
4
|
+
- Have properties (like temperature or resources)
|
|
5
|
+
- Track and limit the agents it contains
|
|
6
|
+
- Connect to neighboring cells
|
|
7
|
+
- Provide neighborhood information
|
|
8
|
+
|
|
9
|
+
Cells form the foundation of the cell space system, enabling rich spatial
|
|
10
|
+
environments where both location properties and agent behaviors matter. They're
|
|
11
|
+
useful for modeling things like varying terrain, infrastructure capacity, or
|
|
12
|
+
environmental conditions.
|
|
13
|
+
"""
|
|
2
14
|
|
|
3
15
|
from __future__ import annotations
|
|
4
16
|
|
|
5
|
-
from collections.abc import Callable
|
|
6
17
|
from functools import cache, cached_property
|
|
7
18
|
from random import Random
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
9
20
|
|
|
10
21
|
from mesa.experimental.cell_space.cell_agent import CellAgent
|
|
11
22
|
from mesa.experimental.cell_space.cell_collection import CellCollection
|
|
12
|
-
from mesa.space import PropertyLayer
|
|
13
23
|
|
|
14
24
|
if TYPE_CHECKING:
|
|
15
25
|
from mesa.agent import Agent
|
|
@@ -24,31 +34,20 @@ class Cell:
|
|
|
24
34
|
coordinate (Tuple[int, int]) : the position of the cell in the discrete space
|
|
25
35
|
agents (List[Agent]): the agents occupying the cell
|
|
26
36
|
capacity (int): the maximum number of agents that can simultaneously occupy the cell
|
|
27
|
-
properties (dict[str, Any]): the properties of the cell
|
|
28
37
|
random (Random): the random number generator
|
|
29
38
|
|
|
30
39
|
"""
|
|
31
40
|
|
|
32
41
|
__slots__ = [
|
|
33
|
-
"
|
|
34
|
-
"connections",
|
|
42
|
+
"__dict__",
|
|
35
43
|
"agents",
|
|
36
44
|
"capacity",
|
|
45
|
+
"connections",
|
|
46
|
+
"coordinate",
|
|
37
47
|
"properties",
|
|
38
48
|
"random",
|
|
39
|
-
"_mesa_property_layers",
|
|
40
|
-
"__dict__",
|
|
41
49
|
]
|
|
42
50
|
|
|
43
|
-
# def __new__(cls,
|
|
44
|
-
# coordinate: tuple[int, ...],
|
|
45
|
-
# capacity: float | None = None,
|
|
46
|
-
# random: Random | None = None,):
|
|
47
|
-
# if capacity != 1:
|
|
48
|
-
# return object.__new__(cls)
|
|
49
|
-
# else:
|
|
50
|
-
# return object.__new__(SingleAgentCell)
|
|
51
|
-
|
|
52
51
|
def __init__(
|
|
53
52
|
self,
|
|
54
53
|
coordinate: Coordinate,
|
|
@@ -70,9 +69,10 @@ class Cell:
|
|
|
70
69
|
Agent
|
|
71
70
|
] = [] # TODO:: change to AgentSet or weakrefs? (neither is very performant, )
|
|
72
71
|
self.capacity: int | None = capacity
|
|
73
|
-
self.properties: dict[
|
|
72
|
+
self.properties: dict[
|
|
73
|
+
Coordinate, object
|
|
74
|
+
] = {} # fixme still used by voronoi mesh
|
|
74
75
|
self.random = random
|
|
75
|
-
self._mesa_property_layers: dict[str, PropertyLayer] = {}
|
|
76
76
|
|
|
77
77
|
def connect(self, other: Cell, key: Coordinate | None = None) -> None:
|
|
78
78
|
"""Connects this cell to another cell.
|
|
@@ -105,6 +105,7 @@ class Cell:
|
|
|
105
105
|
|
|
106
106
|
"""
|
|
107
107
|
n = len(self.agents)
|
|
108
|
+
self.empty = False
|
|
108
109
|
|
|
109
110
|
if self.capacity and n >= self.capacity:
|
|
110
111
|
raise Exception(
|
|
@@ -121,6 +122,7 @@ class Cell:
|
|
|
121
122
|
|
|
122
123
|
"""
|
|
123
124
|
self.agents.remove(agent)
|
|
125
|
+
self.empty = self.is_empty
|
|
124
126
|
|
|
125
127
|
@property
|
|
126
128
|
def is_empty(self) -> bool:
|
|
@@ -195,23 +197,6 @@ class Cell:
|
|
|
195
197
|
neighborhood.pop(self, None)
|
|
196
198
|
return neighborhood
|
|
197
199
|
|
|
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
200
|
def __getstate__(self):
|
|
216
201
|
"""Return state of the Cell with connections set to empty."""
|
|
217
202
|
# fixme, once we shift to 3.11, replace this with super. __getstate__
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Agents that understand how to exist in and move through cell spaces.
|
|
2
|
+
|
|
3
|
+
Provides specialized agent classes that handle cell occupation, movement, and
|
|
4
|
+
proper registration:
|
|
5
|
+
- CellAgent: Mobile agents that can move between cells
|
|
6
|
+
- FixedAgent: Immobile agents permanently fixed to cells
|
|
7
|
+
- Grid2DMovingAgent: Agents with grid-specific movement capabilities
|
|
8
|
+
|
|
9
|
+
These classes ensure consistent agent-cell relationships and proper state management
|
|
10
|
+
as agents move through the space. They can be used directly or as examples for
|
|
11
|
+
creating custom cell-aware agents.
|
|
12
|
+
"""
|
|
2
13
|
|
|
3
14
|
from __future__ import annotations
|
|
4
15
|
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Collection class for managing and querying groups of cells.
|
|
2
|
+
|
|
3
|
+
The CellCollection class provides a consistent interface for operating on multiple
|
|
4
|
+
cells, supporting:
|
|
5
|
+
- Filtering and selecting cells based on conditions
|
|
6
|
+
- Random cell and agent selection
|
|
7
|
+
- Access to contained agents
|
|
8
|
+
- Group operations
|
|
9
|
+
|
|
10
|
+
This is useful for implementing area effects, zones, or any operation that needs
|
|
11
|
+
to work with multiple cells as a unit. The collection handles efficient iteration
|
|
12
|
+
and agent access across cells. The class is used throughout the cell space
|
|
13
|
+
implementation to represent neighborhoods, selections, and other cell groupings.
|
|
14
|
+
"""
|
|
2
15
|
|
|
3
16
|
from __future__ import annotations
|
|
4
17
|
|
|
@@ -48,8 +61,10 @@ class CellCollection(Generic[T]):
|
|
|
48
61
|
else:
|
|
49
62
|
self._cells = {cell: cell.agents for cell in cells}
|
|
50
63
|
|
|
51
|
-
#
|
|
52
|
-
self._capacity: int =
|
|
64
|
+
# Get capacity from first cell if collection is not empty
|
|
65
|
+
self._capacity: int | None = (
|
|
66
|
+
next(iter(self._cells.keys())).capacity if self._cells else None
|
|
67
|
+
)
|
|
53
68
|
|
|
54
69
|
if random is None:
|
|
55
70
|
warnings.warn(
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Base class for building cell-based spatial environments.
|
|
2
|
+
|
|
3
|
+
DiscreteSpace provides the core functionality needed by all cell-based spaces:
|
|
4
|
+
- Cell creation and tracking
|
|
5
|
+
- Agent-cell relationship management
|
|
6
|
+
- Property layer support
|
|
7
|
+
- Random selection capabilities
|
|
8
|
+
- Capacity management
|
|
9
|
+
|
|
10
|
+
This serves as the foundation for specific space implementations like grids
|
|
11
|
+
and networks, ensuring consistent behavior and shared functionality across
|
|
12
|
+
different space types. All concrete cell space implementations (grids, networks, etc.)
|
|
13
|
+
inherit from this class.
|
|
14
|
+
"""
|
|
2
15
|
|
|
3
16
|
from __future__ import annotations
|
|
4
17
|
|
|
5
18
|
import warnings
|
|
6
|
-
from collections.abc import Callable
|
|
7
19
|
from functools import cached_property
|
|
8
20
|
from random import Random
|
|
9
|
-
from typing import
|
|
21
|
+
from typing import Generic, TypeVar
|
|
10
22
|
|
|
11
23
|
from mesa.agent import AgentSet
|
|
12
24
|
from mesa.experimental.cell_space.cell import Cell
|
|
13
25
|
from mesa.experimental.cell_space.cell_collection import CellCollection
|
|
14
|
-
from mesa.space import PropertyLayer
|
|
15
26
|
|
|
16
27
|
T = TypeVar("T", bound=Cell)
|
|
17
28
|
|
|
@@ -61,8 +72,6 @@ class DiscreteSpace(Generic[T]):
|
|
|
61
72
|
self.cell_klass = cell_klass
|
|
62
73
|
|
|
63
74
|
self._empties: dict[tuple[int, ...], None] = {}
|
|
64
|
-
self._empties_initialized = False
|
|
65
|
-
self.property_layers: dict[str, PropertyLayer] = {}
|
|
66
75
|
|
|
67
76
|
@property
|
|
68
77
|
def cutoff_empties(self): # noqa
|
|
@@ -98,64 +107,6 @@ class DiscreteSpace(Generic[T]):
|
|
|
98
107
|
"""Select random empty cell."""
|
|
99
108
|
return self.random.choice(list(self.empties))
|
|
100
109
|
|
|
101
|
-
# PropertyLayer methods
|
|
102
|
-
def add_property_layer(
|
|
103
|
-
self, property_layer: PropertyLayer, add_to_cells: bool = True
|
|
104
|
-
):
|
|
105
|
-
"""Add a property layer to the grid.
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
property_layer: the property layer to add
|
|
109
|
-
add_to_cells: whether to add the property layer to all cells (default: True)
|
|
110
|
-
"""
|
|
111
|
-
if property_layer.name in self.property_layers:
|
|
112
|
-
raise ValueError(f"Property layer {property_layer.name} already exists.")
|
|
113
|
-
self.property_layers[property_layer.name] = property_layer
|
|
114
|
-
if add_to_cells:
|
|
115
|
-
for cell in self._cells.values():
|
|
116
|
-
cell._mesa_property_layers[property_layer.name] = property_layer
|
|
117
|
-
|
|
118
|
-
def remove_property_layer(self, property_name: str, remove_from_cells: bool = True):
|
|
119
|
-
"""Remove a property layer from the grid.
|
|
120
|
-
|
|
121
|
-
Args:
|
|
122
|
-
property_name: the name of the property layer to remove
|
|
123
|
-
remove_from_cells: whether to remove the property layer from all cells (default: True)
|
|
124
|
-
"""
|
|
125
|
-
del self.property_layers[property_name]
|
|
126
|
-
if remove_from_cells:
|
|
127
|
-
for cell in self._cells.values():
|
|
128
|
-
del cell._mesa_property_layers[property_name]
|
|
129
|
-
|
|
130
|
-
def set_property(
|
|
131
|
-
self, property_name: str, value, condition: Callable[[T], bool] | None = None
|
|
132
|
-
):
|
|
133
|
-
"""Set the value of a property for all cells in the grid.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
property_name: the name of the property to set
|
|
137
|
-
value: the value to set
|
|
138
|
-
condition: a function that takes a cell and returns a boolean
|
|
139
|
-
"""
|
|
140
|
-
self.property_layers[property_name].set_cells(value, condition)
|
|
141
|
-
|
|
142
|
-
def modify_properties(
|
|
143
|
-
self,
|
|
144
|
-
property_name: str,
|
|
145
|
-
operation: Callable,
|
|
146
|
-
value: Any = None,
|
|
147
|
-
condition: Callable[[T], bool] | None = None,
|
|
148
|
-
):
|
|
149
|
-
"""Modify the values of a specific property for all cells in the grid.
|
|
150
|
-
|
|
151
|
-
Args:
|
|
152
|
-
property_name: the name of the property to modify
|
|
153
|
-
operation: the operation to perform
|
|
154
|
-
value: the value to use in the operation
|
|
155
|
-
condition: a function that takes a cell and returns a boolean (used to filter cells)
|
|
156
|
-
"""
|
|
157
|
-
self.property_layers[property_name].modify_cells(operation, value, condition)
|
|
158
|
-
|
|
159
110
|
def __setstate__(self, state):
|
|
160
111
|
"""Set the state of the discrete space and rebuild the connections."""
|
|
161
112
|
self.__dict__ = state
|
|
@@ -1,18 +1,62 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Grid-based cell space implementations with different connection patterns.
|
|
2
|
+
|
|
3
|
+
Provides several grid types for organizing cells:
|
|
4
|
+
- OrthogonalMooreGrid: 8 neighbors in 2D, (3^n)-1 in nD
|
|
5
|
+
- OrthogonalVonNeumannGrid: 4 neighbors in 2D, 2n in nD
|
|
6
|
+
- HexGrid: 6 neighbors in hexagonal pattern (2D only)
|
|
7
|
+
|
|
8
|
+
Each grid type supports optional wrapping (torus) and cell capacity limits.
|
|
9
|
+
Choose based on how movement and connectivity should work in your model -
|
|
10
|
+
Moore for unrestricted movement, Von Neumann for orthogonal-only movement,
|
|
11
|
+
or Hex for more uniform distances.
|
|
12
|
+
"""
|
|
2
13
|
|
|
3
14
|
from __future__ import annotations
|
|
4
15
|
|
|
16
|
+
import copyreg
|
|
5
17
|
from collections.abc import Sequence
|
|
6
18
|
from itertools import product
|
|
7
19
|
from random import Random
|
|
8
|
-
from typing import Generic, TypeVar
|
|
20
|
+
from typing import Any, Generic, TypeVar
|
|
9
21
|
|
|
10
22
|
from mesa.experimental.cell_space import Cell, DiscreteSpace
|
|
23
|
+
from mesa.experimental.cell_space.property_layer import (
|
|
24
|
+
HasPropertyLayers,
|
|
25
|
+
PropertyDescriptor,
|
|
26
|
+
)
|
|
11
27
|
|
|
12
28
|
T = TypeVar("T", bound=Cell)
|
|
13
29
|
|
|
14
30
|
|
|
15
|
-
|
|
31
|
+
def pickle_gridcell(obj):
|
|
32
|
+
"""Helper function for pickling GridCell instances."""
|
|
33
|
+
# we have the base class and the state via __getstate__
|
|
34
|
+
args = obj.__class__.__bases__[0], obj.__getstate__()
|
|
35
|
+
return unpickle_gridcell, args
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def unpickle_gridcell(parent, fields):
|
|
39
|
+
"""Helper function for unpickling GridCell instances."""
|
|
40
|
+
# since the class is dynamically created, we recreate it here
|
|
41
|
+
cell_klass = type(
|
|
42
|
+
"GridCell",
|
|
43
|
+
(parent,),
|
|
44
|
+
{"_mesa_properties": set()},
|
|
45
|
+
)
|
|
46
|
+
instance = cell_klass(
|
|
47
|
+
(0, 0)
|
|
48
|
+
) # we use a default coordinate and overwrite it with the correct value next
|
|
49
|
+
|
|
50
|
+
# __gestate__ returns a tuple with dict and slots, but slots contains the dict so we can just use the
|
|
51
|
+
# second item only
|
|
52
|
+
for k, v in fields[1].items():
|
|
53
|
+
if k != "__dict__":
|
|
54
|
+
setattr(instance, k, v)
|
|
55
|
+
|
|
56
|
+
return instance
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Grid(DiscreteSpace[T], Generic[T], HasPropertyLayers):
|
|
16
60
|
"""Base class for all grid classes.
|
|
17
61
|
|
|
18
62
|
Attributes:
|
|
@@ -60,14 +104,23 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
60
104
|
self._try_random = True
|
|
61
105
|
self._ndims = len(dimensions)
|
|
62
106
|
self._validate_parameters()
|
|
107
|
+
self.cell_klass = type(
|
|
108
|
+
"GridCell",
|
|
109
|
+
(self.cell_klass,),
|
|
110
|
+
{"_mesa_properties": set()},
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# we register the pickle_gridcell helper function
|
|
114
|
+
copyreg.pickle(self.cell_klass, pickle_gridcell)
|
|
63
115
|
|
|
64
116
|
coordinates = product(*(range(dim) for dim in self.dimensions))
|
|
65
117
|
|
|
66
118
|
self._cells = {
|
|
67
|
-
coord: cell_klass(coord, capacity, random=self.random)
|
|
119
|
+
coord: self.cell_klass(coord, capacity, random=self.random)
|
|
68
120
|
for coord in coordinates
|
|
69
121
|
}
|
|
70
122
|
self._connect_cells()
|
|
123
|
+
self.create_property_layer("empty", default_value=True, dtype=bool)
|
|
71
124
|
|
|
72
125
|
def _connect_cells(self) -> None:
|
|
73
126
|
if self._ndims == 2:
|
|
@@ -126,6 +179,23 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
126
179
|
if 0 <= ni < height and 0 <= nj < width:
|
|
127
180
|
cell.connect(self._cells[ni, nj], (di, dj))
|
|
128
181
|
|
|
182
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
183
|
+
"""Custom __getstate__ for handling dynamic GridCell class and PropertyDescriptors."""
|
|
184
|
+
state = super().__getstate__()
|
|
185
|
+
state = {k: v for k, v in state.items() if k != "cell_klass"}
|
|
186
|
+
return state
|
|
187
|
+
|
|
188
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
189
|
+
"""Custom __setstate__ for handling dynamic GridCell class and PropertyDescriptors."""
|
|
190
|
+
self.__dict__ = state
|
|
191
|
+
self._connect_cells() # using super fails for this for some reason, so we repeat ourselves
|
|
192
|
+
|
|
193
|
+
self.cell_klass = type(
|
|
194
|
+
self._cells[(0, 0)]
|
|
195
|
+
) # the __reduce__ function handles this for us nicely
|
|
196
|
+
for layer in self._mesa_property_layers.values():
|
|
197
|
+
setattr(self.cell_klass, layer.name, PropertyDescriptor(layer))
|
|
198
|
+
|
|
129
199
|
|
|
130
200
|
class OrthogonalMooreGrid(Grid[T]):
|
|
131
201
|
"""Grid where cells are connected to their 8 neighbors.
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Network-based cell space using arbitrary connection patterns.
|
|
2
|
+
|
|
3
|
+
Creates spaces where cells connect based on network relationships rather than
|
|
4
|
+
spatial proximity. Built on NetworkX graphs, this enables:
|
|
5
|
+
- Arbitrary connectivity patterns between cells
|
|
6
|
+
- Graph-based neighborhood definitions
|
|
7
|
+
- Logical rather than physical distances
|
|
8
|
+
- Dynamic connectivity changes
|
|
9
|
+
- Integration with NetworkX's graph algorithms
|
|
10
|
+
|
|
11
|
+
Useful for modeling systems like social networks, transportation systems,
|
|
12
|
+
or any environment where connectivity matters more than physical location.
|
|
13
|
+
"""
|
|
2
14
|
|
|
3
15
|
from random import Random
|
|
4
16
|
from typing import Any
|