Mesa 3.1.2__py3-none-any.whl → 3.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mesa/__init__.py +1 -1
- mesa/datacollection.py +62 -2
- mesa/examples/advanced/sugarscape_g1mt/app.py +15 -37
- mesa/examples/basic/boid_flockers/agents.py +26 -38
- mesa/examples/basic/boid_flockers/app.py +6 -1
- mesa/examples/basic/boid_flockers/model.py +30 -37
- mesa/experimental/__init__.py +2 -2
- mesa/experimental/cell_space/grid.py +8 -8
- mesa/experimental/cell_space/voronoi.py +1 -4
- mesa/experimental/continuous_space/__init__.py +8 -0
- mesa/experimental/continuous_space/continuous_space.py +273 -0
- mesa/experimental/continuous_space/continuous_space_agents.py +101 -0
- mesa/model.py +1 -1
- mesa/space.py +7 -0
- mesa/visualization/__init__.py +1 -2
- mesa/visualization/components/altair_components.py +10 -8
- mesa/visualization/mpl_space_drawing.py +160 -101
- mesa/visualization/solara_viz.py +35 -8
- {mesa-3.1.2.dist-info → mesa-3.1.4.dist-info}/METADATA +14 -10
- {mesa-3.1.2.dist-info → mesa-3.1.4.dist-info}/RECORD +23 -21
- mesa-3.1.2.dist-info/entry_points.txt +0 -2
- {mesa-3.1.2.dist-info → mesa-3.1.4.dist-info}/WHEEL +0 -0
- {mesa-3.1.2.dist-info → mesa-3.1.4.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.2.dist-info → mesa-3.1.4.dist-info}/licenses/NOTICE +0 -0
|
@@ -8,9 +8,10 @@ for a paper.
|
|
|
8
8
|
|
|
9
9
|
import contextlib
|
|
10
10
|
import itertools
|
|
11
|
-
import math
|
|
12
11
|
import warnings
|
|
13
12
|
from collections.abc import Callable
|
|
13
|
+
from functools import lru_cache
|
|
14
|
+
from itertools import pairwise
|
|
14
15
|
from typing import Any
|
|
15
16
|
|
|
16
17
|
import networkx as nx
|
|
@@ -18,9 +19,9 @@ import numpy as np
|
|
|
18
19
|
from matplotlib import pyplot as plt
|
|
19
20
|
from matplotlib.axes import Axes
|
|
20
21
|
from matplotlib.cm import ScalarMappable
|
|
21
|
-
from matplotlib.collections import PatchCollection
|
|
22
|
+
from matplotlib.collections import LineCollection, PatchCollection, PolyCollection
|
|
22
23
|
from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
|
|
23
|
-
from matplotlib.patches import
|
|
24
|
+
from matplotlib.patches import Polygon
|
|
24
25
|
|
|
25
26
|
import mesa
|
|
26
27
|
from mesa.experimental.cell_space import (
|
|
@@ -143,7 +144,10 @@ def draw_space(
|
|
|
143
144
|
draw_orthogonal_grid(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
144
145
|
case mesa.space.NetworkGrid() | mesa.experimental.cell_space.Network():
|
|
145
146
|
draw_network(space, agent_portrayal, ax=ax, **space_drawing_kwargs)
|
|
146
|
-
case
|
|
147
|
+
case (
|
|
148
|
+
mesa.space.ContinuousSpace()
|
|
149
|
+
| mesa.experimental.continuous_space.ContinuousSpace()
|
|
150
|
+
):
|
|
147
151
|
draw_continuous_space(space, agent_portrayal, ax=ax)
|
|
148
152
|
case VoronoiGrid():
|
|
149
153
|
draw_voronoi_grid(space, agent_portrayal, ax=ax)
|
|
@@ -156,6 +160,40 @@ def draw_space(
|
|
|
156
160
|
return ax
|
|
157
161
|
|
|
158
162
|
|
|
163
|
+
@lru_cache(maxsize=1024, typed=True)
|
|
164
|
+
def _get_hexmesh(
|
|
165
|
+
width: int, height: int, size: float = 1.0
|
|
166
|
+
) -> list[tuple[float, float]]:
|
|
167
|
+
"""Generate hexagon vertices for the mesh. Yields list of vertex coordinates for each hexagon."""
|
|
168
|
+
|
|
169
|
+
# Helper function for getting the vertices of a hexagon given the center and size
|
|
170
|
+
def _get_hex_vertices(
|
|
171
|
+
center_x: float, center_y: float, size: float = 1.0
|
|
172
|
+
) -> list[tuple[float, float]]:
|
|
173
|
+
"""Get vertices for a hexagon centered at (center_x, center_y)."""
|
|
174
|
+
vertices = [
|
|
175
|
+
(center_x, center_y + size), # top
|
|
176
|
+
(center_x + size * np.sqrt(3) / 2, center_y + size / 2), # top right
|
|
177
|
+
(center_x + size * np.sqrt(3) / 2, center_y - size / 2), # bottom right
|
|
178
|
+
(center_x, center_y - size), # bottom
|
|
179
|
+
(center_x - size * np.sqrt(3) / 2, center_y - size / 2), # bottom left
|
|
180
|
+
(center_x - size * np.sqrt(3) / 2, center_y + size / 2), # top left
|
|
181
|
+
]
|
|
182
|
+
return vertices
|
|
183
|
+
|
|
184
|
+
x_spacing = np.sqrt(3) * size
|
|
185
|
+
y_spacing = 1.5 * size
|
|
186
|
+
hexagons = []
|
|
187
|
+
|
|
188
|
+
for row, col in itertools.product(range(height), range(width)):
|
|
189
|
+
# Calculate center position with offset for even rows
|
|
190
|
+
x = col * x_spacing + (row % 2 == 0) * (x_spacing / 2)
|
|
191
|
+
y = row * y_spacing
|
|
192
|
+
hexagons.append(_get_hex_vertices(x, y, size))
|
|
193
|
+
|
|
194
|
+
return hexagons
|
|
195
|
+
|
|
196
|
+
|
|
159
197
|
def draw_property_layers(
|
|
160
198
|
space, propertylayer_portrayal: dict[str, dict[str, Any]], ax: Axes
|
|
161
199
|
):
|
|
@@ -188,11 +226,10 @@ def draw_property_layers(
|
|
|
188
226
|
continue
|
|
189
227
|
|
|
190
228
|
data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
|
|
191
|
-
width, height = data.shape # if space is None else (space.width, space.height)
|
|
192
229
|
|
|
193
|
-
if space
|
|
230
|
+
if (space.width, space.height) != data.shape:
|
|
194
231
|
warnings.warn(
|
|
195
|
-
f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
|
|
232
|
+
f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({space.width}, {space.height}).",
|
|
196
233
|
UserWarning,
|
|
197
234
|
stacklevel=2,
|
|
198
235
|
)
|
|
@@ -203,45 +240,75 @@ def draw_property_layers(
|
|
|
203
240
|
vmax = portrayal.get("vmax", np.max(data))
|
|
204
241
|
colorbar = portrayal.get("colorbar", True)
|
|
205
242
|
|
|
206
|
-
#
|
|
243
|
+
# Prepare colormap
|
|
207
244
|
if "color" in portrayal:
|
|
208
245
|
rgba_color = to_rgba(portrayal["color"])
|
|
209
|
-
normalized_data = (data - vmin) / (vmax - vmin)
|
|
210
|
-
rgba_data = np.full((*data.shape, 4), rgba_color)
|
|
211
|
-
rgba_data[..., 3] *= normalized_data * alpha
|
|
212
|
-
rgba_data = np.clip(rgba_data, 0, 1)
|
|
213
246
|
cmap = LinearSegmentedColormap.from_list(
|
|
214
247
|
layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
|
|
215
248
|
)
|
|
216
|
-
im = ax.imshow(
|
|
217
|
-
rgba_data,
|
|
218
|
-
origin="lower",
|
|
219
|
-
)
|
|
220
|
-
if colorbar:
|
|
221
|
-
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
222
|
-
sm = ScalarMappable(norm=norm, cmap=cmap)
|
|
223
|
-
sm.set_array([])
|
|
224
|
-
ax.figure.colorbar(sm, ax=ax, orientation="vertical")
|
|
225
|
-
|
|
226
249
|
elif "colormap" in portrayal:
|
|
227
250
|
cmap = portrayal.get("colormap", "viridis")
|
|
228
251
|
if isinstance(cmap, list):
|
|
229
252
|
cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
cmap=cmap,
|
|
233
|
-
alpha=alpha,
|
|
234
|
-
vmin=vmin,
|
|
235
|
-
vmax=vmax,
|
|
236
|
-
origin="lower",
|
|
237
|
-
)
|
|
238
|
-
if colorbar:
|
|
239
|
-
plt.colorbar(im, ax=ax, label=layer_name)
|
|
253
|
+
elif isinstance(cmap, str):
|
|
254
|
+
cmap = plt.get_cmap(cmap)
|
|
240
255
|
else:
|
|
241
256
|
raise ValueError(
|
|
242
257
|
f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
|
|
243
258
|
)
|
|
244
259
|
|
|
260
|
+
if isinstance(space, OrthogonalGrid):
|
|
261
|
+
if "color" in portrayal:
|
|
262
|
+
data = data.T
|
|
263
|
+
normalized_data = (data - vmin) / (vmax - vmin)
|
|
264
|
+
rgba_data = np.full((*data.shape, 4), rgba_color)
|
|
265
|
+
rgba_data[..., 3] *= normalized_data * alpha
|
|
266
|
+
rgba_data = np.clip(rgba_data, 0, 1)
|
|
267
|
+
ax.imshow(rgba_data, origin="lower")
|
|
268
|
+
else:
|
|
269
|
+
ax.imshow(
|
|
270
|
+
data.T,
|
|
271
|
+
cmap=cmap,
|
|
272
|
+
alpha=alpha,
|
|
273
|
+
vmin=vmin,
|
|
274
|
+
vmax=vmax,
|
|
275
|
+
origin="lower",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
elif isinstance(space, HexGrid):
|
|
279
|
+
width, height = data.shape
|
|
280
|
+
|
|
281
|
+
# Generate hexagon mesh
|
|
282
|
+
hexagons = _get_hexmesh(width, height)
|
|
283
|
+
|
|
284
|
+
# Normalize colors
|
|
285
|
+
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
286
|
+
colors = data.ravel() # flatten data to 1D array
|
|
287
|
+
|
|
288
|
+
if "color" in portrayal:
|
|
289
|
+
normalized_colors = np.clip(norm(colors), 0, 1)
|
|
290
|
+
rgba_colors = np.full((len(colors), 4), rgba_color)
|
|
291
|
+
rgba_colors[:, 3] = normalized_colors * alpha
|
|
292
|
+
else:
|
|
293
|
+
rgba_colors = cmap(norm(colors))
|
|
294
|
+
rgba_colors[..., 3] *= alpha
|
|
295
|
+
|
|
296
|
+
# Draw hexagons
|
|
297
|
+
collection = PolyCollection(hexagons, facecolors=rgba_colors, zorder=-1)
|
|
298
|
+
ax.add_collection(collection)
|
|
299
|
+
|
|
300
|
+
else:
|
|
301
|
+
raise NotImplementedError(
|
|
302
|
+
f"PropertyLayer visualization not implemented for {type(space)}."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
# Add colorbar if requested
|
|
306
|
+
if colorbar:
|
|
307
|
+
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
308
|
+
sm = ScalarMappable(norm=norm, cmap=cmap)
|
|
309
|
+
sm.set_array([])
|
|
310
|
+
plt.colorbar(sm, ax=ax, label=layer_name)
|
|
311
|
+
|
|
245
312
|
|
|
246
313
|
def draw_orthogonal_grid(
|
|
247
314
|
space: OrthogonalGrid,
|
|
@@ -305,13 +372,6 @@ def draw_hex_grid(
|
|
|
305
372
|
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
306
373
|
draw_grid: whether to draw the grid
|
|
307
374
|
kwargs: additional keyword arguments passed to ax.scatter
|
|
308
|
-
|
|
309
|
-
Returns:
|
|
310
|
-
Returns the Axes object with the plot drawn onto it.
|
|
311
|
-
|
|
312
|
-
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
313
|
-
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
314
|
-
|
|
315
375
|
"""
|
|
316
376
|
if ax is None:
|
|
317
377
|
fig, ax = plt.subplots()
|
|
@@ -320,62 +380,54 @@ def draw_hex_grid(
|
|
|
320
380
|
s_default = (180 / max(space.width, space.height)) ** 2
|
|
321
381
|
arguments = collect_agent_data(space, agent_portrayal, size=s_default)
|
|
322
382
|
|
|
323
|
-
# for
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
# give all rows an offset in the y direction
|
|
328
|
-
|
|
329
|
-
# numbers here are based on a distance of 1 between centers of hexes
|
|
330
|
-
offset = math.sqrt(0.75)
|
|
383
|
+
# Parameters for hexagon grid
|
|
384
|
+
size = 1.0
|
|
385
|
+
x_spacing = np.sqrt(3) * size
|
|
386
|
+
y_spacing = 1.5 * size
|
|
331
387
|
|
|
332
388
|
loc = arguments["loc"].astype(float)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
#
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
389
|
+
# Calculate hexagon centers for agents if agents are present and plot them.
|
|
390
|
+
if loc.size > 0:
|
|
391
|
+
loc[:, 0] = loc[:, 0] * x_spacing + ((loc[:, 1] - 1) % 2) * (x_spacing / 2)
|
|
392
|
+
loc[:, 1] = loc[:, 1] * y_spacing
|
|
393
|
+
arguments["loc"] = loc
|
|
394
|
+
|
|
395
|
+
# plot the agents
|
|
396
|
+
_scatter(ax, arguments, **kwargs)
|
|
397
|
+
|
|
398
|
+
# Calculate proper bounds that account for the full hexagon width and height
|
|
399
|
+
x_max = space.width * x_spacing + (space.height % 2) * (x_spacing / 2)
|
|
400
|
+
y_max = space.height * y_spacing
|
|
401
|
+
|
|
402
|
+
# Add padding that accounts for the hexagon points
|
|
403
|
+
x_padding = (
|
|
404
|
+
size * np.sqrt(3) / 2
|
|
405
|
+
) # Distance from center to rightmost point of hexagon
|
|
406
|
+
y_padding = size # Distance from center to topmost point of hexagon
|
|
407
|
+
|
|
408
|
+
# Plot limits to perfectly contain the hexagonal grid
|
|
409
|
+
# Determined through physical testing.
|
|
410
|
+
ax.set_xlim(-2 * x_padding, x_max + x_padding)
|
|
411
|
+
ax.set_ylim(-2 * y_padding, y_max + y_padding)
|
|
412
|
+
|
|
413
|
+
def setup_hexmesh(width, height):
|
|
414
|
+
"""Helper function for creating the hexmesh with unique edges."""
|
|
415
|
+
edges = set()
|
|
416
|
+
|
|
417
|
+
# Generate edges for each hexagon
|
|
418
|
+
hexagons = _get_hexmesh(width, height)
|
|
419
|
+
for vertices in hexagons:
|
|
420
|
+
# Edge logic, connecting each vertex to the next
|
|
421
|
+
for v1, v2 in pairwise([*vertices, vertices[0]]):
|
|
422
|
+
# Sort vertices to ensure consistent edge representation and avoid duplicates.
|
|
423
|
+
edge = tuple(sorted([tuple(np.round(v1, 6)), tuple(np.round(v2, 6))]))
|
|
424
|
+
edges.add(edge)
|
|
425
|
+
|
|
426
|
+
return LineCollection(edges, linestyle=":", color="black", linewidth=1, alpha=1)
|
|
370
427
|
|
|
371
428
|
if draw_grid:
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
setup_hexmesh(
|
|
375
|
-
space.width,
|
|
376
|
-
space.height,
|
|
377
|
-
)
|
|
378
|
-
)
|
|
429
|
+
ax.add_collection(setup_hexmesh(space.width, space.height))
|
|
430
|
+
|
|
379
431
|
return ax
|
|
380
432
|
|
|
381
433
|
|
|
@@ -498,7 +550,11 @@ def draw_continuous_space(
|
|
|
498
550
|
|
|
499
551
|
|
|
500
552
|
def draw_voronoi_grid(
|
|
501
|
-
space: VoronoiGrid,
|
|
553
|
+
space: VoronoiGrid,
|
|
554
|
+
agent_portrayal: Callable,
|
|
555
|
+
ax: Axes | None = None,
|
|
556
|
+
draw_grid: bool = True,
|
|
557
|
+
**kwargs,
|
|
502
558
|
):
|
|
503
559
|
"""Visualize a voronoi grid.
|
|
504
560
|
|
|
@@ -506,6 +562,7 @@ def draw_voronoi_grid(
|
|
|
506
562
|
space: the space to visualize
|
|
507
563
|
agent_portrayal: a callable that is called with the agent and returns a dict
|
|
508
564
|
ax: a Matplotlib Axes instance. If none is provided a new figure and ax will be created using plt.subplots
|
|
565
|
+
draw_grid: whether to draw the grid or not
|
|
509
566
|
kwargs: additional keyword arguments passed to ax.scatter
|
|
510
567
|
|
|
511
568
|
Returns:
|
|
@@ -538,16 +595,18 @@ def draw_voronoi_grid(
|
|
|
538
595
|
|
|
539
596
|
_scatter(ax, arguments, **kwargs)
|
|
540
597
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
)
|
|
549
|
-
|
|
598
|
+
def setup_voroinoimesh(cells):
|
|
599
|
+
patches = []
|
|
600
|
+
for cell in cells:
|
|
601
|
+
patch = Polygon(cell.properties["polygon"])
|
|
602
|
+
patches.append(patch)
|
|
603
|
+
mesh = PatchCollection(
|
|
604
|
+
patches, edgecolor="k", facecolor=(1, 1, 1, 0), linestyle="dotted", lw=1
|
|
605
|
+
)
|
|
606
|
+
return mesh
|
|
550
607
|
|
|
608
|
+
if draw_grid:
|
|
609
|
+
ax.add_collection(setup_voroinoimesh(space.all_cells.cells))
|
|
551
610
|
return ax
|
|
552
611
|
|
|
553
612
|
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -52,6 +52,7 @@ def SolaraViz(
|
|
|
52
52
|
| Literal["default"] = "default",
|
|
53
53
|
*,
|
|
54
54
|
play_interval: int = 100,
|
|
55
|
+
render_interval: int = 1,
|
|
55
56
|
simulator: Simulator | None = None,
|
|
56
57
|
model_params=None,
|
|
57
58
|
name: str | None = None,
|
|
@@ -72,6 +73,8 @@ def SolaraViz(
|
|
|
72
73
|
Defaults to "default", which uses the default Altair space visualization.
|
|
73
74
|
play_interval (int, optional): Interval for playing the model steps in milliseconds.
|
|
74
75
|
This controls the speed of the model's automatic stepping. Defaults to 100 ms.
|
|
76
|
+
render_interval (int, optional): Controls how often plots are updated during a simulation,
|
|
77
|
+
allowing users to skip intermediate steps and update graphs less frequently.
|
|
75
78
|
simulator: A simulator that controls the model (optional)
|
|
76
79
|
model_params (dict, optional): Parameters for (re-)instantiating a model.
|
|
77
80
|
Can include user-adjustable parameters and fixed parameters. Defaults to None.
|
|
@@ -90,9 +93,15 @@ def SolaraViz(
|
|
|
90
93
|
model instance is provided, it will be converted to a reactive model using `solara.use_reactive`.
|
|
91
94
|
- The `play_interval` argument controls the speed of the model's automatic stepping. A lower
|
|
92
95
|
value results in faster stepping, while a higher value results in slower stepping.
|
|
96
|
+
- The `render_interval` argument determines how often plots are updated during simulation. Higher values
|
|
97
|
+
reduce update frequency,resulting in faster execution.
|
|
93
98
|
"""
|
|
94
99
|
if components == "default":
|
|
95
|
-
components = [
|
|
100
|
+
components = [
|
|
101
|
+
components_altair.make_altair_space(
|
|
102
|
+
agent_portrayal=None, propertylayer_portrayal=None, post_process=None
|
|
103
|
+
)
|
|
104
|
+
]
|
|
96
105
|
if model_params is None:
|
|
97
106
|
model_params = {}
|
|
98
107
|
|
|
@@ -103,7 +112,7 @@ def SolaraViz(
|
|
|
103
112
|
# set up reactive model_parameters shared by ModelCreator and ModelController
|
|
104
113
|
reactive_model_parameters = solara.use_reactive({})
|
|
105
114
|
reactive_play_interval = solara.use_reactive(play_interval)
|
|
106
|
-
|
|
115
|
+
reactive_render_interval = solara.use_reactive(render_interval)
|
|
107
116
|
with solara.AppBar():
|
|
108
117
|
solara.AppBarTitle(name if name else model.value.__class__.__name__)
|
|
109
118
|
|
|
@@ -117,11 +126,20 @@ def SolaraViz(
|
|
|
117
126
|
max=500,
|
|
118
127
|
step=10,
|
|
119
128
|
)
|
|
129
|
+
solara.SliderInt(
|
|
130
|
+
label="Render Interval (steps)",
|
|
131
|
+
value=reactive_render_interval,
|
|
132
|
+
on_value=lambda v: reactive_render_interval.set(v),
|
|
133
|
+
min=1,
|
|
134
|
+
max=100,
|
|
135
|
+
step=2,
|
|
136
|
+
)
|
|
120
137
|
if not isinstance(simulator, Simulator):
|
|
121
138
|
ModelController(
|
|
122
139
|
model,
|
|
123
140
|
model_parameters=reactive_model_parameters,
|
|
124
141
|
play_interval=reactive_play_interval,
|
|
142
|
+
render_interval=reactive_render_interval,
|
|
125
143
|
)
|
|
126
144
|
else:
|
|
127
145
|
SimulatorController(
|
|
@@ -129,6 +147,7 @@ def SolaraViz(
|
|
|
129
147
|
simulator,
|
|
130
148
|
model_parameters=reactive_model_parameters,
|
|
131
149
|
play_interval=reactive_play_interval,
|
|
150
|
+
render_interval=reactive_render_interval,
|
|
132
151
|
)
|
|
133
152
|
with solara.Card("Model Parameters"):
|
|
134
153
|
ModelCreator(
|
|
@@ -189,6 +208,7 @@ def ModelController(
|
|
|
189
208
|
*,
|
|
190
209
|
model_parameters: dict | solara.Reactive[dict] = None,
|
|
191
210
|
play_interval: int | solara.Reactive[int] = 100,
|
|
211
|
+
render_interval: int | solara.Reactive[int] = 1,
|
|
192
212
|
):
|
|
193
213
|
"""Create controls for model execution (step, play, pause, reset).
|
|
194
214
|
|
|
@@ -196,7 +216,7 @@ def ModelController(
|
|
|
196
216
|
model: Reactive model instance
|
|
197
217
|
model_parameters: Reactive parameters for (re-)instantiating a model.
|
|
198
218
|
play_interval: Interval for playing the model steps in milliseconds.
|
|
199
|
-
|
|
219
|
+
render_interval: Controls how often the plots are updated during simulation steps.Higher value reduce update frequency.
|
|
200
220
|
"""
|
|
201
221
|
playing = solara.use_reactive(False)
|
|
202
222
|
running = solara.use_reactive(True)
|
|
@@ -215,9 +235,12 @@ def ModelController(
|
|
|
215
235
|
|
|
216
236
|
@function_logger(__name__)
|
|
217
237
|
def do_step():
|
|
218
|
-
"""Advance the model by
|
|
219
|
-
|
|
238
|
+
"""Advance the model by the number of steps specified by the render_interval slider."""
|
|
239
|
+
for _ in range(render_interval.value):
|
|
240
|
+
model.value.step()
|
|
241
|
+
|
|
220
242
|
running.value = model.value.running
|
|
243
|
+
|
|
221
244
|
force_update()
|
|
222
245
|
|
|
223
246
|
@function_logger(__name__)
|
|
@@ -259,6 +282,7 @@ def SimulatorController(
|
|
|
259
282
|
*,
|
|
260
283
|
model_parameters: dict | solara.Reactive[dict] = None,
|
|
261
284
|
play_interval: int | solara.Reactive[int] = 100,
|
|
285
|
+
render_interval: int | solara.Reactive[int] = 1,
|
|
262
286
|
):
|
|
263
287
|
"""Create controls for model execution (step, play, pause, reset).
|
|
264
288
|
|
|
@@ -267,7 +291,11 @@ def SimulatorController(
|
|
|
267
291
|
simulator: Simulator instance
|
|
268
292
|
model_parameters: Reactive parameters for (re-)instantiating a model.
|
|
269
293
|
play_interval: Interval for playing the model steps in milliseconds.
|
|
294
|
+
render_interval: Controls how often the plots are updated during simulation steps.Higher values reduce update frequency.
|
|
270
295
|
|
|
296
|
+
Notes:
|
|
297
|
+
The `step button` increments the step by the value specified in the `render_interval` slider.
|
|
298
|
+
This behavior ensures synchronization between simulation steps and plot updates.
|
|
271
299
|
"""
|
|
272
300
|
playing = solara.use_reactive(False)
|
|
273
301
|
running = solara.use_reactive(True)
|
|
@@ -285,8 +313,8 @@ def SimulatorController(
|
|
|
285
313
|
)
|
|
286
314
|
|
|
287
315
|
def do_step():
|
|
288
|
-
"""Advance the model by
|
|
289
|
-
simulator.run_for(
|
|
316
|
+
"""Advance the model by the number of steps specified by the render_interval slider."""
|
|
317
|
+
simulator.run_for(render_interval.value)
|
|
290
318
|
running.value = model.value.running
|
|
291
319
|
force_update()
|
|
292
320
|
|
|
@@ -390,7 +418,6 @@ def ModelCreator(
|
|
|
390
418
|
or are dictionaries containing parameter details such as type, value, min, and max.
|
|
391
419
|
- The `seed` argument ensures reproducibility by setting the initial seed for the model's random number generator.
|
|
392
420
|
- The component provides an interface for adjusting user-defined parameters and reseeding the model.
|
|
393
|
-
|
|
394
421
|
"""
|
|
395
422
|
if model_parameters is None:
|
|
396
423
|
model_parameters = {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.4
|
|
4
4
|
Summary: Agent-based modeling (ABM) in Python
|
|
5
5
|
Project-URL: homepage, https://github.com/projectmesa/mesa
|
|
6
6
|
Project-URL: repository, https://github.com/projectmesa/mesa
|
|
@@ -24,8 +24,10 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Life
|
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
25
|
Requires-Dist: numpy
|
|
26
26
|
Requires-Dist: pandas
|
|
27
|
+
Requires-Dist: scipy
|
|
27
28
|
Requires-Dist: tqdm
|
|
28
29
|
Provides-Extra: all
|
|
30
|
+
Requires-Dist: altair; extra == 'all'
|
|
29
31
|
Requires-Dist: ipython; extra == 'all'
|
|
30
32
|
Requires-Dist: matplotlib; extra == 'all'
|
|
31
33
|
Requires-Dist: myst-nb; extra == 'all'
|
|
@@ -40,7 +42,9 @@ Requires-Dist: scipy; extra == 'all'
|
|
|
40
42
|
Requires-Dist: seaborn; extra == 'all'
|
|
41
43
|
Requires-Dist: solara; extra == 'all'
|
|
42
44
|
Requires-Dist: sphinx; extra == 'all'
|
|
45
|
+
Requires-Dist: sphinx-copybutton; extra == 'all'
|
|
43
46
|
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: altair; extra == 'dev'
|
|
44
48
|
Requires-Dist: matplotlib; extra == 'dev'
|
|
45
49
|
Requires-Dist: networkx; extra == 'dev'
|
|
46
50
|
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -50,6 +54,7 @@ Requires-Dist: ruff; extra == 'dev'
|
|
|
50
54
|
Requires-Dist: solara; extra == 'dev'
|
|
51
55
|
Requires-Dist: sphinx; extra == 'dev'
|
|
52
56
|
Provides-Extra: docs
|
|
57
|
+
Requires-Dist: altair; extra == 'docs'
|
|
53
58
|
Requires-Dist: ipython; extra == 'docs'
|
|
54
59
|
Requires-Dist: matplotlib; extra == 'docs'
|
|
55
60
|
Requires-Dist: myst-nb; extra == 'docs'
|
|
@@ -59,7 +64,9 @@ Requires-Dist: pydata-sphinx-theme; extra == 'docs'
|
|
|
59
64
|
Requires-Dist: seaborn; extra == 'docs'
|
|
60
65
|
Requires-Dist: solara; extra == 'docs'
|
|
61
66
|
Requires-Dist: sphinx; extra == 'docs'
|
|
67
|
+
Requires-Dist: sphinx-copybutton; extra == 'docs'
|
|
62
68
|
Provides-Extra: examples
|
|
69
|
+
Requires-Dist: altair; extra == 'examples'
|
|
63
70
|
Requires-Dist: matplotlib; extra == 'examples'
|
|
64
71
|
Requires-Dist: networkx; extra == 'examples'
|
|
65
72
|
Requires-Dist: pytest; extra == 'examples'
|
|
@@ -68,10 +75,12 @@ Requires-Dist: solara; extra == 'examples'
|
|
|
68
75
|
Provides-Extra: network
|
|
69
76
|
Requires-Dist: networkx; extra == 'network'
|
|
70
77
|
Provides-Extra: rec
|
|
78
|
+
Requires-Dist: altair; extra == 'rec'
|
|
71
79
|
Requires-Dist: matplotlib; extra == 'rec'
|
|
72
80
|
Requires-Dist: networkx; extra == 'rec'
|
|
73
81
|
Requires-Dist: solara; extra == 'rec'
|
|
74
82
|
Provides-Extra: viz
|
|
83
|
+
Requires-Dist: altair; extra == 'viz'
|
|
75
84
|
Requires-Dist: matplotlib; extra == 'viz'
|
|
76
85
|
Requires-Dist: solara; extra == 'viz'
|
|
77
86
|
Description-Content-Type: text/markdown
|
|
@@ -106,27 +115,22 @@ can be displayed in browser windows or Jupyter.*
|
|
|
106
115
|
|
|
107
116
|
## Using Mesa
|
|
108
117
|
|
|
109
|
-
To install our latest stable release
|
|
118
|
+
To install our latest stable release, run:
|
|
110
119
|
|
|
111
120
|
``` bash
|
|
112
121
|
pip install -U mesa
|
|
113
122
|
```
|
|
114
123
|
|
|
115
|
-
To install our latest pre-release, run:
|
|
116
|
-
|
|
117
|
-
``` bash
|
|
118
|
-
pip install -U --pre mesa
|
|
119
|
-
```
|
|
120
124
|
Starting with Mesa 3.0, we don't install all our dependencies anymore by default.
|
|
121
125
|
```bash
|
|
122
126
|
# You can customize the additional dependencies you need, if you want. Available are:
|
|
123
|
-
pip install -U
|
|
127
|
+
pip install -U mesa[network,viz]
|
|
124
128
|
|
|
125
129
|
# This is equivalent to our recommended dependencies:
|
|
126
|
-
pip install -U
|
|
130
|
+
pip install -U mesa[rec]
|
|
127
131
|
|
|
128
132
|
# To install all, including developer, dependencies:
|
|
129
|
-
pip install -U
|
|
133
|
+
pip install -U mesa[all]
|
|
130
134
|
```
|
|
131
135
|
|
|
132
136
|
You can also use `pip` to install the latest GitHub version:
|