Mesa 3.0.0__py3-none-any.whl → 3.0.0a0__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 (104) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +114 -406
  3. mesa/batchrunner.py +27 -54
  4. mesa/cookiecutter-mesa/cookiecutter.json +8 -0
  5. mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
  6. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
  7. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  8. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
  9. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
  10. mesa/datacollection.py +29 -140
  11. mesa/experimental/__init__.py +1 -11
  12. mesa/experimental/cell_space/__init__.py +1 -16
  13. mesa/experimental/cell_space/cell.py +23 -93
  14. mesa/experimental/cell_space/cell_agent.py +21 -117
  15. mesa/experimental/cell_space/cell_collection.py +17 -54
  16. mesa/experimental/cell_space/discrete_space.py +8 -92
  17. mesa/experimental/cell_space/grid.py +8 -32
  18. mesa/experimental/cell_space/network.py +7 -12
  19. mesa/experimental/devs/__init__.py +0 -2
  20. mesa/experimental/devs/eventlist.py +14 -52
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
  22. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  23. mesa/experimental/devs/simulator.py +15 -55
  24. mesa/main.py +63 -0
  25. mesa/model.py +83 -211
  26. mesa/space.py +149 -215
  27. mesa/time.py +77 -62
  28. mesa/{experimental → visualization}/UserParam.py +6 -17
  29. mesa/visualization/__init__.py +2 -25
  30. mesa/{experimental → visualization}/components/altair.py +0 -10
  31. mesa/visualization/components/matplotlib.py +134 -0
  32. mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
  33. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
  34. mesa-3.0.0a0.dist-info/RECORD +38 -0
  35. mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
  36. mesa/examples/README.md +0 -37
  37. mesa/examples/__init__.py +0 -21
  38. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
  39. mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
  40. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  41. mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
  42. mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
  43. mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
  44. mesa/examples/advanced/pd_grid/Readme.md +0 -43
  45. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  46. mesa/examples/advanced/pd_grid/agents.py +0 -50
  47. mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
  48. mesa/examples/advanced/pd_grid/app.py +0 -54
  49. mesa/examples/advanced/pd_grid/model.py +0 -71
  50. mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
  51. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  52. mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
  53. mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
  54. mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
  55. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
  56. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  57. mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
  58. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  59. mesa/examples/advanced/wolf_sheep/agents.py +0 -102
  60. mesa/examples/advanced/wolf_sheep/app.py +0 -84
  61. mesa/examples/advanced/wolf_sheep/model.py +0 -137
  62. mesa/examples/basic/__init__.py +0 -0
  63. mesa/examples/basic/boid_flockers/Readme.md +0 -22
  64. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  65. mesa/examples/basic/boid_flockers/agents.py +0 -71
  66. mesa/examples/basic/boid_flockers/app.py +0 -58
  67. mesa/examples/basic/boid_flockers/model.py +0 -69
  68. mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
  69. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  70. mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
  71. mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
  72. mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
  73. mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
  74. mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
  75. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  76. mesa/examples/basic/conways_game_of_life/agents.py +0 -47
  77. mesa/examples/basic/conways_game_of_life/app.py +0 -51
  78. mesa/examples/basic/conways_game_of_life/model.py +0 -31
  79. mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
  80. mesa/examples/basic/schelling/Readme.md +0 -40
  81. mesa/examples/basic/schelling/__init__.py +0 -0
  82. mesa/examples/basic/schelling/agents.py +0 -26
  83. mesa/examples/basic/schelling/analysis.ipynb +0 -205
  84. mesa/examples/basic/schelling/app.py +0 -42
  85. mesa/examples/basic/schelling/model.py +0 -59
  86. mesa/examples/basic/virus_on_network/Readme.md +0 -61
  87. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  88. mesa/examples/basic/virus_on_network/agents.py +0 -69
  89. mesa/examples/basic/virus_on_network/app.py +0 -114
  90. mesa/examples/basic/virus_on_network/model.py +0 -96
  91. mesa/experimental/cell_space/voronoi.py +0 -257
  92. mesa/experimental/components/matplotlib.py +0 -242
  93. mesa/visualization/components/__init__.py +0 -83
  94. mesa/visualization/components/altair_components.py +0 -188
  95. mesa/visualization/components/matplotlib_components.py +0 -175
  96. mesa/visualization/mpl_space_drawing.py +0 -593
  97. mesa/visualization/solara_viz.py +0 -458
  98. mesa/visualization/user_param.py +0 -69
  99. mesa/visualization/utils.py +0 -9
  100. mesa-3.0.0.dist-info/RECORD +0 -95
  101. mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
  102. /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
  103. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
  104. {mesa-3.0.0.dist-info → mesa-3.0.0a0.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
- )