Mesa 3.0.0__py3-none-any.whl → 3.0.0a0__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 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
- mesa-3.0.0a0.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/solara_viz.py +0 -458
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
mesa/agent.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
The agent class for Mesa framework.
|
|
2
3
|
|
|
3
|
-
Core Objects: Agent
|
|
4
|
+
Core Objects: Agent
|
|
4
5
|
"""
|
|
5
6
|
|
|
6
7
|
# Mypy; for the `|` operator purpose
|
|
@@ -9,19 +10,15 @@ from __future__ import annotations
|
|
|
9
10
|
|
|
10
11
|
import contextlib
|
|
11
12
|
import copy
|
|
12
|
-
import functools
|
|
13
|
-
import itertools
|
|
14
13
|
import operator
|
|
15
14
|
import warnings
|
|
16
15
|
import weakref
|
|
17
16
|
from collections import defaultdict
|
|
18
|
-
from collections.abc import Callable,
|
|
17
|
+
from collections.abc import Callable, Iterable, Iterator, MutableSet, Sequence
|
|
19
18
|
from random import Random
|
|
20
19
|
|
|
21
20
|
# mypy
|
|
22
|
-
from typing import TYPE_CHECKING, Any
|
|
23
|
-
|
|
24
|
-
import numpy as np
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
25
22
|
|
|
26
23
|
if TYPE_CHECKING:
|
|
27
24
|
# We ensure that these are not imported during runtime to prevent cyclic
|
|
@@ -31,99 +28,90 @@ if TYPE_CHECKING:
|
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
class Agent:
|
|
34
|
-
"""
|
|
31
|
+
"""
|
|
32
|
+
Base class for a model agent in Mesa.
|
|
35
33
|
|
|
36
34
|
Attributes:
|
|
37
|
-
model (Model): A reference to the model instance.
|
|
38
35
|
unique_id (int): A unique identifier for this agent.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Notes:
|
|
42
|
-
unique_id is unique relative to a model instance and starts from 1
|
|
43
|
-
|
|
36
|
+
model (Model): A reference to the model instance.
|
|
37
|
+
self.pos: Position | None = None
|
|
44
38
|
"""
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_ids = defaultdict(functools.partial(itertools.count, 1))
|
|
50
|
-
|
|
51
|
-
def __init__(self, model: Model, *args, **kwargs) -> None:
|
|
52
|
-
"""Create a new agent.
|
|
40
|
+
def __init__(self, unique_id: int, model: Model) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Create a new agent.
|
|
53
43
|
|
|
54
44
|
Args:
|
|
45
|
+
unique_id (int): A unique identifier for this agent.
|
|
55
46
|
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
|
-
|
|
63
47
|
"""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
self.model: Model = model
|
|
67
|
-
self.unique_id: int = next(self._ids[model])
|
|
48
|
+
self.unique_id = unique_id
|
|
49
|
+
self.model = model
|
|
68
50
|
self.pos: Position | None = None
|
|
69
|
-
self.model.register_agent(self)
|
|
70
51
|
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
# register agent
|
|
53
|
+
try:
|
|
54
|
+
self.model.agents_[type(self)][self] = None
|
|
55
|
+
except AttributeError:
|
|
56
|
+
# model super has not been called
|
|
57
|
+
self.model.agents_ = defaultdict(dict)
|
|
58
|
+
self.model.agents_[type(self)][self] = None
|
|
59
|
+
self.model.agentset_experimental_warning_given = False
|
|
73
60
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
61
|
+
warnings.warn(
|
|
62
|
+
"The Mesa Model class was not initialized. In the future, you need to explicitly initialize the Model by calling super().__init__() on initialization.",
|
|
63
|
+
FutureWarning,
|
|
64
|
+
stacklevel=2,
|
|
65
|
+
)
|
|
77
66
|
|
|
78
|
-
|
|
67
|
+
def remove(self) -> None:
|
|
68
|
+
"""Remove and delete the agent from the model."""
|
|
79
69
|
with contextlib.suppress(KeyError):
|
|
80
|
-
self.model.
|
|
70
|
+
self.model.agents_[type(self)].pop(self)
|
|
81
71
|
|
|
82
72
|
def step(self) -> None:
|
|
83
73
|
"""A single step of the agent."""
|
|
84
74
|
|
|
85
|
-
def advance(self) -> None:
|
|
75
|
+
def advance(self) -> None:
|
|
86
76
|
pass
|
|
87
77
|
|
|
88
78
|
@property
|
|
89
79
|
def random(self) -> Random:
|
|
90
|
-
"""Return a seeded stdlib rng."""
|
|
91
80
|
return self.model.random
|
|
92
81
|
|
|
93
|
-
@property
|
|
94
|
-
def rng(self) -> np.random.Generator:
|
|
95
|
-
"""Return a seeded np.random rng."""
|
|
96
|
-
return self.model.rng
|
|
97
|
-
|
|
98
82
|
|
|
99
83
|
class AgentSet(MutableSet, Sequence):
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
"""
|
|
85
|
+
A collection class that represents an ordered set of agents within an agent-based model (ABM). This class
|
|
86
|
+
extends both MutableSet and Sequence, providing set-like functionality with order preservation and
|
|
103
87
|
sequence operations.
|
|
104
88
|
|
|
105
89
|
Attributes:
|
|
106
90
|
model (Model): The ABM model instance to which this AgentSet belongs.
|
|
107
91
|
|
|
108
|
-
|
|
92
|
+
Methods:
|
|
93
|
+
__len__, __iter__, __contains__, select, shuffle, sort, _update, do, get, __getitem__,
|
|
94
|
+
add, discard, remove, __getstate__, __setstate__, random
|
|
95
|
+
|
|
96
|
+
Note:
|
|
109
97
|
The AgentSet maintains weak references to agents, allowing for efficient management of agent lifecycles
|
|
110
98
|
without preventing garbage collection. It is associated with a specific model instance, enabling
|
|
111
99
|
interactions with the model's environment and other agents.The implementation uses a WeakKeyDictionary to store agents,
|
|
112
100
|
which means that agents not referenced elsewhere in the program may be automatically removed from the AgentSet.
|
|
113
101
|
"""
|
|
114
102
|
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
agentset_experimental_warning_given = False
|
|
104
|
+
|
|
105
|
+
def __init__(self, agents: Iterable[Agent], model: Model):
|
|
106
|
+
"""
|
|
107
|
+
Initializes the AgentSet with a collection of agents and a reference to the model.
|
|
117
108
|
|
|
118
109
|
Args:
|
|
119
110
|
agents (Iterable[Agent]): An iterable of Agent objects to be included in the set.
|
|
120
|
-
|
|
111
|
+
model (Model): The ABM model instance to which this AgentSet belongs.
|
|
121
112
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Random()
|
|
125
|
-
) # FIXME see issue 1981, how to get the central rng from model
|
|
126
|
-
self.random = random
|
|
113
|
+
|
|
114
|
+
self.model = model
|
|
127
115
|
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
128
116
|
|
|
129
117
|
def __len__(self) -> int:
|
|
@@ -141,63 +129,45 @@ class AgentSet(MutableSet, Sequence):
|
|
|
141
129
|
def select(
|
|
142
130
|
self,
|
|
143
131
|
filter_func: Callable[[Agent], bool] | None = None,
|
|
144
|
-
|
|
132
|
+
n: int = 0,
|
|
145
133
|
inplace: bool = False,
|
|
146
134
|
agent_type: type[Agent] | None = None,
|
|
147
|
-
n: int | None = None,
|
|
148
135
|
) -> AgentSet:
|
|
149
|
-
"""
|
|
136
|
+
"""
|
|
137
|
+
Select a subset of agents from the AgentSet based on a filter function and/or quantity limit.
|
|
150
138
|
|
|
151
139
|
Args:
|
|
152
140
|
filter_func (Callable[[Agent], bool], optional): A function that takes an Agent and returns True if the
|
|
153
141
|
agent should be included in the result. Defaults to None, meaning no filtering is applied.
|
|
154
|
-
|
|
155
|
-
- If an integer, at most the first number of matching agents are selected.
|
|
156
|
-
- If a float between 0 and 1, at most that fraction of original the agents are selected.
|
|
142
|
+
n (int, optional): The number of agents to select. If 0, all matching agents are selected. Defaults to 0.
|
|
157
143
|
inplace (bool, optional): If True, modifies the current AgentSet; otherwise, returns a new AgentSet. Defaults to False.
|
|
158
144
|
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
|
|
160
145
|
|
|
161
146
|
Returns:
|
|
162
147
|
AgentSet: A new AgentSet containing the selected agents, unless inplace is True, in which case the current AgentSet is updated.
|
|
163
|
-
|
|
164
|
-
Notes:
|
|
165
|
-
- at_most just return the first n or fraction of agents. To take a random sample, shuffle() beforehand.
|
|
166
|
-
- at_most is an upper limit. When specifying other criteria, the number of agents returned can be smaller.
|
|
167
148
|
"""
|
|
168
|
-
if n is not None:
|
|
169
|
-
warnings.warn(
|
|
170
|
-
"The parameter 'n' is deprecated. Use 'at_most' instead.",
|
|
171
|
-
DeprecationWarning,
|
|
172
|
-
stacklevel=2,
|
|
173
|
-
)
|
|
174
|
-
at_most = n
|
|
175
149
|
|
|
176
|
-
|
|
177
|
-
if filter_func is None and agent_type is None and at_most == inf:
|
|
150
|
+
if filter_func is None and agent_type is None and n == 0:
|
|
178
151
|
return self if inplace else copy.copy(self)
|
|
179
152
|
|
|
180
|
-
|
|
181
|
-
if at_most <= 1.0 and isinstance(at_most, float):
|
|
182
|
-
at_most = int(len(self) * at_most) # Note that it rounds down (floor)
|
|
183
|
-
|
|
184
|
-
def agent_generator(filter_func, agent_type, at_most):
|
|
153
|
+
def agent_generator(filter_func=None, agent_type=None, n=0):
|
|
185
154
|
count = 0
|
|
186
155
|
for agent in self:
|
|
187
|
-
if count >= at_most:
|
|
188
|
-
break
|
|
189
156
|
if (not filter_func or filter_func(agent)) and (
|
|
190
157
|
not agent_type or isinstance(agent, agent_type)
|
|
191
158
|
):
|
|
192
159
|
yield agent
|
|
193
160
|
count += 1
|
|
161
|
+
if 0 < n <= count:
|
|
162
|
+
break
|
|
194
163
|
|
|
195
|
-
agents = agent_generator(filter_func, agent_type,
|
|
164
|
+
agents = agent_generator(filter_func, agent_type, n)
|
|
196
165
|
|
|
197
|
-
return AgentSet(agents, self.
|
|
166
|
+
return AgentSet(agents, self.model) if not inplace else self._update(agents)
|
|
198
167
|
|
|
199
168
|
def shuffle(self, inplace: bool = False) -> AgentSet:
|
|
200
|
-
"""
|
|
169
|
+
"""
|
|
170
|
+
Randomly shuffle the order of agents in the AgentSet.
|
|
201
171
|
|
|
202
172
|
Args:
|
|
203
173
|
inplace (bool, optional): If True, shuffles the agents in the current AgentSet; otherwise, returns a new shuffled AgentSet. Defaults to False.
|
|
@@ -217,7 +187,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
217
187
|
return self
|
|
218
188
|
else:
|
|
219
189
|
return AgentSet(
|
|
220
|
-
(agent for ref in weakrefs if (agent := ref()) is not None), self.
|
|
190
|
+
(agent for ref in weakrefs if (agent := ref()) is not None), self.model
|
|
221
191
|
)
|
|
222
192
|
|
|
223
193
|
def sort(
|
|
@@ -226,7 +196,8 @@ class AgentSet(MutableSet, Sequence):
|
|
|
226
196
|
ascending: bool = False,
|
|
227
197
|
inplace: bool = False,
|
|
228
198
|
) -> AgentSet:
|
|
229
|
-
"""
|
|
199
|
+
"""
|
|
200
|
+
Sort the agents in the AgentSet based on a specified attribute or custom function.
|
|
230
201
|
|
|
231
202
|
Args:
|
|
232
203
|
key (Callable[[Agent], Any] | str): A function or attribute name based on which the agents are sorted.
|
|
@@ -242,207 +213,70 @@ class AgentSet(MutableSet, Sequence):
|
|
|
242
213
|
sorted_agents = sorted(self._agents.keys(), key=key, reverse=not ascending)
|
|
243
214
|
|
|
244
215
|
return (
|
|
245
|
-
AgentSet(sorted_agents, self.
|
|
216
|
+
AgentSet(sorted_agents, self.model)
|
|
246
217
|
if not inplace
|
|
247
218
|
else self._update(sorted_agents)
|
|
248
219
|
)
|
|
249
220
|
|
|
250
221
|
def _update(self, agents: Iterable[Agent]):
|
|
251
222
|
"""Update the AgentSet with a new set of agents.
|
|
252
|
-
|
|
253
223
|
This is a private method primarily used internally by other methods like select, shuffle, and sort.
|
|
254
224
|
"""
|
|
255
|
-
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
256
|
-
return self
|
|
257
|
-
|
|
258
|
-
def do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
|
|
259
|
-
"""Invoke a method or function on each agent in the AgentSet.
|
|
260
|
-
|
|
261
|
-
Args:
|
|
262
|
-
method (str, callable): the callable to do on each agent
|
|
263
|
-
|
|
264
|
-
* in case of str, the name of the method to call on each agent.
|
|
265
|
-
* in case of callable, the function to be called with each agent as first argument
|
|
266
|
-
|
|
267
|
-
*args: Variable length argument list passed to the callable being called.
|
|
268
|
-
**kwargs: Arbitrary keyword arguments passed to the callable being called.
|
|
269
|
-
|
|
270
|
-
Returns:
|
|
271
|
-
AgentSet | list[Any]: The results of the callable calls if return_results is True, otherwise the AgentSet itself.
|
|
272
|
-
"""
|
|
273
|
-
try:
|
|
274
|
-
return_results = kwargs.pop("return_results")
|
|
275
|
-
except KeyError:
|
|
276
|
-
return_results = False
|
|
277
|
-
else:
|
|
278
|
-
warnings.warn(
|
|
279
|
-
"Using return_results is deprecated. Use AgenSet.do in case of return_results=False, and "
|
|
280
|
-
"AgentSet.map in case of return_results=True",
|
|
281
|
-
stacklevel=2,
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
if return_results:
|
|
285
|
-
return self.map(method, *args, **kwargs)
|
|
286
|
-
|
|
287
|
-
# we iterate over the actual weakref keys and check if weakref is alive before calling the method
|
|
288
|
-
if isinstance(method, str):
|
|
289
|
-
for agentref in self._agents.keyrefs():
|
|
290
|
-
if (agent := agentref()) is not None:
|
|
291
|
-
getattr(agent, method)(*args, **kwargs)
|
|
292
|
-
else:
|
|
293
|
-
for agentref in self._agents.keyrefs():
|
|
294
|
-
if (agent := agentref()) is not None:
|
|
295
|
-
method(agent, *args, **kwargs)
|
|
296
225
|
|
|
226
|
+
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
297
227
|
return self
|
|
298
228
|
|
|
299
|
-
def
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
It's a fast, optimized version of calling shuffle() followed by do().
|
|
229
|
+
def do(
|
|
230
|
+
self, method_name: str, *args, return_results: bool = False, **kwargs
|
|
231
|
+
) -> AgentSet | list[Any]:
|
|
303
232
|
"""
|
|
304
|
-
|
|
305
|
-
self.random.shuffle(weakrefs)
|
|
306
|
-
|
|
307
|
-
if isinstance(method, str):
|
|
308
|
-
for ref in weakrefs:
|
|
309
|
-
if (agent := ref()) is not None:
|
|
310
|
-
getattr(agent, method)(*args, **kwargs)
|
|
311
|
-
else:
|
|
312
|
-
for ref in weakrefs:
|
|
313
|
-
if (agent := ref()) is not None:
|
|
314
|
-
method(agent, *args, **kwargs)
|
|
315
|
-
|
|
316
|
-
return self
|
|
317
|
-
|
|
318
|
-
def map(self, method: str | Callable, *args, **kwargs) -> list[Any]:
|
|
319
|
-
"""Invoke a method or function on each agent in the AgentSet and return the results.
|
|
233
|
+
Invoke a method on each agent in the AgentSet.
|
|
320
234
|
|
|
321
235
|
Args:
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
*args: Variable length argument list passed to the callable being called.
|
|
328
|
-
**kwargs: Arbitrary keyword arguments passed to the callable being called.
|
|
236
|
+
method_name (str): The name of the method to call on each agent.
|
|
237
|
+
return_results (bool, optional): If True, returns the results of the method calls; otherwise, returns the AgentSet itself. Defaults to False, so you can chain method calls.
|
|
238
|
+
*args: Variable length argument list passed to the method being called.
|
|
239
|
+
**kwargs: Arbitrary keyword arguments passed to the method being called.
|
|
329
240
|
|
|
330
241
|
Returns:
|
|
331
|
-
|
|
242
|
+
AgentSet | list[Any]: The results of the method calls if return_results is True, otherwise the AgentSet itself.
|
|
332
243
|
"""
|
|
333
244
|
# we iterate over the actual weakref keys and check if weakref is alive before calling the method
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
]
|
|
340
|
-
else:
|
|
341
|
-
res = [
|
|
342
|
-
method(agent, *args, **kwargs)
|
|
343
|
-
for agentref in self._agents.keyrefs()
|
|
344
|
-
if (agent := agentref()) is not None
|
|
345
|
-
]
|
|
245
|
+
res = [
|
|
246
|
+
getattr(agent, method_name)(*args, **kwargs)
|
|
247
|
+
for agentref in self._agents.keyrefs()
|
|
248
|
+
if (agent := agentref()) is not None
|
|
249
|
+
]
|
|
346
250
|
|
|
347
|
-
return res
|
|
251
|
+
return res if return_results else self
|
|
348
252
|
|
|
349
|
-
def
|
|
350
|
-
"""Aggregate an attribute of all agents in the AgentSet using a specified function.
|
|
351
|
-
|
|
352
|
-
Args:
|
|
353
|
-
attribute (str): The name of the attribute to aggregate.
|
|
354
|
-
func (Callable): The function to apply to the attribute values (e.g., min, max, sum, np.mean).
|
|
355
|
-
|
|
356
|
-
Returns:
|
|
357
|
-
Any: The result of applying the function to the attribute values. Often a single value.
|
|
253
|
+
def get(self, attr_names: str | list[str]) -> list[Any]:
|
|
358
254
|
"""
|
|
359
|
-
|
|
360
|
-
return func(values)
|
|
361
|
-
|
|
362
|
-
@overload
|
|
363
|
-
def get(
|
|
364
|
-
self,
|
|
365
|
-
attr_names: str,
|
|
366
|
-
handle_missing: Literal["error", "default"] = "error",
|
|
367
|
-
default_value: Any = None,
|
|
368
|
-
) -> list[Any]: ...
|
|
369
|
-
|
|
370
|
-
@overload
|
|
371
|
-
def get(
|
|
372
|
-
self,
|
|
373
|
-
attr_names: list[str],
|
|
374
|
-
handle_missing: Literal["error", "default"] = "error",
|
|
375
|
-
default_value: Any = None,
|
|
376
|
-
) -> list[list[Any]]: ...
|
|
377
|
-
|
|
378
|
-
def get(
|
|
379
|
-
self,
|
|
380
|
-
attr_names,
|
|
381
|
-
handle_missing="error",
|
|
382
|
-
default_value=None,
|
|
383
|
-
):
|
|
384
|
-
"""Retrieve the specified attribute(s) from each agent in the AgentSet.
|
|
255
|
+
Retrieve the specified attribute(s) from each agent in the AgentSet.
|
|
385
256
|
|
|
386
257
|
Args:
|
|
387
258
|
attr_names (str | list[str]): The name(s) of the attribute(s) to retrieve from each agent.
|
|
388
|
-
handle_missing (str, optional): How to handle missing attributes. Can be:
|
|
389
|
-
- 'error' (default): raises an AttributeError if attribute is missing.
|
|
390
|
-
- 'default': returns the specified default_value.
|
|
391
|
-
default_value (Any, optional): The default value to return if 'handle_missing' is set to 'default'
|
|
392
|
-
and the agent does not have the attribute.
|
|
393
259
|
|
|
394
260
|
Returns:
|
|
395
|
-
list[Any]: A list with the attribute value for each agent if attr_names is a str
|
|
396
|
-
list[list[Any]]: A list with a
|
|
261
|
+
list[Any]: A list with the attribute value for each agent in the set if attr_names is a str
|
|
262
|
+
list[list[Any]]: A list with a list of attribute values for each agent in the set if attr_names is a list of str
|
|
397
263
|
|
|
398
264
|
Raises:
|
|
399
|
-
AttributeError
|
|
400
|
-
|
|
265
|
+
AttributeError if an agent does not have the specified attribute(s)
|
|
266
|
+
|
|
401
267
|
"""
|
|
402
|
-
is_single_attr = isinstance(attr_names, str)
|
|
403
|
-
|
|
404
|
-
if handle_missing == "error":
|
|
405
|
-
if is_single_attr:
|
|
406
|
-
return [getattr(agent, attr_names) for agent in self._agents]
|
|
407
|
-
else:
|
|
408
|
-
return [
|
|
409
|
-
[getattr(agent, attr) for attr in attr_names]
|
|
410
|
-
for agent in self._agents
|
|
411
|
-
]
|
|
412
|
-
|
|
413
|
-
elif handle_missing == "default":
|
|
414
|
-
if is_single_attr:
|
|
415
|
-
return [
|
|
416
|
-
getattr(agent, attr_names, default_value) for agent in self._agents
|
|
417
|
-
]
|
|
418
|
-
else:
|
|
419
|
-
return [
|
|
420
|
-
[getattr(agent, attr, default_value) for attr in attr_names]
|
|
421
|
-
for agent in self._agents
|
|
422
|
-
]
|
|
423
268
|
|
|
269
|
+
if isinstance(attr_names, str):
|
|
270
|
+
return [getattr(agent, attr_names) for agent in self._agents]
|
|
424
271
|
else:
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
def set(self, attr_name: str, value: Any) -> AgentSet:
|
|
431
|
-
"""Set a specified attribute to a given value for all agents in the AgentSet.
|
|
432
|
-
|
|
433
|
-
Args:
|
|
434
|
-
attr_name (str): The name of the attribute to set.
|
|
435
|
-
value (Any): The value to set the attribute to.
|
|
436
|
-
|
|
437
|
-
Returns:
|
|
438
|
-
AgentSet: The AgentSet instance itself, after setting the attribute.
|
|
439
|
-
"""
|
|
440
|
-
for agent in self:
|
|
441
|
-
setattr(agent, attr_name, value)
|
|
442
|
-
return self
|
|
272
|
+
return [
|
|
273
|
+
[getattr(agent, attr_name) for attr_name in attr_names]
|
|
274
|
+
for agent in self._agents
|
|
275
|
+
]
|
|
443
276
|
|
|
444
277
|
def __getitem__(self, item: int | slice) -> Agent:
|
|
445
|
-
"""
|
|
278
|
+
"""
|
|
279
|
+
Retrieve an agent or a slice of agents from the AgentSet.
|
|
446
280
|
|
|
447
281
|
Args:
|
|
448
282
|
item (int | slice): The index or slice for selecting agents.
|
|
@@ -453,7 +287,8 @@ class AgentSet(MutableSet, Sequence):
|
|
|
453
287
|
return list(self._agents.keys())[item]
|
|
454
288
|
|
|
455
289
|
def add(self, agent: Agent):
|
|
456
|
-
"""
|
|
290
|
+
"""
|
|
291
|
+
Add an agent to the AgentSet.
|
|
457
292
|
|
|
458
293
|
Args:
|
|
459
294
|
agent (Agent): The agent to add to the set.
|
|
@@ -464,7 +299,8 @@ class AgentSet(MutableSet, Sequence):
|
|
|
464
299
|
self._agents[agent] = None
|
|
465
300
|
|
|
466
301
|
def discard(self, agent: Agent):
|
|
467
|
-
"""
|
|
302
|
+
"""
|
|
303
|
+
Remove an agent from the AgentSet if it exists.
|
|
468
304
|
|
|
469
305
|
This method does not raise an error if the agent is not present.
|
|
470
306
|
|
|
@@ -478,7 +314,8 @@ class AgentSet(MutableSet, Sequence):
|
|
|
478
314
|
del self._agents[agent]
|
|
479
315
|
|
|
480
316
|
def remove(self, agent: Agent):
|
|
481
|
-
"""
|
|
317
|
+
"""
|
|
318
|
+
Remove an agent from the AgentSet.
|
|
482
319
|
|
|
483
320
|
This method raises an error if the agent is not present.
|
|
484
321
|
|
|
@@ -491,164 +328,35 @@ class AgentSet(MutableSet, Sequence):
|
|
|
491
328
|
del self._agents[agent]
|
|
492
329
|
|
|
493
330
|
def __getstate__(self):
|
|
494
|
-
"""
|
|
331
|
+
"""
|
|
332
|
+
Retrieve the state of the AgentSet for serialization.
|
|
495
333
|
|
|
496
334
|
Returns:
|
|
497
335
|
dict: A dictionary representing the state of the AgentSet.
|
|
498
336
|
"""
|
|
499
|
-
return {"agents": list(self._agents.keys()), "
|
|
337
|
+
return {"agents": list(self._agents.keys()), "model": self.model}
|
|
500
338
|
|
|
501
339
|
def __setstate__(self, state):
|
|
502
|
-
"""
|
|
340
|
+
"""
|
|
341
|
+
Set the state of the AgentSet during deserialization.
|
|
503
342
|
|
|
504
343
|
Args:
|
|
505
344
|
state (dict): A dictionary representing the state to restore.
|
|
506
345
|
"""
|
|
507
|
-
self.
|
|
346
|
+
self.model = state["model"]
|
|
508
347
|
self._update(state["agents"])
|
|
509
348
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
Args:
|
|
514
|
-
by (Callable, str): used to determine what to group agents by
|
|
515
|
-
|
|
516
|
-
* if ``by`` is a callable, it will be called for each agent and the return is used
|
|
517
|
-
for grouping
|
|
518
|
-
* if ``by`` is a str, it should refer to an attribute on the agent and the value
|
|
519
|
-
of this attribute will be used for grouping
|
|
520
|
-
result_type (str, optional): The datatype for the resulting groups {"agentset", "list"}
|
|
521
|
-
|
|
522
|
-
Returns:
|
|
523
|
-
GroupBy
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
Notes:
|
|
527
|
-
There might be performance benefits to using `result_type='list'` if you don't need the advanced functionality
|
|
528
|
-
of an AgentSet.
|
|
529
|
-
|
|
530
|
-
"""
|
|
531
|
-
groups = defaultdict(list)
|
|
532
|
-
|
|
533
|
-
if isinstance(by, Callable):
|
|
534
|
-
for agent in self:
|
|
535
|
-
groups[by(agent)].append(agent)
|
|
536
|
-
else:
|
|
537
|
-
for agent in self:
|
|
538
|
-
groups[getattr(agent, by)].append(agent)
|
|
539
|
-
|
|
540
|
-
if result_type == "agentset":
|
|
541
|
-
return GroupBy(
|
|
542
|
-
{k: AgentSet(v, random=self.random) for k, v in groups.items()}
|
|
543
|
-
)
|
|
544
|
-
else:
|
|
545
|
-
return GroupBy(groups)
|
|
546
|
-
|
|
547
|
-
# consider adding for performance reasons
|
|
548
|
-
# for Sequence: __reversed__, index, and count
|
|
549
|
-
# for MutableSet clear, pop, remove, __ior__, __iand__, __ixor__, and __isub__
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
class GroupBy:
|
|
553
|
-
"""Helper class for AgentSet.groupby.
|
|
554
|
-
|
|
555
|
-
Attributes:
|
|
556
|
-
groups (dict): A dictionary with the group_name as key and group as values
|
|
557
|
-
|
|
558
|
-
"""
|
|
559
|
-
|
|
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
|
-
"""
|
|
567
|
-
self.groups: dict[Any, list | AgentSet] = groups
|
|
568
|
-
|
|
569
|
-
def map(self, method: Callable | str, *args, **kwargs) -> dict[Any, Any]:
|
|
570
|
-
"""Apply the specified callable to each group and return the results.
|
|
571
|
-
|
|
572
|
-
Args:
|
|
573
|
-
method (Callable, str): The callable to apply to each group,
|
|
574
|
-
|
|
575
|
-
* if ``method`` is a callable, it will be called it will be called with the group as first argument
|
|
576
|
-
* if ``method`` is a str, it should refer to a method on the group
|
|
577
|
-
|
|
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
|
|
581
|
-
|
|
582
|
-
Returns:
|
|
583
|
-
dict with group_name as key and the return of the method as value
|
|
584
|
-
|
|
585
|
-
Notes:
|
|
586
|
-
this method is useful for methods or functions that do return something. It
|
|
587
|
-
will break method chaining. For that, use ``do`` instead.
|
|
588
|
-
|
|
589
|
-
"""
|
|
590
|
-
if isinstance(method, str):
|
|
591
|
-
return {
|
|
592
|
-
k: getattr(v, method)(*args, **kwargs) for k, v in self.groups.items()
|
|
593
|
-
}
|
|
594
|
-
else:
|
|
595
|
-
return {k: method(v, *args, **kwargs) for k, v in self.groups.items()}
|
|
596
|
-
|
|
597
|
-
def do(self, method: Callable | str, *args, **kwargs) -> GroupBy:
|
|
598
|
-
"""Apply the specified callable to each group.
|
|
599
|
-
|
|
600
|
-
Args:
|
|
601
|
-
method (Callable, str): The callable to apply to each group,
|
|
602
|
-
|
|
603
|
-
* if ``method`` is a callable, it will be called it will be called with the group as first argument
|
|
604
|
-
* if ``method`` is a str, it should refer to a method on the group
|
|
605
|
-
|
|
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
|
|
609
|
-
|
|
610
|
-
Returns:
|
|
611
|
-
the original GroupBy instance
|
|
612
|
-
|
|
613
|
-
Notes:
|
|
614
|
-
this method is useful for methods or functions that don't return anything and/or
|
|
615
|
-
if you want to chain multiple do calls
|
|
616
|
-
|
|
617
|
-
"""
|
|
618
|
-
if isinstance(method, str):
|
|
619
|
-
for v in self.groups.values():
|
|
620
|
-
getattr(v, method)(*args, **kwargs)
|
|
621
|
-
else:
|
|
622
|
-
for v in self.groups.values():
|
|
623
|
-
method(v, *args, **kwargs)
|
|
624
|
-
|
|
625
|
-
return self
|
|
626
|
-
|
|
627
|
-
def count(self) -> dict[Any, int]:
|
|
628
|
-
"""Return the count of agents in each group.
|
|
629
|
-
|
|
630
|
-
Returns:
|
|
631
|
-
dict: A dictionary mapping group names to the number of agents in each group.
|
|
349
|
+
@property
|
|
350
|
+
def random(self) -> Random:
|
|
632
351
|
"""
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
def agg(self, attr_name: str, func: Callable) -> dict[Hashable, Any]:
|
|
636
|
-
"""Aggregate the values of a specific attribute across each group using the provided function.
|
|
637
|
-
|
|
638
|
-
Args:
|
|
639
|
-
attr_name (str): The name of the attribute to aggregate.
|
|
640
|
-
func (Callable): The function to apply (e.g., sum, min, max, mean).
|
|
352
|
+
Provide access to the model's random number generator.
|
|
641
353
|
|
|
642
354
|
Returns:
|
|
643
|
-
|
|
355
|
+
Random: The random number generator associated with the model.
|
|
644
356
|
"""
|
|
645
|
-
return
|
|
646
|
-
group_name: func([getattr(agent, attr_name) for agent in group])
|
|
647
|
-
for group_name, group in self.groups.items()
|
|
648
|
-
}
|
|
357
|
+
return self.model.random
|
|
649
358
|
|
|
650
|
-
def __iter__(self): # noqa: D105
|
|
651
|
-
return iter(self.groups.items())
|
|
652
359
|
|
|
653
|
-
|
|
654
|
-
|
|
360
|
+
# consider adding for performance reasons
|
|
361
|
+
# for Sequence: __reversed__, index, and count
|
|
362
|
+
# for MutableSet clear, pop, remove, __ior__, __iand__, __ixor__, and __isub__
|