Mesa 3.0.0__py3-none-any.whl → 3.0.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of Mesa might be problematic. Click here for more details.

Files changed (104) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +114 -406
  3. mesa/batchrunner.py +27 -54
  4. mesa/cookiecutter-mesa/cookiecutter.json +8 -0
  5. mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
  6. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
  7. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  8. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
  9. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
  10. mesa/datacollection.py +29 -140
  11. mesa/experimental/__init__.py +1 -11
  12. mesa/experimental/cell_space/__init__.py +1 -16
  13. mesa/experimental/cell_space/cell.py +23 -93
  14. mesa/experimental/cell_space/cell_agent.py +21 -117
  15. mesa/experimental/cell_space/cell_collection.py +17 -54
  16. mesa/experimental/cell_space/discrete_space.py +8 -92
  17. mesa/experimental/cell_space/grid.py +8 -32
  18. mesa/experimental/cell_space/network.py +7 -12
  19. mesa/experimental/devs/__init__.py +0 -2
  20. mesa/experimental/devs/eventlist.py +14 -52
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
  22. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  23. mesa/experimental/devs/simulator.py +15 -55
  24. mesa/main.py +63 -0
  25. mesa/model.py +83 -211
  26. mesa/space.py +149 -215
  27. mesa/time.py +77 -62
  28. mesa/{experimental → visualization}/UserParam.py +6 -17
  29. mesa/visualization/__init__.py +2 -25
  30. mesa/{experimental → visualization}/components/altair.py +0 -10
  31. mesa/visualization/components/matplotlib.py +134 -0
  32. mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
  33. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
  34. mesa-3.0.0a0.dist-info/RECORD +38 -0
  35. mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
  36. mesa/examples/README.md +0 -37
  37. mesa/examples/__init__.py +0 -21
  38. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
  39. mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
  40. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  41. mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
  42. mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
  43. mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
  44. mesa/examples/advanced/pd_grid/Readme.md +0 -43
  45. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  46. mesa/examples/advanced/pd_grid/agents.py +0 -50
  47. mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
  48. mesa/examples/advanced/pd_grid/app.py +0 -54
  49. mesa/examples/advanced/pd_grid/model.py +0 -71
  50. mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
  51. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  52. mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
  53. mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
  54. mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
  55. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
  56. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  57. mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
  58. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  59. mesa/examples/advanced/wolf_sheep/agents.py +0 -102
  60. mesa/examples/advanced/wolf_sheep/app.py +0 -84
  61. mesa/examples/advanced/wolf_sheep/model.py +0 -137
  62. mesa/examples/basic/__init__.py +0 -0
  63. mesa/examples/basic/boid_flockers/Readme.md +0 -22
  64. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  65. mesa/examples/basic/boid_flockers/agents.py +0 -71
  66. mesa/examples/basic/boid_flockers/app.py +0 -58
  67. mesa/examples/basic/boid_flockers/model.py +0 -69
  68. mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
  69. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  70. mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
  71. mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
  72. mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
  73. mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
  74. mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
  75. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  76. mesa/examples/basic/conways_game_of_life/agents.py +0 -47
  77. mesa/examples/basic/conways_game_of_life/app.py +0 -51
  78. mesa/examples/basic/conways_game_of_life/model.py +0 -31
  79. mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
  80. mesa/examples/basic/schelling/Readme.md +0 -40
  81. mesa/examples/basic/schelling/__init__.py +0 -0
  82. mesa/examples/basic/schelling/agents.py +0 -26
  83. mesa/examples/basic/schelling/analysis.ipynb +0 -205
  84. mesa/examples/basic/schelling/app.py +0 -42
  85. mesa/examples/basic/schelling/model.py +0 -59
  86. mesa/examples/basic/virus_on_network/Readme.md +0 -61
  87. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  88. mesa/examples/basic/virus_on_network/agents.py +0 -69
  89. mesa/examples/basic/virus_on_network/app.py +0 -114
  90. mesa/examples/basic/virus_on_network/model.py +0 -96
  91. mesa/experimental/cell_space/voronoi.py +0 -257
  92. mesa/experimental/components/matplotlib.py +0 -242
  93. mesa/visualization/components/__init__.py +0 -83
  94. mesa/visualization/components/altair_components.py +0 -188
  95. mesa/visualization/components/matplotlib_components.py +0 -175
  96. mesa/visualization/mpl_space_drawing.py +0 -593
  97. mesa/visualization/solara_viz.py +0 -458
  98. mesa/visualization/user_param.py +0 -69
  99. mesa/visualization/utils.py +0 -9
  100. mesa-3.0.0.dist-info/RECORD +0 -95
  101. mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
  102. /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
  103. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
  104. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
mesa/time.py CHANGED
@@ -1,9 +1,6 @@
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
1
+ """
2
+ Mesa Time Module
3
+ ================
7
4
 
8
5
  Objects for handling the time component of a model. In particular, this module
9
6
  contains Schedulers, which handle agent activation. A Scheduler is an object
@@ -42,16 +39,23 @@ TimeT = float | int
42
39
 
43
40
 
44
41
  class BaseScheduler:
45
- """A simple scheduler that activates agents one at a time, in the order they were added.
42
+ """
43
+ A simple scheduler that activates agents one at a time, in the order they were added.
46
44
 
47
45
  This scheduler is designed to replicate the behavior of the scheduler in MASON, a multi-agent simulation toolkit.
48
46
  It assumes that each agent added has a `step` method which takes no arguments and executes the agent's actions.
49
47
 
50
48
  Attributes:
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
-
49
+ - model (Model): The model instance associated with the scheduler.
50
+ - steps (int): The number of steps the scheduler has taken.
51
+ - time (TimeT): The current time in the simulation. Can be an integer or a float.
52
+
53
+ Methods:
54
+ - add: Adds an agent to the scheduler.
55
+ - remove: Removes an agent from the scheduler.
56
+ - step: Executes a step, which involves activating each agent once.
57
+ - get_agent_count: Returns the number of agents in the scheduler.
58
+ - agents (property): Returns a list of all agent instances.
55
59
  """
56
60
 
57
61
  def __init__(self, model: Model, agents: Iterable[Agent] | None = None) -> None:
@@ -62,22 +66,16 @@ class BaseScheduler:
62
66
  agents (Iterable[Agent], None, optional): An iterable of agents who are controlled by the schedule
63
67
 
64
68
  """
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
-
73
69
  self.model = model
74
70
  self.steps = 0
75
71
  self.time: TimeT = 0
72
+ self._original_step = self.step
73
+ self.step = self._wrapped_step
76
74
 
77
75
  if agents is None:
78
76
  agents = []
79
77
 
80
- self._agents: AgentSet = AgentSet(agents, model.random)
78
+ self._agents: AgentSet = AgentSet(agents, model)
81
79
 
82
80
  self._remove_warning_given = False
83
81
  self._agents_key_warning_given = False
@@ -86,8 +84,10 @@ class BaseScheduler:
86
84
  """Add an Agent object to the schedule.
87
85
 
88
86
  Args:
89
- agent (Agent): An Agent to be added to the schedule.
87
+ agent: An Agent to be added to the schedule. NOTE: The agent must
88
+ have a step() method.
90
89
  """
90
+
91
91
  if agent not in self._agents:
92
92
  self._agents.add(agent)
93
93
  else:
@@ -96,13 +96,12 @@ class BaseScheduler:
96
96
  def remove(self, agent: Agent) -> None:
97
97
  """Remove all instances of a given agent from the schedule.
98
98
 
99
- Args:
100
- agent: An `Agent` instance.
101
-
102
99
  Note:
103
100
  It is only necessary to explicitly remove agents from the schedule if
104
101
  the agent is not removed from the model.
105
102
 
103
+ Args:
104
+ agent: An agent object.
106
105
  """
107
106
  self._agents.remove(agent)
108
107
 
@@ -114,18 +113,21 @@ class BaseScheduler:
114
113
  self.steps += 1
115
114
  self.time += 1
116
115
 
116
+ def _wrapped_step(self):
117
+ """Wrapper for the step method to include time and step updating."""
118
+ self._original_step()
119
+ self.model._advance_time()
120
+
117
121
  def get_agent_count(self) -> int:
118
122
  """Returns the current number of agents in the queue."""
119
123
  return len(self._agents)
120
124
 
121
125
  @property
122
126
  def agents(self) -> AgentSet:
123
- """Return agents in the scheduler."""
124
127
  # a bit dirty, but returns a copy of the internal agent set
125
128
  return self._agents.select()
126
129
 
127
130
  def get_agent_keys(self, shuffle: bool = False) -> list[int]:
128
- """Deprecated."""
129
131
  # To be able to remove and/or add agents during stepping
130
132
  # it's necessary to cast the keys view to a list.
131
133
 
@@ -143,21 +145,14 @@ class BaseScheduler:
143
145
  return agent_keys
144
146
 
145
147
  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
- """
154
148
  if shuffle:
155
149
  self._agents.shuffle(inplace=True)
156
150
  self._agents.do(method)
157
151
 
158
152
 
159
153
  class RandomActivation(BaseScheduler):
160
- """A scheduler that activates each agent once per step, in a random order, with the order reshuffled each step.
154
+ """
155
+ A scheduler that activates each agent once per step, in a random order, with the order reshuffled each step.
161
156
 
162
157
  This scheduler is equivalent to the NetLogo 'ask agents...' behavior and is a common default for ABMs.
163
158
  It assumes that all agents have a `step` method.
@@ -167,17 +162,23 @@ class RandomActivation(BaseScheduler):
167
162
 
168
163
  Inherits all attributes and methods from BaseScheduler.
169
164
 
165
+ Methods:
166
+ - step: Executes a step, activating each agent in a random order.
170
167
  """
171
168
 
172
169
  def step(self) -> None:
173
- """Executes the step of all agents, one at a time, in random order."""
170
+ """Executes the step of all agents, one at a time, in
171
+ random order.
172
+
173
+ """
174
174
  self.do_each("step", shuffle=True)
175
175
  self.steps += 1
176
176
  self.time += 1
177
177
 
178
178
 
179
179
  class SimultaneousActivation(BaseScheduler):
180
- """A scheduler that simulates the simultaneous activation of all agents.
180
+ """
181
+ A scheduler that simulates the simultaneous activation of all agents.
181
182
 
182
183
  This scheduler is unique in that it requires agents to have both `step` and `advance` methods.
183
184
  - The `step` method is for activating the agent and staging any changes without applying them immediately.
@@ -188,6 +189,8 @@ class SimultaneousActivation(BaseScheduler):
188
189
 
189
190
  Inherits all attributes and methods from BaseScheduler.
190
191
 
192
+ Methods:
193
+ - step: Executes a step for all agents, first calling `step` then `advance` on each.
191
194
  """
192
195
 
193
196
  def step(self) -> None:
@@ -202,9 +205,9 @@ class SimultaneousActivation(BaseScheduler):
202
205
 
203
206
 
204
207
  class StagedActivation(BaseScheduler):
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.
208
+ """
209
+ A scheduler allowing agent activation to be divided into several stages, with all agents executing one stage
210
+ before moving on to the next. This class is a generalization of SimultaneousActivation.
208
211
 
209
212
  This scheduler is useful for complex models where actions need to be broken down into distinct phases
210
213
  for each agent in each time step. Agents must implement methods for each defined stage.
@@ -219,6 +222,8 @@ class StagedActivation(BaseScheduler):
219
222
  - shuffle (bool): Determines whether to shuffle the order of agents each step.
220
223
  - shuffle_between_stages (bool): Determines whether to shuffle agents between each stage.
221
224
 
225
+ Methods:
226
+ - step: Executes all the stages for all agents in the defined order.
222
227
  """
223
228
 
224
229
  def __init__(
@@ -263,7 +268,8 @@ class StagedActivation(BaseScheduler):
263
268
 
264
269
 
265
270
  class RandomActivationByType(BaseScheduler):
266
- """A scheduler that activates each type of agent once per step, in random order, with the order reshuffled every step.
271
+ """
272
+ A scheduler that activates each type of agent once per step, in random order, with the order reshuffled every step.
267
273
 
268
274
  This scheduler is useful for models with multiple types of agents, ensuring that each type is treated
269
275
  equitably in terms of activation order. The randomness in activation order helps in reducing biases
@@ -279,10 +285,14 @@ class RandomActivationByType(BaseScheduler):
279
285
  Attributes:
280
286
  - agents_by_type (defaultdict): A dictionary mapping agent types to dictionaries of agents.
281
287
 
288
+ Methods:
289
+ - step: Executes the step of each agent type in a random order.
290
+ - step_type: Activates all agents of a given type.
291
+ - get_type_count: Returns the count of agents of a specific type.
282
292
  """
283
293
 
284
294
  @property
285
- def agents_by_type(self): # noqa: D102
295
+ def agents_by_type(self):
286
296
  warnings.warn(
287
297
  "Because of the shift to using AgentSet, in the future this attribute will return a dict with"
288
298
  "type as key as AgentSet as value. Future behavior is available via RandomActivationByType._agents_by_type",
@@ -297,13 +307,14 @@ class RandomActivationByType(BaseScheduler):
297
307
  return agentsbytype
298
308
 
299
309
  def __init__(self, model: Model, agents: Iterable[Agent] | None = None) -> None:
300
- """Initialize RandomActivationByType instance.
310
+ super().__init__(model, agents)
311
+ """
301
312
 
302
313
  Args:
303
314
  model (Model): The model to which the schedule belongs
304
315
  agents (Iterable[Agent], None, optional): An iterable of agents who are controlled by the schedule
305
316
  """
306
- super().__init__(model, agents)
317
+
307
318
  # can't be a defaultdict because we need to pass model to AgentSet
308
319
  self._agents_by_type: [type, AgentSet] = {}
309
320
 
@@ -312,12 +323,11 @@ class RandomActivationByType(BaseScheduler):
312
323
  try:
313
324
  self._agents_by_type[type(agent)].add(agent)
314
325
  except KeyError:
315
- self._agents_by_type[type(agent)] = AgentSet(
316
- [agent], self.model.random
317
- )
326
+ self._agents_by_type[type(agent)] = AgentSet([agent], self.model)
318
327
 
319
328
  def add(self, agent: Agent) -> None:
320
- """Add an Agent object to the schedule.
329
+ """
330
+ Add an Agent object to the schedule
321
331
 
322
332
  Args:
323
333
  agent: An Agent to be added to the schedule.
@@ -327,24 +337,24 @@ class RandomActivationByType(BaseScheduler):
327
337
  try:
328
338
  self._agents_by_type[type(agent)].add(agent)
329
339
  except KeyError:
330
- self._agents_by_type[type(agent)] = AgentSet([agent], self.model.random)
340
+ self._agents_by_type[type(agent)] = AgentSet([agent], self.model)
331
341
 
332
342
  def remove(self, agent: Agent) -> None:
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
-
343
+ """
344
+ Remove all instances of a given agent from the schedule.
338
345
  """
339
346
  super().remove(agent)
340
347
  self._agents_by_type[type(agent)].remove(agent)
341
348
 
342
349
  def step(self, shuffle_types: bool = True, shuffle_agents: bool = True) -> None:
343
- """Executes the step of each agent type, one at a time, in random order.
350
+ """
351
+ Executes the step of each agent type, one at a time, in random order.
344
352
 
345
353
  Args:
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.
354
+ shuffle_types: If True, the order of execution of each types is
355
+ shuffled.
356
+ shuffle_agents: If True, the order of execution of each agents in a
357
+ type group is shuffled.
348
358
  """
349
359
  # To be able to remove and/or add agents during stepping
350
360
  # it's necessary to cast the keys view to a list.
@@ -357,11 +367,12 @@ class RandomActivationByType(BaseScheduler):
357
367
  self.time += 1
358
368
 
359
369
  def step_type(self, agenttype: type[Agent], shuffle_agents: bool = True) -> None:
360
- """Shuffle order and run all agents of a given type.
370
+ """
371
+ Shuffle order and run all agents of a given type.
372
+ This method is equivalent to the NetLogo 'ask [breed]...'.
361
373
 
362
374
  Args:
363
375
  agenttype: Class object of the type to run.
364
- shuffle_agents: If True, shuffle agents
365
376
  """
366
377
  agents = self._agents_by_type[agenttype]
367
378
 
@@ -370,15 +381,19 @@ class RandomActivationByType(BaseScheduler):
370
381
  agents.do("step")
371
382
 
372
383
  def get_type_count(self, agenttype: type[Agent]) -> int:
373
- """Returns the current number of agents of certain type in the queue."""
384
+ """
385
+ Returns the current number of agents of certain type in the queue.
386
+ """
374
387
  return len(self._agents_by_type[agenttype])
375
388
 
376
389
 
377
390
  class DiscreteEventScheduler(BaseScheduler):
378
- """This class has been deprecated and replaced by the functionality provided by experimental.devs."""
391
+ """
392
+ This class has been deprecated and replaced by the functionality provided by experimental.devs
393
+ """
379
394
 
380
395
  def __init__(self, model: Model, time_step: TimeT = 1) -> None:
381
- """Initialize DiscreteEventScheduler.
396
+ """
382
397
 
383
398
  Args:
384
399
  model (Model): The model to which the schedule belongs
@@ -1,10 +1,7 @@
1
- """helper classes."""
2
-
3
-
4
- class UserParam: # noqa: D101
1
+ class UserParam:
5
2
  _ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
6
3
 
7
- def maybe_raise_error(self, param_type, valid): # noqa: D102
4
+ def maybe_raise_error(self, param_type, valid):
8
5
  if valid:
9
6
  return
10
7
  msg = self._ERROR_MESSAGE.format(param_type, self.label)
@@ -12,9 +9,11 @@ class UserParam: # noqa: D101
12
9
 
13
10
 
14
11
  class Slider(UserParam):
15
- """A number-based slider input with settable increment.
12
+ """
13
+ A number-based slider input with settable increment.
16
14
 
17
15
  Example:
16
+
18
17
  slider_option = Slider("My Slider", value=123, min=10, max=200, step=0.1)
19
18
 
20
19
  Args:
@@ -35,16 +34,6 @@ class Slider(UserParam):
35
34
  step=1,
36
35
  dtype=None,
37
36
  ):
38
- """Slider class.
39
-
40
- Args:
41
- label: The displayed label in the UI
42
- value: The initial value of the slider
43
- min: The minimum possible value of the slider
44
- max: The maximum possible value of the slider
45
- step: The step between min and max for a range of possible values
46
- dtype: either int or float
47
- """
48
37
  self.label = label
49
38
  self.value = value
50
39
  self.min = min
@@ -63,5 +52,5 @@ class Slider(UserParam):
63
52
  def _check_values_are_float(self, value, min, max, step):
64
53
  return any(isinstance(n, float) for n in (value, min, max, step))
65
54
 
66
- def get(self, attr): # noqa: D102
55
+ def get(self, attr):
67
56
  return getattr(self, attr)
@@ -1,26 +1,3 @@
1
- """Solara based visualization for Mesa models.
1
+ from .jupyter_viz import JupyterViz, Slider, make_text
2
2
 
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
- ]
3
+ __all__ = ["JupyterViz", "make_text", "Slider"]
@@ -1,5 +1,3 @@
1
- """Altair components."""
2
-
3
1
  import contextlib
4
2
 
5
3
  import solara
@@ -10,14 +8,6 @@ with contextlib.suppress(ImportError):
10
8
 
11
9
  @solara.component
12
10
  def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
13
- """A component that renders a Space using Altair.
14
-
15
- Args:
16
- model: a model instance
17
- agent_portrayal: agent portray specification
18
- dependencies: optional list of dependencies (currently not used)
19
-
20
- """
21
11
  space = getattr(model, "grid", None)
22
12
  if space is None:
23
13
  # Sometimes the space is defined as model.space instead of model.grid
@@ -0,0 +1,134 @@
1
+ import networkx as nx
2
+ import solara
3
+ from matplotlib.figure import Figure
4
+ from matplotlib.ticker import MaxNLocator
5
+
6
+ import mesa
7
+
8
+
9
+ @solara.component
10
+ def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
11
+ space_fig = Figure()
12
+ space_ax = space_fig.subplots()
13
+ space = getattr(model, "grid", None)
14
+ if space is None:
15
+ # Sometimes the space is defined as model.space instead of model.grid
16
+ space = model.space
17
+ if isinstance(space, mesa.space.NetworkGrid):
18
+ _draw_network_grid(space, space_ax, agent_portrayal)
19
+ elif isinstance(space, mesa.space.ContinuousSpace):
20
+ _draw_continuous_space(space, space_ax, agent_portrayal)
21
+ else:
22
+ _draw_grid(space, space_ax, agent_portrayal)
23
+ solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
24
+
25
+
26
+ def _draw_grid(space, space_ax, agent_portrayal):
27
+ def portray(g):
28
+ x = []
29
+ y = []
30
+ s = [] # size
31
+ c = [] # color
32
+ for i in range(g.width):
33
+ for j in range(g.height):
34
+ content = g._grid[i][j]
35
+ if not content:
36
+ continue
37
+ if not hasattr(content, "__iter__"):
38
+ # Is a single grid
39
+ content = [content]
40
+ for agent in content:
41
+ data = agent_portrayal(agent)
42
+ x.append(i)
43
+ y.append(j)
44
+ if "size" in data:
45
+ s.append(data["size"])
46
+ if "color" in data:
47
+ c.append(data["color"])
48
+ out = {"x": x, "y": y}
49
+ # This is the default value for the marker size, which auto-scales
50
+ # according to the grid area.
51
+ out["s"] = (180 / min(g.width, g.height)) ** 2
52
+ if len(s) > 0:
53
+ out["s"] = s
54
+ if len(c) > 0:
55
+ out["c"] = c
56
+ return out
57
+
58
+ space_ax.set_xlim(-1, space.width)
59
+ space_ax.set_ylim(-1, space.height)
60
+ space_ax.scatter(**portray(space))
61
+
62
+
63
+ def _draw_network_grid(space, space_ax, agent_portrayal):
64
+ graph = space.G
65
+ pos = nx.spring_layout(graph, seed=0)
66
+ nx.draw(
67
+ graph,
68
+ ax=space_ax,
69
+ pos=pos,
70
+ **agent_portrayal(graph),
71
+ )
72
+
73
+
74
+ def _draw_continuous_space(space, space_ax, agent_portrayal):
75
+ def portray(space):
76
+ x = []
77
+ y = []
78
+ s = [] # size
79
+ c = [] # color
80
+ for agent in space._agent_to_index:
81
+ data = agent_portrayal(agent)
82
+ _x, _y = agent.pos
83
+ x.append(_x)
84
+ y.append(_y)
85
+ if "size" in data:
86
+ s.append(data["size"])
87
+ if "color" in data:
88
+ c.append(data["color"])
89
+ out = {"x": x, "y": y}
90
+ if len(s) > 0:
91
+ out["s"] = s
92
+ if len(c) > 0:
93
+ out["c"] = c
94
+ return out
95
+
96
+ # Determine border style based on space.torus
97
+ border_style = "solid" if not space.torus else (0, (5, 10))
98
+
99
+ # Set the border of the plot
100
+ for spine in space_ax.spines.values():
101
+ spine.set_linewidth(1.5)
102
+ spine.set_color("black")
103
+ spine.set_linestyle(border_style)
104
+
105
+ width = space.x_max - space.x_min
106
+ x_padding = width / 20
107
+ height = space.y_max - space.y_min
108
+ y_padding = height / 20
109
+ space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
110
+ space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
111
+
112
+ # Portray and scatter the agents in the space
113
+ space_ax.scatter(**portray(space))
114
+
115
+
116
+ @solara.component
117
+ def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
118
+ fig = Figure()
119
+ ax = fig.subplots()
120
+ df = model.datacollector.get_model_vars_dataframe()
121
+ if isinstance(measure, str):
122
+ ax.plot(df.loc[:, measure])
123
+ ax.set_ylabel(measure)
124
+ elif isinstance(measure, dict):
125
+ for m, color in measure.items():
126
+ ax.plot(df.loc[:, m], label=m, color=color)
127
+ fig.legend()
128
+ elif isinstance(measure, list | tuple):
129
+ for m in measure:
130
+ ax.plot(df.loc[:, m], label=m)
131
+ fig.legend()
132
+ # Set integer x axis
133
+ ax.xaxis.set_major_locator(MaxNLocator(integer=True))
134
+ solara.FigureMatplotlib(fig, dependencies=dependencies)