Mesa 3.0.0a4__py3-none-any.whl → 3.0.0b0__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 +2 -3
- mesa/agent.py +116 -85
- mesa/batchrunner.py +22 -23
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +2 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +1 -0
- mesa/datacollection.py +138 -27
- mesa/experimental/UserParam.py +17 -6
- mesa/experimental/__init__.py +2 -0
- mesa/experimental/cell_space/__init__.py +14 -1
- mesa/experimental/cell_space/cell.py +84 -23
- mesa/experimental/cell_space/cell_agent.py +117 -21
- mesa/experimental/cell_space/cell_collection.py +54 -17
- mesa/experimental/cell_space/discrete_space.py +79 -7
- mesa/experimental/cell_space/grid.py +19 -8
- mesa/experimental/cell_space/network.py +9 -7
- mesa/experimental/cell_space/voronoi.py +26 -33
- mesa/experimental/components/altair.py +10 -0
- mesa/experimental/components/matplotlib.py +18 -0
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +36 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +65 -29
- mesa/experimental/devs/examples/wolf_sheep.py +40 -35
- mesa/experimental/devs/simulator.py +55 -15
- mesa/experimental/solara_viz.py +10 -19
- mesa/main.py +6 -4
- mesa/model.py +51 -54
- mesa/space.py +145 -120
- mesa/time.py +57 -67
- mesa/visualization/UserParam.py +19 -6
- mesa/visualization/__init__.py +3 -2
- mesa/visualization/components/altair.py +4 -2
- mesa/visualization/components/matplotlib.py +176 -85
- mesa/visualization/solara_viz.py +167 -84
- mesa/visualization/utils.py +3 -1
- {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/METADATA +55 -13
- mesa-3.0.0b0.dist-info/RECORD +45 -0
- mesa-3.0.0b0.dist-info/licenses/LICENSE +202 -0
- mesa-3.0.0a4.dist-info/licenses/LICENSE → mesa-3.0.0b0.dist-info/licenses/NOTICE +2 -2
- mesa-3.0.0a4.dist-info/RECORD +0 -44
- {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/WHEEL +0 -0
- {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/entry_points.txt +0 -0
mesa/__init__.py
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Mesa Agent-Based Modeling Framework
|
|
1
|
+
"""Mesa Agent-Based Modeling Framework.
|
|
3
2
|
|
|
4
3
|
Core Objects: Model, and Agent.
|
|
5
4
|
"""
|
|
@@ -24,7 +23,7 @@ __all__ = [
|
|
|
24
23
|
]
|
|
25
24
|
|
|
26
25
|
__title__ = "mesa"
|
|
27
|
-
__version__ = "3.0.
|
|
26
|
+
__version__ = "3.0.0b0"
|
|
28
27
|
__license__ = "Apache 2.0"
|
|
29
28
|
_this_year = datetime.datetime.now(tz=datetime.timezone.utc).date().year
|
|
30
29
|
__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
|
|
@@ -16,11 +15,11 @@ import operator
|
|
|
16
15
|
import warnings
|
|
17
16
|
import weakref
|
|
18
17
|
from collections import defaultdict
|
|
19
|
-
from collections.abc import Callable, Iterable, Iterator, MutableSet, Sequence
|
|
18
|
+
from collections.abc import Callable, Hashable, Iterable, Iterator, MutableSet, Sequence
|
|
20
19
|
from random import Random
|
|
21
20
|
|
|
22
21
|
# mypy
|
|
23
|
-
from typing import TYPE_CHECKING, Any, Literal
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Literal, overload
|
|
24
23
|
|
|
25
24
|
if TYPE_CHECKING:
|
|
26
25
|
# We ensure that these are not imported during runtime to prevent cyclic
|
|
@@ -30,8 +29,7 @@ if TYPE_CHECKING:
|
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
class Agent:
|
|
33
|
-
"""
|
|
34
|
-
Base class for a model agent in Mesa.
|
|
32
|
+
"""Base class for a model agent in Mesa.
|
|
35
33
|
|
|
36
34
|
Attributes:
|
|
37
35
|
model (Model): A reference to the model instance.
|
|
@@ -48,38 +46,25 @@ class Agent:
|
|
|
48
46
|
# so, unique_id is unique relative to a model, and counting starts from 1
|
|
49
47
|
_ids = defaultdict(functools.partial(itertools.count, 1))
|
|
50
48
|
|
|
51
|
-
def __init__(self, *args, **kwargs) -> None:
|
|
52
|
-
"""
|
|
53
|
-
Create a new agent.
|
|
49
|
+
def __init__(self, model: Model, *args, **kwargs) -> None:
|
|
50
|
+
"""Create a new agent.
|
|
54
51
|
|
|
55
52
|
Args:
|
|
56
53
|
model (Model): The model instance in which the agent exists.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
match args:
|
|
60
|
-
# Case 1: Only the model is provided. The new correct behavior.
|
|
61
|
-
case [model]:
|
|
62
|
-
self.model = model
|
|
63
|
-
self.unique_id = next(self._ids[model])
|
|
64
|
-
# Case 2: Both unique_id and model are provided, deprecated
|
|
65
|
-
case [_, model]:
|
|
66
|
-
warnings.warn(
|
|
67
|
-
"unique ids are assigned automatically to Agents in Mesa 3. The use of custom unique_id is "
|
|
68
|
-
"deprecated. Only input a model when calling `super()__init__(model)`. The unique_id inputted is not used.",
|
|
69
|
-
DeprecationWarning,
|
|
70
|
-
stacklevel=2,
|
|
71
|
-
)
|
|
72
|
-
self.model = model
|
|
73
|
-
self.unique_id = next(self._ids[model])
|
|
74
|
-
# Case 3: Anything else, raise an error
|
|
75
|
-
case _:
|
|
76
|
-
raise ValueError(
|
|
77
|
-
"Invalid arguments provided to initialize the Agent. Only input a model: `super()__init__(model)`."
|
|
78
|
-
)
|
|
54
|
+
args: passed on to super
|
|
55
|
+
kwargs: passed on to super
|
|
79
56
|
|
|
80
|
-
|
|
57
|
+
Notes:
|
|
58
|
+
to make proper use of python's super, in each class remove the arguments and
|
|
59
|
+
keyword arguments you need and pass on the rest to super
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
super().__init__(*args, **kwargs)
|
|
81
63
|
|
|
64
|
+
self.model: Model = model
|
|
82
65
|
self.model.register_agent(self)
|
|
66
|
+
self.unique_id: int = next(self._ids[model])
|
|
67
|
+
self.pos: Position | None = None
|
|
83
68
|
|
|
84
69
|
def remove(self) -> None:
|
|
85
70
|
"""Remove and delete the agent from the model."""
|
|
@@ -89,28 +74,25 @@ class Agent:
|
|
|
89
74
|
def step(self) -> None:
|
|
90
75
|
"""A single step of the agent."""
|
|
91
76
|
|
|
92
|
-
def advance(self) -> None:
|
|
77
|
+
def advance(self) -> None: # noqa: D102
|
|
93
78
|
pass
|
|
94
79
|
|
|
95
80
|
@property
|
|
96
81
|
def random(self) -> Random:
|
|
82
|
+
"""Return a seeded rng."""
|
|
97
83
|
return self.model.random
|
|
98
84
|
|
|
99
85
|
|
|
100
86
|
class AgentSet(MutableSet, Sequence):
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
extends both MutableSet and Sequence, providing set-like functionality with order preservation and
|
|
87
|
+
"""A collection class that represents an ordered set of agents within an agent-based model (ABM).
|
|
88
|
+
|
|
89
|
+
This class extends both MutableSet and Sequence, providing set-like functionality with order preservation and
|
|
104
90
|
sequence operations.
|
|
105
91
|
|
|
106
92
|
Attributes:
|
|
107
93
|
model (Model): The ABM model instance to which this AgentSet belongs.
|
|
108
94
|
|
|
109
|
-
|
|
110
|
-
__len__, __iter__, __contains__, select, shuffle, sort, _update, do, get, __getitem__,
|
|
111
|
-
add, discard, remove, __getstate__, __setstate__, random
|
|
112
|
-
|
|
113
|
-
Note:
|
|
95
|
+
Notes:
|
|
114
96
|
The AgentSet maintains weak references to agents, allowing for efficient management of agent lifecycles
|
|
115
97
|
without preventing garbage collection. It is associated with a specific model instance, enabling
|
|
116
98
|
interactions with the model's environment and other agents.The implementation uses a WeakKeyDictionary to store agents,
|
|
@@ -118,14 +100,12 @@ class AgentSet(MutableSet, Sequence):
|
|
|
118
100
|
"""
|
|
119
101
|
|
|
120
102
|
def __init__(self, agents: Iterable[Agent], model: Model):
|
|
121
|
-
"""
|
|
122
|
-
Initializes the AgentSet with a collection of agents and a reference to the model.
|
|
103
|
+
"""Initializes the AgentSet with a collection of agents and a reference to the model.
|
|
123
104
|
|
|
124
105
|
Args:
|
|
125
106
|
agents (Iterable[Agent]): An iterable of Agent objects to be included in the set.
|
|
126
107
|
model (Model): The ABM model instance to which this AgentSet belongs.
|
|
127
108
|
"""
|
|
128
|
-
|
|
129
109
|
self.model = model
|
|
130
110
|
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
131
111
|
|
|
@@ -149,8 +129,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
149
129
|
agent_type: type[Agent] | None = None,
|
|
150
130
|
n: int | None = None,
|
|
151
131
|
) -> AgentSet:
|
|
152
|
-
"""
|
|
153
|
-
Select a subset of agents from the AgentSet based on a filter function and/or quantity limit.
|
|
132
|
+
"""Select a subset of agents from the AgentSet based on a filter function and/or quantity limit.
|
|
154
133
|
|
|
155
134
|
Args:
|
|
156
135
|
filter_func (Callable[[Agent], bool], optional): A function that takes an Agent and returns True if the
|
|
@@ -160,6 +139,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
160
139
|
- If a float between 0 and 1, at most that fraction of original the agents are selected.
|
|
161
140
|
inplace (bool, optional): If True, modifies the current AgentSet; otherwise, returns a new AgentSet. Defaults to False.
|
|
162
141
|
agent_type (type[Agent], optional): The class type of the agents to select. Defaults to None, meaning no type filtering is applied.
|
|
142
|
+
n (int): deprecated, use at_most instead
|
|
163
143
|
|
|
164
144
|
Returns:
|
|
165
145
|
AgentSet: A new AgentSet containing the selected agents, unless inplace is True, in which case the current AgentSet is updated.
|
|
@@ -200,8 +180,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
200
180
|
return AgentSet(agents, self.model) if not inplace else self._update(agents)
|
|
201
181
|
|
|
202
182
|
def shuffle(self, inplace: bool = False) -> AgentSet:
|
|
203
|
-
"""
|
|
204
|
-
Randomly shuffle the order of agents in the AgentSet.
|
|
183
|
+
"""Randomly shuffle the order of agents in the AgentSet.
|
|
205
184
|
|
|
206
185
|
Args:
|
|
207
186
|
inplace (bool, optional): If True, shuffles the agents in the current AgentSet; otherwise, returns a new shuffled AgentSet. Defaults to False.
|
|
@@ -230,8 +209,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
230
209
|
ascending: bool = False,
|
|
231
210
|
inplace: bool = False,
|
|
232
211
|
) -> AgentSet:
|
|
233
|
-
"""
|
|
234
|
-
Sort the agents in the AgentSet based on a specified attribute or custom function.
|
|
212
|
+
"""Sort the agents in the AgentSet based on a specified attribute or custom function.
|
|
235
213
|
|
|
236
214
|
Args:
|
|
237
215
|
key (Callable[[Agent], Any] | str): A function or attribute name based on which the agents are sorted.
|
|
@@ -254,15 +232,14 @@ class AgentSet(MutableSet, Sequence):
|
|
|
254
232
|
|
|
255
233
|
def _update(self, agents: Iterable[Agent]):
|
|
256
234
|
"""Update the AgentSet with a new set of agents.
|
|
235
|
+
|
|
257
236
|
This is a private method primarily used internally by other methods like select, shuffle, and sort.
|
|
258
237
|
"""
|
|
259
|
-
|
|
260
238
|
self._agents = weakref.WeakKeyDictionary({agent: None for agent in agents})
|
|
261
239
|
return self
|
|
262
240
|
|
|
263
241
|
def do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
|
|
264
|
-
"""
|
|
265
|
-
Invoke a method or function on each agent in the AgentSet.
|
|
242
|
+
"""Invoke a method or function on each agent in the AgentSet.
|
|
266
243
|
|
|
267
244
|
Args:
|
|
268
245
|
method (str, callable): the callable to do on each agent
|
|
@@ -302,9 +279,25 @@ class AgentSet(MutableSet, Sequence):
|
|
|
302
279
|
|
|
303
280
|
return self
|
|
304
281
|
|
|
305
|
-
def
|
|
282
|
+
def shuffle_do(self, method: str | Callable, *args, **kwargs) -> AgentSet:
|
|
283
|
+
"""Shuffle the agents in the AgentSet and then invoke a method or function on each agent.
|
|
284
|
+
|
|
285
|
+
It's a fast, optimized version of calling shuffle() followed by do().
|
|
306
286
|
"""
|
|
307
|
-
|
|
287
|
+
agents = list(self._agents.keys())
|
|
288
|
+
self.random.shuffle(agents)
|
|
289
|
+
|
|
290
|
+
if isinstance(method, str):
|
|
291
|
+
for agent in agents:
|
|
292
|
+
getattr(agent, method)(*args, **kwargs)
|
|
293
|
+
else:
|
|
294
|
+
for agent in agents:
|
|
295
|
+
method(agent, *args, **kwargs)
|
|
296
|
+
|
|
297
|
+
return self
|
|
298
|
+
|
|
299
|
+
def map(self, method: str | Callable, *args, **kwargs) -> list[Any]:
|
|
300
|
+
"""Invoke a method or function on each agent in the AgentSet and return the results.
|
|
308
301
|
|
|
309
302
|
Args:
|
|
310
303
|
method (str, callable): the callable to apply on each agent
|
|
@@ -335,8 +328,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
335
328
|
return res
|
|
336
329
|
|
|
337
330
|
def agg(self, attribute: str, func: Callable) -> Any:
|
|
338
|
-
"""
|
|
339
|
-
Aggregate an attribute of all agents in the AgentSet using a specified function.
|
|
331
|
+
"""Aggregate an attribute of all agents in the AgentSet using a specified function.
|
|
340
332
|
|
|
341
333
|
Args:
|
|
342
334
|
attribute (str): The name of the attribute to aggregate.
|
|
@@ -348,14 +340,29 @@ class AgentSet(MutableSet, Sequence):
|
|
|
348
340
|
values = self.get(attribute)
|
|
349
341
|
return func(values)
|
|
350
342
|
|
|
343
|
+
@overload
|
|
351
344
|
def get(
|
|
352
345
|
self,
|
|
353
|
-
attr_names: str
|
|
346
|
+
attr_names: str,
|
|
354
347
|
handle_missing: Literal["error", "default"] = "error",
|
|
355
348
|
default_value: Any = None,
|
|
356
|
-
) -> list[Any]
|
|
357
|
-
|
|
358
|
-
|
|
349
|
+
) -> list[Any]: ...
|
|
350
|
+
|
|
351
|
+
@overload
|
|
352
|
+
def get(
|
|
353
|
+
self,
|
|
354
|
+
attr_names: list[str],
|
|
355
|
+
handle_missing: Literal["error", "default"] = "error",
|
|
356
|
+
default_value: Any = None,
|
|
357
|
+
) -> list[list[Any]]: ...
|
|
358
|
+
|
|
359
|
+
def get(
|
|
360
|
+
self,
|
|
361
|
+
attr_names,
|
|
362
|
+
handle_missing="error",
|
|
363
|
+
default_value=None,
|
|
364
|
+
):
|
|
365
|
+
"""Retrieve the specified attribute(s) from each agent in the AgentSet.
|
|
359
366
|
|
|
360
367
|
Args:
|
|
361
368
|
attr_names (str | list[str]): The name(s) of the attribute(s) to retrieve from each agent.
|
|
@@ -402,8 +409,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
402
409
|
)
|
|
403
410
|
|
|
404
411
|
def set(self, attr_name: str, value: Any) -> AgentSet:
|
|
405
|
-
"""
|
|
406
|
-
Set a specified attribute to a given value for all agents in the AgentSet.
|
|
412
|
+
"""Set a specified attribute to a given value for all agents in the AgentSet.
|
|
407
413
|
|
|
408
414
|
Args:
|
|
409
415
|
attr_name (str): The name of the attribute to set.
|
|
@@ -417,8 +423,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
417
423
|
return self
|
|
418
424
|
|
|
419
425
|
def __getitem__(self, item: int | slice) -> Agent:
|
|
420
|
-
"""
|
|
421
|
-
Retrieve an agent or a slice of agents from the AgentSet.
|
|
426
|
+
"""Retrieve an agent or a slice of agents from the AgentSet.
|
|
422
427
|
|
|
423
428
|
Args:
|
|
424
429
|
item (int | slice): The index or slice for selecting agents.
|
|
@@ -429,8 +434,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
429
434
|
return list(self._agents.keys())[item]
|
|
430
435
|
|
|
431
436
|
def add(self, agent: Agent):
|
|
432
|
-
"""
|
|
433
|
-
Add an agent to the AgentSet.
|
|
437
|
+
"""Add an agent to the AgentSet.
|
|
434
438
|
|
|
435
439
|
Args:
|
|
436
440
|
agent (Agent): The agent to add to the set.
|
|
@@ -441,8 +445,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
441
445
|
self._agents[agent] = None
|
|
442
446
|
|
|
443
447
|
def discard(self, agent: Agent):
|
|
444
|
-
"""
|
|
445
|
-
Remove an agent from the AgentSet if it exists.
|
|
448
|
+
"""Remove an agent from the AgentSet if it exists.
|
|
446
449
|
|
|
447
450
|
This method does not raise an error if the agent is not present.
|
|
448
451
|
|
|
@@ -456,8 +459,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
456
459
|
del self._agents[agent]
|
|
457
460
|
|
|
458
461
|
def remove(self, agent: Agent):
|
|
459
|
-
"""
|
|
460
|
-
Remove an agent from the AgentSet.
|
|
462
|
+
"""Remove an agent from the AgentSet.
|
|
461
463
|
|
|
462
464
|
This method raises an error if the agent is not present.
|
|
463
465
|
|
|
@@ -470,8 +472,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
470
472
|
del self._agents[agent]
|
|
471
473
|
|
|
472
474
|
def __getstate__(self):
|
|
473
|
-
"""
|
|
474
|
-
Retrieve the state of the AgentSet for serialization.
|
|
475
|
+
"""Retrieve the state of the AgentSet for serialization.
|
|
475
476
|
|
|
476
477
|
Returns:
|
|
477
478
|
dict: A dictionary representing the state of the AgentSet.
|
|
@@ -479,8 +480,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
479
480
|
return {"agents": list(self._agents.keys()), "model": self.model}
|
|
480
481
|
|
|
481
482
|
def __setstate__(self, state):
|
|
482
|
-
"""
|
|
483
|
-
Set the state of the AgentSet during deserialization.
|
|
483
|
+
"""Set the state of the AgentSet during deserialization.
|
|
484
484
|
|
|
485
485
|
Args:
|
|
486
486
|
state (dict): A dictionary representing the state to restore.
|
|
@@ -490,8 +490,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
490
490
|
|
|
491
491
|
@property
|
|
492
492
|
def random(self) -> Random:
|
|
493
|
-
"""
|
|
494
|
-
Provide access to the model's random number generator.
|
|
493
|
+
"""Provide access to the model's random number generator.
|
|
495
494
|
|
|
496
495
|
Returns:
|
|
497
496
|
Random: The random number generator associated with the model.
|
|
@@ -499,8 +498,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
499
498
|
return self.model.random
|
|
500
499
|
|
|
501
500
|
def groupby(self, by: Callable | str, result_type: str = "agentset") -> GroupBy:
|
|
502
|
-
"""
|
|
503
|
-
Group agents by the specified attribute or return from the callable
|
|
501
|
+
"""Group agents by the specified attribute or return from the callable.
|
|
504
502
|
|
|
505
503
|
Args:
|
|
506
504
|
by (Callable, str): used to determine what to group agents by
|
|
@@ -510,6 +508,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
510
508
|
* if ``by`` is a str, it should refer to an attribute on the agent and the value
|
|
511
509
|
of this attribute will be used for grouping
|
|
512
510
|
result_type (str, optional): The datatype for the resulting groups {"agentset", "list"}
|
|
511
|
+
|
|
513
512
|
Returns:
|
|
514
513
|
GroupBy
|
|
515
514
|
|
|
@@ -541,8 +540,7 @@ class AgentSet(MutableSet, Sequence):
|
|
|
541
540
|
|
|
542
541
|
|
|
543
542
|
class GroupBy:
|
|
544
|
-
"""Helper class for AgentSet.groupby
|
|
545
|
-
|
|
543
|
+
"""Helper class for AgentSet.groupby.
|
|
546
544
|
|
|
547
545
|
Attributes:
|
|
548
546
|
groups (dict): A dictionary with the group_name as key and group as values
|
|
@@ -550,6 +548,12 @@ class GroupBy:
|
|
|
550
548
|
"""
|
|
551
549
|
|
|
552
550
|
def __init__(self, groups: dict[Any, list | AgentSet]):
|
|
551
|
+
"""Initialize a GroupBy instance.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
groups (dict): A dictionary with the group_name as key and group as values
|
|
555
|
+
|
|
556
|
+
"""
|
|
553
557
|
self.groups: dict[Any, list | AgentSet] = groups
|
|
554
558
|
|
|
555
559
|
def map(self, method: Callable | str, *args, **kwargs) -> dict[Any, Any]:
|
|
@@ -562,6 +566,8 @@ class GroupBy:
|
|
|
562
566
|
* if ``method`` is a str, it should refer to a method on the group
|
|
563
567
|
|
|
564
568
|
Additional arguments and keyword arguments will be passed on to the callable.
|
|
569
|
+
args: arguments to pass to the callable
|
|
570
|
+
kwargs: keyword arguments to pass to the callable
|
|
565
571
|
|
|
566
572
|
Returns:
|
|
567
573
|
dict with group_name as key and the return of the method as value
|
|
@@ -579,7 +585,7 @@ class GroupBy:
|
|
|
579
585
|
return {k: method(v, *args, **kwargs) for k, v in self.groups.items()}
|
|
580
586
|
|
|
581
587
|
def do(self, method: Callable | str, *args, **kwargs) -> GroupBy:
|
|
582
|
-
"""Apply the specified callable to each group
|
|
588
|
+
"""Apply the specified callable to each group.
|
|
583
589
|
|
|
584
590
|
Args:
|
|
585
591
|
method (Callable, str): The callable to apply to each group,
|
|
@@ -588,6 +594,8 @@ class GroupBy:
|
|
|
588
594
|
* if ``method`` is a str, it should refer to a method on the group
|
|
589
595
|
|
|
590
596
|
Additional arguments and keyword arguments will be passed on to the callable.
|
|
597
|
+
args: arguments to pass to the callable
|
|
598
|
+
kwargs: keyword arguments to pass to the callable
|
|
591
599
|
|
|
592
600
|
Returns:
|
|
593
601
|
the original GroupBy instance
|
|
@@ -606,8 +614,31 @@ class GroupBy:
|
|
|
606
614
|
|
|
607
615
|
return self
|
|
608
616
|
|
|
609
|
-
def
|
|
617
|
+
def count(self) -> dict[Any, int]:
|
|
618
|
+
"""Return the count of agents in each group.
|
|
619
|
+
|
|
620
|
+
Returns:
|
|
621
|
+
dict: A dictionary mapping group names to the number of agents in each group.
|
|
622
|
+
"""
|
|
623
|
+
return {k: len(v) for k, v in self.groups.items()}
|
|
624
|
+
|
|
625
|
+
def agg(self, attr_name: str, func: Callable) -> dict[Hashable, Any]:
|
|
626
|
+
"""Aggregate the values of a specific attribute across each group using the provided function.
|
|
627
|
+
|
|
628
|
+
Args:
|
|
629
|
+
attr_name (str): The name of the attribute to aggregate.
|
|
630
|
+
func (Callable): The function to apply (e.g., sum, min, max, mean).
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
dict[Hashable, Any]: A dictionary mapping group names to the result of applying the aggregation function.
|
|
634
|
+
"""
|
|
635
|
+
return {
|
|
636
|
+
group_name: func([getattr(agent, attr_name) for agent in group])
|
|
637
|
+
for group_name, group in self.groups.items()
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
def __iter__(self): # noqa: D105
|
|
610
641
|
return iter(self.groups.items())
|
|
611
642
|
|
|
612
|
-
def __len__(self):
|
|
643
|
+
def __len__(self): # noqa: D105
|
|
613
644
|
return len(self.groups)
|
mesa/batchrunner.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
"""batchrunner for running a factorial experiment design over a model."""
|
|
2
|
+
|
|
1
3
|
import itertools
|
|
2
4
|
import multiprocessing
|
|
3
5
|
from collections.abc import Iterable, Mapping
|
|
@@ -24,29 +26,22 @@ def batch_run(
|
|
|
24
26
|
) -> list[dict[str, Any]]:
|
|
25
27
|
"""Batch run a mesa model with a set of parameter values.
|
|
26
28
|
|
|
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
|
|
29
|
+
Args:
|
|
30
|
+
model_cls (Type[Model]): The model class to batch-run
|
|
31
|
+
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.
|
|
32
|
+
number_processes (int, optional): Number of processes used, by default 1. Set this to None if you want to use all CPUs.
|
|
33
|
+
iterations (int, optional): Number of iterations for each parameter combination, by default 1
|
|
34
|
+
data_collection_period (int, optional): Number of steps after which data gets collected, by default -1 (end of episode)
|
|
35
|
+
max_steps (int, optional): Maximum number of model steps after which the model halts, by default 1000
|
|
36
|
+
display_progress (bool, optional): Display batch run process, by default True
|
|
43
37
|
|
|
44
|
-
Returns
|
|
45
|
-
|
|
46
|
-
List[Dict[str, Any]]
|
|
47
|
-
[description]
|
|
48
|
-
"""
|
|
38
|
+
Returns:
|
|
39
|
+
List[Dict[str, Any]]
|
|
49
40
|
|
|
41
|
+
Notes:
|
|
42
|
+
batch_run assumes the model has a `datacollector` attribute that has a DataCollector object initialized.
|
|
43
|
+
|
|
44
|
+
"""
|
|
50
45
|
runs_list = []
|
|
51
46
|
run_id = 0
|
|
52
47
|
for iteration in range(iterations):
|
|
@@ -88,7 +83,7 @@ def _make_model_kwargs(
|
|
|
88
83
|
parameters : Mapping[str, Union[Any, Iterable[Any]]]
|
|
89
84
|
Single or multiple values for each model parameter name
|
|
90
85
|
|
|
91
|
-
Returns
|
|
86
|
+
Returns:
|
|
92
87
|
-------
|
|
93
88
|
List[Dict[str, Any]]
|
|
94
89
|
A list of all kwargs combinations.
|
|
@@ -128,7 +123,7 @@ def _model_run_func(
|
|
|
128
123
|
data_collection_period : int
|
|
129
124
|
Number of steps after which data gets collected
|
|
130
125
|
|
|
131
|
-
Returns
|
|
126
|
+
Returns:
|
|
132
127
|
-------
|
|
133
128
|
List[Dict[str, Any]]
|
|
134
129
|
Return model_data, agent_data from the reporters
|
|
@@ -181,6 +176,10 @@ def _collect_data(
|
|
|
181
176
|
step: int,
|
|
182
177
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
183
178
|
"""Collect model and agent data from a model using mesas datacollector."""
|
|
179
|
+
if not hasattr(model, "datacollector"):
|
|
180
|
+
raise AttributeError(
|
|
181
|
+
"The model does not have a datacollector attribute. Please add a DataCollector to your model."
|
|
182
|
+
)
|
|
184
183
|
dc = model.datacollector
|
|
185
184
|
|
|
186
185
|
model_data = {param: values[step] for param, values in dc.model_vars.items()}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""helper modules."""
|