Mesa 3.0.0a5__py3-none-any.whl → 3.0.0b1__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 (91) hide show
  1. examples/README.md +37 -0
  2. examples/__init__.py +0 -0
  3. examples/advanced/__init__.py +0 -0
  4. examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  5. examples/advanced/epstein_civil_violence/Readme.md +33 -0
  6. examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py +0 -0
  7. examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +158 -0
  8. examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +146 -0
  9. examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py +33 -0
  10. examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +81 -0
  11. examples/advanced/epstein_civil_violence/requirements.txt +3 -0
  12. examples/advanced/epstein_civil_violence/run.py +3 -0
  13. examples/advanced/pd_grid/analysis.ipynb +228 -0
  14. examples/advanced/pd_grid/pd_grid/__init__.py +0 -0
  15. examples/advanced/pd_grid/pd_grid/agent.py +50 -0
  16. examples/advanced/pd_grid/pd_grid/model.py +72 -0
  17. examples/advanced/pd_grid/pd_grid/portrayal.py +19 -0
  18. examples/advanced/pd_grid/pd_grid/server.py +21 -0
  19. examples/advanced/pd_grid/readme.md +42 -0
  20. examples/advanced/pd_grid/requirements.txt +3 -0
  21. examples/advanced/pd_grid/run.py +3 -0
  22. examples/advanced/sugarscape_g1mt/Readme.md +87 -0
  23. examples/advanced/sugarscape_g1mt/app.py +61 -0
  24. examples/advanced/sugarscape_g1mt/requirements.txt +6 -0
  25. examples/advanced/sugarscape_g1mt/run.py +105 -0
  26. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py +0 -0
  27. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +180 -0
  28. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +26 -0
  29. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +61 -0
  30. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt +50 -0
  31. examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +321 -0
  32. examples/advanced/sugarscape_g1mt/tests.py +72 -0
  33. examples/advanced/wolf_sheep/Readme.md +57 -0
  34. examples/advanced/wolf_sheep/__init__.py +0 -0
  35. examples/advanced/wolf_sheep/requirements.txt +1 -0
  36. examples/advanced/wolf_sheep/run.py +3 -0
  37. examples/advanced/wolf_sheep/wolf_sheep/__init__.py +0 -0
  38. examples/advanced/wolf_sheep/wolf_sheep/agents.py +102 -0
  39. examples/advanced/wolf_sheep/wolf_sheep/model.py +136 -0
  40. examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png +0 -0
  41. examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png +0 -0
  42. examples/advanced/wolf_sheep/wolf_sheep/server.py +78 -0
  43. examples/basic/__init__.py +13 -0
  44. examples/basic/boid_flockers/Readme.md +43 -0
  45. examples/basic/boid_flockers/agents.py +71 -0
  46. examples/basic/boid_flockers/app.py +59 -0
  47. examples/basic/boid_flockers/model.py +70 -0
  48. examples/basic/boltzmann_wealth_model/Readme.md +60 -0
  49. examples/basic/boltzmann_wealth_model/agents.py +31 -0
  50. examples/basic/boltzmann_wealth_model/app.py +66 -0
  51. examples/basic/boltzmann_wealth_model/model.py +44 -0
  52. examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  53. examples/basic/conways_game_of_life/Readme.md +35 -0
  54. examples/basic/conways_game_of_life/agents.py +47 -0
  55. examples/basic/conways_game_of_life/model.py +32 -0
  56. examples/basic/conways_game_of_life/portrayal.py +18 -0
  57. examples/basic/conways_game_of_life/requirements.txt +1 -0
  58. examples/basic/conways_game_of_life/server.py +11 -0
  59. examples/basic/conways_game_of_life/st_app.py +71 -0
  60. examples/basic/schelling/README.md +47 -0
  61. examples/basic/schelling/agents.py +26 -0
  62. examples/basic/schelling/analysis.ipynb +205 -0
  63. examples/basic/schelling/app.py +43 -0
  64. examples/basic/schelling/model.py +60 -0
  65. examples/basic/virus_on_network/README.md +61 -0
  66. examples/basic/virus_on_network/agents.py +69 -0
  67. examples/basic/virus_on_network/app.py +133 -0
  68. examples/basic/virus_on_network/model.py +99 -0
  69. mesa/__init__.py +4 -1
  70. mesa/agent.py +24 -43
  71. mesa/batchrunner.py +7 -0
  72. mesa/examples.py +3 -0
  73. mesa/experimental/__init__.py +8 -2
  74. mesa/experimental/cell_space/__init__.py +7 -1
  75. mesa/experimental/cell_space/cell.py +35 -6
  76. mesa/experimental/cell_space/cell_agent.py +114 -23
  77. mesa/experimental/cell_space/discrete_space.py +70 -3
  78. mesa/experimental/cell_space/grid.py +13 -0
  79. mesa/experimental/cell_space/network.py +3 -0
  80. mesa/experimental/devs/examples/wolf_sheep.py +2 -1
  81. mesa/model.py +71 -21
  82. mesa/time.py +7 -5
  83. mesa/visualization/components/matplotlib.py +184 -90
  84. mesa/visualization/solara_viz.py +25 -61
  85. {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/METADATA +55 -13
  86. mesa-3.0.0b1.dist-info/RECORD +114 -0
  87. mesa-3.0.0b1.dist-info/licenses/LICENSE +202 -0
  88. mesa-3.0.0a5.dist-info/licenses/LICENSE → mesa-3.0.0b1.dist-info/licenses/NOTICE +2 -2
  89. mesa-3.0.0a5.dist-info/RECORD +0 -44
  90. {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/WHEEL +0 -0
  91. {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/entry_points.txt +0 -0
@@ -1,116 +1,192 @@
1
1
  """Matplotlib based solara components for visualization MESA spaces and plots."""
2
2
 
3
- from collections import defaultdict
3
+ import warnings
4
4
 
5
+ import matplotlib.pyplot as plt
5
6
  import networkx as nx
7
+ import numpy as np
6
8
  import solara
9
+ from matplotlib.cm import ScalarMappable
10
+ from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
7
11
  from matplotlib.figure import Figure
8
- from matplotlib.ticker import MaxNLocator
9
12
 
10
13
  import mesa
11
14
  from mesa.experimental.cell_space import VoronoiGrid
15
+ from mesa.space import PropertyLayer
12
16
  from mesa.visualization.utils import update_counter
13
17
 
14
18
 
15
- def make_space_matplotlib(agent_portrayal=None): # noqa: D103
19
+ def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None):
20
+ """Create a Matplotlib-based space visualization component.
21
+
22
+ Args:
23
+ agent_portrayal (function): Function to portray agents
24
+ propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications
25
+
26
+ Returns:
27
+ function: A function that creates a SpaceMatplotlib component
28
+ """
16
29
  if agent_portrayal is None:
17
30
 
18
31
  def agent_portrayal(a):
19
32
  return {"id": a.unique_id}
20
33
 
21
34
  def MakeSpaceMatplotlib(model):
22
- return SpaceMatplotlib(model, agent_portrayal)
35
+ return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal)
23
36
 
24
37
  return MakeSpaceMatplotlib
25
38
 
26
39
 
27
40
  @solara.component
28
- def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None): # noqa: D103
41
+ def SpaceMatplotlib(
42
+ model,
43
+ agent_portrayal,
44
+ propertylayer_portrayal,
45
+ dependencies: list[any] | None = None,
46
+ ):
47
+ """Create a Matplotlib-based space visualization component."""
29
48
  update_counter.get()
30
49
  space_fig = Figure()
31
50
  space_ax = space_fig.subplots()
32
51
  space = getattr(model, "grid", None)
33
52
  if space is None:
34
- # Sometimes the space is defined as model.space instead of model.grid
35
- space = model.space
36
- if isinstance(space, mesa.space.NetworkGrid):
37
- _draw_network_grid(space, space_ax, agent_portrayal)
53
+ space = getattr(model, "space", None)
54
+
55
+ if isinstance(space, mesa.space._Grid):
56
+ _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model)
38
57
  elif isinstance(space, mesa.space.ContinuousSpace):
39
- _draw_continuous_space(space, space_ax, agent_portrayal)
58
+ _draw_continuous_space(space, space_ax, agent_portrayal, model)
59
+ elif isinstance(space, mesa.space.NetworkGrid):
60
+ _draw_network_grid(space, space_ax, agent_portrayal)
40
61
  elif isinstance(space, VoronoiGrid):
41
62
  _draw_voronoi(space, space_ax, agent_portrayal)
42
- else:
43
- _draw_grid(space, space_ax, agent_portrayal)
44
- solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
45
-
46
-
47
- # matplotlib scatter does not allow for multiple shapes in one call
48
- def _split_and_scatter(portray_data, space_ax):
49
- grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
50
-
51
- # Extract data from the dictionary
52
- x = portray_data["x"]
53
- y = portray_data["y"]
54
- s = portray_data["s"]
55
- c = portray_data["c"]
56
- m = portray_data["m"]
57
-
58
- if not (len(x) == len(y) == len(s) == len(c) == len(m)):
59
- raise ValueError(
60
- "Length mismatch in portrayal data lists: "
61
- f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
62
- f"color: {len(c)}, marker: {len(m)}"
63
- )
63
+ elif space is None and propertylayer_portrayal:
64
+ draw_property_layers(space_ax, space, propertylayer_portrayal, model)
64
65
 
65
- # Group the data by marker
66
- for i in range(len(x)):
67
- marker = m[i]
68
- grouped_data[marker]["x"].append(x[i])
69
- grouped_data[marker]["y"].append(y[i])
70
- grouped_data[marker]["s"].append(s[i])
71
- grouped_data[marker]["c"].append(c[i])
66
+ solara.FigureMatplotlib(
67
+ space_fig, format="png", bbox_inches="tight", dependencies=dependencies
68
+ )
72
69
 
73
- # Plot each group with the same marker
74
- for marker, data in grouped_data.items():
75
- space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
76
70
 
71
+ def draw_property_layers(ax, space, propertylayer_portrayal, model):
72
+ """Draw PropertyLayers on the given axes.
73
+
74
+ Args:
75
+ ax (matplotlib.axes.Axes): The axes to draw on.
76
+ space (mesa.space._Grid): The space containing the PropertyLayers.
77
+ propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications.
78
+ model (mesa.Model): The model instance.
79
+ """
80
+ for layer_name, portrayal in propertylayer_portrayal.items():
81
+ layer = getattr(model, layer_name, None)
82
+ if not isinstance(layer, PropertyLayer):
83
+ continue
84
+
85
+ data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
86
+ width, height = data.shape if space is None else (space.width, space.height)
87
+
88
+ if space and data.shape != (width, height):
89
+ warnings.warn(
90
+ f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
91
+ UserWarning,
92
+ stacklevel=2,
93
+ )
94
+
95
+ # Get portrayal properties, or use defaults
96
+ alpha = portrayal.get("alpha", 1)
97
+ vmin = portrayal.get("vmin", np.min(data))
98
+ vmax = portrayal.get("vmax", np.max(data))
99
+ colorbar = portrayal.get("colorbar", True)
100
+
101
+ # Draw the layer
102
+ if "color" in portrayal:
103
+ rgba_color = to_rgba(portrayal["color"])
104
+ normalized_data = (data - vmin) / (vmax - vmin)
105
+ rgba_data = np.full((*data.shape, 4), rgba_color)
106
+ rgba_data[..., 3] *= normalized_data * alpha
107
+ rgba_data = np.clip(rgba_data, 0, 1)
108
+ cmap = LinearSegmentedColormap.from_list(
109
+ layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
110
+ )
111
+ im = ax.imshow(
112
+ rgba_data.transpose(1, 0, 2),
113
+ extent=(0, width, 0, height),
114
+ origin="lower",
115
+ )
116
+ if colorbar:
117
+ norm = Normalize(vmin=vmin, vmax=vmax)
118
+ sm = ScalarMappable(norm=norm, cmap=cmap)
119
+ sm.set_array([])
120
+ ax.figure.colorbar(sm, ax=ax, orientation="vertical")
121
+
122
+ elif "colormap" in portrayal:
123
+ cmap = portrayal.get("colormap", "viridis")
124
+ if isinstance(cmap, list):
125
+ cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
126
+ im = ax.imshow(
127
+ data.T,
128
+ cmap=cmap,
129
+ alpha=alpha,
130
+ vmin=vmin,
131
+ vmax=vmax,
132
+ extent=(0, width, 0, height),
133
+ origin="lower",
134
+ )
135
+ if colorbar:
136
+ plt.colorbar(im, ax=ax, label=layer_name)
137
+ else:
138
+ raise ValueError(
139
+ f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
140
+ )
141
+
142
+
143
+ def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model):
144
+ if propertylayer_portrayal:
145
+ draw_property_layers(space_ax, space, propertylayer_portrayal, model)
146
+
147
+ agent_data = _get_agent_data(space, agent_portrayal)
148
+
149
+ space_ax.set_xlim(0, space.width)
150
+ space_ax.set_ylim(0, space.height)
151
+ _split_and_scatter(agent_data, space_ax)
152
+
153
+ # Draw grid lines
154
+ for x in range(space.width + 1):
155
+ space_ax.axvline(x, color="gray", linestyle=":")
156
+ for y in range(space.height + 1):
157
+ space_ax.axhline(y, color="gray", linestyle=":")
158
+
159
+
160
+ def _get_agent_data(space, agent_portrayal):
161
+ """Helper function to get agent data for visualization."""
162
+ x, y, s, c, m = [], [], [], [], []
163
+ for agents, pos in space.coord_iter():
164
+ if not agents:
165
+ continue
166
+ if not isinstance(agents, list):
167
+ agents = [agents] # noqa PLW2901
168
+ for agent in agents:
169
+ data = agent_portrayal(agent)
170
+ x.append(pos[0] + 0.5) # Center the agent in the cell
171
+ y.append(pos[1] + 0.5) # Center the agent in the cell
172
+ default_size = (180 / max(space.width, space.height)) ** 2
173
+ s.append(data.get("size", default_size))
174
+ c.append(data.get("color", "tab:blue"))
175
+ m.append(data.get("shape", "o"))
176
+ return {"x": x, "y": y, "s": s, "c": c, "m": m}
77
177
 
78
- def _draw_grid(space, space_ax, agent_portrayal):
79
- def portray(g):
80
- x = []
81
- y = []
82
- s = [] # size
83
- c = [] # color
84
- m = [] # shape
85
- for i in range(g.width):
86
- for j in range(g.height):
87
- content = g._grid[i][j]
88
- if not content:
89
- continue
90
- if not hasattr(content, "__iter__"):
91
- # Is a single grid
92
- content = [content]
93
- for agent in content:
94
- data = agent_portrayal(agent)
95
- x.append(i)
96
- y.append(j)
97
-
98
- # This is the default value for the marker size, which auto-scales
99
- # according to the grid area.
100
- default_size = (180 / max(g.width, g.height)) ** 2
101
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
102
- size = data.get("size", default_size)
103
- s.append(size)
104
- color = data.get("color", "tab:blue")
105
- c.append(color)
106
- mark = data.get("shape", "o")
107
- m.append(mark)
108
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
109
- return out
110
178
 
111
- space_ax.set_xlim(-1, space.width)
112
- space_ax.set_ylim(-1, space.height)
113
- _split_and_scatter(portray(space), space_ax)
179
+ def _split_and_scatter(portray_data, space_ax):
180
+ """Helper function to split and scatter agent data."""
181
+ for marker in set(portray_data["m"]):
182
+ mask = [m == marker for m in portray_data["m"]]
183
+ space_ax.scatter(
184
+ [x for x, show in zip(portray_data["x"], mask) if show],
185
+ [y for y, show in zip(portray_data["y"], mask) if show],
186
+ s=[s for s, show in zip(portray_data["s"], mask) if show],
187
+ c=[c for c, show in zip(portray_data["c"], mask) if show],
188
+ marker=marker,
189
+ )
114
190
 
115
191
 
116
192
  def _draw_network_grid(space, space_ax, agent_portrayal):
@@ -124,7 +200,7 @@ def _draw_network_grid(space, space_ax, agent_portrayal):
124
200
  )
125
201
 
126
202
 
127
- def _draw_continuous_space(space, space_ax, agent_portrayal):
203
+ def _draw_continuous_space(space, space_ax, agent_portrayal, model):
128
204
  def portray(space):
129
205
  x = []
130
206
  y = []
@@ -139,15 +215,13 @@ def _draw_continuous_space(space, space_ax, agent_portrayal):
139
215
 
140
216
  # This is matplotlib's default marker size
141
217
  default_size = 20
142
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
143
218
  size = data.get("size", default_size)
144
219
  s.append(size)
145
220
  color = data.get("color", "tab:blue")
146
221
  c.append(color)
147
222
  mark = data.get("shape", "o")
148
223
  m.append(mark)
149
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
150
- return out
224
+ return {"x": x, "y": y, "s": s, "c": c, "m": m}
151
225
 
152
226
  # Determine border style based on space.torus
153
227
  border_style = "solid" if not space.torus else (0, (5, 10))
@@ -186,8 +260,6 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
186
260
  if "color" in data:
187
261
  c.append(data["color"])
188
262
  out = {"x": x, "y": y}
189
- # This is the default value for the marker size, which auto-scales
190
- # according to the grid area.
191
263
  out["s"] = s
192
264
  if len(c) > 0:
193
265
  out["c"] = c
@@ -216,10 +288,19 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
216
288
  alpha=min(1, cell.properties[space.cell_coloring_property]),
217
289
  c="red",
218
290
  ) # Plot filled polygon
219
- space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
291
+ space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black
292
+
293
+
294
+ def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
295
+ """Create a plotting function for a specified measure.
296
+
297
+ Args:
298
+ measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
220
299
 
300
+ Returns:
301
+ function: A function that creates a PlotMatplotlib component.
302
+ """
221
303
 
222
- def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): # noqa: D103
223
304
  def MakePlotMeasure(model):
224
305
  return PlotMatplotlib(model, measure)
225
306
 
@@ -227,7 +308,17 @@ def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
227
308
 
228
309
 
229
310
  @solara.component
230
- def PlotMatplotlib(model, measure, dependencies: list[any] | None = None): # noqa: D103
311
+ def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
312
+ """Create a Matplotlib-based plot for a measure or measures.
313
+
314
+ Args:
315
+ model (mesa.Model): The model instance.
316
+ measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
317
+ dependencies (list[any] | None): Optional dependencies for the plot.
318
+
319
+ Returns:
320
+ solara.FigureMatplotlib: A component for rendering the plot.
321
+ """
231
322
  update_counter.get()
232
323
  fig = Figure()
233
324
  ax = fig.subplots()
@@ -238,11 +329,14 @@ def PlotMatplotlib(model, measure, dependencies: list[any] | None = None): # no
238
329
  elif isinstance(measure, dict):
239
330
  for m, color in measure.items():
240
331
  ax.plot(df.loc[:, m], label=m, color=color)
241
- fig.legend()
332
+ ax.legend(loc="best")
242
333
  elif isinstance(measure, list | tuple):
243
334
  for m in measure:
244
335
  ax.plot(df.loc[:, m], label=m)
245
- fig.legend()
336
+ ax.legend(loc="best")
337
+ ax.set_xlabel("Step")
246
338
  # Set integer x axis
247
- ax.xaxis.set_major_locator(MaxNLocator(integer=True))
248
- solara.FigureMatplotlib(fig, dependencies=dependencies)
339
+ ax.xaxis.set_major_locator(plt.MaxNLocator(integer=True))
340
+ solara.FigureMatplotlib(
341
+ fig, format="png", bbox_inches="tight", dependencies=dependencies
342
+ )
@@ -30,10 +30,8 @@ from typing import TYPE_CHECKING, Literal
30
30
 
31
31
  import reacton.core
32
32
  import solara
33
- from solara.alias import rv
34
33
 
35
34
  import mesa.visualization.components.altair as components_altair
36
- import mesa.visualization.components.matplotlib as components_matplotlib
37
35
  from mesa.visualization.UserParam import Slider
38
36
  from mesa.visualization.utils import force_update, update_counter
39
37
 
@@ -41,55 +39,6 @@ if TYPE_CHECKING:
41
39
  from mesa.model import Model
42
40
 
43
41
 
44
- # TODO: Turn this function into a Solara component once the current_step.value
45
- # dependency is passed to measure()
46
- def Card(
47
- model, measures, agent_portrayal, space_drawer, dependencies, color, layout_type
48
- ):
49
- """Create a card component for visualizing model space or measures.
50
-
51
- Args:
52
- model: The Mesa model instance
53
- measures: List of measures to be plotted
54
- agent_portrayal: Function to define agent appearance
55
- space_drawer: Method to render agent space
56
- dependencies: List of dependencies for updating the visualization
57
- color: Background color of the card
58
- layout_type: Type of layout (Space or Measure)
59
-
60
- Returns:
61
- rv.Card: A card component containing the visualization
62
- """
63
- with rv.Card(
64
- style_=f"background-color: {color}; width: 100%; height: 100%"
65
- ) as main:
66
- if "Space" in layout_type:
67
- rv.CardTitle(children=["Space"])
68
- if space_drawer == "default":
69
- # draw with the default implementation
70
- components_matplotlib.SpaceMatplotlib(
71
- model, agent_portrayal, dependencies=dependencies
72
- )
73
- elif space_drawer == "altair":
74
- components_altair.SpaceAltair(
75
- model, agent_portrayal, dependencies=dependencies
76
- )
77
- elif space_drawer:
78
- # if specified, draw agent space with an alternate renderer
79
- space_drawer(model, agent_portrayal, dependencies=dependencies)
80
- elif "Measure" in layout_type:
81
- rv.CardTitle(children=["Measure"])
82
- measure = measures[layout_type["Measure"]]
83
- if callable(measure):
84
- # Is a custom object
85
- measure(model)
86
- else:
87
- components_matplotlib.PlotMatplotlib(
88
- model, measure, dependencies=dependencies
89
- )
90
- return main
91
-
92
-
93
42
  @solara.component
94
43
  def SolaraViz(
95
44
  model: Model | solara.Reactive[Model],
@@ -207,10 +156,16 @@ def ComponentsView(
207
156
  model: Model instance to pass to each component
208
157
  """
209
158
  wrapped_components = [_wrap_component(component) for component in components]
210
-
211
- with solara.Column():
212
- for component in wrapped_components:
213
- component(model)
159
+ items = [component(model) for component in wrapped_components]
160
+ grid_layout_initial = make_initial_grid_layout(num_components=len(items))
161
+ grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
162
+ solara.GridDraggable(
163
+ items=items,
164
+ grid_layout=grid_layout,
165
+ resizable=True,
166
+ draggable=True,
167
+ on_grid_layout=set_grid_layout,
168
+ )
214
169
 
215
170
 
216
171
  JupyterViz = SolaraViz
@@ -225,6 +180,7 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
225
180
  play_interval (int, optional): Interval for playing the model steps in milliseconds.
226
181
  """
227
182
  playing = solara.use_reactive(False)
183
+ running = solara.use_reactive(True)
228
184
  original_model = solara.use_reactive(None)
229
185
 
230
186
  def save_initial_model():
@@ -236,19 +192,23 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
236
192
  solara.use_effect(save_initial_model, [model.value])
237
193
 
238
194
  async def step():
239
- while playing.value:
195
+ while playing.value and running.value:
240
196
  await asyncio.sleep(play_interval / 1000)
241
197
  do_step()
242
198
 
243
- solara.lab.use_task(step, dependencies=[playing.value], prefer_threaded=False)
199
+ solara.lab.use_task(
200
+ step, dependencies=[playing.value, running.value], prefer_threaded=False
201
+ )
244
202
 
245
203
  def do_step():
246
204
  """Advance the model by one step."""
247
205
  model.value.step()
206
+ running.value = model.value.running
248
207
 
249
208
  def do_reset():
250
209
  """Reset the model to its initial state."""
251
210
  playing.value = False
211
+ running.value = True
252
212
  model.value = copy.deepcopy(original_model.value)
253
213
 
254
214
  def do_play_pause():
@@ -261,9 +221,13 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
261
221
  label="▶" if not playing.value else "❚❚",
262
222
  color="primary",
263
223
  on_click=do_play_pause,
224
+ disabled=not running.value,
264
225
  )
265
226
  solara.Button(
266
- label="Step", color="primary", on_click=do_step, disabled=playing.value
227
+ label="Step",
228
+ color="primary",
229
+ on_click=do_step,
230
+ disabled=playing.value or not running.value,
267
231
  )
268
232
 
269
233
 
@@ -439,11 +403,11 @@ def UserInputs(user_params, on_change=None):
439
403
  raise ValueError(f"{input_type} is not a supported input type")
440
404
 
441
405
 
442
- def make_initial_grid_layout(layout_types):
406
+ def make_initial_grid_layout(num_components):
443
407
  """Create an initial grid layout for visualization components.
444
408
 
445
409
  Args:
446
- layout_types: List of layout types (Space or Measure)
410
+ num_components: Number of components to display
447
411
 
448
412
  Returns:
449
413
  list: Initial grid layout configuration
@@ -457,7 +421,7 @@ def make_initial_grid_layout(layout_types):
457
421
  "x": 6 * (i % 2),
458
422
  "y": 16 * (i - i % 2),
459
423
  }
460
- for i in range(len(layout_types))
424
+ for i in range(num_components)
461
425
  ]
462
426
 
463
427
 
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: Mesa
3
- Version: 3.0.0a5
3
+ Version: 3.0.0b1
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
7
7
  Author-email: Project Mesa Team <projectmesa@googlegroups.com>
8
8
  License: Apache 2.0
9
9
  License-File: LICENSE
10
+ License-File: NOTICE
10
11
  Keywords: ABM,agent,based,model,modeling,multi-agent,simulation
11
12
  Classifier: Development Status :: 3 - Alpha
12
13
  Classifier: Intended Audience :: Science/Research
@@ -22,30 +23,60 @@ Classifier: Topic :: Scientific/Engineering
22
23
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
24
  Classifier: Topic :: Scientific/Engineering :: Artificial Life
24
25
  Requires-Python: >=3.10
25
- Requires-Dist: click
26
- Requires-Dist: cookiecutter
27
- Requires-Dist: matplotlib
28
- Requires-Dist: networkx
29
26
  Requires-Dist: numpy
30
27
  Requires-Dist: pandas
31
- Requires-Dist: solara
32
28
  Requires-Dist: tqdm
29
+ Provides-Extra: all
30
+ Requires-Dist: cookiecutter; extra == 'all'
31
+ Requires-Dist: ipython; extra == 'all'
32
+ Requires-Dist: matplotlib; extra == 'all'
33
+ Requires-Dist: myst-nb; extra == 'all'
34
+ Requires-Dist: myst-parser; extra == 'all'
35
+ Requires-Dist: networkx; extra == 'all'
36
+ Requires-Dist: pydata-sphinx-theme; extra == 'all'
37
+ Requires-Dist: pytest; extra == 'all'
38
+ Requires-Dist: pytest-cov; extra == 'all'
39
+ Requires-Dist: pytest-mock; extra == 'all'
40
+ Requires-Dist: ruff; extra == 'all'
41
+ Requires-Dist: scipy; extra == 'all'
42
+ Requires-Dist: seaborn; extra == 'all'
43
+ Requires-Dist: solara; extra == 'all'
44
+ Requires-Dist: sphinx; extra == 'all'
33
45
  Provides-Extra: dev
46
+ Requires-Dist: cookiecutter; extra == 'dev'
47
+ Requires-Dist: matplotlib; extra == 'dev'
48
+ Requires-Dist: networkx; extra == 'dev'
49
+ Requires-Dist: pytest; extra == 'dev'
34
50
  Requires-Dist: pytest-cov; extra == 'dev'
35
51
  Requires-Dist: pytest-mock; extra == 'dev'
36
- Requires-Dist: pytest>=4.6; extra == 'dev'
37
- Requires-Dist: ruff~=0.1.1; extra == 'dev'
52
+ Requires-Dist: ruff; extra == 'dev'
53
+ Requires-Dist: solara; extra == 'dev'
38
54
  Requires-Dist: sphinx; extra == 'dev'
39
55
  Provides-Extra: docs
40
56
  Requires-Dist: ipython; extra == 'docs'
57
+ Requires-Dist: matplotlib; extra == 'docs'
41
58
  Requires-Dist: myst-nb; extra == 'docs'
42
59
  Requires-Dist: myst-parser; extra == 'docs'
60
+ Requires-Dist: networkx; extra == 'docs'
43
61
  Requires-Dist: pydata-sphinx-theme; extra == 'docs'
44
62
  Requires-Dist: seaborn; extra == 'docs'
63
+ Requires-Dist: solara; extra == 'docs'
45
64
  Requires-Dist: sphinx; extra == 'docs'
46
65
  Provides-Extra: examples
47
- Requires-Dist: pytest>=4.6; extra == 'examples'
66
+ Requires-Dist: matplotlib; extra == 'examples'
67
+ Requires-Dist: networkx; extra == 'examples'
68
+ Requires-Dist: pytest; extra == 'examples'
48
69
  Requires-Dist: scipy; extra == 'examples'
70
+ Requires-Dist: solara; extra == 'examples'
71
+ Provides-Extra: network
72
+ Requires-Dist: networkx; extra == 'network'
73
+ Provides-Extra: rec
74
+ Requires-Dist: matplotlib; extra == 'rec'
75
+ Requires-Dist: networkx; extra == 'rec'
76
+ Requires-Dist: solara; extra == 'rec'
77
+ Provides-Extra: viz
78
+ Requires-Dist: matplotlib; extra == 'viz'
79
+ Requires-Dist: solara; extra == 'viz'
49
80
  Description-Content-Type: text/markdown
50
81
 
51
82
  # Mesa: Agent-based modeling in Python
@@ -78,7 +109,7 @@ can be displayed in browser windows or Jupyter.*
78
109
 
79
110
  ## Using Mesa
80
111
 
81
- To install our latest stable release (2.3.x), run:
112
+ To install our latest stable release (2.4.x), run:
82
113
 
83
114
  ``` bash
84
115
  pip install -U mesa
@@ -89,6 +120,17 @@ To install our latest pre-release (3.0.0 alpha), run:
89
120
  ``` bash
90
121
  pip install -U --pre mesa
91
122
  ```
123
+ With Mesa 3.0, we don't install all our dependencies anymore by default.
124
+ ```bash
125
+ # You can customize the additional dependencies you need, if you want. Available are:
126
+ pip install -U --pre mesa[network,viz]
127
+
128
+ # This is equivalent to our recommended dependencies:
129
+ pip install -U --pre mesa[rec]
130
+
131
+ # To install all, including developer, dependencies:
132
+ pip install -U --pre mesa[all]
133
+ ```
92
134
 
93
135
  You can also use `pip` to install the latest GitHub version:
94
136
 
@@ -107,13 +149,13 @@ For resources or help on using Mesa, check out the following:
107
149
 
108
150
  - [Intro to Mesa Tutorial](http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html) (An introductory model, the Boltzmann
109
151
  Wealth Model, for beginners or those new to Mesa.)
110
- - [Visualization Tutorial](https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
152
+ - [Visualization Tutorial](https://mesa.readthedocs.io/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
111
153
  - [Complexity Explorer Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) (An advanced-beginner model,
112
154
  SugarScape with Traders, with instructional videos)
113
- - [Mesa Examples](https://github.com/projectmesa/mesa-examples/tree/main/examples) (A repository of seminal ABMs using Mesa and
155
+ - [Mesa Examples](https://github.com/projectmesa/mesa-examples) (A repository of seminal ABMs using Mesa and
114
156
  examples of employing specific Mesa Features)
115
157
  - [Docs](http://mesa.readthedocs.org/) (Mesa's documentation, API and useful snippets)
116
- - [Development version docs](https://mesa.readthedocs.io/en/latest/) (the latest version docs if you're using a pre-release Mesa version)
158
+ - [Development version docs](https://mesa.readthedocs.io/latest/) (the latest version docs if you're using a pre-release Mesa version)
117
159
  - [Discussions](https://github.com/projectmesa/mesa/discussions) (GitHub threaded discussions about Mesa)
118
160
  - [Matrix Chat](https://matrix.to/#/#project-mesa:matrix.org) (Chat Forum via Matrix to talk about Mesa)
119
161