Mesa 3.0.0__py3-none-any.whl → 3.0.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +3 -3
- mesa/agent.py +114 -406
- mesa/batchrunner.py +27 -54
- mesa/cookiecutter-mesa/cookiecutter.json +8 -0
- mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
- mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
- mesa/datacollection.py +29 -140
- mesa/experimental/__init__.py +1 -11
- mesa/experimental/cell_space/__init__.py +1 -16
- mesa/experimental/cell_space/cell.py +23 -93
- mesa/experimental/cell_space/cell_agent.py +21 -117
- mesa/experimental/cell_space/cell_collection.py +17 -54
- mesa/experimental/cell_space/discrete_space.py +8 -92
- mesa/experimental/cell_space/grid.py +8 -32
- mesa/experimental/cell_space/network.py +7 -12
- mesa/experimental/devs/__init__.py +0 -2
- mesa/experimental/devs/eventlist.py +14 -52
- mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
- mesa/experimental/devs/examples/wolf_sheep.py +45 -45
- mesa/experimental/devs/simulator.py +15 -55
- mesa/main.py +63 -0
- mesa/model.py +83 -211
- mesa/space.py +149 -215
- mesa/time.py +77 -62
- mesa/{experimental → visualization}/UserParam.py +6 -17
- mesa/visualization/__init__.py +2 -25
- mesa/{experimental → visualization}/components/altair.py +0 -10
- mesa/visualization/components/matplotlib.py +134 -0
- mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
- mesa-3.0.0a0.dist-info/RECORD +38 -0
- mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
- mesa/examples/README.md +0 -37
- mesa/examples/__init__.py +0 -21
- mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
- mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
- mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
- mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
- mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
- mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
- mesa/examples/advanced/pd_grid/Readme.md +0 -43
- mesa/examples/advanced/pd_grid/__init__.py +0 -0
- mesa/examples/advanced/pd_grid/agents.py +0 -50
- mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
- mesa/examples/advanced/pd_grid/app.py +0 -54
- mesa/examples/advanced/pd_grid/model.py +0 -71
- mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
- mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
- mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
- mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
- mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
- mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
- mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
- mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
- mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
- mesa/examples/advanced/wolf_sheep/agents.py +0 -102
- mesa/examples/advanced/wolf_sheep/app.py +0 -84
- mesa/examples/advanced/wolf_sheep/model.py +0 -137
- mesa/examples/basic/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/Readme.md +0 -22
- mesa/examples/basic/boid_flockers/__init__.py +0 -0
- mesa/examples/basic/boid_flockers/agents.py +0 -71
- mesa/examples/basic/boid_flockers/app.py +0 -58
- mesa/examples/basic/boid_flockers/model.py +0 -69
- mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
- mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
- mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
- mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
- mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
- mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
- mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
- mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
- mesa/examples/basic/conways_game_of_life/agents.py +0 -47
- mesa/examples/basic/conways_game_of_life/app.py +0 -51
- mesa/examples/basic/conways_game_of_life/model.py +0 -31
- mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
- mesa/examples/basic/schelling/Readme.md +0 -40
- mesa/examples/basic/schelling/__init__.py +0 -0
- mesa/examples/basic/schelling/agents.py +0 -26
- mesa/examples/basic/schelling/analysis.ipynb +0 -205
- mesa/examples/basic/schelling/app.py +0 -42
- mesa/examples/basic/schelling/model.py +0 -59
- mesa/examples/basic/virus_on_network/Readme.md +0 -61
- mesa/examples/basic/virus_on_network/__init__.py +0 -0
- mesa/examples/basic/virus_on_network/agents.py +0 -69
- mesa/examples/basic/virus_on_network/app.py +0 -114
- mesa/examples/basic/virus_on_network/model.py +0 -96
- mesa/experimental/cell_space/voronoi.py +0 -257
- mesa/experimental/components/matplotlib.py +0 -242
- mesa/visualization/components/__init__.py +0 -83
- mesa/visualization/components/altair_components.py +0 -188
- mesa/visualization/components/matplotlib_components.py +0 -175
- mesa/visualization/mpl_space_drawing.py +0 -593
- mesa/visualization/solara_viz.py +0 -458
- mesa/visualization/user_param.py +0 -69
- mesa/visualization/utils.py +0 -9
- mesa-3.0.0.dist-info/RECORD +0 -95
- mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
- /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
- {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""Various Grid Spaces."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
from collections.abc import Sequence
|
|
@@ -12,8 +10,8 @@ from mesa.experimental.cell_space import Cell, DiscreteSpace
|
|
|
12
10
|
T = TypeVar("T", bound=Cell)
|
|
13
11
|
|
|
14
12
|
|
|
15
|
-
class Grid(DiscreteSpace
|
|
16
|
-
"""Base class for all grid classes
|
|
13
|
+
class Grid(DiscreteSpace, Generic[T]):
|
|
14
|
+
"""Base class for all grid classes
|
|
17
15
|
|
|
18
16
|
Attributes:
|
|
19
17
|
dimensions (Sequence[int]): the dimensions of the grid
|
|
@@ -22,21 +20,8 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
22
20
|
random (Random): the random number generator
|
|
23
21
|
_try_random (bool): whether to get empty cell be repeatedly trying random cell
|
|
24
22
|
|
|
25
|
-
Notes:
|
|
26
|
-
width and height are accessible via properties, higher dimensions can be retrieved via dimensions
|
|
27
|
-
|
|
28
23
|
"""
|
|
29
24
|
|
|
30
|
-
@property
|
|
31
|
-
def width(self) -> int:
|
|
32
|
-
"""Convenience access to the width of the grid."""
|
|
33
|
-
return self.dimensions[0]
|
|
34
|
-
|
|
35
|
-
@property
|
|
36
|
-
def height(self) -> int:
|
|
37
|
-
"""Convenience access to the height of the grid."""
|
|
38
|
-
return self.dimensions[1]
|
|
39
|
-
|
|
40
25
|
def __init__(
|
|
41
26
|
self,
|
|
42
27
|
dimensions: Sequence[int],
|
|
@@ -45,15 +30,6 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
45
30
|
random: Random | None = None,
|
|
46
31
|
cell_klass: type[T] = Cell,
|
|
47
32
|
) -> None:
|
|
48
|
-
"""Initialise the grid class.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
dimensions: the dimensions of the space
|
|
52
|
-
torus: whether the space wraps
|
|
53
|
-
capacity: capacity of the grid cell
|
|
54
|
-
random: a random number generator
|
|
55
|
-
cell_klass: the base class to use for the cells
|
|
56
|
-
"""
|
|
57
33
|
super().__init__(capacity=capacity, random=random, cell_klass=cell_klass)
|
|
58
34
|
self.torus = torus
|
|
59
35
|
self.dimensions = dimensions
|
|
@@ -87,7 +63,7 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
87
63
|
if self.capacity is not None and not isinstance(self.capacity, float | int):
|
|
88
64
|
raise ValueError("Capacity must be a number or None.")
|
|
89
65
|
|
|
90
|
-
def select_random_empty_cell(self) -> T:
|
|
66
|
+
def select_random_empty_cell(self) -> T:
|
|
91
67
|
# FIXME:: currently just a simple boolean to control behavior
|
|
92
68
|
# FIXME:: basically if grid is close to 99% full, creating empty list can be faster
|
|
93
69
|
# FIXME:: note however that the old results don't apply because in this implementation
|
|
@@ -113,7 +89,7 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
113
89
|
if self.torus:
|
|
114
90
|
n_coord = tuple(nc % d for nc, d in zip(n_coord, self.dimensions))
|
|
115
91
|
if all(0 <= nc < d for nc, d in zip(n_coord, self.dimensions)):
|
|
116
|
-
cell.connect(self._cells[n_coord]
|
|
92
|
+
cell.connect(self._cells[n_coord])
|
|
117
93
|
|
|
118
94
|
def _connect_single_cell_2d(self, cell: T, offsets: list[tuple[int, int]]) -> None:
|
|
119
95
|
i, j = cell.coordinate
|
|
@@ -124,7 +100,7 @@ class Grid(DiscreteSpace[T], Generic[T]):
|
|
|
124
100
|
if self.torus:
|
|
125
101
|
ni, nj = ni % height, nj % width
|
|
126
102
|
if 0 <= ni < height and 0 <= nj < width:
|
|
127
|
-
cell.connect(self._cells[ni, nj]
|
|
103
|
+
cell.connect(self._cells[ni, nj])
|
|
128
104
|
|
|
129
105
|
|
|
130
106
|
class OrthogonalMooreGrid(Grid[T]):
|
|
@@ -146,6 +122,7 @@ class OrthogonalMooreGrid(Grid[T]):
|
|
|
146
122
|
( 1, -1), ( 1, 0), ( 1, 1),
|
|
147
123
|
]
|
|
148
124
|
# fmt: on
|
|
125
|
+
height, width = self.dimensions
|
|
149
126
|
|
|
150
127
|
for cell in self.all_cells:
|
|
151
128
|
self._connect_single_cell_2d(cell, offsets)
|
|
@@ -177,12 +154,13 @@ class OrthogonalVonNeumannGrid(Grid[T]):
|
|
|
177
154
|
( 1, 0),
|
|
178
155
|
]
|
|
179
156
|
# fmt: on
|
|
157
|
+
height, width = self.dimensions
|
|
180
158
|
|
|
181
159
|
for cell in self.all_cells:
|
|
182
160
|
self._connect_single_cell_2d(cell, offsets)
|
|
183
161
|
|
|
184
162
|
def _connect_cells_nd(self) -> None:
|
|
185
|
-
offsets
|
|
163
|
+
offsets = []
|
|
186
164
|
dimensions = len(self.dimensions)
|
|
187
165
|
for dim in range(dimensions):
|
|
188
166
|
for delta in [
|
|
@@ -198,8 +176,6 @@ class OrthogonalVonNeumannGrid(Grid[T]):
|
|
|
198
176
|
|
|
199
177
|
|
|
200
178
|
class HexGrid(Grid[T]):
|
|
201
|
-
"""A Grid with hexagonal tilling of the space."""
|
|
202
|
-
|
|
203
179
|
def _connect_cells_2d(self) -> None:
|
|
204
180
|
# fmt: off
|
|
205
181
|
even_offsets = [
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""A Network grid."""
|
|
2
|
-
|
|
3
1
|
from random import Random
|
|
4
2
|
from typing import Any
|
|
5
3
|
|
|
@@ -7,8 +5,8 @@ from mesa.experimental.cell_space.cell import Cell
|
|
|
7
5
|
from mesa.experimental.cell_space.discrete_space import DiscreteSpace
|
|
8
6
|
|
|
9
7
|
|
|
10
|
-
class Network(DiscreteSpace
|
|
11
|
-
"""A networked discrete space
|
|
8
|
+
class Network(DiscreteSpace):
|
|
9
|
+
"""A networked discrete space"""
|
|
12
10
|
|
|
13
11
|
def __init__(
|
|
14
12
|
self,
|
|
@@ -17,13 +15,13 @@ class Network(DiscreteSpace[Cell]):
|
|
|
17
15
|
random: Random | None = None,
|
|
18
16
|
cell_klass: type[Cell] = Cell,
|
|
19
17
|
) -> None:
|
|
20
|
-
"""A Networked grid
|
|
18
|
+
"""A Networked grid
|
|
21
19
|
|
|
22
20
|
Args:
|
|
23
21
|
G: a NetworkX Graph instance.
|
|
24
22
|
capacity (int) : the capacity of the cell
|
|
25
|
-
random (Random):
|
|
26
|
-
|
|
23
|
+
random (Random):
|
|
24
|
+
CellKlass (type[Cell]): The base Cell class to use in the Network
|
|
27
25
|
|
|
28
26
|
"""
|
|
29
27
|
super().__init__(capacity=capacity, random=random, cell_klass=cell_klass)
|
|
@@ -34,12 +32,9 @@ class Network(DiscreteSpace[Cell]):
|
|
|
34
32
|
node_id, capacity, random=self.random
|
|
35
33
|
)
|
|
36
34
|
|
|
37
|
-
self._connect_cells()
|
|
38
|
-
|
|
39
|
-
def _connect_cells(self) -> None:
|
|
40
35
|
for cell in self.all_cells:
|
|
41
36
|
self._connect_single_cell(cell)
|
|
42
37
|
|
|
43
|
-
def _connect_single_cell(self, cell
|
|
38
|
+
def _connect_single_cell(self, cell):
|
|
44
39
|
for node_id in self.G.neighbors(cell.coordinate):
|
|
45
|
-
cell.connect(self._cells[node_id]
|
|
40
|
+
cell.connect(self._cells[node_id])
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""Eventlist which is at the core of event scheduling."""
|
|
2
|
-
|
|
3
1
|
from __future__ import annotations
|
|
4
2
|
|
|
5
3
|
import itertools
|
|
@@ -12,17 +10,15 @@ from weakref import WeakMethod, ref
|
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class Priority(IntEnum):
|
|
15
|
-
"""Enumeration of priority levels."""
|
|
16
|
-
|
|
17
13
|
LOW = 10
|
|
18
14
|
DEFAULT = 5
|
|
19
15
|
HIGH = 1
|
|
20
16
|
|
|
21
17
|
|
|
22
18
|
class SimulationEvent:
|
|
23
|
-
"""A simulation event
|
|
19
|
+
"""A simulation event
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
the callable is wrapped using weakref, so there is no need to explicitly cancel event if e.g., an agent
|
|
26
22
|
is removed from the simulation.
|
|
27
23
|
|
|
28
24
|
Attributes:
|
|
@@ -33,18 +29,12 @@ class SimulationEvent:
|
|
|
33
29
|
function_args (list[Any]): Argument for the function
|
|
34
30
|
function_kwargs (Dict[str, Any]): Keyword arguments for the function
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
Notes:
|
|
38
|
-
simulation events use a weak reference to the callable. Therefore, you cannot pass a lambda function in fn.
|
|
39
|
-
A simulation event where the callable no longer exists (e.g., because the agent has been removed from the model)
|
|
40
|
-
will fail silently.
|
|
41
|
-
|
|
42
32
|
"""
|
|
43
33
|
|
|
44
34
|
_ids = itertools.count()
|
|
45
35
|
|
|
46
36
|
@property
|
|
47
|
-
def CANCELED(self) -> bool:
|
|
37
|
+
def CANCELED(self) -> bool:
|
|
48
38
|
return self._canceled
|
|
49
39
|
|
|
50
40
|
def __init__(
|
|
@@ -55,15 +45,6 @@ class SimulationEvent:
|
|
|
55
45
|
function_args: list[Any] | None = None,
|
|
56
46
|
function_kwargs: dict[str, Any] | None = None,
|
|
57
47
|
) -> None:
|
|
58
|
-
"""Initialize a simulation event.
|
|
59
|
-
|
|
60
|
-
Args:
|
|
61
|
-
time: the instant of time of the simulation event
|
|
62
|
-
function: the callable to invoke
|
|
63
|
-
priority: the priority of the event
|
|
64
|
-
function_args: arguments for callable
|
|
65
|
-
function_kwargs: keyword arguments for the callable
|
|
66
|
-
"""
|
|
67
48
|
super().__init__()
|
|
68
49
|
if not callable(function):
|
|
69
50
|
raise Exception()
|
|
@@ -83,20 +64,20 @@ class SimulationEvent:
|
|
|
83
64
|
self.function_kwargs = function_kwargs if function_kwargs else {}
|
|
84
65
|
|
|
85
66
|
def execute(self):
|
|
86
|
-
"""
|
|
67
|
+
"""execute this event"""
|
|
87
68
|
if not self._canceled:
|
|
88
69
|
fn = self.fn()
|
|
89
70
|
if fn is not None:
|
|
90
71
|
fn(*self.function_args, **self.function_kwargs)
|
|
91
72
|
|
|
92
73
|
def cancel(self) -> None:
|
|
93
|
-
"""
|
|
74
|
+
"""cancel this event"""
|
|
94
75
|
self._canceled = True
|
|
95
76
|
self.fn = None
|
|
96
77
|
self.function_args = []
|
|
97
78
|
self.function_kwargs = {}
|
|
98
79
|
|
|
99
|
-
def __lt__(self, other):
|
|
80
|
+
def __lt__(self, other):
|
|
100
81
|
# Define a total ordering for events to be used by the heapq
|
|
101
82
|
return (self.time, self.priority, self.unique_id) < (
|
|
102
83
|
other.time,
|
|
@@ -106,31 +87,30 @@ class SimulationEvent:
|
|
|
106
87
|
|
|
107
88
|
|
|
108
89
|
class EventList:
|
|
109
|
-
"""An event list
|
|
90
|
+
"""An event list
|
|
110
91
|
|
|
111
92
|
This is a heap queue sorted list of events. Events are always removed from the left, so heapq is a performant and
|
|
112
93
|
appropriate data structure. Events are sorted based on their time stamp, their priority, and their unique_id
|
|
113
94
|
as a tie-breaker, guaranteeing a complete ordering.
|
|
114
95
|
|
|
115
|
-
|
|
116
96
|
"""
|
|
117
97
|
|
|
118
98
|
def __init__(self):
|
|
119
|
-
"""Initialize an event list."""
|
|
120
99
|
self._events: list[SimulationEvent] = []
|
|
121
100
|
heapify(self._events)
|
|
122
101
|
|
|
123
102
|
def add_event(self, event: SimulationEvent):
|
|
124
|
-
"""Add the event to the event list
|
|
103
|
+
"""Add the event to the event list
|
|
125
104
|
|
|
126
105
|
Args:
|
|
127
106
|
event (SimulationEvent): The event to be added
|
|
128
107
|
|
|
129
108
|
"""
|
|
109
|
+
|
|
130
110
|
heappush(self._events, event)
|
|
131
111
|
|
|
132
112
|
def peak_ahead(self, n: int = 1) -> list[SimulationEvent]:
|
|
133
|
-
"""Look at the first n non-canceled event in the event list
|
|
113
|
+
"""Look at the first n non-canceled event in the event list
|
|
134
114
|
|
|
135
115
|
Args:
|
|
136
116
|
n (int): The number of events to look ahead
|
|
@@ -159,7 +139,7 @@ class EventList:
|
|
|
159
139
|
return peek
|
|
160
140
|
|
|
161
141
|
def pop_event(self) -> SimulationEvent:
|
|
162
|
-
"""
|
|
142
|
+
"""pop the first element from the event list"""
|
|
163
143
|
while self._events:
|
|
164
144
|
event = heappop(self._events)
|
|
165
145
|
if not event.CANCELED:
|
|
@@ -167,33 +147,16 @@ class EventList:
|
|
|
167
147
|
raise IndexError("Event list is empty")
|
|
168
148
|
|
|
169
149
|
def is_empty(self) -> bool:
|
|
170
|
-
"""Return whether the event list is empty."""
|
|
171
150
|
return len(self) == 0
|
|
172
151
|
|
|
173
|
-
def __contains__(self, event: SimulationEvent) -> bool:
|
|
152
|
+
def __contains__(self, event: SimulationEvent) -> bool:
|
|
174
153
|
return event in self._events
|
|
175
154
|
|
|
176
|
-
def __len__(self) -> int:
|
|
155
|
+
def __len__(self) -> int:
|
|
177
156
|
return len(self._events)
|
|
178
157
|
|
|
179
|
-
def __repr__(self) -> str:
|
|
180
|
-
"""Return a string representation of the event list."""
|
|
181
|
-
events_str = ", ".join(
|
|
182
|
-
[
|
|
183
|
-
f"Event(time={e.time}, priority={e.priority}, id={e.unique_id})"
|
|
184
|
-
for e in self._events
|
|
185
|
-
if not e.CANCELED
|
|
186
|
-
]
|
|
187
|
-
)
|
|
188
|
-
return f"EventList([{events_str}])"
|
|
189
|
-
|
|
190
158
|
def remove(self, event: SimulationEvent) -> None:
|
|
191
|
-
"""
|
|
192
|
-
|
|
193
|
-
Args:
|
|
194
|
-
event (SimulationEvent): The event to be removed
|
|
195
|
-
|
|
196
|
-
"""
|
|
159
|
+
"""remove an event from the event list"""
|
|
197
160
|
# we cannot simply remove items from _eventlist because this breaks
|
|
198
161
|
# heap structure invariant. So, we use a form of lazy deletion.
|
|
199
162
|
# SimEvents have a CANCELED flag that we set to True, while popping and peak_ahead
|
|
@@ -201,5 +164,4 @@ class EventList:
|
|
|
201
164
|
event.cancel()
|
|
202
165
|
|
|
203
166
|
def clear(self):
|
|
204
|
-
"""Clear the event list."""
|
|
205
167
|
self._events.clear()
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
"""Epstein civil violence example using ABMSimulator."""
|
|
2
|
-
|
|
3
1
|
import enum
|
|
4
2
|
import math
|
|
5
3
|
|
|
@@ -9,32 +7,21 @@ from mesa.space import SingleGrid
|
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class EpsteinAgent(Agent):
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def __init__(self, model, vision, movement):
|
|
15
|
-
"""Initialize the agent.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
model: a model instance
|
|
19
|
-
vision: size of neighborhood
|
|
20
|
-
movement: boolean whether agent can move or not
|
|
21
|
-
"""
|
|
22
|
-
super().__init__(model)
|
|
10
|
+
def __init__(self, unique_id, model, vision, movement):
|
|
11
|
+
super().__init__(unique_id, model)
|
|
23
12
|
self.vision = vision
|
|
24
13
|
self.movement = movement
|
|
25
14
|
|
|
26
15
|
|
|
27
16
|
class AgentState(enum.IntEnum):
|
|
28
|
-
"""Agent states."""
|
|
29
|
-
|
|
30
17
|
QUIESCENT = enum.auto()
|
|
31
18
|
ARRESTED = enum.auto()
|
|
32
19
|
ACTIVE = enum.auto()
|
|
33
20
|
|
|
34
21
|
|
|
35
22
|
class Citizen(EpsteinAgent):
|
|
36
|
-
"""
|
|
37
|
-
|
|
23
|
+
"""
|
|
24
|
+
A member of the general population, may or may not be in active rebellion.
|
|
38
25
|
Summary of rule: If grievance - risk > threshold, rebel.
|
|
39
26
|
|
|
40
27
|
Attributes:
|
|
@@ -59,6 +46,7 @@ class Citizen(EpsteinAgent):
|
|
|
59
46
|
|
|
60
47
|
def __init__(
|
|
61
48
|
self,
|
|
49
|
+
unique_id,
|
|
62
50
|
model,
|
|
63
51
|
vision,
|
|
64
52
|
movement,
|
|
@@ -68,13 +56,11 @@ class Citizen(EpsteinAgent):
|
|
|
68
56
|
threshold,
|
|
69
57
|
arrest_prob_constant,
|
|
70
58
|
):
|
|
71
|
-
"""
|
|
72
|
-
|
|
59
|
+
"""
|
|
60
|
+
Create a new Citizen.
|
|
73
61
|
Args:
|
|
62
|
+
unique_id: unique int
|
|
74
63
|
model : model instance
|
|
75
|
-
vision: number of cells in each direction (N, S, E and W) that
|
|
76
|
-
agent can inspect. Exogenous.
|
|
77
|
-
movement: whether agent can move or not
|
|
78
64
|
hardship: Agent's 'perceived hardship (i.e., physical or economic
|
|
79
65
|
privation).' Exogenous, drawn from U(0,1).
|
|
80
66
|
regime_legitimacy: Agent's perception of regime legitimacy, equal
|
|
@@ -82,10 +68,10 @@ class Citizen(EpsteinAgent):
|
|
|
82
68
|
risk_aversion: Exogenous, drawn from U(0,1).
|
|
83
69
|
threshold: if (grievance - (risk_aversion * arrest_probability)) >
|
|
84
70
|
threshold, go/remain Active
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
vision: number of cells in each direction (N, S, E and W) that
|
|
72
|
+
agent can inspect. Exogenous.
|
|
87
73
|
"""
|
|
88
|
-
super().__init__(model, vision, movement)
|
|
74
|
+
super().__init__(unique_id, model, vision, movement)
|
|
89
75
|
self.hardship = hardship
|
|
90
76
|
self.regime_legitimacy = regime_legitimacy
|
|
91
77
|
self.risk_aversion = risk_aversion
|
|
@@ -96,7 +82,9 @@ class Citizen(EpsteinAgent):
|
|
|
96
82
|
self.arrest_prob_constant = arrest_prob_constant
|
|
97
83
|
|
|
98
84
|
def step(self):
|
|
99
|
-
"""
|
|
85
|
+
"""
|
|
86
|
+
Decide whether to activate, then move if applicable.
|
|
87
|
+
"""
|
|
100
88
|
self.update_neighbors()
|
|
101
89
|
self.update_estimated_arrest_probability()
|
|
102
90
|
net_risk = self.risk_aversion * self.arrest_probability
|
|
@@ -109,7 +97,9 @@ class Citizen(EpsteinAgent):
|
|
|
109
97
|
self.model.grid.move_agent(self, new_pos)
|
|
110
98
|
|
|
111
99
|
def update_neighbors(self):
|
|
112
|
-
"""
|
|
100
|
+
"""
|
|
101
|
+
Look around and see who my neighbors are
|
|
102
|
+
"""
|
|
113
103
|
self.neighborhood = self.model.grid.get_neighborhood(
|
|
114
104
|
self.pos, moore=True, radius=self.vision
|
|
115
105
|
)
|
|
@@ -119,7 +109,10 @@ class Citizen(EpsteinAgent):
|
|
|
119
109
|
]
|
|
120
110
|
|
|
121
111
|
def update_estimated_arrest_probability(self):
|
|
122
|
-
"""
|
|
112
|
+
"""
|
|
113
|
+
Based on the ratio of cops to actives in my neighborhood, estimate the
|
|
114
|
+
p(Arrest | I go active).
|
|
115
|
+
"""
|
|
123
116
|
cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)])
|
|
124
117
|
actives_in_vision = 1.0 # citizen counts herself
|
|
125
118
|
for c in self.neighbors:
|
|
@@ -130,25 +123,18 @@ class Citizen(EpsteinAgent):
|
|
|
130
123
|
)
|
|
131
124
|
|
|
132
125
|
def sent_to_jail(self, value):
|
|
133
|
-
"""Sent agent to jail.
|
|
134
|
-
|
|
135
|
-
Args:
|
|
136
|
-
value: duration of jail sentence
|
|
137
|
-
|
|
138
|
-
"""
|
|
139
126
|
self.model.active_agents.remove(self)
|
|
140
127
|
self.condition = AgentState.ARRESTED
|
|
141
128
|
self.model.simulator.schedule_event_relative(self.release_from_jail, value)
|
|
142
129
|
|
|
143
130
|
def release_from_jail(self):
|
|
144
|
-
"""Release agent from jail."""
|
|
145
131
|
self.model.active_agents.add(self)
|
|
146
132
|
self.condition = AgentState.QUIESCENT
|
|
147
133
|
|
|
148
134
|
|
|
149
135
|
class Cop(EpsteinAgent):
|
|
150
|
-
"""
|
|
151
|
-
|
|
136
|
+
"""
|
|
137
|
+
A cop for life. No defection.
|
|
152
138
|
Summary of rule: Inspect local vision and arrest a random active agent.
|
|
153
139
|
|
|
154
140
|
Attributes:
|
|
@@ -158,20 +144,15 @@ class Cop(EpsteinAgent):
|
|
|
158
144
|
able to inspect
|
|
159
145
|
"""
|
|
160
146
|
|
|
161
|
-
def __init__(self, model, vision, movement, max_jail_term):
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
model: a model instance
|
|
166
|
-
vision: size of neighborhood
|
|
167
|
-
movement: whether agent can move or not
|
|
168
|
-
max_jail_term: maximum jail sentence
|
|
169
|
-
"""
|
|
170
|
-
super().__init__(model, vision, movement)
|
|
147
|
+
def __init__(self, unique_id, model, vision, movement, max_jail_term):
|
|
148
|
+
super().__init__(unique_id, model, vision, movement)
|
|
171
149
|
self.max_jail_term = max_jail_term
|
|
172
150
|
|
|
173
151
|
def step(self):
|
|
174
|
-
"""
|
|
152
|
+
"""
|
|
153
|
+
Inspect local vision and arrest a random active agent. Move if
|
|
154
|
+
applicable.
|
|
155
|
+
"""
|
|
175
156
|
self.update_neighbors()
|
|
176
157
|
active_neighbors = []
|
|
177
158
|
for agent in self.neighbors:
|
|
@@ -185,7 +166,9 @@ class Cop(EpsteinAgent):
|
|
|
185
166
|
self.model.grid.move_agent(self, new_pos)
|
|
186
167
|
|
|
187
168
|
def update_neighbors(self):
|
|
188
|
-
"""
|
|
169
|
+
"""
|
|
170
|
+
Look around and see who my neighbors are.
|
|
171
|
+
"""
|
|
189
172
|
self.neighborhood = self.model.grid.get_neighborhood(
|
|
190
173
|
self.pos, moore=True, radius=self.vision
|
|
191
174
|
)
|
|
@@ -196,8 +179,9 @@ class Cop(EpsteinAgent):
|
|
|
196
179
|
|
|
197
180
|
|
|
198
181
|
class EpsteinCivilViolence(Model):
|
|
199
|
-
"""
|
|
200
|
-
|
|
182
|
+
"""
|
|
183
|
+
Model 1 from "Modeling civil violence: An agent-based computational
|
|
184
|
+
approach," by Joshua Epstein.
|
|
201
185
|
http://www.pnas.org/content/99/suppl_3/7243.full
|
|
202
186
|
Attributes:
|
|
203
187
|
height: grid height
|
|
@@ -236,23 +220,6 @@ class EpsteinCivilViolence(Model):
|
|
|
236
220
|
max_iters=1000,
|
|
237
221
|
seed=None,
|
|
238
222
|
):
|
|
239
|
-
"""Initialize the Eppstein civil violence model.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
width: the width of the grid
|
|
243
|
-
height: the height of the grid
|
|
244
|
-
citizen_density: density of citizens
|
|
245
|
-
cop_density: density of cops
|
|
246
|
-
citizen_vision: size of citizen vision
|
|
247
|
-
cop_vision: size of cop vision
|
|
248
|
-
legitimacy: perceived legitimacy
|
|
249
|
-
max_jail_term: maximum jail term
|
|
250
|
-
active_threshold: threshold for citizen to become active
|
|
251
|
-
arrest_prob_constant: arrest probability
|
|
252
|
-
movement: allow agent movement or not
|
|
253
|
-
max_iters: number of iterations
|
|
254
|
-
seed: seed for random number generator
|
|
255
|
-
"""
|
|
256
223
|
super().__init__(seed)
|
|
257
224
|
if cop_density + citizen_density > 1:
|
|
258
225
|
raise ValueError("Cop density + citizen density must be less than 1")
|
|
@@ -269,6 +236,7 @@ class EpsteinCivilViolence(Model):
|
|
|
269
236
|
for _, pos in self.grid.coord_iter():
|
|
270
237
|
if self.random.random() < self.cop_density:
|
|
271
238
|
agent = Cop(
|
|
239
|
+
self.next_id(),
|
|
272
240
|
self,
|
|
273
241
|
cop_vision,
|
|
274
242
|
movement,
|
|
@@ -276,6 +244,7 @@ class EpsteinCivilViolence(Model):
|
|
|
276
244
|
)
|
|
277
245
|
elif self.random.random() < (self.cop_density + self.citizen_density):
|
|
278
246
|
agent = Citizen(
|
|
247
|
+
self.next_id(),
|
|
279
248
|
self,
|
|
280
249
|
citizen_vision,
|
|
281
250
|
movement,
|
|
@@ -292,8 +261,7 @@ class EpsteinCivilViolence(Model):
|
|
|
292
261
|
self.active_agents = self.agents
|
|
293
262
|
|
|
294
263
|
def step(self):
|
|
295
|
-
"
|
|
296
|
-
self.active_agents.shuffle_do("step")
|
|
264
|
+
self.active_agents.shuffle(inplace=True).do("step")
|
|
297
265
|
|
|
298
266
|
|
|
299
267
|
if __name__ == "__main__":
|
|
@@ -302,4 +270,4 @@ if __name__ == "__main__":
|
|
|
302
270
|
|
|
303
271
|
simulator.setup(model)
|
|
304
272
|
|
|
305
|
-
simulator.
|
|
273
|
+
simulator.run(time_delta=100)
|