Mesa 3.1.4__py3-none-any.whl → 3.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.
Files changed (65) hide show
  1. mesa/__init__.py +3 -1
  2. mesa/agent.py +26 -9
  3. mesa/batchrunner.py +6 -3
  4. mesa/discrete_space/__init__.py +50 -0
  5. mesa/{experimental/cell_space → discrete_space}/cell.py +29 -10
  6. mesa/{experimental/cell_space → discrete_space}/cell_agent.py +1 -1
  7. mesa/{experimental/cell_space → discrete_space}/cell_collection.py +3 -3
  8. mesa/{experimental/cell_space → discrete_space}/discrete_space.py +65 -3
  9. mesa/{experimental/cell_space → discrete_space}/grid.py +2 -2
  10. mesa/{experimental/cell_space → discrete_space}/network.py +22 -2
  11. mesa/{experimental/cell_space → discrete_space}/property_layer.py +1 -10
  12. mesa/{experimental/cell_space → discrete_space}/voronoi.py +2 -2
  13. mesa/examples/README.md +10 -5
  14. mesa/examples/__init__.py +2 -0
  15. mesa/examples/advanced/alliance_formation/Readme.md +50 -0
  16. mesa/examples/advanced/alliance_formation/__init__ .py +0 -0
  17. mesa/examples/advanced/alliance_formation/agents.py +20 -0
  18. mesa/examples/advanced/alliance_formation/app.py +71 -0
  19. mesa/examples/advanced/alliance_formation/model.py +184 -0
  20. mesa/examples/advanced/epstein_civil_violence/agents.py +1 -1
  21. mesa/examples/advanced/epstein_civil_violence/model.py +1 -1
  22. mesa/examples/advanced/pd_grid/Readme.md +4 -6
  23. mesa/examples/advanced/pd_grid/agents.py +1 -1
  24. mesa/examples/advanced/pd_grid/model.py +1 -1
  25. mesa/examples/advanced/sugarscape_g1mt/Readme.md +4 -5
  26. mesa/examples/advanced/sugarscape_g1mt/agents.py +1 -1
  27. mesa/examples/advanced/sugarscape_g1mt/model.py +2 -2
  28. mesa/examples/advanced/wolf_sheep/Readme.md +2 -17
  29. mesa/examples/advanced/wolf_sheep/agents.py +1 -1
  30. mesa/examples/advanced/wolf_sheep/app.py +2 -1
  31. mesa/examples/advanced/wolf_sheep/model.py +1 -1
  32. mesa/examples/basic/boid_flockers/Readme.md +6 -1
  33. mesa/examples/basic/boid_flockers/agents.py +1 -0
  34. mesa/examples/basic/boid_flockers/app.py +17 -2
  35. mesa/examples/basic/boid_flockers/model.py +12 -0
  36. mesa/examples/basic/boltzmann_wealth_model/Readme.md +2 -12
  37. mesa/examples/basic/boltzmann_wealth_model/agents.py +6 -11
  38. mesa/examples/basic/boltzmann_wealth_model/app.py +2 -2
  39. mesa/examples/basic/boltzmann_wealth_model/model.py +7 -11
  40. mesa/examples/basic/conways_game_of_life/Readme.md +1 -9
  41. mesa/examples/basic/conways_game_of_life/agents.py +13 -5
  42. mesa/examples/basic/conways_game_of_life/model.py +10 -7
  43. mesa/examples/basic/schelling/Readme.md +0 -8
  44. mesa/examples/basic/schelling/agents.py +13 -8
  45. mesa/examples/basic/schelling/model.py +6 -9
  46. mesa/examples/basic/virus_on_network/Readme.md +0 -4
  47. mesa/examples/basic/virus_on_network/agents.py +13 -17
  48. mesa/examples/basic/virus_on_network/model.py +20 -24
  49. mesa/experimental/__init__.py +2 -2
  50. mesa/experimental/cell_space/__init__.py +18 -8
  51. mesa/experimental/meta_agents/__init__.py +25 -0
  52. mesa/experimental/meta_agents/meta_agent.py +387 -0
  53. mesa/model.py +3 -3
  54. mesa/space.py +1 -12
  55. mesa/visualization/__init__.py +2 -0
  56. mesa/visualization/command_console.py +482 -0
  57. mesa/visualization/components/altair_components.py +276 -16
  58. mesa/visualization/mpl_space_drawing.py +17 -9
  59. mesa/visualization/solara_viz.py +150 -21
  60. {mesa-3.1.4.dist-info → mesa-3.2.0.dist-info}/METADATA +12 -8
  61. mesa-3.2.0.dist-info/RECORD +105 -0
  62. mesa-3.1.4.dist-info/RECORD +0 -96
  63. {mesa-3.1.4.dist-info → mesa-3.2.0.dist-info}/WHEEL +0 -0
  64. {mesa-3.1.4.dist-info → mesa-3.2.0.dist-info}/licenses/LICENSE +0 -0
  65. {mesa-3.1.4.dist-info → mesa-3.2.0.dist-info}/licenses/NOTICE +0 -0
mesa/__init__.py CHANGED
@@ -5,6 +5,7 @@ Core Objects: Model, and Agent.
5
5
 
6
6
  import datetime
7
7
 
8
+ import mesa.discrete_space as discrete_space
8
9
  import mesa.experimental as experimental
9
10
  import mesa.space as space
10
11
  from mesa.agent import Agent
@@ -17,12 +18,13 @@ __all__ = [
17
18
  "DataCollector",
18
19
  "Model",
19
20
  "batch_run",
21
+ "discrete_space",
20
22
  "experimental",
21
23
  "space",
22
24
  ]
23
25
 
24
26
  __title__ = "mesa"
25
- __version__ = "3.1.4"
27
+ __version__ = "3.2.0"
26
28
  __license__ = "Apache 2.0"
27
29
  _this_year = datetime.datetime.now(tz=datetime.UTC).date().year
28
30
  __copyright__ = f"Copyright {_this_year} Project Mesa Team"
mesa/agent.py CHANGED
@@ -53,13 +53,12 @@ class Agent:
53
53
 
54
54
  Args:
55
55
  model (Model): The model instance in which the agent exists.
56
- args: passed on to super
57
- kwargs: passed on to super
56
+ args: Passed on to super.
57
+ kwargs: Passed on to super.
58
58
 
59
59
  Notes:
60
60
  to make proper use of python's super, in each class remove the arguments and
61
61
  keyword arguments you need and pass on the rest to super
62
-
63
62
  """
64
63
  super().__init__(*args, **kwargs)
65
64
 
@@ -103,7 +102,10 @@ class Agent:
103
102
  """
104
103
 
105
104
  class ListLike:
106
- """Helper class to make default arguments act as if they are in a list of length N."""
105
+ """Make default arguments act as if they are in a list of length N.
106
+
107
+ This is a helper class.
108
+ """
107
109
 
108
110
  def __init__(self, value):
109
111
  self.value = value
@@ -381,18 +383,33 @@ class AgentSet(MutableSet, Sequence):
381
383
 
382
384
  return res
383
385
 
384
- def agg(self, attribute: str, func: Callable) -> Any:
385
- """Aggregate an attribute of all agents in the AgentSet using a specified function.
386
+ def agg(
387
+ self, attribute: str, func: Callable | Iterable[Callable]
388
+ ) -> Any | list[Any]:
389
+ """Aggregate an attribute of all agents in the AgentSet using one or more functions.
386
390
 
387
391
  Args:
388
392
  attribute (str): The name of the attribute to aggregate.
389
- func (Callable): The function to apply to the attribute values (e.g., min, max, sum, np.mean).
393
+ func (Callable | Iterable[Callable]):
394
+ - If Callable: A single function to apply to the attribute values (e.g., min, max, sum, np.mean)
395
+ - If Iterable: Multiple functions to apply to the attribute values
390
396
 
391
397
  Returns:
392
- Any: The result of applying the function to the attribute values. Often a single value.
398
+ Any | [Any, ...]: Result of applying the function(s) to the attribute values.
399
+
400
+ Examples:
401
+ # Single function
402
+ avg_energy = model.agents.agg("energy", np.mean)
403
+
404
+ # Multiple functions
405
+ min_wealth, max_wealth, total_wealth = model.agents.agg("wealth", [min, max, sum])
393
406
  """
394
407
  values = self.get(attribute)
395
- return func(values)
408
+
409
+ if isinstance(func, Callable):
410
+ return func(values)
411
+ else:
412
+ return [f(values) for f in func]
396
413
 
397
414
  @overload
398
415
  def get(
mesa/batchrunner.py CHANGED
@@ -3,7 +3,12 @@
3
3
  To take advantage of parallel execution of experiments, `batch_run` uses
4
4
  multiprocessing if ``number_processes`` is larger than 1. It is strongly advised
5
5
  to only run in parallel using a normal python file (so don't try to do it in a
6
- jupyter notebook). Moreover, best practice when using multiprocessing is to
6
+ jupyter notebook). This is because Jupyter notebooks have a different execution
7
+ model that can cause issues with Python's multiprocessing module, especially on
8
+ Windows. The main problems include the lack of a traditional __main__ entry
9
+ point, serialization issues, and potential deadlocks.
10
+
11
+ Moreover, best practice when using multiprocessing is to
7
12
  put the code inside an ``if __name__ == '__main__':`` code black as shown below::
8
13
 
9
14
  from mesa.batchrunner import batch_run
@@ -21,8 +26,6 @@ put the code inside an ``if __name__ == '__main__':`` code black as shown below:
21
26
  display_progress=True,
22
27
  )
23
28
 
24
-
25
-
26
29
  """
27
30
 
28
31
  import itertools
@@ -0,0 +1,50 @@
1
+ """Cell spaces for active, property-rich spatial modeling in Mesa.
2
+
3
+ Cell spaces extend Mesa's spatial modeling capabilities by making the space itself active -
4
+ each position (cell) can have properties and behaviors rather than just containing agents.
5
+ This enables more sophisticated environmental modeling and agent-environment interactions.
6
+
7
+ Key components:
8
+ - Cells: Active positions that can have properties and contain agents
9
+ - CellAgents: Agents that understand how to interact with cells
10
+ - Spaces: Different cell organization patterns (grids, networks, etc.)
11
+ - PropertyLayers: Efficient property storage and manipulation
12
+
13
+ This is particularly useful for models where the environment plays an active role,
14
+ like resource growth, pollution diffusion, or infrastructure networks. The cell
15
+ space system is experimental and under active development.
16
+ """
17
+
18
+ from mesa.discrete_space.cell import Cell
19
+ from mesa.discrete_space.cell_agent import (
20
+ CellAgent,
21
+ FixedAgent,
22
+ Grid2DMovingAgent,
23
+ )
24
+ from mesa.discrete_space.cell_collection import CellCollection
25
+ from mesa.discrete_space.discrete_space import DiscreteSpace
26
+ from mesa.discrete_space.grid import (
27
+ Grid,
28
+ HexGrid,
29
+ OrthogonalMooreGrid,
30
+ OrthogonalVonNeumannGrid,
31
+ )
32
+ from mesa.discrete_space.network import Network
33
+ from mesa.discrete_space.property_layer import PropertyLayer
34
+ from mesa.discrete_space.voronoi import VoronoiGrid
35
+
36
+ __all__ = [
37
+ "Cell",
38
+ "CellAgent",
39
+ "CellCollection",
40
+ "DiscreteSpace",
41
+ "FixedAgent",
42
+ "Grid",
43
+ "Grid2DMovingAgent",
44
+ "HexGrid",
45
+ "Network",
46
+ "OrthogonalMooreGrid",
47
+ "OrthogonalVonNeumannGrid",
48
+ "PropertyLayer",
49
+ "VoronoiGrid",
50
+ ]
@@ -18,8 +18,8 @@ from functools import cache, cached_property
18
18
  from random import Random
19
19
  from typing import TYPE_CHECKING
20
20
 
21
- from mesa.experimental.cell_space.cell_agent import CellAgent
22
- from mesa.experimental.cell_space.cell_collection import CellCollection
21
+ from mesa.discrete_space.cell_agent import CellAgent
22
+ from mesa.discrete_space.cell_collection import CellCollection
23
23
 
24
24
  if TYPE_CHECKING:
25
25
  from mesa.agent import Agent
@@ -40,7 +40,7 @@ class Cell:
40
40
 
41
41
  __slots__ = [
42
42
  "__dict__",
43
- "agents",
43
+ "_agents",
44
44
  "capacity",
45
45
  "connections",
46
46
  "coordinate",
@@ -65,8 +65,8 @@ class Cell:
65
65
  super().__init__()
66
66
  self.coordinate = coordinate
67
67
  self.connections: dict[Coordinate, Cell] = {}
68
- self.agents: list[
69
- Agent
68
+ self._agents: list[
69
+ CellAgent
70
70
  ] = [] # TODO:: change to AgentSet or weakrefs? (neither is very performant, )
71
71
  self.capacity: int | None = capacity
72
72
  self.properties: dict[
@@ -84,6 +84,7 @@ class Cell:
84
84
  """
85
85
  if key is None:
86
86
  key = other.coordinate
87
+ self._clear_cache()
87
88
  self.connections[key] = other
88
89
 
89
90
  def disconnect(self, other: Cell) -> None:
@@ -96,6 +97,7 @@ class Cell:
96
97
  keys_to_remove = [k for k, v in self.connections.items() if v == other]
97
98
  for key in keys_to_remove:
98
99
  del self.connections[key]
100
+ self._clear_cache()
99
101
 
100
102
  def add_agent(self, agent: CellAgent) -> None:
101
103
  """Adds an agent to the cell.
@@ -104,7 +106,7 @@ class Cell:
104
106
  agent (CellAgent): agent to add to this Cell
105
107
 
106
108
  """
107
- n = len(self.agents)
109
+ n = len(self._agents)
108
110
  self.empty = False
109
111
 
110
112
  if self.capacity and n >= self.capacity:
@@ -112,7 +114,7 @@ class Cell:
112
114
  "ERROR: Cell is full"
113
115
  ) # FIXME we need MESA errors or a proper error
114
116
 
115
- self.agents.append(agent)
117
+ self._agents.append(agent)
116
118
 
117
119
  def remove_agent(self, agent: CellAgent) -> None:
118
120
  """Removes an agent from the cell.
@@ -121,7 +123,7 @@ class Cell:
121
123
  agent (CellAgent): agent to remove from this cell
122
124
 
123
125
  """
124
- self.agents.remove(agent)
126
+ self._agents.remove(agent)
125
127
  self.empty = self.is_empty
126
128
 
127
129
  @property
@@ -134,6 +136,11 @@ class Cell:
134
136
  """Returns a bool of the contents of a cell."""
135
137
  return len(self.agents) == self.capacity
136
138
 
139
+ @property
140
+ def agents(self) -> list[CellAgent]:
141
+ """Returns a list of the agents occupying the cell."""
142
+ return self._agents.copy()
143
+
137
144
  def __repr__(self): # noqa
138
145
  return f"Cell({self.coordinate}, {self.agents})"
139
146
 
@@ -180,12 +187,12 @@ class Cell:
180
187
  raise ValueError("radius must be larger than one")
181
188
  if radius == 1:
182
189
  neighborhood = {
183
- neighbor: neighbor.agents for neighbor in self.connections.values()
190
+ neighbor: neighbor._agents for neighbor in self.connections.values()
184
191
  }
185
192
  if not include_center:
186
193
  return neighborhood
187
194
  else:
188
- neighborhood[self] = self.agents
195
+ neighborhood[self] = self._agents
189
196
  return neighborhood
190
197
  else:
191
198
  neighborhood: dict[Cell, list[Agent]] = {}
@@ -205,3 +212,15 @@ class Cell:
205
212
  "connections"
206
213
  ] = {} # replace this with empty connections to avoid infinite recursion error in pickle/deepcopy
207
214
  return state
215
+
216
+ def _clear_cache(self):
217
+ """Helper function to clear local cache."""
218
+ try:
219
+ self.__dict__.pop(
220
+ "neighborhood"
221
+ ) # cached properties are stored in __dict__, see functools.cached_property docs
222
+ except KeyError:
223
+ pass # cache is not set
224
+ else:
225
+ self.get_neighborhood.cache_clear()
226
+ self._neighborhood.cache_clear()
@@ -18,7 +18,7 @@ from typing import TYPE_CHECKING, Protocol
18
18
  from mesa.agent import Agent
19
19
 
20
20
  if TYPE_CHECKING:
21
- from mesa.experimental.cell_space import Cell
21
+ from mesa.discrete_space import Cell
22
22
 
23
23
 
24
24
  class HasCellProtocol(Protocol):
@@ -23,8 +23,8 @@ from random import Random
23
23
  from typing import TYPE_CHECKING, Generic, TypeVar
24
24
 
25
25
  if TYPE_CHECKING:
26
- from mesa.experimental.cell_space.cell import Cell
27
- from mesa.experimental.cell_space.cell_agent import CellAgent
26
+ from mesa.discrete_space.cell import Cell
27
+ from mesa.discrete_space.cell_agent import CellAgent
28
28
 
29
29
  T = TypeVar("T", bound="Cell")
30
30
 
@@ -59,7 +59,7 @@ class CellCollection(Generic[T]):
59
59
  if isinstance(cells, dict):
60
60
  self._cells = cells
61
61
  else:
62
- self._cells = {cell: cell.agents for cell in cells}
62
+ self._cells = {cell: cell._agents for cell in cells}
63
63
 
64
64
  # Get capacity from first cell if collection is not empty
65
65
  self._capacity: int | None = (
@@ -21,8 +21,8 @@ from random import Random
21
21
  from typing import Generic, TypeVar
22
22
 
23
23
  from mesa.agent import AgentSet
24
- from mesa.experimental.cell_space.cell import Cell
25
- from mesa.experimental.cell_space.cell_collection import CellCollection
24
+ from mesa.discrete_space.cell import Cell
25
+ from mesa.discrete_space.cell_collection import CellCollection
26
26
 
27
27
  T = TypeVar("T", bound=Cell)
28
28
 
@@ -85,11 +85,73 @@ class DiscreteSpace(Generic[T]):
85
85
  def _connect_cells(self): ...
86
86
  def _connect_single_cell(self, cell: T): ...
87
87
 
88
+ def add_cell(self, cell: T):
89
+ """Add a cell to the space.
90
+
91
+ Args:
92
+ cell: cell to add
93
+
94
+ Note:
95
+ Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and
96
+ connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So
97
+ if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared
98
+ correctly if you are adding or removing cells and connections at runtime.
99
+
100
+ """
101
+ self.__dict__.pop("all_cells", None)
102
+ self._cells[cell.coordinate] = cell
103
+
104
+ def remove_cell(self, cell: T):
105
+ """Remove a cell from the space.
106
+
107
+ Note:
108
+ Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and
109
+ connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So
110
+ if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared
111
+ correctly if you are adding or removing cells and connections at runtime.
112
+
113
+
114
+ """
115
+ neighbors = cell.neighborhood
116
+ self._cells.pop(cell.coordinate)
117
+ self.__dict__.pop("all_cells", None)
118
+
119
+ # iterate over all neighbors
120
+ for neighbor in neighbors.cells:
121
+ neighbor.disconnect(cell)
122
+ cell.disconnect(neighbor)
123
+
124
+ def add_connection(self, cell1: T, cell2: T):
125
+ """Add a connection between the two cells.
126
+
127
+ Note:
128
+ Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and
129
+ connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So
130
+ if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared
131
+ correctly if you are adding or removing cells and connections at runtime.
132
+
133
+ """
134
+ cell1.connect(cell2)
135
+ cell2.connect(cell1)
136
+
137
+ def remove_connection(self, cell1: T, cell2: T):
138
+ """Remove a connection between the two cells.
139
+
140
+ Note:
141
+ Discrete spaces rely on caching neighborhood relations for speedups. Adding or removing cells and
142
+ connections at runtime is possible. However, only the caches of cells directly affected will be cleared. So
143
+ if you rely on getting neighborhoods of cells with a radius higher than 1, these might not be cleared
144
+ correctly if you are adding or removing cells and connections at runtime.
145
+
146
+ """
147
+ cell1.disconnect(cell2)
148
+ cell2.disconnect(cell1)
149
+
88
150
  @cached_property
89
151
  def all_cells(self):
90
152
  """Return all cells in space."""
91
153
  return CellCollection(
92
- {cell: cell.agents for cell in self._cells.values()}, random=self.random
154
+ {cell: cell._agents for cell in self._cells.values()}, random=self.random
93
155
  )
94
156
 
95
157
  def __iter__(self): # noqa
@@ -19,8 +19,8 @@ from itertools import product
19
19
  from random import Random
20
20
  from typing import Any, Generic, TypeVar
21
21
 
22
- from mesa.experimental.cell_space import Cell, DiscreteSpace
23
- from mesa.experimental.cell_space.property_layer import (
22
+ from mesa.discrete_space import Cell, DiscreteSpace
23
+ from mesa.discrete_space.property_layer import (
24
24
  HasPropertyLayers,
25
25
  PropertyDescriptor,
26
26
  )
@@ -15,8 +15,8 @@ or any environment where connectivity matters more than physical location.
15
15
  from random import Random
16
16
  from typing import Any
17
17
 
18
- from mesa.experimental.cell_space.cell import Cell
19
- from mesa.experimental.cell_space.discrete_space import DiscreteSpace
18
+ from mesa.discrete_space.cell import Cell
19
+ from mesa.discrete_space.discrete_space import DiscreteSpace
20
20
 
21
21
 
22
22
  class Network(DiscreteSpace[Cell]):
@@ -55,3 +55,23 @@ class Network(DiscreteSpace[Cell]):
55
55
  def _connect_single_cell(self, cell: Cell):
56
56
  for node_id in self.G.neighbors(cell.coordinate):
57
57
  cell.connect(self._cells[node_id], node_id)
58
+
59
+ def add_cell(self, cell: Cell):
60
+ """Add a cell to the space."""
61
+ super().add_cell(cell)
62
+ self.G.add_node(cell.coordinate)
63
+
64
+ def remove_cell(self, cell: Cell):
65
+ """Remove a cell from the space."""
66
+ super().remove_cell(cell)
67
+ self.G.remove_node(cell.coordinate)
68
+
69
+ def add_connection(self, cell1: Cell, cell2: Cell):
70
+ """Add a connection between the two cells."""
71
+ super().add_connection(cell1, cell2)
72
+ self.G.add_edge(cell1.coordinate, cell2.coordinate)
73
+
74
+ def remove_connection(self, cell1: Cell, cell2: Cell):
75
+ """Remove a connection between the two cells."""
76
+ super().remove_connection(cell1, cell2)
77
+ self.G.remove_edge(cell1.coordinate, cell2.coordinate)
@@ -21,7 +21,7 @@ from typing import Any, TypeVar
21
21
 
22
22
  import numpy as np
23
23
 
24
- from mesa.experimental.cell_space import Cell
24
+ from mesa.discrete_space import Cell
25
25
 
26
26
  Coordinate = Sequence[int]
27
27
  T = TypeVar("T", bound=Cell)
@@ -85,15 +85,6 @@ class PropertyLayer:
85
85
  # fixme why not initialize with empty?
86
86
  self._mesa_data = np.full(self.dimensions, default_value, dtype=dtype)
87
87
 
88
- if not self.__class__.propertylayer_experimental_warning_given:
89
- warnings.warn(
90
- "The property layer functionality and associated classes are experimental. It may be changed or removed in any and all future releases, including patch releases.\n"
91
- "We would love to hear what you think about this new feature. If you have any thoughts, share them with us here: https://github.com/projectmesa/mesa/discussions/1932",
92
- FutureWarning,
93
- stacklevel=2,
94
- )
95
- self.__class__.propertylayer_experimental_warning_given = True
96
-
97
88
  @classmethod
98
89
  def from_data(cls, name: str, data: np.ndarray):
99
90
  """Create a property layer from a NumPy array.
@@ -18,8 +18,8 @@ from random import Random
18
18
 
19
19
  import numpy as np
20
20
 
21
- from mesa.experimental.cell_space.cell import Cell
22
- from mesa.experimental.cell_space.discrete_space import DiscreteSpace
21
+ from mesa.discrete_space.cell import Cell
22
+ from mesa.discrete_space.discrete_space import DiscreteSpace
23
23
 
24
24
 
25
25
  class Delaunay:
mesa/examples/README.md CHANGED
@@ -1,13 +1,18 @@
1
- # Mesa core examples
2
- These examples are a collection of classic agent based models built using Mesa. These core examples are maintained by the Mesa team and are intended to demonstrate the capabilities of Mesa.
1
+ # Mesa Core Examples
2
+ This repository contains a curated set of classic agent-based models implemented using Mesa. These core examples are maintained by the Mesa development team and serve as both demonstrations of Mesa's capabilities and starting points for your own models.
3
3
 
4
- More user examples and showcases can be found in the [mesa-examples](https://github.com/projectmesa/mesa-examples) repository.
4
+ ## Overview
5
+ The examples are categorized into two groups:
6
+ 1. **Basic Examples** - Simpler models that use only stable Mesa features; ideal for beginners
7
+ 2. **Advanced Examples** - More complex models that demonstrate additional concepts and may use some experimental features
8
+
9
+ > **Note:** Looking for more examples? Visit the [mesa-examples](https://github.com/projectmesa/mesa-examples) repository for user-contributed models and showcases.
5
10
 
6
11
  ## Basic Examples
7
12
  The basic examples are relatively simple and only use stable Mesa features. They are good starting points for learning how to use Mesa.
8
13
 
9
14
  ### [Boltzmann Wealth Model](examples/basic/boltzmann_wealth_model)
10
- Completed code to go along with the [tutorial](https://mesa.readthedocs.io/latest/tutorials/intro_tutorial.html) on making a simple model of how a highly-skewed wealth distribution can emerge from simple rules.
15
+ Completed code to go along with the [tutorial](https://mesa.readthedocs.io/latest/tutorials/0_first_model.html) on making a simple model of how a highly-skewed wealth distribution can emerge from simple rules.
11
16
 
12
17
  ### [Boids Flockers Model](examples/basic/boid_flockers)
13
18
  [Boids](https://en.wikipedia.org/wiki/Boids)-style flocking model, demonstrating the use of agents moving through a continuous space following direction vectors.
@@ -34,4 +39,4 @@ Grid-based demographic prisoner's dilemma model, demonstrating how simple rules
34
39
  This is Epstein & Axtell's Sugarscape model with Traders, a detailed description is in Chapter four of *Growing Artificial Societies: Social Science from the Bottom Up (1996)*. The model shows how emergent price equilibrium can happen via decentralized dynamics.
35
40
 
36
41
  ### [Wolf-Sheep Predation Model](examples/advanced/wolf_sheep)
37
- Implementation of an ecological model of predation and reproduction, based on the NetLogo [Wolf Sheep Predation](http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation) model.
42
+ Implementation of an ecological model of predation and reproduction, based on the NetLogo [Wolf Sheep Predation](http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation) model.
mesa/examples/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
+ from mesa.examples.advanced.alliance_formation.model import MultiLevelAllianceModel
1
2
  from mesa.examples.advanced.epstein_civil_violence.model import EpsteinCivilViolence
2
3
  from mesa.examples.advanced.pd_grid.model import PdGrid
3
4
  from mesa.examples.advanced.sugarscape_g1mt.model import SugarscapeG1mt
@@ -13,6 +14,7 @@ __all__ = [
13
14
  "BoltzmannWealth",
14
15
  "ConwaysGameOfLife",
15
16
  "EpsteinCivilViolence",
17
+ "MultiLevelAllianceModel",
16
18
  "PdGrid",
17
19
  "Schelling",
18
20
  "SugarscapeG1mt",
@@ -0,0 +1,50 @@
1
+ # Alliance Formation Model (Meta-Agent Example)
2
+
3
+ ## Summary
4
+
5
+ This model demonstrates Mesa's meta agent capability.
6
+
7
+ **Overview of meta agent:** Complex systems often have multiple levels of components. A city is not a single entity, but it is made of districts,neighborhoods, buildings, and people. A forest comprises an ecosystem of trees, plants, animals, and microorganisms. An organization is not one entity, but is made of departments, sub-departments, and people. A person is not a single entity, but it is made of micro biomes, organs and cells.
8
+
9
+ This reality is the motivation for meta-agents. It allows users to represent these multiple levels, where each level can have agents with sub-agents.
10
+
11
+ This model demonstrates Mesa's ability to dynamically create new classes of agents that are composed of existing agents. These meta-agents inherits functions and attributes from their sub-agents and users can specify new functionality or attributes they want the meta agent to have. For example, if a user is doing a factory simulation with autonomous systems, each major component of that system can be a sub-agent of the overall robot agent. Or, if someone is doing a simulation of an organization, individuals can be part of different organizational units that are working for some purpose.
12
+
13
+ To provide a simple demonstration of this capability is an alliance formation model.
14
+
15
+ In this simulation n agents are created, who have two attributes (1) power and (2) preference. Each attribute is a number between 0 and 1 over a gaussian distribution. Agents then randomly select other agents and use the [bilateral shapley value](https://en.wikipedia.org/wiki/Shapley_value) to determine if they should form an alliance. If the expected utility support an alliances, the agent creates a meta-agent. Subsequent steps may add agents to the meta-agent, create new instances of similar hierarchy, or create a new hierarchy level where meta-agents form an alliance of meta-agents. In this visualization of this model a new meta-agent hierarchy will be a larger node and a new color.
16
+
17
+ In MetaAgents current configuration, agents being part of multiple meta-agents is not supported.
18
+
19
+ If you would like to see an example of explicit meta-agent formation see the [warehouse model in the Mesa example's repository](https://github.com/projectmesa/mesa-examples/tree/main/examples/warehouse)
20
+
21
+
22
+ ## Installation
23
+
24
+ This model requires Mesa's recommended install and scipy
25
+
26
+ ```
27
+ $ pip install mesa[rec]
28
+ ```
29
+
30
+ ## How to Run
31
+
32
+ To run the model interactively, in this directory, run the following command
33
+
34
+ ```
35
+ $ solara run app.py
36
+ ```
37
+
38
+ ## Files
39
+
40
+ - `model.py`: Contains creation of agents, the network and management of agent execution.
41
+ - `agents.py`: Contains logic for forming alliances and creation of new agents
42
+ - `app.py`: Contains the code for the interactive Solara visualization.
43
+
44
+ ## Further Reading
45
+
46
+ The full tutorial describing how the model is built can be found at:
47
+ https://mesa.readthedocs.io/en/latest/tutorials/intro_tutorial.html
48
+
49
+ An example of the bilateral shapley value in another model:
50
+ [Techno-Social Energy Infrastructure Siting: Sustainable Energy Modeling Programming (SEMPro)](https://www.jasss.org/16/3/6.html)
File without changes
@@ -0,0 +1,20 @@
1
+ import mesa
2
+
3
+
4
+ class AllianceAgent(mesa.Agent):
5
+ """
6
+ Agent has three attributes power (float), position (float) and level (int)
7
+
8
+ """
9
+
10
+ def __init__(self, model, power, position, level=0):
11
+ super().__init__(model)
12
+ self.power = power
13
+ self.position = position
14
+ self.level = level
15
+
16
+ """
17
+ For this demo model agent only need attributes.
18
+
19
+ More complex models could have functions that define agent behavior.
20
+ """
@@ -0,0 +1,71 @@
1
+ import matplotlib.pyplot as plt
2
+ import networkx as nx
3
+ import solara
4
+ from matplotlib.figure import Figure
5
+
6
+ from mesa.examples.advanced.alliance_formation.model import MultiLevelAllianceModel
7
+ from mesa.visualization import SolaraViz
8
+ from mesa.visualization.utils import update_counter
9
+
10
+ model_params = {
11
+ "seed": {
12
+ "type": "InputText",
13
+ "value": 42,
14
+ "label": "Random Seed",
15
+ },
16
+ "n": {
17
+ "type": "SliderInt",
18
+ "value": 50,
19
+ "label": "Number of agents:",
20
+ "min": 10,
21
+ "max": 100,
22
+ "step": 1,
23
+ },
24
+ }
25
+
26
+ # Create visualization elements. The visualization elements are solara components
27
+ # that receive the model instance as a "prop" and display it in a certain way.
28
+ # Under the hood these are just classes that receive the model instance.
29
+ # You can also author your own visualization elements, which can also be functions
30
+ # that receive the model instance and return a valid solara component.
31
+
32
+
33
+ @solara.component
34
+ def plot_network(model):
35
+ update_counter.get()
36
+ g = model.network
37
+ pos = nx.fruchterman_reingold_layout(g)
38
+ fig = Figure()
39
+ ax = fig.subplots()
40
+ labels = {agent.unique_id: agent.unique_id for agent in model.agents}
41
+ node_sizes = [g.nodes[node]["size"] for node in g.nodes]
42
+ node_colors = [g.nodes[node]["size"] for node in g.nodes()]
43
+
44
+ nx.draw(
45
+ g,
46
+ pos,
47
+ node_size=node_sizes,
48
+ node_color=node_colors,
49
+ cmap=plt.cm.coolwarm,
50
+ labels=labels,
51
+ ax=ax,
52
+ )
53
+
54
+ solara.FigureMatplotlib(fig)
55
+
56
+
57
+ # Create initial model instance
58
+ model = MultiLevelAllianceModel(50)
59
+
60
+ # Create the SolaraViz page. This will automatically create a server and display the
61
+ # visualization elements in a web browser.
62
+ # Display it using the following command in the example directory:
63
+ # solara run app.py
64
+ # It will automatically update and display any changes made to this file
65
+ page = SolaraViz(
66
+ model,
67
+ components=[plot_network],
68
+ model_params=model_params,
69
+ name="Alliance Formation Model",
70
+ )
71
+ page # noqa