Mesa 2.3.4__py3-none-any.whl → 3.0.0__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 (110) hide show
  1. mesa/__init__.py +3 -5
  2. mesa/agent.py +393 -116
  3. mesa/batchrunner.py +58 -31
  4. mesa/datacollection.py +141 -30
  5. mesa/examples/README.md +37 -0
  6. mesa/examples/__init__.py +21 -0
  7. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  8. mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
  9. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  10. mesa/examples/advanced/epstein_civil_violence/agents.py +164 -0
  11. mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
  12. mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
  13. mesa/examples/advanced/pd_grid/Readme.md +43 -0
  14. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  15. mesa/examples/advanced/pd_grid/agents.py +50 -0
  16. mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
  17. mesa/examples/advanced/pd_grid/app.py +54 -0
  18. mesa/examples/advanced/pd_grid/model.py +71 -0
  19. mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
  20. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  21. mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
  22. mesa/examples/advanced/sugarscape_g1mt/app.py +62 -0
  23. mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
  24. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
  25. mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
  26. mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
  27. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  28. mesa/examples/advanced/wolf_sheep/agents.py +102 -0
  29. mesa/examples/advanced/wolf_sheep/app.py +84 -0
  30. mesa/examples/advanced/wolf_sheep/model.py +137 -0
  31. mesa/examples/basic/__init__.py +0 -0
  32. mesa/examples/basic/boid_flockers/Readme.md +22 -0
  33. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  34. mesa/examples/basic/boid_flockers/agents.py +71 -0
  35. mesa/examples/basic/boid_flockers/app.py +58 -0
  36. mesa/examples/basic/boid_flockers/model.py +69 -0
  37. mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
  38. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  39. mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
  40. mesa/examples/basic/boltzmann_wealth_model/app.py +74 -0
  41. mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
  42. mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  43. mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
  44. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  45. mesa/examples/basic/conways_game_of_life/agents.py +47 -0
  46. mesa/examples/basic/conways_game_of_life/app.py +51 -0
  47. mesa/examples/basic/conways_game_of_life/model.py +31 -0
  48. mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
  49. mesa/examples/basic/schelling/Readme.md +40 -0
  50. mesa/examples/basic/schelling/__init__.py +0 -0
  51. mesa/examples/basic/schelling/agents.py +26 -0
  52. mesa/examples/basic/schelling/analysis.ipynb +205 -0
  53. mesa/examples/basic/schelling/app.py +42 -0
  54. mesa/examples/basic/schelling/model.py +59 -0
  55. mesa/examples/basic/virus_on_network/Readme.md +61 -0
  56. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  57. mesa/examples/basic/virus_on_network/agents.py +69 -0
  58. mesa/examples/basic/virus_on_network/app.py +114 -0
  59. mesa/examples/basic/virus_on_network/model.py +96 -0
  60. mesa/experimental/UserParam.py +18 -7
  61. mesa/experimental/__init__.py +10 -2
  62. mesa/experimental/cell_space/__init__.py +16 -1
  63. mesa/experimental/cell_space/cell.py +93 -23
  64. mesa/experimental/cell_space/cell_agent.py +117 -21
  65. mesa/experimental/cell_space/cell_collection.py +56 -19
  66. mesa/experimental/cell_space/discrete_space.py +92 -8
  67. mesa/experimental/cell_space/grid.py +33 -9
  68. mesa/experimental/cell_space/network.py +15 -10
  69. mesa/experimental/cell_space/voronoi.py +257 -0
  70. mesa/experimental/components/altair.py +11 -2
  71. mesa/experimental/components/matplotlib.py +132 -26
  72. mesa/experimental/devs/__init__.py +2 -0
  73. mesa/experimental/devs/eventlist.py +54 -15
  74. mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
  75. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  76. mesa/experimental/devs/simulator.py +57 -16
  77. mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -98
  78. mesa/model.py +212 -84
  79. mesa/space.py +217 -151
  80. mesa/time.py +63 -80
  81. mesa/visualization/__init__.py +25 -6
  82. mesa/visualization/components/__init__.py +83 -0
  83. mesa/visualization/components/altair_components.py +188 -0
  84. mesa/visualization/components/matplotlib_components.py +175 -0
  85. mesa/visualization/mpl_space_drawing.py +593 -0
  86. mesa/visualization/solara_viz.py +458 -0
  87. mesa/visualization/user_param.py +69 -0
  88. mesa/visualization/utils.py +9 -0
  89. {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/METADATA +65 -19
  90. mesa-3.0.0.dist-info/RECORD +95 -0
  91. mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
  92. mesa-2.3.4.dist-info/licenses/LICENSE → mesa-3.0.0.dist-info/licenses/NOTICE +2 -2
  93. mesa/cookiecutter-mesa/cookiecutter.json +0 -8
  94. mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -11
  95. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
  96. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
  97. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
  98. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
  99. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
  100. mesa/flat/__init__.py +0 -6
  101. mesa/flat/visualization.py +0 -5
  102. mesa/main.py +0 -63
  103. mesa/visualization/ModularVisualization.py +0 -1
  104. mesa/visualization/TextVisualization.py +0 -1
  105. mesa/visualization/UserParam.py +0 -1
  106. mesa/visualization/modules.py +0 -1
  107. mesa-2.3.4.dist-info/RECORD +0 -45
  108. /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
  109. {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
  110. {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
@@ -1,7 +1,29 @@
1
- import sys
1
+ """Mesa visualization module for creating interactive model visualizations.
2
+
3
+ This module provides components to create browser- and Jupyter notebook-based visualizations of
4
+ Mesa models, allowing users to watch models run step-by-step and interact with model parameters.
5
+
6
+ Key features:
7
+ - SolaraViz: Main component for creating visualizations, supporting grid displays and plots
8
+ - ModelController: Handles model execution controls (step, play, pause, reset)
9
+ - UserInputs: Generates UI elements for adjusting model parameters
10
+ - Card: Renders individual visualization elements (space, measures)
11
+
12
+ The module uses Solara for rendering in Jupyter notebooks or as standalone web applications.
13
+ It supports various types of visualizations including matplotlib plots, agent grids, and
14
+ custom visualization components.
15
+
16
+ Usage:
17
+ 1. Define an agent_portrayal function to specify how agents should be displayed
18
+ 2. Set up model_params to define adjustable parameters
19
+ 3. Create a SolaraViz instance with your model, parameters, and desired measures
20
+ 4. Display the visualization in a Jupyter notebook or run as a Solara app
21
+
22
+ See the Visualization Tutorial and example models for more details.
23
+ """
24
+
2
25
  import threading
3
26
 
4
- import matplotlib.pyplot as plt
5
27
  import reacton.ipywidgets as widgets
6
28
  import solara
7
29
  from solara.alias import rv
@@ -10,15 +32,26 @@ import mesa.experimental.components.altair as components_altair
10
32
  import mesa.experimental.components.matplotlib as components_matplotlib
11
33
  from mesa.experimental.UserParam import Slider
12
34
 
13
- # Avoid interactive backend
14
- plt.switch_backend("agg")
15
-
16
35
 
17
36
  # TODO: Turn this function into a Solara component once the current_step.value
18
37
  # dependency is passed to measure()
19
38
  def Card(
20
39
  model, measures, agent_portrayal, space_drawer, dependencies, color, layout_type
21
40
  ):
41
+ """Create a card component for visualizing model space or measures.
42
+
43
+ Args:
44
+ model: The Mesa model instance
45
+ measures: List of measures to be plotted
46
+ agent_portrayal: Function to define agent appearance
47
+ space_drawer: Method to render agent space
48
+ dependencies: List of dependencies for updating the visualization
49
+ color: Background color of the card
50
+ layout_type: Type of layout (Space or Measure)
51
+
52
+ Returns:
53
+ rv.Card: A card component containing the visualization
54
+ """
22
55
  with rv.Card(
23
56
  style_=f"background-color: {color}; width: 100%; height: 100%"
24
57
  ) as main:
@@ -35,7 +68,7 @@ def Card(
35
68
  )
36
69
  elif space_drawer:
37
70
  # if specified, draw agent space with an alternate renderer
38
- space_drawer(model, agent_portrayal)
71
+ space_drawer(model, agent_portrayal, dependencies=dependencies)
39
72
  elif "Measure" in layout_type:
40
73
  rv.CardTitle(children=["Measure"])
41
74
  measure = measures[layout_type["Measure"]]
@@ -50,7 +83,7 @@ def Card(
50
83
 
51
84
 
52
85
  @solara.component
53
- def JupyterViz(
86
+ def SolaraViz(
54
87
  model_class,
55
88
  model_params,
56
89
  measures=None,
@@ -61,18 +94,20 @@ def JupyterViz(
61
94
  seed=None,
62
95
  ):
63
96
  """Initialize a component to visualize a model.
97
+
64
98
  Args:
65
- model_class: class of the model to instantiate
66
- model_params: parameters for initializing the model
67
- measures: list of callables or data attributes to plot
68
- name: name for display
69
- agent_portrayal: options for rendering agents (dictionary)
70
- space_drawer: method to render the agent space for
99
+ model_class: Class of the model to instantiate
100
+ model_params: Parameters for initializing the model
101
+ measures: List of callables or data attributes to plot
102
+ name: Name for display
103
+ agent_portrayal: Options for rendering agents (dictionary);
104
+ Default drawer supports custom `"size"`, `"color"`, and `"shape"`.
105
+ space_drawer: Method to render the agent space for
71
106
  the model; default implementation is the `SpaceMatplotlib` component;
72
107
  simulations with no space to visualize should
73
108
  specify `space_drawer=False`
74
- play_interval: play interval (default: 150)
75
- seed: the random seed used to initialize the model
109
+ play_interval: Play interval (default: 150)
110
+ seed: The random seed used to initialize the model
76
111
  """
77
112
  if name is None:
78
113
  name = model_class.__name__
@@ -88,6 +123,7 @@ def JupyterViz(
88
123
 
89
124
  # 2. Set up Model
90
125
  def make_model():
126
+ """Create a new model instance with current parameters and seed."""
91
127
  model = model_class.__new__(
92
128
  model_class, **model_parameters, seed=reactive_seed.value
93
129
  )
@@ -106,6 +142,7 @@ def JupyterViz(
106
142
  )
107
143
 
108
144
  def handle_change_model_params(name: str, value: any):
145
+ """Update model parameters when user input changes."""
109
146
  set_model_parameters({**model_parameters, name: value})
110
147
 
111
148
  # 3. Set up UI
@@ -115,95 +152,71 @@ def JupyterViz(
115
152
 
116
153
  # render layout and plot
117
154
  def do_reseed():
155
+ """Update the random seed for the model."""
118
156
  reactive_seed.value = model.random.random()
119
157
 
120
- # jupyter
121
- dependencies = [current_step.value, reactive_seed.value]
158
+ dependencies = [
159
+ *list(model_parameters.values()),
160
+ current_step.value,
161
+ reactive_seed.value,
162
+ ]
122
163
 
123
- def render_in_jupyter():
124
- with solara.GridFixed(columns=2):
125
- UserInputs(user_params, on_change=handle_change_model_params)
126
- ModelController(model, play_interval, current_step, reset_counter)
127
- solara.Markdown(md_text=f"###Step - {current_step}")
164
+ # if space drawer is disabled, do not include it
165
+ layout_types = [{"Space": "default"}] if space_drawer else []
128
166
 
129
- with solara.GridFixed(columns=2):
130
- # 4. Space
131
- if space_drawer == "default":
132
- # draw with the default implementation
133
- components_matplotlib.SpaceMatplotlib(
134
- model, agent_portrayal, dependencies=dependencies
135
- )
136
- elif space_drawer == "altair":
137
- components_altair.SpaceAltair(
138
- model, agent_portrayal, dependencies=dependencies
139
- )
140
- elif space_drawer:
141
- # if specified, draw agent space with an alternate renderer
142
- space_drawer(model, agent_portrayal)
143
- # otherwise, do nothing (do not draw space)
144
-
145
- # 5. Plots
146
- for measure in measures:
147
- if callable(measure):
148
- # Is a custom object
149
- measure(model)
150
- else:
151
- components_matplotlib.PlotMatplotlib(
152
- model, measure, dependencies=dependencies
153
- )
154
-
155
- def render_in_browser():
156
- # if space drawer is disabled, do not include it
157
- layout_types = [{"Space": "default"}] if space_drawer else []
158
-
159
- if measures:
160
- layout_types += [{"Measure": elem} for elem in range(len(measures))]
161
-
162
- grid_layout_initial = make_initial_grid_layout(layout_types=layout_types)
163
- grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
164
-
165
- with solara.Sidebar():
166
- with solara.Card("Controls", margin=1, elevation=2):
167
- solara.InputText(
168
- label="Seed",
169
- value=reactive_seed,
170
- continuous_update=True,
171
- )
172
- UserInputs(user_params, on_change=handle_change_model_params)
173
- ModelController(model, play_interval, current_step, reset_counter)
174
- solara.Button(label="Reseed", color="primary", on_click=do_reseed)
175
- with solara.Card("Information", margin=1, elevation=2):
176
- solara.Markdown(md_text=f"Step - {current_step}")
177
-
178
- items = [
179
- Card(
180
- model,
181
- measures,
182
- agent_portrayal,
183
- space_drawer,
184
- dependencies,
185
- color="white",
186
- layout_type=layout_types[i],
167
+ if measures:
168
+ layout_types += [{"Measure": elem} for elem in range(len(measures))]
169
+
170
+ grid_layout_initial = make_initial_grid_layout(layout_types=layout_types)
171
+ grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
172
+
173
+ with solara.Sidebar():
174
+ with solara.Card("Controls", margin=1, elevation=2):
175
+ solara.InputText(
176
+ label="Seed",
177
+ value=reactive_seed,
178
+ continuous_update=True,
187
179
  )
188
- for i in range(len(layout_types))
189
- ]
190
- solara.GridDraggable(
191
- items=items,
192
- grid_layout=grid_layout,
193
- resizable=True,
194
- draggable=True,
195
- on_grid_layout=set_grid_layout,
180
+ UserInputs(user_params, on_change=handle_change_model_params)
181
+ ModelController(model, play_interval, current_step, reset_counter)
182
+ solara.Button(label="Reseed", color="primary", on_click=do_reseed)
183
+ with solara.Card("Information", margin=1, elevation=2):
184
+ solara.Markdown(md_text=f"Step - {current_step}")
185
+
186
+ items = [
187
+ Card(
188
+ model,
189
+ measures,
190
+ agent_portrayal,
191
+ space_drawer,
192
+ dependencies,
193
+ color="white",
194
+ layout_type=layout_types[i],
196
195
  )
196
+ for i in range(len(layout_types))
197
+ ]
198
+ solara.GridDraggable(
199
+ items=items,
200
+ grid_layout=grid_layout,
201
+ resizable=True,
202
+ draggable=True,
203
+ on_grid_layout=set_grid_layout,
204
+ )
197
205
 
198
- if ("ipykernel" in sys.argv[0]) or ("colab_kernel_launcher.py" in sys.argv[0]):
199
- # When in Jupyter or Google Colab
200
- render_in_jupyter()
201
- else:
202
- render_in_browser()
206
+
207
+ JupyterViz = SolaraViz
203
208
 
204
209
 
205
210
  @solara.component
206
211
  def ModelController(model, play_interval, current_step, reset_counter):
212
+ """Create controls for model execution (step, play, pause, reset).
213
+
214
+ Args:
215
+ model: The model being visualized
216
+ play_interval: Interval between steps during play
217
+ current_step: Reactive value for the current step
218
+ reset_counter: Counter to trigger model reset
219
+ """
207
220
  playing = solara.use_reactive(False)
208
221
  thread = solara.use_reactive(None)
209
222
  # We track the previous step to detect if user resets the model via
@@ -213,6 +226,7 @@ def ModelController(model, play_interval, current_step, reset_counter):
213
226
  previous_step = solara.use_reactive(0)
214
227
 
215
228
  def on_value_play(change):
229
+ """Handle play/pause state changes."""
216
230
  if previous_step.value > current_step.value and current_step.value == 0:
217
231
  # We add extra checks for current_step.value == 0, just to be sure.
218
232
  # We automatically stop the playing if a model is reset.
@@ -223,31 +237,37 @@ def ModelController(model, play_interval, current_step, reset_counter):
223
237
  playing.value = False
224
238
 
225
239
  def do_step():
240
+ """Advance the model by one step."""
226
241
  model.step()
227
242
  previous_step.value = current_step.value
228
- current_step.value = model._steps
243
+ current_step.value = model.steps
229
244
 
230
245
  def do_play():
246
+ """Run the model continuously."""
231
247
  model.running = True
232
248
  while model.running:
233
249
  do_step()
234
250
 
235
251
  def threaded_do_play():
252
+ """Start a new thread for continuous model execution."""
236
253
  if thread is not None and thread.is_alive():
237
254
  return
238
255
  thread.value = threading.Thread(target=do_play)
239
256
  thread.start()
240
257
 
241
258
  def do_pause():
259
+ """Pause the model execution."""
242
260
  if (thread is None) or (not thread.is_alive()):
243
261
  return
244
262
  model.running = False
245
263
  thread.join()
246
264
 
247
265
  def do_reset():
266
+ """Reset the model."""
248
267
  reset_counter.value += 1
249
268
 
250
269
  def do_set_playing(value):
270
+ """Set the playing state."""
251
271
  if current_step.value == 0:
252
272
  # This means the model has been recreated, and the step resets to
253
273
  # 0. We want to avoid triggering the playing.value = False in the
@@ -291,6 +311,14 @@ def ModelController(model, play_interval, current_step, reset_counter):
291
311
 
292
312
 
293
313
  def split_model_params(model_params):
314
+ """Split model parameters into user-adjustable and fixed parameters.
315
+
316
+ Args:
317
+ model_params: Dictionary of all model parameters
318
+
319
+ Returns:
320
+ tuple: (user_adjustable_params, fixed_params)
321
+ """
294
322
  model_params_input = {}
295
323
  model_params_fixed = {}
296
324
  for k, v in model_params.items():
@@ -302,6 +330,14 @@ def split_model_params(model_params):
302
330
 
303
331
 
304
332
  def check_param_is_fixed(param):
333
+ """Check if a parameter is fixed (not user-adjustable).
334
+
335
+ Args:
336
+ param: Parameter to check
337
+
338
+ Returns:
339
+ bool: True if parameter is fixed, False otherwise
340
+ """
305
341
  if isinstance(param, Slider):
306
342
  return False
307
343
  if not isinstance(param, dict):
@@ -313,15 +349,15 @@ def check_param_is_fixed(param):
313
349
  @solara.component
314
350
  def UserInputs(user_params, on_change=None):
315
351
  """Initialize user inputs for configurable model parameters.
352
+
316
353
  Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
317
354
  :class:`solara.Select`, and :class:`solara.Checkbox`.
318
355
 
319
- Props:
320
- user_params: dictionary with options for the input, including label,
356
+ Args:
357
+ user_params: Dictionary with options for the input, including label,
321
358
  min and max values, and other fields specific to the input type.
322
- on_change: function to be called with (name, value) when the value of an input changes.
359
+ on_change: Function to be called with (name, value) when the value of an input changes.
323
360
  """
324
-
325
361
  for name, options in user_params.items():
326
362
 
327
363
  def change_handler(value, name=name):
@@ -380,6 +416,15 @@ def UserInputs(user_params, on_change=None):
380
416
 
381
417
 
382
418
  def make_text(renderer):
419
+ """Create a function that renders text using Markdown.
420
+
421
+ Args:
422
+ renderer: Function that takes a model and returns a string
423
+
424
+ Returns:
425
+ function: A function that renders the text as Markdown
426
+ """
427
+
383
428
  def function(model):
384
429
  solara.Markdown(renderer(model))
385
430
 
@@ -387,6 +432,14 @@ def make_text(renderer):
387
432
 
388
433
 
389
434
  def make_initial_grid_layout(layout_types):
435
+ """Create an initial grid layout for visualization components.
436
+
437
+ Args:
438
+ layout_types: List of layout types (Space or Measure)
439
+
440
+ Returns:
441
+ list: Initial grid layout configuration
442
+ """
390
443
  return [
391
444
  {
392
445
  "i": i,