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.

Files changed (104) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +114 -406
  3. mesa/batchrunner.py +27 -54
  4. mesa/cookiecutter-mesa/cookiecutter.json +8 -0
  5. mesa/cookiecutter-mesa/hooks/post_gen_project.py +11 -0
  6. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/README.md +4 -0
  7. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/app.pytemplate +27 -0
  8. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/setup.pytemplate +11 -0
  9. mesa/cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}/model.pytemplate +60 -0
  10. mesa/datacollection.py +29 -140
  11. mesa/experimental/__init__.py +1 -11
  12. mesa/experimental/cell_space/__init__.py +1 -16
  13. mesa/experimental/cell_space/cell.py +23 -93
  14. mesa/experimental/cell_space/cell_agent.py +21 -117
  15. mesa/experimental/cell_space/cell_collection.py +17 -54
  16. mesa/experimental/cell_space/discrete_space.py +8 -92
  17. mesa/experimental/cell_space/grid.py +8 -32
  18. mesa/experimental/cell_space/network.py +7 -12
  19. mesa/experimental/devs/__init__.py +0 -2
  20. mesa/experimental/devs/eventlist.py +14 -52
  21. mesa/experimental/devs/examples/epstein_civil_violence.py +39 -71
  22. mesa/experimental/devs/examples/wolf_sheep.py +45 -45
  23. mesa/experimental/devs/simulator.py +15 -55
  24. mesa/main.py +63 -0
  25. mesa/model.py +83 -211
  26. mesa/space.py +149 -215
  27. mesa/time.py +77 -62
  28. mesa/{experimental → visualization}/UserParam.py +6 -17
  29. mesa/visualization/__init__.py +2 -25
  30. mesa/{experimental → visualization}/components/altair.py +0 -10
  31. mesa/visualization/components/matplotlib.py +134 -0
  32. mesa/{experimental/solara_viz.py → visualization/jupyter_viz.py} +110 -65
  33. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/METADATA +13 -65
  34. mesa-3.0.0a0.dist-info/RECORD +38 -0
  35. mesa-3.0.0.dist-info/licenses/NOTICE → mesa-3.0.0a0.dist-info/licenses/LICENSE +2 -2
  36. mesa/examples/README.md +0 -37
  37. mesa/examples/__init__.py +0 -21
  38. mesa/examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb +0 -116
  39. mesa/examples/advanced/epstein_civil_violence/Readme.md +0 -34
  40. mesa/examples/advanced/epstein_civil_violence/__init__.py +0 -0
  41. mesa/examples/advanced/epstein_civil_violence/agents.py +0 -164
  42. mesa/examples/advanced/epstein_civil_violence/app.py +0 -73
  43. mesa/examples/advanced/epstein_civil_violence/model.py +0 -114
  44. mesa/examples/advanced/pd_grid/Readme.md +0 -43
  45. mesa/examples/advanced/pd_grid/__init__.py +0 -0
  46. mesa/examples/advanced/pd_grid/agents.py +0 -50
  47. mesa/examples/advanced/pd_grid/analysis.ipynb +0 -228
  48. mesa/examples/advanced/pd_grid/app.py +0 -54
  49. mesa/examples/advanced/pd_grid/model.py +0 -71
  50. mesa/examples/advanced/sugarscape_g1mt/Readme.md +0 -64
  51. mesa/examples/advanced/sugarscape_g1mt/__init__.py +0 -0
  52. mesa/examples/advanced/sugarscape_g1mt/agents.py +0 -344
  53. mesa/examples/advanced/sugarscape_g1mt/app.py +0 -62
  54. mesa/examples/advanced/sugarscape_g1mt/model.py +0 -180
  55. mesa/examples/advanced/sugarscape_g1mt/sugar-map.txt +0 -50
  56. mesa/examples/advanced/sugarscape_g1mt/tests.py +0 -69
  57. mesa/examples/advanced/wolf_sheep/Readme.md +0 -57
  58. mesa/examples/advanced/wolf_sheep/__init__.py +0 -0
  59. mesa/examples/advanced/wolf_sheep/agents.py +0 -102
  60. mesa/examples/advanced/wolf_sheep/app.py +0 -84
  61. mesa/examples/advanced/wolf_sheep/model.py +0 -137
  62. mesa/examples/basic/__init__.py +0 -0
  63. mesa/examples/basic/boid_flockers/Readme.md +0 -22
  64. mesa/examples/basic/boid_flockers/__init__.py +0 -0
  65. mesa/examples/basic/boid_flockers/agents.py +0 -71
  66. mesa/examples/basic/boid_flockers/app.py +0 -58
  67. mesa/examples/basic/boid_flockers/model.py +0 -69
  68. mesa/examples/basic/boltzmann_wealth_model/Readme.md +0 -56
  69. mesa/examples/basic/boltzmann_wealth_model/__init__.py +0 -0
  70. mesa/examples/basic/boltzmann_wealth_model/agents.py +0 -31
  71. mesa/examples/basic/boltzmann_wealth_model/app.py +0 -74
  72. mesa/examples/basic/boltzmann_wealth_model/model.py +0 -43
  73. mesa/examples/basic/boltzmann_wealth_model/st_app.py +0 -115
  74. mesa/examples/basic/conways_game_of_life/Readme.md +0 -39
  75. mesa/examples/basic/conways_game_of_life/__init__.py +0 -0
  76. mesa/examples/basic/conways_game_of_life/agents.py +0 -47
  77. mesa/examples/basic/conways_game_of_life/app.py +0 -51
  78. mesa/examples/basic/conways_game_of_life/model.py +0 -31
  79. mesa/examples/basic/conways_game_of_life/st_app.py +0 -72
  80. mesa/examples/basic/schelling/Readme.md +0 -40
  81. mesa/examples/basic/schelling/__init__.py +0 -0
  82. mesa/examples/basic/schelling/agents.py +0 -26
  83. mesa/examples/basic/schelling/analysis.ipynb +0 -205
  84. mesa/examples/basic/schelling/app.py +0 -42
  85. mesa/examples/basic/schelling/model.py +0 -59
  86. mesa/examples/basic/virus_on_network/Readme.md +0 -61
  87. mesa/examples/basic/virus_on_network/__init__.py +0 -0
  88. mesa/examples/basic/virus_on_network/agents.py +0 -69
  89. mesa/examples/basic/virus_on_network/app.py +0 -114
  90. mesa/examples/basic/virus_on_network/model.py +0 -96
  91. mesa/experimental/cell_space/voronoi.py +0 -257
  92. mesa/experimental/components/matplotlib.py +0 -242
  93. mesa/visualization/components/__init__.py +0 -83
  94. mesa/visualization/components/altair_components.py +0 -188
  95. mesa/visualization/components/matplotlib_components.py +0 -175
  96. mesa/visualization/mpl_space_drawing.py +0 -593
  97. mesa/visualization/solara_viz.py +0 -458
  98. mesa/visualization/user_param.py +0 -69
  99. mesa/visualization/utils.py +0 -9
  100. mesa-3.0.0.dist-info/RECORD +0 -95
  101. mesa-3.0.0.dist-info/licenses/LICENSE +0 -202
  102. /mesa/{examples/advanced → cookiecutter-mesa/{{cookiecutter.snake}}/{{cookiecutter.snake}}}/__init__.py +0 -0
  103. {mesa-3.0.0.dist-info → mesa-3.0.0a0.dist-info}/WHEEL +0 -0
  104. {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[T], Generic[T]):
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: # noqa
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], d_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], (di, dj))
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: list[tuple[int, ...]] = []
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[Cell]):
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): a random number generator
26
- cell_klass (type[Cell]): The base Cell class to use in the Network
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: 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], node_id)
40
+ cell.connect(self._cells[node_id])
@@ -1,5 +1,3 @@
1
- """Support for event scheduling."""
2
-
3
1
  from .eventlist import Priority, SimulationEvent
4
2
  from .simulator import ABMSimulator, DEVSimulator
5
3
 
@@ -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
- The callable is wrapped using weakref, so there is no need to explicitly cancel event if e.g., an agent
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: # noqa: D102
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
- """Execute this event."""
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
- """Cancel this event."""
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): # noqa
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
- """Pop the first element from the event list."""
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: # noqa
152
+ def __contains__(self, event: SimulationEvent) -> bool:
174
153
  return event in self._events
175
154
 
176
- def __len__(self) -> int: # noqa
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
- """Remove an event from the event list.
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
- """Epstein Agent."""
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
- """A member of the general population, may or may not be in active rebellion.
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
- """Create a new Citizen.
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
- arrest_prob_constant : agent's assessment of arrest probability
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
- """Decide whether to activate, then move if applicable."""
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
- """Look around and see who my neighbors are."""
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
- """Based on the ratio of cops to actives in my neighborhood, estimate the p(Arrest | I go active)."""
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
- """A cop for life. No defection.
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
- """Initialize a Cop agent.
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
- """Inspect local vision and arrest a random active agent. Move if applicable."""
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
- """Look around and see who my neighbors are."""
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
- """Model 1 from "Modeling civil violence: An agent-based computational approach," by Joshua Epstein.
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
- """Run one step of the model."""
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.run_for(time_delta=100)
273
+ simulator.run(time_delta=100)