Mesa 3.0.0__py3-none-any.whl → 3.0.0a1__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 (104) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +114 -406
  3. mesa/batchrunner.py +27 -54
  4. mesa/cookiecutter-mesa/cookiecutter.json +8 -0
  5. mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
  6. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
  7. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  8. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
  9. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
  10. mesa/datacollection.py +29 -140
  11. mesa/experimental/__init__.py +1 -11
  12. mesa/experimental/cell_space/__init__.py +1 -16
  13. mesa/experimental/cell_space/cell.py +23 -93
  14. mesa/experimental/cell_space/cell_agent.py +21 -117
  15. mesa/experimental/cell_space/cell_collection.py +17 -54
  16. mesa/experimental/cell_space/discrete_space.py +8 -92
  17. mesa/experimental/cell_space/grid.py +8 -32
  18. mesa/experimental/cell_space/network.py +7 -12
  19. mesa/experimental/devs/__init__.py +0 -2
  20. mesa/experimental/devs/eventlist.py +14 -52
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
  22. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  23. mesa/experimental/devs/simulator.py +15 -55
  24. mesa/main.py +63 -0
  25. mesa/model.py +83 -211
  26. mesa/space.py +149 -215
  27. mesa/time.py +77 -62
  28. mesa/{experimental → visualization}/UserParam.py +6 -17
  29. mesa/visualization/__init__.py +2 -25
  30. mesa/{experimental → visualization}/components/altair.py +0 -10
  31. mesa/visualization/components/matplotlib.py +134 -0
  32. mesa/visualization/solara_viz.py +266 -267
  33. {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/METADATA +13 -65
  34. mesa-3.0.0a1.dist-info/RECORD +38 -0
  35. mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a1.dist-info/licenses/LICENSE +2 -2
  36. mesa/examples/README.md +0 -37
  37. mesa/examples/__init__.py +0 -21
  38. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
  39. mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
  40. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  41. mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
  42. mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
  43. mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
  44. mesa/examples/advanced/pd_grid/Readme.md +0 -43
  45. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  46. mesa/examples/advanced/pd_grid/agents.py +0 -50
  47. mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
  48. mesa/examples/advanced/pd_grid/app.py +0 -54
  49. mesa/examples/advanced/pd_grid/model.py +0 -71
  50. mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
  51. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  52. mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
  53. mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
  54. mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
  55. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
  56. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  57. mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
  58. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  59. mesa/examples/advanced/wolf_sheep/agents.py +0 -102
  60. mesa/examples/advanced/wolf_sheep/app.py +0 -84
  61. mesa/examples/advanced/wolf_sheep/model.py +0 -137
  62. mesa/examples/basic/__init__.py +0 -0
  63. mesa/examples/basic/boid_flockers/Readme.md +0 -22
  64. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  65. mesa/examples/basic/boid_flockers/agents.py +0 -71
  66. mesa/examples/basic/boid_flockers/app.py +0 -58
  67. mesa/examples/basic/boid_flockers/model.py +0 -69
  68. mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
  69. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  70. mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
  71. mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
  72. mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
  73. mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
  74. mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
  75. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  76. mesa/examples/basic/conways_game_of_life/agents.py +0 -47
  77. mesa/examples/basic/conways_game_of_life/app.py +0 -51
  78. mesa/examples/basic/conways_game_of_life/model.py +0 -31
  79. mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
  80. mesa/examples/basic/schelling/Readme.md +0 -40
  81. mesa/examples/basic/schelling/__init__.py +0 -0
  82. mesa/examples/basic/schelling/agents.py +0 -26
  83. mesa/examples/basic/schelling/analysis.ipynb +0 -205
  84. mesa/examples/basic/schelling/app.py +0 -42
  85. mesa/examples/basic/schelling/model.py +0 -59
  86. mesa/examples/basic/virus_on_network/Readme.md +0 -61
  87. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  88. mesa/examples/basic/virus_on_network/agents.py +0 -69
  89. mesa/examples/basic/virus_on_network/app.py +0 -114
  90. mesa/examples/basic/virus_on_network/model.py +0 -96
  91. mesa/experimental/cell_space/voronoi.py +0 -257
  92. mesa/experimental/components/matplotlib.py +0 -242
  93. mesa/experimental/solara_viz.py +0 -453
  94. mesa/visualization/components/__init__.py +0 -83
  95. mesa/visualization/components/altair_components.py +0 -188
  96. mesa/visualization/components/matplotlib_components.py +0 -175
  97. mesa/visualization/mpl_space_drawing.py +0 -593
  98. mesa/visualization/user_param.py +0 -69
  99. mesa/visualization/utils.py +0 -9
  100. mesa-3.0.0.dist-info/RECORD +0 -95
  101. mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
  102. /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
  103. {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/WHEEL +0 -0
  104. {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/entry_points.txt +0 -0
@@ -1,4 +1,5 @@
1
- """Mesa visualization module for creating interactive model visualizations.
1
+ """
2
+ Mesa visualization module for creating interactive model visualizations.
2
3
 
3
4
  This module provides components to create browser- and Jupyter notebook-based visualizations of
4
5
  Mesa models, allowing users to watch models run step-by-step and interact with model parameters.
@@ -7,6 +8,7 @@ Key features:
7
8
  - SolaraViz: Main component for creating visualizations, supporting grid displays and plots
8
9
  - ModelController: Handles model execution controls (step, play, pause, reset)
9
10
  - UserInputs: Generates UI elements for adjusting model parameters
11
+ - Card: Renders individual visualization elements (space, measures)
10
12
 
11
13
  The module uses Solara for rendering in Jupyter notebooks or as standalone web applications.
12
14
  It supports various types of visualizations including matplotlib plots, agent grids, and
@@ -21,146 +23,176 @@ Usage:
21
23
  See the Visualization Tutorial and example models for more details.
22
24
  """
23
25
 
24
- from __future__ import annotations
25
-
26
- import asyncio
27
- import inspect
28
- from collections.abc import Callable
29
- from typing import TYPE_CHECKING, Literal
26
+ import threading
30
27
 
31
- import reacton.core
28
+ import reacton.ipywidgets as widgets
32
29
  import solara
30
+ from solara.alias import rv
33
31
 
34
- import mesa.visualization.components.altair_components as components_altair
35
- from mesa.visualization.user_param import Slider
36
- from mesa.visualization.utils import force_update, update_counter
37
-
38
- if TYPE_CHECKING:
39
- from mesa.model import Model
32
+ import mesa.visualization.components.altair as components_altair
33
+ import mesa.visualization.components.matplotlib as components_matplotlib
34
+ from mesa.visualization.UserParam import Slider
40
35
 
41
36
 
42
- @solara.component
43
- def SolaraViz(
44
- model: Model | solara.Reactive[Model],
45
- components: list[reacton.core.Component]
46
- | list[Callable[[Model], reacton.core.Component]]
47
- | Literal["default"] = "default",
48
- play_interval: int = 100,
49
- model_params=None,
50
- name: str | None = None,
37
+ # TODO: Turn this function into a Solara component once the current_step.value
38
+ # dependency is passed to measure()
39
+ def Card(
40
+ model, measures, agent_portrayal, space_drawer, dependencies, color, layout_type
51
41
  ):
52
- """Solara visualization component.
53
-
54
- This component provides a visualization interface for a given model using Solara.
55
- It supports various visualization components and allows for interactive model
56
- stepping and parameter adjustments.
42
+ """
43
+ Create a card component for visualizing model space or measures.
57
44
 
58
45
  Args:
59
- model (Model | solara.Reactive[Model]): A Model instance or a reactive Model.
60
- This is the main model to be visualized. If a non-reactive model is provided,
61
- it will be converted to a reactive model.
62
- components (list[solara.component] | Literal["default"], optional): List of solara
63
- components or functions that return a solara component.
64
- These components are used to render different parts of the model visualization.
65
- Defaults to "default", which uses the default Altair space visualization.
66
- play_interval (int, optional): Interval for playing the model steps in milliseconds.
67
- This controls the speed of the model's automatic stepping. Defaults to 100 ms.
68
- model_params (dict, optional): Parameters for (re-)instantiating a model.
69
- Can include user-adjustable parameters and fixed parameters. Defaults to None.
70
- name (str | None, optional): Name of the visualization. Defaults to the models class name.
46
+ model: The Mesa model instance
47
+ measures: List of measures to be plotted
48
+ agent_portrayal: Function to define agent appearance
49
+ space_drawer: Method to render agent space
50
+ dependencies: List of dependencies for updating the visualization
51
+ color: Background color of the card
52
+ layout_type: Type of layout (Space or Measure)
71
53
 
72
54
  Returns:
73
- solara.component: A Solara component that renders the visualization interface for the model.
74
-
75
- Example:
76
- >>> model = MyModel()
77
- >>> page = SolaraViz(model)
78
- >>> page
79
-
80
- Notes:
81
- - The `model` argument can be either a direct model instance or a reactive model. If a direct
82
- model instance is provided, it will be converted to a reactive model using `solara.use_reactive`.
83
- - The `play_interval` argument controls the speed of the model's automatic stepping. A lower
84
- value results in faster stepping, while a higher value results in slower stepping.
55
+ rv.Card: A card component containing the visualization
85
56
  """
86
- if components == "default":
87
- components = [components_altair.make_altair_space()]
88
- if model_params is None:
89
- model_params = {}
90
-
91
- # Convert model to reactive
92
- if not isinstance(model, solara.Reactive):
93
- model = solara.use_reactive(model) # noqa: SH102, RUF100
57
+ with rv.Card(
58
+ style_=f"background-color: {color}; width: 100%; height: 100%"
59
+ ) as main:
60
+ if "Space" in layout_type:
61
+ rv.CardTitle(children=["Space"])
62
+ if space_drawer == "default":
63
+ # draw with the default implementation
64
+ components_matplotlib.SpaceMatplotlib(
65
+ model, agent_portrayal, dependencies=dependencies
66
+ )
67
+ elif space_drawer == "altair":
68
+ components_altair.SpaceAltair(
69
+ model, agent_portrayal, dependencies=dependencies
70
+ )
71
+ elif space_drawer:
72
+ # if specified, draw agent space with an alternate renderer
73
+ space_drawer(model, agent_portrayal)
74
+ elif "Measure" in layout_type:
75
+ rv.CardTitle(children=["Measure"])
76
+ measure = measures[layout_type["Measure"]]
77
+ if callable(measure):
78
+ # Is a custom object
79
+ measure(model)
80
+ else:
81
+ components_matplotlib.PlotMatplotlib(
82
+ model, measure, dependencies=dependencies
83
+ )
84
+ return main
94
85
 
95
- def connect_to_model():
96
- # Patch the step function to force updates
97
- original_step = model.value.step
98
86
 
99
- def step():
100
- original_step()
101
- force_update()
87
+ @solara.component
88
+ def SolaraViz(
89
+ model_class,
90
+ model_params,
91
+ measures=None,
92
+ name=None,
93
+ agent_portrayal=None,
94
+ space_drawer="default",
95
+ play_interval=150,
96
+ seed=None,
97
+ ):
98
+ """
99
+ Initialize a component to visualize a model.
102
100
 
103
- model.value.step = step
104
- # Add a trigger to model itself
105
- model.value.force_update = force_update
106
- force_update()
101
+ Args:
102
+ model_class: Class of the model to instantiate
103
+ model_params: Parameters for initializing the model
104
+ measures: List of callables or data attributes to plot
105
+ name: Name for display
106
+ agent_portrayal: Options for rendering agents (dictionary)
107
+ space_drawer: Method to render the agent space for
108
+ the model; default implementation is the `SpaceMatplotlib` component;
109
+ simulations with no space to visualize should
110
+ specify `space_drawer=False`
111
+ play_interval: Play interval (default: 150)
112
+ seed: The random seed used to initialize the model
113
+ """
114
+ if name is None:
115
+ name = model_class.__name__
107
116
 
108
- solara.use_effect(connect_to_model, [model.value])
117
+ current_step = solara.use_reactive(0)
109
118
 
110
- # set up reactive model_parameters shared by ModelCreator and ModelController
111
- reactive_model_parameters = solara.use_reactive({})
119
+ # 1. Set up model parameters
120
+ reactive_seed = solara.use_reactive(0)
121
+ user_params, fixed_params = split_model_params(model_params)
122
+ model_parameters, set_model_parameters = solara.use_state(
123
+ {**fixed_params, **{k: v.get("value") for k, v in user_params.items()}}
124
+ )
112
125
 
113
- with solara.AppBar():
114
- solara.AppBarTitle(name if name else model.value.__class__.__name__)
115
-
116
- with solara.Sidebar(), solara.Column():
117
- with solara.Card("Controls"):
118
- ModelController(
119
- model,
120
- model_parameters=reactive_model_parameters,
121
- play_interval=play_interval,
122
- )
123
- with solara.Card("Model Parameters"):
124
- ModelCreator(
125
- model, model_params, model_parameters=reactive_model_parameters
126
- )
127
- with solara.Card("Information"):
128
- ShowSteps(model.value)
126
+ # 2. Set up Model
127
+ def make_model():
128
+ """Create a new model instance with current parameters and seed."""
129
+ model = model_class.__new__(
130
+ model_class, **model_parameters, seed=reactive_seed.value
131
+ )
132
+ model.__init__(**model_parameters)
133
+ current_step.value = 0
134
+ return model
135
+
136
+ reset_counter = solara.use_reactive(0)
137
+ model = solara.use_memo(
138
+ make_model,
139
+ dependencies=[
140
+ *list(model_parameters.values()),
141
+ reset_counter.value,
142
+ reactive_seed.value,
143
+ ],
144
+ )
129
145
 
130
- ComponentsView(components, model.value)
146
+ def handle_change_model_params(name: str, value: any):
147
+ """Update model parameters when user input changes."""
148
+ set_model_parameters({**model_parameters, name: value})
131
149
 
150
+ # 3. Set up UI
132
151
 
133
- def _wrap_component(
134
- component: reacton.core.Component | Callable[[Model], reacton.core.Component],
135
- ) -> reacton.core.Component:
136
- """Wrap a component in an auto-updated Solara component if needed."""
137
- if isinstance(component, reacton.core.Component):
138
- return component
152
+ with solara.AppBar():
153
+ solara.AppBarTitle(name)
139
154
 
140
- @solara.component
141
- def WrappedComponent(model):
142
- update_counter.get()
143
- return component(model)
155
+ # render layout and plot
156
+ def do_reseed():
157
+ """Update the random seed for the model."""
158
+ reactive_seed.value = model.random.random()
144
159
 
145
- return WrappedComponent
160
+ dependencies = [current_step.value, reactive_seed.value]
146
161
 
162
+ # if space drawer is disabled, do not include it
163
+ layout_types = [{"Space": "default"}] if space_drawer else []
147
164
 
148
- @solara.component
149
- def ComponentsView(
150
- components: list[reacton.core.Component]
151
- | list[Callable[[Model], reacton.core.Component]],
152
- model: Model,
153
- ):
154
- """Display a list of components.
165
+ if measures:
166
+ layout_types += [{"Measure": elem} for elem in range(len(measures))]
155
167
 
156
- Args:
157
- components: List of components to display
158
- model: Model instance to pass to each component
159
- """
160
- wrapped_components = [_wrap_component(component) for component in components]
161
- items = [component(model) for component in wrapped_components]
162
- grid_layout_initial = make_initial_grid_layout(num_components=len(items))
168
+ grid_layout_initial = make_initial_grid_layout(layout_types=layout_types)
163
169
  grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
170
+
171
+ with solara.Sidebar():
172
+ with solara.Card("Controls", margin=1, elevation=2):
173
+ solara.InputText(
174
+ label="Seed",
175
+ value=reactive_seed,
176
+ continuous_update=True,
177
+ )
178
+ UserInputs(user_params, on_change=handle_change_model_params)
179
+ ModelController(model, play_interval, current_step, reset_counter)
180
+ solara.Button(label="Reseed", color="primary", on_click=do_reseed)
181
+ with solara.Card("Information", margin=1, elevation=2):
182
+ solara.Markdown(md_text=f"Step - {current_step}")
183
+
184
+ items = [
185
+ Card(
186
+ model,
187
+ measures,
188
+ agent_portrayal,
189
+ space_drawer,
190
+ dependencies,
191
+ color="white",
192
+ layout_type=layout_types[i],
193
+ )
194
+ for i in range(len(layout_types))
195
+ ]
164
196
  solara.GridDraggable(
165
197
  items=items,
166
198
  grid_layout=grid_layout,
@@ -174,68 +206,112 @@ JupyterViz = SolaraViz
174
206
 
175
207
 
176
208
  @solara.component
177
- def ModelController(
178
- model: solara.Reactive[Model],
179
- *,
180
- model_parameters: dict | solara.Reactive[dict] = None,
181
- play_interval: int = 100,
182
- ):
183
- """Create controls for model execution (step, play, pause, reset).
209
+ def ModelController(model, play_interval, current_step, reset_counter):
210
+ """
211
+ Create controls for model execution (step, play, pause, reset).
184
212
 
185
213
  Args:
186
- model: Reactive model instance
187
- model_parameters: Reactive parameters for (re-)instantiating a model.
188
- play_interval: Interval for playing the model steps in milliseconds.
189
-
214
+ model: The model being visualized
215
+ play_interval: Interval between steps during play
216
+ current_step: Reactive value for the current step
217
+ reset_counter: Counter to trigger model reset
190
218
  """
191
219
  playing = solara.use_reactive(False)
192
- running = solara.use_reactive(True)
193
- if model_parameters is None:
194
- model_parameters = {}
195
- model_parameters = solara.use_reactive(model_parameters)
196
-
197
- async def step():
198
- while playing.value and running.value:
199
- await asyncio.sleep(play_interval / 1000)
220
+ thread = solara.use_reactive(None)
221
+ # We track the previous step to detect if user resets the model via
222
+ # clicking the reset button or changing the parameters. If previous_step >
223
+ # current_step, it means a model reset happens while the simulation is
224
+ # still playing.
225
+ previous_step = solara.use_reactive(0)
226
+
227
+ def on_value_play(change):
228
+ """Handle play/pause state changes."""
229
+ if previous_step.value > current_step.value and current_step.value == 0:
230
+ # We add extra checks for current_step.value == 0, just to be sure.
231
+ # We automatically stop the playing if a model is reset.
232
+ playing.value = False
233
+ elif model.running:
200
234
  do_step()
201
-
202
- solara.lab.use_task(
203
- step, dependencies=[playing.value, running.value], prefer_threaded=False
204
- )
235
+ else:
236
+ playing.value = False
205
237
 
206
238
  def do_step():
207
239
  """Advance the model by one step."""
208
- model.value.step()
209
- running.value = model.value.running
240
+ model.step()
241
+ previous_step.value = current_step.value
242
+ current_step.value = model._steps
243
+
244
+ def do_play():
245
+ """Run the model continuously."""
246
+ model.running = True
247
+ while model.running:
248
+ do_step()
210
249
 
211
- def do_reset():
212
- """Reset the model to its initial state."""
213
- playing.value = False
214
- running.value = True
215
- model.value = model.value = model.value.__class__(**model_parameters.value)
250
+ def threaded_do_play():
251
+ """Start a new thread for continuous model execution."""
252
+ if thread is not None and thread.is_alive():
253
+ return
254
+ thread.value = threading.Thread(target=do_play)
255
+ thread.start()
216
256
 
217
- def do_play_pause():
218
- """Toggle play/pause."""
219
- playing.value = not playing.value
257
+ def do_pause():
258
+ """Pause the model execution."""
259
+ if (thread is None) or (not thread.is_alive()):
260
+ return
261
+ model.running = False
262
+ thread.join()
220
263
 
221
- with solara.Row(justify="space-between"):
222
- solara.Button(label="Reset", color="primary", on_click=do_reset)
223
- solara.Button(
224
- label="▶" if not playing.value else "❚❚",
225
- color="primary",
226
- on_click=do_play_pause,
227
- disabled=not running.value,
264
+ def do_reset():
265
+ """Reset the model."""
266
+ reset_counter.value += 1
267
+
268
+ def do_set_playing(value):
269
+ """Set the playing state."""
270
+ if current_step.value == 0:
271
+ # This means the model has been recreated, and the step resets to
272
+ # 0. We want to avoid triggering the playing.value = False in the
273
+ # on_value_play function.
274
+ previous_step.value = current_step.value
275
+ playing.set(value)
276
+
277
+ with solara.Row():
278
+ solara.Button(label="Step", color="primary", on_click=do_step)
279
+ # This style is necessary so that the play widget has almost the same
280
+ # height as typical Solara buttons.
281
+ solara.Style(
282
+ """
283
+ .widget-play {
284
+ height: 35px;
285
+ }
286
+ .widget-play button {
287
+ color: white;
288
+ background-color: #1976D2; // Solara blue color
289
+ }
290
+ """
228
291
  )
229
- solara.Button(
230
- label="Step",
231
- color="primary",
232
- on_click=do_step,
233
- disabled=playing.value or not running.value,
292
+ widgets.Play(
293
+ value=0,
294
+ interval=play_interval,
295
+ repeat=True,
296
+ show_repeat=False,
297
+ on_value=on_value_play,
298
+ playing=playing.value,
299
+ on_playing=do_set_playing,
234
300
  )
301
+ solara.Button(label="Reset", color="primary", on_click=do_reset)
302
+ # threaded_do_play is not used for now because it
303
+ # doesn't work in Google colab. We use
304
+ # ipywidgets.Play until it is fixed. The threading
305
+ # version is definite a much better implementation,
306
+ # if it works.
307
+ # solara.Button(label="▶", color="primary", on_click=viz.threaded_do_play)
308
+ # solara.Button(label="⏸︎", color="primary", on_click=viz.do_pause)
309
+ # solara.Button(label="Reset", color="primary", on_click=do_reset)
235
310
 
236
311
 
237
312
  def split_model_params(model_params):
238
- """Split model parameters into user-adjustable and fixed parameters.
313
+ """
314
+ Split model parameters into user-adjustable and fixed parameters.
239
315
 
240
316
  Args:
241
317
  model_params: Dictionary of all model parameters
@@ -254,7 +330,8 @@ def split_model_params(model_params):
254
330
 
255
331
 
256
332
  def check_param_is_fixed(param):
257
- """Check if a parameter is fixed (not user-adjustable).
333
+ """
334
+ Check if a parameter is fixed (not user-adjustable).
258
335
 
259
336
  Args:
260
337
  param: Parameter to check
@@ -270,102 +347,19 @@ def check_param_is_fixed(param):
270
347
  return True
271
348
 
272
349
 
273
- @solara.component
274
- def ModelCreator(
275
- model: solara.Reactive[Model],
276
- user_params: dict,
277
- *,
278
- model_parameters: dict | solara.Reactive[dict] = None,
279
- ):
280
- """Solara component for creating and managing a model instance with user-defined parameters.
281
-
282
- This component allows users to create a model instance with specified parameters and seed.
283
- It provides an interface for adjusting model parameters and reseeding the model's random
284
- number generator.
285
-
286
- Args:
287
- model: A reactive model instance. This is the main model to be created and managed.
288
- user_params: Parameters for (re-)instantiating a model. Can include user-adjustable parameters and fixed parameters. Defaults to None.
289
- model_parameters: reactive parameters for reinitializing the model
290
-
291
- Returns:
292
- solara.component: A Solara component that renders the model creation and management interface.
293
-
294
- Example:
295
- >>> model = solara.reactive(MyModel())
296
- >>> model_params = {
297
- >>> "param1": {"type": "slider", "value": 10, "min": 0, "max": 100},
298
- >>> "param2": {"type": "slider", "value": 5, "min": 1, "max": 10},
299
- >>> }
300
- >>> creator = ModelCreator(model, model_params)
301
- >>> creator
302
-
303
- Notes:
304
- - The `model_params` argument should be a dictionary where keys are parameter names and values either fixed values
305
- or are dictionaries containing parameter details such as type, value, min, and max.
306
- - The `seed` argument ensures reproducibility by setting the initial seed for the model's random number generator.
307
- - The component provides an interface for adjusting user-defined parameters and reseeding the model.
308
-
309
- """
310
- if model_parameters is None:
311
- model_parameters = {}
312
- model_parameters = solara.use_reactive(model_parameters)
313
-
314
- solara.use_effect(
315
- lambda: _check_model_params(model.value.__class__.__init__, fixed_params),
316
- [model.value],
317
- )
318
- user_params, fixed_params = split_model_params(user_params)
319
-
320
- # set model_parameters to the default values for all parameters
321
- model_parameters.value = {
322
- **fixed_params,
323
- **{k: v.get("value") for k, v in user_params.items()},
324
- }
325
-
326
- def on_change(name, value):
327
- new_model_parameters = {**model_parameters.value, name: value}
328
- model.value = model.value.__class__(**new_model_parameters)
329
- model_parameters.value = new_model_parameters
330
-
331
- UserInputs(user_params, on_change=on_change)
332
-
333
-
334
- def _check_model_params(init_func, model_params):
335
- """Check if model parameters are valid for the model's initialization function.
336
-
337
- Args:
338
- init_func: Model initialization function
339
- model_params: Dictionary of model parameters
340
-
341
- Raises:
342
- ValueError: If a parameter is not valid for the model's initialization function
343
- """
344
- model_parameters = inspect.signature(init_func).parameters
345
- for name in model_parameters:
346
- if (
347
- model_parameters[name].default == inspect.Parameter.empty
348
- and name not in model_params
349
- and name != "self"
350
- and name != "kwargs"
351
- ):
352
- raise ValueError(f"Missing required model parameter: {name}")
353
- for name in model_params:
354
- if name not in model_parameters and "kwargs" not in model_parameters:
355
- raise ValueError(f"Invalid model parameter: {name}")
356
-
357
-
358
350
  @solara.component
359
351
  def UserInputs(user_params, on_change=None):
360
- """Initialize user inputs for configurable model parameters.
361
-
352
+ """
353
+ Initialize user inputs for configurable model parameters.
362
354
  Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
363
355
  :class:`solara.Select`, and :class:`solara.Checkbox`.
364
356
 
365
357
  Args:
366
- user_params: Dictionary with options for the input, including label, min and max values, and other fields specific to the input type.
358
+ user_params: Dictionary with options for the input, including label,
359
+ min and max values, and other fields specific to the input type.
367
360
  on_change: Function to be called with (name, value) when the value of an input changes.
368
361
  """
362
+
369
363
  for name, options in user_params.items():
370
364
 
371
365
  def change_handler(value, name=name):
@@ -419,21 +413,33 @@ def UserInputs(user_params, on_change=None):
419
413
  on_value=change_handler,
420
414
  value=options.get("value"),
421
415
  )
422
- elif input_type == "InputText":
423
- solara.InputText(
424
- label=label,
425
- on_value=change_handler,
426
- value=options.get("value"),
427
- )
428
416
  else:
429
417
  raise ValueError(f"{input_type} is not a supported input type")
430
418
 
431
419
 
432
- def make_initial_grid_layout(num_components):
433
- """Create an initial grid layout for visualization components.
420
+ def make_text(renderer):
421
+ """
422
+ Create a function that renders text using Markdown.
423
+
424
+ Args:
425
+ renderer: Function that takes a model and returns a string
426
+
427
+ Returns:
428
+ function: A function that renders the text as Markdown
429
+ """
430
+
431
+ def function(model):
432
+ solara.Markdown(renderer(model))
433
+
434
+ return function
435
+
436
+
437
+ def make_initial_grid_layout(layout_types):
438
+ """
439
+ Create an initial grid layout for visualization components.
434
440
 
435
441
  Args:
436
- num_components: Number of components to display
442
+ layout_types: List of layout types (Space or Measure)
437
443
 
438
444
  Returns:
439
445
  list: Initial grid layout configuration
@@ -447,12 +453,5 @@ def make_initial_grid_layout(num_components):
447
453
  "x": 6 * (i % 2),
448
454
  "y": 16 * (i - i % 2),
449
455
  }
450
- for i in range(num_components)
456
+ for i in range(len(layout_types))
451
457
  ]
452
-
453
-
454
- @solara.component
455
- def ShowSteps(model):
456
- """Display the current step of the model."""
457
- update_counter.get()
458
- return solara.Text(f"Step: {model.steps}")