Mesa 1.1.0__py3-none-any.whl → 1.2.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-1.1.0.dist-info → Mesa-1.2.0.dist-info}/LICENSE +1 -1
- {Mesa-1.1.0.dist-info → Mesa-1.2.0.dist-info}/METADATA +15 -14
- {Mesa-1.1.0.dist-info → Mesa-1.2.0.dist-info}/RECORD +41 -41
- {Mesa-1.1.0.dist-info → Mesa-1.2.0.dist-info}/WHEEL +1 -1
- mesa/__init__.py +8 -9
- mesa/agent.py +2 -3
- mesa/batchrunner.py +19 -28
- mesa/datacollection.py +15 -28
- mesa/main.py +4 -4
- mesa/model.py +2 -6
- mesa/space.py +379 -286
- mesa/time.py +21 -22
- mesa/visualization/ModularVisualization.py +11 -9
- mesa/visualization/TextVisualization.py +0 -3
- mesa/visualization/UserParam.py +8 -11
- mesa/visualization/__init__.py +0 -1
- mesa/visualization/modules/BarChartVisualization.py +7 -8
- mesa/visualization/modules/CanvasGridVisualization.py +1 -3
- mesa/visualization/modules/ChartVisualization.py +2 -3
- mesa/visualization/modules/HexGridVisualization.py +1 -3
- mesa/visualization/modules/NetworkVisualization.py +1 -2
- mesa/visualization/modules/PieChartVisualization.py +2 -6
- mesa/visualization/templates/js/GridDraw.js +6 -10
- mesa/visualization/templates/js/HexDraw.js +5 -9
- mesa/visualization/templates/js/InteractionHandler.js +0 -2
- tests/test_batchrunner.py +3 -4
- tests/test_batchrunnerMP.py +4 -4
- tests/test_datacollector.py +2 -2
- tests/test_examples.py +8 -5
- tests/test_grid.py +104 -37
- tests/test_import_namespace.py +0 -1
- tests/test_lifespan.py +4 -3
- tests/test_main.py +5 -1
- tests/test_scaffold.py +2 -1
- tests/test_space.py +13 -20
- tests/test_time.py +44 -14
- tests/test_tornado.py +4 -2
- tests/test_usersettableparam.py +4 -3
- tests/test_visualization.py +4 -8
- {Mesa-1.1.0.dist-info → Mesa-1.2.0.dist-info}/entry_points.txt +0 -0
- {Mesa-1.1.0.dist-info → Mesa-1.2.0.dist-info}/top_level.txt +0 -0
mesa/time.py
CHANGED
|
@@ -25,14 +25,14 @@ Key concepts:
|
|
|
25
25
|
# Remove this __future__ import once the oldest supported Python is 3.10
|
|
26
26
|
from __future__ import annotations
|
|
27
27
|
|
|
28
|
-
from collections import
|
|
28
|
+
from collections import defaultdict
|
|
29
29
|
|
|
30
30
|
# mypy
|
|
31
31
|
from typing import Iterator, Union
|
|
32
|
+
|
|
32
33
|
from mesa.agent import Agent
|
|
33
34
|
from mesa.model import Model
|
|
34
35
|
|
|
35
|
-
|
|
36
36
|
# BaseScheduler has a self.time of int, while
|
|
37
37
|
# StagedActivation has a self.time of float
|
|
38
38
|
TimeT = Union[float, int]
|
|
@@ -45,7 +45,6 @@ class BaseScheduler:
|
|
|
45
45
|
Assumes that each agent added has a *step* method which takes no arguments.
|
|
46
46
|
|
|
47
47
|
(This is explicitly meant to replicate the scheduler in MASON).
|
|
48
|
-
|
|
49
48
|
"""
|
|
50
49
|
|
|
51
50
|
def __init__(self, model: Model) -> None:
|
|
@@ -53,7 +52,7 @@ class BaseScheduler:
|
|
|
53
52
|
self.model = model
|
|
54
53
|
self.steps = 0
|
|
55
54
|
self.time: TimeT = 0
|
|
56
|
-
self._agents: dict[int, Agent] =
|
|
55
|
+
self._agents: dict[int, Agent] = {}
|
|
57
56
|
|
|
58
57
|
def add(self, agent: Agent) -> None:
|
|
59
58
|
"""Add an Agent object to the schedule.
|
|
@@ -61,9 +60,7 @@ class BaseScheduler:
|
|
|
61
60
|
Args:
|
|
62
61
|
agent: An Agent to be added to the schedule. NOTE: The agent must
|
|
63
62
|
have a step() method.
|
|
64
|
-
|
|
65
63
|
"""
|
|
66
|
-
|
|
67
64
|
if agent.unique_id in self._agents:
|
|
68
65
|
raise Exception(
|
|
69
66
|
f"Agent with unique id {repr(agent.unique_id)} already added to scheduler"
|
|
@@ -76,7 +73,6 @@ class BaseScheduler:
|
|
|
76
73
|
|
|
77
74
|
Args:
|
|
78
75
|
agent: An agent object.
|
|
79
|
-
|
|
80
76
|
"""
|
|
81
77
|
del self._agents[agent.unique_id]
|
|
82
78
|
|
|
@@ -89,7 +85,7 @@ class BaseScheduler:
|
|
|
89
85
|
|
|
90
86
|
def get_agent_count(self) -> int:
|
|
91
87
|
"""Returns the current number of agents in the queue."""
|
|
92
|
-
return len(self._agents
|
|
88
|
+
return len(self._agents)
|
|
93
89
|
|
|
94
90
|
@property
|
|
95
91
|
def agents(self) -> list[Agent]:
|
|
@@ -98,15 +94,16 @@ class BaseScheduler:
|
|
|
98
94
|
def agent_buffer(self, shuffled: bool = False) -> Iterator[Agent]:
|
|
99
95
|
"""Simple generator that yields the agents while letting the user
|
|
100
96
|
remove and/or add agents during stepping.
|
|
101
|
-
|
|
102
97
|
"""
|
|
98
|
+
# To be able to remove and/or add agents during stepping
|
|
99
|
+
# it's necessary to cast the keys view to a list.
|
|
103
100
|
agent_keys = list(self._agents.keys())
|
|
104
101
|
if shuffled:
|
|
105
102
|
self.model.random.shuffle(agent_keys)
|
|
106
103
|
|
|
107
|
-
for
|
|
108
|
-
if
|
|
109
|
-
yield self._agents[
|
|
104
|
+
for agent_key in agent_keys:
|
|
105
|
+
if agent_key in self._agents:
|
|
106
|
+
yield self._agents[agent_key]
|
|
110
107
|
|
|
111
108
|
|
|
112
109
|
class RandomActivation(BaseScheduler):
|
|
@@ -117,7 +114,6 @@ class RandomActivation(BaseScheduler):
|
|
|
117
114
|
default behavior for an ABM.
|
|
118
115
|
|
|
119
116
|
Assumes that all agents have a step(model) method.
|
|
120
|
-
|
|
121
117
|
"""
|
|
122
118
|
|
|
123
119
|
def step(self) -> None:
|
|
@@ -137,11 +133,12 @@ class SimultaneousActivation(BaseScheduler):
|
|
|
137
133
|
This scheduler requires that each agent have two methods: step and advance.
|
|
138
134
|
step() activates the agent and stages any necessary changes, but does not
|
|
139
135
|
apply them yet. advance() then applies the changes.
|
|
140
|
-
|
|
141
136
|
"""
|
|
142
137
|
|
|
143
138
|
def step(self) -> None:
|
|
144
139
|
"""Step all agents, then advance them."""
|
|
140
|
+
# To be able to remove and/or add agents during stepping
|
|
141
|
+
# it's necessary to cast the keys view to a list.
|
|
145
142
|
agent_keys = list(self._agents.keys())
|
|
146
143
|
for agent_key in agent_keys:
|
|
147
144
|
self._agents[agent_key].step()
|
|
@@ -164,7 +161,6 @@ class StagedActivation(BaseScheduler):
|
|
|
164
161
|
|
|
165
162
|
This schedule tracks steps and time separately. Time advances in fractional
|
|
166
163
|
increments of 1 / (# of stages), meaning that 1 step = 1 unit of time.
|
|
167
|
-
|
|
168
164
|
"""
|
|
169
165
|
|
|
170
166
|
def __init__(
|
|
@@ -184,22 +180,24 @@ class StagedActivation(BaseScheduler):
|
|
|
184
180
|
shuffle_between_stages: If True, shuffle the agents after each
|
|
185
181
|
stage; otherwise, only shuffle at the start
|
|
186
182
|
of each step.
|
|
187
|
-
|
|
188
183
|
"""
|
|
189
184
|
super().__init__(model)
|
|
190
|
-
self.stage_list =
|
|
185
|
+
self.stage_list = stage_list if stage_list else ["step"]
|
|
191
186
|
self.shuffle = shuffle
|
|
192
187
|
self.shuffle_between_stages = shuffle_between_stages
|
|
193
188
|
self.stage_time = 1 / len(self.stage_list)
|
|
194
189
|
|
|
195
190
|
def step(self) -> None:
|
|
196
191
|
"""Executes all the stages for all agents."""
|
|
192
|
+
# To be able to remove and/or add agents during stepping
|
|
193
|
+
# it's necessary to cast the keys view to a list.
|
|
197
194
|
agent_keys = list(self._agents.keys())
|
|
198
195
|
if self.shuffle:
|
|
199
196
|
self.model.random.shuffle(agent_keys)
|
|
200
197
|
for stage in self.stage_list:
|
|
201
198
|
for agent_key in agent_keys:
|
|
202
|
-
|
|
199
|
+
if agent_key in self._agents:
|
|
200
|
+
getattr(self._agents[agent_key], stage)() # Run stage
|
|
203
201
|
# We recompute the keys because some agents might have been removed
|
|
204
202
|
# in the previous loop.
|
|
205
203
|
agent_keys = list(self._agents.keys())
|
|
@@ -241,7 +239,6 @@ class RandomActivationByType(BaseScheduler):
|
|
|
241
239
|
Args:
|
|
242
240
|
agent: An Agent to be added to the schedule.
|
|
243
241
|
"""
|
|
244
|
-
|
|
245
242
|
super().add(agent)
|
|
246
243
|
agent_class: type[Agent] = type(agent)
|
|
247
244
|
self.agents_by_type[agent_class][agent.unique_id] = agent
|
|
@@ -250,7 +247,6 @@ class RandomActivationByType(BaseScheduler):
|
|
|
250
247
|
"""
|
|
251
248
|
Remove all instances of a given agent from the schedule.
|
|
252
249
|
"""
|
|
253
|
-
|
|
254
250
|
del self._agents[agent.unique_id]
|
|
255
251
|
|
|
256
252
|
agent_class: type[Agent] = type(agent)
|
|
@@ -266,6 +262,8 @@ class RandomActivationByType(BaseScheduler):
|
|
|
266
262
|
shuffle_agents: If True, the order of execution of each agents in a
|
|
267
263
|
type group is shuffled.
|
|
268
264
|
"""
|
|
265
|
+
# To be able to remove and/or add agents during stepping
|
|
266
|
+
# it's necessary to cast the keys view to a list.
|
|
269
267
|
type_keys: list[type[Agent]] = list(self.agents_by_type.keys())
|
|
270
268
|
if shuffle_types:
|
|
271
269
|
self.model.random.shuffle(type_keys)
|
|
@@ -286,10 +284,11 @@ class RandomActivationByType(BaseScheduler):
|
|
|
286
284
|
if shuffle_agents:
|
|
287
285
|
self.model.random.shuffle(agent_keys)
|
|
288
286
|
for agent_key in agent_keys:
|
|
289
|
-
self.agents_by_type[type_class]
|
|
287
|
+
if agent_key in self.agents_by_type[type_class]:
|
|
288
|
+
self.agents_by_type[type_class][agent_key].step()
|
|
290
289
|
|
|
291
290
|
def get_type_count(self, type_class: type[Agent]) -> int:
|
|
292
291
|
"""
|
|
293
292
|
Returns the current number of agents of certain type in the queue.
|
|
294
293
|
"""
|
|
295
|
-
return len(self.agents_by_type[type_class]
|
|
294
|
+
return len(self.agents_by_type[type_class])
|
|
@@ -93,20 +93,20 @@ Client -> Server:
|
|
|
93
93
|
{
|
|
94
94
|
"type": "get_params"
|
|
95
95
|
}
|
|
96
|
-
|
|
97
96
|
"""
|
|
98
97
|
import asyncio
|
|
99
98
|
import os
|
|
100
99
|
import platform
|
|
100
|
+
import webbrowser
|
|
101
|
+
|
|
101
102
|
import tornado.autoreload
|
|
103
|
+
import tornado.escape
|
|
104
|
+
import tornado.gen
|
|
102
105
|
import tornado.ioloop
|
|
103
106
|
import tornado.web
|
|
104
107
|
import tornado.websocket
|
|
105
|
-
import tornado.escape
|
|
106
|
-
import tornado.gen
|
|
107
|
-
import webbrowser
|
|
108
108
|
|
|
109
|
-
from mesa.visualization.UserParam import
|
|
109
|
+
from mesa.visualization.UserParam import UserParam, UserSettableParameter
|
|
110
110
|
|
|
111
111
|
# Suppress several pylint warnings for this file.
|
|
112
112
|
# Attributes being defined outside of init is a Tornado feature.
|
|
@@ -145,7 +145,6 @@ class VisualizationElement:
|
|
|
145
145
|
Methods:
|
|
146
146
|
render: Takes a model object, and produces JSON data which can be sent
|
|
147
147
|
to the client.
|
|
148
|
-
|
|
149
148
|
"""
|
|
150
149
|
|
|
151
150
|
package_includes = []
|
|
@@ -165,7 +164,6 @@ class VisualizationElement:
|
|
|
165
164
|
|
|
166
165
|
Returns:
|
|
167
166
|
A JSON-ready object.
|
|
168
|
-
|
|
169
167
|
"""
|
|
170
168
|
return "<b>VisualizationElement goes here</b>."
|
|
171
169
|
|
|
@@ -297,7 +295,12 @@ class ModularServer(tornado.web.Application):
|
|
|
297
295
|
tornado.web.StaticFileHandler,
|
|
298
296
|
{"path": os.path.dirname(__file__) + "/templates"},
|
|
299
297
|
)
|
|
300
|
-
|
|
298
|
+
custom_handler = (
|
|
299
|
+
r"/local/custom/(.*)",
|
|
300
|
+
tornado.web.StaticFileHandler,
|
|
301
|
+
{"path": ""},
|
|
302
|
+
)
|
|
303
|
+
self.handlers = [page_handler, socket_handler, static_handler, custom_handler]
|
|
301
304
|
|
|
302
305
|
self.settings = {
|
|
303
306
|
"debug": True,
|
|
@@ -384,7 +387,6 @@ class ModularServer(tornado.web.Application):
|
|
|
384
387
|
def render_model(self):
|
|
385
388
|
"""Turn the current state of the model into a dictionary of
|
|
386
389
|
visualizations
|
|
387
|
-
|
|
388
390
|
"""
|
|
389
391
|
visualization_state = []
|
|
390
392
|
for element in self.visualization_elements:
|
|
@@ -24,7 +24,6 @@ one ASCII character via a converter method. This (as opposed to a dictionary)
|
|
|
24
24
|
is used so as to allow the method to access Agent internals, as well as to
|
|
25
25
|
potentially render a cell based on several values (e.g. an Agent grid and a
|
|
26
26
|
Patch value grid).
|
|
27
|
-
|
|
28
27
|
"""
|
|
29
28
|
# Pylint instructions: allow single-character variable names.
|
|
30
29
|
# pylint: disable=invalid-name
|
|
@@ -38,7 +37,6 @@ class TextVisualization:
|
|
|
38
37
|
model: The underlying model object to be visualized.
|
|
39
38
|
elements: List of visualization elements, which will be rendered
|
|
40
39
|
in the order they are added.
|
|
41
|
-
|
|
42
40
|
"""
|
|
43
41
|
|
|
44
42
|
def __init__(self, model):
|
|
@@ -97,7 +95,6 @@ class TextGrid(ASCIIElement):
|
|
|
97
95
|
|
|
98
96
|
Properties:
|
|
99
97
|
grid: The underlying grid object.
|
|
100
|
-
|
|
101
98
|
"""
|
|
102
99
|
|
|
103
100
|
grid = None
|
mesa/visualization/UserParam.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
from warnings import warn
|
|
2
1
|
import numbers
|
|
3
|
-
|
|
2
|
+
from warnings import warn
|
|
4
3
|
|
|
5
4
|
NUMBER = "number"
|
|
6
5
|
CHECKBOX = "checkbox"
|
|
@@ -67,7 +66,6 @@ class UserSettableParameter:
|
|
|
67
66
|
choices=None,
|
|
68
67
|
description=None,
|
|
69
68
|
):
|
|
70
|
-
|
|
71
69
|
warn(
|
|
72
70
|
"UserSettableParameter is deprecated in favor of UserParam objects "
|
|
73
71
|
"such as Slider, Checkbox, Choice, StaticText, NumberInput. "
|
|
@@ -75,7 +73,7 @@ class UserSettableParameter:
|
|
|
75
73
|
"UserSettableParameter will be removed in the next major release."
|
|
76
74
|
)
|
|
77
75
|
if choices is None:
|
|
78
|
-
choices =
|
|
76
|
+
choices = []
|
|
79
77
|
if param_type not in self.TYPES:
|
|
80
78
|
raise ValueError(f"{param_type} is not a valid Option type")
|
|
81
79
|
self.param_type = param_type
|
|
@@ -92,7 +90,7 @@ class UserSettableParameter:
|
|
|
92
90
|
valid = True
|
|
93
91
|
|
|
94
92
|
if self.param_type == self.NUMBER:
|
|
95
|
-
valid =
|
|
93
|
+
valid = self.value is not None
|
|
96
94
|
|
|
97
95
|
elif self.param_type == self.SLIDER:
|
|
98
96
|
valid = not (
|
|
@@ -123,12 +121,11 @@ class UserSettableParameter:
|
|
|
123
121
|
self._value = self.min_value
|
|
124
122
|
elif self._value > self.max_value:
|
|
125
123
|
self._value = self.max_value
|
|
126
|
-
elif self.param_type == self.CHOICE:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
self._value = self.choices[0]
|
|
124
|
+
elif (self.param_type == self.CHOICE) and self._value not in self.choices:
|
|
125
|
+
print(
|
|
126
|
+
"Selected choice value not in available choices, selected first choice from 'choices' list"
|
|
127
|
+
)
|
|
128
|
+
self._value = self.choices[0]
|
|
132
129
|
|
|
133
130
|
@property
|
|
134
131
|
def json(self):
|
mesa/visualization/__init__.py
CHANGED
|
@@ -3,10 +3,10 @@ Pie Chart Module
|
|
|
3
3
|
============
|
|
4
4
|
|
|
5
5
|
Module for drawing live-updating bar charts using d3.js
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
7
|
import json
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
from mesa.visualization.ModularVisualization import D3_JS_FILE, VisualizationElement
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class BarChartModule(VisualizationElement):
|
|
@@ -24,7 +24,6 @@ class BarChartModule(VisualizationElement):
|
|
|
24
24
|
canvas_height, canvas_width: The width and height to draw the chart on the page, in pixels.
|
|
25
25
|
Default to 800 x 400
|
|
26
26
|
data_collector_name: Name of the DataCollector object in the model to retrieve data from.
|
|
27
|
-
|
|
28
27
|
"""
|
|
29
28
|
|
|
30
29
|
package_includes = [D3_JS_FILE, "BarChartModule.js"]
|
|
@@ -76,20 +75,20 @@ class BarChartModule(VisualizationElement):
|
|
|
76
75
|
if self.scope == "agent":
|
|
77
76
|
df = data_collector.get_agent_vars_dataframe().astype("float")
|
|
78
77
|
latest_step = df.index.levels[0][-1]
|
|
79
|
-
|
|
80
|
-
dict = df.loc[latest_step].T.loc[
|
|
78
|
+
label_strings = [f["Label"] for f in self.fields]
|
|
79
|
+
dict = df.loc[latest_step].T.loc[label_strings].to_dict()
|
|
81
80
|
current_values = list(dict.values())
|
|
82
81
|
|
|
83
82
|
elif self.scope == "model":
|
|
84
|
-
|
|
83
|
+
out_dict = {}
|
|
85
84
|
for s in self.fields:
|
|
86
85
|
name = s["Label"]
|
|
87
86
|
try:
|
|
88
87
|
val = data_collector.model_vars[name][-1]
|
|
89
88
|
except (IndexError, KeyError):
|
|
90
89
|
val = 0
|
|
91
|
-
|
|
92
|
-
current_values.append(
|
|
90
|
+
out_dict[name] = val
|
|
91
|
+
current_values.append(out_dict)
|
|
93
92
|
else:
|
|
94
93
|
raise ValueError("scope must be 'agent' or 'model'")
|
|
95
94
|
return current_values
|
|
@@ -3,9 +3,9 @@ Modular Canvas Rendering
|
|
|
3
3
|
========================
|
|
4
4
|
|
|
5
5
|
Module for visualizing model objects in grid cells.
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
7
|
from collections import defaultdict
|
|
8
|
+
|
|
9
9
|
from mesa.visualization.ModularVisualization import VisualizationElement
|
|
10
10
|
|
|
11
11
|
|
|
@@ -59,7 +59,6 @@ class CanvasGrid(VisualizationElement):
|
|
|
59
59
|
canvas_height, canvas_width: Size, in pixels, of the grid visualization
|
|
60
60
|
to draw on the client.
|
|
61
61
|
template: "canvas_module.html" stores the module's HTML template.
|
|
62
|
-
|
|
63
62
|
"""
|
|
64
63
|
|
|
65
64
|
package_includes = ["GridDraw.js", "CanvasModule.js", "InteractionHandler.js"]
|
|
@@ -80,7 +79,6 @@ class CanvasGrid(VisualizationElement):
|
|
|
80
79
|
grid_width, grid_height: Size of the grid, in cells.
|
|
81
80
|
canvas_height, canvas_width: Size of the canvas to draw in the
|
|
82
81
|
client, in pixels. (default: 500x500)
|
|
83
|
-
|
|
84
82
|
"""
|
|
85
83
|
self.portrayal_method = portrayal_method
|
|
86
84
|
self.grid_width = grid_width
|
|
@@ -3,10 +3,10 @@ Chart Module
|
|
|
3
3
|
============
|
|
4
4
|
|
|
5
5
|
Module for drawing live-updating line charts using Charts.js
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
7
|
import json
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
from mesa.visualization.ModularVisualization import CHART_JS_FILE, VisualizationElement
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ChartModule(VisualizationElement):
|
|
@@ -39,7 +39,6 @@ class ChartModule(VisualizationElement):
|
|
|
39
39
|
More Pythonic customization; in particular, have both series-level and
|
|
40
40
|
chart-level options settable in Python, and passed to the front-end
|
|
41
41
|
the same way that "Color" is currently.
|
|
42
|
-
|
|
43
42
|
"""
|
|
44
43
|
|
|
45
44
|
package_includes = [CHART_JS_FILE, "ChartModule.js"]
|
|
@@ -3,9 +3,9 @@ Modular Canvas Rendering
|
|
|
3
3
|
========================
|
|
4
4
|
|
|
5
5
|
Module for visualizing model objects in hexagonal grid cells.
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
7
|
from collections import defaultdict
|
|
8
|
+
|
|
9
9
|
from mesa.visualization.ModularVisualization import VisualizationElement
|
|
10
10
|
|
|
11
11
|
|
|
@@ -36,7 +36,6 @@ class CanvasHexGrid(VisualizationElement):
|
|
|
36
36
|
canvas_height, canvas_width: Size, in pixels, of the grid visualization
|
|
37
37
|
to draw on the client.
|
|
38
38
|
template: "canvas_module.html" stores the module's HTML template.
|
|
39
|
-
|
|
40
39
|
"""
|
|
41
40
|
|
|
42
41
|
package_includes = ["HexDraw.js", "CanvasHexModule.js", "InteractionHandler.js"]
|
|
@@ -60,7 +59,6 @@ class CanvasHexGrid(VisualizationElement):
|
|
|
60
59
|
grid_width, grid_height: Size of the grid, in cells.
|
|
61
60
|
canvas_height, canvas_width: Size of the canvas to draw in the
|
|
62
61
|
client, in pixels. (default: 500x500)
|
|
63
|
-
|
|
64
62
|
"""
|
|
65
63
|
self.portrayal_method = portrayal_method
|
|
66
64
|
self.grid_width = grid_width
|
|
@@ -3,9 +3,8 @@ Network Visualization Module
|
|
|
3
3
|
============
|
|
4
4
|
|
|
5
5
|
Module for rendering the network, using [d3.js](https://d3js.org/) framework.
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
|
-
from mesa.visualization.ModularVisualization import
|
|
7
|
+
from mesa.visualization.ModularVisualization import D3_JS_FILE, VisualizationElement
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class NetworkModule(VisualizationElement):
|
|
@@ -3,17 +3,16 @@ Pie Chart Module
|
|
|
3
3
|
============
|
|
4
4
|
|
|
5
5
|
Module for drawing live-updating pie charts using d3.js
|
|
6
|
-
|
|
7
6
|
"""
|
|
8
7
|
import json
|
|
9
|
-
|
|
8
|
+
|
|
9
|
+
from mesa.visualization.ModularVisualization import D3_JS_FILE, VisualizationElement
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class PieChartModule(VisualizationElement):
|
|
13
13
|
"""Each chart can visualize one set of fields from a datacollector as a
|
|
14
14
|
pie chart.
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
Attributes:
|
|
18
17
|
fields: A list of dictionaries containing information on fields to
|
|
19
18
|
plot. Each dictionary must contain (at least) the "Label" and
|
|
@@ -24,9 +23,6 @@ class PieChartModule(VisualizationElement):
|
|
|
24
23
|
the page, in pixels. Default to 500 x 500
|
|
25
24
|
data_collector_name: Name of the DataCollector object in the model to
|
|
26
25
|
retrieve data from.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
26
|
"""
|
|
31
27
|
|
|
32
28
|
package_includes = [D3_JS_FILE, "PieChartModule.js"]
|
|
@@ -57,7 +57,7 @@ const GridVisualization = function (
|
|
|
57
57
|
// Calls the appropriate shape(agent)
|
|
58
58
|
this.drawLayer = function (portrayalLayer) {
|
|
59
59
|
// Re-initialize the lookup table
|
|
60
|
-
interactionHandler
|
|
60
|
+
if (interactionHandler) interactionHandler.mouseoverLookupTable.init();
|
|
61
61
|
|
|
62
62
|
for (const i in portrayalLayer) {
|
|
63
63
|
const p = portrayalLayer[i];
|
|
@@ -72,9 +72,7 @@ const GridVisualization = function (
|
|
|
72
72
|
p.y = gridHeight - p.y - 1;
|
|
73
73
|
|
|
74
74
|
// if a handler exists, add coordinates for the portrayalLayer index
|
|
75
|
-
interactionHandler
|
|
76
|
-
? interactionHandler.mouseoverLookupTable.set(p.x, p.y, i)
|
|
77
|
-
: null;
|
|
75
|
+
if (interactionHandler) interactionHandler.mouseoverLookupTable.set(p.x, p.y, i);
|
|
78
76
|
|
|
79
77
|
// If the stroke color is not defined, then the first color in the colors array is the stroke color.
|
|
80
78
|
if (!p.stroke_color) p.stroke_color = p.Color[0];
|
|
@@ -127,9 +125,7 @@ const GridVisualization = function (
|
|
|
127
125
|
this.drawCustomImage(p.Shape, p.x, p.y, p.scale, p.text, p.text_color);
|
|
128
126
|
}
|
|
129
127
|
// if a handler exists, update its mouse listeners with the new data
|
|
130
|
-
interactionHandler
|
|
131
|
-
? interactionHandler.updateMouseListeners(portrayalLayer)
|
|
132
|
-
: null;
|
|
128
|
+
if (interactionHandler) interactionHandler.updateMouseListeners(portrayalLayer);
|
|
133
129
|
};
|
|
134
130
|
|
|
135
131
|
// DRAWING METHODS
|
|
@@ -359,7 +355,7 @@ const GridVisualization = function (
|
|
|
359
355
|
|
|
360
356
|
this.drawCustomImage = function (shape, x, y, scale, text, text_color_) {
|
|
361
357
|
const img = new Image();
|
|
362
|
-
img.src = "local/".concat(shape);
|
|
358
|
+
img.src = "local/custom/".concat(shape);
|
|
363
359
|
if (scale === undefined) {
|
|
364
360
|
scale = 1;
|
|
365
361
|
}
|
|
@@ -393,8 +389,8 @@ const GridVisualization = function (
|
|
|
393
389
|
this.drawGridLines = function () {
|
|
394
390
|
context.beginPath();
|
|
395
391
|
context.strokeStyle = "#eee";
|
|
396
|
-
maxX = cellWidth * gridWidth;
|
|
397
|
-
maxY = cellHeight * gridHeight;
|
|
392
|
+
const maxX = cellWidth * gridWidth;
|
|
393
|
+
const maxY = cellHeight * gridHeight;
|
|
398
394
|
|
|
399
395
|
// Draw horizontal grid lines:
|
|
400
396
|
for (let y = 0; y <= maxY; y += cellHeight) {
|
|
@@ -55,12 +55,12 @@ const HexVisualization = function (
|
|
|
55
55
|
const maxR = Math.min(cellHeight, cellWidth) / 2 - 1;
|
|
56
56
|
|
|
57
57
|
// Configure the interaction handler to use a hex coordinate mapper
|
|
58
|
-
interactionHandler
|
|
58
|
+
if (interactionHandler) interactionHandler.setCoordinateMapper("hex");
|
|
59
59
|
|
|
60
60
|
// Calls the appropriate shape(agent)
|
|
61
61
|
this.drawLayer = function (portrayalLayer) {
|
|
62
62
|
// Re-initialize the lookup table
|
|
63
|
-
interactionHandler
|
|
63
|
+
if (interactionHandler) interactionHandler.mouseoverLookupTable.init();
|
|
64
64
|
for (const i in portrayalLayer) {
|
|
65
65
|
const p = portrayalLayer[i];
|
|
66
66
|
// Does the inversion of y positioning because of html5
|
|
@@ -69,9 +69,7 @@ const HexVisualization = function (
|
|
|
69
69
|
p.y = gridHeight - p.y - 1;
|
|
70
70
|
|
|
71
71
|
// if a handler exists, add coordinates for the portrayalLayer index
|
|
72
|
-
interactionHandler
|
|
73
|
-
? interactionHandler.mouseoverLookupTable.set(p.x, p.y, i)
|
|
74
|
-
: null;
|
|
72
|
+
if (interactionHandler) interactionHandler.mouseoverLookupTable.set(p.x, p.y, i);
|
|
75
73
|
|
|
76
74
|
if (p.Shape == "hex")
|
|
77
75
|
this.drawHex(p.x, p.y, p.r, p.Color, p.Filled, p.text, p.text_color);
|
|
@@ -93,9 +91,7 @@ const HexVisualization = function (
|
|
|
93
91
|
this.drawCustomImage(p.Shape, p.x, p.y, p.scale, p.text, p.text_color);
|
|
94
92
|
}
|
|
95
93
|
// if a handler exists, update its mouse listeners with the new data
|
|
96
|
-
interactionHandler
|
|
97
|
-
? interactionHandler.updateMouseListeners(portrayalLayer)
|
|
98
|
-
: null;
|
|
94
|
+
if (interactionHandler) interactionHandler.updateMouseListeners(portrayalLayer);
|
|
99
95
|
};
|
|
100
96
|
|
|
101
97
|
// DRAWING METHODS
|
|
@@ -158,7 +154,7 @@ const HexVisualization = function (
|
|
|
158
154
|
} else {
|
|
159
155
|
cy = (y + 0.5) * cellHeight + cellHeight / 2;
|
|
160
156
|
}
|
|
161
|
-
maxHexRadius = cellHeight / Math.sqrt(3);
|
|
157
|
+
const maxHexRadius = cellHeight / Math.sqrt(3);
|
|
162
158
|
const r = radius * maxHexRadius;
|
|
163
159
|
|
|
164
160
|
function hex_corner(x, y, size, i) {
|
|
@@ -125,8 +125,6 @@ const InteractionHandler = function (width, height, gridWidth, gridHeight, ctx)
|
|
|
125
125
|
|
|
126
126
|
// map the event to x,y coordinates
|
|
127
127
|
const position = coordinateMapper(event);
|
|
128
|
-
const yPosition = Math.floor(event.offsetY / cellHeight);
|
|
129
|
-
const xPosition = Math.floor(event.offsetX / cellWidth);
|
|
130
128
|
|
|
131
129
|
// look up the portrayal items the coordinates refer to and draw a tooltip
|
|
132
130
|
mouseoverLookupTable
|
tests/test_batchrunner.py
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Test the BatchRunner
|
|
3
3
|
"""
|
|
4
|
+
import unittest
|
|
4
5
|
from functools import reduce
|
|
5
6
|
from operator import mul
|
|
6
|
-
import unittest
|
|
7
7
|
|
|
8
8
|
from mesa import Agent, Model
|
|
9
|
-
from mesa.time import BaseScheduler
|
|
10
|
-
from mesa.datacollection import DataCollector
|
|
11
9
|
from mesa.batchrunner import (
|
|
12
10
|
BatchRunner,
|
|
13
11
|
FixedBatchRunner,
|
|
14
12
|
ParameterProduct,
|
|
15
13
|
ParameterSampler,
|
|
16
14
|
)
|
|
17
|
-
|
|
15
|
+
from mesa.datacollection import DataCollector
|
|
16
|
+
from mesa.time import BaseScheduler
|
|
18
17
|
|
|
19
18
|
NUM_AGENTS = 7
|
|
20
19
|
|
tests/test_batchrunnerMP.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Test the BatchRunner
|
|
3
3
|
"""
|
|
4
|
+
import unittest
|
|
4
5
|
from functools import reduce
|
|
6
|
+
from multiprocessing import cpu_count, freeze_support
|
|
5
7
|
from operator import mul
|
|
6
|
-
import unittest
|
|
7
8
|
|
|
8
9
|
from mesa import Agent, Model
|
|
9
|
-
from mesa.time import BaseScheduler
|
|
10
|
-
from mesa.datacollection import DataCollector
|
|
11
10
|
from mesa.batchrunner import BatchRunnerMP, ParameterProduct, ParameterSampler
|
|
12
|
-
from
|
|
11
|
+
from mesa.datacollection import DataCollector
|
|
12
|
+
from mesa.time import BaseScheduler
|
|
13
13
|
|
|
14
14
|
NUM_AGENTS = 7
|
|
15
15
|
|
tests/test_datacollector.py
CHANGED
|
@@ -3,7 +3,7 @@ Test the DataCollector
|
|
|
3
3
|
"""
|
|
4
4
|
import unittest
|
|
5
5
|
|
|
6
|
-
from mesa import
|
|
6
|
+
from mesa import Agent, Model
|
|
7
7
|
from mesa.time import BaseScheduler
|
|
8
8
|
|
|
9
9
|
|
|
@@ -148,7 +148,7 @@ class TestDataCollector(unittest.TestCase):
|
|
|
148
148
|
assert len(data_collector.tables["Final_Values"]) == 2
|
|
149
149
|
assert "agent_id" in data_collector.tables["Final_Values"]
|
|
150
150
|
assert "final_value" in data_collector.tables["Final_Values"]
|
|
151
|
-
for
|
|
151
|
+
for _key, data in data_collector.tables["Final_Values"].items():
|
|
152
152
|
assert len(data) == 9
|
|
153
153
|
|
|
154
154
|
with self.assertRaises(Exception):
|