Mesa 3.2.0.dev0__py3-none-any.whl → 3.3.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.

Files changed (58) hide show
  1. mesa/__init__.py +1 -1
  2. mesa/agent.py +9 -7
  3. mesa/datacollection.py +1 -1
  4. mesa/examples/README.md +1 -1
  5. mesa/examples/__init__.py +2 -0
  6. mesa/examples/advanced/alliance_formation/Readme.md +50 -0
  7. mesa/examples/advanced/alliance_formation/__init__ .py +0 -0
  8. mesa/examples/advanced/alliance_formation/agents.py +20 -0
  9. mesa/examples/advanced/alliance_formation/app.py +71 -0
  10. mesa/examples/advanced/alliance_formation/model.py +184 -0
  11. mesa/examples/advanced/epstein_civil_violence/app.py +11 -11
  12. mesa/examples/advanced/pd_grid/Readme.md +4 -6
  13. mesa/examples/advanced/pd_grid/app.py +10 -11
  14. mesa/examples/advanced/sugarscape_g1mt/Readme.md +4 -5
  15. mesa/examples/advanced/sugarscape_g1mt/app.py +34 -16
  16. mesa/examples/advanced/wolf_sheep/Readme.md +2 -17
  17. mesa/examples/advanced/wolf_sheep/app.py +21 -18
  18. mesa/examples/basic/boid_flockers/Readme.md +6 -1
  19. mesa/examples/basic/boid_flockers/app.py +15 -11
  20. mesa/examples/basic/boltzmann_wealth_model/Readme.md +2 -12
  21. mesa/examples/basic/boltzmann_wealth_model/app.py +39 -32
  22. mesa/examples/basic/conways_game_of_life/Readme.md +1 -9
  23. mesa/examples/basic/conways_game_of_life/app.py +13 -16
  24. mesa/examples/basic/schelling/Readme.md +2 -10
  25. mesa/examples/basic/schelling/agents.py +9 -3
  26. mesa/examples/basic/schelling/app.py +50 -3
  27. mesa/examples/basic/schelling/model.py +2 -0
  28. mesa/examples/basic/schelling/resources/blue_happy.png +0 -0
  29. mesa/examples/basic/schelling/resources/blue_unhappy.png +0 -0
  30. mesa/examples/basic/schelling/resources/orange_happy.png +0 -0
  31. mesa/examples/basic/schelling/resources/orange_unhappy.png +0 -0
  32. mesa/examples/basic/virus_on_network/Readme.md +0 -4
  33. mesa/examples/basic/virus_on_network/app.py +31 -14
  34. mesa/experimental/__init__.py +2 -2
  35. mesa/experimental/continuous_space/continuous_space.py +1 -1
  36. mesa/experimental/meta_agents/__init__.py +25 -0
  37. mesa/experimental/meta_agents/meta_agent.py +387 -0
  38. mesa/model.py +3 -3
  39. mesa/space.py +4 -1
  40. mesa/visualization/__init__.py +2 -0
  41. mesa/visualization/backends/__init__.py +23 -0
  42. mesa/visualization/backends/abstract_renderer.py +97 -0
  43. mesa/visualization/backends/altair_backend.py +440 -0
  44. mesa/visualization/backends/matplotlib_backend.py +419 -0
  45. mesa/visualization/components/__init__.py +28 -8
  46. mesa/visualization/components/altair_components.py +86 -0
  47. mesa/visualization/components/matplotlib_components.py +4 -2
  48. mesa/visualization/components/portrayal_components.py +120 -0
  49. mesa/visualization/mpl_space_drawing.py +292 -129
  50. mesa/visualization/solara_viz.py +274 -32
  51. mesa/visualization/space_drawers.py +797 -0
  52. mesa/visualization/space_renderer.py +399 -0
  53. {mesa-3.2.0.dev0.dist-info → mesa-3.3.0.dist-info}/METADATA +13 -4
  54. {mesa-3.2.0.dev0.dist-info → mesa-3.3.0.dist-info}/RECORD +57 -40
  55. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  56. {mesa-3.2.0.dev0.dist-info → mesa-3.3.0.dist-info}/WHEEL +0 -0
  57. {mesa-3.2.0.dev0.dist-info → mesa-3.3.0.dist-info}/licenses/LICENSE +0 -0
  58. {mesa-3.2.0.dev0.dist-info → mesa-3.3.0.dist-info}/licenses/NOTICE +0 -0
@@ -117,7 +117,7 @@ class ContinuousSpace:
117
117
  if self._agent_positions.shape[0] <= index:
118
118
  # we are out of space
119
119
  fraction = 0.2 # we add 20% Fixme
120
- n = int(round(fraction * self._n_agents))
120
+ n = round(fraction * self._n_agents, None)
121
121
  self._agent_positions = np.vstack(
122
122
  [
123
123
  self._agent_positions,
@@ -0,0 +1,25 @@
1
+ """This method is for dynamically creating new agents (meta-agents).
2
+
3
+ Meta-agents are defined as agents composed of existing agents.
4
+
5
+ Meta-agents are created dynamically with a pointer to the model, name of the meta-agent,,
6
+ iterable of agents to belong to the new meta-agents, any new functions for the meta-agent,
7
+ any new attributes for the meta-agent, whether to retain sub-agent functions,
8
+ whether to retain sub-agent attributes.
9
+
10
+ Examples of meta-agents:
11
+ - An autonomous car where the subagents are the wheels, sensors,
12
+ battery, computer etc. and the meta-agent is the car itself.
13
+ - A company where the subagents are employees, departments, buildings, etc.
14
+ - A city where the subagents are people, buildings, streets, etc.
15
+
16
+ Currently meta-agents are restricted to one parent agent for each subagent/
17
+ one meta-agent per subagent.
18
+
19
+ Goal is to assess usage and expand functionality.
20
+
21
+ """
22
+
23
+ from .meta_agent import MetaAgent
24
+
25
+ __all__ = ["MetaAgent"]
@@ -0,0 +1,387 @@
1
+ """Implementation of Mesa's meta agent capability.
2
+
3
+ Overview: Complex systems often have multiple levels of components. An
4
+ organization is not one entity, but is made of departments, sub-departments,
5
+ and people. A person is not a single entity, but it is made of micro biomes,
6
+ organs and cells. A city is not a single entity, but it is made of districts,
7
+ neighborhoods, buildings, and people. A forest comprises an ecosystem of
8
+ trees, plants, animals, and microorganisms.
9
+
10
+ This reality is the motivation for meta-agents. It allows users to represent
11
+ these multiple levels, where each level can have agents with constituting_agents.
12
+
13
+ To demonstrate meta-agents capability there are two examples:
14
+ 1 - Alliance formation which shows emergent meta-agent formation in
15
+ advanced examples:
16
+ https://github.com/projectmesa/mesa/tree/main/mesa/examples/advanced/alliance_formation
17
+ 2 - Warehouse model in the Mesa example's repository
18
+ https://github.com/projectmesa/mesa-examples/tree/main/examples/warehouse
19
+
20
+ To accomplish this the MetaAgent module is as follows:
21
+
22
+ This contains four helper functions and a MetaAgent class that can be used to
23
+ create agents that contain other agents as components.
24
+
25
+ Helper methods:
26
+ 1 - find_combinations: Find combinations of agents to create a meta-agent
27
+ constituting_set.
28
+ 2- evaluate_combination: Evaluate combinations of agents by some user based
29
+ criteria to determine if it should be a constituting_set of agents.
30
+ 3- extract_class: Helper function for create_meta-agent. Extracts the types of
31
+ agent being created to create a new instance of that agent type.
32
+ 4- create_meta_agent: Create a new meta-agent class and instantiate
33
+ agents in that class.
34
+
35
+ Meta-Agent class (MetaAgent): An agent that contains other agents
36
+ as components.
37
+
38
+ .
39
+ """
40
+
41
+ import itertools
42
+ from collections.abc import Callable, Iterable
43
+ from types import MethodType
44
+ from typing import Any
45
+
46
+ from mesa.agent import Agent, AgentSet
47
+
48
+
49
+ def evaluate_combination(
50
+ candidate_group: tuple[Agent, ...],
51
+ model,
52
+ evaluation_func: Callable[[AgentSet], float] | None,
53
+ ) -> tuple[AgentSet, float] | None:
54
+ """Evaluate a combination of agents.
55
+
56
+ Args:
57
+ candidate_group (Tuple[Agent, ...]): The group of agents to evaluate.
58
+ model: The model instance.
59
+ evaluation_func (Optional[Callable[[AgentSet], float]]): The function
60
+ to evaluate the group.
61
+
62
+ Returns:
63
+ Optional[Tuple[AgentSet, float]]: The evaluated group and its value,
64
+ or None.
65
+ """
66
+ group_set = AgentSet(candidate_group, random=model.random)
67
+ if evaluation_func:
68
+ value = evaluation_func(group_set)
69
+ return group_set, value
70
+ return None
71
+
72
+
73
+ def find_combinations(
74
+ model,
75
+ group: AgentSet,
76
+ size: int | tuple[int, int] = (2, 5),
77
+ evaluation_func: Callable[[AgentSet], float] | None = None,
78
+ filter_func: Callable[[list[tuple[AgentSet, float]]], list[tuple[AgentSet, float]]]
79
+ | None = None,
80
+ ) -> list[tuple[AgentSet, float]]:
81
+ """Find valuable combinations of agents in this set.
82
+
83
+ Args:
84
+ model: The model instance.
85
+ group (AgentSet): The set of agents to find combinations in.
86
+ size (Union[int, Tuple[int, int]], optional): The size or range of
87
+ sizes for combinations. Defaults to (2, 5).
88
+ evaluation_func (Optional[Callable[[AgentSet], float]], optional): The
89
+ function to evaluate combinations. Defaults to None.
90
+ filter_func (Optional[Callable[[List[Tuple[AgentSet, float]]]): Allows
91
+ the user to specify how agents are filtered to form groups.
92
+ Defaults to None.
93
+ List[Tuple[AgentSet, float]]]], optional): The function to filter
94
+ combinations. Defaults to None.
95
+
96
+ Returns:
97
+ List[Tuple[AgentSet, float]]: The list of valuable combinations, in
98
+ a tuple first agentset of valuable combination and then the value of
99
+ the combination.
100
+ """
101
+ combinations = []
102
+ # Allow one size or range of sizes to be passed
103
+ size_range = (size, size + 1) if isinstance(size, int) else size
104
+
105
+ for candidate_group in itertools.chain.from_iterable(
106
+ itertools.combinations(group, size) for size in range(*size_range)
107
+ ):
108
+ group_set, result = evaluate_combination(
109
+ candidate_group, model, evaluation_func
110
+ )
111
+ if result:
112
+ combinations.append((group_set, result))
113
+
114
+ if len(combinations) > 0 and filter_func:
115
+ filtered_combinations = filter_func(combinations)
116
+ return filtered_combinations
117
+
118
+ return combinations
119
+
120
+
121
+ def extract_class(agents_by_type: dict, new_agent_class: object) -> type[Agent] | None:
122
+ """Helper function for create_meta_agents extracts the types of agents.
123
+
124
+ Args:
125
+ agents_by_type (dict): The dictionary of agents by type.
126
+ new_agent_class (str): The name of the agent class to be created
127
+
128
+ Returns:
129
+ type(Agent) if agent type exists
130
+ None otherwise
131
+ """
132
+ agent_type_names = {}
133
+ for agent in agents_by_type:
134
+ agent_type_names[agent.__name__] = agent
135
+
136
+ if new_agent_class in agent_type_names:
137
+ return type(agents_by_type[agent_type_names[new_agent_class]][0])
138
+ return None
139
+
140
+
141
+ def create_meta_agent(
142
+ model: Any,
143
+ new_agent_class: str,
144
+ agents: Iterable[Any],
145
+ mesa_agent_type: type[Agent] | None,
146
+ meta_attributes: dict[str, Any] | None = None,
147
+ meta_methods: dict[str, Callable] | None = None,
148
+ assume_constituting_agent_methods: bool = False,
149
+ assume_constituting_agent_attributes: bool = False,
150
+ ) -> Any | None:
151
+ """Create a new meta-agent class and instantiate agents.
152
+
153
+ Parameters:
154
+ model (Any): The model instance.
155
+ new_agent_class (str): The name of the new meta-agent class.
156
+ agents (Iterable[Any]): The agents to be included in the meta-agent.
157
+ meta_attributes (Dict[str, Any]): Attributes to be added to the meta-agent.
158
+ meta_methods (Dict[str, Callable]): Methods to be added to the meta-agent.
159
+ assume_constituting_agent_methods (bool): Whether to assume methods from
160
+ constituting_-agents as meta_agent methods.
161
+ assume_constituting_agent_attributes (bool): Whether to retain attributes
162
+ from constituting_-agents.
163
+
164
+ Returns:
165
+ - MetaAgent Instance
166
+ """
167
+ # Convert agents to set to ensure uniqueness
168
+ agents = set(agents)
169
+
170
+ # Ensure there is at least one agent base class
171
+ if not mesa_agent_type:
172
+ mesa_agent_type = (Agent,)
173
+ elif not isinstance(mesa_agent_type, tuple):
174
+ mesa_agent_type = (mesa_agent_type,)
175
+
176
+ def add_methods(
177
+ meta_agent_instance: Any,
178
+ agents: Iterable[Any],
179
+ meta_methods: dict[str, Callable],
180
+ ) -> None:
181
+ """Add methods to the meta-agent instance.
182
+
183
+ Parameters:
184
+ meta_agent_instance (Any): The meta-agent instance.
185
+ agents (Iterable[Any]): The agents to derive methods from.
186
+ meta_methods (Dict[str, Callable]): methods to be added to the meta-agent.
187
+ """
188
+ if assume_constituting_agent_methods:
189
+ agent_classes = {type(agent) for agent in agents}
190
+ if meta_methods is None:
191
+ # Initialize meta_methods if not provided
192
+ meta_methods = {}
193
+ for agent_class in agent_classes:
194
+ for name in agent_class.__dict__:
195
+ if callable(getattr(agent_class, name)) and not name.startswith(
196
+ "__"
197
+ ):
198
+ original_method = getattr(agent_class, name)
199
+ meta_methods[name] = original_method
200
+
201
+ if meta_methods is not None:
202
+ for name, meth in meta_methods.items():
203
+ bound_method = MethodType(meth, meta_agent_instance)
204
+ setattr(meta_agent_instance, name, bound_method)
205
+
206
+ def add_attributes(
207
+ meta_agent_instance: Any, agents: Iterable[Any], meta_attributes: dict[str, Any]
208
+ ) -> None:
209
+ """Add attributes to the meta-agent instance.
210
+
211
+ Parameters:
212
+ meta_agent_instance (Any): The meta-agent instance.
213
+ agents (Iterable[Any]): The agents to derive attributes from.
214
+ meta_attributes (Dict[str, Any]): Attributes to be added to the
215
+ meta-agent.
216
+ """
217
+ if assume_constituting_agent_attributes:
218
+ if meta_attributes is None:
219
+ # Initialize meta_attributes if not provided
220
+ meta_attributes = {}
221
+ for agent in agents:
222
+ for name, value in agent.__dict__.items():
223
+ if not callable(value):
224
+ meta_attributes[name] = value
225
+
226
+ if meta_attributes is not None:
227
+ for key, value in meta_attributes.items():
228
+ setattr(meta_agent_instance, key, value)
229
+
230
+ # Path 1 - Add agents to existing meta-agent
231
+ constituting_agents = [a for a in agents if hasattr(a, "meta_agent")]
232
+ if len(constituting_agents) > 0:
233
+ if len(constituting_agents) == 1:
234
+ add_attributes(constituting_agents[0].meta_agent, agents, meta_attributes)
235
+ add_methods(constituting_agents[0].meta_agent, agents, meta_methods)
236
+ constituting_agents[0].meta_agent.add_constituting_agents(agents)
237
+
238
+ return constituting_agents[0].meta_agent # Return the existing meta-agent
239
+
240
+ else:
241
+ constituting_agent = model.random.choice(constituting_agents)
242
+ agents = set(agents) - set(constituting_agents)
243
+ add_attributes(constituting_agent.meta_agent, agents, meta_attributes)
244
+ add_methods(constituting_agent.meta_agent, agents, meta_methods)
245
+ constituting_agent.meta_agent.add_constituting_agents(agents)
246
+ # TODO: Add way for user to specify how agents join meta-agent
247
+ # instead of random choice
248
+ return constituting_agent.meta_agent
249
+
250
+ else:
251
+ # Path 2 - Create a new instance of an existing meta-agent class
252
+ agent_class = extract_class(model.agents_by_type, new_agent_class)
253
+
254
+ if agent_class:
255
+ meta_agent_instance = agent_class(model, agents)
256
+ add_attributes(meta_agent_instance, agents, meta_attributes)
257
+ add_methods(meta_agent_instance, agents, meta_methods)
258
+ return meta_agent_instance
259
+ else:
260
+ # Path 3 - Create a new meta-agent class
261
+ meta_agent_class = type(
262
+ new_agent_class,
263
+ (MetaAgent, *mesa_agent_type), # Inherit Mesa Agent Classes
264
+ {
265
+ "unique_id": None,
266
+ "_constituting_set": None,
267
+ },
268
+ )
269
+ meta_agent_instance = meta_agent_class(model, agents)
270
+ add_attributes(meta_agent_instance, agents, meta_attributes)
271
+ add_methods(meta_agent_instance, agents, meta_methods)
272
+ return meta_agent_instance
273
+
274
+
275
+ class MetaAgent(Agent):
276
+ """A MetaAgent is an agent that contains other agents as components."""
277
+
278
+ def __init__(
279
+ self, model, agents: set[Agent] | None = None, name: str = "MetaAgent"
280
+ ):
281
+ """Create a new MetaAgent.
282
+
283
+ Args:
284
+ model: The model instance.
285
+ agents (Optional[set[Agent]], optional): The set of agents to
286
+ include in the MetaAgent. Defaults to None.
287
+ name (str, optional): The name of the MetaAgent. Defaults to "MetaAgent".
288
+ """
289
+ super().__init__(model)
290
+ self._constituting_set = AgentSet(agents or [], random=model.random)
291
+ self.name = name
292
+
293
+ # Add ref to meta_agent in constituting_agents
294
+ for agent in self._constituting_set:
295
+ agent.meta_agent = self # TODO: Make a set for meta_agents
296
+
297
+ def __len__(self) -> int:
298
+ """Return the number of components."""
299
+ return len(self._constituting_set)
300
+
301
+ def __iter__(self):
302
+ """Iterate over components."""
303
+ return iter(self._constituting_set)
304
+
305
+ def __contains__(self, agent: Agent) -> bool:
306
+ """Check if an agent is a component."""
307
+ return agent in self._constituting_set
308
+
309
+ @property
310
+ def agents(self) -> AgentSet:
311
+ """Get list of Meta-Agent constituting_agents."""
312
+ return self._constituting_set
313
+
314
+ @property
315
+ def constituting_agents_by_type(self) -> dict[type, list[Agent]]:
316
+ """Get the constituting_agents grouped by type.
317
+
318
+ Returns:
319
+ dict[type, list[Agent]]: A dictionary of constituting_agents grouped by type.
320
+ """
321
+ constituting_agents_by_type = {}
322
+ for agent in self._constituting_set:
323
+ agent_type = type(agent)
324
+ if agent_type not in constituting_agents_by_type:
325
+ constituting_agents_by_type[agent_type] = []
326
+ constituting_agents_by_type[agent_type].append(agent)
327
+ return constituting_agents_by_type
328
+
329
+ @property
330
+ def constituting_agent_types(self) -> set[type]:
331
+ """Get the types of all constituting_agents.
332
+
333
+ Returns:
334
+ set[type]: A set of unique types of the constituting_agents.
335
+ """
336
+ return {type(agent) for agent in self._constituting_set}
337
+
338
+ def get_constituting_agent_instance(self, agent_type) -> set[type]:
339
+ """Get the instance of a constituting_agent of the specified type.
340
+
341
+ Args:
342
+ agent_type: The type of the constituting_agent to retrieve.
343
+
344
+ Returns:
345
+ The first instance of the specified constituting_agent type.
346
+
347
+ Raises:
348
+ ValueError: If no constituting_agent of the specified type is found.
349
+ """
350
+ try:
351
+ return self.constituting_agents_by_type[agent_type][0]
352
+ except KeyError:
353
+ raise ValueError(
354
+ f"No constituting_agent of type {agent_type} found."
355
+ ) from None
356
+
357
+ def add_constituting_agents(
358
+ self,
359
+ new_agents: set[Agent],
360
+ ):
361
+ """Add agents as components.
362
+
363
+ Args:
364
+ new_agents (set[Agent]): The agents to add to MetaAgent constituting_set.
365
+ """
366
+ for agent in new_agents:
367
+ self._constituting_set.add(agent)
368
+ agent.meta_agent = self # TODO: Make a set for meta_agents
369
+ self.model.register_agent(agent)
370
+
371
+ def remove_constituting_agents(self, remove_agents: set[Agent]):
372
+ """Remove agents as components.
373
+
374
+ Args:
375
+ remove_agents (set[Agent]): The agents to remove from MetaAgent.
376
+ """
377
+ for agent in remove_agents:
378
+ self._constituting_set.discard(agent)
379
+ agent.meta_agent = None # TODO: Remove meta_agent from set
380
+ self.model.deregister_agent(agent)
381
+
382
+ def step(self):
383
+ """Perform the agent's step.
384
+
385
+ Override this method to define the meta agent's behavior.
386
+ By default, does nothing.
387
+ """
mesa/model.py CHANGED
@@ -151,9 +151,9 @@ class Model:
151
151
  agent: The agent to register.
152
152
 
153
153
  Notes:
154
- This method is called automatically by ``Agent.__init__``, so there is no need to use this
155
- if you are subclassing Agent and calling its super in the ``__init__`` method.
156
-
154
+ This method is called automatically by ``Agent.__init__``, so there
155
+ is no need to use this if you are subclassing Agent and calling its
156
+ super in the ``__init__`` method.
157
157
  """
158
158
  self._agents[agent] = None
159
159
 
mesa/space.py CHANGED
@@ -1571,7 +1571,10 @@ class NetworkGrid:
1571
1571
  )
1572
1572
  if not include_center:
1573
1573
  del neighbors_with_distance[node_id]
1574
- neighborhood = sorted(neighbors_with_distance.keys())
1574
+ neighbors_with_distance = sorted(
1575
+ neighbors_with_distance.items(), key=lambda item: item[1]
1576
+ )
1577
+ neighborhood = [node_id for node_id, _ in neighbors_with_distance]
1575
1578
  return neighborhood
1576
1579
 
1577
1580
  def get_neighbors(
@@ -13,6 +13,7 @@ from .command_console import CommandConsole
13
13
  from .components import make_plot_component, make_space_component
14
14
  from .components.altair_components import make_space_altair
15
15
  from .solara_viz import JupyterViz, SolaraViz
16
+ from .space_renderer import SpaceRenderer
16
17
  from .user_param import Slider
17
18
 
18
19
  __all__ = [
@@ -20,6 +21,7 @@ __all__ = [
20
21
  "JupyterViz",
21
22
  "Slider",
22
23
  "SolaraViz",
24
+ "SpaceRenderer",
23
25
  "draw_space",
24
26
  "make_plot_component",
25
27
  "make_space_altair",
@@ -0,0 +1,23 @@
1
+ """Visualization backends for Mesa space rendering.
2
+
3
+ This module provides different backend implementations for visualizing
4
+ Mesa agent-based model spaces and components.
5
+
6
+ Note:
7
+ These backends are used internally by the space renderer and are not intended for
8
+ direct use by end users. See `SpaceRenderer` for actual usage and setting up
9
+ visualizations.
10
+
11
+ Available Backends:
12
+ 1. AltairBackend
13
+ 2. MatplotlibBackend
14
+
15
+ """
16
+
17
+ from .altair_backend import AltairBackend
18
+ from .matplotlib_backend import MatplotlibBackend
19
+
20
+ __all__ = [
21
+ "AltairBackend",
22
+ "MatplotlibBackend",
23
+ ]
@@ -0,0 +1,97 @@
1
+ """Abstract base class for visualization backends in Mesa.
2
+
3
+ This module provides the foundational interface for implementing various
4
+ visualization backends for Mesa agent-based models.
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+
9
+ import mesa
10
+ from mesa.discrete_space import (
11
+ OrthogonalMooreGrid,
12
+ OrthogonalVonNeumannGrid,
13
+ )
14
+ from mesa.space import (
15
+ HexMultiGrid,
16
+ HexSingleGrid,
17
+ MultiGrid,
18
+ NetworkGrid,
19
+ SingleGrid,
20
+ )
21
+
22
+ OrthogonalGrid = SingleGrid | MultiGrid | OrthogonalMooreGrid | OrthogonalVonNeumannGrid
23
+ HexGrid = HexSingleGrid | HexMultiGrid | mesa.discrete_space.HexGrid
24
+ Network = NetworkGrid | mesa.discrete_space.Network
25
+
26
+
27
+ class AbstractRenderer(ABC):
28
+ """Abstract base class for visualization backends.
29
+
30
+ This class defines the interface for rendering Mesa spaces and agents.
31
+ For details on the methods checkout specific backend implementations.
32
+ """
33
+
34
+ def __init__(self, space_drawer):
35
+ """Initialize the renderer.
36
+
37
+ Args:
38
+ space_drawer: Object responsible for drawing space elements. Checkout `SpaceDrawer`
39
+ for more details on the detailed implementations of the drawing functions.
40
+ """
41
+ self.space_drawer = space_drawer
42
+ self._canvas = None
43
+
44
+ def _get_agent_pos(self, agent, space):
45
+ """Get agent position based on space type."""
46
+ if isinstance(space, NetworkGrid):
47
+ return agent.pos, agent.pos
48
+ elif isinstance(space, Network):
49
+ return agent.cell.coordinate, agent.cell.coordinate
50
+ else:
51
+ x = agent.pos[0] if agent.pos is not None else agent.cell.coordinate[0]
52
+ y = agent.pos[1] if agent.pos is not None else agent.cell.coordinate[1]
53
+ return x, y
54
+
55
+ @abstractmethod
56
+ def initialize_canvas(self):
57
+ """Set up the drawing canvas."""
58
+
59
+ @abstractmethod
60
+ def draw_structure(self, **kwargs):
61
+ """Draw the space structure.
62
+
63
+ Args:
64
+ **kwargs: Structure drawing configuration options.
65
+ """
66
+
67
+ @abstractmethod
68
+ def collect_agent_data(self, space, agent_portrayal, default_size=None):
69
+ """Collect plotting data for all agents in the space.
70
+
71
+ Args:
72
+ space: The Mesa space containing agents.
73
+ agent_portrayal (Callable): Function that returns AgentPortrayalStyle for each agent.
74
+ default_size (float, optional): Default marker size if not specified in portrayal.
75
+
76
+ Returns:
77
+ dict: Dictionary containing agent plotting data arrays with keys:
78
+ """
79
+
80
+ @abstractmethod
81
+ def draw_agents(self, arguments, **kwargs):
82
+ """Drawing agents on space.
83
+
84
+ Args:
85
+ arguments (dict): Dictionary containing agent data.
86
+ **kwargs: Additional drawing configuration options.
87
+ """
88
+
89
+ @abstractmethod
90
+ def draw_propertylayer(self, space, property_layers, propertylayer_portrayal):
91
+ """Draw property layers on the visualization.
92
+
93
+ Args:
94
+ space: The model's space object.
95
+ property_layers (dict): Dictionary of property layers to visualize.
96
+ propertylayer_portrayal (Callable): Function that returns PropertyLayerStyle.
97
+ """