Mesa 2.4.0__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 +105 -92
- mesa/batchrunner.py +55 -31
- mesa/datacollection.py +10 -14
- 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 +69 -38
- mesa/experimental/devs/examples/wolf_sheep.py +42 -43
- mesa/experimental/devs/simulator.py +57 -16
- mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -99
- mesa/model.py +136 -78
- mesa/space.py +208 -148
- 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.4.0.dist-info → mesa-3.0.0.dist-info}/METADATA +62 -17
- mesa-3.0.0.dist-info/RECORD +95 -0
- mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
- mesa-2.4.0.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.4.0.dist-info/RECORD +0 -45
- /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
- {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
- {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
mesa/__init__.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Mesa Agent-Based Modeling Framework
|
|
1
|
+
"""Mesa Agent-Based Modeling Framework.
|
|
3
2
|
|
|
4
3
|
Core Objects: Model, and Agent.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
import datetime
|
|
8
7
|
|
|
8
|
+
import mesa.experimental as experimental
|
|
9
9
|
import mesa.space as space
|
|
10
10
|
import mesa.time as time
|
|
11
|
-
import mesa.visualization as visualization
|
|
12
11
|
from mesa.agent import Agent
|
|
13
12
|
from mesa.batchrunner import batch_run
|
|
14
13
|
from mesa.datacollection import DataCollector
|
|
@@ -19,14 +18,13 @@ __all__ = [
|
|
|
19
18
|
"Agent",
|
|
20
19
|
"time",
|
|
21
20
|
"space",
|
|
22
|
-
"visualization",
|
|
23
21
|
"DataCollector",
|
|
24
22
|
"batch_run",
|
|
25
23
|
"experimental",
|
|
26
24
|
]
|
|
27
25
|
|
|
28
26
|
__title__ = "mesa"
|
|
29
|
-
__version__ = "
|
|
27
|
+
__version__ = "3.0.0"
|
|
30
28
|
__license__ = "Apache 2.0"
|
|
31
29
|
_this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
|
|
32
30
|
__copyright__ = f"Copyright {_this_year} Project Mesa Team"
|
mesa/agent.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The agent class for Mesa framework.
|
|
1
|
+
"""Agent related classes.
|
|
3
2
|
|
|
4
|
-
Core Objects: Agent
|
|
3
|
+
Core Objects: Agent and AgentSet.
|
|
5
4
|
"""
|
|
6
5
|
|
|
7
6
|
# Mypy; for the `|` operator purpose
|
|
@@ -10,15 +9,19 @@ from __future__ import annotations
|
|
|
10
9
|
|
|
11
10
|
import contextlib
|
|
12
11
|
import copy
|
|
12
|
+
import functools
|
|
13
|
+
import itertools
|
|
13
14
|
import operator
|
|
14
15
|
import warnings
|
|
15
16
|
import weakref
|
|
16
17
|
from collections import defaultdict
|
|
17
|
-
from collections.abc import Hashable, Iterable, Iterator, MutableSet, Sequence
|
|
18
|
+
from collections.abc import Callable, Hashable, Iterable, Iterator, MutableSet, Sequence
|
|
18
19
|
from random import Random
|
|
19
20
|
|
|
20
21
|
# mypy
|
|
21
|
-
from typing import TYPE_CHECKING, Any,
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
22
25
|
|
|
23
26
|
if TYPE_CHECKING:
|
|
24
27
|
# We ensure that these are not imported during runtime to prevent cyclic
|
|
@@ -28,77 +31,99 @@ if TYPE_CHECKING:
|
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
class Agent:
|
|
31
|
-
"""
|
|
32
|
-
Base class for a model agent in Mesa.
|
|
34
|
+
"""Base class for a model agent in Mesa.
|
|
33
35
|
|
|
34
36
|
Attributes:
|
|
35
|
-
unique_id (int): A unique identifier for this agent.
|
|
36
37
|
model (Model): A reference to the model instance.
|
|
37
|
-
|
|
38
|
+
unique_id (int): A unique identifier for this agent.
|
|
39
|
+
pos (Position): A reference to the position where this agent is located.
|
|
40
|
+
|
|
41
|
+
Notes:
|
|
42
|
+
unique_id is unique relative to a model instance and starts from 1
|
|
43
|
+
|
|
38
44
|
"""
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
# this is a class level attribute
|
|
47
|
+
# it is a dictionary, indexed by model instance
|
|
48
|
+
# so, unique_id is unique relative to a model, and counting starts from 1
|
|
49
|
+
_ids = defaultdict(functools.partial(itertools.count, 1))
|
|
50
|
+
|
|
51
|
+
def __init__(self, model: Model, *args, **kwargs) -> None:
|
|
52
|
+
"""Create a new agent.
|
|
43
53
|
|
|
44
54
|
Args:
|
|
45
|
-
unique_id (int): A unique identifier for this agent.
|
|
46
55
|
model (Model): The model instance in which the agent exists.
|
|
56
|
+
args: passed on to super
|
|
57
|
+
kwargs: passed on to super
|
|
58
|
+
|
|
59
|
+
Notes:
|
|
60
|
+
to make proper use of python's super, in each class remove the arguments and
|
|
61
|
+
keyword arguments you need and pass on the rest to super
|
|
62
|
+
|
|
47
63
|
"""
|
|
48
|
-
|
|
49
|
-
self.model = model
|
|
50
|
-
self.pos: Position | None = None
|
|
64
|
+
super().__init__(*args, **kwargs)
|
|
51
65
|
|
|
66
|
+
self.model: Model = model
|
|
67
|
+
self.unique_id: int = next(self._ids[model])
|
|
68
|
+
self.pos: Position | None = None
|
|
52
69
|
self.model.register_agent(self)
|
|
53
70
|
|
|
54
71
|
def remove(self) -> None:
|
|
55
|
-
"""Remove and delete the agent from the model.
|
|
72
|
+
"""Remove and delete the agent from the model.
|
|
73
|
+
|
|
74
|
+
Notes:
|
|
75
|
+
If you need to do additional cleanup when removing an agent by for example removing
|
|
76
|
+
it from a space, consider extending this method in your own agent class.
|
|
77
|
+
|
|
78
|
+
"""
|
|
56
79
|
with contextlib.suppress(KeyError):
|
|
57
80
|
self.model.deregister_agent(self)
|
|
58
81
|
|
|
59
82
|
def step(self) -> None:
|
|
60
83
|
"""A single step of the agent."""
|
|
61
84
|
|
|
62
|
-
def advance(self) -> None:
|
|
85
|
+
def advance(self) -> None: # noqa: D102
|
|
63
86
|
pass
|
|
64
87
|
|
|
65
88
|
@property
|
|
66
89
|
def random(self) -> Random:
|
|
90
|
+
"""Return a seeded stdlib rng."""
|
|
67
91
|
return self.model.random
|
|
68
92
|
|
|
93
|
+
@property
|
|
94
|
+
def rng(self) -> np.random.Generator:
|
|
95
|
+
"""Return a seeded np.random rng."""
|
|
96
|
+
return self.model.rng
|
|
97
|
+
|
|
69
98
|
|
|
70
99
|
class AgentSet(MutableSet, Sequence):
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
extends both MutableSet and Sequence, providing set-like functionality with order preservation and
|
|
100
|
+
"""A collection class that represents an ordered set of agents within an agent-based model (ABM).
|
|
101
|
+
|
|
102
|
+
This class extends both MutableSet and Sequence, providing set-like functionality with order preservation and
|
|
74
103
|
sequence operations.
|
|
75
104
|
|
|
76
105
|
Attributes:
|
|
77
106
|
model (Model): The ABM model instance to which this AgentSet belongs.
|
|
78
107
|
|
|
79
|
-
|
|
80
|
-
__len__, __iter__, __contains__, select, shuffle, sort, _update, do, get, __getitem__,
|
|
81
|
-
add, discard, remove, __getstate__, __setstate__, random
|
|
82
|
-
|
|
83
|
-
Note:
|
|
108
|
+
Notes:
|
|
84
109
|
The AgentSet maintains weak references to agents, allowing for efficient management of agent lifecycles
|
|
85
110
|
without preventing garbage collection. It is associated with a specific model instance, enabling
|
|
86
111
|
interactions with the model's environment and other agents.The implementation uses a WeakKeyDictionary to store agents,
|
|
87
112
|
which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
|
|
88
113
|
"""
|
|
89
114
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def __init__(self, agents: Iterable[Agent], model: Model):
|
|
93
|
-
"""
|
|
94
|
-
Initializes the AgentSet with a collection of agents and a reference to the model.
|
|
115
|
+
def __init__(self, agents: Iterable[Agent], random: Random | None = None):
|
|
116
|
+
"""Initializes the AgentSet with a collection of agents and a reference to the model.
|
|
95
117
|
|
|
96
118
|
Args:
|
|
97
119
|
agents (Iterable[Agent]): An iterable of Agent objects to be included in the set.
|
|
98
|
-
|
|
120
|
+
random (Random): the random number generator
|
|
99
121
|
"""
|
|
100
|
-
|
|
101
|
-
|
|
122
|
+
if random is None:
|
|
123
|
+
random = (
|
|
124
|
+
Random()
|
|
125
|
+
) # FIXME see issue 1981, how to get the central rng from model
|
|
126
|
+
self.random = random
|
|
102
127
|
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
103
128
|
|
|
104
129
|
def __len__(self) -> int:
|
|
@@ -121,8 +146,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
121
146
|
agent_type: type[Agent] | None = None,
|
|
122
147
|
n: int | None = None,
|
|
123
148
|
) -> AgentSet:
|
|
124
|
-
"""
|
|
125
|
-
Select a subset of agents from the AgentSet based on a filter function and/or quantity limit.
|
|
149
|
+
"""Select a subset of agents from the AgentSet based on a filter function and/or quantity limit.
|
|
126
150
|
|
|
127
151
|
Args:
|
|
128
152
|
filter_func (Callable[[Agent], bool], optional): A function that takes an Agent and returns True if the
|
|
@@ -132,6 +156,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
132
156
|
- If a float between 0 and 1, at most that fraction of original the agents are selected.
|
|
133
157
|
inplace (bool, optional): If True, modifies the current AgentSet; otherwise, returns a new AgentSet. Defaults to False.
|
|
134
158
|
agent_type (type[Agent], optional): The class type of the agents to select. Defaults to None, meaning no type filtering is applied.
|
|
159
|
+
n (int): deprecated, use at_most instead
|
|
135
160
|
|
|
136
161
|
Returns:
|
|
137
162
|
AgentSet: A new AgentSet containing the selected agents, unless inplace is True, in which case the current AgentSet is updated.
|
|
@@ -169,11 +194,10 @@ class AgentSet(MutableSet, Sequence):
|
|
|
169
194
|
|
|
170
195
|
agents = agent_generator(filter_func, agent_type, at_most)
|
|
171
196
|
|
|
172
|
-
return AgentSet(agents, self.
|
|
197
|
+
return AgentSet(agents, self.random) if not inplace else self._update(agents)
|
|
173
198
|
|
|
174
199
|
def shuffle(self, inplace: bool = False) -> AgentSet:
|
|
175
|
-
"""
|
|
176
|
-
Randomly shuffle the order of agents in the AgentSet.
|
|
200
|
+
"""Randomly shuffle the order of agents in the AgentSet.
|
|
177
201
|
|
|
178
202
|
Args:
|
|
179
203
|
inplace (bool, optional): If True, shuffles the agents in the current AgentSet; otherwise, returns a new shuffled AgentSet. Defaults to False.
|
|
@@ -193,7 +217,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
193
217
|
return self
|
|
194
218
|
else:
|
|
195
219
|
return AgentSet(
|
|
196
|
-
(agent for ref in weakrefs if (agent := ref()) is not None), self.
|
|
220
|
+
(agent for ref in weakrefs if (agent := ref()) is not None), self.random
|
|
197
221
|
)
|
|
198
222
|
|
|
199
223
|
def sort(
|
|
@@ -202,8 +226,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
202
226
|
ascending: bool = False,
|
|
203
227
|
inplace: bool = False,
|
|
204
228
|
) -> AgentSet:
|
|
205
|
-
"""
|
|
206
|
-
Sort the agents in the AgentSet based on a specified attribute or custom function.
|
|
229
|
+
"""Sort the agents in the AgentSet based on a specified attribute or custom function.
|
|
207
230
|
|
|
208
231
|
Args:
|
|
209
232
|
key (Callable[[Agent], Any] | str): A function or attribute name based on which the agents are sorted.
|
|
@@ -219,22 +242,21 @@ class AgentSet(MutableSet, Sequence):
|
|
|
219
242
|
sorted_agents = sorted(self._agents.keys(), key=key, reverse=not ascending)
|
|
220
243
|
|
|
221
244
|
return (
|
|
222
|
-
AgentSet(sorted_agents, self.
|
|
245
|
+
AgentSet(sorted_agents, self.random)
|
|
223
246
|
if not inplace
|
|
224
247
|
else self._update(sorted_agents)
|
|
225
248
|
)
|
|
226
249
|
|
|
227
250
|
def _update(self, agents: Iterable[Agent]):
|
|
228
251
|
"""Update the AgentSet with a new set of agents.
|
|
252
|
+
|
|
229
253
|
This is a private method primarily used internally by other methods like select, shuffle, and sort.
|
|
230
254
|
"""
|
|
231
|
-
|
|
232
255
|
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
233
256
|
return self
|
|
234
257
|
|
|
235
258
|
def do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
|
|
236
|
-
"""
|
|
237
|
-
Invoke a method or function on each agent in the AgentSet.
|
|
259
|
+
"""Invoke a method or function on each agent in the AgentSet.
|
|
238
260
|
|
|
239
261
|
Args:
|
|
240
262
|
method (str, callable): the callable to do on each agent
|
|
@@ -279,21 +301,22 @@ class AgentSet(MutableSet, Sequence):
|
|
|
279
301
|
|
|
280
302
|
It's a fast, optimized version of calling shuffle() followed by do().
|
|
281
303
|
"""
|
|
282
|
-
|
|
283
|
-
self.random.shuffle(
|
|
304
|
+
weakrefs = list(self._agents.keyrefs())
|
|
305
|
+
self.random.shuffle(weakrefs)
|
|
284
306
|
|
|
285
307
|
if isinstance(method, str):
|
|
286
|
-
for
|
|
287
|
-
|
|
308
|
+
for ref in weakrefs:
|
|
309
|
+
if (agent := ref()) is not None:
|
|
310
|
+
getattr(agent, method)(*args, **kwargs)
|
|
288
311
|
else:
|
|
289
|
-
for
|
|
290
|
-
|
|
312
|
+
for ref in weakrefs:
|
|
313
|
+
if (agent := ref()) is not None:
|
|
314
|
+
method(agent, *args, **kwargs)
|
|
291
315
|
|
|
292
316
|
return self
|
|
293
317
|
|
|
294
318
|
def map(self, method: str | Callable, *args, **kwargs) -> list[Any]:
|
|
295
|
-
"""
|
|
296
|
-
Invoke a method or function on each agent in the AgentSet and return the results.
|
|
319
|
+
"""Invoke a method or function on each agent in the AgentSet and return the results.
|
|
297
320
|
|
|
298
321
|
Args:
|
|
299
322
|
method (str, callable): the callable to apply on each agent
|
|
@@ -324,8 +347,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
324
347
|
return res
|
|
325
348
|
|
|
326
349
|
def agg(self, attribute: str, func: Callable) -> Any:
|
|
327
|
-
"""
|
|
328
|
-
Aggregate an attribute of all agents in the AgentSet using a specified function.
|
|
350
|
+
"""Aggregate an attribute of all agents in the AgentSet using a specified function.
|
|
329
351
|
|
|
330
352
|
Args:
|
|
331
353
|
attribute (str): The name of the attribute to aggregate.
|
|
@@ -359,8 +381,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
359
381
|
handle_missing="error",
|
|
360
382
|
default_value=None,
|
|
361
383
|
):
|
|
362
|
-
"""
|
|
363
|
-
Retrieve the specified attribute(s) from each agent in the AgentSet.
|
|
384
|
+
"""Retrieve the specified attribute(s) from each agent in the AgentSet.
|
|
364
385
|
|
|
365
386
|
Args:
|
|
366
387
|
attr_names (str | list[str]): The name(s) of the attribute(s) to retrieve from each agent.
|
|
@@ -407,8 +428,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
407
428
|
)
|
|
408
429
|
|
|
409
430
|
def set(self, attr_name: str, value: Any) -> AgentSet:
|
|
410
|
-
"""
|
|
411
|
-
Set a specified attribute to a given value for all agents in the AgentSet.
|
|
431
|
+
"""Set a specified attribute to a given value for all agents in the AgentSet.
|
|
412
432
|
|
|
413
433
|
Args:
|
|
414
434
|
attr_name (str): The name of the attribute to set.
|
|
@@ -422,8 +442,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
422
442
|
return self
|
|
423
443
|
|
|
424
444
|
def __getitem__(self, item: int | slice) -> Agent:
|
|
425
|
-
"""
|
|
426
|
-
Retrieve an agent or a slice of agents from the AgentSet.
|
|
445
|
+
"""Retrieve an agent or a slice of agents from the AgentSet.
|
|
427
446
|
|
|
428
447
|
Args:
|
|
429
448
|
item (int | slice): The index or slice for selecting agents.
|
|
@@ -434,8 +453,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
434
453
|
return list(self._agents.keys())[item]
|
|
435
454
|
|
|
436
455
|
def add(self, agent: Agent):
|
|
437
|
-
"""
|
|
438
|
-
Add an agent to the AgentSet.
|
|
456
|
+
"""Add an agent to the AgentSet.
|
|
439
457
|
|
|
440
458
|
Args:
|
|
441
459
|
agent (Agent): The agent to add to the set.
|
|
@@ -446,8 +464,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
446
464
|
self._agents[agent] = None
|
|
447
465
|
|
|
448
466
|
def discard(self, agent: Agent):
|
|
449
|
-
"""
|
|
450
|
-
Remove an agent from the AgentSet if it exists.
|
|
467
|
+
"""Remove an agent from the AgentSet if it exists.
|
|
451
468
|
|
|
452
469
|
This method does not raise an error if the agent is not present.
|
|
453
470
|
|
|
@@ -461,8 +478,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
461
478
|
del self._agents[agent]
|
|
462
479
|
|
|
463
480
|
def remove(self, agent: Agent):
|
|
464
|
-
"""
|
|
465
|
-
Remove an agent from the AgentSet.
|
|
481
|
+
"""Remove an agent from the AgentSet.
|
|
466
482
|
|
|
467
483
|
This method raises an error if the agent is not present.
|
|
468
484
|
|
|
@@ -475,37 +491,24 @@ class AgentSet(MutableSet, Sequence):
|
|
|
475
491
|
del self._agents[agent]
|
|
476
492
|
|
|
477
493
|
def __getstate__(self):
|
|
478
|
-
"""
|
|
479
|
-
Retrieve the state of the AgentSet for serialization.
|
|
494
|
+
"""Retrieve the state of the AgentSet for serialization.
|
|
480
495
|
|
|
481
496
|
Returns:
|
|
482
497
|
dict: A dictionary representing the state of the AgentSet.
|
|
483
498
|
"""
|
|
484
|
-
return {"agents": list(self._agents.keys()), "
|
|
499
|
+
return {"agents": list(self._agents.keys()), "random": self.random}
|
|
485
500
|
|
|
486
501
|
def __setstate__(self, state):
|
|
487
|
-
"""
|
|
488
|
-
Set the state of the AgentSet during deserialization.
|
|
502
|
+
"""Set the state of the AgentSet during deserialization.
|
|
489
503
|
|
|
490
504
|
Args:
|
|
491
505
|
state (dict): A dictionary representing the state to restore.
|
|
492
506
|
"""
|
|
493
|
-
self.
|
|
507
|
+
self.random = state["random"]
|
|
494
508
|
self._update(state["agents"])
|
|
495
509
|
|
|
496
|
-
@property
|
|
497
|
-
def random(self) -> Random:
|
|
498
|
-
"""
|
|
499
|
-
Provide access to the model's random number generator.
|
|
500
|
-
|
|
501
|
-
Returns:
|
|
502
|
-
Random: The random number generator associated with the model.
|
|
503
|
-
"""
|
|
504
|
-
return self.model.random
|
|
505
|
-
|
|
506
510
|
def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
|
|
507
|
-
"""
|
|
508
|
-
Group agents by the specified attribute or return from the callable
|
|
511
|
+
"""Group agents by the specified attribute or return from the callable.
|
|
509
512
|
|
|
510
513
|
Args:
|
|
511
514
|
by (Callable, str): used to determine what to group agents by
|
|
@@ -515,6 +518,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
515
518
|
* if ``by`` is a str, it should refer to an attribute on the agent and the value
|
|
516
519
|
of this attribute will be used for grouping
|
|
517
520
|
result_type (str, optional): The datatype for the resulting groups {"agentset", "list"}
|
|
521
|
+
|
|
518
522
|
Returns:
|
|
519
523
|
GroupBy
|
|
520
524
|
|
|
@@ -535,7 +539,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
535
539
|
|
|
536
540
|
if result_type == "agentset":
|
|
537
541
|
return GroupBy(
|
|
538
|
-
{k: AgentSet(v,
|
|
542
|
+
{k: AgentSet(v, random=self.random) for k, v in groups.items()}
|
|
539
543
|
)
|
|
540
544
|
else:
|
|
541
545
|
return GroupBy(groups)
|
|
@@ -546,8 +550,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
546
550
|
|
|
547
551
|
|
|
548
552
|
class GroupBy:
|
|
549
|
-
"""Helper class for AgentSet.groupby
|
|
550
|
-
|
|
553
|
+
"""Helper class for AgentSet.groupby.
|
|
551
554
|
|
|
552
555
|
Attributes:
|
|
553
556
|
groups (dict): A dictionary with the group_name as key and group as values
|
|
@@ -555,6 +558,12 @@ class GroupBy:
|
|
|
555
558
|
"""
|
|
556
559
|
|
|
557
560
|
def __init__(self, groups: dict[Any, list | AgentSet]):
|
|
561
|
+
"""Initialize a GroupBy instance.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
groups (dict): A dictionary with the group_name as key and group as values
|
|
565
|
+
|
|
566
|
+
"""
|
|
558
567
|
self.groups: dict[Any, list | AgentSet] = groups
|
|
559
568
|
|
|
560
569
|
def map(self, method: Callable | str, *args, **kwargs) -> dict[Any, Any]:
|
|
@@ -567,6 +576,8 @@ class GroupBy:
|
|
|
567
576
|
* if ``method`` is a str, it should refer to a method on the group
|
|
568
577
|
|
|
569
578
|
Additional arguments and keyword arguments will be passed on to the callable.
|
|
579
|
+
args: arguments to pass to the callable
|
|
580
|
+
kwargs: keyword arguments to pass to the callable
|
|
570
581
|
|
|
571
582
|
Returns:
|
|
572
583
|
dict with group_name as key and the return of the method as value
|
|
@@ -584,7 +595,7 @@ class GroupBy:
|
|
|
584
595
|
return {k: method(v, *args, **kwargs) for k, v in self.groups.items()}
|
|
585
596
|
|
|
586
597
|
def do(self, method: Callable | str, *args, **kwargs) -> GroupBy:
|
|
587
|
-
"""Apply the specified callable to each group
|
|
598
|
+
"""Apply the specified callable to each group.
|
|
588
599
|
|
|
589
600
|
Args:
|
|
590
601
|
method (Callable, str): The callable to apply to each group,
|
|
@@ -593,6 +604,8 @@ class GroupBy:
|
|
|
593
604
|
* if ``method`` is a str, it should refer to a method on the group
|
|
594
605
|
|
|
595
606
|
Additional arguments and keyword arguments will be passed on to the callable.
|
|
607
|
+
args: arguments to pass to the callable
|
|
608
|
+
kwargs: keyword arguments to pass to the callable
|
|
596
609
|
|
|
597
610
|
Returns:
|
|
598
611
|
the original GroupBy instance
|
|
@@ -634,8 +647,8 @@ class GroupBy:
|
|
|
634
647
|
for group_name, group in self.groups.items()
|
|
635
648
|
}
|
|
636
649
|
|
|
637
|
-
def __iter__(self):
|
|
650
|
+
def __iter__(self): # noqa: D105
|
|
638
651
|
return iter(self.groups.items())
|
|
639
652
|
|
|
640
|
-
def __len__(self):
|
|
653
|
+
def __len__(self): # noqa: D105
|
|
641
654
|
return len(self.groups)
|
mesa/batchrunner.py
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
|
+
"""batchrunner for running a factorial experiment design over a model.
|
|
2
|
+
|
|
3
|
+
To take advantage of parallel execution of experiments, `batch_run` uses
|
|
4
|
+
multiprocessing if ``number_processes`` is larger than 1. It is strongly advised
|
|
5
|
+
to only run in parallel using a normal python file (so don't try to do it in a
|
|
6
|
+
jupyter notebook). Moreover, best practice when using multiprocessing is to
|
|
7
|
+
put the code inside an ``if __name__ == '__main__':`` code black as shown below::
|
|
8
|
+
|
|
9
|
+
from mesa.batchrunner import batch_run
|
|
10
|
+
|
|
11
|
+
params = {"width": 10, "height": 10, "N": range(10, 500, 10)}
|
|
12
|
+
|
|
13
|
+
if __name__ == '__main__':
|
|
14
|
+
results = batch_run(
|
|
15
|
+
MoneyModel,
|
|
16
|
+
parameters=params,
|
|
17
|
+
iterations=5,
|
|
18
|
+
max_steps=100,
|
|
19
|
+
number_processes=None,
|
|
20
|
+
data_collection_period=1,
|
|
21
|
+
display_progress=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
1
28
|
import itertools
|
|
2
29
|
import multiprocessing
|
|
3
30
|
from collections.abc import Iterable, Mapping
|
|
4
31
|
from functools import partial
|
|
5
32
|
from multiprocessing import Pool
|
|
6
|
-
from typing import Any
|
|
33
|
+
from typing import Any
|
|
7
34
|
|
|
8
35
|
from tqdm.auto import tqdm
|
|
9
36
|
|
|
@@ -14,9 +41,9 @@ multiprocessing.set_start_method("spawn", force=True)
|
|
|
14
41
|
|
|
15
42
|
def batch_run(
|
|
16
43
|
model_cls: type[Model],
|
|
17
|
-
parameters: Mapping[str,
|
|
44
|
+
parameters: Mapping[str, Any | Iterable[Any]],
|
|
18
45
|
# We still retain the Optional[int] because users may set it to None (i.e. use all CPUs)
|
|
19
|
-
number_processes:
|
|
46
|
+
number_processes: int | None = 1,
|
|
20
47
|
iterations: int = 1,
|
|
21
48
|
data_collection_period: int = -1,
|
|
22
49
|
max_steps: int = 1000,
|
|
@@ -24,29 +51,22 @@ def batch_run(
|
|
|
24
51
|
) -> list[dict[str, Any]]:
|
|
25
52
|
"""Batch run a mesa model with a set of parameter values.
|
|
26
53
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
iterations : int, optional
|
|
36
|
-
Number of iterations for each parameter combination, by default 1
|
|
37
|
-
data_collection_period : int, optional
|
|
38
|
-
Number of steps after which data gets collected, by default -1 (end of episode)
|
|
39
|
-
max_steps : int, optional
|
|
40
|
-
Maximum number of model steps after which the model halts, by default 1000
|
|
41
|
-
display_progress : bool, optional
|
|
42
|
-
Display batch run process, by default True
|
|
54
|
+
Args:
|
|
55
|
+
model_cls (Type[Model]): The model class to batch-run
|
|
56
|
+
parameters (Mapping[str, Union[Any, Iterable[Any]]]): Dictionary with model parameters over which to run the model. You can either pass single values or iterables.
|
|
57
|
+
number_processes (int, optional): Number of processes used, by default 1. Set this to None if you want to use all CPUs.
|
|
58
|
+
iterations (int, optional): Number of iterations for each parameter combination, by default 1
|
|
59
|
+
data_collection_period (int, optional): Number of steps after which data gets collected, by default -1 (end of episode)
|
|
60
|
+
max_steps (int, optional): Maximum number of model steps after which the model halts, by default 1000
|
|
61
|
+
display_progress (bool, optional): Display batch run process, by default True
|
|
43
62
|
|
|
44
|
-
Returns
|
|
45
|
-
|
|
46
|
-
List[Dict[str, Any]]
|
|
47
|
-
[description]
|
|
48
|
-
"""
|
|
63
|
+
Returns:
|
|
64
|
+
List[Dict[str, Any]]
|
|
49
65
|
|
|
66
|
+
Notes:
|
|
67
|
+
batch_run assumes the model has a `datacollector` attribute that has a DataCollector object initialized.
|
|
68
|
+
|
|
69
|
+
"""
|
|
50
70
|
runs_list = []
|
|
51
71
|
run_id = 0
|
|
52
72
|
for iteration in range(iterations):
|
|
@@ -79,7 +99,7 @@ def batch_run(
|
|
|
79
99
|
|
|
80
100
|
|
|
81
101
|
def _make_model_kwargs(
|
|
82
|
-
parameters: Mapping[str,
|
|
102
|
+
parameters: Mapping[str, Any | Iterable[Any]],
|
|
83
103
|
) -> list[dict[str, Any]]:
|
|
84
104
|
"""Create model kwargs from parameters dictionary.
|
|
85
105
|
|
|
@@ -88,7 +108,7 @@ def _make_model_kwargs(
|
|
|
88
108
|
parameters : Mapping[str, Union[Any, Iterable[Any]]]
|
|
89
109
|
Single or multiple values for each model parameter name
|
|
90
110
|
|
|
91
|
-
Returns
|
|
111
|
+
Returns:
|
|
92
112
|
-------
|
|
93
113
|
List[Dict[str, Any]]
|
|
94
114
|
A list of all kwargs combinations.
|
|
@@ -128,21 +148,21 @@ def _model_run_func(
|
|
|
128
148
|
data_collection_period : int
|
|
129
149
|
Number of steps after which data gets collected
|
|
130
150
|
|
|
131
|
-
Returns
|
|
151
|
+
Returns:
|
|
132
152
|
-------
|
|
133
153
|
List[Dict[str, Any]]
|
|
134
154
|
Return model_data, agent_data from the reporters
|
|
135
155
|
"""
|
|
136
156
|
run_id, iteration, kwargs = run
|
|
137
157
|
model = model_cls(**kwargs)
|
|
138
|
-
while model.running and model.
|
|
158
|
+
while model.running and model.steps <= max_steps:
|
|
139
159
|
model.step()
|
|
140
160
|
|
|
141
161
|
data = []
|
|
142
162
|
|
|
143
|
-
steps = list(range(0, model.
|
|
144
|
-
if not steps or steps[-1] != model.
|
|
145
|
-
steps.append(model.
|
|
163
|
+
steps = list(range(0, model.steps, data_collection_period))
|
|
164
|
+
if not steps or steps[-1] != model.steps - 1:
|
|
165
|
+
steps.append(model.steps - 1)
|
|
146
166
|
|
|
147
167
|
for step in steps:
|
|
148
168
|
model_data, all_agents_data = _collect_data(model, step)
|
|
@@ -181,6 +201,10 @@ def _collect_data(
|
|
|
181
201
|
step: int,
|
|
182
202
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
183
203
|
"""Collect model and agent data from a model using mesas datacollector."""
|
|
204
|
+
if not hasattr(model, "datacollector"):
|
|
205
|
+
raise AttributeError(
|
|
206
|
+
"The model does not have a datacollector attribute. Please add a DataCollector to your model."
|
|
207
|
+
)
|
|
184
208
|
dc = model.datacollector
|
|
185
209
|
|
|
186
210
|
model_data = {param: values[step] for param, values in dc.model_vars.items()}
|