Mesa 3.0.0a2__py3-none-any.whl → 3.0.0a4__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 +1 -1
- mesa/agent.py +293 -43
- mesa/batchrunner.py +7 -4
- mesa/datacollection.py +2 -2
- mesa/experimental/UserParam.py +56 -0
- mesa/experimental/__init__.py +3 -1
- mesa/experimental/cell_space/__init__.py +2 -0
- mesa/experimental/cell_space/cell_agent.py +2 -2
- mesa/experimental/cell_space/voronoi.py +264 -0
- mesa/experimental/components/altair.py +71 -0
- mesa/experimental/components/matplotlib.py +224 -0
- mesa/experimental/devs/examples/epstein_civil_violence.py +6 -10
- mesa/experimental/devs/examples/wolf_sheep.py +7 -12
- mesa/experimental/solara_viz.py +462 -0
- mesa/model.py +108 -38
- mesa/space.py +9 -3
- mesa/time.py +0 -7
- mesa/visualization/__init__.py +13 -2
- mesa/visualization/components/altair.py +15 -0
- mesa/visualization/components/matplotlib.py +75 -0
- mesa/visualization/solara_viz.py +121 -185
- mesa/visualization/utils.py +7 -0
- {mesa-3.0.0a2.dist-info → mesa-3.0.0a4.dist-info}/METADATA +8 -1
- mesa-3.0.0a4.dist-info/RECORD +44 -0
- mesa-3.0.0a2.dist-info/RECORD +0 -38
- {mesa-3.0.0a2.dist-info → mesa-3.0.0a4.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a2.dist-info → mesa-3.0.0a4.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.0a2.dist-info → mesa-3.0.0a4.dist-info}/licenses/LICENSE +0 -0
mesa/time.py
CHANGED
|
@@ -69,8 +69,6 @@ class BaseScheduler:
|
|
|
69
69
|
self.model = model
|
|
70
70
|
self.steps = 0
|
|
71
71
|
self.time: TimeT = 0
|
|
72
|
-
self._original_step = self.step
|
|
73
|
-
self.step = self._wrapped_step
|
|
74
72
|
|
|
75
73
|
if agents is None:
|
|
76
74
|
agents = []
|
|
@@ -113,11 +111,6 @@ class BaseScheduler:
|
|
|
113
111
|
self.steps += 1
|
|
114
112
|
self.time += 1
|
|
115
113
|
|
|
116
|
-
def _wrapped_step(self):
|
|
117
|
-
"""Wrapper for the step method to include time and step updating."""
|
|
118
|
-
self._original_step()
|
|
119
|
-
self.model._advance_time()
|
|
120
|
-
|
|
121
114
|
def get_agent_count(self) -> int:
|
|
122
115
|
"""Returns the current number of agents in the queue."""
|
|
123
116
|
return len(self._agents)
|
mesa/visualization/__init__.py
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .components.altair import make_space_altair
|
|
2
|
+
from .components.matplotlib import make_plot_measure, make_space_matplotlib
|
|
3
|
+
from .solara_viz import JupyterViz, SolaraViz, make_text
|
|
4
|
+
from .UserParam import Slider
|
|
2
5
|
|
|
3
|
-
__all__ = [
|
|
6
|
+
__all__ = [
|
|
7
|
+
"JupyterViz",
|
|
8
|
+
"SolaraViz",
|
|
9
|
+
"make_text",
|
|
10
|
+
"Slider",
|
|
11
|
+
"make_space_altair",
|
|
12
|
+
"make_space_matplotlib",
|
|
13
|
+
"make_plot_measure",
|
|
14
|
+
]
|
|
@@ -5,9 +5,24 @@ import solara
|
|
|
5
5
|
with contextlib.suppress(ImportError):
|
|
6
6
|
import altair as alt
|
|
7
7
|
|
|
8
|
+
from mesa.visualization.utils import update_counter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def make_space_altair(agent_portrayal=None):
|
|
12
|
+
if agent_portrayal is None:
|
|
13
|
+
|
|
14
|
+
def agent_portrayal(a):
|
|
15
|
+
return {"id": a.unique_id}
|
|
16
|
+
|
|
17
|
+
def MakeSpaceAltair(model):
|
|
18
|
+
return SpaceAltair(model, agent_portrayal)
|
|
19
|
+
|
|
20
|
+
return MakeSpaceAltair
|
|
21
|
+
|
|
8
22
|
|
|
9
23
|
@solara.component
|
|
10
24
|
def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
25
|
+
update_counter.get()
|
|
11
26
|
space = getattr(model, "grid", None)
|
|
12
27
|
if space is None:
|
|
13
28
|
# Sometimes the space is defined as model.space instead of model.grid
|
|
@@ -6,10 +6,25 @@ from matplotlib.figure import Figure
|
|
|
6
6
|
from matplotlib.ticker import MaxNLocator
|
|
7
7
|
|
|
8
8
|
import mesa
|
|
9
|
+
from mesa.experimental.cell_space import VoronoiGrid
|
|
10
|
+
from mesa.visualization.utils import update_counter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_space_matplotlib(agent_portrayal=None):
|
|
14
|
+
if agent_portrayal is None:
|
|
15
|
+
|
|
16
|
+
def agent_portrayal(a):
|
|
17
|
+
return {"id": a.unique_id}
|
|
18
|
+
|
|
19
|
+
def MakeSpaceMatplotlib(model):
|
|
20
|
+
return SpaceMatplotlib(model, agent_portrayal)
|
|
21
|
+
|
|
22
|
+
return MakeSpaceMatplotlib
|
|
9
23
|
|
|
10
24
|
|
|
11
25
|
@solara.component
|
|
12
26
|
def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
27
|
+
update_counter.get()
|
|
13
28
|
space_fig = Figure()
|
|
14
29
|
space_ax = space_fig.subplots()
|
|
15
30
|
space = getattr(model, "grid", None)
|
|
@@ -20,6 +35,8 @@ def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = Non
|
|
|
20
35
|
_draw_network_grid(space, space_ax, agent_portrayal)
|
|
21
36
|
elif isinstance(space, mesa.space.ContinuousSpace):
|
|
22
37
|
_draw_continuous_space(space, space_ax, agent_portrayal)
|
|
38
|
+
elif isinstance(space, VoronoiGrid):
|
|
39
|
+
_draw_voronoi(space, space_ax, agent_portrayal)
|
|
23
40
|
else:
|
|
24
41
|
_draw_grid(space, space_ax, agent_portrayal)
|
|
25
42
|
solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
|
|
@@ -150,8 +167,66 @@ def _draw_continuous_space(space, space_ax, agent_portrayal):
|
|
|
150
167
|
_split_and_scatter(portray(space), space_ax)
|
|
151
168
|
|
|
152
169
|
|
|
170
|
+
def _draw_voronoi(space, space_ax, agent_portrayal):
|
|
171
|
+
def portray(g):
|
|
172
|
+
x = []
|
|
173
|
+
y = []
|
|
174
|
+
s = [] # size
|
|
175
|
+
c = [] # color
|
|
176
|
+
|
|
177
|
+
for cell in g.all_cells:
|
|
178
|
+
for agent in cell.agents:
|
|
179
|
+
data = agent_portrayal(agent)
|
|
180
|
+
x.append(cell.coordinate[0])
|
|
181
|
+
y.append(cell.coordinate[1])
|
|
182
|
+
if "size" in data:
|
|
183
|
+
s.append(data["size"])
|
|
184
|
+
if "color" in data:
|
|
185
|
+
c.append(data["color"])
|
|
186
|
+
out = {"x": x, "y": y}
|
|
187
|
+
# This is the default value for the marker size, which auto-scales
|
|
188
|
+
# according to the grid area.
|
|
189
|
+
out["s"] = s
|
|
190
|
+
if len(c) > 0:
|
|
191
|
+
out["c"] = c
|
|
192
|
+
|
|
193
|
+
return out
|
|
194
|
+
|
|
195
|
+
x_list = [i[0] for i in space.centroids_coordinates]
|
|
196
|
+
y_list = [i[1] for i in space.centroids_coordinates]
|
|
197
|
+
x_max = max(x_list)
|
|
198
|
+
x_min = min(x_list)
|
|
199
|
+
y_max = max(y_list)
|
|
200
|
+
y_min = min(y_list)
|
|
201
|
+
|
|
202
|
+
width = x_max - x_min
|
|
203
|
+
x_padding = width / 20
|
|
204
|
+
height = y_max - y_min
|
|
205
|
+
y_padding = height / 20
|
|
206
|
+
space_ax.set_xlim(x_min - x_padding, x_max + x_padding)
|
|
207
|
+
space_ax.set_ylim(y_min - y_padding, y_max + y_padding)
|
|
208
|
+
space_ax.scatter(**portray(space))
|
|
209
|
+
|
|
210
|
+
for cell in space.all_cells:
|
|
211
|
+
polygon = cell.properties["polygon"]
|
|
212
|
+
space_ax.fill(
|
|
213
|
+
*zip(*polygon),
|
|
214
|
+
alpha=min(1, cell.properties[space.cell_coloring_property]),
|
|
215
|
+
c="red",
|
|
216
|
+
) # Plot filled polygon
|
|
217
|
+
space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
|
|
221
|
+
def MakePlotMeasure(model):
|
|
222
|
+
return PlotMatplotlib(model, measure)
|
|
223
|
+
|
|
224
|
+
return MakePlotMeasure
|
|
225
|
+
|
|
226
|
+
|
|
153
227
|
@solara.component
|
|
154
228
|
def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
|
|
229
|
+
update_counter.get()
|
|
155
230
|
fig = Figure()
|
|
156
231
|
ax = fig.subplots()
|
|
157
232
|
df = model.datacollector.get_model_vars_dataframe()
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -23,15 +23,20 @@ Usage:
|
|
|
23
23
|
See the Visualization Tutorial and example models for more details.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
import
|
|
26
|
+
import copy
|
|
27
|
+
import time
|
|
28
|
+
from typing import TYPE_CHECKING, Literal
|
|
27
29
|
|
|
28
|
-
import reacton.ipywidgets as widgets
|
|
29
30
|
import solara
|
|
30
31
|
from solara.alias import rv
|
|
31
32
|
|
|
32
33
|
import mesa.visualization.components.altair as components_altair
|
|
33
34
|
import mesa.visualization.components.matplotlib as components_matplotlib
|
|
34
35
|
from mesa.visualization.UserParam import Slider
|
|
36
|
+
from mesa.visualization.utils import force_update, update_counter
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from mesa.model import Model
|
|
35
40
|
|
|
36
41
|
|
|
37
42
|
# TODO: Turn this function into a Solara component once the current_step.value
|
|
@@ -86,120 +91,56 @@ def Card(
|
|
|
86
91
|
|
|
87
92
|
@solara.component
|
|
88
93
|
def SolaraViz(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
seed=None,
|
|
94
|
+
model: "Model" | solara.Reactive["Model"],
|
|
95
|
+
components: list[solara.component] | Literal["default"] = "default",
|
|
96
|
+
*args,
|
|
97
|
+
play_interval=100,
|
|
98
|
+
model_params=None,
|
|
99
|
+
seed=0,
|
|
100
|
+
name: str | None = None,
|
|
97
101
|
):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
Default drawer supports custom `"size"`, `"color"`, and `"shape"`.
|
|
108
|
-
space_drawer: Method to render the agent space for
|
|
109
|
-
the model; default implementation is the `SpaceMatplotlib` component;
|
|
110
|
-
simulations with no space to visualize should
|
|
111
|
-
specify `space_drawer=False`
|
|
112
|
-
play_interval: Play interval (default: 150)
|
|
113
|
-
seed: The random seed used to initialize the model
|
|
114
|
-
"""
|
|
115
|
-
if name is None:
|
|
116
|
-
name = model_class.__name__
|
|
102
|
+
update_counter.get()
|
|
103
|
+
if components == "default":
|
|
104
|
+
components = [components_altair.make_space_altair()]
|
|
117
105
|
|
|
118
|
-
|
|
106
|
+
# Convert model to reactive
|
|
107
|
+
if not isinstance(model, solara.Reactive):
|
|
108
|
+
model = solara.use_reactive(model)
|
|
119
109
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
model_parameters, set_model_parameters = solara.use_state(
|
|
124
|
-
{**fixed_params, **{k: v.get("value") for k, v in user_params.items()}}
|
|
125
|
-
)
|
|
110
|
+
def connect_to_model():
|
|
111
|
+
# Patch the step function to force updates
|
|
112
|
+
original_step = model.value.step
|
|
126
113
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
model = model_class.__new__(
|
|
131
|
-
model_class, **model_parameters, seed=reactive_seed.value
|
|
132
|
-
)
|
|
133
|
-
model.__init__(**model_parameters)
|
|
134
|
-
current_step.value = 0
|
|
135
|
-
return model
|
|
136
|
-
|
|
137
|
-
reset_counter = solara.use_reactive(0)
|
|
138
|
-
model = solara.use_memo(
|
|
139
|
-
make_model,
|
|
140
|
-
dependencies=[
|
|
141
|
-
*list(model_parameters.values()),
|
|
142
|
-
reset_counter.value,
|
|
143
|
-
reactive_seed.value,
|
|
144
|
-
],
|
|
145
|
-
)
|
|
114
|
+
def step():
|
|
115
|
+
original_step()
|
|
116
|
+
force_update()
|
|
146
117
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
118
|
+
model.value.step = step
|
|
119
|
+
# Add a trigger to model itself
|
|
120
|
+
model.value.force_update = force_update
|
|
121
|
+
force_update()
|
|
150
122
|
|
|
151
|
-
|
|
123
|
+
solara.use_effect(connect_to_model, [model.value])
|
|
152
124
|
|
|
153
125
|
with solara.AppBar():
|
|
154
|
-
solara.AppBarTitle(name)
|
|
155
|
-
|
|
156
|
-
# render layout and plot
|
|
157
|
-
def do_reseed():
|
|
158
|
-
"""Update the random seed for the model."""
|
|
159
|
-
reactive_seed.value = model.random.random()
|
|
160
|
-
|
|
161
|
-
dependencies = [current_step.value, reactive_seed.value]
|
|
162
|
-
|
|
163
|
-
# if space drawer is disabled, do not include it
|
|
164
|
-
layout_types = [{"Space": "default"}] if space_drawer else []
|
|
165
|
-
|
|
166
|
-
if measures:
|
|
167
|
-
layout_types += [{"Measure": elem} for elem in range(len(measures))]
|
|
168
|
-
|
|
169
|
-
grid_layout_initial = make_initial_grid_layout(layout_types=layout_types)
|
|
170
|
-
grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
|
|
126
|
+
solara.AppBarTitle(name if name else model.value.__class__.__name__)
|
|
171
127
|
|
|
172
128
|
with solara.Sidebar():
|
|
173
129
|
with solara.Card("Controls", margin=1, elevation=2):
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
ModelController(model, play_interval
|
|
181
|
-
solara.Button(label="Reseed", color="primary", on_click=do_reseed)
|
|
130
|
+
if model_params is not None:
|
|
131
|
+
ModelCreator(
|
|
132
|
+
model,
|
|
133
|
+
model_params,
|
|
134
|
+
seed=seed,
|
|
135
|
+
)
|
|
136
|
+
ModelController(model, play_interval)
|
|
182
137
|
with solara.Card("Information", margin=1, elevation=2):
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
model,
|
|
188
|
-
|
|
189
|
-
agent_portrayal,
|
|
190
|
-
space_drawer,
|
|
191
|
-
dependencies,
|
|
192
|
-
color="white",
|
|
193
|
-
layout_type=layout_types[i],
|
|
194
|
-
)
|
|
195
|
-
for i in range(len(layout_types))
|
|
196
|
-
]
|
|
197
|
-
solara.GridDraggable(
|
|
198
|
-
items=items,
|
|
199
|
-
grid_layout=grid_layout,
|
|
200
|
-
resizable=True,
|
|
201
|
-
draggable=True,
|
|
202
|
-
on_grid_layout=set_grid_layout,
|
|
138
|
+
ShowSteps(model.value)
|
|
139
|
+
|
|
140
|
+
solara.Column(
|
|
141
|
+
[
|
|
142
|
+
*(component(model.value) for component in components),
|
|
143
|
+
]
|
|
203
144
|
)
|
|
204
145
|
|
|
205
146
|
|
|
@@ -207,107 +148,57 @@ JupyterViz = SolaraViz
|
|
|
207
148
|
|
|
208
149
|
|
|
209
150
|
@solara.component
|
|
210
|
-
def ModelController(model, play_interval
|
|
151
|
+
def ModelController(model: solara.Reactive["Model"], play_interval=100):
|
|
211
152
|
"""
|
|
212
153
|
Create controls for model execution (step, play, pause, reset).
|
|
213
154
|
|
|
214
155
|
Args:
|
|
215
|
-
model: The model being visualized
|
|
156
|
+
model: The reactive model being visualized
|
|
216
157
|
play_interval: Interval between steps during play
|
|
217
|
-
current_step: Reactive value for the current step
|
|
218
|
-
reset_counter: Counter to trigger model reset
|
|
219
158
|
"""
|
|
159
|
+
if not isinstance(model, solara.Reactive):
|
|
160
|
+
model = solara.use_reactive(model)
|
|
161
|
+
|
|
220
162
|
playing = solara.use_reactive(False)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
elif model.running:
|
|
163
|
+
original_model = solara.use_reactive(None)
|
|
164
|
+
|
|
165
|
+
def save_initial_model():
|
|
166
|
+
"""Save the initial model for comparison."""
|
|
167
|
+
original_model.set(copy.deepcopy(model.value))
|
|
168
|
+
playing.value = False
|
|
169
|
+
force_update()
|
|
170
|
+
|
|
171
|
+
solara.use_effect(save_initial_model, [model.value])
|
|
172
|
+
|
|
173
|
+
def step():
|
|
174
|
+
while playing.value:
|
|
175
|
+
time.sleep(play_interval / 1000)
|
|
235
176
|
do_step()
|
|
236
|
-
|
|
237
|
-
|
|
177
|
+
|
|
178
|
+
solara.use_thread(step, [playing.value])
|
|
238
179
|
|
|
239
180
|
def do_step():
|
|
240
181
|
"""Advance the model by one step."""
|
|
241
|
-
model.step()
|
|
242
|
-
previous_step.value = current_step.value
|
|
243
|
-
current_step.value = model._steps
|
|
182
|
+
model.value.step()
|
|
244
183
|
|
|
245
184
|
def do_play():
|
|
246
185
|
"""Run the model continuously."""
|
|
247
|
-
|
|
248
|
-
while model.running:
|
|
249
|
-
do_step()
|
|
250
|
-
|
|
251
|
-
def threaded_do_play():
|
|
252
|
-
"""Start a new thread for continuous model execution."""
|
|
253
|
-
if thread is not None and thread.is_alive():
|
|
254
|
-
return
|
|
255
|
-
thread.value = threading.Thread(target=do_play)
|
|
256
|
-
thread.start()
|
|
186
|
+
playing.value = True
|
|
257
187
|
|
|
258
188
|
def do_pause():
|
|
259
189
|
"""Pause the model execution."""
|
|
260
|
-
|
|
261
|
-
return
|
|
262
|
-
model.running = False
|
|
263
|
-
thread.join()
|
|
190
|
+
playing.value = False
|
|
264
191
|
|
|
265
192
|
def do_reset():
|
|
266
|
-
"""Reset the model."""
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if current_step.value == 0:
|
|
272
|
-
# This means the model has been recreated, and the step resets to
|
|
273
|
-
# 0. We want to avoid triggering the playing.value = False in the
|
|
274
|
-
# on_value_play function.
|
|
275
|
-
previous_step.value = current_step.value
|
|
276
|
-
playing.set(value)
|
|
277
|
-
|
|
278
|
-
with solara.Row():
|
|
279
|
-
solara.Button(label="Step", color="primary", on_click=do_step)
|
|
280
|
-
# This style is necessary so that the play widget has almost the same
|
|
281
|
-
# height as typical Solara buttons.
|
|
282
|
-
solara.Style(
|
|
283
|
-
"""
|
|
284
|
-
.widget-play {
|
|
285
|
-
height: 35px;
|
|
286
|
-
}
|
|
287
|
-
.widget-play button {
|
|
288
|
-
color: white;
|
|
289
|
-
background-color: #1976D2; // Solara blue color
|
|
290
|
-
}
|
|
291
|
-
"""
|
|
292
|
-
)
|
|
293
|
-
widgets.Play(
|
|
294
|
-
value=0,
|
|
295
|
-
interval=play_interval,
|
|
296
|
-
repeat=True,
|
|
297
|
-
show_repeat=False,
|
|
298
|
-
on_value=on_value_play,
|
|
299
|
-
playing=playing.value,
|
|
300
|
-
on_playing=do_set_playing,
|
|
301
|
-
)
|
|
193
|
+
"""Reset the model to its initial state."""
|
|
194
|
+
playing.value = False
|
|
195
|
+
model.value = copy.deepcopy(original_model.value)
|
|
196
|
+
|
|
197
|
+
with solara.Row(justify="space-between"):
|
|
302
198
|
solara.Button(label="Reset", color="primary", on_click=do_reset)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
# version is definite a much better implementation,
|
|
307
|
-
# if it works.
|
|
308
|
-
# solara.Button(label="▶", color="primary", on_click=viz.threaded_do_play)
|
|
309
|
-
# solara.Button(label="⏸︎", color="primary", on_click=viz.do_pause)
|
|
310
|
-
# solara.Button(label="Reset", color="primary", on_click=do_reset)
|
|
199
|
+
solara.Button(label="Step", color="primary", on_click=do_step)
|
|
200
|
+
solara.Button(label="▶", color="primary", on_click=do_play)
|
|
201
|
+
solara.Button(label="⏸︎", color="primary", on_click=do_pause)
|
|
311
202
|
|
|
312
203
|
|
|
313
204
|
def split_model_params(model_params):
|
|
@@ -348,6 +239,45 @@ def check_param_is_fixed(param):
|
|
|
348
239
|
return True
|
|
349
240
|
|
|
350
241
|
|
|
242
|
+
@solara.component
|
|
243
|
+
def ModelCreator(model, model_params, seed=1):
|
|
244
|
+
user_params, fixed_params = split_model_params(model_params)
|
|
245
|
+
|
|
246
|
+
reactive_seed = solara.use_reactive(seed)
|
|
247
|
+
|
|
248
|
+
model_parameters, set_model_parameters = solara.use_state(
|
|
249
|
+
{
|
|
250
|
+
**fixed_params,
|
|
251
|
+
**{k: v.get("value") for k, v in user_params.items()},
|
|
252
|
+
}
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
def do_reseed():
|
|
256
|
+
"""Update the random seed for the model."""
|
|
257
|
+
reactive_seed.value = model.value.random.random()
|
|
258
|
+
|
|
259
|
+
def on_change(name, value):
|
|
260
|
+
set_model_parameters({**model_parameters, name: value})
|
|
261
|
+
|
|
262
|
+
def create_model():
|
|
263
|
+
model.value = model.value.__class__.__new__(
|
|
264
|
+
model.value.__class__, **model_parameters, seed=reactive_seed.value
|
|
265
|
+
)
|
|
266
|
+
model.value.__init__(**model_parameters)
|
|
267
|
+
|
|
268
|
+
solara.use_effect(create_model, [model_parameters, reactive_seed.value])
|
|
269
|
+
|
|
270
|
+
solara.InputText(
|
|
271
|
+
label="Seed",
|
|
272
|
+
value=reactive_seed,
|
|
273
|
+
continuous_update=True,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
solara.Button(label="Reseed", color="primary", on_click=do_reseed)
|
|
277
|
+
|
|
278
|
+
UserInputs(user_params, on_change=on_change)
|
|
279
|
+
|
|
280
|
+
|
|
351
281
|
@solara.component
|
|
352
282
|
def UserInputs(user_params, on_change=None):
|
|
353
283
|
"""
|
|
@@ -456,3 +386,9 @@ def make_initial_grid_layout(layout_types):
|
|
|
456
386
|
}
|
|
457
387
|
for i in range(len(layout_types))
|
|
458
388
|
]
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@solara.component
|
|
392
|
+
def ShowSteps(model):
|
|
393
|
+
update_counter.get()
|
|
394
|
+
return solara.Text(f"Step: {model.steps}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0a4
|
|
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
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
21
|
Classifier: Topic :: Scientific/Engineering
|
|
21
22
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
23
|
Classifier: Topic :: Scientific/Engineering :: Artificial Life
|
|
@@ -42,6 +43,9 @@ Requires-Dist: myst-parser; extra == 'docs'
|
|
|
42
43
|
Requires-Dist: pydata-sphinx-theme; extra == 'docs'
|
|
43
44
|
Requires-Dist: seaborn; extra == 'docs'
|
|
44
45
|
Requires-Dist: sphinx; extra == 'docs'
|
|
46
|
+
Provides-Extra: examples
|
|
47
|
+
Requires-Dist: pytest>=4.6; extra == 'examples'
|
|
48
|
+
Requires-Dist: scipy; extra == 'examples'
|
|
45
49
|
Description-Content-Type: text/markdown
|
|
46
50
|
|
|
47
51
|
# Mesa: Agent-based modeling in Python
|
|
@@ -98,15 +102,18 @@ Or any other (development) branch on this repo or your own fork:
|
|
|
98
102
|
pip install -U -e git+https://github.com/YOUR_FORK/mesa@YOUR_BRANCH#egg=mesa
|
|
99
103
|
```
|
|
100
104
|
|
|
105
|
+
## Resources
|
|
101
106
|
For resources or help on using Mesa, check out the following:
|
|
102
107
|
|
|
103
108
|
- [Intro to Mesa Tutorial](http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html) (An introductory model, the Boltzmann
|
|
104
109
|
Wealth Model, for beginners or those new to Mesa.)
|
|
110
|
+
- [Visualization Tutorial](https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
|
|
105
111
|
- [Complexity Explorer Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) (An advanced-beginner model,
|
|
106
112
|
SugarScape with Traders, with instructional videos)
|
|
107
113
|
- [Mesa Examples](https://github.com/projectmesa/mesa-examples/tree/main/examples) (A repository of seminal ABMs using Mesa and
|
|
108
114
|
examples of employing specific Mesa Features)
|
|
109
115
|
- [Docs](http://mesa.readthedocs.org/) (Mesa's documentation, API and useful snippets)
|
|
116
|
+
- [Development version docs](https://mesa.readthedocs.io/en/latest/) (the latest version docs if you're using a pre-release Mesa version)
|
|
110
117
|
- [Discussions](https://github.com/projectmesa/mesa/discussions) (GitHub threaded discussions about Mesa)
|
|
111
118
|
- [Matrix Chat](https://matrix.to/#/#project-mesa:matrix.org) (Chat Forum via Matrix to talk about Mesa)
|
|
112
119
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
mesa/__init__.py,sha256=UIq0dHx3cmtSSVuVFYYLpYS1rzbfn_tcT7vbqo1XLa0,618
|
|
2
|
+
mesa/agent.py,sha256=45KS_Cus2Yu-2g5moA8W6ntLWPKNnj9jUeNZiO4NqoI,22920
|
|
3
|
+
mesa/batchrunner.py,sha256=-n5mtNGWuKkE0kxWVenXw8b4LXe_LVAZ3rNEBcR2l1A,6097
|
|
4
|
+
mesa/datacollection.py,sha256=WUpZoFC2ZdLtKZ0oTwZTqraoP_yNx_yQY9pxO0TR8y0,11442
|
|
5
|
+
mesa/main.py,sha256=7MovfNz88VWNnfXP0kcERB6C3GfkVOh0hb0o32hM9LU,1602
|
|
6
|
+
mesa/model.py,sha256=_z_aPVu3ADJHZZD7YavBG7fdCYaDQqpkVNEcatWMM90,8551
|
|
7
|
+
mesa/space.py,sha256=5St5E26Np_b_fWv-_NEH82ZU0H3C9xHBAschRJtPpng,62698
|
|
8
|
+
mesa/time.py,sha256=53VX0x8zujaq32R6w_aBv1NmLpWO_h5K5BQTaK4mO3Q,14960
|
|
9
|
+
mesa/cookiecutter-mesa/cookiecutter.json,sha256=tBSWli39fOWUXGfiDCTKd92M7uKaBIswXbkOdbUufYY,337
|
|
10
|
+
mesa/cookiecutter-mesa/hooks/post_gen_project.py,sha256=8JoXZKIioRYEWJURC0udj8WS3rg0c4So62sOZSGbrMY,294
|
|
11
|
+
mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md,sha256=Yji4lGY-NtQSnW-oBj0_Jhs-XhCfZA8R1mBBM_IllGs,80
|
|
12
|
+
mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate,sha256=36f9k9CH6TK6VrXsPvTFXGUfCKzCLwgYTeK-Gt27GNg,584
|
|
13
|
+
mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate,sha256=UtRpLM_CkeUZRec-Ef_LiO_x7SKaWN11fOiH9T1UmTw,214
|
|
14
|
+
mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate,sha256=Aml4Z6E1yj7E7DtHNSUqnKNRUdkxG9WWtJyW8fkxCng,1870
|
|
16
|
+
mesa/experimental/UserParam.py,sha256=WgnY3Q0padtGqUCaezgYzd6cZ7LziuIQnGKP3DBuHZY,1641
|
|
17
|
+
mesa/experimental/__init__.py,sha256=8KfQcTMv9HJEAjmK5A_KdnFYfoY0nHAz97G_JLJVMNM,183
|
|
18
|
+
mesa/experimental/solara_viz.py,sha256=WXQY4b5jLp_ABvhV2df6yerDIijBzns3pXd4hCl5fAA,15253
|
|
19
|
+
mesa/experimental/cell_space/__init__.py,sha256=gXntv_Ie13SIegBumJNaIPcTUmTQLb4LlGd1JxoIn9M,708
|
|
20
|
+
mesa/experimental/cell_space/cell.py,sha256=AUnvVnXWhdgzr0bLKDRDO9c93v22Zkw6W-tWxhEhGdQ,4578
|
|
21
|
+
mesa/experimental/cell_space/cell_agent.py,sha256=Q3SoeK8Af7p4bdcm_E6Hr2uiHCSRsXYvttPUAOo4lRs,1080
|
|
22
|
+
mesa/experimental/cell_space/cell_collection.py,sha256=4FmfDEg9LoFiJ0mF_nC8KUt9fCJ7Q21erjWPeBTQ_lw,2293
|
|
23
|
+
mesa/experimental/cell_space/discrete_space.py,sha256=ta__YojsrrhWL4DgMzUqZpSgbeexKMrA6bxlYPJGfK0,1921
|
|
24
|
+
mesa/experimental/cell_space/grid.py,sha256=gYDExuFBMF3OThUkhbXmolQFKBOqTukcibjfgXicP00,6948
|
|
25
|
+
mesa/experimental/cell_space/network.py,sha256=mAaFHBdd4s9kxUWHbViovLW2-pU2yXH0dtY_vF8sCJg,1179
|
|
26
|
+
mesa/experimental/cell_space/voronoi.py,sha256=swwfV1Hfi7wp3XfM-rb9Lq5H0yAPH9zohZnXzU8SiHM,9997
|
|
27
|
+
mesa/experimental/components/altair.py,sha256=V2CQ-Zr7PeijgWtYBNH3VklGVfrf1ee70XVh0DBBONQ,2366
|
|
28
|
+
mesa/experimental/components/matplotlib.py,sha256=J61gHkXd37XyZiNLGF1NaQPOYqDdh9tpSfbOzMqK3dI,7570
|
|
29
|
+
mesa/experimental/devs/__init__.py,sha256=CWam15vCj-RD_biMyqv4sJfos1fsL823P7MDEGrbwW8,174
|
|
30
|
+
mesa/experimental/devs/eventlist.py,sha256=nyUFNDWnnSPQnrMtj7Qj1PexxKyOwSJuIGBoxtSwVI0,5269
|
|
31
|
+
mesa/experimental/devs/simulator.py,sha256=0SMC7daIOyL2rYfoQOOTaTOYDos0gLeBUbU1Krd42HA,9557
|
|
32
|
+
mesa/experimental/devs/examples/epstein_civil_violence.py,sha256=wCquOwcNYc--DYGM9Vg4YGx1Kh--HRhSVGjGgzT4k-I,9491
|
|
33
|
+
mesa/experimental/devs/examples/wolf_sheep.py,sha256=Hz3sExzjKEzrkFcE2gd7p7a0Ubg-QBGrV-4XQYWgt3c,7501
|
|
34
|
+
mesa/visualization/UserParam.py,sha256=WgnY3Q0padtGqUCaezgYzd6cZ7LziuIQnGKP3DBuHZY,1641
|
|
35
|
+
mesa/visualization/__init__.py,sha256=T_3QNz9BVuyfcbcMK_2RGGfMYhB7J-8XtaVPViXm1OU,372
|
|
36
|
+
mesa/visualization/solara_viz.py,sha256=8eQ-4dTYWuP0fmoaRMV3QqA1DL0tG0wPLmLAiYiiDO0,12188
|
|
37
|
+
mesa/visualization/utils.py,sha256=ade9YVhKx3gEaqDySj4YeSixTOHWGjzIIFDPGXL4uAs,103
|
|
38
|
+
mesa/visualization/components/altair.py,sha256=2VE4yRHrvBNXpDQUPuj0ejsPU0v3BqSzkphjbIcqzoc,2707
|
|
39
|
+
mesa/visualization/components/matplotlib.py,sha256=v-XwsChqWy9ukLe3zi-Q93UBUF20lxTB7lwckX4pB3M,8138
|
|
40
|
+
mesa-3.0.0a4.dist-info/METADATA,sha256=2DMGHgOya24WeUxtyEsBY1rzGlFxVkMZ2bcKFXeCZRw,8339
|
|
41
|
+
mesa-3.0.0a4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
42
|
+
mesa-3.0.0a4.dist-info/entry_points.txt,sha256=IOcQtetGF8l4wHpOs_hGb19Rz-FS__BMXOJR10IBPsA,39
|
|
43
|
+
mesa-3.0.0a4.dist-info/licenses/LICENSE,sha256=OGUgret9fRrm8J3pdsPXETIjf0H8puK_Nmy970ZzT78,572
|
|
44
|
+
mesa-3.0.0a4.dist-info/RECORD,,
|