Mesa 3.0.0b2__py3-none-any.whl → 3.0.1__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 +15 -3
- mesa/batchrunner.py +26 -1
- mesa/examples/README.md +11 -11
- mesa/examples/__init__.py +2 -2
- mesa/examples/advanced/epstein_civil_violence/agents.py +44 -38
- mesa/examples/advanced/epstein_civil_violence/app.py +29 -28
- mesa/examples/advanced/epstein_civil_violence/model.py +33 -65
- mesa/examples/advanced/pd_grid/app.py +9 -5
- mesa/examples/advanced/pd_grid/model.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/app.py +5 -13
- mesa/examples/advanced/sugarscape_g1mt/model.py +3 -1
- mesa/examples/advanced/wolf_sheep/agents.py +53 -39
- mesa/examples/advanced/wolf_sheep/app.py +37 -19
- mesa/examples/advanced/wolf_sheep/model.py +68 -74
- mesa/examples/basic/boid_flockers/agents.py +49 -18
- mesa/examples/basic/boid_flockers/app.py +2 -2
- mesa/examples/basic/boid_flockers/model.py +55 -19
- mesa/examples/basic/boltzmann_wealth_model/agents.py +23 -5
- mesa/examples/basic/boltzmann_wealth_model/app.py +22 -13
- mesa/examples/basic/boltzmann_wealth_model/model.py +48 -13
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +2 -2
- mesa/examples/basic/conways_game_of_life/app.py +15 -3
- mesa/examples/basic/schelling/agents.py +9 -5
- mesa/examples/basic/schelling/app.py +5 -5
- mesa/examples/basic/schelling/model.py +48 -26
- mesa/examples/basic/virus_on_network/app.py +25 -47
- mesa/experimental/cell_space/cell_collection.py +14 -2
- mesa/experimental/cell_space/discrete_space.py +16 -2
- mesa/experimental/devs/simulator.py +59 -14
- mesa/model.py +4 -4
- mesa/space.py +0 -30
- mesa/time.py +4 -4
- mesa/visualization/__init__.py +17 -6
- mesa/visualization/components/__init__.py +83 -0
- mesa/visualization/components/{altair.py → altair_components.py} +34 -2
- mesa/visualization/components/matplotlib_components.py +175 -0
- mesa/visualization/mpl_space_drawing.py +593 -0
- mesa/visualization/solara_viz.py +156 -67
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/METADATA +6 -8
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/RECORD +46 -44
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/WHEEL +1 -1
- mesa/visualization/components/matplotlib.py +0 -386
- /mesa/visualization/{UserParam.py → user_param.py} +0 -0
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/entry_points.txt +0 -0
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.0.0b2.dist-info → mesa-3.0.1.dist-info}/licenses/NOTICE +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import itertools
|
|
6
|
+
import warnings
|
|
6
7
|
from collections.abc import Callable, Iterable, Mapping
|
|
7
8
|
from functools import cached_property
|
|
8
9
|
from random import Random
|
|
@@ -23,6 +24,12 @@ class CellCollection(Generic[T]):
|
|
|
23
24
|
agents (List[CellAgent]) : List of agents occupying the cells in this collection
|
|
24
25
|
random (Random) : The random number generator
|
|
25
26
|
|
|
27
|
+
Notes:
|
|
28
|
+
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
|
|
29
|
+
passing a random number generator. In most cases, this will be the seeded random number
|
|
30
|
+
generator in the model. So, you would do `random=self.random` in a `Model` or `Agent` instance.
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
"""
|
|
27
34
|
|
|
28
35
|
def __init__(
|
|
@@ -45,7 +52,12 @@ class CellCollection(Generic[T]):
|
|
|
45
52
|
self._capacity: int = next(iter(self._cells.keys())).capacity
|
|
46
53
|
|
|
47
54
|
if random is None:
|
|
48
|
-
|
|
55
|
+
warnings.warn(
|
|
56
|
+
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
|
|
57
|
+
UserWarning,
|
|
58
|
+
stacklevel=2,
|
|
59
|
+
)
|
|
60
|
+
random = Random()
|
|
49
61
|
self.random = random
|
|
50
62
|
|
|
51
63
|
def __iter__(self): # noqa
|
|
@@ -115,4 +127,4 @@ class CellCollection(Generic[T]):
|
|
|
115
127
|
yield cell
|
|
116
128
|
count += 1
|
|
117
129
|
|
|
118
|
-
return CellCollection(cell_generator(filter_func, at_most))
|
|
130
|
+
return CellCollection(cell_generator(filter_func, at_most), random=self.random)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import warnings
|
|
5
6
|
from collections.abc import Callable
|
|
6
7
|
from functools import cached_property
|
|
7
8
|
from random import Random
|
|
@@ -25,6 +26,12 @@ class DiscreteSpace(Generic[T]):
|
|
|
25
26
|
cell_klass (Type) : the type of cell class
|
|
26
27
|
empties (CellCollection) : collection of all cells that are empty
|
|
27
28
|
property_layers (dict[str, PropertyLayer]): the property layers of the discrete space
|
|
29
|
+
|
|
30
|
+
Notes:
|
|
31
|
+
A `UserWarning` is issued if `random=None`. You can resolve this warning by explicitly
|
|
32
|
+
passing a random number generator. In most cases, this will be the seeded random number
|
|
33
|
+
generator in the model. So, you would do `random=self.random` in a `Model` or `Agent` instance.
|
|
34
|
+
|
|
28
35
|
"""
|
|
29
36
|
|
|
30
37
|
def __init__(
|
|
@@ -44,7 +51,12 @@ class DiscreteSpace(Generic[T]):
|
|
|
44
51
|
self.capacity = capacity
|
|
45
52
|
self._cells: dict[tuple[int, ...], T] = {}
|
|
46
53
|
if random is None:
|
|
47
|
-
|
|
54
|
+
warnings.warn(
|
|
55
|
+
"Random number generator not specified, this can make models non-reproducible. Please pass a random number generator explicitly",
|
|
56
|
+
UserWarning,
|
|
57
|
+
stacklevel=2,
|
|
58
|
+
)
|
|
59
|
+
random = Random()
|
|
48
60
|
self.random = random
|
|
49
61
|
self.cell_klass = cell_klass
|
|
50
62
|
|
|
@@ -67,7 +79,9 @@ class DiscreteSpace(Generic[T]):
|
|
|
67
79
|
@cached_property
|
|
68
80
|
def all_cells(self):
|
|
69
81
|
"""Return all cells in space."""
|
|
70
|
-
return CellCollection(
|
|
82
|
+
return CellCollection(
|
|
83
|
+
{cell: cell.agents for cell in self._cells.values()}, random=self.random
|
|
84
|
+
)
|
|
71
85
|
|
|
72
86
|
def __iter__(self): # noqa
|
|
73
87
|
return iter(self._cells.values())
|
|
@@ -57,8 +57,20 @@ class Simulator:
|
|
|
57
57
|
Args:
|
|
58
58
|
model (Model): The model to simulate
|
|
59
59
|
|
|
60
|
+
Raises:
|
|
61
|
+
Exception if simulator.time is not equal to simulator.starttime
|
|
62
|
+
Exception if event list is not empty
|
|
63
|
+
|
|
60
64
|
"""
|
|
61
|
-
self.
|
|
65
|
+
if self.time != self.start_time:
|
|
66
|
+
raise ValueError(
|
|
67
|
+
"trying to setup model, but current time is not equal to start_time, Has the simulator been reset or freshly initialized?"
|
|
68
|
+
)
|
|
69
|
+
if not self.event_list.is_empty():
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"trying to setup model, but events have already been scheduled. Call simulator.setup before any scheduling"
|
|
72
|
+
)
|
|
73
|
+
|
|
62
74
|
self.model = model
|
|
63
75
|
|
|
64
76
|
def reset(self):
|
|
@@ -68,7 +80,20 @@ class Simulator:
|
|
|
68
80
|
self.time = self.start_time
|
|
69
81
|
|
|
70
82
|
def run_until(self, end_time: int | float) -> None:
|
|
71
|
-
"""Run the simulator until the end time.
|
|
83
|
+
"""Run the simulator until the end time.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
end_time (int | float): The end time for stopping the simulator
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
Exception if simulator.setup() has not yet been called
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
if self.model is None:
|
|
93
|
+
raise Exception(
|
|
94
|
+
"simulator has not been setup, call simulator.setup(model) first"
|
|
95
|
+
)
|
|
96
|
+
|
|
72
97
|
while True:
|
|
73
98
|
try:
|
|
74
99
|
event = self.event_list.pop_event()
|
|
@@ -84,6 +109,26 @@ class Simulator:
|
|
|
84
109
|
self._schedule_event(event) # reschedule event
|
|
85
110
|
break
|
|
86
111
|
|
|
112
|
+
def run_next_event(self):
|
|
113
|
+
"""Execute the next event.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
Exception if simulator.setup() has not yet been called
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
if self.model is None:
|
|
120
|
+
raise Exception(
|
|
121
|
+
"simulator has not been setup, call simulator.setup(model) first"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
event = self.event_list.pop_event()
|
|
126
|
+
except IndexError: # event list is empty
|
|
127
|
+
return
|
|
128
|
+
else:
|
|
129
|
+
self.time = event.time
|
|
130
|
+
event.execute()
|
|
131
|
+
|
|
87
132
|
def run_for(self, time_delta: int | float):
|
|
88
133
|
"""Run the simulator for the specified time delta.
|
|
89
134
|
|
|
@@ -92,6 +137,7 @@ class Simulator:
|
|
|
92
137
|
plus the time delta
|
|
93
138
|
|
|
94
139
|
"""
|
|
140
|
+
# fixme, raise initialization error or something like it if model.setup has not been called
|
|
95
141
|
end_time = self.time + time_delta
|
|
96
142
|
self.run_until(end_time)
|
|
97
143
|
|
|
@@ -228,7 +274,7 @@ class ABMSimulator(Simulator):
|
|
|
228
274
|
|
|
229
275
|
"""
|
|
230
276
|
super().setup(model)
|
|
231
|
-
self.
|
|
277
|
+
self.schedule_event_next_tick(self.model.step, priority=Priority.HIGH)
|
|
232
278
|
|
|
233
279
|
def check_time_unit(self, time) -> bool:
|
|
234
280
|
"""Check whether the time is of the correct unit.
|
|
@@ -277,7 +323,15 @@ class ABMSimulator(Simulator):
|
|
|
277
323
|
Args:
|
|
278
324
|
end_time (float| int): The end_time delta. The simulator is until the specified end time
|
|
279
325
|
|
|
326
|
+
Raises:
|
|
327
|
+
Exception if simulator.setup() has not yet been called
|
|
328
|
+
|
|
280
329
|
"""
|
|
330
|
+
if self.model is None:
|
|
331
|
+
raise Exception(
|
|
332
|
+
"simulator has not been setup, call simulator.setup(model) first"
|
|
333
|
+
)
|
|
334
|
+
|
|
281
335
|
while True:
|
|
282
336
|
try:
|
|
283
337
|
event = self.event_list.pop_event()
|
|
@@ -285,6 +339,8 @@ class ABMSimulator(Simulator):
|
|
|
285
339
|
self.time = end_time
|
|
286
340
|
break
|
|
287
341
|
|
|
342
|
+
# fixme: the alternative would be to wrap model.step with an annotation which
|
|
343
|
+
# handles this scheduling.
|
|
288
344
|
if event.time <= end_time:
|
|
289
345
|
self.time = event.time
|
|
290
346
|
if event.fn() == self.model.step:
|
|
@@ -298,17 +354,6 @@ class ABMSimulator(Simulator):
|
|
|
298
354
|
self._schedule_event(event)
|
|
299
355
|
break
|
|
300
356
|
|
|
301
|
-
def run_for(self, time_delta: int):
|
|
302
|
-
"""Run the simulator for the specified time delta.
|
|
303
|
-
|
|
304
|
-
Args:
|
|
305
|
-
time_delta (float| int): The time delta. The simulator is run from the current time to the current time
|
|
306
|
-
plus the time delta
|
|
307
|
-
|
|
308
|
-
"""
|
|
309
|
-
end_time = self.time + time_delta - 1
|
|
310
|
-
self.run_until(end_time)
|
|
311
|
-
|
|
312
357
|
|
|
313
358
|
class DEVSimulator(Simulator):
|
|
314
359
|
"""A simulator where the unit of time is a float.
|
mesa/model.py
CHANGED
|
@@ -114,7 +114,7 @@ class Model:
|
|
|
114
114
|
|
|
115
115
|
def next_id(self) -> int: # noqa: D102
|
|
116
116
|
warnings.warn(
|
|
117
|
-
"using model.next_id() is deprecated. Agents track their unique ID automatically",
|
|
117
|
+
"using model.next_id() is deprecated and will be removed in Mesa 3.1. Agents track their unique ID automatically",
|
|
118
118
|
DeprecationWarning,
|
|
119
119
|
stacklevel=2,
|
|
120
120
|
)
|
|
@@ -146,8 +146,8 @@ class Model:
|
|
|
146
146
|
def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet:
|
|
147
147
|
"""Deprecated: Retrieves an AgentSet containing all agents of the specified type."""
|
|
148
148
|
warnings.warn(
|
|
149
|
-
f"Model.get_agents_of_type() is deprecated
|
|
150
|
-
f"with the property agents_by_type[{agenttype}].",
|
|
149
|
+
f"Model.get_agents_of_type() is deprecated and will be removed in Mesa 3.1."
|
|
150
|
+
f"Please replace get_agents_of_type({agenttype}) with the property agents_by_type[{agenttype}].",
|
|
151
151
|
DeprecationWarning,
|
|
152
152
|
stacklevel=2,
|
|
153
153
|
)
|
|
@@ -262,7 +262,7 @@ class Model:
|
|
|
262
262
|
|
|
263
263
|
"""
|
|
264
264
|
warnings.warn(
|
|
265
|
-
"initialize_data_collector() is deprecated. Please use the DataCollector class directly. "
|
|
265
|
+
"initialize_data_collector() is deprecated and will be removed in Mesa 3.1. Please use the DataCollector class directly. "
|
|
266
266
|
"by using `self.datacollector = DataCollector(...)`.",
|
|
267
267
|
DeprecationWarning,
|
|
268
268
|
stacklevel=2,
|
mesa/space.py
CHANGED
|
@@ -1287,36 +1287,6 @@ class HexMultiGrid(_HexGrid, MultiGrid):
|
|
|
1287
1287
|
"""
|
|
1288
1288
|
|
|
1289
1289
|
|
|
1290
|
-
class HexGrid(HexSingleGrid):
|
|
1291
|
-
"""Hexagonal Grid: a Grid where neighbors are computed according to a hexagonal tiling of the grid.
|
|
1292
|
-
|
|
1293
|
-
Functions according to odd-q rules.
|
|
1294
|
-
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
|
|
1295
|
-
|
|
1296
|
-
Properties:
|
|
1297
|
-
width, height: The grid's width and height.
|
|
1298
|
-
torus: Boolean which determines whether to treat the grid as a torus.
|
|
1299
|
-
"""
|
|
1300
|
-
|
|
1301
|
-
def __init__(self, width: int, height: int, torus: bool) -> None:
|
|
1302
|
-
"""Initializes a HexGrid, deprecated.
|
|
1303
|
-
|
|
1304
|
-
Args:
|
|
1305
|
-
width: the width of the grid
|
|
1306
|
-
height: the height of the grid
|
|
1307
|
-
torus: whether the grid wraps
|
|
1308
|
-
"""
|
|
1309
|
-
super().__init__(width, height, torus)
|
|
1310
|
-
warn(
|
|
1311
|
-
(
|
|
1312
|
-
"HexGrid is being deprecated; use instead HexSingleGrid or HexMultiGrid "
|
|
1313
|
-
"depending on your use case."
|
|
1314
|
-
),
|
|
1315
|
-
DeprecationWarning,
|
|
1316
|
-
stacklevel=2,
|
|
1317
|
-
)
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
1290
|
class ContinuousSpace:
|
|
1321
1291
|
"""Continuous space where each agent can have an arbitrary position.
|
|
1322
1292
|
|
mesa/time.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Mesa Time Module.
|
|
2
2
|
|
|
3
3
|
.. warning::
|
|
4
|
-
The time module and all its Schedulers are deprecated and will be removed in
|
|
4
|
+
The time module and all its Schedulers are deprecated and will be removed in Mesa 3.1.
|
|
5
5
|
They can be replaced with AgentSet functionality. See the migration guide for details:
|
|
6
6
|
https://mesa.readthedocs.io/latest/migration_guide.html#time-and-schedulers
|
|
7
7
|
|
|
@@ -63,7 +63,7 @@ class BaseScheduler:
|
|
|
63
63
|
|
|
64
64
|
"""
|
|
65
65
|
warnings.warn(
|
|
66
|
-
"The time module and all its Schedulers are deprecated and will be removed in
|
|
66
|
+
"The time module and all its Schedulers are deprecated and will be removed in Mesa 3.1. "
|
|
67
67
|
"They can be replaced with AgentSet functionality. See the migration guide for details. "
|
|
68
68
|
"https://mesa.readthedocs.io/latest/migration_guide.html#time-and-schedulers",
|
|
69
69
|
DeprecationWarning,
|
|
@@ -375,7 +375,7 @@ class RandomActivationByType(BaseScheduler):
|
|
|
375
375
|
|
|
376
376
|
|
|
377
377
|
class DiscreteEventScheduler(BaseScheduler):
|
|
378
|
-
"""This class has been
|
|
378
|
+
"""This class has been removed and replaced by the functionality provided by experimental.devs."""
|
|
379
379
|
|
|
380
380
|
def __init__(self, model: Model, time_step: TimeT = 1) -> None:
|
|
381
381
|
"""Initialize DiscreteEventScheduler.
|
|
@@ -387,5 +387,5 @@ class DiscreteEventScheduler(BaseScheduler):
|
|
|
387
387
|
"""
|
|
388
388
|
super().__init__(model)
|
|
389
389
|
raise Exception(
|
|
390
|
-
"DiscreteEventScheduler is
|
|
390
|
+
"DiscreteEventScheduler is removed in favor of the functionality provided by experimental.devs"
|
|
391
391
|
)
|
mesa/visualization/__init__.py
CHANGED
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
"""Solara based visualization for Mesa models.
|
|
1
|
+
"""Solara based visualization for Mesa models.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
.. note::
|
|
4
|
+
SolaraViz is experimental and still in active development for Mesa 3.0. While we attempt to minimize them, there might be API breaking changes between Mesa 3.0 and 3.1.
|
|
5
|
+
|
|
6
|
+
There won't be breaking changes between Mesa 3.0.x patch releases.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from mesa.visualization.mpl_space_drawing import (
|
|
10
|
+
draw_space,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .components import make_plot_component, make_space_component
|
|
14
|
+
from .components.altair_components import make_space_altair
|
|
5
15
|
from .solara_viz import JupyterViz, SolaraViz
|
|
6
|
-
from .
|
|
16
|
+
from .user_param import Slider
|
|
7
17
|
|
|
8
18
|
__all__ = [
|
|
9
19
|
"JupyterViz",
|
|
10
20
|
"SolaraViz",
|
|
11
21
|
"Slider",
|
|
12
22
|
"make_space_altair",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
23
|
+
"draw_space",
|
|
24
|
+
"make_plot_component",
|
|
25
|
+
"make_space_component",
|
|
15
26
|
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""custom solara components."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from .altair_components import SpaceAltair, make_altair_space
|
|
8
|
+
from .matplotlib_components import (
|
|
9
|
+
SpaceMatplotlib,
|
|
10
|
+
make_mpl_plot_component,
|
|
11
|
+
make_mpl_space_component,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_space_component(
|
|
16
|
+
agent_portrayal: Callable | None = None,
|
|
17
|
+
propertylayer_portrayal: dict | None = None,
|
|
18
|
+
post_process: Callable | None = None,
|
|
19
|
+
backend: str = "matplotlib",
|
|
20
|
+
**space_drawing_kwargs,
|
|
21
|
+
) -> SpaceMatplotlib | SpaceAltair:
|
|
22
|
+
"""Create a Matplotlib-based space visualization component.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
agent_portrayal: Function to portray agents.
|
|
26
|
+
propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications
|
|
27
|
+
post_process : a callable that will be called with the Axes instance. Allows for fine-tuning plots (e.g., control ticks)
|
|
28
|
+
backend: the backend to use {"matplotlib", "altair"}
|
|
29
|
+
space_drawing_kwargs : additional keyword arguments to be passed on to the underlying backend specific space drawer function. See
|
|
30
|
+
the functions for drawing the various spaces for the appropriate backend further details.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
function: A function that creates a space component
|
|
35
|
+
"""
|
|
36
|
+
if backend == "matplotlib":
|
|
37
|
+
return make_mpl_space_component(
|
|
38
|
+
agent_portrayal,
|
|
39
|
+
propertylayer_portrayal,
|
|
40
|
+
post_process,
|
|
41
|
+
**space_drawing_kwargs,
|
|
42
|
+
)
|
|
43
|
+
elif backend == "altair":
|
|
44
|
+
return make_altair_space(
|
|
45
|
+
agent_portrayal,
|
|
46
|
+
propertylayer_portrayal,
|
|
47
|
+
post_process,
|
|
48
|
+
**space_drawing_kwargs,
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"unknown backend {backend}, must be one of matplotlib, altair"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def make_plot_component(
|
|
57
|
+
measure: str | dict[str, str] | list[str] | tuple[str],
|
|
58
|
+
post_process: Callable | None = None,
|
|
59
|
+
backend: str = "matplotlib",
|
|
60
|
+
**plot_drawing_kwargs,
|
|
61
|
+
):
|
|
62
|
+
"""Create a plotting function for a specified measure using the specified backend.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
66
|
+
post_process: a user-specified callable to do post-processing called with the Axes instance.
|
|
67
|
+
backend: the backend to use {"matplotlib", "altair"}
|
|
68
|
+
plot_drawing_kwargs: additional keyword arguments to pass onto the backend specific function for making a plotting component
|
|
69
|
+
|
|
70
|
+
Notes:
|
|
71
|
+
altair plotting backend is not yet implemented and planned for mesa 3.1.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
function: A function that creates a plot component
|
|
75
|
+
"""
|
|
76
|
+
if backend == "matplotlib":
|
|
77
|
+
return make_mpl_plot_component(measure, post_process, **plot_drawing_kwargs)
|
|
78
|
+
elif backend == "altair":
|
|
79
|
+
raise NotImplementedError("altair line plots are not yet implemented")
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"unknown backend {backend}, must be one of matplotlib, altair"
|
|
83
|
+
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Altair based solara components for visualization mesa spaces."""
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
import warnings
|
|
4
5
|
|
|
5
6
|
import solara
|
|
6
7
|
|
|
@@ -12,7 +13,33 @@ from mesa.space import ContinuousSpace, _Grid
|
|
|
12
13
|
from mesa.visualization.utils import update_counter
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def make_space_altair(
|
|
16
|
+
def make_space_altair(*args, **kwargs): # noqa: D103
|
|
17
|
+
warnings.warn(
|
|
18
|
+
"make_space_altair has been renamed to make_altair_space",
|
|
19
|
+
DeprecationWarning,
|
|
20
|
+
stacklevel=2,
|
|
21
|
+
)
|
|
22
|
+
return make_altair_space(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_altair_space(
|
|
26
|
+
agent_portrayal, propertylayer_portrayal, post_process, **space_drawing_kwargs
|
|
27
|
+
):
|
|
28
|
+
"""Create an Altair-based space visualization component.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
agent_portrayal: Function to portray agents.
|
|
32
|
+
propertylayer_portrayal: not yet implemented
|
|
33
|
+
post_process :not yet implemented
|
|
34
|
+
space_drawing_kwargs : not yet implemented
|
|
35
|
+
|
|
36
|
+
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
37
|
+
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
function: A function that creates a SpaceMatplotlib component
|
|
42
|
+
"""
|
|
16
43
|
if agent_portrayal is None:
|
|
17
44
|
|
|
18
45
|
def agent_portrayal(a):
|
|
@@ -25,7 +52,12 @@ def make_space_altair(agent_portrayal=None): # noqa: D103
|
|
|
25
52
|
|
|
26
53
|
|
|
27
54
|
@solara.component
|
|
28
|
-
def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
55
|
+
def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
56
|
+
"""Create an Altair-based space visualization component.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
a solara FigureAltair instance
|
|
60
|
+
"""
|
|
29
61
|
update_counter.get()
|
|
30
62
|
space = getattr(model, "grid", None)
|
|
31
63
|
if space is None:
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Matplotlib based solara components for visualization MESA spaces and plots."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import solara
|
|
10
|
+
from matplotlib.figure import Figure
|
|
11
|
+
|
|
12
|
+
from mesa.visualization.mpl_space_drawing import draw_space
|
|
13
|
+
from mesa.visualization.utils import update_counter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_space_matplotlib(*args, **kwargs): # noqa: D103
|
|
17
|
+
warnings.warn(
|
|
18
|
+
"make_space_matplotlib has been renamed to make_mpl_space_component",
|
|
19
|
+
DeprecationWarning,
|
|
20
|
+
stacklevel=2,
|
|
21
|
+
)
|
|
22
|
+
return make_mpl_space_component(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_mpl_space_component(
|
|
26
|
+
agent_portrayal: Callable | None = None,
|
|
27
|
+
propertylayer_portrayal: dict | None = None,
|
|
28
|
+
post_process: Callable | None = None,
|
|
29
|
+
**space_drawing_kwargs,
|
|
30
|
+
) -> SpaceMatplotlib:
|
|
31
|
+
"""Create a Matplotlib-based space visualization component.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
agent_portrayal: Function to portray agents.
|
|
35
|
+
propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications
|
|
36
|
+
post_process : a callable that will be called with the Axes instance. Allows for fine tuning plots (e.g., control ticks)
|
|
37
|
+
space_drawing_kwargs : additional keyword arguments to be passed on to the underlying space drawer function. See
|
|
38
|
+
the functions for drawing the various spaces for further details.
|
|
39
|
+
|
|
40
|
+
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
41
|
+
"size", "marker", "zorder", alpha, linewidths, and edgecolors. Other field are ignored and will result in a user warning.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
function: A function that creates a SpaceMatplotlib component
|
|
45
|
+
"""
|
|
46
|
+
if agent_portrayal is None:
|
|
47
|
+
|
|
48
|
+
def agent_portrayal(a):
|
|
49
|
+
return {}
|
|
50
|
+
|
|
51
|
+
def MakeSpaceMatplotlib(model):
|
|
52
|
+
return SpaceMatplotlib(
|
|
53
|
+
model,
|
|
54
|
+
agent_portrayal,
|
|
55
|
+
propertylayer_portrayal,
|
|
56
|
+
post_process=post_process,
|
|
57
|
+
**space_drawing_kwargs,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return MakeSpaceMatplotlib
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@solara.component
|
|
64
|
+
def SpaceMatplotlib(
|
|
65
|
+
model,
|
|
66
|
+
agent_portrayal,
|
|
67
|
+
propertylayer_portrayal,
|
|
68
|
+
dependencies: list[any] | None = None,
|
|
69
|
+
post_process: Callable | None = None,
|
|
70
|
+
**space_drawing_kwargs,
|
|
71
|
+
):
|
|
72
|
+
"""Create a Matplotlib-based space visualization component."""
|
|
73
|
+
update_counter.get()
|
|
74
|
+
|
|
75
|
+
space = getattr(model, "grid", None)
|
|
76
|
+
if space is None:
|
|
77
|
+
space = getattr(model, "space", None)
|
|
78
|
+
|
|
79
|
+
fig = Figure()
|
|
80
|
+
ax = fig.add_subplot()
|
|
81
|
+
|
|
82
|
+
draw_space(
|
|
83
|
+
space,
|
|
84
|
+
agent_portrayal,
|
|
85
|
+
propertylayer_portrayal=propertylayer_portrayal,
|
|
86
|
+
ax=ax,
|
|
87
|
+
**space_drawing_kwargs,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if post_process is not None:
|
|
91
|
+
post_process(ax)
|
|
92
|
+
|
|
93
|
+
solara.FigureMatplotlib(
|
|
94
|
+
fig, format="png", bbox_inches="tight", dependencies=dependencies
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def make_plot_measure(*args, **kwargs): # noqa: D103
|
|
99
|
+
warnings.warn(
|
|
100
|
+
"make_plot_measure has been renamed to make_plot_component",
|
|
101
|
+
DeprecationWarning,
|
|
102
|
+
stacklevel=2,
|
|
103
|
+
)
|
|
104
|
+
return make_mpl_plot_component(*args, **kwargs)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def make_mpl_plot_component(
|
|
108
|
+
measure: str | dict[str, str] | list[str] | tuple[str],
|
|
109
|
+
post_process: Callable | None = None,
|
|
110
|
+
save_format="png",
|
|
111
|
+
):
|
|
112
|
+
"""Create a plotting function for a specified measure.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
116
|
+
post_process: a user-specified callable to do post-processing called with the Axes instance.
|
|
117
|
+
save_format: save format of figure in solara backend
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
function: A function that creates a PlotMatplotlib component.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def MakePlotMatplotlib(model):
|
|
124
|
+
return PlotMatplotlib(
|
|
125
|
+
model, measure, post_process=post_process, save_format=save_format
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return MakePlotMatplotlib
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@solara.component
|
|
132
|
+
def PlotMatplotlib(
|
|
133
|
+
model,
|
|
134
|
+
measure,
|
|
135
|
+
dependencies: list[any] | None = None,
|
|
136
|
+
post_process: Callable | None = None,
|
|
137
|
+
save_format="png",
|
|
138
|
+
):
|
|
139
|
+
"""Create a Matplotlib-based plot for a measure or measures.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
model (mesa.Model): The model instance.
|
|
143
|
+
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
144
|
+
dependencies (list[any] | None): Optional dependencies for the plot.
|
|
145
|
+
post_process: a user-specified callable to do post-processing called with the Axes instance.
|
|
146
|
+
save_format: format used for saving the figure.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
solara.FigureMatplotlib: A component for rendering the plot.
|
|
150
|
+
"""
|
|
151
|
+
update_counter.get()
|
|
152
|
+
fig = Figure()
|
|
153
|
+
ax = fig.subplots()
|
|
154
|
+
df = model.datacollector.get_model_vars_dataframe()
|
|
155
|
+
if isinstance(measure, str):
|
|
156
|
+
ax.plot(df.loc[:, measure])
|
|
157
|
+
ax.set_ylabel(measure)
|
|
158
|
+
elif isinstance(measure, dict):
|
|
159
|
+
for m, color in measure.items():
|
|
160
|
+
ax.plot(df.loc[:, m], label=m, color=color)
|
|
161
|
+
ax.legend(loc="best")
|
|
162
|
+
elif isinstance(measure, list | tuple):
|
|
163
|
+
for m in measure:
|
|
164
|
+
ax.plot(df.loc[:, m], label=m)
|
|
165
|
+
ax.legend(loc="best")
|
|
166
|
+
|
|
167
|
+
if post_process is not None:
|
|
168
|
+
post_process(ax)
|
|
169
|
+
|
|
170
|
+
ax.set_xlabel("Step")
|
|
171
|
+
# Set integer x axis
|
|
172
|
+
ax.xaxis.set_major_locator(plt.MaxNLocator(integer=True))
|
|
173
|
+
solara.FigureMatplotlib(
|
|
174
|
+
fig, format=save_format, bbox_inches="tight", dependencies=dependencies
|
|
175
|
+
)
|