Mesa 3.0.0a5__py3-none-any.whl → 3.0.0b1__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.
- examples/README.md +37 -0
- examples/__init__.py +0 -0
- examples/advanced/__init__.py +0 -0
- examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
- examples/advanced/epstein_civil_violence/Readme.md +33 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py +0 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/agent.py +158 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/model.py +146 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/portrayal.py +33 -0
- examples/advanced/epstein_civil_violence/epstein_civil_violence/server.py +81 -0
- examples/advanced/epstein_civil_violence/requirements.txt +3 -0
- examples/advanced/epstein_civil_violence/run.py +3 -0
- examples/advanced/pd_grid/analysis.ipynb +228 -0
- examples/advanced/pd_grid/pd_grid/__init__.py +0 -0
- examples/advanced/pd_grid/pd_grid/agent.py +50 -0
- examples/advanced/pd_grid/pd_grid/model.py +72 -0
- examples/advanced/pd_grid/pd_grid/portrayal.py +19 -0
- examples/advanced/pd_grid/pd_grid/server.py +21 -0
- examples/advanced/pd_grid/readme.md +42 -0
- examples/advanced/pd_grid/requirements.txt +3 -0
- examples/advanced/pd_grid/run.py +3 -0
- examples/advanced/sugarscape_g1mt/Readme.md +87 -0
- examples/advanced/sugarscape_g1mt/app.py +61 -0
- examples/advanced/sugarscape_g1mt/requirements.txt +6 -0
- examples/advanced/sugarscape_g1mt/run.py +105 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/__init__.py +0 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/model.py +180 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/resource_agents.py +26 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/server.py +61 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/sugar-map.txt +50 -0
- examples/advanced/sugarscape_g1mt/sugarscape_g1mt/trader_agents.py +321 -0
- examples/advanced/sugarscape_g1mt/tests.py +72 -0
- examples/advanced/wolf_sheep/Readme.md +57 -0
- examples/advanced/wolf_sheep/__init__.py +0 -0
- examples/advanced/wolf_sheep/requirements.txt +1 -0
- examples/advanced/wolf_sheep/run.py +3 -0
- examples/advanced/wolf_sheep/wolf_sheep/__init__.py +0 -0
- examples/advanced/wolf_sheep/wolf_sheep/agents.py +102 -0
- examples/advanced/wolf_sheep/wolf_sheep/model.py +136 -0
- examples/advanced/wolf_sheep/wolf_sheep/resources/sheep.png +0 -0
- examples/advanced/wolf_sheep/wolf_sheep/resources/wolf.png +0 -0
- examples/advanced/wolf_sheep/wolf_sheep/server.py +78 -0
- examples/basic/__init__.py +13 -0
- examples/basic/boid_flockers/Readme.md +43 -0
- examples/basic/boid_flockers/agents.py +71 -0
- examples/basic/boid_flockers/app.py +59 -0
- examples/basic/boid_flockers/model.py +70 -0
- examples/basic/boltzmann_wealth_model/Readme.md +60 -0
- examples/basic/boltzmann_wealth_model/agents.py +31 -0
- examples/basic/boltzmann_wealth_model/app.py +66 -0
- examples/basic/boltzmann_wealth_model/model.py +44 -0
- examples/basic/boltzmann_wealth_model/st_app.py +115 -0
- examples/basic/conways_game_of_life/Readme.md +35 -0
- examples/basic/conways_game_of_life/agents.py +47 -0
- examples/basic/conways_game_of_life/model.py +32 -0
- examples/basic/conways_game_of_life/portrayal.py +18 -0
- examples/basic/conways_game_of_life/requirements.txt +1 -0
- examples/basic/conways_game_of_life/server.py +11 -0
- examples/basic/conways_game_of_life/st_app.py +71 -0
- examples/basic/schelling/README.md +47 -0
- examples/basic/schelling/agents.py +26 -0
- examples/basic/schelling/analysis.ipynb +205 -0
- examples/basic/schelling/app.py +43 -0
- examples/basic/schelling/model.py +60 -0
- examples/basic/virus_on_network/README.md +61 -0
- examples/basic/virus_on_network/agents.py +69 -0
- examples/basic/virus_on_network/app.py +133 -0
- examples/basic/virus_on_network/model.py +99 -0
- mesa/__init__.py +4 -1
- mesa/agent.py +24 -43
- mesa/batchrunner.py +7 -0
- mesa/examples.py +3 -0
- mesa/experimental/__init__.py +8 -2
- mesa/experimental/cell_space/__init__.py +7 -1
- mesa/experimental/cell_space/cell.py +35 -6
- mesa/experimental/cell_space/cell_agent.py +114 -23
- mesa/experimental/cell_space/discrete_space.py +70 -3
- mesa/experimental/cell_space/grid.py +13 -0
- mesa/experimental/cell_space/network.py +3 -0
- mesa/experimental/devs/examples/wolf_sheep.py +2 -1
- mesa/model.py +71 -21
- mesa/time.py +7 -5
- mesa/visualization/components/matplotlib.py +184 -90
- mesa/visualization/solara_viz.py +25 -61
- {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/METADATA +55 -13
- mesa-3.0.0b1.dist-info/RECORD +114 -0
- mesa-3.0.0b1.dist-info/licenses/LICENSE +202 -0
- mesa-3.0.0a5.dist-info/licenses/LICENSE → mesa-3.0.0b1.dist-info/licenses/NOTICE +2 -2
- mesa-3.0.0a5.dist-info/RECORD +0 -44
- {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a5.dist-info → mesa-3.0.0b1.dist-info}/entry_points.txt +0 -0
|
@@ -1,116 +1,192 @@
|
|
|
1
1
|
"""Matplotlib based solara components for visualization MESA spaces and plots."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import warnings
|
|
4
4
|
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
5
6
|
import networkx as nx
|
|
7
|
+
import numpy as np
|
|
6
8
|
import solara
|
|
9
|
+
from matplotlib.cm import ScalarMappable
|
|
10
|
+
from matplotlib.colors import LinearSegmentedColormap, Normalize, to_rgba
|
|
7
11
|
from matplotlib.figure import Figure
|
|
8
|
-
from matplotlib.ticker import MaxNLocator
|
|
9
12
|
|
|
10
13
|
import mesa
|
|
11
14
|
from mesa.experimental.cell_space import VoronoiGrid
|
|
15
|
+
from mesa.space import PropertyLayer
|
|
12
16
|
from mesa.visualization.utils import update_counter
|
|
13
17
|
|
|
14
18
|
|
|
15
|
-
def make_space_matplotlib(agent_portrayal=None):
|
|
19
|
+
def make_space_matplotlib(agent_portrayal=None, propertylayer_portrayal=None):
|
|
20
|
+
"""Create a Matplotlib-based space visualization component.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
agent_portrayal (function): Function to portray agents
|
|
24
|
+
propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
function: A function that creates a SpaceMatplotlib component
|
|
28
|
+
"""
|
|
16
29
|
if agent_portrayal is None:
|
|
17
30
|
|
|
18
31
|
def agent_portrayal(a):
|
|
19
32
|
return {"id": a.unique_id}
|
|
20
33
|
|
|
21
34
|
def MakeSpaceMatplotlib(model):
|
|
22
|
-
return SpaceMatplotlib(model, agent_portrayal)
|
|
35
|
+
return SpaceMatplotlib(model, agent_portrayal, propertylayer_portrayal)
|
|
23
36
|
|
|
24
37
|
return MakeSpaceMatplotlib
|
|
25
38
|
|
|
26
39
|
|
|
27
40
|
@solara.component
|
|
28
|
-
def SpaceMatplotlib(
|
|
41
|
+
def SpaceMatplotlib(
|
|
42
|
+
model,
|
|
43
|
+
agent_portrayal,
|
|
44
|
+
propertylayer_portrayal,
|
|
45
|
+
dependencies: list[any] | None = None,
|
|
46
|
+
):
|
|
47
|
+
"""Create a Matplotlib-based space visualization component."""
|
|
29
48
|
update_counter.get()
|
|
30
49
|
space_fig = Figure()
|
|
31
50
|
space_ax = space_fig.subplots()
|
|
32
51
|
space = getattr(model, "grid", None)
|
|
33
52
|
if space is None:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if isinstance(space, mesa.space.
|
|
37
|
-
|
|
53
|
+
space = getattr(model, "space", None)
|
|
54
|
+
|
|
55
|
+
if isinstance(space, mesa.space._Grid):
|
|
56
|
+
_draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model)
|
|
38
57
|
elif isinstance(space, mesa.space.ContinuousSpace):
|
|
39
|
-
_draw_continuous_space(space, space_ax, agent_portrayal)
|
|
58
|
+
_draw_continuous_space(space, space_ax, agent_portrayal, model)
|
|
59
|
+
elif isinstance(space, mesa.space.NetworkGrid):
|
|
60
|
+
_draw_network_grid(space, space_ax, agent_portrayal)
|
|
40
61
|
elif isinstance(space, VoronoiGrid):
|
|
41
62
|
_draw_voronoi(space, space_ax, agent_portrayal)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
# matplotlib scatter does not allow for multiple shapes in one call
|
|
48
|
-
def _split_and_scatter(portray_data, space_ax):
|
|
49
|
-
grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
|
|
50
|
-
|
|
51
|
-
# Extract data from the dictionary
|
|
52
|
-
x = portray_data["x"]
|
|
53
|
-
y = portray_data["y"]
|
|
54
|
-
s = portray_data["s"]
|
|
55
|
-
c = portray_data["c"]
|
|
56
|
-
m = portray_data["m"]
|
|
57
|
-
|
|
58
|
-
if not (len(x) == len(y) == len(s) == len(c) == len(m)):
|
|
59
|
-
raise ValueError(
|
|
60
|
-
"Length mismatch in portrayal data lists: "
|
|
61
|
-
f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
|
|
62
|
-
f"color: {len(c)}, marker: {len(m)}"
|
|
63
|
-
)
|
|
63
|
+
elif space is None and propertylayer_portrayal:
|
|
64
|
+
draw_property_layers(space_ax, space, propertylayer_portrayal, model)
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
grouped_data[marker]["x"].append(x[i])
|
|
69
|
-
grouped_data[marker]["y"].append(y[i])
|
|
70
|
-
grouped_data[marker]["s"].append(s[i])
|
|
71
|
-
grouped_data[marker]["c"].append(c[i])
|
|
66
|
+
solara.FigureMatplotlib(
|
|
67
|
+
space_fig, format="png", bbox_inches="tight", dependencies=dependencies
|
|
68
|
+
)
|
|
72
69
|
|
|
73
|
-
# Plot each group with the same marker
|
|
74
|
-
for marker, data in grouped_data.items():
|
|
75
|
-
space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
|
|
76
70
|
|
|
71
|
+
def draw_property_layers(ax, space, propertylayer_portrayal, model):
|
|
72
|
+
"""Draw PropertyLayers on the given axes.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
ax (matplotlib.axes.Axes): The axes to draw on.
|
|
76
|
+
space (mesa.space._Grid): The space containing the PropertyLayers.
|
|
77
|
+
propertylayer_portrayal (dict): Dictionary of PropertyLayer portrayal specifications.
|
|
78
|
+
model (mesa.Model): The model instance.
|
|
79
|
+
"""
|
|
80
|
+
for layer_name, portrayal in propertylayer_portrayal.items():
|
|
81
|
+
layer = getattr(model, layer_name, None)
|
|
82
|
+
if not isinstance(layer, PropertyLayer):
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
data = layer.data.astype(float) if layer.data.dtype == bool else layer.data
|
|
86
|
+
width, height = data.shape if space is None else (space.width, space.height)
|
|
87
|
+
|
|
88
|
+
if space and data.shape != (width, height):
|
|
89
|
+
warnings.warn(
|
|
90
|
+
f"Layer {layer_name} dimensions ({data.shape}) do not match space dimensions ({width}, {height}).",
|
|
91
|
+
UserWarning,
|
|
92
|
+
stacklevel=2,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Get portrayal properties, or use defaults
|
|
96
|
+
alpha = portrayal.get("alpha", 1)
|
|
97
|
+
vmin = portrayal.get("vmin", np.min(data))
|
|
98
|
+
vmax = portrayal.get("vmax", np.max(data))
|
|
99
|
+
colorbar = portrayal.get("colorbar", True)
|
|
100
|
+
|
|
101
|
+
# Draw the layer
|
|
102
|
+
if "color" in portrayal:
|
|
103
|
+
rgba_color = to_rgba(portrayal["color"])
|
|
104
|
+
normalized_data = (data - vmin) / (vmax - vmin)
|
|
105
|
+
rgba_data = np.full((*data.shape, 4), rgba_color)
|
|
106
|
+
rgba_data[..., 3] *= normalized_data * alpha
|
|
107
|
+
rgba_data = np.clip(rgba_data, 0, 1)
|
|
108
|
+
cmap = LinearSegmentedColormap.from_list(
|
|
109
|
+
layer_name, [(0, 0, 0, 0), (*rgba_color[:3], alpha)]
|
|
110
|
+
)
|
|
111
|
+
im = ax.imshow(
|
|
112
|
+
rgba_data.transpose(1, 0, 2),
|
|
113
|
+
extent=(0, width, 0, height),
|
|
114
|
+
origin="lower",
|
|
115
|
+
)
|
|
116
|
+
if colorbar:
|
|
117
|
+
norm = Normalize(vmin=vmin, vmax=vmax)
|
|
118
|
+
sm = ScalarMappable(norm=norm, cmap=cmap)
|
|
119
|
+
sm.set_array([])
|
|
120
|
+
ax.figure.colorbar(sm, ax=ax, orientation="vertical")
|
|
121
|
+
|
|
122
|
+
elif "colormap" in portrayal:
|
|
123
|
+
cmap = portrayal.get("colormap", "viridis")
|
|
124
|
+
if isinstance(cmap, list):
|
|
125
|
+
cmap = LinearSegmentedColormap.from_list(layer_name, cmap)
|
|
126
|
+
im = ax.imshow(
|
|
127
|
+
data.T,
|
|
128
|
+
cmap=cmap,
|
|
129
|
+
alpha=alpha,
|
|
130
|
+
vmin=vmin,
|
|
131
|
+
vmax=vmax,
|
|
132
|
+
extent=(0, width, 0, height),
|
|
133
|
+
origin="lower",
|
|
134
|
+
)
|
|
135
|
+
if colorbar:
|
|
136
|
+
plt.colorbar(im, ax=ax, label=layer_name)
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"PropertyLayer {layer_name} portrayal must include 'color' or 'colormap'."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _draw_grid(space, space_ax, agent_portrayal, propertylayer_portrayal, model):
|
|
144
|
+
if propertylayer_portrayal:
|
|
145
|
+
draw_property_layers(space_ax, space, propertylayer_portrayal, model)
|
|
146
|
+
|
|
147
|
+
agent_data = _get_agent_data(space, agent_portrayal)
|
|
148
|
+
|
|
149
|
+
space_ax.set_xlim(0, space.width)
|
|
150
|
+
space_ax.set_ylim(0, space.height)
|
|
151
|
+
_split_and_scatter(agent_data, space_ax)
|
|
152
|
+
|
|
153
|
+
# Draw grid lines
|
|
154
|
+
for x in range(space.width + 1):
|
|
155
|
+
space_ax.axvline(x, color="gray", linestyle=":")
|
|
156
|
+
for y in range(space.height + 1):
|
|
157
|
+
space_ax.axhline(y, color="gray", linestyle=":")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _get_agent_data(space, agent_portrayal):
|
|
161
|
+
"""Helper function to get agent data for visualization."""
|
|
162
|
+
x, y, s, c, m = [], [], [], [], []
|
|
163
|
+
for agents, pos in space.coord_iter():
|
|
164
|
+
if not agents:
|
|
165
|
+
continue
|
|
166
|
+
if not isinstance(agents, list):
|
|
167
|
+
agents = [agents] # noqa PLW2901
|
|
168
|
+
for agent in agents:
|
|
169
|
+
data = agent_portrayal(agent)
|
|
170
|
+
x.append(pos[0] + 0.5) # Center the agent in the cell
|
|
171
|
+
y.append(pos[1] + 0.5) # Center the agent in the cell
|
|
172
|
+
default_size = (180 / max(space.width, space.height)) ** 2
|
|
173
|
+
s.append(data.get("size", default_size))
|
|
174
|
+
c.append(data.get("color", "tab:blue"))
|
|
175
|
+
m.append(data.get("shape", "o"))
|
|
176
|
+
return {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
77
177
|
|
|
78
|
-
def _draw_grid(space, space_ax, agent_portrayal):
|
|
79
|
-
def portray(g):
|
|
80
|
-
x = []
|
|
81
|
-
y = []
|
|
82
|
-
s = [] # size
|
|
83
|
-
c = [] # color
|
|
84
|
-
m = [] # shape
|
|
85
|
-
for i in range(g.width):
|
|
86
|
-
for j in range(g.height):
|
|
87
|
-
content = g._grid[i][j]
|
|
88
|
-
if not content:
|
|
89
|
-
continue
|
|
90
|
-
if not hasattr(content, "__iter__"):
|
|
91
|
-
# Is a single grid
|
|
92
|
-
content = [content]
|
|
93
|
-
for agent in content:
|
|
94
|
-
data = agent_portrayal(agent)
|
|
95
|
-
x.append(i)
|
|
96
|
-
y.append(j)
|
|
97
|
-
|
|
98
|
-
# This is the default value for the marker size, which auto-scales
|
|
99
|
-
# according to the grid area.
|
|
100
|
-
default_size = (180 / max(g.width, g.height)) ** 2
|
|
101
|
-
# establishing a default prevents misalignment if some agents are not given size, color, etc.
|
|
102
|
-
size = data.get("size", default_size)
|
|
103
|
-
s.append(size)
|
|
104
|
-
color = data.get("color", "tab:blue")
|
|
105
|
-
c.append(color)
|
|
106
|
-
mark = data.get("shape", "o")
|
|
107
|
-
m.append(mark)
|
|
108
|
-
out = {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
109
|
-
return out
|
|
110
178
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
179
|
+
def _split_and_scatter(portray_data, space_ax):
|
|
180
|
+
"""Helper function to split and scatter agent data."""
|
|
181
|
+
for marker in set(portray_data["m"]):
|
|
182
|
+
mask = [m == marker for m in portray_data["m"]]
|
|
183
|
+
space_ax.scatter(
|
|
184
|
+
[x for x, show in zip(portray_data["x"], mask) if show],
|
|
185
|
+
[y for y, show in zip(portray_data["y"], mask) if show],
|
|
186
|
+
s=[s for s, show in zip(portray_data["s"], mask) if show],
|
|
187
|
+
c=[c for c, show in zip(portray_data["c"], mask) if show],
|
|
188
|
+
marker=marker,
|
|
189
|
+
)
|
|
114
190
|
|
|
115
191
|
|
|
116
192
|
def _draw_network_grid(space, space_ax, agent_portrayal):
|
|
@@ -124,7 +200,7 @@ def _draw_network_grid(space, space_ax, agent_portrayal):
|
|
|
124
200
|
)
|
|
125
201
|
|
|
126
202
|
|
|
127
|
-
def _draw_continuous_space(space, space_ax, agent_portrayal):
|
|
203
|
+
def _draw_continuous_space(space, space_ax, agent_portrayal, model):
|
|
128
204
|
def portray(space):
|
|
129
205
|
x = []
|
|
130
206
|
y = []
|
|
@@ -139,15 +215,13 @@ def _draw_continuous_space(space, space_ax, agent_portrayal):
|
|
|
139
215
|
|
|
140
216
|
# This is matplotlib's default marker size
|
|
141
217
|
default_size = 20
|
|
142
|
-
# establishing a default prevents misalignment if some agents are not given size, color, etc.
|
|
143
218
|
size = data.get("size", default_size)
|
|
144
219
|
s.append(size)
|
|
145
220
|
color = data.get("color", "tab:blue")
|
|
146
221
|
c.append(color)
|
|
147
222
|
mark = data.get("shape", "o")
|
|
148
223
|
m.append(mark)
|
|
149
|
-
|
|
150
|
-
return out
|
|
224
|
+
return {"x": x, "y": y, "s": s, "c": c, "m": m}
|
|
151
225
|
|
|
152
226
|
# Determine border style based on space.torus
|
|
153
227
|
border_style = "solid" if not space.torus else (0, (5, 10))
|
|
@@ -186,8 +260,6 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
|
|
|
186
260
|
if "color" in data:
|
|
187
261
|
c.append(data["color"])
|
|
188
262
|
out = {"x": x, "y": y}
|
|
189
|
-
# This is the default value for the marker size, which auto-scales
|
|
190
|
-
# according to the grid area.
|
|
191
263
|
out["s"] = s
|
|
192
264
|
if len(c) > 0:
|
|
193
265
|
out["c"] = c
|
|
@@ -216,10 +288,19 @@ def _draw_voronoi(space, space_ax, agent_portrayal):
|
|
|
216
288
|
alpha=min(1, cell.properties[space.cell_coloring_property]),
|
|
217
289
|
c="red",
|
|
218
290
|
) # Plot filled polygon
|
|
219
|
-
space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in
|
|
291
|
+
space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in black
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
|
|
295
|
+
"""Create a plotting function for a specified measure.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
220
299
|
|
|
300
|
+
Returns:
|
|
301
|
+
function: A function that creates a PlotMatplotlib component.
|
|
302
|
+
"""
|
|
221
303
|
|
|
222
|
-
def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]): # noqa: D103
|
|
223
304
|
def MakePlotMeasure(model):
|
|
224
305
|
return PlotMatplotlib(model, measure)
|
|
225
306
|
|
|
@@ -227,7 +308,17 @@ def make_plot_measure(measure: str | dict[str, str] | list[str] | tuple[str]):
|
|
|
227
308
|
|
|
228
309
|
|
|
229
310
|
@solara.component
|
|
230
|
-
def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
|
|
311
|
+
def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
|
|
312
|
+
"""Create a Matplotlib-based plot for a measure or measures.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
model (mesa.Model): The model instance.
|
|
316
|
+
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
317
|
+
dependencies (list[any] | None): Optional dependencies for the plot.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
solara.FigureMatplotlib: A component for rendering the plot.
|
|
321
|
+
"""
|
|
231
322
|
update_counter.get()
|
|
232
323
|
fig = Figure()
|
|
233
324
|
ax = fig.subplots()
|
|
@@ -238,11 +329,14 @@ def PlotMatplotlib(model, measure, dependencies: list[any] | None = None): # no
|
|
|
238
329
|
elif isinstance(measure, dict):
|
|
239
330
|
for m, color in measure.items():
|
|
240
331
|
ax.plot(df.loc[:, m], label=m, color=color)
|
|
241
|
-
|
|
332
|
+
ax.legend(loc="best")
|
|
242
333
|
elif isinstance(measure, list | tuple):
|
|
243
334
|
for m in measure:
|
|
244
335
|
ax.plot(df.loc[:, m], label=m)
|
|
245
|
-
|
|
336
|
+
ax.legend(loc="best")
|
|
337
|
+
ax.set_xlabel("Step")
|
|
246
338
|
# Set integer x axis
|
|
247
|
-
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
|
248
|
-
solara.FigureMatplotlib(
|
|
339
|
+
ax.xaxis.set_major_locator(plt.MaxNLocator(integer=True))
|
|
340
|
+
solara.FigureMatplotlib(
|
|
341
|
+
fig, format="png", bbox_inches="tight", dependencies=dependencies
|
|
342
|
+
)
|
mesa/visualization/solara_viz.py
CHANGED
|
@@ -30,10 +30,8 @@ from typing import TYPE_CHECKING, Literal
|
|
|
30
30
|
|
|
31
31
|
import reacton.core
|
|
32
32
|
import solara
|
|
33
|
-
from solara.alias import rv
|
|
34
33
|
|
|
35
34
|
import mesa.visualization.components.altair as components_altair
|
|
36
|
-
import mesa.visualization.components.matplotlib as components_matplotlib
|
|
37
35
|
from mesa.visualization.UserParam import Slider
|
|
38
36
|
from mesa.visualization.utils import force_update, update_counter
|
|
39
37
|
|
|
@@ -41,55 +39,6 @@ if TYPE_CHECKING:
|
|
|
41
39
|
from mesa.model import Model
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
# TODO: Turn this function into a Solara component once the current_step.value
|
|
45
|
-
# dependency is passed to measure()
|
|
46
|
-
def Card(
|
|
47
|
-
model, measures, agent_portrayal, space_drawer, dependencies, color, layout_type
|
|
48
|
-
):
|
|
49
|
-
"""Create a card component for visualizing model space or measures.
|
|
50
|
-
|
|
51
|
-
Args:
|
|
52
|
-
model: The Mesa model instance
|
|
53
|
-
measures: List of measures to be plotted
|
|
54
|
-
agent_portrayal: Function to define agent appearance
|
|
55
|
-
space_drawer: Method to render agent space
|
|
56
|
-
dependencies: List of dependencies for updating the visualization
|
|
57
|
-
color: Background color of the card
|
|
58
|
-
layout_type: Type of layout (Space or Measure)
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
rv.Card: A card component containing the visualization
|
|
62
|
-
"""
|
|
63
|
-
with rv.Card(
|
|
64
|
-
style_=f"background-color: {color}; width: 100%; height: 100%"
|
|
65
|
-
) as main:
|
|
66
|
-
if "Space" in layout_type:
|
|
67
|
-
rv.CardTitle(children=["Space"])
|
|
68
|
-
if space_drawer == "default":
|
|
69
|
-
# draw with the default implementation
|
|
70
|
-
components_matplotlib.SpaceMatplotlib(
|
|
71
|
-
model, agent_portrayal, dependencies=dependencies
|
|
72
|
-
)
|
|
73
|
-
elif space_drawer == "altair":
|
|
74
|
-
components_altair.SpaceAltair(
|
|
75
|
-
model, agent_portrayal, dependencies=dependencies
|
|
76
|
-
)
|
|
77
|
-
elif space_drawer:
|
|
78
|
-
# if specified, draw agent space with an alternate renderer
|
|
79
|
-
space_drawer(model, agent_portrayal, dependencies=dependencies)
|
|
80
|
-
elif "Measure" in layout_type:
|
|
81
|
-
rv.CardTitle(children=["Measure"])
|
|
82
|
-
measure = measures[layout_type["Measure"]]
|
|
83
|
-
if callable(measure):
|
|
84
|
-
# Is a custom object
|
|
85
|
-
measure(model)
|
|
86
|
-
else:
|
|
87
|
-
components_matplotlib.PlotMatplotlib(
|
|
88
|
-
model, measure, dependencies=dependencies
|
|
89
|
-
)
|
|
90
|
-
return main
|
|
91
|
-
|
|
92
|
-
|
|
93
42
|
@solara.component
|
|
94
43
|
def SolaraViz(
|
|
95
44
|
model: Model | solara.Reactive[Model],
|
|
@@ -207,10 +156,16 @@ def ComponentsView(
|
|
|
207
156
|
model: Model instance to pass to each component
|
|
208
157
|
"""
|
|
209
158
|
wrapped_components = [_wrap_component(component) for component in components]
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
159
|
+
items = [component(model) for component in wrapped_components]
|
|
160
|
+
grid_layout_initial = make_initial_grid_layout(num_components=len(items))
|
|
161
|
+
grid_layout, set_grid_layout = solara.use_state(grid_layout_initial)
|
|
162
|
+
solara.GridDraggable(
|
|
163
|
+
items=items,
|
|
164
|
+
grid_layout=grid_layout,
|
|
165
|
+
resizable=True,
|
|
166
|
+
draggable=True,
|
|
167
|
+
on_grid_layout=set_grid_layout,
|
|
168
|
+
)
|
|
214
169
|
|
|
215
170
|
|
|
216
171
|
JupyterViz = SolaraViz
|
|
@@ -225,6 +180,7 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
|
|
|
225
180
|
play_interval (int, optional): Interval for playing the model steps in milliseconds.
|
|
226
181
|
"""
|
|
227
182
|
playing = solara.use_reactive(False)
|
|
183
|
+
running = solara.use_reactive(True)
|
|
228
184
|
original_model = solara.use_reactive(None)
|
|
229
185
|
|
|
230
186
|
def save_initial_model():
|
|
@@ -236,19 +192,23 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
|
|
|
236
192
|
solara.use_effect(save_initial_model, [model.value])
|
|
237
193
|
|
|
238
194
|
async def step():
|
|
239
|
-
while playing.value:
|
|
195
|
+
while playing.value and running.value:
|
|
240
196
|
await asyncio.sleep(play_interval / 1000)
|
|
241
197
|
do_step()
|
|
242
198
|
|
|
243
|
-
solara.lab.use_task(
|
|
199
|
+
solara.lab.use_task(
|
|
200
|
+
step, dependencies=[playing.value, running.value], prefer_threaded=False
|
|
201
|
+
)
|
|
244
202
|
|
|
245
203
|
def do_step():
|
|
246
204
|
"""Advance the model by one step."""
|
|
247
205
|
model.value.step()
|
|
206
|
+
running.value = model.value.running
|
|
248
207
|
|
|
249
208
|
def do_reset():
|
|
250
209
|
"""Reset the model to its initial state."""
|
|
251
210
|
playing.value = False
|
|
211
|
+
running.value = True
|
|
252
212
|
model.value = copy.deepcopy(original_model.value)
|
|
253
213
|
|
|
254
214
|
def do_play_pause():
|
|
@@ -261,9 +221,13 @@ def ModelController(model: solara.Reactive[Model], play_interval=100):
|
|
|
261
221
|
label="▶" if not playing.value else "❚❚",
|
|
262
222
|
color="primary",
|
|
263
223
|
on_click=do_play_pause,
|
|
224
|
+
disabled=not running.value,
|
|
264
225
|
)
|
|
265
226
|
solara.Button(
|
|
266
|
-
label="Step",
|
|
227
|
+
label="Step",
|
|
228
|
+
color="primary",
|
|
229
|
+
on_click=do_step,
|
|
230
|
+
disabled=playing.value or not running.value,
|
|
267
231
|
)
|
|
268
232
|
|
|
269
233
|
|
|
@@ -439,11 +403,11 @@ def UserInputs(user_params, on_change=None):
|
|
|
439
403
|
raise ValueError(f"{input_type} is not a supported input type")
|
|
440
404
|
|
|
441
405
|
|
|
442
|
-
def make_initial_grid_layout(
|
|
406
|
+
def make_initial_grid_layout(num_components):
|
|
443
407
|
"""Create an initial grid layout for visualization components.
|
|
444
408
|
|
|
445
409
|
Args:
|
|
446
|
-
|
|
410
|
+
num_components: Number of components to display
|
|
447
411
|
|
|
448
412
|
Returns:
|
|
449
413
|
list: Initial grid layout configuration
|
|
@@ -457,7 +421,7 @@ def make_initial_grid_layout(layout_types):
|
|
|
457
421
|
"x": 6 * (i % 2),
|
|
458
422
|
"y": 16 * (i - i % 2),
|
|
459
423
|
}
|
|
460
|
-
for i in range(
|
|
424
|
+
for i in range(num_components)
|
|
461
425
|
]
|
|
462
426
|
|
|
463
427
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: Mesa
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.0b1
|
|
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
|
|
@@ -22,30 +23,60 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
22
23
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
24
|
Classifier: Topic :: Scientific/Engineering :: Artificial Life
|
|
24
25
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist: click
|
|
26
|
-
Requires-Dist: cookiecutter
|
|
27
|
-
Requires-Dist: matplotlib
|
|
28
|
-
Requires-Dist: networkx
|
|
29
26
|
Requires-Dist: numpy
|
|
30
27
|
Requires-Dist: pandas
|
|
31
|
-
Requires-Dist: solara
|
|
32
28
|
Requires-Dist: tqdm
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: cookiecutter; extra == 'all'
|
|
31
|
+
Requires-Dist: ipython; extra == 'all'
|
|
32
|
+
Requires-Dist: matplotlib; extra == 'all'
|
|
33
|
+
Requires-Dist: myst-nb; extra == 'all'
|
|
34
|
+
Requires-Dist: myst-parser; extra == 'all'
|
|
35
|
+
Requires-Dist: networkx; extra == 'all'
|
|
36
|
+
Requires-Dist: pydata-sphinx-theme; extra == 'all'
|
|
37
|
+
Requires-Dist: pytest; extra == 'all'
|
|
38
|
+
Requires-Dist: pytest-cov; extra == 'all'
|
|
39
|
+
Requires-Dist: pytest-mock; extra == 'all'
|
|
40
|
+
Requires-Dist: ruff; extra == 'all'
|
|
41
|
+
Requires-Dist: scipy; extra == 'all'
|
|
42
|
+
Requires-Dist: seaborn; extra == 'all'
|
|
43
|
+
Requires-Dist: solara; extra == 'all'
|
|
44
|
+
Requires-Dist: sphinx; extra == 'all'
|
|
33
45
|
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: cookiecutter; extra == 'dev'
|
|
47
|
+
Requires-Dist: matplotlib; extra == 'dev'
|
|
48
|
+
Requires-Dist: networkx; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
34
50
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
35
51
|
Requires-Dist: pytest-mock; extra == 'dev'
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
52
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
53
|
+
Requires-Dist: solara; extra == 'dev'
|
|
38
54
|
Requires-Dist: sphinx; extra == 'dev'
|
|
39
55
|
Provides-Extra: docs
|
|
40
56
|
Requires-Dist: ipython; extra == 'docs'
|
|
57
|
+
Requires-Dist: matplotlib; extra == 'docs'
|
|
41
58
|
Requires-Dist: myst-nb; extra == 'docs'
|
|
42
59
|
Requires-Dist: myst-parser; extra == 'docs'
|
|
60
|
+
Requires-Dist: networkx; extra == 'docs'
|
|
43
61
|
Requires-Dist: pydata-sphinx-theme; extra == 'docs'
|
|
44
62
|
Requires-Dist: seaborn; extra == 'docs'
|
|
63
|
+
Requires-Dist: solara; extra == 'docs'
|
|
45
64
|
Requires-Dist: sphinx; extra == 'docs'
|
|
46
65
|
Provides-Extra: examples
|
|
47
|
-
Requires-Dist:
|
|
66
|
+
Requires-Dist: matplotlib; extra == 'examples'
|
|
67
|
+
Requires-Dist: networkx; extra == 'examples'
|
|
68
|
+
Requires-Dist: pytest; extra == 'examples'
|
|
48
69
|
Requires-Dist: scipy; extra == 'examples'
|
|
70
|
+
Requires-Dist: solara; extra == 'examples'
|
|
71
|
+
Provides-Extra: network
|
|
72
|
+
Requires-Dist: networkx; extra == 'network'
|
|
73
|
+
Provides-Extra: rec
|
|
74
|
+
Requires-Dist: matplotlib; extra == 'rec'
|
|
75
|
+
Requires-Dist: networkx; extra == 'rec'
|
|
76
|
+
Requires-Dist: solara; extra == 'rec'
|
|
77
|
+
Provides-Extra: viz
|
|
78
|
+
Requires-Dist: matplotlib; extra == 'viz'
|
|
79
|
+
Requires-Dist: solara; extra == 'viz'
|
|
49
80
|
Description-Content-Type: text/markdown
|
|
50
81
|
|
|
51
82
|
# Mesa: Agent-based modeling in Python
|
|
@@ -78,7 +109,7 @@ can be displayed in browser windows or Jupyter.*
|
|
|
78
109
|
|
|
79
110
|
## Using Mesa
|
|
80
111
|
|
|
81
|
-
To install our latest stable release (2.
|
|
112
|
+
To install our latest stable release (2.4.x), run:
|
|
82
113
|
|
|
83
114
|
``` bash
|
|
84
115
|
pip install -U mesa
|
|
@@ -89,6 +120,17 @@ To install our latest pre-release (3.0.0 alpha), run:
|
|
|
89
120
|
``` bash
|
|
90
121
|
pip install -U --pre mesa
|
|
91
122
|
```
|
|
123
|
+
With Mesa 3.0, we don't install all our dependencies anymore by default.
|
|
124
|
+
```bash
|
|
125
|
+
# You can customize the additional dependencies you need, if you want. Available are:
|
|
126
|
+
pip install -U --pre mesa[network,viz]
|
|
127
|
+
|
|
128
|
+
# This is equivalent to our recommended dependencies:
|
|
129
|
+
pip install -U --pre mesa[rec]
|
|
130
|
+
|
|
131
|
+
# To install all, including developer, dependencies:
|
|
132
|
+
pip install -U --pre mesa[all]
|
|
133
|
+
```
|
|
92
134
|
|
|
93
135
|
You can also use `pip` to install the latest GitHub version:
|
|
94
136
|
|
|
@@ -107,13 +149,13 @@ For resources or help on using Mesa, check out the following:
|
|
|
107
149
|
|
|
108
150
|
- [Intro to Mesa Tutorial](http://mesa.readthedocs.org/en/stable/tutorials/intro_tutorial.html) (An introductory model, the Boltzmann
|
|
109
151
|
Wealth Model, for beginners or those new to Mesa.)
|
|
110
|
-
- [Visualization Tutorial](https://mesa.readthedocs.io/
|
|
152
|
+
- [Visualization Tutorial](https://mesa.readthedocs.io/stable/tutorials/visualization_tutorial.html) (An introduction into our Solara visualization)
|
|
111
153
|
- [Complexity Explorer Tutorial](https://www.complexityexplorer.org/courses/172-agent-based-models-with-python-an-introduction-to-mesa) (An advanced-beginner model,
|
|
112
154
|
SugarScape with Traders, with instructional videos)
|
|
113
|
-
- [Mesa Examples](https://github.com/projectmesa/mesa-examples
|
|
155
|
+
- [Mesa Examples](https://github.com/projectmesa/mesa-examples) (A repository of seminal ABMs using Mesa and
|
|
114
156
|
examples of employing specific Mesa Features)
|
|
115
157
|
- [Docs](http://mesa.readthedocs.org/) (Mesa's documentation, API and useful snippets)
|
|
116
|
-
- [Development version docs](https://mesa.readthedocs.io/
|
|
158
|
+
- [Development version docs](https://mesa.readthedocs.io/latest/) (the latest version docs if you're using a pre-release Mesa version)
|
|
117
159
|
- [Discussions](https://github.com/projectmesa/mesa/discussions) (GitHub threaded discussions about Mesa)
|
|
118
160
|
- [Matrix Chat](https://matrix.to/#/#project-mesa:matrix.org) (Chat Forum via Matrix to talk about Mesa)
|
|
119
161
|
|