Mesa 2.4.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.

Files changed (36) hide show
  1. mesa/__init__.py +1 -3
  2. mesa/agent.py +53 -332
  3. mesa/batchrunner.py +8 -11
  4. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  5. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +1 -1
  6. mesa/datacollection.py +21 -136
  7. mesa/experimental/__init__.py +1 -3
  8. mesa/experimental/cell_space/cell_collection.py +2 -2
  9. mesa/experimental/cell_space/grid.py +1 -1
  10. mesa/experimental/cell_space/network.py +3 -3
  11. mesa/experimental/devs/eventlist.py +2 -1
  12. mesa/experimental/devs/examples/epstein_civil_violence.py +1 -2
  13. mesa/experimental/devs/examples/wolf_sheep.py +2 -3
  14. mesa/experimental/devs/simulator.py +2 -1
  15. mesa/model.py +23 -93
  16. mesa/space.py +11 -17
  17. mesa/time.py +1 -3
  18. mesa/visualization/UserParam.py +56 -1
  19. mesa/visualization/__init__.py +2 -6
  20. mesa/{experimental → visualization}/components/altair.py +1 -2
  21. mesa/{experimental → visualization}/components/matplotlib.py +4 -6
  22. mesa/{experimental → visualization}/jupyter_viz.py +117 -20
  23. {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +4 -11
  24. mesa-3.0.0a0.dist-info/RECORD +38 -0
  25. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
  26. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
  27. mesa/experimental/UserParam.py +0 -56
  28. mesa/flat/__init__.py +0 -6
  29. mesa/flat/visualization.py +0 -5
  30. mesa/visualization/ModularVisualization.py +0 -1
  31. mesa/visualization/TextVisualization.py +0 -1
  32. mesa/visualization/modules.py +0 -1
  33. mesa-2.4.0.dist-info/RECORD +0 -45
  34. {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
  35. {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
  36. {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/licenses/LICENSE +0 -0
mesa/datacollection.py CHANGED
@@ -3,30 +3,28 @@ Mesa Data Collection Module
3
3
  ===========================
4
4
 
5
5
  DataCollector is meant to provide a simple, standard way to collect data
6
- generated by a Mesa model. It collects four types of data: model-level data,
7
- agent-level data, agent-type-level data, and tables.
6
+ generated by a Mesa model. It collects three types of data: model-level data,
7
+ agent-level data, and tables.
8
8
 
9
- A DataCollector is instantiated with three dictionaries of reporter names and
10
- associated variable names or functions for each, one for model-level data,
11
- one for agent-level data, and one for agent-type-level data; a fourth dictionary
12
- provides table names and columns. Variable names are converted into functions
13
- which retrieve attributes of that name.
9
+ A DataCollector is instantiated with two dictionaries of reporter names and
10
+ associated variable names or functions for each, one for model-level data and
11
+ one for agent-level data; a third dictionary provides table names and columns.
12
+ Variable names are converted into functions which retrieve attributes of that
13
+ name.
14
14
 
15
15
  When the collect() method is called, each model-level function is called, with
16
16
  the model as the argument, and the results associated with the relevant
17
- variable. Then the agent-level functions are called on each agent, and the
18
- agent-type-level functions are called on each agent of the specified type.
17
+ variable. Then the agent-level functions are called on each agent.
19
18
 
20
19
  Additionally, other objects can write directly to tables by passing in an
21
20
  appropriate dictionary object for a table row.
22
21
 
22
+ The DataCollector then stores the data it collects in dictionaries:
23
23
  * model_vars maps each reporter to a list of its values
24
24
  * tables maps each table to a dictionary, with each column as a key with a
25
25
  list as its value.
26
- * _agent_records maps each model step to a list of each agent's id
26
+ * _agent_records maps each model step to a list of each agents id
27
27
  and its values.
28
- * _agenttype_records maps each model step to a dictionary of agent types,
29
- each containing a list of each agent's id and its values.
30
28
 
31
29
  Finally, DataCollector can create a pandas DataFrame from each collection.
32
30
 
@@ -38,7 +36,6 @@ The default DataCollector here makes several assumptions:
38
36
  import contextlib
39
37
  import itertools
40
38
  import types
41
- import warnings
42
39
  from copy import deepcopy
43
40
  from functools import partial
44
41
 
@@ -49,25 +46,24 @@ with contextlib.suppress(ImportError):
49
46
  class DataCollector:
50
47
  """Class for collecting data generated by a Mesa model.
51
48
 
52
- A DataCollector is instantiated with dictionaries of names of model-,
53
- agent-, and agent-type-level variables to collect, associated with
54
- attribute names or functions which actually collect them. When the
55
- collect(...) method is called, it collects these attributes and executes
56
- these functions one by one and stores the results.
49
+ A DataCollector is instantiated with dictionaries of names of model- and
50
+ agent-level variables to collect, associated with attribute names or
51
+ functions which actually collect them. When the collect(...) method is
52
+ called, it collects these attributes and executes these functions one by
53
+ one and stores the results.
57
54
  """
58
55
 
59
56
  def __init__(
60
57
  self,
61
58
  model_reporters=None,
62
59
  agent_reporters=None,
63
- agenttype_reporters=None,
64
60
  tables=None,
65
61
  ):
66
- """Instantiate a DataCollector with lists of model, agent, and agent-type reporters.
67
-
68
- Both model_reporters, agent_reporters, and agenttype_reporters accept a
69
- dictionary mapping a variable name to either an attribute name, a function,
70
- a method of a class/instance, or a function with parameters placed in a list.
62
+ """
63
+ Instantiate a DataCollector with lists of model and agent reporters.
64
+ Both model_reporters and agent_reporters accept a dictionary mapping a
65
+ variable name to either an attribute name, a function, a method of a class/instance,
66
+ or a function with parameters placed in a list.
71
67
 
72
68
  Model reporters can take four types of arguments:
73
69
  1. Lambda function:
@@ -91,10 +87,6 @@ class DataCollector:
91
87
  4. Functions with parameters placed in a list:
92
88
  {"Agent_Function": [function, [param_1, param_2]]}
93
89
 
94
- Agenttype reporters take a dictionary mapping agent types to dictionaries
95
- of reporter names and attributes/funcs/methods, similar to agent_reporters:
96
- {Wolf: {"energy": lambda a: a.energy}}
97
-
98
90
  The tables arg accepts a dictionary mapping names of tables to lists of
99
91
  columns. For example, if we want to allow agents to write their age
100
92
  when they are destroyed (to keep track of lifespans), it might look
@@ -104,8 +96,6 @@ class DataCollector:
104
96
  Args:
105
97
  model_reporters: Dictionary of reporter names and attributes/funcs/methods.
106
98
  agent_reporters: Dictionary of reporter names and attributes/funcs/methods.
107
- agenttype_reporters: Dictionary of agent types to dictionaries of
108
- reporter names and attributes/funcs/methods.
109
99
  tables: Dictionary of table names to lists of column names.
110
100
 
111
101
  Notes:
@@ -115,11 +105,9 @@ class DataCollector:
115
105
  """
116
106
  self.model_reporters = {}
117
107
  self.agent_reporters = {}
118
- self.agenttype_reporters = {}
119
108
 
120
109
  self.model_vars = {}
121
110
  self._agent_records = {}
122
- self._agenttype_records = {}
123
111
  self.tables = {}
124
112
 
125
113
  if model_reporters is not None:
@@ -130,11 +118,6 @@ class DataCollector:
130
118
  for name, reporter in agent_reporters.items():
131
119
  self._new_agent_reporter(name, reporter)
132
120
 
133
- if agenttype_reporters is not None:
134
- for agent_type, reporters in agenttype_reporters.items():
135
- for name, reporter in reporters.items():
136
- self._new_agenttype_reporter(agent_type, name, reporter)
137
-
138
121
  if tables is not None:
139
122
  for name, columns in tables.items():
140
123
  self._new_table(name, columns)
@@ -182,38 +165,6 @@ class DataCollector:
182
165
 
183
166
  self.agent_reporters[name] = reporter
184
167
 
185
- def _new_agenttype_reporter(self, agent_type, name, reporter):
186
- """Add a new agent-type-level reporter to collect.
187
-
188
- Args:
189
- agent_type: The type of agent to collect data for.
190
- name: Name of the agent-type-level variable to collect.
191
- reporter: Attribute string, function object, method of a class/instance, or
192
- function with parameters placed in a list that returns the
193
- variable when given an agent instance.
194
- """
195
- if agent_type not in self.agenttype_reporters:
196
- self.agenttype_reporters[agent_type] = {}
197
-
198
- # Use the same logic as _new_agent_reporter
199
- if isinstance(reporter, str):
200
- attribute_name = reporter
201
-
202
- def attr_reporter(agent):
203
- return getattr(agent, attribute_name, None)
204
-
205
- reporter = attr_reporter
206
-
207
- elif isinstance(reporter, list):
208
- func, params = reporter[0], reporter[1]
209
-
210
- def func_with_params(agent):
211
- return func(agent, *params)
212
-
213
- reporter = func_with_params
214
-
215
- self.agenttype_reporters[agent_type][name] = reporter
216
-
217
168
  def _new_table(self, table_name, table_columns):
218
169
  """Add a new table that objects can write to.
219
170
 
@@ -241,40 +192,12 @@ class DataCollector:
241
192
  )
242
193
  return agent_records
243
194
 
244
- def _record_agenttype(self, model, agent_type):
245
- """Record agent-type data in a mapping of functions and agents."""
246
- rep_funcs = self.agenttype_reporters[agent_type].values()
247
-
248
- def get_reports(agent):
249
- _prefix = (agent.model._steps, agent.unique_id)
250
- reports = tuple(rep(agent) for rep in rep_funcs)
251
- return _prefix + reports
252
-
253
- agent_types = model.agent_types
254
- if agent_type in agent_types:
255
- agents = model.agents_by_type[agent_type]
256
- else:
257
- from mesa import Agent
258
-
259
- if issubclass(agent_type, Agent):
260
- agents = [
261
- agent for agent in model.agents if isinstance(agent, agent_type)
262
- ]
263
- else:
264
- # Raise error if agent_type is not in model.agent_types
265
- raise ValueError(
266
- f"Agent type {agent_type} is not recognized as an Agent type in the model or Agent subclass. Use an Agent (sub)class, like {agent_types}."
267
- )
268
-
269
- agenttype_records = map(get_reports, agents)
270
- return agenttype_records
271
-
272
195
  def collect(self, model):
273
196
  """Collect all the data for the given model object."""
274
197
  if self.model_reporters:
275
198
  for var, reporter in self.model_reporters.items():
276
199
  # Check if lambda or partial function
277
- if isinstance(reporter, (types.LambdaType, partial)):
200
+ if isinstance(reporter, types.LambdaType | partial):
278
201
  # Use deepcopy to store a copy of the data,
279
202
  # preventing references from being updated across steps.
280
203
  self.model_vars[var].append(deepcopy(reporter(model)))
@@ -295,14 +218,6 @@ class DataCollector:
295
218
  agent_records = self._record_agents(model)
296
219
  self._agent_records[model._steps] = list(agent_records)
297
220
 
298
- if self.agenttype_reporters:
299
- self._agenttype_records[model._steps] = {}
300
- for agent_type in self.agenttype_reporters:
301
- agenttype_records = self._record_agenttype(model, agent_type)
302
- self._agenttype_records[model._steps][agent_type] = list(
303
- agenttype_records
304
- )
305
-
306
221
  def add_table_row(self, table_name, row, ignore_missing=False):
307
222
  """Add a row dictionary to a specific table.
308
223
 
@@ -359,36 +274,6 @@ class DataCollector:
359
274
  )
360
275
  return df
361
276
 
362
- def get_agenttype_vars_dataframe(self, agent_type):
363
- """Create a pandas DataFrame from the agent-type variables for a specific agent type.
364
- The DataFrame has one column for each variable, with two additional
365
- columns for tick and agent_id.
366
- Args:
367
- agent_type: The type of agent to get the data for.
368
- """
369
- # Check if self.agenttype_reporters dictionary is empty for this agent type, if so return empty DataFrame
370
- if agent_type not in self.agenttype_reporters:
371
- warnings.warn(
372
- f"No agent-type reporters have been defined for {agent_type} in the DataCollector, returning empty DataFrame.",
373
- UserWarning,
374
- stacklevel=2,
375
- )
376
- return pd.DataFrame()
377
-
378
- all_records = itertools.chain.from_iterable(
379
- records[agent_type]
380
- for records in self._agenttype_records.values()
381
- if agent_type in records
382
- )
383
- rep_names = list(self.agenttype_reporters[agent_type])
384
-
385
- df = pd.DataFrame.from_records(
386
- data=all_records,
387
- columns=["Step", "AgentID", *rep_names],
388
- index=["Step", "AgentID"],
389
- )
390
- return df
391
-
392
277
  def get_table_dataframe(self, table_name):
393
278
  """Create a pandas DataFrame from a particular table.
394
279
 
@@ -1,5 +1,3 @@
1
- from .jupyter_viz import JupyterViz, make_text, Slider # noqa
2
1
  from mesa.experimental import cell_space
3
2
 
4
-
5
- __all__ = ["JupyterViz", "make_text", "Slider", "cell_space"]
3
+ __all__ = ["cell_space"]
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools
4
- from collections.abc import Iterable, Mapping
4
+ from collections.abc import Callable, Iterable, Mapping
5
5
  from functools import cached_property
6
6
  from random import Random
7
- from typing import TYPE_CHECKING, Callable, Generic, TypeVar
7
+ from typing import TYPE_CHECKING, Generic, TypeVar
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from mesa.experimental.cell_space.cell import Cell
@@ -60,7 +60,7 @@ class Grid(DiscreteSpace, Generic[T]):
60
60
  raise ValueError("Dimensions must be a list of positive integers.")
61
61
  if not isinstance(self.torus, bool):
62
62
  raise ValueError("Torus must be a boolean.")
63
- if self.capacity is not None and not isinstance(self.capacity, (float, int)):
63
+ if self.capacity is not None and not isinstance(self.capacity, float | int):
64
64
  raise ValueError("Capacity must be a number or None.")
65
65
 
66
66
  def select_random_empty_cell(self) -> T:
@@ -1,5 +1,5 @@
1
1
  from random import Random
2
- from typing import Any, Optional
2
+ from typing import Any
3
3
 
4
4
  from mesa.experimental.cell_space.cell import Cell
5
5
  from mesa.experimental.cell_space.discrete_space import DiscreteSpace
@@ -11,8 +11,8 @@ class Network(DiscreteSpace):
11
11
  def __init__(
12
12
  self,
13
13
  G: Any, # noqa: N803
14
- capacity: Optional[int] = None,
15
- random: Optional[Random] = None,
14
+ capacity: int | None = None,
15
+ random: Random | None = None,
16
16
  cell_klass: type[Cell] = Cell,
17
17
  ) -> None:
18
18
  """A Networked grid
@@ -1,10 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools
4
+ from collections.abc import Callable
4
5
  from enum import IntEnum
5
6
  from heapq import heapify, heappop, heappush
6
7
  from types import MethodType
7
- from typing import Any, Callable
8
+ from typing import Any
8
9
  from weakref import WeakMethod, ref
9
10
 
10
11
 
@@ -261,8 +261,7 @@ class EpsteinCivilViolence(Model):
261
261
  self.active_agents = self.agents
262
262
 
263
263
  def step(self):
264
- """Run one step of the model."""
265
- self.active_agents.shuffle_do("step")
264
+ self.active_agents.shuffle(inplace=True).do("step")
266
265
 
267
266
 
268
267
  if __name__ == "__main__":
@@ -231,9 +231,8 @@ class WolfSheep(mesa.Model):
231
231
  self.grid.place_agent(patch, pos)
232
232
 
233
233
  def step(self):
234
- """Perform one step of the model."""
235
- self.agents_by_type[Sheep].shuffle_do("step")
236
- self.agents_by_type[Wolf].shuffle_do("step")
234
+ self.get_agents_of_type(Sheep).shuffle(inplace=True).do("step")
235
+ self.get_agents_of_type(Wolf).shuffle(inplace=True).do("step")
237
236
 
238
237
 
239
238
  if __name__ == "__main__":
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import numbers
4
- from typing import Any, Callable
4
+ from collections.abc import Callable
5
+ from typing import Any
5
6
 
6
7
  from mesa import Model
7
8
 
mesa/model.py CHANGED
@@ -8,16 +8,18 @@ Core Objects: Model
8
8
  # Remove this __future__ import once the oldest supported Python is 3.10
9
9
  from __future__ import annotations
10
10
 
11
+ import itertools
11
12
  import random
12
13
  import warnings
14
+ from collections import defaultdict
13
15
 
14
16
  # mypy
15
- from typing import Any, Union
17
+ from typing import Any
16
18
 
17
19
  from mesa.agent import Agent, AgentSet
18
20
  from mesa.datacollection import DataCollector
19
21
 
20
- TimeT = Union[float, int]
22
+ TimeT = float | int
21
23
 
22
24
 
23
25
  class Model:
@@ -31,28 +33,20 @@ class Model:
31
33
  running: A boolean indicating if the model should continue running.
32
34
  schedule: An object to manage the order and execution of agent steps.
33
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.
34
38
 
35
39
  Properties:
36
- agents: An AgentSet containing all agents in the model
40
+ agents: An AgentSet containing all agents in the model, generated from the _agents attribute.
37
41
  agent_types: A list of different agent types present in the model.
38
- agents_by_type: A dictionary where the keys are agent types and the values are the corresponding AgentSets.
39
42
 
40
43
  Methods:
41
44
  get_agents_of_type: Returns an AgentSet of agents of the specified type.
42
- Deprecated: Use agents_by_type[agenttype] instead.
43
45
  run_model: Runs the model's simulation until a defined end condition is reached.
44
46
  step: Executes a single step of the model's simulation process.
45
47
  next_id: Generates and returns the next unique identifier for an agent.
46
48
  reset_randomizer: Resets the model's random number generator with a new or existing seed.
47
49
  initialize_data_collector: Sets up the data collector for the model, requiring an initialized scheduler and agents.
48
- register_agent : register an agent with the model
49
- deregister_agent : remove an agent from the model
50
-
51
- Notes:
52
- Model.agents returns the AgentSet containing all agents registered with the model. Changing
53
- the content of the AgentSet directly can result in strange behavior. If you want change the
54
- composition of this AgentSet, ensure you operate on a copy.
55
-
56
50
  """
57
51
 
58
52
  def __new__(cls, *args: Any, **kwargs: Any) -> Any:
@@ -64,7 +58,6 @@ class Model:
64
58
  # advance.
65
59
  obj._seed = random.random()
66
60
  obj.random = random.Random(obj._seed)
67
-
68
61
  # TODO: Remove these 2 lines just before Mesa 3.0
69
62
  obj._steps = 0
70
63
  obj._time = 0
@@ -79,8 +72,7 @@ class Model:
79
72
  self.running = True
80
73
  self.schedule = None
81
74
  self.current_id = 0
82
-
83
- self._setup_agent_registration()
75
+ self.agents_: defaultdict[type, dict] = defaultdict(dict)
84
76
 
85
77
  self._steps: int = 0
86
78
  self._time: TimeT = 0 # the model's clock
@@ -88,93 +80,33 @@ class Model:
88
80
  @property
89
81
  def agents(self) -> AgentSet:
90
82
  """Provides an AgentSet of all agents in the model, combining agents from all types."""
91
- return self._all_agents
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)
92
89
 
93
90
  @agents.setter
94
91
  def agents(self, agents: Any) -> None:
95
92
  warnings.warn(
96
- "You are trying to set model.agents. In Mesa 3.0 and higher, this attribute is "
97
- "used by Mesa itself, so you cannot use it directly anymore."
98
- "Please adjust your code to use a different attribute name for custom agent storage.",
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",
99
96
  UserWarning,
100
97
  stacklevel=2,
101
98
  )
102
99
 
103
- @property
104
- def agent_types(self) -> list[type]:
105
- """Return a list of all unique agent types registered with the model."""
106
- return list(self._agents_by_type.keys())
100
+ self._agents = agents
107
101
 
108
102
  @property
109
- def agents_by_type(self) -> dict[type[Agent], AgentSet]:
110
- """A dictionary where the keys are agent types and the values are the corresponding AgentSets."""
111
- return self._agents_by_type
103
+ def agent_types(self) -> list[type]:
104
+ """Return a list of different agent types."""
105
+ return list(self.agents_.keys())
112
106
 
113
107
  def get_agents_of_type(self, agenttype: type[Agent]) -> AgentSet:
114
- """Deprecated: Retrieves an AgentSet containing all agents of the specified type."""
115
- warnings.warn(
116
- f"Model.get_agents_of_type() is deprecated, please replace get_agents_of_type({agenttype})"
117
- f"with the property agents_by_type[{agenttype}].",
118
- DeprecationWarning,
119
- stacklevel=2,
120
- )
121
- return self.agents_by_type[agenttype]
122
-
123
- def _setup_agent_registration(self):
124
- """helper method to initialize the agent registration datastructures"""
125
- self._agents = {} # the hard references to all agents in the model
126
- self._agents_by_type: dict[
127
- type[Agent], AgentSet
128
- ] = {} # a dict with an agentset for each class of agents
129
- self._all_agents = AgentSet([], self) # an agenset with all agents
130
-
131
- def register_agent(self, agent):
132
- """Register the agent with the model
133
-
134
- Args:
135
- agent: The agent to register.
136
-
137
- Notes:
138
- This method is called automatically by ``Agent.__init__``, so there is no need to use this
139
- if you are subclassing Agent and calling its super in the ``__init__`` method.
140
-
141
- """
142
- if not hasattr(self, "_agents"):
143
- self._setup_agent_registration()
144
-
145
- warnings.warn(
146
- "The Mesa Model class was not initialized. In the future, you need to explicitly initialize "
147
- "the Model by calling super().__init__() on initialization.",
148
- FutureWarning,
149
- stacklevel=2,
150
- )
151
-
152
- self._agents[agent] = None
153
-
154
- # because AgentSet requires model, we cannot use defaultdict
155
- # tricks with a function won't work because model then cannot be pickled
156
- try:
157
- self._agents_by_type[type(agent)].add(agent)
158
- except KeyError:
159
- self._agents_by_type[type(agent)] = AgentSet(
160
- [
161
- agent,
162
- ],
163
- self,
164
- )
165
-
166
- self._all_agents.add(agent)
167
-
168
- def deregister_agent(self, agent):
169
- """Deregister the agent with the model
170
-
171
- Notes::
172
- This method is called automatically by ``Agent.remove``
173
-
174
- """
175
- del self._agents[agent]
176
- self._agents_by_type[type(agent)].remove(agent)
177
- self._all_agents.remove(agent)
108
+ """Retrieves an AgentSet containing all agents of the specified type."""
109
+ return AgentSet(self.agents_[agenttype].keys(), self)
178
110
 
179
111
  def run_model(self) -> None:
180
112
  """Run the model until the end condition is reached. Overload as
@@ -212,7 +144,6 @@ class Model:
212
144
  self,
213
145
  model_reporters=None,
214
146
  agent_reporters=None,
215
- agenttype_reporters=None,
216
147
  tables=None,
217
148
  ) -> None:
218
149
  if not hasattr(self, "schedule") or self.schedule is None:
@@ -226,7 +157,6 @@ class Model:
226
157
  self.datacollector = DataCollector(
227
158
  model_reporters=model_reporters,
228
159
  agent_reporters=agent_reporters,
229
- agenttype_reporters=agenttype_reporters,
230
160
  tables=tables,
231
161
  )
232
162
  # Collect data for the first time during initialization.
mesa/space.py CHANGED
@@ -23,9 +23,9 @@ import inspect
23
23
  import itertools
24
24
  import math
25
25
  import warnings
26
- from collections.abc import Iterable, Iterator, Sequence
26
+ from collections.abc import Callable, Iterable, Iterator, Sequence
27
27
  from numbers import Real
28
- from typing import Any, Callable, TypeVar, Union, cast, overload
28
+ from typing import Any, TypeVar, cast, overload
29
29
  from warnings import warn
30
30
 
31
31
  with contextlib.suppress(ImportError):
@@ -42,12 +42,12 @@ _types_integer = (int, np.integer)
42
42
 
43
43
  Coordinate = tuple[int, int]
44
44
  # used in ContinuousSpace
45
- FloatCoordinate = Union[tuple[float, float], npt.NDArray[float]]
45
+ FloatCoordinate = tuple[float, float] | npt.NDArray[float]
46
46
  NetworkCoordinate = int
47
47
 
48
- Position = Union[Coordinate, FloatCoordinate, NetworkCoordinate]
48
+ Position = Coordinate | FloatCoordinate | NetworkCoordinate
49
49
 
50
- GridContent = Union[Agent, None]
50
+ GridContent = Agent | None
51
51
  MultiGridContent = list[Agent]
52
52
 
53
53
  F = TypeVar("F", bound=Callable[..., Any])
@@ -459,21 +459,15 @@ class _Grid:
459
459
  elif selection == "closest":
460
460
  current_pos = agent.pos
461
461
  # Find the closest position without sorting all positions
462
- # TODO: See if this method can be optimized further
463
- closest_pos = []
462
+ closest_pos = None
464
463
  min_distance = float("inf")
465
464
  agent.random.shuffle(pos)
466
465
  for p in pos:
467
466
  distance = self._distance_squared(p, current_pos)
468
467
  if distance < min_distance:
469
468
  min_distance = distance
470
- closest_pos.clear()
471
- closest_pos.append(p)
472
- elif distance == min_distance:
473
- closest_pos.append(p)
474
-
475
- chosen_pos = agent.random.choice(closest_pos)
476
-
469
+ closest_pos = p
470
+ chosen_pos = closest_pos
477
471
  else:
478
472
  raise ValueError(
479
473
  f"Invalid selection method {selection}. Choose 'random' or 'closest'."
@@ -592,7 +586,7 @@ class PropertyLayer:
592
586
  aggregate_property(operation): Performs an aggregate operation over all cells.
593
587
  """
594
588
 
595
- propertylayer_experimental_warning_given = False
589
+ agentset_experimental_warning_given = False
596
590
 
597
591
  def __init__(
598
592
  self, name: str, width: int, height: int, default_value, dtype=np.float64
@@ -639,14 +633,14 @@ class PropertyLayer:
639
633
 
640
634
  self.data = np.full((width, height), default_value, dtype=dtype)
641
635
 
642
- if not self.__class__.propertylayer_experimental_warning_given:
636
+ if not self.__class__.agentset_experimental_warning_given:
643
637
  warnings.warn(
644
638
  "The new PropertyLayer and _PropertyGrid classes experimental. It may be changed or removed in any and all future releases, including patch releases.\n"
645
639
  "We would love to hear what you think about this new feature. If you have any thoughts, share them with us here: https://github.com/projectmesa/mesa/discussions/1932",
646
640
  FutureWarning,
647
641
  stacklevel=2,
648
642
  )
649
- self.__class__.propertylayer_experimental_warning_given = True
643
+ self.__class__.agentset_experimental_warning_given = True
650
644
 
651
645
  def set_cell(self, position: Coordinate, value):
652
646
  """
mesa/time.py CHANGED
@@ -30,14 +30,12 @@ from collections import defaultdict
30
30
  from collections.abc import Iterable
31
31
 
32
32
  # mypy
33
- from typing import Union
34
-
35
33
  from mesa.agent import Agent, AgentSet
36
34
  from mesa.model import Model
37
35
 
38
36
  # BaseScheduler has a self.time of int, while
39
37
  # StagedActivation has a self.time of float
40
- TimeT = Union[float, int]
38
+ TimeT = float | int
41
39
 
42
40
 
43
41
  class BaseScheduler: