Mesa 2.4.0__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 +105 -92
  3. mesa/batchrunner.py +55 -31
  4. mesa/datacollection.py +10 -14
  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 +69 -38
  75. mesa/experimental/devs/examples/wolf_sheep.py +42 -43
  76. mesa/experimental/devs/simulator.py +57 -16
  77. mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -99
  78. mesa/model.py +136 -78
  79. mesa/space.py +208 -148
  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.4.0.dist-info → mesa-3.0.0.dist-info}/METADATA +62 -17
  90. mesa-3.0.0.dist-info/RECORD +95 -0
  91. mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
  92. mesa-2.4.0.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.4.0.dist-info/RECORD +0 -45
  108. /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
  109. {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
  110. {mesa-2.4.0.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,96 +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
- if measures:
147
- for measure in measures:
148
- if callable(measure):
149
- # Is a custom object
150
- measure(model)
151
- else:
152
- components_matplotlib.PlotMatplotlib(
153
- model, measure, dependencies=dependencies
154
- )
155
-
156
- def render_in_browser():
157
- # if space drawer is disabled, do not include it
158
- layout_types = [{"Space": "default"}] if space_drawer else []
159
-
160
- if measures:
161
- layout_types += [{"Measure": elem} for elem in range(len(measures))]
162
-
163
- grid_layout_initial = make_initial_grid_layout(layout_types=layout_types)
164
- grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
165
-
166
- with solara.Sidebar():
167
- with solara.Card("Controls", margin=1, elevation=2):
168
- solara.InputText(
169
- label="Seed",
170
- value=reactive_seed,
171
- continuous_update=True,
172
- )
173
- UserInputs(user_params, on_change=handle_change_model_params)
174
- ModelController(model, play_interval, current_step, reset_counter)
175
- solara.Button(label="Reseed", color="primary", on_click=do_reseed)
176
- with solara.Card("Information", margin=1, elevation=2):
177
- solara.Markdown(md_text=f"Step - {current_step}")
178
-
179
- items = [
180
- Card(
181
- model,
182
- measures,
183
- agent_portrayal,
184
- space_drawer,
185
- dependencies,
186
- color="white",
187
- 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,
188
179
  )
189
- for i in range(len(layout_types))
190
- ]
191
- solara.GridDraggable(
192
- items=items,
193
- grid_layout=grid_layout,
194
- resizable=True,
195
- draggable=True,
196
- 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],
197
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
+ )
198
205
 
199
- if ("ipykernel" in sys.argv[0]) or ("colab_kernel_launcher.py" in sys.argv[0]):
200
- # When in Jupyter or Google Colab
201
- render_in_jupyter()
202
- else:
203
- render_in_browser()
206
+
207
+ JupyterViz = SolaraViz
204
208
 
205
209
 
206
210
  @solara.component
207
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
+ """
208
220
  playing = solara.use_reactive(False)
209
221
  thread = solara.use_reactive(None)
210
222
  # We track the previous step to detect if user resets the model via
@@ -214,6 +226,7 @@ def ModelController(model, play_interval, current_step, reset_counter):
214
226
  previous_step = solara.use_reactive(0)
215
227
 
216
228
  def on_value_play(change):
229
+ """Handle play/pause state changes."""
217
230
  if previous_step.value > current_step.value and current_step.value == 0:
218
231
  # We add extra checks for current_step.value == 0, just to be sure.
219
232
  # We automatically stop the playing if a model is reset.
@@ -224,31 +237,37 @@ def ModelController(model, play_interval, current_step, reset_counter):
224
237
  playing.value = False
225
238
 
226
239
  def do_step():
240
+ """Advance the model by one step."""
227
241
  model.step()
228
242
  previous_step.value = current_step.value
229
- current_step.value = model._steps
243
+ current_step.value = model.steps
230
244
 
231
245
  def do_play():
246
+ """Run the model continuously."""
232
247
  model.running = True
233
248
  while model.running:
234
249
  do_step()
235
250
 
236
251
  def threaded_do_play():
252
+ """Start a new thread for continuous model execution."""
237
253
  if thread is not None and thread.is_alive():
238
254
  return
239
255
  thread.value = threading.Thread(target=do_play)
240
256
  thread.start()
241
257
 
242
258
  def do_pause():
259
+ """Pause the model execution."""
243
260
  if (thread is None) or (not thread.is_alive()):
244
261
  return
245
262
  model.running = False
246
263
  thread.join()
247
264
 
248
265
  def do_reset():
266
+ """Reset the model."""
249
267
  reset_counter.value += 1
250
268
 
251
269
  def do_set_playing(value):
270
+ """Set the playing state."""
252
271
  if current_step.value == 0:
253
272
  # This means the model has been recreated, and the step resets to
254
273
  # 0. We want to avoid triggering the playing.value = False in the
@@ -292,6 +311,14 @@ def ModelController(model, play_interval, current_step, reset_counter):
292
311
 
293
312
 
294
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
+ """
295
322
  model_params_input = {}
296
323
  model_params_fixed = {}
297
324
  for k, v in model_params.items():
@@ -303,6 +330,14 @@ def split_model_params(model_params):
303
330
 
304
331
 
305
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
+ """
306
341
  if isinstance(param, Slider):
307
342
  return False
308
343
  if not isinstance(param, dict):
@@ -314,15 +349,15 @@ def check_param_is_fixed(param):
314
349
  @solara.component
315
350
  def UserInputs(user_params, on_change=None):
316
351
  """Initialize user inputs for configurable model parameters.
352
+
317
353
  Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
318
354
  :class:`solara.Select`, and :class:`solara.Checkbox`.
319
355
 
320
- Props:
321
- user_params: dictionary with options for the input, including label,
356
+ Args:
357
+ user_params: Dictionary with options for the input, including label,
322
358
  min and max values, and other fields specific to the input type.
323
- 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.
324
360
  """
325
-
326
361
  for name, options in user_params.items():
327
362
 
328
363
  def change_handler(value, name=name):
@@ -381,6 +416,15 @@ def UserInputs(user_params, on_change=None):
381
416
 
382
417
 
383
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
+
384
428
  def function(model):
385
429
  solara.Markdown(renderer(model))
386
430
 
@@ -388,6 +432,14 @@ def make_text(renderer):
388
432
 
389
433
 
390
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
+ """
391
443
  return [
392
444
  {
393
445
  "i": i,