Mesa 3.1.0__py3-none-any.whl → 3.1.0.dev0__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 +0 -48
- mesa/batchrunner.py +1 -14
- mesa/datacollection.py +6 -1
- mesa/examples/__init__.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -5
- mesa/examples/advanced/pd_grid/app.py +0 -5
- mesa/examples/advanced/sugarscape_g1mt/app.py +2 -7
- mesa/examples/basic/boid_flockers/app.py +0 -5
- mesa/examples/basic/boltzmann_wealth_model/app.py +5 -8
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
- mesa/examples/basic/conways_game_of_life/app.py +0 -5
- mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
- mesa/examples/basic/schelling/app.py +0 -5
- mesa/examples/basic/virus_on_network/app.py +0 -5
- mesa/experimental/UserParam.py +67 -0
- mesa/experimental/__init__.py +10 -17
- mesa/experimental/cell_space/__init__.py +7 -19
- mesa/experimental/cell_space/cell.py +37 -22
- mesa/experimental/cell_space/cell_agent.py +1 -12
- mesa/experimental/cell_space/cell_collection.py +3 -18
- mesa/experimental/cell_space/discrete_space.py +64 -15
- mesa/experimental/cell_space/grid.py +4 -74
- mesa/experimental/cell_space/network.py +1 -13
- mesa/experimental/cell_space/voronoi.py +1 -13
- mesa/experimental/components/altair.py +81 -0
- mesa/experimental/components/matplotlib.py +242 -0
- mesa/experimental/devs/__init__.py +2 -20
- mesa/experimental/devs/eventlist.py +1 -19
- mesa/experimental/devs/examples/epstein_civil_violence.py +305 -0
- mesa/experimental/devs/examples/wolf_sheep.py +250 -0
- mesa/experimental/devs/simulator.py +8 -24
- mesa/experimental/solara_viz.py +453 -0
- mesa/model.py +23 -17
- mesa/visualization/__init__.py +2 -2
- mesa/visualization/mpl_space_drawing.py +2 -2
- mesa/visualization/solara_viz.py +5 -23
- {mesa-3.1.0.dist-info → mesa-3.1.0.dev0.dist-info}/METADATA +1 -1
- {mesa-3.1.0.dist-info → mesa-3.1.0.dev0.dist-info}/RECORD +43 -43
- {mesa-3.1.0.dist-info → mesa-3.1.0.dev0.dist-info}/WHEEL +1 -1
- mesa/experimental/cell_space/property_layer.py +0 -444
- mesa/experimental/mesa_signals/__init__.py +0 -23
- mesa/experimental/mesa_signals/mesa_signal.py +0 -485
- mesa/experimental/mesa_signals/observable_collections.py +0 -133
- mesa/experimental/mesa_signals/signals_util.py +0 -52
- mesa/mesa_logging.py +0 -190
- {mesa-3.1.0.dist-info → mesa-3.1.0.dev0.dist-info}/entry_points.txt +0 -0
- {mesa-3.1.0.dist-info → mesa-3.1.0.dev0.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.0.dist-info → mesa-3.1.0.dev0.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,28 +1,17 @@
|
|
|
1
|
-
"""
|
|
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
|
-
"""
|
|
1
|
+
"""DiscreteSpace base class."""
|
|
15
2
|
|
|
16
3
|
from __future__ import annotations
|
|
17
4
|
|
|
18
5
|
import warnings
|
|
6
|
+
from collections.abc import Callable
|
|
19
7
|
from functools import cached_property
|
|
20
8
|
from random import Random
|
|
21
|
-
from typing import Generic, TypeVar
|
|
9
|
+
from typing import Any, Generic, TypeVar
|
|
22
10
|
|
|
23
11
|
from mesa.agent import AgentSet
|
|
24
12
|
from mesa.experimental.cell_space.cell import Cell
|
|
25
13
|
from mesa.experimental.cell_space.cell_collection import CellCollection
|
|
14
|
+
from mesa.space import PropertyLayer
|
|
26
15
|
|
|
27
16
|
T = TypeVar("T", bound=Cell)
|
|
28
17
|
|
|
@@ -72,6 +61,8 @@ class DiscreteSpace(Generic[T]):
|
|
|
72
61
|
self.cell_klass = cell_klass
|
|
73
62
|
|
|
74
63
|
self._empties: dict[tuple[int, ...], None] = {}
|
|
64
|
+
self._empties_initialized = False
|
|
65
|
+
self.property_layers: dict[str, PropertyLayer] = {}
|
|
75
66
|
|
|
76
67
|
@property
|
|
77
68
|
def cutoff_empties(self): # noqa
|
|
@@ -107,6 +98,64 @@ class DiscreteSpace(Generic[T]):
|
|
|
107
98
|
"""Select random empty cell."""
|
|
108
99
|
return self.random.choice(list(self.empties))
|
|
109
100
|
|
|
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
|
+
|
|
110
159
|
def __setstate__(self, state):
|
|
111
160
|
"""Set the state of the discrete space and rebuild the connections."""
|
|
112
161
|
self.__dict__ = state
|
|
@@ -1,62 +1,18 @@
|
|
|
1
|
-
"""Grid
|
|
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
|
-
"""
|
|
1
|
+
"""Various Grid Spaces."""
|
|
13
2
|
|
|
14
3
|
from __future__ import annotations
|
|
15
4
|
|
|
16
|
-
import copyreg
|
|
17
5
|
from collections.abc import Sequence
|
|
18
6
|
from itertools import product
|
|
19
7
|
from random import Random
|
|
20
|
-
from typing import
|
|
8
|
+
from typing import Generic, TypeVar
|
|
21
9
|
|
|
22
10
|
from mesa.experimental.cell_space import Cell, DiscreteSpace
|
|
23
|
-
from mesa.experimental.cell_space.property_layer import (
|
|
24
|
-
HasPropertyLayers,
|
|
25
|
-
PropertyDescriptor,
|
|
26
|
-
)
|
|
27
11
|
|
|
28
12
|
T = TypeVar("T", bound=Cell)
|
|
29
13
|
|
|
30
14
|
|
|
31
|
-
|
|
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):
|
|
15
|
+
class Grid(DiscreteSpace[T], Generic[T]):
|
|
60
16
|
"""Base class for all grid classes.
|
|
61
17
|
|
|
62
18
|
Attributes:
|
|
@@ -104,23 +60,14 @@ class Grid(DiscreteSpace[T], Generic[T], HasPropertyLayers):
|
|
|
104
60
|
self._try_random = True
|
|
105
61
|
self._ndims = len(dimensions)
|
|
106
62
|
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)
|
|
115
63
|
|
|
116
64
|
coordinates = product(*(range(dim) for dim in self.dimensions))
|
|
117
65
|
|
|
118
66
|
self._cells = {
|
|
119
|
-
coord:
|
|
67
|
+
coord: cell_klass(coord, capacity, random=self.random)
|
|
120
68
|
for coord in coordinates
|
|
121
69
|
}
|
|
122
70
|
self._connect_cells()
|
|
123
|
-
self.create_property_layer("empty", default_value=True, dtype=bool)
|
|
124
71
|
|
|
125
72
|
def _connect_cells(self) -> None:
|
|
126
73
|
if self._ndims == 2:
|
|
@@ -179,23 +126,6 @@ class Grid(DiscreteSpace[T], Generic[T], HasPropertyLayers):
|
|
|
179
126
|
if 0 <= ni < height and 0 <= nj < width:
|
|
180
127
|
cell.connect(self._cells[ni, nj], (di, dj))
|
|
181
128
|
|
|
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
|
-
|
|
199
129
|
|
|
200
130
|
class OrthogonalMooreGrid(Grid[T]):
|
|
201
131
|
"""Grid where cells are connected to their 8 neighbors.
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
"""Network
|
|
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
|
-
"""
|
|
1
|
+
"""A Network grid."""
|
|
14
2
|
|
|
15
3
|
from random import Random
|
|
16
4
|
from typing import Any
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
Creates irregular spatial divisions by building cells around seed points,
|
|
4
|
-
where each cell contains the area closer to its seed than any other.
|
|
5
|
-
Features:
|
|
6
|
-
- Organic-looking spaces from point sets
|
|
7
|
-
- Automatic neighbor determination
|
|
8
|
-
- Area-based cell capacities
|
|
9
|
-
- Natural regional divisions
|
|
10
|
-
|
|
11
|
-
Useful for models requiring irregular but mathematically meaningful spatial
|
|
12
|
-
divisions, like territories, service areas, or natural regions.
|
|
13
|
-
"""
|
|
1
|
+
"""Support for Voronoi meshed grids."""
|
|
14
2
|
|
|
15
3
|
from collections.abc import Sequence
|
|
16
4
|
from itertools import combinations
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Altair components."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
|
|
5
|
+
import solara
|
|
6
|
+
|
|
7
|
+
with contextlib.suppress(ImportError):
|
|
8
|
+
import altair as alt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@solara.component
|
|
12
|
+
def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
13
|
+
"""A component that renders a Space using Altair.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
model: a model instance
|
|
17
|
+
agent_portrayal: agent portray specification
|
|
18
|
+
dependencies: optional list of dependencies (currently not used)
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
space = getattr(model, "grid", None)
|
|
22
|
+
if space is None:
|
|
23
|
+
# Sometimes the space is defined as model.space instead of model.grid
|
|
24
|
+
space = model.space
|
|
25
|
+
chart = _draw_grid(space, agent_portrayal)
|
|
26
|
+
solara.FigureAltair(chart)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _draw_grid(space, agent_portrayal):
|
|
30
|
+
def portray(g):
|
|
31
|
+
all_agent_data = []
|
|
32
|
+
for content, (x, y) in g.coord_iter():
|
|
33
|
+
if not content:
|
|
34
|
+
continue
|
|
35
|
+
if not hasattr(content, "__iter__"):
|
|
36
|
+
# Is a single grid
|
|
37
|
+
content = [content] # noqa: PLW2901
|
|
38
|
+
for agent in content:
|
|
39
|
+
# use all data from agent portrayal, and add x,y coordinates
|
|
40
|
+
agent_data = agent_portrayal(agent)
|
|
41
|
+
agent_data["x"] = x
|
|
42
|
+
agent_data["y"] = y
|
|
43
|
+
all_agent_data.append(agent_data)
|
|
44
|
+
return all_agent_data
|
|
45
|
+
|
|
46
|
+
all_agent_data = portray(space)
|
|
47
|
+
invalid_tooltips = ["color", "size", "x", "y"]
|
|
48
|
+
|
|
49
|
+
encoding_dict = {
|
|
50
|
+
# no x-axis label
|
|
51
|
+
"x": alt.X("x", axis=None, type="ordinal"),
|
|
52
|
+
# no y-axis label
|
|
53
|
+
"y": alt.Y("y", axis=None, type="ordinal"),
|
|
54
|
+
"tooltip": [
|
|
55
|
+
alt.Tooltip(key, type=alt.utils.infer_vegalite_type([value]))
|
|
56
|
+
for key, value in all_agent_data[0].items()
|
|
57
|
+
if key not in invalid_tooltips
|
|
58
|
+
],
|
|
59
|
+
}
|
|
60
|
+
has_color = "color" in all_agent_data[0]
|
|
61
|
+
if has_color:
|
|
62
|
+
encoding_dict["color"] = alt.Color("color", type="nominal")
|
|
63
|
+
has_size = "size" in all_agent_data[0]
|
|
64
|
+
if has_size:
|
|
65
|
+
encoding_dict["size"] = alt.Size("size", type="quantitative")
|
|
66
|
+
|
|
67
|
+
chart = (
|
|
68
|
+
alt.Chart(
|
|
69
|
+
alt.Data(values=all_agent_data), encoding=alt.Encoding(**encoding_dict)
|
|
70
|
+
)
|
|
71
|
+
.mark_point(filled=True)
|
|
72
|
+
.properties(width=280, height=280)
|
|
73
|
+
# .configure_view(strokeOpacity=0) # hide grid/chart lines
|
|
74
|
+
)
|
|
75
|
+
# This is the default value for the marker size, which auto-scales
|
|
76
|
+
# according to the grid area.
|
|
77
|
+
if not has_size:
|
|
78
|
+
length = min(space.width, space.height)
|
|
79
|
+
chart = chart.mark_point(size=30000 / length**2, filled=True)
|
|
80
|
+
|
|
81
|
+
return chart
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Support for using matplotlib to draw spaces."""
|
|
2
|
+
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
import networkx as nx
|
|
6
|
+
import solara
|
|
7
|
+
from matplotlib.figure import Figure
|
|
8
|
+
from matplotlib.ticker import MaxNLocator
|
|
9
|
+
|
|
10
|
+
import mesa
|
|
11
|
+
from mesa.experimental.cell_space import VoronoiGrid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@solara.component
|
|
15
|
+
def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
16
|
+
"""A component for rendering a space using Matplotlib.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
model: a model instance
|
|
20
|
+
agent_portrayal: a specification of how to portray an agent.
|
|
21
|
+
dependencies: list of dependencies.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
space_fig = Figure()
|
|
25
|
+
space_ax = space_fig.subplots()
|
|
26
|
+
space = getattr(model, "grid", None)
|
|
27
|
+
if space is None:
|
|
28
|
+
# Sometimes the space is defined as model.space instead of model.grid
|
|
29
|
+
space = model.space
|
|
30
|
+
if isinstance(space, mesa.space.NetworkGrid):
|
|
31
|
+
_draw_network_grid(space, space_ax, agent_portrayal)
|
|
32
|
+
elif isinstance(space, mesa.space.ContinuousSpace):
|
|
33
|
+
_draw_continuous_space(space, space_ax, agent_portrayal)
|
|
34
|
+
elif isinstance(space, VoronoiGrid):
|
|
35
|
+
_draw_voronoi(space, space_ax, agent_portrayal)
|
|
36
|
+
else:
|
|
37
|
+
_draw_grid(space, space_ax, agent_portrayal)
|
|
38
|
+
solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# matplotlib scatter does not allow for multiple shapes in one call
|
|
42
|
+
def _split_and_scatter(portray_data, space_ax):
|
|
43
|
+
grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
|
|
44
|
+
|
|
45
|
+
# Extract data from the dictionary
|
|
46
|
+
x = portray_data["x"]
|
|
47
|
+
y = portray_data["y"]
|
|
48
|
+
s = portray_data["s"]
|
|
49
|
+
c = portray_data["c"]
|
|
50
|
+
m = portray_data["m"]
|
|
51
|
+
|
|
52
|
+
if not (len(x) == len(y) == len(s) == len(c) == len(m)):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"Length mismatch in portrayal data lists: "
|
|
55
|
+
f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
|
|
56
|
+
f"color: {len(c)}, marker: {len(m)}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Group the data by marker
|
|
60
|
+
for i in range(len(x)):
|
|
61
|
+
marker = m[i]
|
|
62
|
+
grouped_data[marker]["x"].append(x[i])
|
|
63
|
+
grouped_data[marker]["y"].append(y[i])
|
|
64
|
+
grouped_data[marker]["s"].append(s[i])
|
|
65
|
+
grouped_data[marker]["c"].append(c[i])
|
|
66
|
+
|
|
67
|
+
# Plot each group with the same marker
|
|
68
|
+
for marker, data in grouped_data.items():
|
|
69
|
+
space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _draw_grid(space, space_ax, agent_portrayal):
|
|
73
|
+
def portray(g):
|
|
74
|
+
x = []
|
|
75
|
+
y = []
|
|
76
|
+
s = [] # size
|
|
77
|
+
c = [] # color
|
|
78
|
+
m = [] # shape
|
|
79
|
+
for i in range(g.width):
|
|
80
|
+
for j in range(g.height):
|
|
81
|
+
content = g._grid[i][j]
|
|
82
|
+
if not content:
|
|
83
|
+
continue
|
|
84
|
+
if not hasattr(content, "__iter__"):
|
|
85
|
+
# Is a single grid
|
|
86
|
+
content = [content]
|
|
87
|
+
for agent in content:
|
|
88
|
+
data = agent_portrayal(agent)
|
|
89
|
+
x.append(i)
|
|
90
|
+
y.append(j)
|
|
91
|
+
|
|
92
|
+
# This is the default value for the marker size, which auto-scales
|
|
93
|
+
# according to the grid area.
|
|
94
|
+
default_size = (180 / max(g.width, g.height)) ** 2
|
|
95
|
+
# establishing a default prevents misalignment if some agents are not given size, color, etc.
|
|
96
|
+
size = data.get("size", default_size)
|
|
97
|
+
s.append(size)
|
|
98
|
+
color = data.get("color", "tab:blue")
|
|
99
|
+
c.append(color)
|
|
100
|
+
mark = data.get("shape", "o")
|
|
101
|
+
m.append(mark)
|
|
102
|
+
out = {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
103
|
+
return out
|
|
104
|
+
|
|
105
|
+
space_ax.set_xlim(-1, space.width)
|
|
106
|
+
space_ax.set_ylim(-1, space.height)
|
|
107
|
+
_split_and_scatter(portray(space), space_ax)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _draw_network_grid(space, space_ax, agent_portrayal):
|
|
111
|
+
graph = space.G
|
|
112
|
+
pos = nx.spring_layout(graph, seed=0)
|
|
113
|
+
nx.draw(
|
|
114
|
+
graph,
|
|
115
|
+
ax=space_ax,
|
|
116
|
+
pos=pos,
|
|
117
|
+
**agent_portrayal(graph),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _draw_continuous_space(space, space_ax, agent_portrayal):
|
|
122
|
+
def portray(space):
|
|
123
|
+
x = []
|
|
124
|
+
y = []
|
|
125
|
+
s = [] # size
|
|
126
|
+
c = [] # color
|
|
127
|
+
m = [] # shape
|
|
128
|
+
for agent in space._agent_to_index:
|
|
129
|
+
data = agent_portrayal(agent)
|
|
130
|
+
_x, _y = agent.pos
|
|
131
|
+
x.append(_x)
|
|
132
|
+
y.append(_y)
|
|
133
|
+
|
|
134
|
+
# This is matplotlib's default marker size
|
|
135
|
+
default_size = 20
|
|
136
|
+
# establishing a default prevents misalignment if some agents are not given size, color, etc.
|
|
137
|
+
size = data.get("size", default_size)
|
|
138
|
+
s.append(size)
|
|
139
|
+
color = data.get("color", "tab:blue")
|
|
140
|
+
c.append(color)
|
|
141
|
+
mark = data.get("shape", "o")
|
|
142
|
+
m.append(mark)
|
|
143
|
+
out = {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
144
|
+
return out
|
|
145
|
+
|
|
146
|
+
# Determine border style based on space.torus
|
|
147
|
+
border_style = "solid" if not space.torus else (0, (5, 10))
|
|
148
|
+
|
|
149
|
+
# Set the border of the plot
|
|
150
|
+
for spine in space_ax.spines.values():
|
|
151
|
+
spine.set_linewidth(1.5)
|
|
152
|
+
spine.set_color("black")
|
|
153
|
+
spine.set_linestyle(border_style)
|
|
154
|
+
|
|
155
|
+
width = space.x_max - space.x_min
|
|
156
|
+
x_padding = width / 20
|
|
157
|
+
height = space.y_max - space.y_min
|
|
158
|
+
y_padding = height / 20
|
|
159
|
+
space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
|
|
160
|
+
space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
|
|
161
|
+
|
|
162
|
+
# Portray and scatter the agents in the space
|
|
163
|
+
_split_and_scatter(portray(space), space_ax)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _draw_voronoi(space, space_ax, agent_portrayal):
|
|
167
|
+
def portray(g):
|
|
168
|
+
x = []
|
|
169
|
+
y = []
|
|
170
|
+
s = [] # size
|
|
171
|
+
c = [] # color
|
|
172
|
+
|
|
173
|
+
for cell in g.all_cells:
|
|
174
|
+
for agent in cell.agents:
|
|
175
|
+
data = agent_portrayal(agent)
|
|
176
|
+
x.append(cell.coordinate[0])
|
|
177
|
+
y.append(cell.coordinate[1])
|
|
178
|
+
if "size" in data:
|
|
179
|
+
s.append(data["size"])
|
|
180
|
+
if "color" in data:
|
|
181
|
+
c.append(data["color"])
|
|
182
|
+
out = {"x": x, "y": y}
|
|
183
|
+
# This is the default value for the marker size, which auto-scales
|
|
184
|
+
# according to the grid area.
|
|
185
|
+
out["s"] = s
|
|
186
|
+
if len(c) > 0:
|
|
187
|
+
out["c"] = c
|
|
188
|
+
|
|
189
|
+
return out
|
|
190
|
+
|
|
191
|
+
x_list = [i[0] for i in space.centroids_coordinates]
|
|
192
|
+
y_list = [i[1] for i in space.centroids_coordinates]
|
|
193
|
+
x_max = max(x_list)
|
|
194
|
+
x_min = min(x_list)
|
|
195
|
+
y_max = max(y_list)
|
|
196
|
+
y_min = min(y_list)
|
|
197
|
+
|
|
198
|
+
width = x_max - x_min
|
|
199
|
+
x_padding = width / 20
|
|
200
|
+
height = y_max - y_min
|
|
201
|
+
y_padding = height / 20
|
|
202
|
+
space_ax.set_xlim(x_min - x_padding, x_max + x_padding)
|
|
203
|
+
space_ax.set_ylim(y_min - y_padding, y_max + y_padding)
|
|
204
|
+
space_ax.scatter(**portray(space))
|
|
205
|
+
|
|
206
|
+
for cell in space.all_cells:
|
|
207
|
+
polygon = cell.properties["polygon"]
|
|
208
|
+
space_ax.fill(
|
|
209
|
+
*zip(*polygon),
|
|
210
|
+
alpha=min(1, cell.properties[space.cell_coloring_property]),
|
|
211
|
+
c="red",
|
|
212
|
+
) # Plot filled polygon
|
|
213
|
+
space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@solara.component
|
|
217
|
+
def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
|
|
218
|
+
"""A solara component for creating a matplotlib figure.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
model: Model instance
|
|
222
|
+
measure: measure to plot
|
|
223
|
+
dependencies: list of additional dependencies
|
|
224
|
+
|
|
225
|
+
"""
|
|
226
|
+
fig = Figure()
|
|
227
|
+
ax = fig.subplots()
|
|
228
|
+
df = model.datacollector.get_model_vars_dataframe()
|
|
229
|
+
if isinstance(measure, str):
|
|
230
|
+
ax.plot(df.loc[:, measure])
|
|
231
|
+
ax.set_ylabel(measure)
|
|
232
|
+
elif isinstance(measure, dict):
|
|
233
|
+
for m, color in measure.items():
|
|
234
|
+
ax.plot(df.loc[:, m], label=m, color=color)
|
|
235
|
+
fig.legend()
|
|
236
|
+
elif isinstance(measure, list | tuple):
|
|
237
|
+
for m in measure:
|
|
238
|
+
ax.plot(df.loc[:, m], label=m)
|
|
239
|
+
fig.legend()
|
|
240
|
+
# Set integer x axis
|
|
241
|
+
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
242
|
+
solara.FigureMatplotlib(fig, dependencies=dependencies)
|
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
This module provides the foundational data structures and classes needed for event-based
|
|
4
|
-
simulation in Mesa. The EventList class is a priority queue implementation that maintains
|
|
5
|
-
simulation events in chronological order while respecting event priorities. Key features:
|
|
6
|
-
|
|
7
|
-
- Priority-based event ordering
|
|
8
|
-
- Weak references to prevent memory leaks from canceled events
|
|
9
|
-
- Efficient event insertion and removal using a heap queue
|
|
10
|
-
- Support for event cancellation without breaking the heap structure
|
|
11
|
-
|
|
12
|
-
The module contains three main components:
|
|
13
|
-
- Priority: An enumeration defining event priority levels (HIGH, DEFAULT, LOW)
|
|
14
|
-
- SimulationEvent: A class representing individual events with timing and execution details
|
|
15
|
-
- EventList: A heap-based priority queue managing the chronological ordering of events
|
|
16
|
-
|
|
17
|
-
The implementation supports both pure discrete event simulation and hybrid approaches
|
|
18
|
-
combining agent-based modeling with event scheduling.
|
|
19
|
-
"""
|
|
1
|
+
"""Support for event scheduling."""
|
|
20
2
|
|
|
21
3
|
from .eventlist import Priority, SimulationEvent
|
|
22
4
|
from .simulator import ABMSimulator, DEVSimulator
|
|
23
5
|
|
|
24
|
-
__all__ = ["ABMSimulator", "DEVSimulator", "
|
|
6
|
+
__all__ = ["ABMSimulator", "DEVSimulator", "SimulationEvent", "Priority"]
|
|
@@ -1,22 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
This module provides the foundational data structures and classes needed for event-based
|
|
4
|
-
simulation in Mesa. The EventList class is a priority queue implementation that maintains
|
|
5
|
-
simulation events in chronological order while respecting event priorities. Key features:
|
|
6
|
-
|
|
7
|
-
- Priority-based event ordering
|
|
8
|
-
- Weak references to prevent memory leaks from canceled events
|
|
9
|
-
- Efficient event insertion and removal using a heap queue
|
|
10
|
-
- Support for event cancellation without breaking the heap structure
|
|
11
|
-
|
|
12
|
-
The module contains three main components:
|
|
13
|
-
- Priority: An enumeration defining event priority levels (HIGH, DEFAULT, LOW)
|
|
14
|
-
- SimulationEvent: A class representing individual events with timing and execution details
|
|
15
|
-
- EventList: A heap-based priority queue managing the chronological ordering of events
|
|
16
|
-
|
|
17
|
-
The implementation supports both pure discrete event simulation and hybrid approaches
|
|
18
|
-
combining agent-based modeling with event scheduling.
|
|
19
|
-
"""
|
|
1
|
+
"""Eventlist which is at the core of event scheduling."""
|
|
20
2
|
|
|
21
3
|
from __future__ import annotations
|
|
22
4
|
|