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.
- mesa/__init__.py +3 -5
- mesa/agent.py +393 -116
- mesa/batchrunner.py +58 -31
- mesa/datacollection.py +141 -30
- mesa/examples/README.md +37 -0
- mesa/examples/__init__.py +21 -0
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +116 -0
- mesa/examples/advanced/epstein_civil_violence/Readme.md +34 -0
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +164 -0
- mesa/examples/advanced/epstein_civil_violence/app.py +73 -0
- mesa/examples/advanced/epstein_civil_violence/model.py +114 -0
- mesa/examples/advanced/pd_grid/Readme.md +43 -0
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +50 -0
- mesa/examples/advanced/pd_grid/analysis.ipynb +228 -0
- mesa/examples/advanced/pd_grid/app.py +54 -0
- mesa/examples/advanced/pd_grid/model.py +71 -0
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +64 -0
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +344 -0
- mesa/examples/advanced/sugarscape_g1mt/app.py +62 -0
- mesa/examples/advanced/sugarscape_g1mt/model.py +180 -0
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +50 -0
- mesa/examples/advanced/sugarscape_g1mt/tests.py +69 -0
- mesa/examples/advanced/wolf_sheep/Readme.md +57 -0
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +102 -0
- mesa/examples/advanced/wolf_sheep/app.py +84 -0
- mesa/examples/advanced/wolf_sheep/model.py +137 -0
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +22 -0
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +71 -0
- mesa/examples/basic/boid_flockers/app.py +58 -0
- mesa/examples/basic/boid_flockers/model.py +69 -0
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +56 -0
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +31 -0
- mesa/examples/basic/boltzmann_wealth_model/app.py +74 -0
- mesa/examples/basic/boltzmann_wealth_model/model.py +43 -0
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +115 -0
- mesa/examples/basic/conways_game_of_life/Readme.md +39 -0
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +47 -0
- mesa/examples/basic/conways_game_of_life/app.py +51 -0
- mesa/examples/basic/conways_game_of_life/model.py +31 -0
- mesa/examples/basic/conways_game_of_life/st_app.py +72 -0
- mesa/examples/basic/schelling/Readme.md +40 -0
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +26 -0
- mesa/examples/basic/schelling/analysis.ipynb +205 -0
- mesa/examples/basic/schelling/app.py +42 -0
- mesa/examples/basic/schelling/model.py +59 -0
- mesa/examples/basic/virus_on_network/Readme.md +61 -0
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +69 -0
- mesa/examples/basic/virus_on_network/app.py +114 -0
- mesa/examples/basic/virus_on_network/model.py +96 -0
- mesa/experimental/UserParam.py +18 -7
- mesa/experimental/__init__.py +10 -2
- mesa/experimental/cell_space/__init__.py +16 -1
- mesa/experimental/cell_space/cell.py +93 -23
- mesa/experimental/cell_space/cell_agent.py +117 -21
- mesa/experimental/cell_space/cell_collection.py +56 -19
- mesa/experimental/cell_space/discrete_space.py +92 -8
- mesa/experimental/cell_space/grid.py +33 -9
- mesa/experimental/cell_space/network.py +15 -10
- mesa/experimental/cell_space/voronoi.py +257 -0
- mesa/experimental/components/altair.py +11 -2
- mesa/experimental/components/matplotlib.py +132 -26
- mesa/experimental/devs/__init__.py +2 -0
- mesa/experimental/devs/eventlist.py +54 -15
- mesa/experimental/devs/examples/epstein_civil_violence.py +71 -39
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +57 -16
- mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -98
- mesa/model.py +212 -84
- mesa/space.py +217 -151
- mesa/time.py +63 -80
- mesa/visualization/__init__.py +25 -6
- mesa/visualization/components/__init__.py +83 -0
- mesa/visualization/components/altair_components.py +188 -0
- mesa/visualization/components/matplotlib_components.py +175 -0
- mesa/visualization/mpl_space_drawing.py +593 -0
- mesa/visualization/solara_viz.py +458 -0
- mesa/visualization/user_param.py +69 -0
- mesa/visualization/utils.py +9 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/METADATA +65 -19
- mesa-3.0.0.dist-info/RECORD +95 -0
- mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
- mesa-2.3.4.dist-info/licenses/LICENSE → mesa-3.0.0.dist-info/licenses/NOTICE +2 -2
- mesa/cookiecutter-mesa/cookiecutter.json +0 -8
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +0 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +0 -4
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/run.pytemplate +0 -3
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +0 -11
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +0 -60
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/server.pytemplate +0 -36
- mesa/flat/__init__.py +0 -6
- mesa/flat/visualization.py +0 -5
- mesa/main.py +0 -63
- mesa/visualization/ModularVisualization.py +0 -1
- mesa/visualization/TextVisualization.py +0 -1
- mesa/visualization/UserParam.py +0 -1
- mesa/visualization/modules.py +0 -1
- mesa-2.3.4.dist-info/RECORD +0 -45
- /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
- {mesa-2.3.4.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
mesa/batchrunner.py
CHANGED
|
@@ -1,19 +1,49 @@
|
|
|
1
|
+
"""batchrunner for running a factorial experiment design over a model.
|
|
2
|
+
|
|
3
|
+
To take advantage of parallel execution of experiments, `batch_run` uses
|
|
4
|
+
multiprocessing if ``number_processes`` is larger than 1. It is strongly advised
|
|
5
|
+
to only run in parallel using a normal python file (so don't try to do it in a
|
|
6
|
+
jupyter notebook). Moreover, best practice when using multiprocessing is to
|
|
7
|
+
put the code inside an ``if __name__ == '__main__':`` code black as shown below::
|
|
8
|
+
|
|
9
|
+
from mesa.batchrunner import batch_run
|
|
10
|
+
|
|
11
|
+
params = {"width": 10, "height": 10, "N": range(10, 500, 10)}
|
|
12
|
+
|
|
13
|
+
if __name__ == '__main__':
|
|
14
|
+
results = batch_run(
|
|
15
|
+
MoneyModel,
|
|
16
|
+
parameters=params,
|
|
17
|
+
iterations=5,
|
|
18
|
+
max_steps=100,
|
|
19
|
+
number_processes=None,
|
|
20
|
+
data_collection_period=1,
|
|
21
|
+
display_progress=True,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
1
28
|
import itertools
|
|
29
|
+
import multiprocessing
|
|
2
30
|
from collections.abc import Iterable, Mapping
|
|
3
31
|
from functools import partial
|
|
4
32
|
from multiprocessing import Pool
|
|
5
|
-
from typing import Any
|
|
33
|
+
from typing import Any
|
|
6
34
|
|
|
7
35
|
from tqdm.auto import tqdm
|
|
8
36
|
|
|
9
37
|
from mesa.model import Model
|
|
10
38
|
|
|
39
|
+
multiprocessing.set_start_method("spawn", force=True)
|
|
40
|
+
|
|
11
41
|
|
|
12
42
|
def batch_run(
|
|
13
43
|
model_cls: type[Model],
|
|
14
|
-
parameters: Mapping[str,
|
|
44
|
+
parameters: Mapping[str, Any | Iterable[Any]],
|
|
15
45
|
# We still retain the Optional[int] because users may set it to None (i.e. use all CPUs)
|
|
16
|
-
number_processes:
|
|
46
|
+
number_processes: int | None = 1,
|
|
17
47
|
iterations: int = 1,
|
|
18
48
|
data_collection_period: int = -1,
|
|
19
49
|
max_steps: int = 1000,
|
|
@@ -21,29 +51,22 @@ def batch_run(
|
|
|
21
51
|
) -> list[dict[str, Any]]:
|
|
22
52
|
"""Batch run a mesa model with a set of parameter values.
|
|
23
53
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
iterations : int, optional
|
|
33
|
-
Number of iterations for each parameter combination, by default 1
|
|
34
|
-
data_collection_period : int, optional
|
|
35
|
-
Number of steps after which data gets collected, by default -1 (end of episode)
|
|
36
|
-
max_steps : int, optional
|
|
37
|
-
Maximum number of model steps after which the model halts, by default 1000
|
|
38
|
-
display_progress : bool, optional
|
|
39
|
-
Display batch run process, by default True
|
|
54
|
+
Args:
|
|
55
|
+
model_cls (Type[Model]): The model class to batch-run
|
|
56
|
+
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.
|
|
57
|
+
number_processes (int, optional): Number of processes used, by default 1. Set this to None if you want to use all CPUs.
|
|
58
|
+
iterations (int, optional): Number of iterations for each parameter combination, by default 1
|
|
59
|
+
data_collection_period (int, optional): Number of steps after which data gets collected, by default -1 (end of episode)
|
|
60
|
+
max_steps (int, optional): Maximum number of model steps after which the model halts, by default 1000
|
|
61
|
+
display_progress (bool, optional): Display batch run process, by default True
|
|
40
62
|
|
|
41
|
-
Returns
|
|
42
|
-
|
|
43
|
-
List[Dict[str, Any]]
|
|
44
|
-
[description]
|
|
45
|
-
"""
|
|
63
|
+
Returns:
|
|
64
|
+
List[Dict[str, Any]]
|
|
46
65
|
|
|
66
|
+
Notes:
|
|
67
|
+
batch_run assumes the model has a `datacollector` attribute that has a DataCollector object initialized.
|
|
68
|
+
|
|
69
|
+
"""
|
|
47
70
|
runs_list = []
|
|
48
71
|
run_id = 0
|
|
49
72
|
for iteration in range(iterations):
|
|
@@ -76,7 +99,7 @@ def batch_run(
|
|
|
76
99
|
|
|
77
100
|
|
|
78
101
|
def _make_model_kwargs(
|
|
79
|
-
parameters: Mapping[str,
|
|
102
|
+
parameters: Mapping[str, Any | Iterable[Any]],
|
|
80
103
|
) -> list[dict[str, Any]]:
|
|
81
104
|
"""Create model kwargs from parameters dictionary.
|
|
82
105
|
|
|
@@ -85,7 +108,7 @@ def _make_model_kwargs(
|
|
|
85
108
|
parameters : Mapping[str, Union[Any, Iterable[Any]]]
|
|
86
109
|
Single or multiple values for each model parameter name
|
|
87
110
|
|
|
88
|
-
Returns
|
|
111
|
+
Returns:
|
|
89
112
|
-------
|
|
90
113
|
List[Dict[str, Any]]
|
|
91
114
|
A list of all kwargs combinations.
|
|
@@ -125,21 +148,21 @@ def _model_run_func(
|
|
|
125
148
|
data_collection_period : int
|
|
126
149
|
Number of steps after which data gets collected
|
|
127
150
|
|
|
128
|
-
Returns
|
|
151
|
+
Returns:
|
|
129
152
|
-------
|
|
130
153
|
List[Dict[str, Any]]
|
|
131
154
|
Return model_data, agent_data from the reporters
|
|
132
155
|
"""
|
|
133
156
|
run_id, iteration, kwargs = run
|
|
134
157
|
model = model_cls(**kwargs)
|
|
135
|
-
while model.running and model.
|
|
158
|
+
while model.running and model.steps <= max_steps:
|
|
136
159
|
model.step()
|
|
137
160
|
|
|
138
161
|
data = []
|
|
139
162
|
|
|
140
|
-
steps = list(range(0, model.
|
|
141
|
-
if not steps or steps[-1] != model.
|
|
142
|
-
steps.append(model.
|
|
163
|
+
steps = list(range(0, model.steps, data_collection_period))
|
|
164
|
+
if not steps or steps[-1] != model.steps - 1:
|
|
165
|
+
steps.append(model.steps - 1)
|
|
143
166
|
|
|
144
167
|
for step in steps:
|
|
145
168
|
model_data, all_agents_data = _collect_data(model, step)
|
|
@@ -178,6 +201,10 @@ def _collect_data(
|
|
|
178
201
|
step: int,
|
|
179
202
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
180
203
|
"""Collect model and agent data from a model using mesas datacollector."""
|
|
204
|
+
if not hasattr(model, "datacollector"):
|
|
205
|
+
raise AttributeError(
|
|
206
|
+
"The model does not have a datacollector attribute. Please add a DataCollector to your model."
|
|
207
|
+
)
|
|
181
208
|
dc = model.datacollector
|
|
182
209
|
|
|
183
210
|
model_data = {param: values[step] for param, values in dc.model_vars.items()}
|
mesa/datacollection.py
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Mesa Data Collection Module
|
|
3
|
-
===========================
|
|
1
|
+
"""Mesa Data Collection Module.
|
|
4
2
|
|
|
5
3
|
DataCollector is meant to provide a simple, standard way to collect data
|
|
6
|
-
generated by a Mesa model. It collects
|
|
7
|
-
agent-level data, and tables.
|
|
4
|
+
generated by a Mesa model. It collects four types of data: model-level data,
|
|
5
|
+
agent-level data, agent-type-level data, and tables.
|
|
8
6
|
|
|
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
|
-
Variable names are converted into functions
|
|
13
|
-
name.
|
|
7
|
+
A DataCollector is instantiated with three dictionaries of reporter names and
|
|
8
|
+
associated variable names or functions for each, one for model-level data,
|
|
9
|
+
one for agent-level data, and one for agent-type-level data; a fourth dictionary
|
|
10
|
+
provides table names and columns. Variable names are converted into functions
|
|
11
|
+
which retrieve attributes of that name.
|
|
14
12
|
|
|
15
13
|
When the collect() method is called, each model-level function is called, with
|
|
16
14
|
the model as the argument, and the results associated with the relevant
|
|
17
|
-
variable. Then the agent-level functions are called on each agent
|
|
15
|
+
variable. Then the agent-level functions are called on each agent, and the
|
|
16
|
+
agent-type-level functions are called on each agent of the specified type.
|
|
18
17
|
|
|
19
18
|
Additionally, other objects can write directly to tables by passing in an
|
|
20
19
|
appropriate dictionary object for a table row.
|
|
@@ -23,19 +22,18 @@ The DataCollector then stores the data it collects in dictionaries:
|
|
|
23
22
|
* model_vars maps each reporter to a list of its values
|
|
24
23
|
* tables maps each table to a dictionary, with each column as a key with a
|
|
25
24
|
list as its value.
|
|
26
|
-
* _agent_records maps each model step to a list of each
|
|
25
|
+
* _agent_records maps each model step to a list of each agent's id
|
|
27
26
|
and its values.
|
|
27
|
+
* _agenttype_records maps each model step to a dictionary of agent types,
|
|
28
|
+
each containing a list of each agent's id and its values.
|
|
28
29
|
|
|
29
30
|
Finally, DataCollector can create a pandas DataFrame from each collection.
|
|
30
|
-
|
|
31
|
-
The default DataCollector here makes several assumptions:
|
|
32
|
-
* The model has an agent list called agents
|
|
33
|
-
* For collecting agent-level variables, agents must have a unique_id
|
|
34
31
|
"""
|
|
35
32
|
|
|
36
33
|
import contextlib
|
|
37
34
|
import itertools
|
|
38
35
|
import types
|
|
36
|
+
import warnings
|
|
39
37
|
from copy import deepcopy
|
|
40
38
|
from functools import partial
|
|
41
39
|
|
|
@@ -46,24 +44,25 @@ with contextlib.suppress(ImportError):
|
|
|
46
44
|
class DataCollector:
|
|
47
45
|
"""Class for collecting data generated by a Mesa model.
|
|
48
46
|
|
|
49
|
-
A DataCollector is instantiated with dictionaries of names of model
|
|
50
|
-
agent-level variables to collect, associated with
|
|
51
|
-
functions which actually collect them. When the
|
|
52
|
-
called, it collects these attributes and executes
|
|
53
|
-
one and stores the results.
|
|
47
|
+
A DataCollector is instantiated with dictionaries of names of model-,
|
|
48
|
+
agent-, and agent-type-level variables to collect, associated with
|
|
49
|
+
attribute names or functions which actually collect them. When the
|
|
50
|
+
collect(...) method is called, it collects these attributes and executes
|
|
51
|
+
these functions one by one and stores the results.
|
|
54
52
|
"""
|
|
55
53
|
|
|
56
54
|
def __init__(
|
|
57
55
|
self,
|
|
58
56
|
model_reporters=None,
|
|
59
57
|
agent_reporters=None,
|
|
58
|
+
agenttype_reporters=None,
|
|
60
59
|
tables=None,
|
|
61
60
|
):
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
Both model_reporters and
|
|
65
|
-
variable name to either an attribute name, a function,
|
|
66
|
-
or a function with parameters placed in a list.
|
|
61
|
+
"""Instantiate a DataCollector with lists of model, agent, and agent-type reporters.
|
|
62
|
+
|
|
63
|
+
Both model_reporters, agent_reporters, and agenttype_reporters accept a
|
|
64
|
+
dictionary mapping a variable name to either an attribute name, a function,
|
|
65
|
+
a method of a class/instance, or a function with parameters placed in a list.
|
|
67
66
|
|
|
68
67
|
Model reporters can take four types of arguments:
|
|
69
68
|
1. Lambda function:
|
|
@@ -87,6 +86,10 @@ class DataCollector:
|
|
|
87
86
|
4. Functions with parameters placed in a list:
|
|
88
87
|
{"Agent_Function": [function, [param_1, param_2]]}
|
|
89
88
|
|
|
89
|
+
Agenttype reporters take a dictionary mapping agent types to dictionaries
|
|
90
|
+
of reporter names and attributes/funcs/methods, similar to agent_reporters:
|
|
91
|
+
{Wolf: {"energy": lambda a: a.energy}}
|
|
92
|
+
|
|
90
93
|
The tables arg accepts a dictionary mapping names of tables to lists of
|
|
91
94
|
columns. For example, if we want to allow agents to write their age
|
|
92
95
|
when they are destroyed (to keep track of lifespans), it might look
|
|
@@ -96,6 +99,8 @@ class DataCollector:
|
|
|
96
99
|
Args:
|
|
97
100
|
model_reporters: Dictionary of reporter names and attributes/funcs/methods.
|
|
98
101
|
agent_reporters: Dictionary of reporter names and attributes/funcs/methods.
|
|
102
|
+
agenttype_reporters: Dictionary of agent types to dictionaries of
|
|
103
|
+
reporter names and attributes/funcs/methods.
|
|
99
104
|
tables: Dictionary of table names to lists of column names.
|
|
100
105
|
|
|
101
106
|
Notes:
|
|
@@ -105,9 +110,11 @@ class DataCollector:
|
|
|
105
110
|
"""
|
|
106
111
|
self.model_reporters = {}
|
|
107
112
|
self.agent_reporters = {}
|
|
113
|
+
self.agenttype_reporters = {}
|
|
108
114
|
|
|
109
115
|
self.model_vars = {}
|
|
110
116
|
self._agent_records = {}
|
|
117
|
+
self._agenttype_records = {}
|
|
111
118
|
self.tables = {}
|
|
112
119
|
|
|
113
120
|
if model_reporters is not None:
|
|
@@ -118,6 +125,11 @@ class DataCollector:
|
|
|
118
125
|
for name, reporter in agent_reporters.items():
|
|
119
126
|
self._new_agent_reporter(name, reporter)
|
|
120
127
|
|
|
128
|
+
if agenttype_reporters is not None:
|
|
129
|
+
for agent_type, reporters in agenttype_reporters.items():
|
|
130
|
+
for name, reporter in reporters.items():
|
|
131
|
+
self._new_agenttype_reporter(agent_type, name, reporter)
|
|
132
|
+
|
|
121
133
|
if tables is not None:
|
|
122
134
|
for name, columns in tables.items():
|
|
123
135
|
self._new_table(name, columns)
|
|
@@ -165,6 +177,38 @@ class DataCollector:
|
|
|
165
177
|
|
|
166
178
|
self.agent_reporters[name] = reporter
|
|
167
179
|
|
|
180
|
+
def _new_agenttype_reporter(self, agent_type, name, reporter):
|
|
181
|
+
"""Add a new agent-type-level reporter to collect.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
agent_type: The type of agent to collect data for.
|
|
185
|
+
name: Name of the agent-type-level variable to collect.
|
|
186
|
+
reporter: Attribute string, function object, method of a class/instance, or
|
|
187
|
+
function with parameters placed in a list that returns the
|
|
188
|
+
variable when given an agent instance.
|
|
189
|
+
"""
|
|
190
|
+
if agent_type not in self.agenttype_reporters:
|
|
191
|
+
self.agenttype_reporters[agent_type] = {}
|
|
192
|
+
|
|
193
|
+
# Use the same logic as _new_agent_reporter
|
|
194
|
+
if isinstance(reporter, str):
|
|
195
|
+
attribute_name = reporter
|
|
196
|
+
|
|
197
|
+
def attr_reporter(agent):
|
|
198
|
+
return getattr(agent, attribute_name, None)
|
|
199
|
+
|
|
200
|
+
reporter = attr_reporter
|
|
201
|
+
|
|
202
|
+
elif isinstance(reporter, list):
|
|
203
|
+
func, params = reporter[0], reporter[1]
|
|
204
|
+
|
|
205
|
+
def func_with_params(agent):
|
|
206
|
+
return func(agent, *params)
|
|
207
|
+
|
|
208
|
+
reporter = func_with_params
|
|
209
|
+
|
|
210
|
+
self.agenttype_reporters[agent_type][name] = reporter
|
|
211
|
+
|
|
168
212
|
def _new_table(self, table_name, table_columns):
|
|
169
213
|
"""Add a new table that objects can write to.
|
|
170
214
|
|
|
@@ -180,7 +224,7 @@ class DataCollector:
|
|
|
180
224
|
rep_funcs = self.agent_reporters.values()
|
|
181
225
|
|
|
182
226
|
def get_reports(agent):
|
|
183
|
-
_prefix = (agent.model.
|
|
227
|
+
_prefix = (agent.model.steps, agent.unique_id)
|
|
184
228
|
reports = tuple(rep(agent) for rep in rep_funcs)
|
|
185
229
|
return _prefix + reports
|
|
186
230
|
|
|
@@ -192,12 +236,40 @@ class DataCollector:
|
|
|
192
236
|
)
|
|
193
237
|
return agent_records
|
|
194
238
|
|
|
239
|
+
def _record_agenttype(self, model, agent_type):
|
|
240
|
+
"""Record agent-type data in a mapping of functions and agents."""
|
|
241
|
+
rep_funcs = self.agenttype_reporters[agent_type].values()
|
|
242
|
+
|
|
243
|
+
def get_reports(agent):
|
|
244
|
+
_prefix = (agent.model.steps, agent.unique_id)
|
|
245
|
+
reports = tuple(rep(agent) for rep in rep_funcs)
|
|
246
|
+
return _prefix + reports
|
|
247
|
+
|
|
248
|
+
agent_types = model.agent_types
|
|
249
|
+
if agent_type in agent_types:
|
|
250
|
+
agents = model.agents_by_type[agent_type]
|
|
251
|
+
else:
|
|
252
|
+
from mesa import Agent
|
|
253
|
+
|
|
254
|
+
if issubclass(agent_type, Agent):
|
|
255
|
+
agents = [
|
|
256
|
+
agent for agent in model.agents if isinstance(agent, agent_type)
|
|
257
|
+
]
|
|
258
|
+
else:
|
|
259
|
+
# Raise error if agent_type is not in model.agent_types
|
|
260
|
+
raise ValueError(
|
|
261
|
+
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}."
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
agenttype_records = map(get_reports, agents)
|
|
265
|
+
return agenttype_records
|
|
266
|
+
|
|
195
267
|
def collect(self, model):
|
|
196
268
|
"""Collect all the data for the given model object."""
|
|
197
269
|
if self.model_reporters:
|
|
198
270
|
for var, reporter in self.model_reporters.items():
|
|
199
271
|
# Check if lambda or partial function
|
|
200
|
-
if isinstance(reporter,
|
|
272
|
+
if isinstance(reporter, types.LambdaType | partial):
|
|
201
273
|
# Use deepcopy to store a copy of the data,
|
|
202
274
|
# preventing references from being updated across steps.
|
|
203
275
|
self.model_vars[var].append(deepcopy(reporter(model)))
|
|
@@ -210,13 +282,20 @@ class DataCollector:
|
|
|
210
282
|
elif isinstance(reporter, list):
|
|
211
283
|
self.model_vars[var].append(deepcopy(reporter[0](*reporter[1])))
|
|
212
284
|
# Assume it's a callable otherwise (e.g., method)
|
|
213
|
-
# TODO: Check if method of a class explicitly
|
|
214
285
|
else:
|
|
215
286
|
self.model_vars[var].append(deepcopy(reporter()))
|
|
216
287
|
|
|
217
288
|
if self.agent_reporters:
|
|
218
289
|
agent_records = self._record_agents(model)
|
|
219
|
-
self._agent_records[model.
|
|
290
|
+
self._agent_records[model.steps] = list(agent_records)
|
|
291
|
+
|
|
292
|
+
if self.agenttype_reporters:
|
|
293
|
+
self._agenttype_records[model.steps] = {}
|
|
294
|
+
for agent_type in self.agenttype_reporters:
|
|
295
|
+
agenttype_records = self._record_agenttype(model, agent_type)
|
|
296
|
+
self._agenttype_records[model.steps][agent_type] = list(
|
|
297
|
+
agenttype_records
|
|
298
|
+
)
|
|
220
299
|
|
|
221
300
|
def add_table_row(self, table_name, row, ignore_missing=False):
|
|
222
301
|
"""Add a row dictionary to a specific table.
|
|
@@ -274,6 +353,38 @@ class DataCollector:
|
|
|
274
353
|
)
|
|
275
354
|
return df
|
|
276
355
|
|
|
356
|
+
def get_agenttype_vars_dataframe(self, agent_type):
|
|
357
|
+
"""Create a pandas DataFrame from the agent-type variables for a specific agent type.
|
|
358
|
+
|
|
359
|
+
The DataFrame has one column for each variable, with two additional
|
|
360
|
+
columns for tick and agent_id.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
agent_type: The type of agent to get the data for.
|
|
364
|
+
"""
|
|
365
|
+
# Check if self.agenttype_reporters dictionary is empty for this agent type, if so return empty DataFrame
|
|
366
|
+
if agent_type not in self.agenttype_reporters:
|
|
367
|
+
warnings.warn(
|
|
368
|
+
f"No agent-type reporters have been defined for {agent_type} in the DataCollector, returning empty DataFrame.",
|
|
369
|
+
UserWarning,
|
|
370
|
+
stacklevel=2,
|
|
371
|
+
)
|
|
372
|
+
return pd.DataFrame()
|
|
373
|
+
|
|
374
|
+
all_records = itertools.chain.from_iterable(
|
|
375
|
+
records[agent_type]
|
|
376
|
+
for records in self._agenttype_records.values()
|
|
377
|
+
if agent_type in records
|
|
378
|
+
)
|
|
379
|
+
rep_names = list(self.agenttype_reporters[agent_type])
|
|
380
|
+
|
|
381
|
+
df = pd.DataFrame.from_records(
|
|
382
|
+
data=all_records,
|
|
383
|
+
columns=["Step", "AgentID", *rep_names],
|
|
384
|
+
index=["Step", "AgentID"],
|
|
385
|
+
)
|
|
386
|
+
return df
|
|
387
|
+
|
|
277
388
|
def get_table_dataframe(self, table_name):
|
|
278
389
|
"""Create a pandas DataFrame from a particular table.
|
|
279
390
|
|
mesa/examples/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Mesa core examples
|
|
2
|
+
These examples are a collection of classic agent based models built using Mesa. These core examples are maintained by the Mesa team and are intended to demonstrate the capabilities of Mesa.
|
|
3
|
+
|
|
4
|
+
More user examples and showcases can be found in the [mesa-examples](https://github.com/projectmesa/mesa-examples) repository.
|
|
5
|
+
|
|
6
|
+
## Basic Examples
|
|
7
|
+
The basic examples are relatively simple and only use stable Mesa features. They are good starting points for learning how to use Mesa.
|
|
8
|
+
|
|
9
|
+
### [Boltzmann Wealth Model](examples/basic/boltzmann_wealth_model)
|
|
10
|
+
Completed code to go along with the [tutorial](https://mesa.readthedocs.io/latest/tutorials/intro_tutorial.html) on making a simple model of how a highly-skewed wealth distribution can emerge from simple rules.
|
|
11
|
+
|
|
12
|
+
### [Boids Flockers Model](examples/basic/boid_flockers)
|
|
13
|
+
[Boids](https://en.wikipedia.org/wiki/Boids)-style flocking model, demonstrating the use of agents moving through a continuous space following direction vectors.
|
|
14
|
+
|
|
15
|
+
### [Conway's Game of Life](examples/basic/conways_game_of_life)
|
|
16
|
+
Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life), a cellular automata where simple rules can give rise to complex patterns.
|
|
17
|
+
|
|
18
|
+
### [Schelling Segregation Model](examples/basic/schelling)
|
|
19
|
+
Mesa implementation of the classic [Schelling segregation](http://nifty.stanford.edu/2014/mccown-schelling-model-segregation/) model.
|
|
20
|
+
|
|
21
|
+
### [Virus on a Network Model](examples/basic/virus_on_network)
|
|
22
|
+
This model is based on the NetLogo [Virus on a Network](https://ccl.northwestern.edu/netlogo/models/VirusonaNetwork) model.
|
|
23
|
+
|
|
24
|
+
## Advanced Examples
|
|
25
|
+
The advanced examples are more complex and may use experimental Mesa features. They are good starting points for learning how to build more complex models.
|
|
26
|
+
|
|
27
|
+
### [Epstein Civil Violence Model](examples/advanced/epstein_civil_violence)
|
|
28
|
+
Joshua Epstein's [model](https://www.pnas.org/doi/10.1073/pnas.092080199) of how a decentralized uprising can be suppressed or reach a critical mass of support.
|
|
29
|
+
|
|
30
|
+
### [Demographic Prisoner's Dilemma on a Grid](examples/advanced/pd_grid)
|
|
31
|
+
Grid-based demographic prisoner's dilemma model, demonstrating how simple rules can lead to the emergence of widespread cooperation -- and how a model activation regime can change its outcome.
|
|
32
|
+
|
|
33
|
+
### [Sugarscape Model with Traders](examples/advanced/sugarscape_g1mt)
|
|
34
|
+
This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of *Growing Artificial Societies: Social Science from the Bottom Up (1996)*. The model shows how emergent price equilibrium can happen via decentralized dynamics.
|
|
35
|
+
|
|
36
|
+
### [Wolf-Sheep Predation Model](examples/advanced/wolf_sheep)
|
|
37
|
+
Implementation of an ecological model of predation and reproduction, based on the NetLogo [Wolf Sheep Predation](http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation) model.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from mesa.examples.advanced.epstein_civil_violence.model import EpsteinCivilViolence
|
|
2
|
+
from mesa.examples.advanced.pd_grid.model import PdGrid
|
|
3
|
+
from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
|
|
4
|
+
from mesa.examples.advanced.wolf_sheep.model import WolfSheep
|
|
5
|
+
from mesa.examples.basic.boid_flockers.model import BoidFlockers
|
|
6
|
+
from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealthModel
|
|
7
|
+
from mesa.examples.basic.conways_game_of_life.model import ConwaysGameOfLife
|
|
8
|
+
from mesa.examples.basic.schelling.model import Schelling
|
|
9
|
+
from mesa.examples.basic.virus_on_network.model import VirusOnNetwork
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BoidFlockers",
|
|
13
|
+
"BoltzmannWealthModel",
|
|
14
|
+
"ConwaysGameOfLife",
|
|
15
|
+
"Schelling",
|
|
16
|
+
"VirusOnNetwork",
|
|
17
|
+
"EpsteinCivilViolence",
|
|
18
|
+
"PdGrid",
|
|
19
|
+
"SugarscapeG1mt",
|
|
20
|
+
"WolfSheep",
|
|
21
|
+
]
|