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.
- mesa/__init__.py +1 -3
- mesa/agent.py +53 -332
- mesa/batchrunner.py +8 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +1 -1
- mesa/datacollection.py +21 -136
- mesa/experimental/__init__.py +1 -3
- mesa/experimental/cell_space/cell_collection.py +2 -2
- mesa/experimental/cell_space/grid.py +1 -1
- mesa/experimental/cell_space/network.py +3 -3
- mesa/experimental/devs/eventlist.py +2 -1
- mesa/experimental/devs/examples/epstein_civil_violence.py +1 -2
- mesa/experimental/devs/examples/wolf_sheep.py +2 -3
- mesa/experimental/devs/simulator.py +2 -1
- mesa/model.py +23 -93
- mesa/space.py +11 -17
- mesa/time.py +1 -3
- mesa/visualization/UserParam.py +56 -1
- mesa/visualization/__init__.py +2 -6
- mesa/{experimental → visualization}/components/altair.py +1 -2
- mesa/{experimental → visualization}/components/matplotlib.py +4 -6
- mesa/{experimental → visualization}/jupyter_viz.py +117 -20
- {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +4 -11
- mesa-3.0.0a0.dist-info/RECORD +38 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
- mesa/experimental/UserParam.py +0 -56
- mesa/flat/__init__.py +0 -6
- mesa/flat/visualization.py +0 -5
- mesa/visualization/ModularVisualization.py +0 -1
- mesa/visualization/TextVisualization.py +0 -1
- mesa/visualization/modules.py +0 -1
- mesa-2.4.0.dist-info/RECORD +0 -45
- {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
- {mesa-2.4.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
- {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
|
|
7
|
-
agent-level data,
|
|
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
|
|
10
|
-
associated variable names or functions for each, one for model-level data
|
|
11
|
-
one for agent-level data
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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
|
|
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
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
"""
|
|
67
|
-
|
|
68
|
-
Both model_reporters
|
|
69
|
-
|
|
70
|
-
|
|
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,
|
|
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
|
|
mesa/experimental/__init__.py
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
|
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:
|
|
15
|
-
random:
|
|
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
|
|
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
|
-
"
|
|
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
|
-
"
|
|
235
|
-
self.
|
|
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__":
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
97
|
-
"
|
|
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
|
-
|
|
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
|
|
110
|
-
"""
|
|
111
|
-
return self.
|
|
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
|
-
"""
|
|
115
|
-
|
|
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,
|
|
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 =
|
|
45
|
+
FloatCoordinate = tuple[float, float] | npt.NDArray[float]
|
|
46
46
|
NetworkCoordinate = int
|
|
47
47
|
|
|
48
|
-
Position =
|
|
48
|
+
Position = Coordinate | FloatCoordinate | NetworkCoordinate
|
|
49
49
|
|
|
50
|
-
GridContent =
|
|
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
|
-
|
|
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
|
|
471
|
-
|
|
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
|
-
|
|
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__.
|
|
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__.
|
|
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 =
|
|
38
|
+
TimeT = float | int
|
|
41
39
|
|
|
42
40
|
|
|
43
41
|
class BaseScheduler:
|