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.
- mesa/__init__.py +3 -5
- mesa/agent.py +393 -116
- mesa/batchrunner.py +58 -31
- mesa/datacollection.py +141 -30
- mesa/examples/README.md +37 -0
- mesa/examples/__init__.py +21 -0
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
- mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +164 -0
- mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
- mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
- mesa/examples/advanced/pd_grid/Readme.md +43 -0
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +50 -0
- mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
- mesa/examples/advanced/pd_grid/app.py +54 -0
- mesa/examples/advanced/pd_grid/model.py +71 -0
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
- mesa/examples/advanced/sugarscape_g1mt/app.py +62 -0
- mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
- mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
- mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +102 -0
- mesa/examples/advanced/wolf_sheep/app.py +84 -0
- mesa/examples/advanced/wolf_sheep/model.py +137 -0
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +22 -0
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +71 -0
- mesa/examples/basic/boid_flockers/app.py +58 -0
- mesa/examples/basic/boid_flockers/model.py +69 -0
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
- mesa/examples/basic/boltzmann_wealth_model/app.py +74 -0
- mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
- mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +47 -0
- mesa/examples/basic/conways_game_of_life/app.py +51 -0
- mesa/examples/basic/conways_game_of_life/model.py +31 -0
- mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
- mesa/examples/basic/schelling/Readme.md +40 -0
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +26 -0
- mesa/examples/basic/schelling/analysis.ipynb +205 -0
- mesa/examples/basic/schelling/app.py +42 -0
- mesa/examples/basic/schelling/model.py +59 -0
- mesa/examples/basic/virus_on_network/Readme.md +61 -0
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +69 -0
- mesa/examples/basic/virus_on_network/app.py +114 -0
- mesa/examples/basic/virus_on_network/model.py +96 -0
- mesa/experimental/UserParam.py +18 -7
- mesa/experimental/__init__.py +10 -2
- mesa/experimental/cell_space/__init__.py +16 -1
- mesa/experimental/cell_space/cell.py +93 -23
- mesa/experimental/cell_space/cell_agent.py +117 -21
- mesa/experimental/cell_space/cell_collection.py +56 -19
- mesa/experimental/cell_space/discrete_space.py +92 -8
- mesa/experimental/cell_space/grid.py +33 -9
- mesa/experimental/cell_space/network.py +15 -10
- mesa/experimental/cell_space/voronoi.py +257 -0
- mesa/experimental/components/altair.py +11 -2
- mesa/experimental/components/matplotlib.py +132 -26
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +54 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +57 -16
- mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -98
- mesa/model.py +212 -84
- mesa/space.py +217 -151
- mesa/time.py +63 -80
- mesa/visualization/__init__.py +25 -6
- mesa/visualization/components/__init__.py +83 -0
- mesa/visualization/components/altair_components.py +188 -0
- mesa/visualization/components/matplotlib_components.py +175 -0
- mesa/visualization/mpl_space_drawing.py +593 -0
- mesa/visualization/solara_viz.py +458 -0
- mesa/visualization/user_param.py +69 -0
- mesa/visualization/utils.py +9 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/METADATA +65 -19
- mesa-3.0.0.dist-info/RECORD +95 -0
- mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
- mesa-2.3.4.dist-info/licenses/LICENSE → mesa-3.0.0.dist-info/licenses/NOTICE +2 -2
- mesa/cookiecutter-mesa/cookiecutter.json +0 -8
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
- mesa/flat/__init__.py +0 -6
- mesa/flat/visualization.py +0 -5
- mesa/main.py +0 -63
- mesa/visualization/ModularVisualization.py +0 -1
- mesa/visualization/TextVisualization.py +0 -1
- mesa/visualization/UserParam.py +0 -1
- mesa/visualization/modules.py +0 -1
- mesa-2.3.4.dist-info/RECORD +0 -45
- /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,458 @@
|
|
|
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
|
+
|
|
11
|
+
The module uses Solara for rendering in Jupyter notebooks or as standalone web applications.
|
|
12
|
+
It supports various types of visualizations including matplotlib plots, agent grids, and
|
|
13
|
+
custom visualization components.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
1. Define an agent_portrayal function to specify how agents should be displayed
|
|
17
|
+
2. Set up model_params to define adjustable parameters
|
|
18
|
+
3. Create a SolaraViz instance with your model, parameters, and desired measures
|
|
19
|
+
4. Display the visualization in a Jupyter notebook or run as a Solara app
|
|
20
|
+
|
|
21
|
+
See the Visualization Tutorial and example models for more details.
|
|
22
|
+
"""
|
|
23
|
+
|
|
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
|
|
30
|
+
|
|
31
|
+
import reacton.core
|
|
32
|
+
import solara
|
|
33
|
+
|
|
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
|
|
40
|
+
|
|
41
|
+
|
|
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,
|
|
51
|
+
):
|
|
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.
|
|
57
|
+
|
|
58
|
+
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.
|
|
71
|
+
|
|
72
|
+
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.
|
|
85
|
+
"""
|
|
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
|
|
94
|
+
|
|
95
|
+
def connect_to_model():
|
|
96
|
+
# Patch the step function to force updates
|
|
97
|
+
original_step = model.value.step
|
|
98
|
+
|
|
99
|
+
def step():
|
|
100
|
+
original_step()
|
|
101
|
+
force_update()
|
|
102
|
+
|
|
103
|
+
model.value.step = step
|
|
104
|
+
# Add a trigger to model itself
|
|
105
|
+
model.value.force_update = force_update
|
|
106
|
+
force_update()
|
|
107
|
+
|
|
108
|
+
solara.use_effect(connect_to_model, [model.value])
|
|
109
|
+
|
|
110
|
+
# set up reactive model_parameters shared by ModelCreator and ModelController
|
|
111
|
+
reactive_model_parameters = solara.use_reactive({})
|
|
112
|
+
|
|
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)
|
|
129
|
+
|
|
130
|
+
ComponentsView(components, model.value)
|
|
131
|
+
|
|
132
|
+
|
|
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
|
|
139
|
+
|
|
140
|
+
@solara.component
|
|
141
|
+
def WrappedComponent(model):
|
|
142
|
+
update_counter.get()
|
|
143
|
+
return component(model)
|
|
144
|
+
|
|
145
|
+
return WrappedComponent
|
|
146
|
+
|
|
147
|
+
|
|
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.
|
|
155
|
+
|
|
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))
|
|
163
|
+
grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
|
|
164
|
+
solara.GridDraggable(
|
|
165
|
+
items=items,
|
|
166
|
+
grid_layout=grid_layout,
|
|
167
|
+
resizable=True,
|
|
168
|
+
draggable=True,
|
|
169
|
+
on_grid_layout=set_grid_layout,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
JupyterViz = SolaraViz
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@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).
|
|
184
|
+
|
|
185
|
+
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
|
+
|
|
190
|
+
"""
|
|
191
|
+
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)
|
|
200
|
+
do_step()
|
|
201
|
+
|
|
202
|
+
solara.lab.use_task(
|
|
203
|
+
step, dependencies=[playing.value, running.value], prefer_threaded=False
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def do_step():
|
|
207
|
+
"""Advance the model by one step."""
|
|
208
|
+
model.value.step()
|
|
209
|
+
running.value = model.value.running
|
|
210
|
+
|
|
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)
|
|
216
|
+
|
|
217
|
+
def do_play_pause():
|
|
218
|
+
"""Toggle play/pause."""
|
|
219
|
+
playing.value = not playing.value
|
|
220
|
+
|
|
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,
|
|
228
|
+
)
|
|
229
|
+
solara.Button(
|
|
230
|
+
label="Step",
|
|
231
|
+
color="primary",
|
|
232
|
+
on_click=do_step,
|
|
233
|
+
disabled=playing.value or not running.value,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def split_model_params(model_params):
|
|
238
|
+
"""Split model parameters into user-adjustable and fixed parameters.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
model_params: Dictionary of all model parameters
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
tuple: (user_adjustable_params, fixed_params)
|
|
245
|
+
"""
|
|
246
|
+
model_params_input = {}
|
|
247
|
+
model_params_fixed = {}
|
|
248
|
+
for k, v in model_params.items():
|
|
249
|
+
if check_param_is_fixed(v):
|
|
250
|
+
model_params_fixed[k] = v
|
|
251
|
+
else:
|
|
252
|
+
model_params_input[k] = v
|
|
253
|
+
return model_params_input, model_params_fixed
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def check_param_is_fixed(param):
|
|
257
|
+
"""Check if a parameter is fixed (not user-adjustable).
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
param: Parameter to check
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
bool: True if parameter is fixed, False otherwise
|
|
264
|
+
"""
|
|
265
|
+
if isinstance(param, Slider):
|
|
266
|
+
return False
|
|
267
|
+
if not isinstance(param, dict):
|
|
268
|
+
return True
|
|
269
|
+
if "type" not in param:
|
|
270
|
+
return True
|
|
271
|
+
|
|
272
|
+
|
|
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
|
+
@solara.component
|
|
359
|
+
def UserInputs(user_params, on_change=None):
|
|
360
|
+
"""Initialize user inputs for configurable model parameters.
|
|
361
|
+
|
|
362
|
+
Currently supports :class:`solara.SliderInt`, :class:`solara.SliderFloat`,
|
|
363
|
+
:class:`solara.Select`, and :class:`solara.Checkbox`.
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
user_params: Dictionary with options for the input, including label, min and max values, and other fields specific to the input type.
|
|
367
|
+
on_change: Function to be called with (name, value) when the value of an input changes.
|
|
368
|
+
"""
|
|
369
|
+
for name, options in user_params.items():
|
|
370
|
+
|
|
371
|
+
def change_handler(value, name=name):
|
|
372
|
+
on_change(name, value)
|
|
373
|
+
|
|
374
|
+
if isinstance(options, Slider):
|
|
375
|
+
slider_class = (
|
|
376
|
+
solara.SliderFloat if options.is_float_slider else solara.SliderInt
|
|
377
|
+
)
|
|
378
|
+
slider_class(
|
|
379
|
+
options.label,
|
|
380
|
+
value=options.value,
|
|
381
|
+
on_value=change_handler,
|
|
382
|
+
min=options.min,
|
|
383
|
+
max=options.max,
|
|
384
|
+
step=options.step,
|
|
385
|
+
)
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
# label for the input is "label" from options or name
|
|
389
|
+
label = options.get("label", name)
|
|
390
|
+
input_type = options.get("type")
|
|
391
|
+
if input_type == "SliderInt":
|
|
392
|
+
solara.SliderInt(
|
|
393
|
+
label,
|
|
394
|
+
value=options.get("value"),
|
|
395
|
+
on_value=change_handler,
|
|
396
|
+
min=options.get("min"),
|
|
397
|
+
max=options.get("max"),
|
|
398
|
+
step=options.get("step"),
|
|
399
|
+
)
|
|
400
|
+
elif input_type == "SliderFloat":
|
|
401
|
+
solara.SliderFloat(
|
|
402
|
+
label,
|
|
403
|
+
value=options.get("value"),
|
|
404
|
+
on_value=change_handler,
|
|
405
|
+
min=options.get("min"),
|
|
406
|
+
max=options.get("max"),
|
|
407
|
+
step=options.get("step"),
|
|
408
|
+
)
|
|
409
|
+
elif input_type == "Select":
|
|
410
|
+
solara.Select(
|
|
411
|
+
label,
|
|
412
|
+
value=options.get("value"),
|
|
413
|
+
on_value=change_handler,
|
|
414
|
+
values=options.get("values"),
|
|
415
|
+
)
|
|
416
|
+
elif input_type == "Checkbox":
|
|
417
|
+
solara.Checkbox(
|
|
418
|
+
label=label,
|
|
419
|
+
on_value=change_handler,
|
|
420
|
+
value=options.get("value"),
|
|
421
|
+
)
|
|
422
|
+
elif input_type == "InputText":
|
|
423
|
+
solara.InputText(
|
|
424
|
+
label=label,
|
|
425
|
+
on_value=change_handler,
|
|
426
|
+
value=options.get("value"),
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
raise ValueError(f"{input_type} is not a supported input type")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def make_initial_grid_layout(num_components):
|
|
433
|
+
"""Create an initial grid layout for visualization components.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
num_components: Number of components to display
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
list: Initial grid layout configuration
|
|
440
|
+
"""
|
|
441
|
+
return [
|
|
442
|
+
{
|
|
443
|
+
"i": i,
|
|
444
|
+
"w": 6,
|
|
445
|
+
"h": 10,
|
|
446
|
+
"moved": False,
|
|
447
|
+
"x": 6 * (i % 2),
|
|
448
|
+
"y": 16 * (i - i % 2),
|
|
449
|
+
}
|
|
450
|
+
for i in range(num_components)
|
|
451
|
+
]
|
|
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}")
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Solara visualization related helper classes."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UserParam:
|
|
5
|
+
"""UserParam."""
|
|
6
|
+
|
|
7
|
+
_ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
|
|
8
|
+
|
|
9
|
+
def maybe_raise_error(self, param_type, valid): # noqa: D102
|
|
10
|
+
if valid:
|
|
11
|
+
return
|
|
12
|
+
msg = self._ERROR_MESSAGE.format(param_type, self.label)
|
|
13
|
+
raise ValueError(msg)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Slider(UserParam):
|
|
17
|
+
"""A number-based slider input with settable increment.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
slider_option = Slider("My Slider", value=123, min=10, max=200, step=0.1)
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
label: The displayed label in the UI
|
|
24
|
+
value: The initial value of the slider
|
|
25
|
+
min: The minimum possible value of the slider
|
|
26
|
+
max: The maximum possible value of the slider
|
|
27
|
+
step: The step between min and max for a range of possible values
|
|
28
|
+
dtype: either int or float
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
label="",
|
|
34
|
+
value=None,
|
|
35
|
+
min=None,
|
|
36
|
+
max=None,
|
|
37
|
+
step=1,
|
|
38
|
+
dtype=None,
|
|
39
|
+
):
|
|
40
|
+
"""Initializes a slider.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
label: The displayed label in the UI
|
|
44
|
+
value: The initial value of the slider
|
|
45
|
+
min: The minimum possible value of the slider
|
|
46
|
+
max: The maximum possible value of the slider
|
|
47
|
+
step: The step between min and max for a range of possible values
|
|
48
|
+
dtype: either int or float
|
|
49
|
+
"""
|
|
50
|
+
self.label = label
|
|
51
|
+
self.value = value
|
|
52
|
+
self.min = min
|
|
53
|
+
self.max = max
|
|
54
|
+
self.step = step
|
|
55
|
+
|
|
56
|
+
# Validate option type to make sure values are supplied properly
|
|
57
|
+
valid = not (self.value is None or self.min is None or self.max is None)
|
|
58
|
+
self.maybe_raise_error("slider", valid)
|
|
59
|
+
|
|
60
|
+
if dtype is None:
|
|
61
|
+
self.is_float_slider = self._check_values_are_float(value, min, max, step)
|
|
62
|
+
else:
|
|
63
|
+
self.is_float_slider = dtype is float
|
|
64
|
+
|
|
65
|
+
def _check_values_are_float(self, value, min, max, step): # D103
|
|
66
|
+
return any(isinstance(n, float) for n in (value, min, max, step))
|
|
67
|
+
|
|
68
|
+
def get(self, attr): # noqa: D102
|
|
69
|
+
return getattr(self, attr)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Agent-based modeling (ABM) in Python
|
|
5
5
|
Project-URL: homepage, https://github.com/projectmesa/mesa
|
|
6
6
|
Project-URL: repository, https://github.com/projectmesa/mesa
|
|
7
7
|
Author-email: Project Mesa Team <projectmesa@googlegroups.com>
|
|
8
8
|
License: Apache 2.0
|
|
9
9
|
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE
|
|
10
11
|
Keywords: ABM,agent,based,model,modeling,multi-agent,simulation
|
|
11
12
|
Classifier: Development Status :: 3 - Alpha
|
|
12
13
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -14,36 +15,66 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
14
15
|
Classifier: Natural Language :: English
|
|
15
16
|
Classifier: Operating System :: OS Independent
|
|
16
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
22
|
Classifier: Topic :: Scientific/Engineering
|
|
22
23
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
24
|
Classifier: Topic :: Scientific/Engineering :: Artificial Life
|
|
24
|
-
Requires-Python: >=3.
|
|
25
|
-
Requires-Dist: click
|
|
26
|
-
Requires-Dist: cookiecutter
|
|
27
|
-
Requires-Dist: matplotlib
|
|
28
|
-
Requires-Dist: mesa-viz-tornado>=0.1.3,~=0.1.0
|
|
29
|
-
Requires-Dist: networkx
|
|
25
|
+
Requires-Python: >=3.10
|
|
30
26
|
Requires-Dist: numpy
|
|
31
27
|
Requires-Dist: pandas
|
|
32
|
-
Requires-Dist: solara
|
|
33
28
|
Requires-Dist: tqdm
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: ipython; extra == 'all'
|
|
31
|
+
Requires-Dist: matplotlib; extra == 'all'
|
|
32
|
+
Requires-Dist: myst-nb; extra == 'all'
|
|
33
|
+
Requires-Dist: myst-parser; extra == 'all'
|
|
34
|
+
Requires-Dist: networkx; extra == 'all'
|
|
35
|
+
Requires-Dist: pydata-sphinx-theme; extra == 'all'
|
|
36
|
+
Requires-Dist: pytest; extra == 'all'
|
|
37
|
+
Requires-Dist: pytest-cov; extra == 'all'
|
|
38
|
+
Requires-Dist: pytest-mock; extra == 'all'
|
|
39
|
+
Requires-Dist: ruff; extra == 'all'
|
|
40
|
+
Requires-Dist: scipy; extra == 'all'
|
|
41
|
+
Requires-Dist: seaborn; extra == 'all'
|
|
42
|
+
Requires-Dist: solara; extra == 'all'
|
|
43
|
+
Requires-Dist: sphinx; extra == 'all'
|
|
34
44
|
Provides-Extra: dev
|
|
45
|
+
Requires-Dist: matplotlib; extra == 'dev'
|
|
46
|
+
Requires-Dist: networkx; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
35
48
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
36
49
|
Requires-Dist: pytest-mock; extra == 'dev'
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
Requires-Dist:
|
|
50
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
51
|
+
Requires-Dist: solara; extra == 'dev'
|
|
39
52
|
Requires-Dist: sphinx; extra == 'dev'
|
|
40
53
|
Provides-Extra: docs
|
|
41
54
|
Requires-Dist: ipython; extra == 'docs'
|
|
55
|
+
Requires-Dist: matplotlib; extra == 'docs'
|
|
42
56
|
Requires-Dist: myst-nb; extra == 'docs'
|
|
43
57
|
Requires-Dist: myst-parser; extra == 'docs'
|
|
58
|
+
Requires-Dist: networkx; extra == 'docs'
|
|
44
59
|
Requires-Dist: pydata-sphinx-theme; extra == 'docs'
|
|
45
60
|
Requires-Dist: seaborn; extra == 'docs'
|
|
61
|
+
Requires-Dist: solara; extra == 'docs'
|
|
46
62
|
Requires-Dist: sphinx; extra == 'docs'
|
|
63
|
+
Provides-Extra: examples
|
|
64
|
+
Requires-Dist: matplotlib; extra == 'examples'
|
|
65
|
+
Requires-Dist: networkx; extra == 'examples'
|
|
66
|
+
Requires-Dist: pytest; extra == 'examples'
|
|
67
|
+
Requires-Dist: scipy; extra == 'examples'
|
|
68
|
+
Requires-Dist: solara; extra == 'examples'
|
|
69
|
+
Provides-Extra: network
|
|
70
|
+
Requires-Dist: networkx; extra == 'network'
|
|
71
|
+
Provides-Extra: rec
|
|
72
|
+
Requires-Dist: matplotlib; extra == 'rec'
|
|
73
|
+
Requires-Dist: networkx; extra == 'rec'
|
|
74
|
+
Requires-Dist: solara; extra == 'rec'
|
|
75
|
+
Provides-Extra: viz
|
|
76
|
+
Requires-Dist: matplotlib; extra == 'viz'
|
|
77
|
+
Requires-Dist: solara; extra == 'viz'
|
|
47
78
|
Description-Content-Type: text/markdown
|
|
48
79
|
|
|
49
80
|
# Mesa: Agent-based modeling in Python
|
|
@@ -55,8 +86,6 @@ Description-Content-Type: text/markdown
|
|
|
55
86
|
| Meta | [](https://github.com/astral-sh/ruff) [](https://github.com/psf/black) [](https://github.com/pypa/hatch) |
|
|
56
87
|
| Chat | [](https://matrix.to/#/#project-mesa:matrix.org) |
|
|
57
88
|
|
|
58
|
-
*This is the `2.3.x-maintenance` branch. Example models for Mesa 2.x can be found [here](https://github.com/projectmesa/mesa-examples/tree/mesa-2.x/examples).*
|
|
59
|
-
|
|
60
89
|
Mesa allows users to quickly create agent-based models using built-in
|
|
61
90
|
core components (such as spatial grids and agent schedulers) or
|
|
62
91
|
customized implementations; visualize them using a browser-based
|
|
@@ -78,13 +107,30 @@ can be displayed in browser windows or Jupyter.*
|
|
|
78
107
|
|
|
79
108
|
## Using Mesa
|
|
80
109
|
|
|
81
|
-
|
|
110
|
+
To install our latest stable release (3.0.x), run:
|
|
111
|
+
|
|
112
|
+
``` bash
|
|
113
|
+
pip install -U mesa
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
To install our latest pre-release, run:
|
|
82
117
|
|
|
83
118
|
``` bash
|
|
84
|
-
pip install mesa
|
|
119
|
+
pip install -U --pre mesa
|
|
120
|
+
```
|
|
121
|
+
Starting with Mesa 3.0, we don't install all our dependencies anymore by default.
|
|
122
|
+
```bash
|
|
123
|
+
# You can customize the additional dependencies you need, if you want. Available are:
|
|
124
|
+
pip install -U --pre mesa[network,viz]
|
|
125
|
+
|
|
126
|
+
# This is equivalent to our recommended dependencies:
|
|
127
|
+
pip install -U --pre mesa[rec]
|
|
128
|
+
|
|
129
|
+
# To install all, including developer, dependencies:
|
|
130
|
+
pip install -U --pre mesa[all]
|
|
85
131
|
```
|
|
86
132
|
|
|
87
|
-
You can also use `pip` to install the
|
|
133
|
+
You can also use `pip` to install the latest GitHub version:
|
|
88
134
|
|
|
89
135
|
``` bash
|
|
90
136
|
pip install -U -e git+https://github.com/projectmesa/mesa@main#egg=mesa
|
|
@@ -101,13 +147,13 @@ For resources or help on using Mesa, check out the following:
|
|
|
101
147
|
|
|
102
148
|
- [Intro to Mesa Tutorial](http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html) (An introductory model, the Boltzmann
|
|
103
149
|
Wealth Model, for beginners or those new to Mesa.)
|
|
104
|
-
- [Visualization Tutorial](https://mesa.readthedocs.io/
|
|
150
|
+
- [Visualization Tutorial](https://mesa.readthedocs.io/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
|
|
105
151
|
- [Complexity Explorer Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) (An advanced-beginner model,
|
|
106
152
|
SugarScape with Traders, with instructional videos)
|
|
107
|
-
- [Mesa Examples](https://github.com/projectmesa/mesa-examples
|
|
153
|
+
- [Mesa Examples](https://github.com/projectmesa/mesa-examples) (A repository of seminal ABMs using Mesa and
|
|
108
154
|
examples of employing specific Mesa Features)
|
|
109
155
|
- [Docs](http://mesa.readthedocs.org/) (Mesa's documentation, API and useful snippets)
|
|
110
|
-
- [Development version docs](https://mesa.readthedocs.io/
|
|
156
|
+
- [Development version docs](https://mesa.readthedocs.io/latest/) (the latest version docs if you're using a pre-release Mesa version)
|
|
111
157
|
- [Discussions](https://github.com/projectmesa/mesa/discussions) (GitHub threaded discussions about Mesa)
|
|
112
158
|
- [Matrix Chat](https://matrix.to/#/#project-mesa:matrix.org) (Chat Forum via Matrix to talk about Mesa)
|
|
113
159
|
|