Mesa 3.0.0__py3-none-any.whl → 3.0.0a1__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 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/visualization/solara_viz.py +266 -267
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/METADATA +13 -65
- mesa-3.0.0a1.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a1.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/experimental/solara_viz.py +0 -453
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a1.dist-info}/entry_points.txt +0 -0
mesa/batchrunner.py
CHANGED
|
@@ -1,32 +1,4 @@
|
|
|
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
|
-
|
|
28
1
|
import itertools
|
|
29
|
-
import multiprocessing
|
|
30
2
|
from collections.abc import Iterable, Mapping
|
|
31
3
|
from functools import partial
|
|
32
4
|
from multiprocessing import Pool
|
|
@@ -36,8 +8,6 @@ from tqdm.auto import tqdm
|
|
|
36
8
|
|
|
37
9
|
from mesa.model import Model
|
|
38
10
|
|
|
39
|
-
multiprocessing.set_start_method("spawn", force=True)
|
|
40
|
-
|
|
41
11
|
|
|
42
12
|
def batch_run(
|
|
43
13
|
model_cls: type[Model],
|
|
@@ -51,22 +21,29 @@ def batch_run(
|
|
|
51
21
|
) -> list[dict[str, Any]]:
|
|
52
22
|
"""Batch run a mesa model with a set of parameter values.
|
|
53
23
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
model_cls : Type[Model]
|
|
27
|
+
The model class to batch-run
|
|
28
|
+
parameters : Mapping[str, Union[Any, Iterable[Any]]],
|
|
29
|
+
Dictionary with model parameters over which to run the model. You can either pass single values or iterables.
|
|
30
|
+
number_processes : int, optional
|
|
31
|
+
Number of processes used, by default 1. Set this to None if you want to use all CPUs.
|
|
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
|
|
68
40
|
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
List[Dict[str, Any]]
|
|
44
|
+
[description]
|
|
69
45
|
"""
|
|
46
|
+
|
|
70
47
|
runs_list = []
|
|
71
48
|
run_id = 0
|
|
72
49
|
for iteration in range(iterations):
|
|
@@ -108,7 +85,7 @@ def _make_model_kwargs(
|
|
|
108
85
|
parameters : Mapping[str, Union[Any, Iterable[Any]]]
|
|
109
86
|
Single or multiple values for each model parameter name
|
|
110
87
|
|
|
111
|
-
Returns
|
|
88
|
+
Returns
|
|
112
89
|
-------
|
|
113
90
|
List[Dict[str, Any]]
|
|
114
91
|
A list of all kwargs combinations.
|
|
@@ -148,21 +125,21 @@ def _model_run_func(
|
|
|
148
125
|
data_collection_period : int
|
|
149
126
|
Number of steps after which data gets collected
|
|
150
127
|
|
|
151
|
-
Returns
|
|
128
|
+
Returns
|
|
152
129
|
-------
|
|
153
130
|
List[Dict[str, Any]]
|
|
154
131
|
Return model_data, agent_data from the reporters
|
|
155
132
|
"""
|
|
156
133
|
run_id, iteration, kwargs = run
|
|
157
134
|
model = model_cls(**kwargs)
|
|
158
|
-
while model.running and model.
|
|
135
|
+
while model.running and model._steps <= max_steps:
|
|
159
136
|
model.step()
|
|
160
137
|
|
|
161
138
|
data = []
|
|
162
139
|
|
|
163
|
-
steps = list(range(0, model.
|
|
164
|
-
if not steps or steps[-1] != model.
|
|
165
|
-
steps.append(model.
|
|
140
|
+
steps = list(range(0, model._steps, data_collection_period))
|
|
141
|
+
if not steps or steps[-1] != model._steps - 1:
|
|
142
|
+
steps.append(model._steps - 1)
|
|
166
143
|
|
|
167
144
|
for step in steps:
|
|
168
145
|
model_data, all_agents_data = _collect_data(model, step)
|
|
@@ -201,10 +178,6 @@ def _collect_data(
|
|
|
201
178
|
step: int,
|
|
202
179
|
) -> tuple[dict[str, Any], list[dict[str, Any]]]:
|
|
203
180
|
"""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
|
-
)
|
|
208
181
|
dc = model.datacollector
|
|
209
182
|
|
|
210
183
|
model_data = {param: values[step] for param, values in dc.model_vars.items()}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": "Example Project",
|
|
3
|
+
"snake": "{{ cookiecutter.project.lower().replace(' ', '_') }}",
|
|
4
|
+
"camel": "{{ cookiecutter.project.title().replace(' ', '') }}",
|
|
5
|
+
"agent": "{{ cookiecutter.camel + 'Agent'}}",
|
|
6
|
+
"model": "{{ cookiecutter.camel + 'Model'}}",
|
|
7
|
+
"description": "Example short description of the project"
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
file_list = glob.glob("**/*.pytemplate", recursive=True)
|
|
5
|
+
|
|
6
|
+
for file_path in file_list:
|
|
7
|
+
# Check if the file is a regular file
|
|
8
|
+
if not os.path.isfile(file_path):
|
|
9
|
+
continue
|
|
10
|
+
# Rename the file
|
|
11
|
+
os.rename(file_path, file_path.replace(".pytemplate", ".py"))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure visualization elements and instantiate a server
|
|
3
|
+
"""
|
|
4
|
+
from mesa.visualization import SolaraViz
|
|
5
|
+
|
|
6
|
+
from {{ cookiecutter.snake }}.model import {{ cookiecutter.model }}, {{ cookiecutter.agent }} # noqa
|
|
7
|
+
|
|
8
|
+
import mesa
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def circle_portrayal_example(agent):
|
|
12
|
+
return {
|
|
13
|
+
"size": 40,
|
|
14
|
+
# This is Matplotlib's color
|
|
15
|
+
"color": "tab:pink",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
model_params = {"num_agents": 10, "width": 10, "height": 10}
|
|
20
|
+
|
|
21
|
+
page = SolaraViz(
|
|
22
|
+
{{cookiecutter.model}},
|
|
23
|
+
model_params,
|
|
24
|
+
measures=["num_agents"],
|
|
25
|
+
agent_portrayal=circle_portrayal_example
|
|
26
|
+
)
|
|
27
|
+
page # noqa
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import mesa
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class {{cookiecutter.agent}}(mesa.Agent): # noqa
|
|
5
|
+
"""
|
|
6
|
+
An agent
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, unique_id, model):
|
|
10
|
+
"""
|
|
11
|
+
Customize the agent
|
|
12
|
+
"""
|
|
13
|
+
self.unique_id = unique_id
|
|
14
|
+
super().__init__(unique_id, model)
|
|
15
|
+
|
|
16
|
+
def step(self):
|
|
17
|
+
"""
|
|
18
|
+
Modify this method to change what an individual agent will do during each step.
|
|
19
|
+
Can include logic based on neighbors states.
|
|
20
|
+
"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class {{cookiecutter.model}}(mesa.Model):
|
|
25
|
+
"""
|
|
26
|
+
The model class holds the model-level attributes, manages the agents, and generally handles
|
|
27
|
+
the global level of our model.
|
|
28
|
+
|
|
29
|
+
There is only one model-level parameter: how many agents the model contains. When a new model
|
|
30
|
+
is started, we want it to populate itself with the given number of agents.
|
|
31
|
+
|
|
32
|
+
The scheduler is a special model component which controls the order in which agents are activated.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, num_agents, width, height):
|
|
36
|
+
super().__init__()
|
|
37
|
+
self.num_agents = num_agents
|
|
38
|
+
self.schedule = mesa.time.RandomActivation(self)
|
|
39
|
+
self.grid = mesa.space.MultiGrid(width=width, height=height, torus=True)
|
|
40
|
+
|
|
41
|
+
for i in range(self.num_agents):
|
|
42
|
+
agent = {{cookiecutter.agent}}(i, self)
|
|
43
|
+
self.schedule.add(agent)
|
|
44
|
+
|
|
45
|
+
x = self.random.randrange(self.grid.width)
|
|
46
|
+
y = self.random.randrange(self.grid.height)
|
|
47
|
+
self.grid.place_agent(agent, (x, y))
|
|
48
|
+
|
|
49
|
+
# example data collector
|
|
50
|
+
self.datacollector = mesa.datacollection.DataCollector({"num_agents": "num_agents"})
|
|
51
|
+
|
|
52
|
+
self.running = True
|
|
53
|
+
self.datacollector.collect(self)
|
|
54
|
+
|
|
55
|
+
def step(self):
|
|
56
|
+
"""
|
|
57
|
+
A model step. Used for collecting data and advancing the schedule
|
|
58
|
+
"""
|
|
59
|
+
self.datacollector.collect(self)
|
|
60
|
+
self.schedule.step()
|
mesa/datacollection.py
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Mesa Data Collection Module
|
|
3
|
+
===========================
|
|
2
4
|
|
|
3
5
|
DataCollector is meant to provide a simple, standard way to collect data
|
|
4
|
-
generated by a Mesa model. It collects
|
|
5
|
-
agent-level data,
|
|
6
|
+
generated by a Mesa model. It collects three types of data: model-level data,
|
|
7
|
+
agent-level data, and tables.
|
|
6
8
|
|
|
7
|
-
A DataCollector is instantiated with
|
|
8
|
-
associated variable names or functions for each, one for model-level data
|
|
9
|
-
one for agent-level data
|
|
10
|
-
|
|
11
|
-
|
|
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.
|
|
12
14
|
|
|
13
15
|
When the collect() method is called, each model-level function is called, with
|
|
14
16
|
the model as the argument, and the results associated with the relevant
|
|
15
|
-
variable. Then the agent-level functions are called on each agent
|
|
16
|
-
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.
|
|
17
18
|
|
|
18
19
|
Additionally, other objects can write directly to tables by passing in an
|
|
19
20
|
appropriate dictionary object for a table row.
|
|
@@ -22,18 +23,19 @@ The DataCollector then stores the data it collects in dictionaries:
|
|
|
22
23
|
* model_vars maps each reporter to a list of its values
|
|
23
24
|
* tables maps each table to a dictionary, with each column as a key with a
|
|
24
25
|
list as its value.
|
|
25
|
-
* _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
|
|
26
27
|
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.
|
|
29
28
|
|
|
30
29
|
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
|
|
31
34
|
"""
|
|
32
35
|
|
|
33
36
|
import contextlib
|
|
34
37
|
import itertools
|
|
35
38
|
import types
|
|
36
|
-
import warnings
|
|
37
39
|
from copy import deepcopy
|
|
38
40
|
from functools import partial
|
|
39
41
|
|
|
@@ -44,25 +46,24 @@ with contextlib.suppress(ImportError):
|
|
|
44
46
|
class DataCollector:
|
|
45
47
|
"""Class for collecting data generated by a Mesa model.
|
|
46
48
|
|
|
47
|
-
A DataCollector is instantiated with dictionaries of names of model
|
|
48
|
-
agent
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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.
|
|
52
54
|
"""
|
|
53
55
|
|
|
54
56
|
def __init__(
|
|
55
57
|
self,
|
|
56
58
|
model_reporters=None,
|
|
57
59
|
agent_reporters=None,
|
|
58
|
-
agenttype_reporters=None,
|
|
59
60
|
tables=None,
|
|
60
61
|
):
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
Both model_reporters
|
|
64
|
-
|
|
65
|
-
|
|
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.
|
|
66
67
|
|
|
67
68
|
Model reporters can take four types of arguments:
|
|
68
69
|
1. Lambda function:
|
|
@@ -86,10 +87,6 @@ class DataCollector:
|
|
|
86
87
|
4. Functions with parameters placed in a list:
|
|
87
88
|
{"Agent_Function": [function, [param_1, param_2]]}
|
|
88
89
|
|
|
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
|
-
|
|
93
90
|
The tables arg accepts a dictionary mapping names of tables to lists of
|
|
94
91
|
columns. For example, if we want to allow agents to write their age
|
|
95
92
|
when they are destroyed (to keep track of lifespans), it might look
|
|
@@ -99,8 +96,6 @@ class DataCollector:
|
|
|
99
96
|
Args:
|
|
100
97
|
model_reporters: Dictionary of reporter names and attributes/funcs/methods.
|
|
101
98
|
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.
|
|
104
99
|
tables: Dictionary of table names to lists of column names.
|
|
105
100
|
|
|
106
101
|
Notes:
|
|
@@ -110,11 +105,9 @@ class DataCollector:
|
|
|
110
105
|
"""
|
|
111
106
|
self.model_reporters = {}
|
|
112
107
|
self.agent_reporters = {}
|
|
113
|
-
self.agenttype_reporters = {}
|
|
114
108
|
|
|
115
109
|
self.model_vars = {}
|
|
116
110
|
self._agent_records = {}
|
|
117
|
-
self._agenttype_records = {}
|
|
118
111
|
self.tables = {}
|
|
119
112
|
|
|
120
113
|
if model_reporters is not None:
|
|
@@ -125,11 +118,6 @@ class DataCollector:
|
|
|
125
118
|
for name, reporter in agent_reporters.items():
|
|
126
119
|
self._new_agent_reporter(name, reporter)
|
|
127
120
|
|
|
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
|
-
|
|
133
121
|
if tables is not None:
|
|
134
122
|
for name, columns in tables.items():
|
|
135
123
|
self._new_table(name, columns)
|
|
@@ -177,38 +165,6 @@ class DataCollector:
|
|
|
177
165
|
|
|
178
166
|
self.agent_reporters[name] = reporter
|
|
179
167
|
|
|
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
|
-
|
|
212
168
|
def _new_table(self, table_name, table_columns):
|
|
213
169
|
"""Add a new table that objects can write to.
|
|
214
170
|
|
|
@@ -224,7 +180,7 @@ class DataCollector:
|
|
|
224
180
|
rep_funcs = self.agent_reporters.values()
|
|
225
181
|
|
|
226
182
|
def get_reports(agent):
|
|
227
|
-
_prefix = (agent.model.
|
|
183
|
+
_prefix = (agent.model._steps, agent.unique_id)
|
|
228
184
|
reports = tuple(rep(agent) for rep in rep_funcs)
|
|
229
185
|
return _prefix + reports
|
|
230
186
|
|
|
@@ -236,34 +192,6 @@ class DataCollector:
|
|
|
236
192
|
)
|
|
237
193
|
return agent_records
|
|
238
194
|
|
|
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
|
-
|
|
267
195
|
def collect(self, model):
|
|
268
196
|
"""Collect all the data for the given model object."""
|
|
269
197
|
if self.model_reporters:
|
|
@@ -282,20 +210,13 @@ class DataCollector:
|
|
|
282
210
|
elif isinstance(reporter, list):
|
|
283
211
|
self.model_vars[var].append(deepcopy(reporter[0](*reporter[1])))
|
|
284
212
|
# Assume it's a callable otherwise (e.g., method)
|
|
213
|
+
# TODO: Check if method of a class explicitly
|
|
285
214
|
else:
|
|
286
215
|
self.model_vars[var].append(deepcopy(reporter()))
|
|
287
216
|
|
|
288
217
|
if self.agent_reporters:
|
|
289
218
|
agent_records = self._record_agents(model)
|
|
290
|
-
self._agent_records[model.
|
|
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
|
-
)
|
|
219
|
+
self._agent_records[model._steps] = list(agent_records)
|
|
299
220
|
|
|
300
221
|
def add_table_row(self, table_name, row, ignore_missing=False):
|
|
301
222
|
"""Add a row dictionary to a specific table.
|
|
@@ -353,38 +274,6 @@ class DataCollector:
|
|
|
353
274
|
)
|
|
354
275
|
return df
|
|
355
276
|
|
|
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
|
-
|
|
388
277
|
def get_table_dataframe(self, table_name):
|
|
389
278
|
"""Create a pandas DataFrame from a particular table.
|
|
390
279
|
|
mesa/experimental/__init__.py
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
"""Experimental init."""
|
|
2
|
-
|
|
3
1
|
from mesa.experimental import cell_space
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
from .solara_viz import JupyterViz, Slider, SolaraViz, make_text
|
|
7
|
-
|
|
8
|
-
__all__ = ["cell_space", "JupyterViz", "Slider", "SolaraViz", "make_text"]
|
|
9
|
-
except ImportError:
|
|
10
|
-
print(
|
|
11
|
-
"Could not import SolaraViz. If you need it, install with 'pip install --pre mesa[viz]'"
|
|
12
|
-
)
|
|
13
|
-
__all__ = ["cell_space"]
|
|
3
|
+
__all__ = ["cell_space"]
|
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
"""Cell spaces.
|
|
2
|
-
|
|
3
|
-
Cell spaces offer an alternative API for discrete spaces. It is experimental and under development. The API is more
|
|
4
|
-
expressive that the default grids available in `mesa.space`.
|
|
5
|
-
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
from mesa.experimental.cell_space.cell import Cell
|
|
9
|
-
from mesa.experimental.cell_space.cell_agent import
|
|
10
|
-
CellAgent,
|
|
11
|
-
FixedAgent,
|
|
12
|
-
Grid2DMovingAgent,
|
|
13
|
-
)
|
|
2
|
+
from mesa.experimental.cell_space.cell_agent import CellAgent
|
|
14
3
|
from mesa.experimental.cell_space.cell_collection import CellCollection
|
|
15
4
|
from mesa.experimental.cell_space.discrete_space import DiscreteSpace
|
|
16
5
|
from mesa.experimental.cell_space.grid import (
|
|
@@ -20,19 +9,15 @@ from mesa.experimental.cell_space.grid import (
|
|
|
20
9
|
OrthogonalVonNeumannGrid,
|
|
21
10
|
)
|
|
22
11
|
from mesa.experimental.cell_space.network import Network
|
|
23
|
-
from mesa.experimental.cell_space.voronoi import VoronoiGrid
|
|
24
12
|
|
|
25
13
|
__all__ = [
|
|
26
14
|
"CellCollection",
|
|
27
15
|
"Cell",
|
|
28
16
|
"CellAgent",
|
|
29
|
-
"Grid2DMovingAgent",
|
|
30
|
-
"FixedAgent",
|
|
31
17
|
"DiscreteSpace",
|
|
32
18
|
"Grid",
|
|
33
19
|
"HexGrid",
|
|
34
20
|
"OrthogonalMooreGrid",
|
|
35
21
|
"OrthogonalVonNeumannGrid",
|
|
36
22
|
"Network",
|
|
37
|
-
"VoronoiGrid",
|
|
38
23
|
]
|