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.

Files changed (41) hide show
  1. mesa/__init__.py +2 -3
  2. mesa/agent.py +116 -85
  3. mesa/batchrunner.py +22 -23
  4. mesa/cookiecutter-mesa/hooks/post_gen_project.py +2 -0
  5. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/__init__.py +1 -0
  6. mesa/datacollection.py +138 -27
  7. mesa/experimental/UserParam.py +17 -6
  8. mesa/experimental/__init__.py +2 -0
  9. mesa/experimental/cell_space/__init__.py +14 -1
  10. mesa/experimental/cell_space/cell.py +84 -23
  11. mesa/experimental/cell_space/cell_agent.py +117 -21
  12. mesa/experimental/cell_space/cell_collection.py +54 -17
  13. mesa/experimental/cell_space/discrete_space.py +79 -7
  14. mesa/experimental/cell_space/grid.py +19 -8
  15. mesa/experimental/cell_space/network.py +9 -7
  16. mesa/experimental/cell_space/voronoi.py +26 -33
  17. mesa/experimental/components/altair.py +10 -0
  18. mesa/experimental/components/matplotlib.py +18 -0
  19. mesa/experimental/devs/__init__.py +2 -0
  20. mesa/experimental/devs/eventlist.py +36 -15
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +65 -29
  22. mesa/experimental/devs/examples/wolf_sheep.py +40 -35
  23. mesa/experimental/devs/simulator.py +55 -15
  24. mesa/experimental/solara_viz.py +10 -19
  25. mesa/main.py +6 -4
  26. mesa/model.py +51 -54
  27. mesa/space.py +145 -120
  28. mesa/time.py +57 -67
  29. mesa/visualization/UserParam.py +19 -6
  30. mesa/visualization/__init__.py +3 -2
  31. mesa/visualization/components/altair.py +4 -2
  32. mesa/visualization/components/matplotlib.py +176 -85
  33. mesa/visualization/solara_viz.py +167 -84
  34. mesa/visualization/utils.py +3 -1
  35. {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/METADATA +55 -13
  36. mesa-3.0.0b0.dist-info/RECORD +45 -0
  37. mesa-3.0.0b0.dist-info/licenses/LICENSE +202 -0
  38. mesa-3.0.0a4.dist-info/licenses/LICENSE → mesa-3.0.0b0.dist-info/licenses/NOTICE +2 -2
  39. mesa-3.0.0a4.dist-info/RECORD +0 -44
  40. {mesa-3.0.0a4.dist-info → mesa-3.0.0b0.dist-info}/WHEEL +0 -0
  41. {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.0a4"
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
- # TODO: Cleanup in future Mesa version (3.1+)
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
- self.pos: Position | None = None
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
- A collection class that represents an ordered set of agents within an agent-based model (ABM). This class
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
- Methods:
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 map(self, method: str | Callable, *args, **kwargs) -> list[Any]:
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
- Invoke a method or function on each agent in the AgentSet and return the results.
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 | list[str],
346
+ attr_names: str,
354
347
  handle_missing: Literal["error", "default"] = "error",
355
348
  default_value: Any = None,
356
- ) -> list[Any] | list[list[Any]]:
357
- """
358
- Retrieve the specified attribute(s) from each agent in the AgentSet.
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 __iter__(self):
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
- Parameters
28
- ----------
29
- model_cls : Type[Model]
30
- The model class to batch-run
31
- parameters : Mapping[str, Union[Any, Iterable[Any]]],
32
- Dictionary with model parameters over which to run the model. You can either pass single values or iterables.
33
- number_processes : int, optional
34
- Number of processes used, by default 1. Set this to None if you want to use all CPUs.
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()}
@@ -1,3 +1,5 @@
1
+ """helper module."""
2
+
1
3
  import glob
2
4
  import os
3
5