Mesa 3.1.3__py3-none-any.whl → 3.1.4__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.
- mesa/__init__.py +1 -1
- mesa/datacollection.py +62 -2
- mesa/examples/advanced/sugarscape_g1mt/app.py +15 -37
- mesa/experimental/cell_space/grid.py +8 -8
- mesa/visualization/__init__.py +1 -2
- mesa/visualization/components/altair_components.py +10 -8
- mesa/visualization/mpl_space_drawing.py +139 -90
- mesa/visualization/solara_viz.py +5 -1
- {mesa-3.1.3.dist-info → mesa-3.1.4.dist-info}/METADATA +13 -10
- {mesa-3.1.3.dist-info → mesa-3.1.4.dist-info}/RECORD +13 -14
- mesa-3.1.3.dist-info/entry_points.txt +0 -2
- {mesa-3.1.3.dist-info → mesa-3.1.4.dist-info}/WHEEL +0 -0
- {mesa-3.1.3.dist-info → mesa-3.1.4.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.3.dist-info → mesa-3.1.4.dist-info}/licenses/NOTICE +0 -0
mesa/__init__.py
CHANGED
mesa/datacollection.py
CHANGED
|
@@ -117,6 +117,9 @@ class DataCollector:
|
|
|
117
117
|
self._agenttype_records = {}
|
|
118
118
|
self.tables = {}
|
|
119
119
|
|
|
120
|
+
# add the signal of the validation of model reporter
|
|
121
|
+
self._validated = False
|
|
122
|
+
|
|
120
123
|
if model_reporters is not None:
|
|
121
124
|
for name, reporter in model_reporters.items():
|
|
122
125
|
self._new_model_reporter(name, reporter)
|
|
@@ -134,13 +137,66 @@ class DataCollector:
|
|
|
134
137
|
for name, columns in tables.items():
|
|
135
138
|
self._new_table(name, columns)
|
|
136
139
|
|
|
140
|
+
def _validate_model_reporter(self, name, reporter, model):
|
|
141
|
+
"""Validate model reporter and handle validation results appropriately.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
name: Name of the reporter
|
|
145
|
+
reporter: Reporter definition (lambda/method/attribute/function list)
|
|
146
|
+
model: Model instance
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ValueError: If reporter is None or has invalid format
|
|
150
|
+
AttributeError: If model attribute doesn't exist
|
|
151
|
+
TypeError: If reporter type is not supported
|
|
152
|
+
RuntimeError: If reporter execution fails
|
|
153
|
+
"""
|
|
154
|
+
self._validated = True # put the change of signal firstly avoid losing efficacy
|
|
155
|
+
|
|
156
|
+
# Type 1: Lambda function
|
|
157
|
+
if isinstance(reporter, types.LambdaType):
|
|
158
|
+
try:
|
|
159
|
+
reporter(model)
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
f"Lambda reporter '{name}' failed validation: {e!s}\n"
|
|
163
|
+
f"Example: lambda m: len(m.agents)"
|
|
164
|
+
) from e
|
|
165
|
+
|
|
166
|
+
# Type 2: Method of class/instance
|
|
167
|
+
if not callable(reporter) and not isinstance(reporter, types.LambdaType):
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
# Type 3: Model attribute (string)
|
|
171
|
+
if isinstance(reporter, str):
|
|
172
|
+
try:
|
|
173
|
+
if not hasattr(model, reporter):
|
|
174
|
+
raise AttributeError(
|
|
175
|
+
f"Model reporter '{name}' references non-existent attribute '{reporter}'\n"
|
|
176
|
+
)
|
|
177
|
+
getattr(model, reporter) # 验证属性是否可访问
|
|
178
|
+
except AttributeError as e:
|
|
179
|
+
raise AttributeError(
|
|
180
|
+
f"Model reporter '{name}' attribute validation failed: {e!s}\n"
|
|
181
|
+
) from e
|
|
182
|
+
|
|
183
|
+
# Type 4: Function with parameters in list
|
|
184
|
+
if isinstance(reporter, list) and (not reporter or not callable(reporter[0])):
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"Invalid function list format for reporter '{name}'\n"
|
|
187
|
+
f"Expected: [function, [param1, param2]], got: {reporter}"
|
|
188
|
+
)
|
|
189
|
+
|
|
137
190
|
def _new_model_reporter(self, name, reporter):
|
|
138
191
|
"""Add a new model-level reporter to collect.
|
|
139
192
|
|
|
140
193
|
Args:
|
|
141
194
|
name: Name of the model-level variable to collect.
|
|
142
|
-
reporter:
|
|
143
|
-
|
|
195
|
+
reporter: Can be one of four types:
|
|
196
|
+
1. Attribute name (str): "attribute_name"
|
|
197
|
+
2. Lambda function: lambda m: len(m.agents)
|
|
198
|
+
3. Method: model.get_count or Model.get_count
|
|
199
|
+
4. List of [function, [parameters]]
|
|
144
200
|
"""
|
|
145
201
|
self.model_reporters[name] = reporter
|
|
146
202
|
self.model_vars[name] = []
|
|
@@ -262,6 +318,10 @@ class DataCollector:
|
|
|
262
318
|
def collect(self, model):
|
|
263
319
|
"""Collect all the data for the given model object."""
|
|
264
320
|
if self.model_reporters:
|
|
321
|
+
if not self._validated:
|
|
322
|
+
for name, reporter in self.model_reporters.items():
|
|
323
|
+
self._validate_model_reporter(name, reporter, model)
|
|
324
|
+
|
|
265
325
|
for var, reporter in self.model_reporters.items():
|
|
266
326
|
# Check if lambda or partial function
|
|
267
327
|
if isinstance(reporter, types.LambdaType | partial):
|
|
@@ -1,46 +1,24 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import solara
|
|
3
|
-
from matplotlib.figure import Figure
|
|
4
|
-
|
|
5
1
|
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
|
|
6
2
|
from mesa.visualization import Slider, SolaraViz, make_plot_component
|
|
3
|
+
from mesa.visualization.components.matplotlib_components import make_mpl_space_component
|
|
4
|
+
|
|
7
5
|
|
|
6
|
+
def agent_portrayal(agent):
|
|
7
|
+
return {"marker": "o", "color": "red", "size": 10}
|
|
8
8
|
|
|
9
|
-
def SpaceDrawer(model):
|
|
10
|
-
def portray(g):
|
|
11
|
-
layers = {
|
|
12
|
-
"trader": {"x": [], "y": [], "c": "tab:red", "marker": "o", "s": 10},
|
|
13
|
-
}
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return layers
|
|
10
|
+
propertylayer_portrayal = {
|
|
11
|
+
"sugar": {"color": "blue", "alpha": 0.8, "colorbar": True, "vmin": 0, "vmax": 10},
|
|
12
|
+
"spice": {"color": "red", "alpha": 0.8, "colorbar": True, "vmin": 0, "vmax": 10},
|
|
13
|
+
}
|
|
20
14
|
|
|
21
|
-
fig = Figure()
|
|
22
|
-
ax = fig.subplots()
|
|
23
|
-
out = portray(model.grid)
|
|
24
|
-
# Sugar
|
|
25
|
-
# Important note: imshow by default draws from upper left. You have to
|
|
26
|
-
# always explicitly specify origin="lower".
|
|
27
|
-
im = ax.imshow(
|
|
28
|
-
np.ma.masked_where(model.grid.sugar.data <= 1, model.grid.sugar.data),
|
|
29
|
-
cmap="spring",
|
|
30
|
-
origin="lower",
|
|
31
|
-
)
|
|
32
|
-
fig.colorbar(im, orientation="vertical")
|
|
33
|
-
# Spice
|
|
34
|
-
ax.imshow(
|
|
35
|
-
np.ma.masked_where(model.grid.spice.data <= 1, model.grid.spice.data),
|
|
36
|
-
cmap="winter",
|
|
37
|
-
origin="lower",
|
|
38
|
-
)
|
|
39
|
-
# Trader
|
|
40
|
-
ax.scatter(**out["trader"])
|
|
41
|
-
ax.set_axis_off()
|
|
42
|
-
return solara.FigureMatplotlib(fig)
|
|
43
15
|
|
|
16
|
+
sugarscape_space = make_mpl_space_component(
|
|
17
|
+
agent_portrayal=agent_portrayal,
|
|
18
|
+
propertylayer_portrayal=propertylayer_portrayal,
|
|
19
|
+
post_process=None,
|
|
20
|
+
draw_grid=False,
|
|
21
|
+
)
|
|
44
22
|
|
|
45
23
|
model_params = {
|
|
46
24
|
"seed": {
|
|
@@ -72,7 +50,7 @@ model = SugarscapeG1mt()
|
|
|
72
50
|
page = SolaraViz(
|
|
73
51
|
model,
|
|
74
52
|
components=[
|
|
75
|
-
|
|
53
|
+
sugarscape_space,
|
|
76
54
|
make_plot_component("#Traders"),
|
|
77
55
|
make_plot_component("Price"),
|
|
78
56
|
],
|
|
@@ -273,20 +273,20 @@ class HexGrid(Grid[T]):
|
|
|
273
273
|
def _connect_cells_2d(self) -> None:
|
|
274
274
|
# fmt: off
|
|
275
275
|
even_offsets = [
|
|
276
|
-
(-1, -1), (-1
|
|
277
|
-
(
|
|
278
|
-
( 1,
|
|
276
|
+
(-1, -1), (0, -1),
|
|
277
|
+
( -1, 0), ( 1, 0),
|
|
278
|
+
( -1, 1), (0, 1),
|
|
279
279
|
]
|
|
280
280
|
odd_offsets = [
|
|
281
|
-
(-1
|
|
282
|
-
(
|
|
283
|
-
(
|
|
281
|
+
(0, -1), (1, -1),
|
|
282
|
+
( -1, 0), ( 1, 0),
|
|
283
|
+
( 0, 1), ( 1, 1),
|
|
284
284
|
]
|
|
285
285
|
# fmt: on
|
|
286
286
|
|
|
287
287
|
for cell in self.all_cells:
|
|
288
|
-
i = cell.coordinate[
|
|
289
|
-
offsets = even_offsets if i % 2
|
|
288
|
+
i = cell.coordinate[1]
|
|
289
|
+
offsets = even_offsets if i % 2 else odd_offsets
|
|
290
290
|
self._connect_single_cell_2d(cell, offsets=offsets)
|
|
291
291
|
|
|
292
292
|
def _connect_cells_nd(self) -> None:
|
mesa/visualization/__init__.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
"""Solara based visualization for Mesa models.
|
|
2
2
|
|
|
3
3
|
.. note::
|
|
4
|
-
SolaraViz is experimental and still in active development
|
|
4
|
+
SolaraViz is experimental and still in active development in Mesa 3.x. While we attempt to minimize them, there might be API breaking changes in minor releases.
|
|
5
5
|
|
|
6
|
-
There won't be breaking changes between Mesa 3.0.x patch releases.
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
8
|
from mesa.visualization.mpl_space_drawing import (
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
"""Altair based solara components for visualization mesa spaces."""
|
|
2
2
|
|
|
3
|
-
import contextlib
|
|
4
3
|
import warnings
|
|
5
4
|
|
|
5
|
+
import altair as alt
|
|
6
6
|
import solara
|
|
7
7
|
|
|
8
|
-
with contextlib.suppress(ImportError):
|
|
9
|
-
import altair as alt
|
|
10
|
-
|
|
11
8
|
from mesa.experimental.cell_space import DiscreteSpace, Grid
|
|
12
9
|
from mesa.space import ContinuousSpace, _Grid
|
|
13
10
|
from mesa.visualization.utils import update_counter
|
|
@@ -30,7 +27,7 @@ def make_altair_space(
|
|
|
30
27
|
Args:
|
|
31
28
|
agent_portrayal: Function to portray agents.
|
|
32
29
|
propertylayer_portrayal: not yet implemented
|
|
33
|
-
post_process :
|
|
30
|
+
post_process :A user specified callable that will be called with the Chart instance from Altair. Allows for fine tuning plots (e.g., control ticks)
|
|
34
31
|
space_drawing_kwargs : not yet implemented
|
|
35
32
|
|
|
36
33
|
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
@@ -46,13 +43,15 @@ def make_altair_space(
|
|
|
46
43
|
return {"id": a.unique_id}
|
|
47
44
|
|
|
48
45
|
def MakeSpaceAltair(model):
|
|
49
|
-
return SpaceAltair(model, agent_portrayal)
|
|
46
|
+
return SpaceAltair(model, agent_portrayal, post_process=post_process)
|
|
50
47
|
|
|
51
48
|
return MakeSpaceAltair
|
|
52
49
|
|
|
53
50
|
|
|
54
51
|
@solara.component
|
|
55
|
-
def SpaceAltair(
|
|
52
|
+
def SpaceAltair(
|
|
53
|
+
model, agent_portrayal, dependencies: list[any] | None = None, post_process=None
|
|
54
|
+
):
|
|
56
55
|
"""Create an Altair-based space visualization component.
|
|
57
56
|
|
|
58
57
|
Returns:
|
|
@@ -65,6 +64,9 @@ def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
|
65
64
|
space = model.space
|
|
66
65
|
|
|
67
66
|
chart = _draw_grid(space, agent_portrayal)
|
|
67
|
+
# Apply post-processing if provided
|
|
68
|
+
if post_process is not None:
|
|
69
|
+
chart = post_process(chart)
|
|
68
70
|
solara.FigureAltair(chart)
|
|
69
71
|
|
|
70
72
|
|
|
@@ -159,7 +161,7 @@ def _draw_grid(space, agent_portrayal):
|
|
|
159
161
|
# no y-axis label
|
|
160
162
|
"y": alt.Y("y", axis=None, type=x_y_type),
|
|
161
163
|
"tooltip": [
|
|
162
|
-
alt.Tooltip(key, type=alt.utils.
|
|
164
|
+
alt.Tooltip(key, type=alt.utils.infer_vegalite_type_for_pandas([value]))
|
|
163
165
|
for key, value in all_agent_data[0].items()
|
|
164
166
|
if key not in invalid_tooltips
|
|
165
167
|
],
|
|
@@ -8,9 +8,10 @@ for a paper.
|
|
|
8
8
|
|
|
9
9
|
import contextlib
|
|
10
10
|
import itertools
|
|
11
|
-
import math
|
|
12
11
|
import warnings
|
|
13
12
|
from collections.abc import Callable
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from itertools import pairwise
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
16
17
|
import networkx as nx
|
|
@@ -18,9 +19,9 @@ import numpy as np
|
|
|
18
19
|
from matplotlib import pyplot as plt
|
|
19
20
|
from matplotlib.axes import Axes
|
|
20
21
|
from matplotlib.cm import ScalarMappable
|
|
21
|
-
from matplotlib.collections import PatchCollection
|
|
22
|
+
from matplotlib.collections import LineCollection, PatchCollection, PolyCollection
|
|
22
23
|
from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
|
|
23
|
-
from matplotlib.patches import Polygon
|
|
24
|
+
from matplotlib.patches import Polygon
|
|
24
25
|
|
|
25
26
|
import mesa
|
|
26
27
|
from mesa.experimental.cell_space import (
|
|
@@ -159,6 +160,40 @@ def draw_space(
|
|
|
159
160
|
return ax
|
|
160
161
|
|
|
161
162
|
|
|
163
|
+
@lru_cache(maxsize=1024, typed=True)
|
|
164
|
+
def _get_hexmesh(
|
|
165
|
+
width: int, height: int, size: float = 1.0
|
|
166
|
+
) -> list[tuple[float, float]]:
|
|
167
|
+
"""Generate hexagon vertices for the mesh. Yields list of vertex coordinates for each hexagon."""
|
|
168
|
+
|
|
169
|
+
# Helper function for getting the vertices of a hexagon given the center and size
|
|
170
|
+
def _get_hex_vertices(
|
|
171
|
+
center_x: float, center_y: float, size: float = 1.0
|
|
172
|
+
) -> list[tuple[float, float]]:
|
|
173
|
+
"""Get vertices for a hexagon centered at (center_x, center_y)."""
|
|
174
|
+
vertices = [
|
|
175
|
+
(center_x, center_y + size), # top
|
|
176
|
+
(center_x + size * np.sqrt(3) / 2, center_y + size / 2), # top right
|
|
177
|
+
(center_x + size * np.sqrt(3) / 2, center_y - size / 2), # bottom right
|
|
178
|
+
(center_x, center_y - size), # bottom
|
|
179
|
+
(center_x - size * np.sqrt(3) / 2, center_y - size / 2), # bottom left
|
|
180
|
+
(center_x - size * np.sqrt(3) / 2, center_y + size / 2), # top left
|
|
181
|
+
]
|
|
182
|
+
return vertices
|
|
183
|
+
|
|
184
|
+
x_spacing = np.sqrt(3) * size
|
|
185
|
+
y_spacing = 1.5 * size
|
|
186
|
+
hexagons = []
|
|
187
|
+
|
|
188
|
+
for row, col in itertools.product(range(height), range(width)):
|
|
189
|
+
# Calculate center position with offset for even rows
|
|
190
|
+
x = col * x_spacing + (row % 2 == 0) * (x_spacing / 2)
|
|
191
|
+
y = row * y_spacing
|
|
192
|
+
hexagons.append(_get_hex_vertices(x, y, size))
|
|
193
|
+
|
|
194
|
+
return hexagons
|
|
195
|
+
|
|
196
|
+
|
|
162
197
|
def draw_property_layers(
|
|
163
198
|
space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes
|
|
164
199
|
):
|
|
@@ -191,11 +226,10 @@ def draw_property_layers(
|
|
|
191
226
|
continue
|
|
192
227
|
|
|
193
228
|
data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
|
|
194
|
-
width, height = data.shape # if space is None else (space.width, space.height)
|
|
195
229
|
|
|
196
|
-
if space
|
|
230
|
+
if (space.width, space.height) != data.shape:
|
|
197
231
|
warnings.warn(
|
|
198
|
-
f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
|
|
232
|
+
f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({space.width}, {space.height}).",
|
|
199
233
|
UserWarning,
|
|
200
234
|
stacklevel=2,
|
|
201
235
|
)
|
|
@@ -206,45 +240,75 @@ def draw_property_layers(
|
|
|
206
240
|
vmax = portrayal.get("vmax", np.max(data))
|
|
207
241
|
colorbar = portrayal.get("colorbar", True)
|
|
208
242
|
|
|
209
|
-
#
|
|
243
|
+
# Prepare colormap
|
|
210
244
|
if "color" in portrayal:
|
|
211
245
|
rgba_color = to_rgba(portrayal["color"])
|
|
212
|
-
normalized_data = (data - vmin) / (vmax - vmin)
|
|
213
|
-
rgba_data = np.full((*data.shape, 4), rgba_color)
|
|
214
|
-
rgba_data[..., 3] *= normalized_data * alpha
|
|
215
|
-
rgba_data = np.clip(rgba_data, 0, 1)
|
|
216
246
|
cmap = LinearSegmentedColormap.from_list(
|
|
217
247
|
layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
|
|
218
248
|
)
|
|
219
|
-
im = ax.imshow(
|
|
220
|
-
rgba_data,
|
|
221
|
-
origin="lower",
|
|
222
|
-
)
|
|
223
|
-
if colorbar:
|
|
224
|
-
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
225
|
-
sm = ScalarMappable(norm=norm, cmap=cmap)
|
|
226
|
-
sm.set_array([])
|
|
227
|
-
ax.figure.colorbar(sm, ax=ax, orientation="vertical")
|
|
228
|
-
|
|
229
249
|
elif "colormap" in portrayal:
|
|
230
250
|
cmap = portrayal.get("colormap", "viridis")
|
|
231
251
|
if isinstance(cmap, list):
|
|
232
252
|
cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
cmap=cmap,
|
|
236
|
-
alpha=alpha,
|
|
237
|
-
vmin=vmin,
|
|
238
|
-
vmax=vmax,
|
|
239
|
-
origin="lower",
|
|
240
|
-
)
|
|
241
|
-
if colorbar:
|
|
242
|
-
plt.colorbar(im, ax=ax, label=layer_name)
|
|
253
|
+
elif isinstance(cmap, str):
|
|
254
|
+
cmap = plt.get_cmap(cmap)
|
|
243
255
|
else:
|
|
244
256
|
raise ValueError(
|
|
245
257
|
f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
|
|
246
258
|
)
|
|
247
259
|
|
|
260
|
+
if isinstance(space, OrthogonalGrid):
|
|
261
|
+
if "color" in portrayal:
|
|
262
|
+
data = data.T
|
|
263
|
+
normalized_data = (data - vmin) / (vmax - vmin)
|
|
264
|
+
rgba_data = np.full((*data.shape, 4), rgba_color)
|
|
265
|
+
rgba_data[..., 3] *= normalized_data * alpha
|
|
266
|
+
rgba_data = np.clip(rgba_data, 0, 1)
|
|
267
|
+
ax.imshow(rgba_data, origin="lower")
|
|
268
|
+
else:
|
|
269
|
+
ax.imshow(
|
|
270
|
+
data.T,
|
|
271
|
+
cmap=cmap,
|
|
272
|
+
alpha=alpha,
|
|
273
|
+
vmin=vmin,
|
|
274
|
+
vmax=vmax,
|
|
275
|
+
origin="lower",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
elif isinstance(space, HexGrid):
|
|
279
|
+
width, height = data.shape
|
|
280
|
+
|
|
281
|
+
# Generate hexagon mesh
|
|
282
|
+
hexagons = _get_hexmesh(width, height)
|
|
283
|
+
|
|
284
|
+
# Normalize colors
|
|
285
|
+
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
286
|
+
colors = data.ravel() # flatten data to 1D array
|
|
287
|
+
|
|
288
|
+
if "color" in portrayal:
|
|
289
|
+
normalized_colors = np.clip(norm(colors), 0, 1)
|
|
290
|
+
rgba_colors = np.full((len(colors), 4), rgba_color)
|
|
291
|
+
rgba_colors[:, 3] = normalized_colors * alpha
|
|
292
|
+
else:
|
|
293
|
+
rgba_colors = cmap(norm(colors))
|
|
294
|
+
rgba_colors[..., 3] *= alpha
|
|
295
|
+
|
|
296
|
+
# Draw hexagons
|
|
297
|
+
collection = PolyCollection(hexagons, facecolors=rgba_colors, zorder=-1)
|
|
298
|
+
ax.add_collection(collection)
|
|
299
|
+
|
|
300
|
+
else:
|
|
301
|
+
raise NotImplementedError(
|
|
302
|
+
f"PropertyLayer visualization not implemented for {type(space)}."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Add colorbar if requested
|
|
306
|
+
if colorbar:
|
|
307
|
+
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
308
|
+
sm = ScalarMappable(norm=norm, cmap=cmap)
|
|
309
|
+
sm.set_array([])
|
|
310
|
+
plt.colorbar(sm, ax=ax, label=layer_name)
|
|
311
|
+
|
|
248
312
|
|
|
249
313
|
def draw_orthogonal_grid(
|
|
250
314
|
space: OrthogonalGrid,
|
|
@@ -308,13 +372,6 @@ def draw_hex_grid(
|
|
|
308
372
|
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
309
373
|
draw_grid: whether to draw the grid
|
|
310
374
|
kwargs: additional keyword arguments passed to ax.scatter
|
|
311
|
-
|
|
312
|
-
Returns:
|
|
313
|
-
Returns the Axes object with the plot drawn onto it.
|
|
314
|
-
|
|
315
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
316
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
317
|
-
|
|
318
375
|
"""
|
|
319
376
|
if ax is None:
|
|
320
377
|
fig, ax = plt.subplots()
|
|
@@ -323,62 +380,54 @@ def draw_hex_grid(
|
|
|
323
380
|
s_default = (180 / max(space.width, space.height)) ** 2
|
|
324
381
|
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
325
382
|
|
|
326
|
-
# for
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# give all rows an offset in the y direction
|
|
331
|
-
|
|
332
|
-
# numbers here are based on a distance of 1 between centers of hexes
|
|
333
|
-
offset = math.sqrt(0.75)
|
|
383
|
+
# Parameters for hexagon grid
|
|
384
|
+
size = 1.0
|
|
385
|
+
x_spacing = np.sqrt(3) * size
|
|
386
|
+
y_spacing = 1.5 * size
|
|
334
387
|
|
|
335
388
|
loc = arguments["loc"].astype(float)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
#
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
389
|
+
# Calculate hexagon centers for agents if agents are present and plot them.
|
|
390
|
+
if loc.size > 0:
|
|
391
|
+
loc[:, 0] = loc[:, 0] * x_spacing + ((loc[:, 1] - 1) % 2) * (x_spacing / 2)
|
|
392
|
+
loc[:, 1] = loc[:, 1] * y_spacing
|
|
393
|
+
arguments["loc"] = loc
|
|
394
|
+
|
|
395
|
+
# plot the agents
|
|
396
|
+
_scatter(ax, arguments, **kwargs)
|
|
397
|
+
|
|
398
|
+
# Calculate proper bounds that account for the full hexagon width and height
|
|
399
|
+
x_max = space.width * x_spacing + (space.height % 2) * (x_spacing / 2)
|
|
400
|
+
y_max = space.height * y_spacing
|
|
401
|
+
|
|
402
|
+
# Add padding that accounts for the hexagon points
|
|
403
|
+
x_padding = (
|
|
404
|
+
size * np.sqrt(3) / 2
|
|
405
|
+
) # Distance from center to rightmost point of hexagon
|
|
406
|
+
y_padding = size # Distance from center to topmost point of hexagon
|
|
407
|
+
|
|
408
|
+
# Plot limits to perfectly contain the hexagonal grid
|
|
409
|
+
# Determined through physical testing.
|
|
410
|
+
ax.set_xlim(-2 * x_padding, x_max + x_padding)
|
|
411
|
+
ax.set_ylim(-2 * y_padding, y_max + y_padding)
|
|
412
|
+
|
|
413
|
+
def setup_hexmesh(width, height):
|
|
414
|
+
"""Helper function for creating the hexmesh with unique edges."""
|
|
415
|
+
edges = set()
|
|
416
|
+
|
|
417
|
+
# Generate edges for each hexagon
|
|
418
|
+
hexagons = _get_hexmesh(width, height)
|
|
419
|
+
for vertices in hexagons:
|
|
420
|
+
# Edge logic, connecting each vertex to the next
|
|
421
|
+
for v1, v2 in pairwise([*vertices, vertices[0]]):
|
|
422
|
+
# Sort vertices to ensure consistent edge representation and avoid duplicates.
|
|
423
|
+
edge = tuple(sorted([tuple(np.round(v1, 6)), tuple(np.round(v2, 6))]))
|
|
424
|
+
edges.add(edge)
|
|
425
|
+
|
|
426
|
+
return LineCollection(edges, linestyle=":", color="black", linewidth=1, alpha=1)
|
|
373
427
|
|
|
374
428
|
if draw_grid:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
setup_hexmesh(
|
|
378
|
-
space.width,
|
|
379
|
-
space.height,
|
|
380
|
-
)
|
|
381
|
-
)
|
|
429
|
+
ax.add_collection(setup_hexmesh(space.width, space.height))
|
|
430
|
+
|
|
382
431
|
return ax
|
|
383
432
|
|
|
384
433
|
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -97,7 +97,11 @@ def SolaraViz(
|
|
|
97
97
|
reduce update frequency,resulting in faster execution.
|
|
98
98
|
"""
|
|
99
99
|
if components == "default":
|
|
100
|
-
components = [
|
|
100
|
+
components = [
|
|
101
|
+
components_altair.make_altair_space(
|
|
102
|
+
agent_portrayal=None, propertylayer_portrayal=None, post_process=None
|
|
103
|
+
)
|
|
104
|
+
]
|
|
101
105
|
if model_params is None:
|
|
102
106
|
model_params = {}
|
|
103
107
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.4
|
|
4
4
|
Summary: Agent-based modeling (ABM) in Python
|
|
5
5
|
Project-URL: homepage, https://github.com/projectmesa/mesa
|
|
6
6
|
Project-URL: repository, https://github.com/projectmesa/mesa
|
|
@@ -27,6 +27,7 @@ Requires-Dist: pandas
|
|
|
27
27
|
Requires-Dist: scipy
|
|
28
28
|
Requires-Dist: tqdm
|
|
29
29
|
Provides-Extra: all
|
|
30
|
+
Requires-Dist: altair; extra == 'all'
|
|
30
31
|
Requires-Dist: ipython; extra == 'all'
|
|
31
32
|
Requires-Dist: matplotlib; extra == 'all'
|
|
32
33
|
Requires-Dist: myst-nb; extra == 'all'
|
|
@@ -41,7 +42,9 @@ Requires-Dist: scipy; extra == 'all'
|
|
|
41
42
|
Requires-Dist: seaborn; extra == 'all'
|
|
42
43
|
Requires-Dist: solara; extra == 'all'
|
|
43
44
|
Requires-Dist: sphinx; extra == 'all'
|
|
45
|
+
Requires-Dist: sphinx-copybutton; extra == 'all'
|
|
44
46
|
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: altair; extra == 'dev'
|
|
45
48
|
Requires-Dist: matplotlib; extra == 'dev'
|
|
46
49
|
Requires-Dist: networkx; extra == 'dev'
|
|
47
50
|
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -51,6 +54,7 @@ Requires-Dist: ruff; extra == 'dev'
|
|
|
51
54
|
Requires-Dist: solara; extra == 'dev'
|
|
52
55
|
Requires-Dist: sphinx; extra == 'dev'
|
|
53
56
|
Provides-Extra: docs
|
|
57
|
+
Requires-Dist: altair; extra == 'docs'
|
|
54
58
|
Requires-Dist: ipython; extra == 'docs'
|
|
55
59
|
Requires-Dist: matplotlib; extra == 'docs'
|
|
56
60
|
Requires-Dist: myst-nb; extra == 'docs'
|
|
@@ -60,7 +64,9 @@ Requires-Dist: pydata-sphinx-theme; extra == 'docs'
|
|
|
60
64
|
Requires-Dist: seaborn; extra == 'docs'
|
|
61
65
|
Requires-Dist: solara; extra == 'docs'
|
|
62
66
|
Requires-Dist: sphinx; extra == 'docs'
|
|
67
|
+
Requires-Dist: sphinx-copybutton; extra == 'docs'
|
|
63
68
|
Provides-Extra: examples
|
|
69
|
+
Requires-Dist: altair; extra == 'examples'
|
|
64
70
|
Requires-Dist: matplotlib; extra == 'examples'
|
|
65
71
|
Requires-Dist: networkx; extra == 'examples'
|
|
66
72
|
Requires-Dist: pytest; extra == 'examples'
|
|
@@ -69,10 +75,12 @@ Requires-Dist: solara; extra == 'examples'
|
|
|
69
75
|
Provides-Extra: network
|
|
70
76
|
Requires-Dist: networkx; extra == 'network'
|
|
71
77
|
Provides-Extra: rec
|
|
78
|
+
Requires-Dist: altair; extra == 'rec'
|
|
72
79
|
Requires-Dist: matplotlib; extra == 'rec'
|
|
73
80
|
Requires-Dist: networkx; extra == 'rec'
|
|
74
81
|
Requires-Dist: solara; extra == 'rec'
|
|
75
82
|
Provides-Extra: viz
|
|
83
|
+
Requires-Dist: altair; extra == 'viz'
|
|
76
84
|
Requires-Dist: matplotlib; extra == 'viz'
|
|
77
85
|
Requires-Dist: solara; extra == 'viz'
|
|
78
86
|
Description-Content-Type: text/markdown
|
|
@@ -107,27 +115,22 @@ can be displayed in browser windows or Jupyter.*
|
|
|
107
115
|
|
|
108
116
|
## Using Mesa
|
|
109
117
|
|
|
110
|
-
To install our latest stable release
|
|
118
|
+
To install our latest stable release, run:
|
|
111
119
|
|
|
112
120
|
``` bash
|
|
113
121
|
pip install -U mesa
|
|
114
122
|
```
|
|
115
123
|
|
|
116
|
-
To install our latest pre-release, run:
|
|
117
|
-
|
|
118
|
-
``` bash
|
|
119
|
-
pip install -U --pre mesa
|
|
120
|
-
```
|
|
121
124
|
Starting with Mesa 3.0, we don't install all our dependencies anymore by default.
|
|
122
125
|
```bash
|
|
123
126
|
# You can customize the additional dependencies you need, if you want. Available are:
|
|
124
|
-
pip install -U
|
|
127
|
+
pip install -U mesa[network,viz]
|
|
125
128
|
|
|
126
129
|
# This is equivalent to our recommended dependencies:
|
|
127
|
-
pip install -U
|
|
130
|
+
pip install -U mesa[rec]
|
|
128
131
|
|
|
129
132
|
# To install all, including developer, dependencies:
|
|
130
|
-
pip install -U
|
|
133
|
+
pip install -U mesa[all]
|
|
131
134
|
```
|
|
132
135
|
|
|
133
136
|
You can also use `pip` to install the latest GitHub version:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
mesa/__init__.py,sha256=
|
|
1
|
+
mesa/__init__.py,sha256=wLwKIsmbzCJwpwM_L7SH--8rPFOpXmSEK2F7LlkTyCM,611
|
|
2
2
|
mesa/agent.py,sha256=4CXMOFA9KhvTypaV_OHZGqxOR4GVwyX4x8DOtQENUQA,26130
|
|
3
3
|
mesa/batchrunner.py,sha256=w8StV82F_7DAAVQc5V7_Ggp0EL1NYn__UcBE-Nwrgv4,7771
|
|
4
|
-
mesa/datacollection.py,sha256=
|
|
4
|
+
mesa/datacollection.py,sha256=vxW0KEmx6BHMM6SnKrRJJr7Eb_siGLyofwuyN8pSWXg,18483
|
|
5
5
|
mesa/mesa_logging.py,sha256=PEDqUaQ2Y4bkYBkrHVkGT0sF86gUdbSH1T3vCg3qQeE,4949
|
|
6
6
|
mesa/model.py,sha256=VkdBea_mkWcBMxMq-pwuU23UlI1gbG4TOIyqkBfeiFo,8354
|
|
7
7
|
mesa/space.py,sha256=MNCblKf862pdkoIAa-VpjaurmI8GJtb02W3q3QWFjTE,64458
|
|
@@ -23,7 +23,7 @@ mesa/examples/advanced/pd_grid/model.py,sha256=-ytTSW0a_TQGTQW02k7XKAMiMak_b2XkJ
|
|
|
23
23
|
mesa/examples/advanced/sugarscape_g1mt/Readme.md,sha256=x3kKw1Rre2FPkNhGDLtdzeThmH089mxsGYUPZUeu26k,3595
|
|
24
24
|
mesa/examples/advanced/sugarscape_g1mt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
mesa/examples/advanced/sugarscape_g1mt/agents.py,sha256=ugTNvWjQNm_jMInv9d0sOIFaChIDs-TWazN78YrDP9g,8466
|
|
26
|
-
mesa/examples/advanced/sugarscape_g1mt/app.py,sha256=
|
|
26
|
+
mesa/examples/advanced/sugarscape_g1mt/app.py,sha256=fg8ZXBoc4tPI9AfOLF0fgMk3Ey4H8xZu6tUaTwDp3Nk,1952
|
|
27
27
|
mesa/examples/advanced/sugarscape_g1mt/model.py,sha256=s4n9SRaaMNY9CXFtry83BELZU69UJOCa4DMrw_eehr4,5763
|
|
28
28
|
mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt,sha256=zZtGYciBPT4miZVnbVuoQ5TugTmGrbDWV9yb5KH6tnU,5000
|
|
29
29
|
mesa/examples/advanced/sugarscape_g1mt/tests.py,sha256=UNahmZTgLquSqmoi_9GcE3JP0qBHjkrHFZ15NMm0ce8,2517
|
|
@@ -67,7 +67,7 @@ mesa/experimental/cell_space/cell.py,sha256=Dl0ek7W_vgAZOp6zXBvkwYTG7E36OQ-4zZ5b
|
|
|
67
67
|
mesa/experimental/cell_space/cell_agent.py,sha256=LOTLKR2To9hU-igKsauA5sikS7k8wgwlt-Pi0C7lGU0,4262
|
|
68
68
|
mesa/experimental/cell_space/cell_collection.py,sha256=VnD3I4bqnwgFxKSzTElYYJIBFJGdaEaNTggrbFKgPrc,4715
|
|
69
69
|
mesa/experimental/cell_space/discrete_space.py,sha256=qNGez9SV4E83Outs_12suuS0ZtTHcwyv6ZB1xzTXUWk,3846
|
|
70
|
-
mesa/experimental/cell_space/grid.py,sha256=
|
|
70
|
+
mesa/experimental/cell_space/grid.py,sha256=VTPjqzryAX6I9EunuqSvZvYD2zHznlt6Ct7HXQ_7STQ,10426
|
|
71
71
|
mesa/experimental/cell_space/network.py,sha256=ujN2dV1i9hcXh6H0s7gwTuPT6gh7BCaziOUYPCybQKk,1862
|
|
72
72
|
mesa/experimental/cell_space/property_layer.py,sha256=HFpBWOjI7PFU_K8VDb_pl9h62MftCBWL7PUKQNT3Ke8,17379
|
|
73
73
|
mesa/experimental/cell_space/voronoi.py,sha256=FXJD8ci81Jil3FaL7ZFNfMPGvXvg3uym5Ooo1ZqKSKs,10199
|
|
@@ -81,17 +81,16 @@ mesa/experimental/mesa_signals/__init__.py,sha256=QjG4FSKQl5ZSzV9ctiaB7UqYDR3FAR
|
|
|
81
81
|
mesa/experimental/mesa_signals/mesa_signal.py,sha256=Vxo4gIV6a959MANL3RMANsGh0R9lkZTNO19XIYzvKSM,16860
|
|
82
82
|
mesa/experimental/mesa_signals/observable_collections.py,sha256=rHEj6BYxLHFFGzSdoDKAdtzJ6y-IHHfcP3qEDJJsY6Y,3917
|
|
83
83
|
mesa/experimental/mesa_signals/signals_util.py,sha256=fmq_FsIxsIvGjtmc4A9TGdBUtdliMHhEOpjRXivRDjA,1618
|
|
84
|
-
mesa/visualization/__init__.py,sha256=
|
|
85
|
-
mesa/visualization/mpl_space_drawing.py,sha256=
|
|
86
|
-
mesa/visualization/solara_viz.py,sha256=
|
|
84
|
+
mesa/visualization/__init__.py,sha256=wnafHqOc7q-niu8HDOLwcmwuYu5BuvdjLTHmfpyXAoA,664
|
|
85
|
+
mesa/visualization/mpl_space_drawing.py,sha256=9byWxbKHVVT5GLmu-6U0qVFddThEag612JBGYmZiWps,22686
|
|
86
|
+
mesa/visualization/solara_viz.py,sha256=AoMVDdjMc99qCClAvsFXg-jBNPaDF5kfIhXa5Hd57MA,21052
|
|
87
87
|
mesa/visualization/user_param.py,sha256=Dl2WOwLYLf0pfLpabCZtIdFRyKZrK6Qtc3utZx5GPYg,2139
|
|
88
88
|
mesa/visualization/utils.py,sha256=lJHgRKF5BHLf72Tw3YpwyiWuRoIimaTKQ7xBCw_Rx3A,146
|
|
89
89
|
mesa/visualization/components/__init__.py,sha256=Bq3nrPikcaIo9BSs0O3zptWVLlUmAkLo3s0mEmpH1RE,3022
|
|
90
|
-
mesa/visualization/components/altair_components.py,sha256=
|
|
90
|
+
mesa/visualization/components/altair_components.py,sha256=7OdVdZOvAx4p3j6iux6FqUYrWT0OADBIhFGf6EeGcb4,6071
|
|
91
91
|
mesa/visualization/components/matplotlib_components.py,sha256=xQETaFyHIfmL_9JwrLIgubuIQ7-pp7TMoXT1WMmozus,5441
|
|
92
|
-
mesa-3.1.
|
|
93
|
-
mesa-3.1.
|
|
94
|
-
mesa-3.1.
|
|
95
|
-
mesa-3.1.
|
|
96
|
-
mesa-3.1.
|
|
97
|
-
mesa-3.1.3.dist-info/RECORD,,
|
|
92
|
+
mesa-3.1.4.dist-info/METADATA,sha256=NFoxMdQKJ_fLTMOskESPV6JZ1nfKJHzD2Vu4qxChx4o,10197
|
|
93
|
+
mesa-3.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
94
|
+
mesa-3.1.4.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
95
|
+
mesa-3.1.4.dist-info/licenses/NOTICE,sha256=GbsWoK0QWv1JyZ_xer2s-jNilv0RtWl-0UrtlJANHPg,578
|
|
96
|
+
mesa-3.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|