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.
- 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/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
- mesa-3.0.0a0.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.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/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/solara_viz.py +0 -458
- 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.0a0.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
"""Support for Voronoi meshed grids."""
|
|
2
|
-
|
|
3
|
-
from collections.abc import Sequence
|
|
4
|
-
from itertools import combinations
|
|
5
|
-
from random import Random
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
|
|
9
|
-
from mesa.experimental.cell_space.cell import Cell
|
|
10
|
-
from mesa.experimental.cell_space.discrete_space import DiscreteSpace
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class Delaunay:
|
|
14
|
-
"""Class to compute a Delaunay triangulation in 2D.
|
|
15
|
-
|
|
16
|
-
ref: http://github.com/jmespadero/pyDelaunay2D
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def __init__(self, center: tuple = (0, 0), radius: int = 9999) -> None:
|
|
20
|
-
"""Init and create a new frame to contain the triangulation.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
center: Optional position for the center of the frame. Default (0,0)
|
|
24
|
-
radius: Optional distance from corners to the center.
|
|
25
|
-
"""
|
|
26
|
-
center = np.asarray(center)
|
|
27
|
-
# Create coordinates for the corners of the frame
|
|
28
|
-
self.coords = [
|
|
29
|
-
center + radius * np.array((-1, -1)),
|
|
30
|
-
center + radius * np.array((+1, -1)),
|
|
31
|
-
center + radius * np.array((+1, +1)),
|
|
32
|
-
center + radius * np.array((-1, +1)),
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
# Create two dicts to store triangle neighbours and circumcircles.
|
|
36
|
-
self.triangles = {}
|
|
37
|
-
self.circles = {}
|
|
38
|
-
|
|
39
|
-
# Create two CCW triangles for the frame
|
|
40
|
-
triangle1 = (0, 1, 3)
|
|
41
|
-
triangle2 = (2, 3, 1)
|
|
42
|
-
self.triangles[triangle1] = [triangle2, None, None]
|
|
43
|
-
self.triangles[triangle2] = [triangle1, None, None]
|
|
44
|
-
|
|
45
|
-
# Compute circumcenters and circumradius for each triangle
|
|
46
|
-
for t in self.triangles:
|
|
47
|
-
self.circles[t] = self._circumcenter(t)
|
|
48
|
-
|
|
49
|
-
def _circumcenter(self, triangle: list) -> tuple:
|
|
50
|
-
"""Compute circumcenter and circumradius of a triangle in 2D."""
|
|
51
|
-
points = np.asarray([self.coords[v] for v in triangle])
|
|
52
|
-
points2 = np.dot(points, points.T)
|
|
53
|
-
a = np.bmat([[2 * points2, [[1], [1], [1]]], [[[1, 1, 1, 0]]]])
|
|
54
|
-
|
|
55
|
-
b = np.hstack((np.sum(points * points, axis=1), [1]))
|
|
56
|
-
x = np.linalg.solve(a, b)
|
|
57
|
-
bary_coords = x[:-1]
|
|
58
|
-
center = np.dot(bary_coords, points)
|
|
59
|
-
|
|
60
|
-
radius = np.sum(np.square(points[0] - center)) # squared distance
|
|
61
|
-
return (center, radius)
|
|
62
|
-
|
|
63
|
-
def _in_circle(self, triangle: list, point: list) -> bool:
|
|
64
|
-
"""Check if point p is inside of precomputed circumcircle of triangle."""
|
|
65
|
-
center, radius = self.circles[triangle]
|
|
66
|
-
return np.sum(np.square(center - point)) <= radius
|
|
67
|
-
|
|
68
|
-
def add_point(self, point: Sequence) -> None:
|
|
69
|
-
"""Add a point to the current DT, and refine it using Bowyer-Watson."""
|
|
70
|
-
point_index = len(self.coords)
|
|
71
|
-
self.coords.append(np.asarray(point))
|
|
72
|
-
|
|
73
|
-
bad_triangles = []
|
|
74
|
-
for triangle in self.triangles:
|
|
75
|
-
if self._in_circle(triangle, point):
|
|
76
|
-
bad_triangles.append(triangle)
|
|
77
|
-
|
|
78
|
-
boundary = []
|
|
79
|
-
triangle = bad_triangles[0]
|
|
80
|
-
edge = 0
|
|
81
|
-
|
|
82
|
-
while True:
|
|
83
|
-
opposite_triangle = self.triangles[triangle][edge]
|
|
84
|
-
if opposite_triangle not in bad_triangles:
|
|
85
|
-
boundary.append(
|
|
86
|
-
(
|
|
87
|
-
triangle[(edge + 1) % 3],
|
|
88
|
-
triangle[(edge - 1) % 3],
|
|
89
|
-
opposite_triangle,
|
|
90
|
-
)
|
|
91
|
-
)
|
|
92
|
-
edge = (edge + 1) % 3
|
|
93
|
-
if boundary[0][0] == boundary[-1][1]:
|
|
94
|
-
break
|
|
95
|
-
else:
|
|
96
|
-
edge = (self.triangles[opposite_triangle].index(triangle) + 1) % 3
|
|
97
|
-
triangle = opposite_triangle
|
|
98
|
-
|
|
99
|
-
for triangle in bad_triangles:
|
|
100
|
-
del self.triangles[triangle]
|
|
101
|
-
del self.circles[triangle]
|
|
102
|
-
|
|
103
|
-
new_triangles = []
|
|
104
|
-
for e0, e1, opposite_triangle in boundary:
|
|
105
|
-
triangle = (point_index, e0, e1)
|
|
106
|
-
self.circles[triangle] = self._circumcenter(triangle)
|
|
107
|
-
self.triangles[triangle] = [opposite_triangle, None, None]
|
|
108
|
-
if opposite_triangle:
|
|
109
|
-
for i, neighbor in enumerate(self.triangles[opposite_triangle]):
|
|
110
|
-
if neighbor and e1 in neighbor and e0 in neighbor:
|
|
111
|
-
self.triangles[opposite_triangle][i] = triangle
|
|
112
|
-
|
|
113
|
-
new_triangles.append(triangle)
|
|
114
|
-
|
|
115
|
-
n = len(new_triangles)
|
|
116
|
-
for i, triangle in enumerate(new_triangles):
|
|
117
|
-
self.triangles[triangle][1] = new_triangles[(i + 1) % n] # next
|
|
118
|
-
self.triangles[triangle][2] = new_triangles[(i - 1) % n] # previous
|
|
119
|
-
|
|
120
|
-
def export_triangles(self) -> list:
|
|
121
|
-
"""Export the current list of Delaunay triangles."""
|
|
122
|
-
triangles_list = [
|
|
123
|
-
(a - 4, b - 4, c - 4)
|
|
124
|
-
for (a, b, c) in self.triangles
|
|
125
|
-
if a > 3 and b > 3 and c > 3
|
|
126
|
-
]
|
|
127
|
-
return triangles_list
|
|
128
|
-
|
|
129
|
-
def export_voronoi_regions(self):
|
|
130
|
-
"""Export coordinates and regions of Voronoi diagram as indexed data."""
|
|
131
|
-
use_vertex = {i: [] for i in range(len(self.coords))}
|
|
132
|
-
vor_coors = []
|
|
133
|
-
index = {}
|
|
134
|
-
for triangle_index, (a, b, c) in enumerate(sorted(self.triangles)):
|
|
135
|
-
vor_coors.append(self.circles[(a, b, c)][0])
|
|
136
|
-
use_vertex[a] += [(b, c, a)]
|
|
137
|
-
use_vertex[b] += [(c, a, b)]
|
|
138
|
-
use_vertex[c] += [(a, b, c)]
|
|
139
|
-
|
|
140
|
-
index[(a, b, c)] = triangle_index
|
|
141
|
-
index[(c, a, b)] = triangle_index
|
|
142
|
-
index[(b, c, a)] = triangle_index
|
|
143
|
-
|
|
144
|
-
regions = {}
|
|
145
|
-
for i in range(4, len(self.coords)):
|
|
146
|
-
vertex = use_vertex[i][0][0]
|
|
147
|
-
region = []
|
|
148
|
-
for _ in range(len(use_vertex[i])):
|
|
149
|
-
triangle = next(
|
|
150
|
-
triangle for triangle in use_vertex[i] if triangle[0] == vertex
|
|
151
|
-
)
|
|
152
|
-
region.append(index[triangle])
|
|
153
|
-
vertex = triangle[1]
|
|
154
|
-
regions[i - 4] = region
|
|
155
|
-
|
|
156
|
-
return vor_coors, regions
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def round_float(x: float) -> int: # noqa
|
|
160
|
-
return int(x * 500)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class VoronoiGrid(DiscreteSpace):
|
|
164
|
-
"""Voronoi meshed GridSpace."""
|
|
165
|
-
|
|
166
|
-
triangulation: Delaunay
|
|
167
|
-
voronoi_coordinates: list
|
|
168
|
-
regions: list
|
|
169
|
-
|
|
170
|
-
def __init__(
|
|
171
|
-
self,
|
|
172
|
-
centroids_coordinates: Sequence[Sequence[float]],
|
|
173
|
-
capacity: float | None = None,
|
|
174
|
-
random: Random | None = None,
|
|
175
|
-
cell_klass: type[Cell] = Cell,
|
|
176
|
-
capacity_function: callable = round_float,
|
|
177
|
-
cell_coloring_property: str | None = None,
|
|
178
|
-
) -> None:
|
|
179
|
-
"""A Voronoi Tessellation Grid.
|
|
180
|
-
|
|
181
|
-
Given a set of points, this class creates a grid where a cell is centered in each point,
|
|
182
|
-
its neighbors are given by Voronoi Tessellation cells neighbors
|
|
183
|
-
and the capacity by the polygon area.
|
|
184
|
-
|
|
185
|
-
Args:
|
|
186
|
-
centroids_coordinates: coordinates of centroids to build the tessellation space
|
|
187
|
-
capacity (int) : capacity of the cells in the discrete space
|
|
188
|
-
random (Random): random number generator
|
|
189
|
-
cell_klass (type[Cell]): type of cell class
|
|
190
|
-
capacity_function (Callable): function to compute (int) capacity according to (float) area
|
|
191
|
-
cell_coloring_property (str): voronoi visualization polygon fill property
|
|
192
|
-
"""
|
|
193
|
-
super().__init__(capacity=capacity, random=random, cell_klass=cell_klass)
|
|
194
|
-
self.centroids_coordinates = centroids_coordinates
|
|
195
|
-
self._validate_parameters()
|
|
196
|
-
|
|
197
|
-
self._cells = {
|
|
198
|
-
i: cell_klass(self.centroids_coordinates[i], capacity, random=self.random)
|
|
199
|
-
for i in range(len(self.centroids_coordinates))
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
self.regions = None
|
|
203
|
-
self.triangulation = None
|
|
204
|
-
self.voronoi_coordinates = None
|
|
205
|
-
self.capacity_function = capacity_function
|
|
206
|
-
self.cell_coloring_property = cell_coloring_property
|
|
207
|
-
|
|
208
|
-
self._connect_cells()
|
|
209
|
-
self._build_cell_polygons()
|
|
210
|
-
|
|
211
|
-
def _connect_cells(self) -> None:
|
|
212
|
-
"""Connect cells to neighbors based on given centroids and using Delaunay Triangulation."""
|
|
213
|
-
self.triangulation = Delaunay()
|
|
214
|
-
for centroid in self.centroids_coordinates:
|
|
215
|
-
self.triangulation.add_point(centroid)
|
|
216
|
-
|
|
217
|
-
for point in self.triangulation.export_triangles():
|
|
218
|
-
for i, j in combinations(point, 2):
|
|
219
|
-
self._cells[i].connect(self._cells[j], (i, j))
|
|
220
|
-
self._cells[j].connect(self._cells[i], (j, i))
|
|
221
|
-
|
|
222
|
-
def _validate_parameters(self) -> None:
|
|
223
|
-
if self.capacity is not None and not isinstance(self.capacity, float | int):
|
|
224
|
-
raise ValueError("Capacity must be a number or None.")
|
|
225
|
-
if not isinstance(self.centroids_coordinates, Sequence) or not isinstance(
|
|
226
|
-
self.centroids_coordinates[0], Sequence
|
|
227
|
-
):
|
|
228
|
-
raise ValueError("Centroids should be a list of lists")
|
|
229
|
-
dimension_1 = len(self.centroids_coordinates[0])
|
|
230
|
-
for coordinate in self.centroids_coordinates:
|
|
231
|
-
if dimension_1 != len(coordinate):
|
|
232
|
-
raise ValueError("Centroid coordinates should be a homogeneous array")
|
|
233
|
-
|
|
234
|
-
def _get_voronoi_regions(self) -> tuple:
|
|
235
|
-
if self.voronoi_coordinates is None or self.regions is None:
|
|
236
|
-
(
|
|
237
|
-
self.voronoi_coordinates,
|
|
238
|
-
self.regions,
|
|
239
|
-
) = self.triangulation.export_voronoi_regions()
|
|
240
|
-
return self.voronoi_coordinates, self.regions
|
|
241
|
-
|
|
242
|
-
@staticmethod
|
|
243
|
-
def _compute_polygon_area(polygon: list) -> float:
|
|
244
|
-
polygon = np.array(polygon)
|
|
245
|
-
x = polygon[:, 0]
|
|
246
|
-
y = polygon[:, 1]
|
|
247
|
-
return 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
|
|
248
|
-
|
|
249
|
-
def _build_cell_polygons(self):
|
|
250
|
-
coordinates, regions = self._get_voronoi_regions()
|
|
251
|
-
for region in regions:
|
|
252
|
-
polygon = [coordinates[i] for i in regions[region]]
|
|
253
|
-
self._cells[region].properties["polygon"] = polygon
|
|
254
|
-
polygon_area = self._compute_polygon_area(polygon)
|
|
255
|
-
self._cells[region].properties["area"] = polygon_area
|
|
256
|
-
self._cells[region].capacity = self.capacity_function(polygon_area)
|
|
257
|
-
self._cells[region].properties[self.cell_coloring_property] = 0
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
"""Support for using matplotlib to draw spaces."""
|
|
2
|
-
|
|
3
|
-
from collections import defaultdict
|
|
4
|
-
|
|
5
|
-
import networkx as nx
|
|
6
|
-
import solara
|
|
7
|
-
from matplotlib.figure import Figure
|
|
8
|
-
from matplotlib.ticker import MaxNLocator
|
|
9
|
-
|
|
10
|
-
import mesa
|
|
11
|
-
from mesa.experimental.cell_space import VoronoiGrid
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@solara.component
|
|
15
|
-
def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
16
|
-
"""A component for rendering a space using Matplotlib.
|
|
17
|
-
|
|
18
|
-
Args:
|
|
19
|
-
model: a model instance
|
|
20
|
-
agent_portrayal: a specification of how to portray an agent.
|
|
21
|
-
dependencies: list of dependencies.
|
|
22
|
-
|
|
23
|
-
"""
|
|
24
|
-
space_fig = Figure()
|
|
25
|
-
space_ax = space_fig.subplots()
|
|
26
|
-
space = getattr(model, "grid", None)
|
|
27
|
-
if space is None:
|
|
28
|
-
# Sometimes the space is defined as model.space instead of model.grid
|
|
29
|
-
space = model.space
|
|
30
|
-
if isinstance(space, mesa.space.NetworkGrid):
|
|
31
|
-
_draw_network_grid(space, space_ax, agent_portrayal)
|
|
32
|
-
elif isinstance(space, mesa.space.ContinuousSpace):
|
|
33
|
-
_draw_continuous_space(space, space_ax, agent_portrayal)
|
|
34
|
-
elif isinstance(space, VoronoiGrid):
|
|
35
|
-
_draw_voronoi(space, space_ax, agent_portrayal)
|
|
36
|
-
else:
|
|
37
|
-
_draw_grid(space, space_ax, agent_portrayal)
|
|
38
|
-
solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# matplotlib scatter does not allow for multiple shapes in one call
|
|
42
|
-
def _split_and_scatter(portray_data, space_ax):
|
|
43
|
-
grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
|
|
44
|
-
|
|
45
|
-
# Extract data from the dictionary
|
|
46
|
-
x = portray_data["x"]
|
|
47
|
-
y = portray_data["y"]
|
|
48
|
-
s = portray_data["s"]
|
|
49
|
-
c = portray_data["c"]
|
|
50
|
-
m = portray_data["m"]
|
|
51
|
-
|
|
52
|
-
if not (len(x) == len(y) == len(s) == len(c) == len(m)):
|
|
53
|
-
raise ValueError(
|
|
54
|
-
"Length mismatch in portrayal data lists: "
|
|
55
|
-
f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
|
|
56
|
-
f"color: {len(c)}, marker: {len(m)}"
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# Group the data by marker
|
|
60
|
-
for i in range(len(x)):
|
|
61
|
-
marker = m[i]
|
|
62
|
-
grouped_data[marker]["x"].append(x[i])
|
|
63
|
-
grouped_data[marker]["y"].append(y[i])
|
|
64
|
-
grouped_data[marker]["s"].append(s[i])
|
|
65
|
-
grouped_data[marker]["c"].append(c[i])
|
|
66
|
-
|
|
67
|
-
# Plot each group with the same marker
|
|
68
|
-
for marker, data in grouped_data.items():
|
|
69
|
-
space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _draw_grid(space, space_ax, agent_portrayal):
|
|
73
|
-
def portray(g):
|
|
74
|
-
x = []
|
|
75
|
-
y = []
|
|
76
|
-
s = [] # size
|
|
77
|
-
c = [] # color
|
|
78
|
-
m = [] # shape
|
|
79
|
-
for i in range(g.width):
|
|
80
|
-
for j in range(g.height):
|
|
81
|
-
content = g._grid[i][j]
|
|
82
|
-
if not content:
|
|
83
|
-
continue
|
|
84
|
-
if not hasattr(content, "__iter__"):
|
|
85
|
-
# Is a single grid
|
|
86
|
-
content = [content]
|
|
87
|
-
for agent in content:
|
|
88
|
-
data = agent_portrayal(agent)
|
|
89
|
-
x.append(i)
|
|
90
|
-
y.append(j)
|
|
91
|
-
|
|
92
|
-
# This is the default value for the marker size, which auto-scales
|
|
93
|
-
# according to the grid area.
|
|
94
|
-
default_size = (180 / max(g.width, g.height)) ** 2
|
|
95
|
-
# establishing a default prevents misalignment if some agents are not given size, color, etc.
|
|
96
|
-
size = data.get("size", default_size)
|
|
97
|
-
s.append(size)
|
|
98
|
-
color = data.get("color", "tab:blue")
|
|
99
|
-
c.append(color)
|
|
100
|
-
mark = data.get("shape", "o")
|
|
101
|
-
m.append(mark)
|
|
102
|
-
out = {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
103
|
-
return out
|
|
104
|
-
|
|
105
|
-
space_ax.set_xlim(-1, space.width)
|
|
106
|
-
space_ax.set_ylim(-1, space.height)
|
|
107
|
-
_split_and_scatter(portray(space), space_ax)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _draw_network_grid(space, space_ax, agent_portrayal):
|
|
111
|
-
graph = space.G
|
|
112
|
-
pos = nx.spring_layout(graph, seed=0)
|
|
113
|
-
nx.draw(
|
|
114
|
-
graph,
|
|
115
|
-
ax=space_ax,
|
|
116
|
-
pos=pos,
|
|
117
|
-
**agent_portrayal(graph),
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _draw_continuous_space(space, space_ax, agent_portrayal):
|
|
122
|
-
def portray(space):
|
|
123
|
-
x = []
|
|
124
|
-
y = []
|
|
125
|
-
s = [] # size
|
|
126
|
-
c = [] # color
|
|
127
|
-
m = [] # shape
|
|
128
|
-
for agent in space._agent_to_index:
|
|
129
|
-
data = agent_portrayal(agent)
|
|
130
|
-
_x, _y = agent.pos
|
|
131
|
-
x.append(_x)
|
|
132
|
-
y.append(_y)
|
|
133
|
-
|
|
134
|
-
# This is matplotlib's default marker size
|
|
135
|
-
default_size = 20
|
|
136
|
-
# establishing a default prevents misalignment if some agents are not given size, color, etc.
|
|
137
|
-
size = data.get("size", default_size)
|
|
138
|
-
s.append(size)
|
|
139
|
-
color = data.get("color", "tab:blue")
|
|
140
|
-
c.append(color)
|
|
141
|
-
mark = data.get("shape", "o")
|
|
142
|
-
m.append(mark)
|
|
143
|
-
out = {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
144
|
-
return out
|
|
145
|
-
|
|
146
|
-
# Determine border style based on space.torus
|
|
147
|
-
border_style = "solid" if not space.torus else (0, (5, 10))
|
|
148
|
-
|
|
149
|
-
# Set the border of the plot
|
|
150
|
-
for spine in space_ax.spines.values():
|
|
151
|
-
spine.set_linewidth(1.5)
|
|
152
|
-
spine.set_color("black")
|
|
153
|
-
spine.set_linestyle(border_style)
|
|
154
|
-
|
|
155
|
-
width = space.x_max - space.x_min
|
|
156
|
-
x_padding = width / 20
|
|
157
|
-
height = space.y_max - space.y_min
|
|
158
|
-
y_padding = height / 20
|
|
159
|
-
space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
|
|
160
|
-
space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
|
|
161
|
-
|
|
162
|
-
# Portray and scatter the agents in the space
|
|
163
|
-
_split_and_scatter(portray(space), space_ax)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _draw_voronoi(space, space_ax, agent_portrayal):
|
|
167
|
-
def portray(g):
|
|
168
|
-
x = []
|
|
169
|
-
y = []
|
|
170
|
-
s = [] # size
|
|
171
|
-
c = [] # color
|
|
172
|
-
|
|
173
|
-
for cell in g.all_cells:
|
|
174
|
-
for agent in cell.agents:
|
|
175
|
-
data = agent_portrayal(agent)
|
|
176
|
-
x.append(cell.coordinate[0])
|
|
177
|
-
y.append(cell.coordinate[1])
|
|
178
|
-
if "size" in data:
|
|
179
|
-
s.append(data["size"])
|
|
180
|
-
if "color" in data:
|
|
181
|
-
c.append(data["color"])
|
|
182
|
-
out = {"x": x, "y": y}
|
|
183
|
-
# This is the default value for the marker size, which auto-scales
|
|
184
|
-
# according to the grid area.
|
|
185
|
-
out["s"] = s
|
|
186
|
-
if len(c) > 0:
|
|
187
|
-
out["c"] = c
|
|
188
|
-
|
|
189
|
-
return out
|
|
190
|
-
|
|
191
|
-
x_list = [i[0] for i in space.centroids_coordinates]
|
|
192
|
-
y_list = [i[1] for i in space.centroids_coordinates]
|
|
193
|
-
x_max = max(x_list)
|
|
194
|
-
x_min = min(x_list)
|
|
195
|
-
y_max = max(y_list)
|
|
196
|
-
y_min = min(y_list)
|
|
197
|
-
|
|
198
|
-
width = x_max - x_min
|
|
199
|
-
x_padding = width / 20
|
|
200
|
-
height = y_max - y_min
|
|
201
|
-
y_padding = height / 20
|
|
202
|
-
space_ax.set_xlim(x_min - x_padding, x_max + x_padding)
|
|
203
|
-
space_ax.set_ylim(y_min - y_padding, y_max + y_padding)
|
|
204
|
-
space_ax.scatter(**portray(space))
|
|
205
|
-
|
|
206
|
-
for cell in space.all_cells:
|
|
207
|
-
polygon = cell.properties["polygon"]
|
|
208
|
-
space_ax.fill(
|
|
209
|
-
*zip(*polygon),
|
|
210
|
-
alpha=min(1, cell.properties[space.cell_coloring_property]),
|
|
211
|
-
c="red",
|
|
212
|
-
) # Plot filled polygon
|
|
213
|
-
space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
@solara.component
|
|
217
|
-
def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
|
|
218
|
-
"""A solara component for creating a matplotlib figure.
|
|
219
|
-
|
|
220
|
-
Args:
|
|
221
|
-
model: Model instance
|
|
222
|
-
measure: measure to plot
|
|
223
|
-
dependencies: list of additional dependencies
|
|
224
|
-
|
|
225
|
-
"""
|
|
226
|
-
fig = Figure()
|
|
227
|
-
ax = fig.subplots()
|
|
228
|
-
df = model.datacollector.get_model_vars_dataframe()
|
|
229
|
-
if isinstance(measure, str):
|
|
230
|
-
ax.plot(df.loc[:, measure])
|
|
231
|
-
ax.set_ylabel(measure)
|
|
232
|
-
elif isinstance(measure, dict):
|
|
233
|
-
for m, color in measure.items():
|
|
234
|
-
ax.plot(df.loc[:, m], label=m, color=color)
|
|
235
|
-
fig.legend()
|
|
236
|
-
elif isinstance(measure, list | tuple):
|
|
237
|
-
for m in measure:
|
|
238
|
-
ax.plot(df.loc[:, m], label=m)
|
|
239
|
-
fig.legend()
|
|
240
|
-
# Set integer x axis
|
|
241
|
-
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
242
|
-
solara.FigureMatplotlib(fig, dependencies=dependencies)
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"""custom solara components."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Callable
|
|
6
|
-
|
|
7
|
-
from .altair_components import SpaceAltair, make_altair_space
|
|
8
|
-
from .matplotlib_components import (
|
|
9
|
-
SpaceMatplotlib,
|
|
10
|
-
make_mpl_plot_component,
|
|
11
|
-
make_mpl_space_component,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def make_space_component(
|
|
16
|
-
agent_portrayal: Callable | None = None,
|
|
17
|
-
propertylayer_portrayal: dict | None = None,
|
|
18
|
-
post_process: Callable | None = None,
|
|
19
|
-
backend: str = "matplotlib",
|
|
20
|
-
**space_drawing_kwargs,
|
|
21
|
-
) -> SpaceMatplotlib | SpaceAltair:
|
|
22
|
-
"""Create a Matplotlib-based space visualization component.
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
agent_portrayal: Function to portray agents.
|
|
26
|
-
propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications
|
|
27
|
-
post_process : a callable that will be called with the Axes instance. Allows for fine-tuning plots (e.g., control ticks)
|
|
28
|
-
backend: the backend to use {"matplotlib", "altair"}
|
|
29
|
-
space_drawing_kwargs : additional keyword arguments to be passed on to the underlying backend specific space drawer function. See
|
|
30
|
-
the functions for drawing the various spaces for the appropriate backend further details.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
function: A function that creates a space component
|
|
35
|
-
"""
|
|
36
|
-
if backend == "matplotlib":
|
|
37
|
-
return make_mpl_space_component(
|
|
38
|
-
agent_portrayal,
|
|
39
|
-
propertylayer_portrayal,
|
|
40
|
-
post_process,
|
|
41
|
-
**space_drawing_kwargs,
|
|
42
|
-
)
|
|
43
|
-
elif backend == "altair":
|
|
44
|
-
return make_altair_space(
|
|
45
|
-
agent_portrayal,
|
|
46
|
-
propertylayer_portrayal,
|
|
47
|
-
post_process,
|
|
48
|
-
**space_drawing_kwargs,
|
|
49
|
-
)
|
|
50
|
-
else:
|
|
51
|
-
raise ValueError(
|
|
52
|
-
f"unknown backend {backend}, must be one of matplotlib, altair"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def make_plot_component(
|
|
57
|
-
measure: str | dict[str, str] | list[str] | tuple[str],
|
|
58
|
-
post_process: Callable | None = None,
|
|
59
|
-
backend: str = "matplotlib",
|
|
60
|
-
**plot_drawing_kwargs,
|
|
61
|
-
):
|
|
62
|
-
"""Create a plotting function for a specified measure using the specified backend.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
66
|
-
post_process: a user-specified callable to do post-processing called with the Axes instance.
|
|
67
|
-
backend: the backend to use {"matplotlib", "altair"}
|
|
68
|
-
plot_drawing_kwargs: additional keyword arguments to pass onto the backend specific function for making a plotting component
|
|
69
|
-
|
|
70
|
-
Notes:
|
|
71
|
-
altair plotting backend is not yet implemented and planned for mesa 3.1.
|
|
72
|
-
|
|
73
|
-
Returns:
|
|
74
|
-
function: A function that creates a plot component
|
|
75
|
-
"""
|
|
76
|
-
if backend == "matplotlib":
|
|
77
|
-
return make_mpl_plot_component(measure, post_process, **plot_drawing_kwargs)
|
|
78
|
-
elif backend == "altair":
|
|
79
|
-
raise NotImplementedError("altair line plots are not yet implemented")
|
|
80
|
-
else:
|
|
81
|
-
raise ValueError(
|
|
82
|
-
f"unknown backend {backend}, must be one of matplotlib, altair"
|
|
83
|
-
)
|