Mesa 2.3.4__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.

Files changed (110) hide show
  1. mesa/__init__.py +3 -5
  2. mesa/agent.py +393 -116
  3. mesa/batchrunner.py +58 -31
  4. mesa/datacollection.py +141 -30
  5. mesa/examples/README.md +37 -0
  6. mesa/examples/__init__.py +21 -0
  7. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
  8. mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
  9. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  10. mesa/examples/advanced/epstein_civil_violence/agents.py +164 -0
  11. mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
  12. mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
  13. mesa/examples/advanced/pd_grid/Readme.md +43 -0
  14. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  15. mesa/examples/advanced/pd_grid/agents.py +50 -0
  16. mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
  17. mesa/examples/advanced/pd_grid/app.py +54 -0
  18. mesa/examples/advanced/pd_grid/model.py +71 -0
  19. mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
  20. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  21. mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
  22. mesa/examples/advanced/sugarscape_g1mt/app.py +62 -0
  23. mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
  24. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
  25. mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
  26. mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
  27. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  28. mesa/examples/advanced/wolf_sheep/agents.py +102 -0
  29. mesa/examples/advanced/wolf_sheep/app.py +84 -0
  30. mesa/examples/advanced/wolf_sheep/model.py +137 -0
  31. mesa/examples/basic/__init__.py +0 -0
  32. mesa/examples/basic/boid_flockers/Readme.md +22 -0
  33. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  34. mesa/examples/basic/boid_flockers/agents.py +71 -0
  35. mesa/examples/basic/boid_flockers/app.py +58 -0
  36. mesa/examples/basic/boid_flockers/model.py +69 -0
  37. mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
  38. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  39. mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
  40. mesa/examples/basic/boltzmann_wealth_model/app.py +74 -0
  41. mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
  42. mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
  43. mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
  44. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  45. mesa/examples/basic/conways_game_of_life/agents.py +47 -0
  46. mesa/examples/basic/conways_game_of_life/app.py +51 -0
  47. mesa/examples/basic/conways_game_of_life/model.py +31 -0
  48. mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
  49. mesa/examples/basic/schelling/Readme.md +40 -0
  50. mesa/examples/basic/schelling/__init__.py +0 -0
  51. mesa/examples/basic/schelling/agents.py +26 -0
  52. mesa/examples/basic/schelling/analysis.ipynb +205 -0
  53. mesa/examples/basic/schelling/app.py +42 -0
  54. mesa/examples/basic/schelling/model.py +59 -0
  55. mesa/examples/basic/virus_on_network/Readme.md +61 -0
  56. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  57. mesa/examples/basic/virus_on_network/agents.py +69 -0
  58. mesa/examples/basic/virus_on_network/app.py +114 -0
  59. mesa/examples/basic/virus_on_network/model.py +96 -0
  60. mesa/experimental/UserParam.py +18 -7
  61. mesa/experimental/__init__.py +10 -2
  62. mesa/experimental/cell_space/__init__.py +16 -1
  63. mesa/experimental/cell_space/cell.py +93 -23
  64. mesa/experimental/cell_space/cell_agent.py +117 -21
  65. mesa/experimental/cell_space/cell_collection.py +56 -19
  66. mesa/experimental/cell_space/discrete_space.py +92 -8
  67. mesa/experimental/cell_space/grid.py +33 -9
  68. mesa/experimental/cell_space/network.py +15 -10
  69. mesa/experimental/cell_space/voronoi.py +257 -0
  70. mesa/experimental/components/altair.py +11 -2
  71. mesa/experimental/components/matplotlib.py +132 -26
  72. mesa/experimental/devs/__init__.py +2 -0
  73. mesa/experimental/devs/eventlist.py +54 -15
  74. mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
  75. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  76. mesa/experimental/devs/simulator.py +57 -16
  77. mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -98
  78. mesa/model.py +212 -84
  79. mesa/space.py +217 -151
  80. mesa/time.py +63 -80
  81. mesa/visualization/__init__.py +25 -6
  82. mesa/visualization/components/__init__.py +83 -0
  83. mesa/visualization/components/altair_components.py +188 -0
  84. mesa/visualization/components/matplotlib_components.py +175 -0
  85. mesa/visualization/mpl_space_drawing.py +593 -0
  86. mesa/visualization/solara_viz.py +458 -0
  87. mesa/visualization/user_param.py +69 -0
  88. mesa/visualization/utils.py +9 -0
  89. {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/METADATA +65 -19
  90. mesa-3.0.0.dist-info/RECORD +95 -0
  91. mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
  92. mesa-2.3.4.dist-info/licenses/LICENSE → mesa-3.0.0.dist-info/licenses/NOTICE +2 -2
  93. mesa/cookiecutter-mesa/cookiecutter.json +0 -8
  94. mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -11
  95. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
  96. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
  97. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
  98. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
  99. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
  100. mesa/flat/__init__.py +0 -6
  101. mesa/flat/visualization.py +0 -5
  102. mesa/main.py +0 -63
  103. mesa/visualization/ModularVisualization.py +0 -1
  104. mesa/visualization/TextVisualization.py +0 -1
  105. mesa/visualization/UserParam.py +0 -1
  106. mesa/visualization/modules.py +0 -1
  107. mesa-2.3.4.dist-info/RECORD +0 -45
  108. /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
  109. {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
  110. {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
mesa/model.py CHANGED
@@ -1,5 +1,4 @@
1
- """
2
- The model class for Mesa framework.
1
+ """The model class for Mesa framework.
3
2
 
4
3
  Core Objects: Model
5
4
  """
@@ -8,18 +7,21 @@ Core Objects: Model
8
7
  # Remove this __future__ import once the oldest supported Python is 3.10
9
8
  from __future__ import annotations
10
9
 
11
- import itertools
12
10
  import random
11
+ import sys
13
12
  import warnings
14
- from collections import defaultdict
13
+ from collections.abc import Sequence
15
14
 
16
15
  # mypy
17
- from typing import Any, Union
16
+ from typing import Any
17
+
18
+ import numpy as np
18
19
 
19
20
  from mesa.agent import Agent, AgentSet
20
21
  from mesa.datacollection import DataCollector
21
22
 
22
- TimeT = Union[float, int]
23
+ SeedLike = int | np.integer | Sequence[int] | np.random.SeedSequence
24
+ RNGLike = np.random.Generator | np.random.BitGenerator
23
25
 
24
26
 
25
27
  class Model:
@@ -32,85 +34,190 @@ class Model:
32
34
  Attributes:
33
35
  running: A boolean indicating if the model should continue running.
34
36
  schedule: An object to manage the order and execution of agent steps.
35
- current_id: A counter for assigning unique IDs to agents.
36
- agents_: A defaultdict mapping each agent type to a dict of its instances.
37
- This private attribute is used internally to manage agents.
38
-
39
- Properties:
40
- agents: An AgentSet containing all agents in the model, generated from the _agents attribute.
41
- agent_types: A list of different agent types present in the model.
42
-
43
- Methods:
44
- get_agents_of_type: Returns an AgentSet of agents of the specified type.
45
- run_model: Runs the model's simulation until a defined end condition is reached.
46
- step: Executes a single step of the model's simulation process.
47
- next_id: Generates and returns the next unique identifier for an agent.
48
- reset_randomizer: Resets the model's random number generator with a new or existing seed.
49
- initialize_data_collector: Sets up the data collector for the model, requiring an initialized scheduler and agents.
37
+ steps: the number of times `model.step()` has been called.
38
+ random: a seeded python.random number generator.
39
+ rng : a seeded numpy.random.Generator
40
+
41
+ Notes:
42
+ Model.agents returns the AgentSet containing all agents registered with the model. Changing
43
+ the content of the AgentSet directly can result in strange behavior. If you want change the
44
+ composition of this AgentSet, ensure you operate on a copy.
45
+
50
46
  """
51
47
 
52
- def __new__(cls, *args: Any, **kwargs: Any) -> Any:
53
- """Create a new model object and instantiate its RNG automatically."""
54
- obj = object.__new__(cls)
55
- obj._seed = kwargs.get("seed")
56
- if obj._seed is None:
57
- # We explicitly specify the seed here so that we know its value in
58
- # advance.
59
- obj._seed = random.random()
60
- obj.random = random.Random(obj._seed)
61
- # TODO: Remove these 2 lines just before Mesa 3.0
62
- obj._steps = 0
63
- obj._time = 0
64
- return obj
65
-
66
- def __init__(self, *args: Any, **kwargs: Any) -> None:
67
- """Create a new model. Overload this method with the actual code to
68
- start the model. Always start with super().__init__() to initialize the
69
- model object properly.
70
- """
48
+ def __init__(
49
+ self,
50
+ *args: Any,
51
+ seed: float | None = None,
52
+ rng: RNGLike | SeedLike | None = None,
53
+ **kwargs: Any,
54
+ ) -> None:
55
+ """Create a new model.
56
+
57
+ Overload this method with the actual code to initialize the model. Always start with super().__init__()
58
+ to initialize the model object properly.
59
+
60
+ Args:
61
+ args: arguments to pass onto super
62
+ seed: the seed for the random number generator
63
+ rng : Pseudorandom number generator state. When `rng` is None, a new `numpy.random.Generator` is created
64
+ using entropy from the operating system. Types other than `numpy.random.Generator` are passed to
65
+ `numpy.random.default_rng` to instantiate a `Generator`.
66
+ kwargs: keyword arguments to pass onto super
67
+
68
+ Notes:
69
+ you have to pass either seed or rng, but not both.
71
70
 
71
+ """
72
+ super().__init__(*args, **kwargs)
72
73
  self.running = True
73
- self.schedule = None
74
- self.current_id = 0
75
- self.agents_: defaultdict[type, dict] = defaultdict(dict)
74
+ self.steps: int = 0
75
+
76
+ if (seed is not None) and (rng is not None):
77
+ raise ValueError("you have to pass either rng or seed, not both")
78
+ elif seed is None:
79
+ self.rng: np.random.Generator = np.random.default_rng(rng)
80
+ self._rng = (
81
+ self.rng.bit_generator.state
82
+ ) # this allows for reproducing the rng
83
+
84
+ try:
85
+ self.random = random.Random(rng)
86
+ except TypeError:
87
+ seed = int(self.rng.integers(np.iinfo(np.int32).max))
88
+ self.random = random.Random(seed)
89
+ self._seed = seed # this allows for reproducing stdlib.random
90
+ elif rng is None:
91
+ self.random = random.Random(seed)
92
+ self._seed = seed # this allows for reproducing stdlib.random
93
+
94
+ try:
95
+ self.rng: np.random.Generator = np.random.default_rng(rng)
96
+ except TypeError:
97
+ rng = self.random.randint(0, sys.maxsize)
98
+ self.rng: np.random.Generator = np.random.default_rng(rng)
99
+ self._rng = self.rng.bit_generator.state
100
+
101
+ # Wrap the user-defined step method
102
+ self._user_step = self.step
103
+ self.step = self._wrapped_step
104
+
105
+ # setup agent registration data structures
106
+ self._setup_agent_registration()
107
+
108
+ def _wrapped_step(self, *args: Any, **kwargs: Any) -> None:
109
+ """Automatically increments time and steps after calling the user's step method."""
110
+ # Automatically increment time and step counters
111
+ self.steps += 1
112
+ # Call the original user-defined step method
113
+ self._user_step(*args, **kwargs)
76
114
 
77
- self._steps: int = 0
78
- self._time: TimeT = 0 # the model's clock
115
+ def next_id(self) -> int: # noqa: D102
116
+ warnings.warn(
117
+ "using model.next_id() is deprecated. Agents track their unique ID automatically",
118
+ DeprecationWarning,
119
+ stacklevel=2,
120
+ )
121
+ return 0
79
122
 
80
123
  @property
81
124
  def agents(self) -> AgentSet:
82
125
  """Provides an AgentSet of all agents in the model, combining agents from all types."""
83
-
84
- if hasattr(self, "_agents"):
85
- return self._agents
86
- else:
87
- all_agents = itertools.chain.from_iterable(self.agents_.values())
88
- return AgentSet(all_agents, self)
126
+ return self._all_agents
89
127
 
90
128
  @agents.setter
91
129
  def agents(self, agents: Any) -> None:
92
- warnings.warn(
93
- "You are trying to set model.agents. In a next release, this attribute is used "
94
- "by MESA itself so you cannot use it directly anymore."
95
- "Please adjust your code to use a different attribute name for custom agent storage",
96
- UserWarning,
97
- stacklevel=2,
130
+ raise AttributeError(
131
+ "You are trying to set model.agents. In Mesa 3.0 and higher, this attribute is "
132
+ "used by Mesa itself, so you cannot use it directly anymore."
133
+ "Please adjust your code to use a different attribute name for custom agent storage."
98
134
  )
99
135
 
100
- self._agents = agents
101
-
102
136
  @property
103
137
  def agent_types(self) -> list[type]:
104
- """Return a list of different agent types."""
105
- return list(self.agents_.keys())
138
+ """Return a list of all unique agent types registered with the model."""
139
+ return list(self._agents_by_type.keys())
140
+
141
+ @property
142
+ def agents_by_type(self) -> dict[type[Agent], AgentSet]:
143
+ """A dictionary where the keys are agent types and the values are the corresponding AgentSets."""
144
+ return self._agents_by_type
106
145
 
107
146
  def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet:
108
- """Retrieves an AgentSet containing all agents of the specified type."""
109
- return AgentSet(self.agents_[agenttype].keys(), self)
147
+ """Deprecated: Retrieves an AgentSet containing all agents of the specified type."""
148
+ warnings.warn(
149
+ f"Model.get_agents_of_type() is deprecated, please replace get_agents_of_type({agenttype})"
150
+ f"with the property agents_by_type[{agenttype}].",
151
+ DeprecationWarning,
152
+ stacklevel=2,
153
+ )
154
+ return self.agents_by_type[agenttype]
155
+
156
+ def _setup_agent_registration(self):
157
+ """Helper method to initialize the agent registration datastructures."""
158
+ self._agents = {} # the hard references to all agents in the model
159
+ self._agents_by_type: dict[
160
+ type[Agent], AgentSet
161
+ ] = {} # a dict with an agentset for each class of agents
162
+ self._all_agents = AgentSet(
163
+ [], random=self.random
164
+ ) # an agenset with all agents
165
+
166
+ def register_agent(self, agent):
167
+ """Register the agent with the model.
168
+
169
+ Args:
170
+ agent: The agent to register.
171
+
172
+ Notes:
173
+ This method is called automatically by ``Agent.__init__``, so there is no need to use this
174
+ if you are subclassing Agent and calling its super in the ``__init__`` method.
175
+
176
+ """
177
+ if not hasattr(self, "_agents"):
178
+ self._setup_agent_registration()
179
+
180
+ warnings.warn(
181
+ "The Mesa Model class was not initialized. In the future, you need to explicitly initialize "
182
+ "the Model by calling super().__init__() on initialization.",
183
+ FutureWarning,
184
+ stacklevel=2,
185
+ )
186
+
187
+ self._agents[agent] = None
188
+
189
+ # because AgentSet requires model, we cannot use defaultdict
190
+ # tricks with a function won't work because model then cannot be pickled
191
+ try:
192
+ self._agents_by_type[type(agent)].add(agent)
193
+ except KeyError:
194
+ self._agents_by_type[type(agent)] = AgentSet(
195
+ [
196
+ agent,
197
+ ],
198
+ random=self.random,
199
+ )
200
+
201
+ self._all_agents.add(agent)
202
+
203
+ def deregister_agent(self, agent):
204
+ """Deregister the agent with the model.
205
+
206
+ Args:
207
+ agent: The agent to deregister.
208
+
209
+ Notes:
210
+ This method is called automatically by ``Agent.remove``
211
+
212
+ """
213
+ del self._agents[agent]
214
+ self._agents_by_type[type(agent)].remove(agent)
215
+ self._all_agents.remove(agent)
110
216
 
111
217
  def run_model(self) -> None:
112
- """Run the model until the end condition is reached. Overload as
113
- needed.
218
+ """Run the model until the end condition is reached.
219
+
220
+ Overload as needed.
114
221
  """
115
222
  while self.running:
116
223
  self.step()
@@ -118,46 +225,67 @@ class Model:
118
225
  def step(self) -> None:
119
226
  """A single step. Fill in here."""
120
227
 
121
- def _advance_time(self, deltat: TimeT = 1):
122
- """Increment the model's steps counter and clock."""
123
- self._steps += 1
124
- self._time += deltat
125
-
126
- def next_id(self) -> int:
127
- """Return the next unique ID for agents, increment current_id"""
128
- self.current_id += 1
129
- return self.current_id
130
-
131
228
  def reset_randomizer(self, seed: int | None = None) -> None:
132
229
  """Reset the model random number generator.
133
230
 
134
231
  Args:
135
232
  seed: A new seed for the RNG; if None, reset using the current seed
136
233
  """
137
-
138
234
  if seed is None:
139
235
  seed = self._seed
140
236
  self.random.seed(seed)
141
237
  self._seed = seed
142
238
 
239
+ def reset_rng(self, rng: RNGLike | SeedLike | None = None) -> None:
240
+ """Reset the model random number generator.
241
+
242
+ Args:
243
+ rng: A new seed for the RNG; if None, reset using the current seed
244
+ """
245
+ self.rng = np.random.default_rng(rng)
246
+ self._rng = self.rng.bit_generator.state
247
+
143
248
  def initialize_data_collector(
144
249
  self,
145
250
  model_reporters=None,
146
251
  agent_reporters=None,
252
+ agenttype_reporters=None,
147
253
  tables=None,
148
254
  ) -> None:
149
- if not hasattr(self, "schedule") or self.schedule is None:
150
- raise RuntimeError(
151
- "You must initialize the scheduler (self.schedule) before initializing the data collector."
152
- )
153
- if self.schedule.get_agent_count() == 0:
154
- raise RuntimeError(
155
- "You must add agents to the scheduler before initializing the data collector."
156
- )
255
+ """Initialize the data collector for the model.
256
+
257
+ Args:
258
+ model_reporters: model reporters to collect
259
+ agent_reporters: agent reporters to collect
260
+ agenttype_reporters: agent type reporters to collect
261
+ tables: tables to collect
262
+
263
+ """
264
+ warnings.warn(
265
+ "initialize_data_collector() is deprecated. Please use the DataCollector class directly. "
266
+ "by using `self.datacollector = DataCollector(...)`.",
267
+ DeprecationWarning,
268
+ stacklevel=2,
269
+ )
270
+
157
271
  self.datacollector = DataCollector(
158
272
  model_reporters=model_reporters,
159
273
  agent_reporters=agent_reporters,
274
+ agenttype_reporters=agenttype_reporters,
160
275
  tables=tables,
161
276
  )
162
277
  # Collect data for the first time during initialization.
163
278
  self.datacollector.collect(self)
279
+
280
+ def remove_all_agents(self):
281
+ """Remove all agents from the model.
282
+
283
+ Notes:
284
+ This method calls agent.remove for all agents in the model. If you need to remove agents from
285
+ e.g., a SingleGrid, you can either explicitly implement your own agent.remove method or clean this up
286
+ near where you are calling this method.
287
+
288
+ """
289
+ # we need to wrap keys in a list to avoid a RunTimeError: dictionary changed size during iteration
290
+ for agent in list(self._agents.keys()):
291
+ agent.remove()