Mesa 3.0.0b2__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Mesa might be problematic. Click here for more details.

Files changed (47) hide show
  1. mesa/__init__.py +1 -1
  2. mesa/agent.py +15 -3
  3. mesa/batchrunner.py +26 -1
  4. mesa/examples/README.md +11 -11
  5. mesa/examples/__init__.py +2 -2
  6. mesa/examples/advanced/epstein_civil_violence/agents.py +44 -38
  7. mesa/examples/advanced/epstein_civil_violence/app.py +29 -28
  8. mesa/examples/advanced/epstein_civil_violence/model.py +33 -65
  9. mesa/examples/advanced/pd_grid/app.py +9 -5
  10. mesa/examples/advanced/pd_grid/model.py +1 -1
  11. mesa/examples/advanced/sugarscape_g1mt/app.py +5 -13
  12. mesa/examples/advanced/sugarscape_g1mt/model.py +3 -1
  13. mesa/examples/advanced/wolf_sheep/agents.py +53 -39
  14. mesa/examples/advanced/wolf_sheep/app.py +37 -19
  15. mesa/examples/advanced/wolf_sheep/model.py +68 -74
  16. mesa/examples/basic/boid_flockers/agents.py +49 -18
  17. mesa/examples/basic/boid_flockers/app.py +2 -2
  18. mesa/examples/basic/boid_flockers/model.py +55 -19
  19. mesa/examples/basic/boltzmann_wealth_model/agents.py +23 -5
  20. mesa/examples/basic/boltzmann_wealth_model/app.py +22 -13
  21. mesa/examples/basic/boltzmann_wealth_model/model.py +48 -13
  22. mesa/examples/basic/boltzmann_wealth_model/st_app.py +2 -2
  23. mesa/examples/basic/conways_game_of_life/app.py +15 -3
  24. mesa/examples/basic/schelling/agents.py +9 -5
  25. mesa/examples/basic/schelling/app.py +5 -5
  26. mesa/examples/basic/schelling/model.py +48 -26
  27. mesa/examples/basic/virus_on_network/app.py +25 -47
  28. mesa/experimental/cell_space/cell_collection.py +14 -2
  29. mesa/experimental/cell_space/discrete_space.py +16 -2
  30. mesa/experimental/devs/simulator.py +59 -14
  31. mesa/model.py +4 -4
  32. mesa/space.py +0 -30
  33. mesa/time.py +4 -4
  34. mesa/visualization/__init__.py +17 -6
  35. mesa/visualization/components/__init__.py +83 -0
  36. mesa/visualization/components/{altair.py → altair_components.py} +34 -2
  37. mesa/visualization/components/matplotlib_components.py +175 -0
  38. mesa/visualization/mpl_space_drawing.py +593 -0
  39. mesa/visualization/solara_viz.py +156 -67
  40. {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/METADATA +6 -8
  41. {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/RECORD +46 -44
  42. {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/WHEEL +1 -1
  43. mesa/visualization/components/matplotlib.py +0 -386
  44. /mesa/visualization/{UserParam.py → user_param.py} +0 -0
  45. {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/entry_points.txt +0 -0
  46. {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/licenses/LICENSE +0 -0
  47. {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/licenses/NOTICE +0 -0
@@ -1,386 +0,0 @@
1
- """Matplotlib based solara components for visualization MESA spaces and plots."""
2
-
3
- import warnings
4
-
5
- import matplotlib.pyplot as plt
6
- import networkx as nx
7
- import numpy as np
8
- import solara
9
- from matplotlib.cm import ScalarMappable
10
- from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
11
- from matplotlib.figure import Figure
12
-
13
- import mesa
14
- from mesa.experimental.cell_space import Grid, VoronoiGrid
15
- from mesa.space import PropertyLayer
16
- from mesa.visualization.utils import update_counter
17
-
18
-
19
- def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None):
20
- """Create a Matplotlib-based space visualization component.
21
-
22
- Args:
23
- agent_portrayal (function): Function to portray agents
24
- propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications
25
-
26
- Returns:
27
- function: A function that creates a SpaceMatplotlib component
28
- """
29
- if agent_portrayal is None:
30
-
31
- def agent_portrayal(a):
32
- return {"id": a.unique_id}
33
-
34
- def MakeSpaceMatplotlib(model):
35
- return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal)
36
-
37
- return MakeSpaceMatplotlib
38
-
39
-
40
- @solara.component
41
- def SpaceMatplotlib(
42
- model,
43
- agent_portrayal,
44
- propertylayer_portrayal,
45
- dependencies: list[any] | None = None,
46
- ):
47
- """Create a Matplotlib-based space visualization component."""
48
- update_counter.get()
49
- space_fig = Figure()
50
- space_ax = space_fig.subplots()
51
- space = getattr(model, "grid", None)
52
- if space is None:
53
- space = getattr(model, "space", None)
54
-
55
- # https://stackoverflow.com/questions/67524641/convert-multiple-isinstance-checks-to-structural-pattern-matching
56
- match space:
57
- case mesa.space._Grid():
58
- _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model)
59
- case mesa.space.ContinuousSpace():
60
- _draw_continuous_space(space, space_ax, agent_portrayal, model)
61
- case mesa.space.NetworkGrid():
62
- _draw_network_grid(space, space_ax, agent_portrayal)
63
- case VoronoiGrid():
64
- _draw_voronoi(space, space_ax, agent_portrayal)
65
- case Grid(): # matches OrthogonalMooreGrid, OrthogonalVonNeumannGrid, and Hexgrid
66
- # fixme add a separate draw method for hexgrids in the future
67
- _draw_discrete_space_grid(space, space_ax, agent_portrayal)
68
- case None:
69
- if propertylayer_portrayal:
70
- draw_property_layers(space_ax, space, propertylayer_portrayal, model)
71
-
72
- solara.FigureMatplotlib(
73
- space_fig, format="png", bbox_inches="tight", dependencies=dependencies
74
- )
75
-
76
-
77
- def draw_property_layers(ax, space, propertylayer_portrayal, model):
78
- """Draw PropertyLayers on the given axes.
79
-
80
- Args:
81
- ax (matplotlib.axes.Axes): The axes to draw on.
82
- space (mesa.space._Grid): The space containing the PropertyLayers.
83
- propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications.
84
- model (mesa.Model): The model instance.
85
- """
86
- for layer_name, portrayal in propertylayer_portrayal.items():
87
- layer = getattr(model, layer_name, None)
88
- if not isinstance(layer, PropertyLayer):
89
- continue
90
-
91
- data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
92
- width, height = data.shape if space is None else (space.width, space.height)
93
-
94
- if space and data.shape != (width, height):
95
- warnings.warn(
96
- f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
97
- UserWarning,
98
- stacklevel=2,
99
- )
100
-
101
- # Get portrayal properties, or use defaults
102
- alpha = portrayal.get("alpha", 1)
103
- vmin = portrayal.get("vmin", np.min(data))
104
- vmax = portrayal.get("vmax", np.max(data))
105
- colorbar = portrayal.get("colorbar", True)
106
-
107
- # Draw the layer
108
- if "color" in portrayal:
109
- rgba_color = to_rgba(portrayal["color"])
110
- normalized_data = (data - vmin) / (vmax - vmin)
111
- rgba_data = np.full((*data.shape, 4), rgba_color)
112
- rgba_data[..., 3] *= normalized_data * alpha
113
- rgba_data = np.clip(rgba_data, 0, 1)
114
- cmap = LinearSegmentedColormap.from_list(
115
- layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
116
- )
117
- im = ax.imshow(
118
- rgba_data.transpose(1, 0, 2),
119
- extent=(0, width, 0, height),
120
- origin="lower",
121
- )
122
- if colorbar:
123
- norm = Normalize(vmin=vmin, vmax=vmax)
124
- sm = ScalarMappable(norm=norm, cmap=cmap)
125
- sm.set_array([])
126
- ax.figure.colorbar(sm, ax=ax, orientation="vertical")
127
-
128
- elif "colormap" in portrayal:
129
- cmap = portrayal.get("colormap", "viridis")
130
- if isinstance(cmap, list):
131
- cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
132
- im = ax.imshow(
133
- data.T,
134
- cmap=cmap,
135
- alpha=alpha,
136
- vmin=vmin,
137
- vmax=vmax,
138
- extent=(0, width, 0, height),
139
- origin="lower",
140
- )
141
- if colorbar:
142
- plt.colorbar(im, ax=ax, label=layer_name)
143
- else:
144
- raise ValueError(
145
- f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
146
- )
147
-
148
-
149
- def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model):
150
- if propertylayer_portrayal:
151
- draw_property_layers(space_ax, space, propertylayer_portrayal, model)
152
-
153
- agent_data = _get_agent_data(space, agent_portrayal)
154
-
155
- space_ax.set_xlim(0, space.width)
156
- space_ax.set_ylim(0, space.height)
157
- _split_and_scatter(agent_data, space_ax)
158
-
159
- # Draw grid lines
160
- for x in range(space.width + 1):
161
- space_ax.axvline(x, color="gray", linestyle=":")
162
- for y in range(space.height + 1):
163
- space_ax.axhline(y, color="gray", linestyle=":")
164
-
165
-
166
- def _get_agent_data(space, agent_portrayal):
167
- """Helper function to get agent data for visualization."""
168
- x, y, s, c, m = [], [], [], [], []
169
- for agents, pos in space.coord_iter():
170
- if not agents:
171
- continue
172
- if not isinstance(agents, list):
173
- agents = [agents] # noqa PLW2901
174
- for agent in agents:
175
- data = agent_portrayal(agent)
176
- x.append(pos[0] + 0.5) # Center the agent in the cell
177
- y.append(pos[1] + 0.5) # Center the agent in the cell
178
- default_size = (180 / max(space.width, space.height)) ** 2
179
- s.append(data.get("size", default_size))
180
- c.append(data.get("color", "tab:blue"))
181
- m.append(data.get("shape", "o"))
182
- return {"x": x, "y": y, "s": s, "c": c, "m": m}
183
-
184
-
185
- def _split_and_scatter(portray_data, space_ax):
186
- """Helper function to split and scatter agent data."""
187
- for marker in set(portray_data["m"]):
188
- mask = [m == marker for m in portray_data["m"]]
189
- space_ax.scatter(
190
- [x for x, show in zip(portray_data["x"], mask) if show],
191
- [y for y, show in zip(portray_data["y"], mask) if show],
192
- s=[s for s, show in zip(portray_data["s"], mask) if show],
193
- c=[c for c, show in zip(portray_data["c"], mask) if show],
194
- marker=marker,
195
- )
196
-
197
-
198
- def _draw_network_grid(space, space_ax, agent_portrayal):
199
- graph = space.G
200
- pos = nx.spring_layout(graph, seed=0)
201
- nx.draw(
202
- graph,
203
- ax=space_ax,
204
- pos=pos,
205
- **agent_portrayal(graph),
206
- )
207
-
208
-
209
- def _draw_continuous_space(space, space_ax, agent_portrayal, model):
210
- def portray(space):
211
- x = []
212
- y = []
213
- s = [] # size
214
- c = [] # color
215
- m = [] # shape
216
- for agent in space._agent_to_index:
217
- data = agent_portrayal(agent)
218
- _x, _y = agent.pos
219
- x.append(_x)
220
- y.append(_y)
221
-
222
- # This is matplotlib's default marker size
223
- default_size = 20
224
- size = data.get("size", default_size)
225
- s.append(size)
226
- color = data.get("color", "tab:blue")
227
- c.append(color)
228
- mark = data.get("shape", "o")
229
- m.append(mark)
230
- return {"x": x, "y": y, "s": s, "c": c, "m": m}
231
-
232
- # Determine border style based on space.torus
233
- border_style = "solid" if not space.torus else (0, (5, 10))
234
-
235
- # Set the border of the plot
236
- for spine in space_ax.spines.values():
237
- spine.set_linewidth(1.5)
238
- spine.set_color("black")
239
- spine.set_linestyle(border_style)
240
-
241
- width = space.x_max - space.x_min
242
- x_padding = width / 20
243
- height = space.y_max - space.y_min
244
- y_padding = height / 20
245
- space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
246
- space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
247
-
248
- # Portray and scatter the agents in the space
249
- _split_and_scatter(portray(space), space_ax)
250
-
251
-
252
- def _draw_voronoi(space, space_ax, agent_portrayal):
253
- def portray(g):
254
- x = []
255
- y = []
256
- s = [] # size
257
- c = [] # color
258
-
259
- for cell in g.all_cells:
260
- for agent in cell.agents:
261
- data = agent_portrayal(agent)
262
- x.append(cell.coordinate[0])
263
- y.append(cell.coordinate[1])
264
- if "size" in data:
265
- s.append(data["size"])
266
- if "color" in data:
267
- c.append(data["color"])
268
- out = {"x": x, "y": y}
269
- out["s"] = s
270
- if len(c) > 0:
271
- out["c"] = c
272
-
273
- return out
274
-
275
- x_list = [i[0] for i in space.centroids_coordinates]
276
- y_list = [i[1] for i in space.centroids_coordinates]
277
- x_max = max(x_list)
278
- x_min = min(x_list)
279
- y_max = max(y_list)
280
- y_min = min(y_list)
281
-
282
- width = x_max - x_min
283
- x_padding = width / 20
284
- height = y_max - y_min
285
- y_padding = height / 20
286
- space_ax.set_xlim(x_min - x_padding, x_max + x_padding)
287
- space_ax.set_ylim(y_min - y_padding, y_max + y_padding)
288
- space_ax.scatter(**portray(space))
289
-
290
- for cell in space.all_cells:
291
- polygon = cell.properties["polygon"]
292
- space_ax.fill(
293
- *zip(*polygon),
294
- alpha=min(1, cell.properties[space.cell_coloring_property]),
295
- c="red",
296
- ) # Plot filled polygon
297
- space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black
298
-
299
-
300
- def _draw_discrete_space_grid(space: Grid, space_ax, agent_portrayal):
301
- if space._ndims != 2:
302
- raise ValueError("Space must be 2D")
303
-
304
- def portray(g):
305
- x = []
306
- y = []
307
- s = [] # size
308
- c = [] # color
309
-
310
- for cell in g.all_cells:
311
- for agent in cell.agents:
312
- data = agent_portrayal(agent)
313
- x.append(cell.coordinate[0])
314
- y.append(cell.coordinate[1])
315
- if "size" in data:
316
- s.append(data["size"])
317
- if "color" in data:
318
- c.append(data["color"])
319
- out = {"x": x, "y": y}
320
- out["s"] = s
321
- if len(c) > 0:
322
- out["c"] = c
323
-
324
- return out
325
-
326
- space_ax.set_xlim(0, space.width)
327
- space_ax.set_ylim(0, space.height)
328
-
329
- # Draw grid lines
330
- for x in range(space.width + 1):
331
- space_ax.axvline(x, color="gray", linestyle=":")
332
- for y in range(space.height + 1):
333
- space_ax.axhline(y, color="gray", linestyle=":")
334
-
335
- space_ax.scatter(**portray(space))
336
-
337
-
338
- def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
339
- """Create a plotting function for a specified measure.
340
-
341
- Args:
342
- measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
343
-
344
- Returns:
345
- function: A function that creates a PlotMatplotlib component.
346
- """
347
-
348
- def MakePlotMeasure(model):
349
- return PlotMatplotlib(model, measure)
350
-
351
- return MakePlotMeasure
352
-
353
-
354
- @solara.component
355
- def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
356
- """Create a Matplotlib-based plot for a measure or measures.
357
-
358
- Args:
359
- model (mesa.Model): The model instance.
360
- measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
361
- dependencies (list[any] | None): Optional dependencies for the plot.
362
-
363
- Returns:
364
- solara.FigureMatplotlib: A component for rendering the plot.
365
- """
366
- update_counter.get()
367
- fig = Figure()
368
- ax = fig.subplots()
369
- df = model.datacollector.get_model_vars_dataframe()
370
- if isinstance(measure, str):
371
- ax.plot(df.loc[:, measure])
372
- ax.set_ylabel(measure)
373
- elif isinstance(measure, dict):
374
- for m, color in measure.items():
375
- ax.plot(df.loc[:, m], label=m, color=color)
376
- ax.legend(loc="best")
377
- elif isinstance(measure, list | tuple):
378
- for m in measure:
379
- ax.plot(df.loc[:, m], label=m)
380
- ax.legend(loc="best")
381
- ax.set_xlabel("Step")
382
- # Set integer x axis
383
- ax.xaxis.set_major_locator(plt.MaxNLocator(integer=True))
384
- solara.FigureMatplotlib(
385
- fig, format="png", bbox_inches="tight", dependencies=dependencies
386
- )
File without changes