Mesa 2.4.0__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 +105 -92
- mesa/batchrunner.py +55 -31
- mesa/datacollection.py +10 -14
- 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 +69 -38
- mesa/experimental/devs/examples/wolf_sheep.py +42 -43
- mesa/experimental/devs/simulator.py +57 -16
- mesa/experimental/{jupyter_viz.py → solara_viz.py} +151 -99
- mesa/model.py +136 -78
- mesa/space.py +208 -148
- 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.4.0.dist-info → mesa-3.0.0.dist-info}/METADATA +62 -17
- mesa-3.0.0.dist-info/RECORD +95 -0
- mesa-3.0.0.dist-info/licenses/LICENSE +202 -0
- mesa-2.4.0.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.4.0.dist-info/RECORD +0 -45
- /mesa/{cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}} → examples/advanced}/__init__.py +0 -0
- {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/WHEEL +0 -0
- {mesa-2.4.0.dist-info → mesa-3.0.0.dist-info}/entry_points.txt +0 -0
mesa/time.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""Mesa Time Module.
|
|
2
|
+
|
|
3
|
+
.. warning::
|
|
4
|
+
The time module and all its Schedulers are deprecated and will be removed in a future version.
|
|
5
|
+
They can be replaced with AgentSet functionality. See the migration guide for details:
|
|
6
|
+
https://mesa.readthedocs.io/latest/migration_guide.html#time-and-schedulers
|
|
4
7
|
|
|
5
8
|
Objects for handling the time component of a model. In particular, this module
|
|
6
9
|
contains Schedulers, which handle agent activation. A Scheduler is an object
|
|
@@ -30,34 +33,25 @@ from collections import defaultdict
|
|
|
30
33
|
from collections.abc import Iterable
|
|
31
34
|
|
|
32
35
|
# mypy
|
|
33
|
-
from typing import Union
|
|
34
|
-
|
|
35
36
|
from mesa.agent import Agent, AgentSet
|
|
36
37
|
from mesa.model import Model
|
|
37
38
|
|
|
38
39
|
# BaseScheduler has a self.time of int, while
|
|
39
40
|
# StagedActivation has a self.time of float
|
|
40
|
-
TimeT =
|
|
41
|
+
TimeT = float | int
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
class BaseScheduler:
|
|
44
|
-
"""
|
|
45
|
-
A simple scheduler that activates agents one at a time, in the order they were added.
|
|
45
|
+
"""A simple scheduler that activates agents one at a time, in the order they were added.
|
|
46
46
|
|
|
47
47
|
This scheduler is designed to replicate the behavior of the scheduler in MASON, a multi-agent simulation toolkit.
|
|
48
48
|
It assumes that each agent added has a `step` method which takes no arguments and executes the agent's actions.
|
|
49
49
|
|
|
50
50
|
Attributes:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Methods:
|
|
56
|
-
- add: Adds an agent to the scheduler.
|
|
57
|
-
- remove: Removes an agent from the scheduler.
|
|
58
|
-
- step: Executes a step, which involves activating each agent once.
|
|
59
|
-
- get_agent_count: Returns the number of agents in the scheduler.
|
|
60
|
-
- agents (property): Returns a list of all agent instances.
|
|
51
|
+
model (Model): The model instance associated with the scheduler.
|
|
52
|
+
steps (int): The number of steps the scheduler has taken.
|
|
53
|
+
time (TimeT): The current time in the simulation. Can be an integer or a float.
|
|
54
|
+
|
|
61
55
|
"""
|
|
62
56
|
|
|
63
57
|
def __init__(self, model: Model, agents: Iterable[Agent] | None = None) -> None:
|
|
@@ -68,16 +62,22 @@ class BaseScheduler:
|
|
|
68
62
|
agents (Iterable[Agent], None, optional): An iterable of agents who are controlled by the schedule
|
|
69
63
|
|
|
70
64
|
"""
|
|
65
|
+
warnings.warn(
|
|
66
|
+
"The time module and all its Schedulers are deprecated and will be removed in a future version. "
|
|
67
|
+
"They can be replaced with AgentSet functionality. See the migration guide for details. "
|
|
68
|
+
"https://mesa.readthedocs.io/latest/migration_guide.html#time-and-schedulers",
|
|
69
|
+
DeprecationWarning,
|
|
70
|
+
stacklevel=2,
|
|
71
|
+
)
|
|
72
|
+
|
|
71
73
|
self.model = model
|
|
72
74
|
self.steps = 0
|
|
73
75
|
self.time: TimeT = 0
|
|
74
|
-
self._original_step = self.step
|
|
75
|
-
self.step = self._wrapped_step
|
|
76
76
|
|
|
77
77
|
if agents is None:
|
|
78
78
|
agents = []
|
|
79
79
|
|
|
80
|
-
self._agents: AgentSet = AgentSet(agents, model)
|
|
80
|
+
self._agents: AgentSet = AgentSet(agents, model.random)
|
|
81
81
|
|
|
82
82
|
self._remove_warning_given = False
|
|
83
83
|
self._agents_key_warning_given = False
|
|
@@ -86,10 +86,8 @@ class BaseScheduler:
|
|
|
86
86
|
"""Add an Agent object to the schedule.
|
|
87
87
|
|
|
88
88
|
Args:
|
|
89
|
-
agent: An Agent to be added to the schedule.
|
|
90
|
-
have a step() method.
|
|
89
|
+
agent (Agent): An Agent to be added to the schedule.
|
|
91
90
|
"""
|
|
92
|
-
|
|
93
91
|
if agent not in self._agents:
|
|
94
92
|
self._agents.add(agent)
|
|
95
93
|
else:
|
|
@@ -98,12 +96,13 @@ class BaseScheduler:
|
|
|
98
96
|
def remove(self, agent: Agent) -> None:
|
|
99
97
|
"""Remove all instances of a given agent from the schedule.
|
|
100
98
|
|
|
99
|
+
Args:
|
|
100
|
+
agent: An `Agent` instance.
|
|
101
|
+
|
|
101
102
|
Note:
|
|
102
103
|
It is only necessary to explicitly remove agents from the schedule if
|
|
103
104
|
the agent is not removed from the model.
|
|
104
105
|
|
|
105
|
-
Args:
|
|
106
|
-
agent: An agent object.
|
|
107
106
|
"""
|
|
108
107
|
self._agents.remove(agent)
|
|
109
108
|
|
|
@@ -115,21 +114,18 @@ class BaseScheduler:
|
|
|
115
114
|
self.steps += 1
|
|
116
115
|
self.time += 1
|
|
117
116
|
|
|
118
|
-
def _wrapped_step(self):
|
|
119
|
-
"""Wrapper for the step method to include time and step updating."""
|
|
120
|
-
self._original_step()
|
|
121
|
-
self.model._advance_time()
|
|
122
|
-
|
|
123
117
|
def get_agent_count(self) -> int:
|
|
124
118
|
"""Returns the current number of agents in the queue."""
|
|
125
119
|
return len(self._agents)
|
|
126
120
|
|
|
127
121
|
@property
|
|
128
122
|
def agents(self) -> AgentSet:
|
|
123
|
+
"""Return agents in the scheduler."""
|
|
129
124
|
# a bit dirty, but returns a copy of the internal agent set
|
|
130
125
|
return self._agents.select()
|
|
131
126
|
|
|
132
127
|
def get_agent_keys(self, shuffle: bool = False) -> list[int]:
|
|
128
|
+
"""Deprecated."""
|
|
133
129
|
# To be able to remove and/or add agents during stepping
|
|
134
130
|
# it's necessary to cast the keys view to a list.
|
|
135
131
|
|
|
@@ -147,14 +143,21 @@ class BaseScheduler:
|
|
|
147
143
|
return agent_keys
|
|
148
144
|
|
|
149
145
|
def do_each(self, method, shuffle=False):
|
|
146
|
+
"""Perform `method` on each agent.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
method: method to call
|
|
150
|
+
shuffle: shuffle the agents or not prior to calling method
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
"""
|
|
150
154
|
if shuffle:
|
|
151
155
|
self._agents.shuffle(inplace=True)
|
|
152
156
|
self._agents.do(method)
|
|
153
157
|
|
|
154
158
|
|
|
155
159
|
class RandomActivation(BaseScheduler):
|
|
156
|
-
"""
|
|
157
|
-
A scheduler that activates each agent once per step, in a random order, with the order reshuffled each step.
|
|
160
|
+
"""A scheduler that activates each agent once per step, in a random order, with the order reshuffled each step.
|
|
158
161
|
|
|
159
162
|
This scheduler is equivalent to the NetLogo 'ask agents...' behavior and is a common default for ABMs.
|
|
160
163
|
It assumes that all agents have a `step` method.
|
|
@@ -164,23 +167,17 @@ class RandomActivation(BaseScheduler):
|
|
|
164
167
|
|
|
165
168
|
Inherits all attributes and methods from BaseScheduler.
|
|
166
169
|
|
|
167
|
-
Methods:
|
|
168
|
-
- step: Executes a step, activating each agent in a random order.
|
|
169
170
|
"""
|
|
170
171
|
|
|
171
172
|
def step(self) -> None:
|
|
172
|
-
"""Executes the step of all agents, one at a time, in
|
|
173
|
-
random order.
|
|
174
|
-
|
|
175
|
-
"""
|
|
173
|
+
"""Executes the step of all agents, one at a time, in random order."""
|
|
176
174
|
self.do_each("step", shuffle=True)
|
|
177
175
|
self.steps += 1
|
|
178
176
|
self.time += 1
|
|
179
177
|
|
|
180
178
|
|
|
181
179
|
class SimultaneousActivation(BaseScheduler):
|
|
182
|
-
"""
|
|
183
|
-
A scheduler that simulates the simultaneous activation of all agents.
|
|
180
|
+
"""A scheduler that simulates the simultaneous activation of all agents.
|
|
184
181
|
|
|
185
182
|
This scheduler is unique in that it requires agents to have both `step` and `advance` methods.
|
|
186
183
|
- The `step` method is for activating the agent and staging any changes without applying them immediately.
|
|
@@ -191,8 +188,6 @@ class SimultaneousActivation(BaseScheduler):
|
|
|
191
188
|
|
|
192
189
|
Inherits all attributes and methods from BaseScheduler.
|
|
193
190
|
|
|
194
|
-
Methods:
|
|
195
|
-
- step: Executes a step for all agents, first calling `step` then `advance` on each.
|
|
196
191
|
"""
|
|
197
192
|
|
|
198
193
|
def step(self) -> None:
|
|
@@ -207,9 +202,9 @@ class SimultaneousActivation(BaseScheduler):
|
|
|
207
202
|
|
|
208
203
|
|
|
209
204
|
class StagedActivation(BaseScheduler):
|
|
210
|
-
"""
|
|
211
|
-
|
|
212
|
-
before moving on to the next. This class is a generalization of SimultaneousActivation.
|
|
205
|
+
"""A scheduler allowing agent activation to be divided into several stages.
|
|
206
|
+
|
|
207
|
+
All agents executing one stage before moving on to the next. This class is a generalization of SimultaneousActivation.
|
|
213
208
|
|
|
214
209
|
This scheduler is useful for complex models where actions need to be broken down into distinct phases
|
|
215
210
|
for each agent in each time step. Agents must implement methods for each defined stage.
|
|
@@ -224,8 +219,6 @@ class StagedActivation(BaseScheduler):
|
|
|
224
219
|
- shuffle (bool): Determines whether to shuffle the order of agents each step.
|
|
225
220
|
- shuffle_between_stages (bool): Determines whether to shuffle agents between each stage.
|
|
226
221
|
|
|
227
|
-
Methods:
|
|
228
|
-
- step: Executes all the stages for all agents in the defined order.
|
|
229
222
|
"""
|
|
230
223
|
|
|
231
224
|
def __init__(
|
|
@@ -270,8 +263,7 @@ class StagedActivation(BaseScheduler):
|
|
|
270
263
|
|
|
271
264
|
|
|
272
265
|
class RandomActivationByType(BaseScheduler):
|
|
273
|
-
"""
|
|
274
|
-
A scheduler that activates each type of agent once per step, in random order, with the order reshuffled every step.
|
|
266
|
+
"""A scheduler that activates each type of agent once per step, in random order, with the order reshuffled every step.
|
|
275
267
|
|
|
276
268
|
This scheduler is useful for models with multiple types of agents, ensuring that each type is treated
|
|
277
269
|
equitably in terms of activation order. The randomness in activation order helps in reducing biases
|
|
@@ -287,14 +279,10 @@ class RandomActivationByType(BaseScheduler):
|
|
|
287
279
|
Attributes:
|
|
288
280
|
- agents_by_type (defaultdict): A dictionary mapping agent types to dictionaries of agents.
|
|
289
281
|
|
|
290
|
-
Methods:
|
|
291
|
-
- step: Executes the step of each agent type in a random order.
|
|
292
|
-
- step_type: Activates all agents of a given type.
|
|
293
|
-
- get_type_count: Returns the count of agents of a specific type.
|
|
294
282
|
"""
|
|
295
283
|
|
|
296
284
|
@property
|
|
297
|
-
def agents_by_type(self):
|
|
285
|
+
def agents_by_type(self): # noqa: D102
|
|
298
286
|
warnings.warn(
|
|
299
287
|
"Because of the shift to using AgentSet, in the future this attribute will return a dict with"
|
|
300
288
|
"type as key as AgentSet as value. Future behavior is available via RandomActivationByType._agents_by_type",
|
|
@@ -309,14 +297,13 @@ class RandomActivationByType(BaseScheduler):
|
|
|
309
297
|
return agentsbytype
|
|
310
298
|
|
|
311
299
|
def __init__(self, model: Model, agents: Iterable[Agent] | None = None) -> None:
|
|
312
|
-
|
|
313
|
-
"""
|
|
300
|
+
"""Initialize RandomActivationByType instance.
|
|
314
301
|
|
|
315
302
|
Args:
|
|
316
303
|
model (Model): The model to which the schedule belongs
|
|
317
304
|
agents (Iterable[Agent], None, optional): An iterable of agents who are controlled by the schedule
|
|
318
305
|
"""
|
|
319
|
-
|
|
306
|
+
super().__init__(model, agents)
|
|
320
307
|
# can't be a defaultdict because we need to pass model to AgentSet
|
|
321
308
|
self._agents_by_type: [type, AgentSet] = {}
|
|
322
309
|
|
|
@@ -325,11 +312,12 @@ class RandomActivationByType(BaseScheduler):
|
|
|
325
312
|
try:
|
|
326
313
|
self._agents_by_type[type(agent)].add(agent)
|
|
327
314
|
except KeyError:
|
|
328
|
-
self._agents_by_type[type(agent)] = AgentSet(
|
|
315
|
+
self._agents_by_type[type(agent)] = AgentSet(
|
|
316
|
+
[agent], self.model.random
|
|
317
|
+
)
|
|
329
318
|
|
|
330
319
|
def add(self, agent: Agent) -> None:
|
|
331
|
-
"""
|
|
332
|
-
Add an Agent object to the schedule
|
|
320
|
+
"""Add an Agent object to the schedule.
|
|
333
321
|
|
|
334
322
|
Args:
|
|
335
323
|
agent: An Agent to be added to the schedule.
|
|
@@ -339,24 +327,24 @@ class RandomActivationByType(BaseScheduler):
|
|
|
339
327
|
try:
|
|
340
328
|
self._agents_by_type[type(agent)].add(agent)
|
|
341
329
|
except KeyError:
|
|
342
|
-
self._agents_by_type[type(agent)] = AgentSet([agent], self.model)
|
|
330
|
+
self._agents_by_type[type(agent)] = AgentSet([agent], self.model.random)
|
|
343
331
|
|
|
344
332
|
def remove(self, agent: Agent) -> None:
|
|
345
|
-
"""
|
|
346
|
-
|
|
333
|
+
"""Remove all instances of a given agent from the schedule.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
agent: An Agent to be removed from the schedule.
|
|
337
|
+
|
|
347
338
|
"""
|
|
348
339
|
super().remove(agent)
|
|
349
340
|
self._agents_by_type[type(agent)].remove(agent)
|
|
350
341
|
|
|
351
342
|
def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None:
|
|
352
|
-
"""
|
|
353
|
-
Executes the step of each agent type, one at a time, in random order.
|
|
343
|
+
"""Executes the step of each agent type, one at a time, in random order.
|
|
354
344
|
|
|
355
345
|
Args:
|
|
356
|
-
shuffle_types: If True, the order of execution of each types is
|
|
357
|
-
|
|
358
|
-
shuffle_agents: If True, the order of execution of each agents in a
|
|
359
|
-
type group is shuffled.
|
|
346
|
+
shuffle_types: If True, the order of execution of each types is shuffled.
|
|
347
|
+
shuffle_agents: If True, the order of execution of each agents in a type group is shuffled.
|
|
360
348
|
"""
|
|
361
349
|
# To be able to remove and/or add agents during stepping
|
|
362
350
|
# it's necessary to cast the keys view to a list.
|
|
@@ -369,12 +357,11 @@ class RandomActivationByType(BaseScheduler):
|
|
|
369
357
|
self.time += 1
|
|
370
358
|
|
|
371
359
|
def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None:
|
|
372
|
-
"""
|
|
373
|
-
Shuffle order and run all agents of a given type.
|
|
374
|
-
This method is equivalent to the NetLogo 'ask [breed]...'.
|
|
360
|
+
"""Shuffle order and run all agents of a given type.
|
|
375
361
|
|
|
376
362
|
Args:
|
|
377
363
|
agenttype: Class object of the type to run.
|
|
364
|
+
shuffle_agents: If True, shuffle agents
|
|
378
365
|
"""
|
|
379
366
|
agents = self._agents_by_type[agenttype]
|
|
380
367
|
|
|
@@ -383,19 +370,15 @@ class RandomActivationByType(BaseScheduler):
|
|
|
383
370
|
agents.do("step")
|
|
384
371
|
|
|
385
372
|
def get_type_count(self, agenttype: type[Agent]) -> int:
|
|
386
|
-
"""
|
|
387
|
-
Returns the current number of agents of certain type in the queue.
|
|
388
|
-
"""
|
|
373
|
+
"""Returns the current number of agents of certain type in the queue."""
|
|
389
374
|
return len(self._agents_by_type[agenttype])
|
|
390
375
|
|
|
391
376
|
|
|
392
377
|
class DiscreteEventScheduler(BaseScheduler):
|
|
393
|
-
"""
|
|
394
|
-
This class has been deprecated and replaced by the functionality provided by experimental.devs
|
|
395
|
-
"""
|
|
378
|
+
"""This class has been deprecated and replaced by the functionality provided by experimental.devs."""
|
|
396
379
|
|
|
397
380
|
def __init__(self, model: Model, time_step: TimeT = 1) -> None:
|
|
398
|
-
"""
|
|
381
|
+
"""Initialize DiscreteEventScheduler.
|
|
399
382
|
|
|
400
383
|
Args:
|
|
401
384
|
model (Model): The model to which the schedule belongs
|
mesa/visualization/__init__.py
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
"""Solara based visualization for Mesa models.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
.. note::
|
|
4
|
+
SolaraViz is experimental and still in active development for Mesa 3.0. While we attempt to minimize them, there might be API breaking changes between Mesa 3.0 and 3.1.
|
|
5
|
+
|
|
6
|
+
There won't be breaking changes between Mesa 3.0.x patch releases.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from mesa.visualization.mpl_space_drawing import (
|
|
10
|
+
draw_space,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .components import make_plot_component, make_space_component
|
|
14
|
+
from .components.altair_components import make_space_altair
|
|
15
|
+
from .solara_viz import JupyterViz, SolaraViz
|
|
16
|
+
from .user_param import Slider
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"JupyterViz",
|
|
20
|
+
"SolaraViz",
|
|
21
|
+
"Slider",
|
|
22
|
+
"make_space_altair",
|
|
23
|
+
"draw_space",
|
|
24
|
+
"make_plot_component",
|
|
25
|
+
"make_space_component",
|
|
26
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""custom solara components."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from .altair_components import SpaceAltair, make_altair_space
|
|
8
|
+
from .matplotlib_components import (
|
|
9
|
+
SpaceMatplotlib,
|
|
10
|
+
make_mpl_plot_component,
|
|
11
|
+
make_mpl_space_component,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def make_space_component(
|
|
16
|
+
agent_portrayal: Callable | None = None,
|
|
17
|
+
propertylayer_portrayal: dict | None = None,
|
|
18
|
+
post_process: Callable | None = None,
|
|
19
|
+
backend: str = "matplotlib",
|
|
20
|
+
**space_drawing_kwargs,
|
|
21
|
+
) -> SpaceMatplotlib | SpaceAltair:
|
|
22
|
+
"""Create a Matplotlib-based space visualization component.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
agent_portrayal: Function to portray agents.
|
|
26
|
+
propertylayer_portrayal: Dictionary of PropertyLayer portrayal specifications
|
|
27
|
+
post_process : a callable that will be called with the Axes instance. Allows for fine-tuning plots (e.g., control ticks)
|
|
28
|
+
backend: the backend to use {"matplotlib", "altair"}
|
|
29
|
+
space_drawing_kwargs : additional keyword arguments to be passed on to the underlying backend specific space drawer function. See
|
|
30
|
+
the functions for drawing the various spaces for the appropriate backend further details.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
function: A function that creates a space component
|
|
35
|
+
"""
|
|
36
|
+
if backend == "matplotlib":
|
|
37
|
+
return make_mpl_space_component(
|
|
38
|
+
agent_portrayal,
|
|
39
|
+
propertylayer_portrayal,
|
|
40
|
+
post_process,
|
|
41
|
+
**space_drawing_kwargs,
|
|
42
|
+
)
|
|
43
|
+
elif backend == "altair":
|
|
44
|
+
return make_altair_space(
|
|
45
|
+
agent_portrayal,
|
|
46
|
+
propertylayer_portrayal,
|
|
47
|
+
post_process,
|
|
48
|
+
**space_drawing_kwargs,
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"unknown backend {backend}, must be one of matplotlib, altair"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def make_plot_component(
|
|
57
|
+
measure: str | dict[str, str] | list[str] | tuple[str],
|
|
58
|
+
post_process: Callable | None = None,
|
|
59
|
+
backend: str = "matplotlib",
|
|
60
|
+
**plot_drawing_kwargs,
|
|
61
|
+
):
|
|
62
|
+
"""Create a plotting function for a specified measure using the specified backend.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
measure (str | dict[str, str] | list[str] | tuple[str]): Measure(s) to plot.
|
|
66
|
+
post_process: a user-specified callable to do post-processing called with the Axes instance.
|
|
67
|
+
backend: the backend to use {"matplotlib", "altair"}
|
|
68
|
+
plot_drawing_kwargs: additional keyword arguments to pass onto the backend specific function for making a plotting component
|
|
69
|
+
|
|
70
|
+
Notes:
|
|
71
|
+
altair plotting backend is not yet implemented and planned for mesa 3.1.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
function: A function that creates a plot component
|
|
75
|
+
"""
|
|
76
|
+
if backend == "matplotlib":
|
|
77
|
+
return make_mpl_plot_component(measure, post_process, **plot_drawing_kwargs)
|
|
78
|
+
elif backend == "altair":
|
|
79
|
+
raise NotImplementedError("altair line plots are not yet implemented")
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"unknown backend {backend}, must be one of matplotlib, altair"
|
|
83
|
+
)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Altair based solara components for visualization mesa spaces."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
import solara
|
|
7
|
+
|
|
8
|
+
with contextlib.suppress(ImportError):
|
|
9
|
+
import altair as alt
|
|
10
|
+
|
|
11
|
+
from mesa.experimental.cell_space import DiscreteSpace, Grid
|
|
12
|
+
from mesa.space import ContinuousSpace, _Grid
|
|
13
|
+
from mesa.visualization.utils import update_counter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def make_space_altair(*args, **kwargs): # noqa: D103
|
|
17
|
+
warnings.warn(
|
|
18
|
+
"make_space_altair has been renamed to make_altair_space",
|
|
19
|
+
DeprecationWarning,
|
|
20
|
+
stacklevel=2,
|
|
21
|
+
)
|
|
22
|
+
return make_altair_space(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def make_altair_space(
|
|
26
|
+
agent_portrayal, propertylayer_portrayal, post_process, **space_drawing_kwargs
|
|
27
|
+
):
|
|
28
|
+
"""Create an Altair-based space visualization component.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
agent_portrayal: Function to portray agents.
|
|
32
|
+
propertylayer_portrayal: not yet implemented
|
|
33
|
+
post_process :not yet implemented
|
|
34
|
+
space_drawing_kwargs : not yet implemented
|
|
35
|
+
|
|
36
|
+
``agent_portrayal`` is called with an agent and should return a dict. Valid fields in this dict are "color",
|
|
37
|
+
"size", "marker", and "zorder". Other field are ignored and will result in a user warning.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
function: A function that creates a SpaceMatplotlib component
|
|
42
|
+
"""
|
|
43
|
+
if agent_portrayal is None:
|
|
44
|
+
|
|
45
|
+
def agent_portrayal(a):
|
|
46
|
+
return {"id": a.unique_id}
|
|
47
|
+
|
|
48
|
+
def MakeSpaceAltair(model):
|
|
49
|
+
return SpaceAltair(model, agent_portrayal)
|
|
50
|
+
|
|
51
|
+
return MakeSpaceAltair
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@solara.component
|
|
55
|
+
def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
|
|
56
|
+
"""Create an Altair-based space visualization component.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
a solara FigureAltair instance
|
|
60
|
+
"""
|
|
61
|
+
update_counter.get()
|
|
62
|
+
space = getattr(model, "grid", None)
|
|
63
|
+
if space is None:
|
|
64
|
+
# Sometimes the space is defined as model.space instead of model.grid
|
|
65
|
+
space = model.space
|
|
66
|
+
|
|
67
|
+
chart = _draw_grid(space, agent_portrayal)
|
|
68
|
+
solara.FigureAltair(chart)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_agent_data_old__discrete_space(space, agent_portrayal):
|
|
72
|
+
"""Format agent portrayal data for old-style discrete spaces.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
space: the mesa.space._Grid instance
|
|
76
|
+
agent_portrayal: the agent portrayal callable
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
list of dicts
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
all_agent_data = []
|
|
83
|
+
for content, (x, y) in space.coord_iter():
|
|
84
|
+
if not content:
|
|
85
|
+
continue
|
|
86
|
+
if not hasattr(content, "__iter__"):
|
|
87
|
+
# Is a single grid
|
|
88
|
+
content = [content] # noqa: PLW2901
|
|
89
|
+
for agent in content:
|
|
90
|
+
# use all data from agent portrayal, and add x,y coordinates
|
|
91
|
+
agent_data = agent_portrayal(agent)
|
|
92
|
+
agent_data["x"] = x
|
|
93
|
+
agent_data["y"] = y
|
|
94
|
+
all_agent_data.append(agent_data)
|
|
95
|
+
return all_agent_data
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _get_agent_data_new_discrete_space(space: DiscreteSpace, agent_portrayal):
|
|
99
|
+
"""Format agent portrayal data for new-style discrete spaces.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
space: the mesa.experiment.cell_space.Grid instance
|
|
103
|
+
agent_portrayal: the agent portrayal callable
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
list of dicts
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
all_agent_data = []
|
|
110
|
+
|
|
111
|
+
for cell in space.all_cells:
|
|
112
|
+
for agent in cell.agents:
|
|
113
|
+
agent_data = agent_portrayal(agent)
|
|
114
|
+
agent_data["x"] = cell.coordinate[0]
|
|
115
|
+
agent_data["y"] = cell.coordinate[1]
|
|
116
|
+
all_agent_data.append(agent_data)
|
|
117
|
+
return all_agent_data
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _get_agent_data_continuous_space(space: ContinuousSpace, agent_portrayal):
|
|
121
|
+
"""Format agent portrayal data for continuous space.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
space: the ContinuousSpace instance
|
|
125
|
+
agent_portrayal: the agent portrayal callable
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
list of dicts
|
|
129
|
+
"""
|
|
130
|
+
all_agent_data = []
|
|
131
|
+
for agent in space._agent_to_index:
|
|
132
|
+
agent_data = agent_portrayal(agent)
|
|
133
|
+
agent_data["x"] = agent.pos[0]
|
|
134
|
+
agent_data["y"] = agent.pos[1]
|
|
135
|
+
all_agent_data.append(agent_data)
|
|
136
|
+
return all_agent_data
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _draw_grid(space, agent_portrayal):
|
|
140
|
+
match space:
|
|
141
|
+
case Grid():
|
|
142
|
+
all_agent_data = _get_agent_data_new_discrete_space(space, agent_portrayal)
|
|
143
|
+
case _Grid():
|
|
144
|
+
all_agent_data = _get_agent_data_old__discrete_space(space, agent_portrayal)
|
|
145
|
+
case ContinuousSpace():
|
|
146
|
+
all_agent_data = _get_agent_data_continuous_space(space, agent_portrayal)
|
|
147
|
+
case _:
|
|
148
|
+
raise NotImplementedError(
|
|
149
|
+
f"visualizing {type(space)} is currently not supported through altair"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
invalid_tooltips = ["color", "size", "x", "y"]
|
|
153
|
+
|
|
154
|
+
x_y_type = "ordinal" if not isinstance(space, ContinuousSpace) else "nominal"
|
|
155
|
+
|
|
156
|
+
encoding_dict = {
|
|
157
|
+
# no x-axis label
|
|
158
|
+
"x": alt.X("x", axis=None, type=x_y_type),
|
|
159
|
+
# no y-axis label
|
|
160
|
+
"y": alt.Y("y", axis=None, type=x_y_type),
|
|
161
|
+
"tooltip": [
|
|
162
|
+
alt.Tooltip(key, type=alt.utils.infer_vegalite_type([value]))
|
|
163
|
+
for key, value in all_agent_data[0].items()
|
|
164
|
+
if key not in invalid_tooltips
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
has_color = "color" in all_agent_data[0]
|
|
168
|
+
if has_color:
|
|
169
|
+
encoding_dict["color"] = alt.Color("color", type="nominal")
|
|
170
|
+
has_size = "size" in all_agent_data[0]
|
|
171
|
+
if has_size:
|
|
172
|
+
encoding_dict["size"] = alt.Size("size", type="quantitative")
|
|
173
|
+
|
|
174
|
+
chart = (
|
|
175
|
+
alt.Chart(
|
|
176
|
+
alt.Data(values=all_agent_data), encoding=alt.Encoding(**encoding_dict)
|
|
177
|
+
)
|
|
178
|
+
.mark_point(filled=True)
|
|
179
|
+
.properties(width=280, height=280)
|
|
180
|
+
# .configure_view(strokeOpacity=0) # hide grid/chart lines
|
|
181
|
+
)
|
|
182
|
+
# This is the default value for the marker size, which auto-scales
|
|
183
|
+
# according to the grid area.
|
|
184
|
+
if not has_size:
|
|
185
|
+
length = min(space.width, space.height)
|
|
186
|
+
chart = chart.mark_point(size=30000 / length**2, filled=True)
|
|
187
|
+
|
|
188
|
+
return chart
|