Mesa 3.1.5__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.
- mesa/__init__.py +3 -1
- mesa/agent.py +26 -9
- mesa/discrete_space/__init__.py +50 -0
- mesa/{experimental/cell_space → discrete_space}/cell.py +29 -10
- mesa/{experimental/cell_space → discrete_space}/cell_agent.py +1 -1
- mesa/{experimental/cell_space → discrete_space}/cell_collection.py +3 -3
- mesa/{experimental/cell_space → discrete_space}/discrete_space.py +65 -3
- mesa/{experimental/cell_space → discrete_space}/grid.py +2 -2
- mesa/{experimental/cell_space → discrete_space}/network.py +22 -2
- mesa/{experimental/cell_space → discrete_space}/property_layer.py +1 -10
- mesa/{experimental/cell_space → discrete_space}/voronoi.py +2 -2
- mesa/examples/README.md +1 -1
- mesa/examples/__init__.py +2 -0
- mesa/examples/advanced/alliance_formation/Readme.md +50 -0
- mesa/examples/advanced/alliance_formation/__init__ .py +0 -0
- mesa/examples/advanced/alliance_formation/agents.py +20 -0
- mesa/examples/advanced/alliance_formation/app.py +71 -0
- mesa/examples/advanced/alliance_formation/model.py +184 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +1 -1
- mesa/examples/advanced/epstein_civil_violence/model.py +1 -1
- mesa/examples/advanced/pd_grid/Readme.md +4 -6
- mesa/examples/advanced/pd_grid/agents.py +1 -1
- mesa/examples/advanced/pd_grid/model.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +4 -5
- mesa/examples/advanced/sugarscape_g1mt/agents.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/model.py +2 -2
- mesa/examples/advanced/wolf_sheep/Readme.md +2 -17
- mesa/examples/advanced/wolf_sheep/agents.py +1 -1
- mesa/examples/advanced/wolf_sheep/app.py +2 -1
- mesa/examples/advanced/wolf_sheep/model.py +1 -1
- mesa/examples/basic/boid_flockers/Readme.md +6 -1
- mesa/examples/basic/boid_flockers/agents.py +1 -0
- mesa/examples/basic/boid_flockers/app.py +17 -2
- mesa/examples/basic/boid_flockers/model.py +12 -0
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +2 -12
- mesa/examples/basic/boltzmann_wealth_model/agents.py +6 -11
- mesa/examples/basic/boltzmann_wealth_model/app.py +2 -2
- mesa/examples/basic/boltzmann_wealth_model/model.py +7 -11
- mesa/examples/basic/conways_game_of_life/Readme.md +1 -9
- mesa/examples/basic/conways_game_of_life/agents.py +13 -5
- mesa/examples/basic/conways_game_of_life/model.py +10 -7
- mesa/examples/basic/schelling/Readme.md +0 -8
- mesa/examples/basic/schelling/agents.py +13 -8
- mesa/examples/basic/schelling/model.py +6 -9
- mesa/examples/basic/virus_on_network/Readme.md +0 -4
- mesa/examples/basic/virus_on_network/agents.py +13 -17
- mesa/examples/basic/virus_on_network/model.py +20 -24
- mesa/experimental/__init__.py +2 -2
- mesa/experimental/cell_space/__init__.py +18 -8
- mesa/experimental/meta_agents/__init__.py +25 -0
- mesa/experimental/meta_agents/meta_agent.py +387 -0
- mesa/model.py +3 -3
- mesa/space.py +1 -12
- mesa/visualization/__init__.py +2 -0
- mesa/visualization/command_console.py +482 -0
- mesa/visualization/components/altair_components.py +276 -16
- mesa/visualization/mpl_space_drawing.py +17 -9
- mesa/visualization/solara_viz.py +150 -21
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/METADATA +12 -8
- mesa-3.2.0.dist-info/RECORD +105 -0
- mesa-3.1.5.dist-info/RECORD +0 -96
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/WHEEL +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.5.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.
|
|
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:
|
|
57
|
-
kwargs:
|
|
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
|
-
"""
|
|
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(
|
|
385
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
@@ -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.
|
|
22
|
-
from mesa.
|
|
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
|
-
"
|
|
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.
|
|
69
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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()
|
|
@@ -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.
|
|
27
|
-
from mesa.
|
|
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.
|
|
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.
|
|
25
|
-
from mesa.
|
|
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.
|
|
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.
|
|
23
|
-
from mesa.
|
|
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.
|
|
19
|
-
from mesa.
|
|
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.
|
|
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.
|
|
22
|
-
from mesa.
|
|
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
|
@@ -12,7 +12,7 @@ The examples are categorized into two groups:
|
|
|
12
12
|
The basic examples are relatively simple and only use stable Mesa features. They are good starting points for learning how to use Mesa.
|
|
13
13
|
|
|
14
14
|
### [Boltzmann Wealth Model](examples/basic/boltzmann_wealth_model)
|
|
15
|
-
Completed code to go along with the [tutorial](https://mesa.readthedocs.io/latest/tutorials/
|
|
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.
|
|
16
16
|
|
|
17
17
|
### [Boids Flockers Model](examples/basic/boid_flockers)
|
|
18
18
|
[Boids](https://en.wikipedia.org/wiki/Boids)-style flocking model, demonstrating the use of agents moving through a continuous space following direction vectors.
|
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
|