Mesa 3.0.0__py3-none-any.whl → 3.0.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +3 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/visualization/solara_viz.py +266 -267
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/METADATA +13 -65
- mesa-3.0.0a1.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a1.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/solara_viz.py +0 -453
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -1,593 +0,0 @@
|
|
|
1
|
-
"""Helper functions for drawing mesa spaces with matplotlib.
|
|
2
|
-
|
|
3
|
-
These functions are used by the provided matplotlib components, but can also be used to quickly visualize
|
|
4
|
-
a space with matplotlib for example when creating a mp4 of a movie run or when needing a figure
|
|
5
|
-
for a paper.
|
|
6
|
-
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import contextlib
|
|
10
|
-
import itertools
|
|
11
|
-
import math
|
|
12
|
-
import warnings
|
|
13
|
-
from collections.abc import Callable
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
import networkx as nx
|
|
17
|
-
import numpy as np
|
|
18
|
-
from matplotlib import pyplot as plt
|
|
19
|
-
from matplotlib.axes import Axes
|
|
20
|
-
from matplotlib.cm import ScalarMappable
|
|
21
|
-
from matplotlib.collections import PatchCollection
|
|
22
|
-
from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
|
|
23
|
-
from matplotlib.patches import RegularPolygon
|
|
24
|
-
|
|
25
|
-
import mesa
|
|
26
|
-
from mesa.experimental.cell_space import (
|
|
27
|
-
OrthogonalMooreGrid,
|
|
28
|
-
OrthogonalVonNeumannGrid,
|
|
29
|
-
VoronoiGrid,
|
|
30
|
-
)
|
|
31
|
-
from mesa.space import (
|
|
32
|
-
ContinuousSpace,
|
|
33
|
-
HexMultiGrid,
|
|
34
|
-
HexSingleGrid,
|
|
35
|
-
MultiGrid,
|
|
36
|
-
NetworkGrid,
|
|
37
|
-
PropertyLayer,
|
|
38
|
-
SingleGrid,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid
|
|
42
|
-
HexGrid = HexSingleGrid | HexMultiGrid | mesa.experimental.cell_space.HexGrid
|
|
43
|
-
Network = NetworkGrid | mesa.experimental.cell_space.Network
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def collect_agent_data(
|
|
47
|
-
space: OrthogonalGrid | HexGrid | Network | ContinuousSpace | VoronoiGrid,
|
|
48
|
-
agent_portrayal: Callable,
|
|
49
|
-
color="tab:blue",
|
|
50
|
-
size=25,
|
|
51
|
-
marker="o",
|
|
52
|
-
zorder: int = 1,
|
|
53
|
-
):
|
|
54
|
-
"""Collect the plotting data for all agents in the space.
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
space: The space containing the Agents.
|
|
58
|
-
agent_portrayal: A callable that is called with the agent and returns a dict
|
|
59
|
-
color: default color
|
|
60
|
-
size: default size
|
|
61
|
-
marker: default marker
|
|
62
|
-
zorder: default zorder
|
|
63
|
-
|
|
64
|
-
agent_portrayal should return a dict, limited to size (size of marker), color (color of marker), zorder (z-order),
|
|
65
|
-
marker (marker style), alpha, linewidths, and edgecolors
|
|
66
|
-
|
|
67
|
-
"""
|
|
68
|
-
arguments = {
|
|
69
|
-
"s": [],
|
|
70
|
-
"c": [],
|
|
71
|
-
"marker": [],
|
|
72
|
-
"zorder": [],
|
|
73
|
-
"loc": [],
|
|
74
|
-
"alpha": [],
|
|
75
|
-
"edgecolors": [],
|
|
76
|
-
"linewidths": [],
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
for agent in space.agents:
|
|
80
|
-
portray = agent_portrayal(agent)
|
|
81
|
-
loc = agent.pos
|
|
82
|
-
if loc is None:
|
|
83
|
-
loc = agent.cell.coordinate
|
|
84
|
-
|
|
85
|
-
arguments["loc"].append(loc)
|
|
86
|
-
arguments["s"].append(portray.pop("size", size))
|
|
87
|
-
arguments["c"].append(portray.pop("color", color))
|
|
88
|
-
arguments["marker"].append(portray.pop("marker", marker))
|
|
89
|
-
arguments["zorder"].append(portray.pop("zorder", zorder))
|
|
90
|
-
|
|
91
|
-
for entry in ["alpha", "edgecolors", "linewidths"]:
|
|
92
|
-
with contextlib.suppress(KeyError):
|
|
93
|
-
arguments[entry].append(portray.pop(entry))
|
|
94
|
-
|
|
95
|
-
if len(portray) > 0:
|
|
96
|
-
ignored_fields = list(portray.keys())
|
|
97
|
-
msg = ", ".join(ignored_fields)
|
|
98
|
-
warnings.warn(
|
|
99
|
-
f"the following fields are not used in agent portrayal and thus ignored: {msg}.",
|
|
100
|
-
stacklevel=2,
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return {k: np.asarray(v) for k, v in arguments.items()}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def draw_space(
|
|
107
|
-
space,
|
|
108
|
-
agent_portrayal: Callable,
|
|
109
|
-
propertylayer_portrayal: dict | None = None,
|
|
110
|
-
ax: Axes | None = None,
|
|
111
|
-
**space_drawing_kwargs,
|
|
112
|
-
):
|
|
113
|
-
"""Draw a Matplotlib-based visualization of the space.
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
space: the space of the mesa model
|
|
117
|
-
agent_portrayal: A callable that returns a dict specifying how to show the agent
|
|
118
|
-
propertylayer_portrayal: a dict specifying how to show propertylayer(s)
|
|
119
|
-
ax: the axes upon which to draw the plot
|
|
120
|
-
post_process: a callable called with the Axes instance
|
|
121
|
-
space_drawing_kwargs: any additional keyword arguments to be passed on to the underlying function for drawing the space.
|
|
122
|
-
|
|
123
|
-
Returns:
|
|
124
|
-
Returns the Axes object with the plot drawn onto it.
|
|
125
|
-
|
|
126
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
127
|
-
"size", "marker", "zorder", alpha, linewidths, and edgecolors. Other field are ignored and will result in a user warning.
|
|
128
|
-
|
|
129
|
-
"""
|
|
130
|
-
if ax is None:
|
|
131
|
-
fig, ax = plt.subplots()
|
|
132
|
-
|
|
133
|
-
# https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching
|
|
134
|
-
match space:
|
|
135
|
-
# order matters here given the class structure of old-style grid spaces
|
|
136
|
-
case HexSingleGrid() | HexMultiGrid() | mesa.experimental.cell_space.HexGrid():
|
|
137
|
-
draw_hex_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
138
|
-
case (
|
|
139
|
-
mesa.space.SingleGrid()
|
|
140
|
-
| OrthogonalMooreGrid()
|
|
141
|
-
| OrthogonalVonNeumannGrid()
|
|
142
|
-
| mesa.space.MultiGrid()
|
|
143
|
-
):
|
|
144
|
-
draw_orthogonal_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
145
|
-
case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network():
|
|
146
|
-
draw_network(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
147
|
-
case mesa.space.ContinuousSpace():
|
|
148
|
-
draw_continuous_space(space, agent_portrayal, ax=ax)
|
|
149
|
-
case VoronoiGrid():
|
|
150
|
-
draw_voronoi_grid(space, agent_portrayal, ax=ax)
|
|
151
|
-
case _:
|
|
152
|
-
raise ValueError(f"Unknown space type: {type(space)}")
|
|
153
|
-
|
|
154
|
-
if propertylayer_portrayal:
|
|
155
|
-
draw_property_layers(space, propertylayer_portrayal, ax=ax)
|
|
156
|
-
|
|
157
|
-
return ax
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def draw_property_layers(
|
|
161
|
-
space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes
|
|
162
|
-
):
|
|
163
|
-
"""Draw PropertyLayers on the given axes.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
space (mesa.space._Grid): The space containing the PropertyLayers.
|
|
167
|
-
propertylayer_portrayal (dict): the key is the name of the layer, the value is a dict with
|
|
168
|
-
fields specifying how the layer is to be portrayed
|
|
169
|
-
ax (matplotlib.axes.Axes): The axes to draw on.
|
|
170
|
-
|
|
171
|
-
Notes:
|
|
172
|
-
valid fields in in the inner dict of propertylayer_portrayal are "alpha", "vmin", "vmax", "color" or "colormap", and "colorbar"
|
|
173
|
-
so you can do `{"some_layer":{"colormap":'viridis', 'alpha':.25, "colorbar":False}}`
|
|
174
|
-
|
|
175
|
-
"""
|
|
176
|
-
try:
|
|
177
|
-
# old style spaces
|
|
178
|
-
property_layers = space.properties
|
|
179
|
-
except AttributeError:
|
|
180
|
-
# new style spaces
|
|
181
|
-
property_layers = space.property_layers
|
|
182
|
-
|
|
183
|
-
for layer_name, portrayal in propertylayer_portrayal.items():
|
|
184
|
-
layer = property_layers.get(layer_name, None)
|
|
185
|
-
if not isinstance(layer, PropertyLayer):
|
|
186
|
-
continue
|
|
187
|
-
|
|
188
|
-
data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
|
|
189
|
-
width, height = data.shape if space is None else (space.width, space.height)
|
|
190
|
-
|
|
191
|
-
if space and data.shape != (width, height):
|
|
192
|
-
warnings.warn(
|
|
193
|
-
f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
|
|
194
|
-
UserWarning,
|
|
195
|
-
stacklevel=2,
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
# Get portrayal properties, or use defaults
|
|
199
|
-
alpha = portrayal.get("alpha", 1)
|
|
200
|
-
vmin = portrayal.get("vmin", np.min(data))
|
|
201
|
-
vmax = portrayal.get("vmax", np.max(data))
|
|
202
|
-
colorbar = portrayal.get("colorbar", True)
|
|
203
|
-
|
|
204
|
-
# Draw the layer
|
|
205
|
-
if "color" in portrayal:
|
|
206
|
-
rgba_color = to_rgba(portrayal["color"])
|
|
207
|
-
normalized_data = (data - vmin) / (vmax - vmin)
|
|
208
|
-
rgba_data = np.full((*data.shape, 4), rgba_color)
|
|
209
|
-
rgba_data[..., 3] *= normalized_data * alpha
|
|
210
|
-
rgba_data = np.clip(rgba_data, 0, 1)
|
|
211
|
-
cmap = LinearSegmentedColormap.from_list(
|
|
212
|
-
layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
|
|
213
|
-
)
|
|
214
|
-
im = ax.imshow(
|
|
215
|
-
rgba_data.transpose(1, 0, 2),
|
|
216
|
-
origin="lower",
|
|
217
|
-
)
|
|
218
|
-
if colorbar:
|
|
219
|
-
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
220
|
-
sm = ScalarMappable(norm=norm, cmap=cmap)
|
|
221
|
-
sm.set_array([])
|
|
222
|
-
ax.figure.colorbar(sm, ax=ax, orientation="vertical")
|
|
223
|
-
|
|
224
|
-
elif "colormap" in portrayal:
|
|
225
|
-
cmap = portrayal.get("colormap", "viridis")
|
|
226
|
-
if isinstance(cmap, list):
|
|
227
|
-
cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
|
|
228
|
-
im = ax.imshow(
|
|
229
|
-
data.T,
|
|
230
|
-
cmap=cmap,
|
|
231
|
-
alpha=alpha,
|
|
232
|
-
vmin=vmin,
|
|
233
|
-
vmax=vmax,
|
|
234
|
-
origin="lower",
|
|
235
|
-
)
|
|
236
|
-
if colorbar:
|
|
237
|
-
plt.colorbar(im, ax=ax, label=layer_name)
|
|
238
|
-
else:
|
|
239
|
-
raise ValueError(
|
|
240
|
-
f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
def draw_orthogonal_grid(
|
|
245
|
-
space: OrthogonalGrid,
|
|
246
|
-
agent_portrayal: Callable,
|
|
247
|
-
ax: Axes | None = None,
|
|
248
|
-
draw_grid: bool = True,
|
|
249
|
-
**kwargs,
|
|
250
|
-
):
|
|
251
|
-
"""Visualize a orthogonal grid.
|
|
252
|
-
|
|
253
|
-
Args:
|
|
254
|
-
space: the space to visualize
|
|
255
|
-
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
256
|
-
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
257
|
-
draw_grid: whether to draw the grid
|
|
258
|
-
kwargs: additional keyword arguments passed to ax.scatter
|
|
259
|
-
|
|
260
|
-
Returns:
|
|
261
|
-
Returns the Axes object with the plot drawn onto it.
|
|
262
|
-
|
|
263
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
264
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
265
|
-
|
|
266
|
-
"""
|
|
267
|
-
if ax is None:
|
|
268
|
-
fig, ax = plt.subplots()
|
|
269
|
-
|
|
270
|
-
# gather agent data
|
|
271
|
-
s_default = (180 / max(space.width, space.height)) ** 2
|
|
272
|
-
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
273
|
-
|
|
274
|
-
# plot the agents
|
|
275
|
-
_scatter(ax, arguments, **kwargs)
|
|
276
|
-
|
|
277
|
-
# further styling
|
|
278
|
-
ax.set_xlim(-0.5, space.width - 0.5)
|
|
279
|
-
ax.set_ylim(-0.5, space.height - 0.5)
|
|
280
|
-
|
|
281
|
-
if draw_grid:
|
|
282
|
-
# Draw grid lines
|
|
283
|
-
for x in np.arange(-0.5, space.width - 0.5, 1):
|
|
284
|
-
ax.axvline(x, color="gray", linestyle=":")
|
|
285
|
-
for y in np.arange(-0.5, space.height - 0.5, 1):
|
|
286
|
-
ax.axhline(y, color="gray", linestyle=":")
|
|
287
|
-
|
|
288
|
-
return ax
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def draw_hex_grid(
|
|
292
|
-
space: HexGrid,
|
|
293
|
-
agent_portrayal: Callable,
|
|
294
|
-
ax: Axes | None = None,
|
|
295
|
-
draw_grid: bool = True,
|
|
296
|
-
**kwargs,
|
|
297
|
-
):
|
|
298
|
-
"""Visualize a hex grid.
|
|
299
|
-
|
|
300
|
-
Args:
|
|
301
|
-
space: the space to visualize
|
|
302
|
-
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
303
|
-
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
304
|
-
draw_grid: whether to draw the grid
|
|
305
|
-
kwargs: additional keyword arguments passed to ax.scatter
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
Returns the Axes object with the plot drawn onto it.
|
|
309
|
-
|
|
310
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
311
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
312
|
-
|
|
313
|
-
"""
|
|
314
|
-
if ax is None:
|
|
315
|
-
fig, ax = plt.subplots()
|
|
316
|
-
|
|
317
|
-
# gather data
|
|
318
|
-
s_default = (180 / max(space.width, space.height)) ** 2
|
|
319
|
-
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
320
|
-
|
|
321
|
-
# for hexgrids we have to go from logical coordinates to visual coordinates
|
|
322
|
-
# this is a bit messy.
|
|
323
|
-
|
|
324
|
-
# give all even rows an offset in the x direction
|
|
325
|
-
# give all rows an offset in the y direction
|
|
326
|
-
|
|
327
|
-
# numbers here are based on a distance of 1 between centers of hexes
|
|
328
|
-
offset = math.sqrt(0.75)
|
|
329
|
-
|
|
330
|
-
loc = arguments["loc"].astype(float)
|
|
331
|
-
|
|
332
|
-
logical = np.mod(loc[:, 1], 2) == 0
|
|
333
|
-
loc[:, 0][logical] += 0.5
|
|
334
|
-
loc[:, 1] *= offset
|
|
335
|
-
arguments["loc"] = loc
|
|
336
|
-
|
|
337
|
-
# plot the agents
|
|
338
|
-
_scatter(ax, arguments, **kwargs)
|
|
339
|
-
|
|
340
|
-
# further styling and adding of grid
|
|
341
|
-
ax.set_xlim(-1, space.width + 0.5)
|
|
342
|
-
ax.set_ylim(-offset, space.height * offset)
|
|
343
|
-
|
|
344
|
-
def setup_hexmesh(
|
|
345
|
-
width,
|
|
346
|
-
height,
|
|
347
|
-
):
|
|
348
|
-
"""Helper function for creating the hexmaesh."""
|
|
349
|
-
# fixme: this should be done once, rather than in each update
|
|
350
|
-
# fixme check coordinate system in hexgrid (see https://www.redblobgames.com/grids/hexagons/#coordinates-offset)
|
|
351
|
-
|
|
352
|
-
patches = []
|
|
353
|
-
for x, y in itertools.product(range(width), range(height)):
|
|
354
|
-
if y % 2 == 0:
|
|
355
|
-
x += 0.5 # noqa: PLW2901
|
|
356
|
-
y *= offset # noqa: PLW2901
|
|
357
|
-
hex = RegularPolygon(
|
|
358
|
-
(x, y),
|
|
359
|
-
numVertices=6,
|
|
360
|
-
radius=math.sqrt(1 / 3),
|
|
361
|
-
orientation=np.radians(120),
|
|
362
|
-
)
|
|
363
|
-
patches.append(hex)
|
|
364
|
-
mesh = PatchCollection(
|
|
365
|
-
patches, edgecolor="k", facecolor=(1, 1, 1, 0), linestyle="dotted", lw=1
|
|
366
|
-
)
|
|
367
|
-
return mesh
|
|
368
|
-
|
|
369
|
-
if draw_grid:
|
|
370
|
-
# add grid
|
|
371
|
-
ax.add_collection(
|
|
372
|
-
setup_hexmesh(
|
|
373
|
-
space.width,
|
|
374
|
-
space.height,
|
|
375
|
-
)
|
|
376
|
-
)
|
|
377
|
-
return ax
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
def draw_network(
|
|
381
|
-
space: Network,
|
|
382
|
-
agent_portrayal: Callable,
|
|
383
|
-
ax: Axes | None = None,
|
|
384
|
-
draw_grid: bool = True,
|
|
385
|
-
layout_alg=nx.spring_layout,
|
|
386
|
-
layout_kwargs=None,
|
|
387
|
-
**kwargs,
|
|
388
|
-
):
|
|
389
|
-
"""Visualize a network space.
|
|
390
|
-
|
|
391
|
-
Args:
|
|
392
|
-
space: the space to visualize
|
|
393
|
-
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
394
|
-
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
395
|
-
draw_grid: whether to draw the grid
|
|
396
|
-
layout_alg: a networkx layout algorithm or other callable with the same behavior
|
|
397
|
-
layout_kwargs: a dictionary of keyword arguments for the layout algorithm
|
|
398
|
-
kwargs: additional keyword arguments passed to ax.scatter
|
|
399
|
-
|
|
400
|
-
Returns:
|
|
401
|
-
Returns the Axes object with the plot drawn onto it.
|
|
402
|
-
|
|
403
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
404
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
405
|
-
|
|
406
|
-
"""
|
|
407
|
-
if ax is None:
|
|
408
|
-
fig, ax = plt.subplots()
|
|
409
|
-
if layout_kwargs is None:
|
|
410
|
-
layout_kwargs = {"seed": 0}
|
|
411
|
-
|
|
412
|
-
# gather locations for nodes in network
|
|
413
|
-
graph = space.G
|
|
414
|
-
pos = layout_alg(graph, **layout_kwargs)
|
|
415
|
-
x, y = list(zip(*pos.values()))
|
|
416
|
-
xmin, xmax = min(x), max(x)
|
|
417
|
-
ymin, ymax = min(y), max(y)
|
|
418
|
-
|
|
419
|
-
width = xmax - xmin
|
|
420
|
-
height = ymax - ymin
|
|
421
|
-
x_padding = width / 20
|
|
422
|
-
y_padding = height / 20
|
|
423
|
-
|
|
424
|
-
# gather agent data
|
|
425
|
-
s_default = (180 / max(width, height)) ** 2
|
|
426
|
-
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
427
|
-
|
|
428
|
-
# this assumes that nodes are identified by an integer
|
|
429
|
-
# which is true for default nx graphs but might user changeable
|
|
430
|
-
pos = np.asarray(list(pos.values()))
|
|
431
|
-
arguments["loc"] = pos[arguments["loc"]]
|
|
432
|
-
|
|
433
|
-
# plot the agents
|
|
434
|
-
_scatter(ax, arguments, **kwargs)
|
|
435
|
-
|
|
436
|
-
# further styling
|
|
437
|
-
ax.set_axis_off()
|
|
438
|
-
ax.set_xlim(xmin=xmin - x_padding, xmax=xmax + x_padding)
|
|
439
|
-
ax.set_ylim(ymin=ymin - y_padding, ymax=ymax + y_padding)
|
|
440
|
-
|
|
441
|
-
if draw_grid:
|
|
442
|
-
# fixme we need to draw the empty nodes as well
|
|
443
|
-
edge_collection = nx.draw_networkx_edges(
|
|
444
|
-
graph, pos, ax=ax, alpha=0.5, style="--"
|
|
445
|
-
)
|
|
446
|
-
edge_collection.set_zorder(0)
|
|
447
|
-
|
|
448
|
-
return ax
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
def draw_continuous_space(
|
|
452
|
-
space: ContinuousSpace, agent_portrayal: Callable, ax: Axes | None = None, **kwargs
|
|
453
|
-
):
|
|
454
|
-
"""Visualize a continuous space.
|
|
455
|
-
|
|
456
|
-
Args:
|
|
457
|
-
space: the space to visualize
|
|
458
|
-
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
459
|
-
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
460
|
-
kwargs: additional keyword arguments passed to ax.scatter
|
|
461
|
-
|
|
462
|
-
Returns:
|
|
463
|
-
Returns the Axes object with the plot drawn onto it.
|
|
464
|
-
|
|
465
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
466
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
467
|
-
|
|
468
|
-
"""
|
|
469
|
-
if ax is None:
|
|
470
|
-
fig, ax = plt.subplots()
|
|
471
|
-
|
|
472
|
-
# space related setup
|
|
473
|
-
width = space.x_max - space.x_min
|
|
474
|
-
x_padding = width / 20
|
|
475
|
-
height = space.y_max - space.y_min
|
|
476
|
-
y_padding = height / 20
|
|
477
|
-
|
|
478
|
-
# gather agent data
|
|
479
|
-
s_default = (180 / max(width, height)) ** 2
|
|
480
|
-
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
481
|
-
|
|
482
|
-
# plot the agents
|
|
483
|
-
_scatter(ax, arguments, **kwargs)
|
|
484
|
-
|
|
485
|
-
# further visual styling
|
|
486
|
-
border_style = "solid" if not space.torus else (0, (5, 10))
|
|
487
|
-
for spine in ax.spines.values():
|
|
488
|
-
spine.set_linewidth(1.5)
|
|
489
|
-
spine.set_color("black")
|
|
490
|
-
spine.set_linestyle(border_style)
|
|
491
|
-
|
|
492
|
-
ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
|
|
493
|
-
ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
|
|
494
|
-
|
|
495
|
-
return ax
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
def draw_voronoi_grid(
|
|
499
|
-
space: VoronoiGrid, agent_portrayal: Callable, ax: Axes | None = None, **kwargs
|
|
500
|
-
):
|
|
501
|
-
"""Visualize a voronoi grid.
|
|
502
|
-
|
|
503
|
-
Args:
|
|
504
|
-
space: the space to visualize
|
|
505
|
-
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
506
|
-
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
507
|
-
kwargs: additional keyword arguments passed to ax.scatter
|
|
508
|
-
|
|
509
|
-
Returns:
|
|
510
|
-
Returns the Axes object with the plot drawn onto it.
|
|
511
|
-
|
|
512
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
513
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
514
|
-
|
|
515
|
-
"""
|
|
516
|
-
if ax is None:
|
|
517
|
-
fig, ax = plt.subplots()
|
|
518
|
-
|
|
519
|
-
x_list = [i[0] for i in space.centroids_coordinates]
|
|
520
|
-
y_list = [i[1] for i in space.centroids_coordinates]
|
|
521
|
-
x_max = max(x_list)
|
|
522
|
-
x_min = min(x_list)
|
|
523
|
-
y_max = max(y_list)
|
|
524
|
-
y_min = min(y_list)
|
|
525
|
-
|
|
526
|
-
width = x_max - x_min
|
|
527
|
-
x_padding = width / 20
|
|
528
|
-
height = y_max - y_min
|
|
529
|
-
y_padding = height / 20
|
|
530
|
-
|
|
531
|
-
s_default = (180 / max(width, height)) ** 2
|
|
532
|
-
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
533
|
-
|
|
534
|
-
ax.set_xlim(x_min - x_padding, x_max + x_padding)
|
|
535
|
-
ax.set_ylim(y_min - y_padding, y_max + y_padding)
|
|
536
|
-
|
|
537
|
-
_scatter(ax, arguments, **kwargs)
|
|
538
|
-
|
|
539
|
-
for cell in space.all_cells:
|
|
540
|
-
polygon = cell.properties["polygon"]
|
|
541
|
-
ax.fill(
|
|
542
|
-
*zip(*polygon),
|
|
543
|
-
alpha=min(1, cell.properties[space.cell_coloring_property]),
|
|
544
|
-
c="red",
|
|
545
|
-
zorder=0,
|
|
546
|
-
) # Plot filled polygon
|
|
547
|
-
ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black
|
|
548
|
-
|
|
549
|
-
return ax
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
def _scatter(ax: Axes, arguments, **kwargs):
|
|
553
|
-
"""Helper function for plotting the agents.
|
|
554
|
-
|
|
555
|
-
Args:
|
|
556
|
-
ax: a Matplotlib Axes instance
|
|
557
|
-
arguments: the agents specific arguments for platting
|
|
558
|
-
kwargs: additional keyword arguments for ax.scatter
|
|
559
|
-
|
|
560
|
-
"""
|
|
561
|
-
loc = arguments.pop("loc")
|
|
562
|
-
|
|
563
|
-
x = loc[:, 0]
|
|
564
|
-
y = loc[:, 1]
|
|
565
|
-
marker = arguments.pop("marker")
|
|
566
|
-
zorder = arguments.pop("zorder")
|
|
567
|
-
|
|
568
|
-
# we check if edgecolor, linewidth, and alpha are specified
|
|
569
|
-
# at the agent level, if not, we remove them from the arguments dict
|
|
570
|
-
# and fallback to the default value in ax.scatter / use what is passed via **kwargs
|
|
571
|
-
for entry in ["edgecolors", "linewidths", "alpha"]:
|
|
572
|
-
if len(arguments[entry]) == 0:
|
|
573
|
-
arguments.pop(entry)
|
|
574
|
-
else:
|
|
575
|
-
if entry in kwargs:
|
|
576
|
-
raise ValueError(
|
|
577
|
-
f"{entry} is specified in agent portrayal and via plotting kwargs, you can only use one or the other"
|
|
578
|
-
)
|
|
579
|
-
|
|
580
|
-
for mark in np.unique(marker):
|
|
581
|
-
mark_mask = marker == mark
|
|
582
|
-
for z_order in np.unique(zorder):
|
|
583
|
-
zorder_mask = z_order == zorder
|
|
584
|
-
logical = mark_mask & zorder_mask
|
|
585
|
-
|
|
586
|
-
ax.scatter(
|
|
587
|
-
x[logical],
|
|
588
|
-
y[logical],
|
|
589
|
-
marker=mark,
|
|
590
|
-
zorder=z_order,
|
|
591
|
-
**{k: v[logical] for k, v in arguments.items()},
|
|
592
|
-
**kwargs,
|
|
593
|
-
)
|
mesa/visualization/user_param.py
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
"""Solara visualization related helper classes."""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class UserParam:
|
|
5
|
-
"""UserParam."""
|
|
6
|
-
|
|
7
|
-
_ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
|
|
8
|
-
|
|
9
|
-
def maybe_raise_error(self, param_type, valid): # noqa: D102
|
|
10
|
-
if valid:
|
|
11
|
-
return
|
|
12
|
-
msg = self._ERROR_MESSAGE.format(param_type, self.label)
|
|
13
|
-
raise ValueError(msg)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class Slider(UserParam):
|
|
17
|
-
"""A number-based slider input with settable increment.
|
|
18
|
-
|
|
19
|
-
Example:
|
|
20
|
-
slider_option = Slider("My Slider", value=123, min=10, max=200, step=0.1)
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
label: The displayed label in the UI
|
|
24
|
-
value: The initial value of the slider
|
|
25
|
-
min: The minimum possible value of the slider
|
|
26
|
-
max: The maximum possible value of the slider
|
|
27
|
-
step: The step between min and max for a range of possible values
|
|
28
|
-
dtype: either int or float
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
def __init__(
|
|
32
|
-
self,
|
|
33
|
-
label="",
|
|
34
|
-
value=None,
|
|
35
|
-
min=None,
|
|
36
|
-
max=None,
|
|
37
|
-
step=1,
|
|
38
|
-
dtype=None,
|
|
39
|
-
):
|
|
40
|
-
"""Initializes a slider.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
label: The displayed label in the UI
|
|
44
|
-
value: The initial value of the slider
|
|
45
|
-
min: The minimum possible value of the slider
|
|
46
|
-
max: The maximum possible value of the slider
|
|
47
|
-
step: The step between min and max for a range of possible values
|
|
48
|
-
dtype: either int or float
|
|
49
|
-
"""
|
|
50
|
-
self.label = label
|
|
51
|
-
self.value = value
|
|
52
|
-
self.min = min
|
|
53
|
-
self.max = max
|
|
54
|
-
self.step = step
|
|
55
|
-
|
|
56
|
-
# Validate option type to make sure values are supplied properly
|
|
57
|
-
valid = not (self.value is None or self.min is None or self.max is None)
|
|
58
|
-
self.maybe_raise_error("slider", valid)
|
|
59
|
-
|
|
60
|
-
if dtype is None:
|
|
61
|
-
self.is_float_slider = self._check_values_are_float(value, min, max, step)
|
|
62
|
-
else:
|
|
63
|
-
self.is_float_slider = dtype is float
|
|
64
|
-
|
|
65
|
-
def _check_values_are_float(self, value, min, max, step): # D103
|
|
66
|
-
return any(isinstance(n, float) for n in (value, min, max, step))
|
|
67
|
-
|
|
68
|
-
def get(self, attr): # noqa: D102
|
|
69
|
-
return getattr(self, attr)
|