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.
- mesa/__init__.py +3 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/visualization/solara_viz.py +266 -267
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/METADATA +13 -65
- mesa-3.0.0a1.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a1.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/solara_viz.py +0 -453
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/entry_points.txt +0 -0
mesa/visualization/solara_viz.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""
|
|
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
|
-
|
|
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.
|
|
28
|
+
import reacton.ipywidgets as widgets
|
|
32
29
|
import solara
|
|
30
|
+
from solara.alias import rv
|
|
33
31
|
|
|
34
|
-
import mesa.visualization.components.
|
|
35
|
-
|
|
36
|
-
from mesa.visualization.
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
"""
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
117
|
+
current_step = solara.use_reactive(0)
|
|
109
118
|
|
|
110
|
-
#
|
|
111
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
141
|
-
def
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
193
|
-
if
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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.
|
|
209
|
-
|
|
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
|
|
212
|
-
"""
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
218
|
-
"""
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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,
|
|
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
|
|
433
|
-
"""
|
|
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
|
-
|
|
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(
|
|
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}")
|