Mesa 3.1.5__py3-none-any.whl → 3.2.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +3 -1
- mesa/agent.py +20 -5
- 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/advanced/epstein_civil_violence/agents.py +1 -1
- mesa/examples/advanced/epstein_civil_violence/model.py +1 -1
- mesa/examples/advanced/pd_grid/agents.py +1 -1
- mesa/examples/advanced/pd_grid/model.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/agents.py +1 -1
- mesa/examples/advanced/sugarscape_g1mt/model.py +2 -2
- 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/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/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/agents.py +13 -5
- mesa/examples/basic/conways_game_of_life/model.py +10 -7
- mesa/examples/basic/schelling/agents.py +13 -8
- mesa/examples/basic/schelling/model.py +6 -9
- 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/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.dev0.dist-info}/METADATA +12 -8
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/RECORD +45 -43
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/WHEEL +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.dist-info}/licenses/LICENSE +0 -0
- {mesa-3.1.5.dist-info → mesa-3.2.0.dev0.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.dev"
|
|
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
|
@@ -381,18 +381,33 @@ class AgentSet(MutableSet, Sequence):
|
|
|
381
381
|
|
|
382
382
|
return res
|
|
383
383
|
|
|
384
|
-
def agg(
|
|
385
|
-
|
|
384
|
+
def agg(
|
|
385
|
+
self, attribute: str, func: Callable | Iterable[Callable]
|
|
386
|
+
) -> Any | list[Any]:
|
|
387
|
+
"""Aggregate an attribute of all agents in the AgentSet using one or more functions.
|
|
386
388
|
|
|
387
389
|
Args:
|
|
388
390
|
attribute (str): The name of the attribute to aggregate.
|
|
389
|
-
func (Callable
|
|
391
|
+
func (Callable | Iterable[Callable]):
|
|
392
|
+
- If Callable: A single function to apply to the attribute values (e.g., min, max, sum, np.mean)
|
|
393
|
+
- If Iterable: Multiple functions to apply to the attribute values
|
|
390
394
|
|
|
391
395
|
Returns:
|
|
392
|
-
Any:
|
|
396
|
+
Any | [Any, ...]: Result of applying the function(s) to the attribute values.
|
|
397
|
+
|
|
398
|
+
Examples:
|
|
399
|
+
# Single function
|
|
400
|
+
avg_energy = model.agents.agg("energy", np.mean)
|
|
401
|
+
|
|
402
|
+
# Multiple functions
|
|
403
|
+
min_wealth, max_wealth, total_wealth = model.agents.agg("wealth", [min, max, sum])
|
|
393
404
|
"""
|
|
394
405
|
values = self.get(attribute)
|
|
395
|
-
|
|
406
|
+
|
|
407
|
+
if isinstance(func, Callable):
|
|
408
|
+
return func(values)
|
|
409
|
+
else:
|
|
410
|
+
return [f(values) for f in func]
|
|
396
411
|
|
|
397
412
|
@overload
|
|
398
413
|
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:
|
|
@@ -53,7 +53,7 @@ class EpsteinCivilViolence(mesa.Model):
|
|
|
53
53
|
self.movement = movement
|
|
54
54
|
self.max_iters = max_iters
|
|
55
55
|
|
|
56
|
-
self.grid = mesa.
|
|
56
|
+
self.grid = mesa.discrete_space.OrthogonalVonNeumannGrid(
|
|
57
57
|
(width, height), capacity=1, torus=True, random=self.random
|
|
58
58
|
)
|
|
59
59
|
|
|
@@ -3,9 +3,9 @@ from pathlib import Path
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
|
|
5
5
|
import mesa
|
|
6
|
+
from mesa.discrete_space import OrthogonalVonNeumannGrid
|
|
7
|
+
from mesa.discrete_space.property_layer import PropertyLayer
|
|
6
8
|
from mesa.examples.advanced.sugarscape_g1mt.agents import Trader
|
|
7
|
-
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
|
|
8
|
-
from mesa.experimental.cell_space.property_layer import PropertyLayer
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
# Helper Functions
|
|
@@ -2,6 +2,7 @@ from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
|
2
2
|
from mesa.examples.advanced.wolf_sheep.model import WolfSheep
|
|
3
3
|
from mesa.experimental.devs import ABMSimulator
|
|
4
4
|
from mesa.visualization import (
|
|
5
|
+
CommandConsole,
|
|
5
6
|
Slider,
|
|
6
7
|
SolaraViz,
|
|
7
8
|
make_plot_component,
|
|
@@ -87,7 +88,7 @@ model = WolfSheep(simulator=simulator, grass=True)
|
|
|
87
88
|
|
|
88
89
|
page = SolaraViz(
|
|
89
90
|
model,
|
|
90
|
-
components=[space_component, lineplot_component],
|
|
91
|
+
components=[space_component, lineplot_component, CommandConsole],
|
|
91
92
|
model_params=model_params,
|
|
92
93
|
name="Wolf Sheep",
|
|
93
94
|
simulator=simulator,
|
|
@@ -13,8 +13,8 @@ import math
|
|
|
13
13
|
|
|
14
14
|
from mesa import Model
|
|
15
15
|
from mesa.datacollection import DataCollector
|
|
16
|
+
from mesa.discrete_space import OrthogonalVonNeumannGrid
|
|
16
17
|
from mesa.examples.advanced.wolf_sheep.agents import GrassPatch, Sheep, Wolf
|
|
17
|
-
from mesa.experimental.cell_space import OrthogonalVonNeumannGrid
|
|
18
18
|
from mesa.experimental.devs import ABMSimulator
|
|
19
19
|
|
|
20
20
|
|
|
@@ -58,6 +58,7 @@ class Boid(ContinuousSpaceAgent):
|
|
|
58
58
|
self.separate_factor = separate
|
|
59
59
|
self.match_factor = match
|
|
60
60
|
self.neighbors = []
|
|
61
|
+
self.angle = 0.0 # represents the angle at which the boid is moving
|
|
61
62
|
|
|
62
63
|
def step(self):
|
|
63
64
|
"""Get the Boid's neighbors, compute the new vector, and move accordingly."""
|
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
+
from matplotlib.markers import MarkerStyle
|
|
5
|
+
|
|
4
6
|
sys.path.insert(0, os.path.abspath("../../../.."))
|
|
5
7
|
|
|
6
8
|
from mesa.examples.basic.boid_flockers.model import BoidFlockers
|
|
7
9
|
from mesa.visualization import Slider, SolaraViz, make_space_component
|
|
8
10
|
|
|
11
|
+
# Pre-compute markers for different angles (e.g., every 10 degrees)
|
|
12
|
+
MARKER_CACHE = {}
|
|
13
|
+
for angle in range(0, 360, 10):
|
|
14
|
+
marker = MarkerStyle(10)
|
|
15
|
+
marker._transform = marker.get_transform().rotate_deg(angle)
|
|
16
|
+
MARKER_CACHE[angle] = marker
|
|
17
|
+
|
|
9
18
|
|
|
10
19
|
def boid_draw(agent):
|
|
11
20
|
neighbors = len(agent.neighbors)
|
|
12
21
|
|
|
22
|
+
# Calculate the angle
|
|
23
|
+
deg = agent.angle
|
|
24
|
+
# Round to nearest 10 degrees
|
|
25
|
+
rounded_deg = round(deg / 10) * 10 % 360
|
|
26
|
+
|
|
27
|
+
# using cached markers to speed things up
|
|
13
28
|
if neighbors <= 1:
|
|
14
|
-
return {"color": "red", "size": 20}
|
|
29
|
+
return {"color": "red", "size": 20, "marker": MARKER_CACHE[rounded_deg]}
|
|
15
30
|
elif neighbors >= 2:
|
|
16
|
-
return {"color": "green", "size": 20}
|
|
31
|
+
return {"color": "green", "size": 20, "marker": MARKER_CACHE[rounded_deg]}
|
|
17
32
|
|
|
18
33
|
|
|
19
34
|
model_params = {
|
|
@@ -49,6 +49,9 @@ class BoidFlockers(Model):
|
|
|
49
49
|
seed: Random seed for reproducibility (default: None)
|
|
50
50
|
"""
|
|
51
51
|
super().__init__(seed=seed)
|
|
52
|
+
self.agent_angles = np.zeros(
|
|
53
|
+
population_size
|
|
54
|
+
) # holds the angle representing the direction of all agents at a given step
|
|
52
55
|
|
|
53
56
|
# Set up the space
|
|
54
57
|
self.space = ContinuousSpace(
|
|
@@ -79,6 +82,14 @@ class BoidFlockers(Model):
|
|
|
79
82
|
self.average_heading = None
|
|
80
83
|
self.update_average_heading()
|
|
81
84
|
|
|
85
|
+
# vectorizing the calculation of angles for all agents
|
|
86
|
+
def calculate_angles(self):
|
|
87
|
+
d1 = np.array([agent.direction[0] for agent in self.agents])
|
|
88
|
+
d2 = np.array([agent.direction[1] for agent in self.agents])
|
|
89
|
+
self.agent_angles = np.degrees(np.arctan2(d1, d2))
|
|
90
|
+
for agent, angle in zip(self.agents, self.agent_angles):
|
|
91
|
+
agent.angle = angle
|
|
92
|
+
|
|
82
93
|
def update_average_heading(self):
|
|
83
94
|
"""Calculate the average heading (direction) of all Boids."""
|
|
84
95
|
if not self.agents:
|
|
@@ -96,3 +107,4 @@ class BoidFlockers(Model):
|
|
|
96
107
|
"""
|
|
97
108
|
self.agents.shuffle_do("step")
|
|
98
109
|
self.update_average_heading()
|
|
110
|
+
self.calculate_angles()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from mesa import
|
|
1
|
+
from mesa.discrete_space import CellAgent
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class MoneyAgent(
|
|
4
|
+
class MoneyAgent(CellAgent):
|
|
5
5
|
"""An agent with fixed initial wealth.
|
|
6
6
|
|
|
7
7
|
Each agent starts with 1 unit of wealth and can give 1 unit to other agents
|
|
@@ -11,28 +11,23 @@ class MoneyAgent(Agent):
|
|
|
11
11
|
wealth (int): The agent's current wealth (starts at 1)
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
def __init__(self, model):
|
|
14
|
+
def __init__(self, model, cell):
|
|
15
15
|
"""Create a new agent.
|
|
16
16
|
|
|
17
17
|
Args:
|
|
18
18
|
model (Model): The model instance that contains the agent
|
|
19
19
|
"""
|
|
20
20
|
super().__init__(model)
|
|
21
|
+
self.cell = cell
|
|
21
22
|
self.wealth = 1
|
|
22
23
|
|
|
23
24
|
def move(self):
|
|
24
25
|
"""Move the agent to a random neighboring cell."""
|
|
25
|
-
|
|
26
|
-
self.pos, moore=True, include_center=False
|
|
27
|
-
)
|
|
28
|
-
new_position = self.random.choice(possible_steps)
|
|
29
|
-
self.model.grid.move_agent(self, new_position)
|
|
26
|
+
self.cell = self.cell.neighborhood.select_random_cell()
|
|
30
27
|
|
|
31
28
|
def give_money(self):
|
|
32
29
|
"""Give 1 unit of wealth to a random agent in the same cell."""
|
|
33
|
-
cellmates = self.
|
|
34
|
-
# Remove self from potential recipients
|
|
35
|
-
cellmates.pop(cellmates.index(self))
|
|
30
|
+
cellmates = [a for a in self.cell.agents if a is not self]
|
|
36
31
|
|
|
37
32
|
if cellmates: # Only give money if there are other agents present
|
|
38
33
|
other = self.random.choice(cellmates)
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from mesa.examples.basic.boltzmann_wealth_model.model import BoltzmannWealth
|
|
2
|
-
from mesa.mesa_logging import
|
|
2
|
+
from mesa.mesa_logging import INFO, log_to_stderr
|
|
3
3
|
from mesa.visualization import (
|
|
4
4
|
SolaraViz,
|
|
5
5
|
make_plot_component,
|
|
6
6
|
make_space_component,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
log_to_stderr(
|
|
9
|
+
log_to_stderr(INFO)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def agent_portrayal(agent):
|