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.
@@ -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 RegularPolygon
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 mesa.space.ContinuousSpace():
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 and data.shape != (width, height):
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
- # Draw the layer
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
- im = ax.imshow(
231
- data,
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 hexgrids we have to go from logical coordinates to visual coordinates
324
- # this is a bit messy.
325
-
326
- # give all even rows an offset in the x direction
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
- logical = np.mod(loc[:, 1], 2) == 0
335
- loc[:, 0][logical] += 0.5
336
- loc[:, 1] *= offset
337
- arguments["loc"] = loc
338
-
339
- # plot the agents
340
- _scatter(ax, arguments, **kwargs)
341
-
342
- # further styling and adding of grid
343
- ax.set_xlim(-1, space.width + 0.5)
344
- ax.set_ylim(-offset, space.height * offset)
345
-
346
- def setup_hexmesh(
347
- width,
348
- height,
349
- ):
350
- """Helper function for creating the hexmaesh."""
351
- # fixme: this should be done once, rather than in each update
352
- # fixme check coordinate system in hexgrid (see https://www.redblobgames.com/grids/hexagons/#coordinates-offset)
353
-
354
- patches = []
355
- for x, y in itertools.product(range(width), range(height)):
356
- if y % 2 == 0:
357
- x += 0.5 # noqa: PLW2901
358
- y *= offset # noqa: PLW2901
359
- hex = RegularPolygon(
360
- (x, y),
361
- numVertices=6,
362
- radius=math.sqrt(1 / 3),
363
- orientation=np.radians(120),
364
- )
365
- patches.append(hex)
366
- mesh = PatchCollection(
367
- patches, edgecolor="k", facecolor=(1, 1, 1, 0), linestyle="dotted", lw=1
368
- )
369
- return mesh
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
- # add grid
373
- ax.add_collection(
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, agent_portrayal: Callable, ax: Axes | None = None, **kwargs
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
- for cell in space.all_cells:
542
- polygon = cell.properties["polygon"]
543
- ax.fill(
544
- *zip(*polygon),
545
- alpha=min(1, cell.properties[space.cell_coloring_property]),
546
- c="red",
547
- zorder=0,
548
- ) # Plot filled polygon
549
- ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black
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
 
@@ -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 = [components_altair.make_altair_space()]
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 one step."""
219
- model.value.step()
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 one step."""
289
- simulator.run_for(1)
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.2
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 (3.0.x), run:
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 --pre mesa[network,viz]
127
+ pip install -U mesa[network,viz]
124
128
 
125
129
  # This is equivalent to our recommended dependencies:
126
- pip install -U --pre mesa[rec]
130
+ pip install -U mesa[rec]
127
131
 
128
132
  # To install all, including developer, dependencies:
129
- pip install -U --pre mesa[all]
133
+ pip install -U mesa[all]
130
134
  ```
131
135
 
132
136
  You can also use `pip` to install the latest GitHub version: