Mesa 3.1.0.dev0__py3-none-any.whl → 3.1.1__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 (57) hide show
  1. mesa/__init__.py +3 -3
  2. mesa/agent.py +48 -0
  3. mesa/batchrunner.py +14 -1
  4. mesa/datacollection.py +1 -6
  5. mesa/examples/__init__.py +2 -2
  6. mesa/examples/advanced/epstein_civil_violence/app.py +5 -0
  7. mesa/examples/advanced/pd_grid/agents.py +2 -1
  8. mesa/examples/advanced/pd_grid/app.py +5 -0
  9. mesa/examples/advanced/pd_grid/model.py +3 -5
  10. mesa/examples/advanced/sugarscape_g1mt/agents.py +12 -65
  11. mesa/examples/advanced/sugarscape_g1mt/app.py +24 -19
  12. mesa/examples/advanced/sugarscape_g1mt/model.py +45 -52
  13. mesa/examples/advanced/wolf_sheep/agents.py +3 -1
  14. mesa/examples/advanced/wolf_sheep/model.py +17 -16
  15. mesa/examples/basic/boid_flockers/app.py +5 -0
  16. mesa/examples/basic/boltzmann_wealth_model/app.py +8 -5
  17. mesa/examples/basic/boltzmann_wealth_model/st_app.py +1 -1
  18. mesa/examples/basic/conways_game_of_life/app.py +5 -0
  19. mesa/examples/basic/conways_game_of_life/st_app.py +2 -2
  20. mesa/examples/basic/schelling/agents.py +11 -5
  21. mesa/examples/basic/schelling/app.py +6 -1
  22. mesa/examples/basic/virus_on_network/app.py +5 -0
  23. mesa/experimental/__init__.py +17 -10
  24. mesa/experimental/cell_space/__init__.py +19 -7
  25. mesa/experimental/cell_space/cell.py +22 -37
  26. mesa/experimental/cell_space/cell_agent.py +12 -1
  27. mesa/experimental/cell_space/cell_collection.py +18 -3
  28. mesa/experimental/cell_space/discrete_space.py +15 -64
  29. mesa/experimental/cell_space/grid.py +74 -4
  30. mesa/experimental/cell_space/network.py +13 -1
  31. mesa/experimental/cell_space/property_layer.py +444 -0
  32. mesa/experimental/cell_space/voronoi.py +13 -1
  33. mesa/experimental/devs/__init__.py +20 -2
  34. mesa/experimental/devs/eventlist.py +19 -1
  35. mesa/experimental/devs/simulator.py +24 -8
  36. mesa/experimental/mesa_signals/__init__.py +23 -0
  37. mesa/experimental/mesa_signals/mesa_signal.py +485 -0
  38. mesa/experimental/mesa_signals/observable_collections.py +133 -0
  39. mesa/experimental/mesa_signals/signals_util.py +52 -0
  40. mesa/mesa_logging.py +190 -0
  41. mesa/model.py +17 -23
  42. mesa/visualization/__init__.py +2 -2
  43. mesa/visualization/mpl_space_drawing.py +8 -5
  44. mesa/visualization/solara_viz.py +49 -11
  45. {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/METADATA +1 -1
  46. mesa-3.1.1.dist-info/RECORD +94 -0
  47. {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/WHEEL +1 -1
  48. mesa/experimental/UserParam.py +0 -67
  49. mesa/experimental/components/altair.py +0 -81
  50. mesa/experimental/components/matplotlib.py +0 -242
  51. mesa/experimental/devs/examples/epstein_civil_violence.py +0 -305
  52. mesa/experimental/devs/examples/wolf_sheep.py +0 -250
  53. mesa/experimental/solara_viz.py +0 -453
  54. mesa-3.1.0.dev0.dist-info/RECORD +0 -94
  55. {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/entry_points.txt +0 -0
  56. {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/licenses/LICENSE +0 -0
  57. {mesa-3.1.0.dev0.dist-info → mesa-3.1.1.dist-info}/licenses/NOTICE +0 -0
@@ -1,67 +0,0 @@
1
- """helper classes."""
2
-
3
-
4
- class UserParam: # noqa: D101
5
- _ERROR_MESSAGE = "Missing or malformed inputs for '{}' Option '{}'"
6
-
7
- def maybe_raise_error(self, param_type, valid): # noqa: D102
8
- if valid:
9
- return
10
- msg = self._ERROR_MESSAGE.format(param_type, self.label)
11
- raise ValueError(msg)
12
-
13
-
14
- class Slider(UserParam):
15
- """A number-based slider input with settable increment.
16
-
17
- Example:
18
- slider_option = Slider("My Slider", value=123, min=10, max=200, step=0.1)
19
-
20
- Args:
21
- label: The displayed label in the UI
22
- value: The initial value of the slider
23
- min: The minimum possible value of the slider
24
- max: The maximum possible value of the slider
25
- step: The step between min and max for a range of possible values
26
- dtype: either int or float
27
- """
28
-
29
- def __init__(
30
- self,
31
- label="",
32
- value=None,
33
- min=None,
34
- max=None,
35
- step=1,
36
- dtype=None,
37
- ):
38
- """Slider class.
39
-
40
- Args:
41
- label: The displayed label in the UI
42
- value: The initial value of the slider
43
- min: The minimum possible value of the slider
44
- max: The maximum possible value of the slider
45
- step: The step between min and max for a range of possible values
46
- dtype: either int or float
47
- """
48
- self.label = label
49
- self.value = value
50
- self.min = min
51
- self.max = max
52
- self.step = step
53
-
54
- # Validate option type to make sure values are supplied properly
55
- valid = not (self.value is None or self.min is None or self.max is None)
56
- self.maybe_raise_error("slider", valid)
57
-
58
- if dtype is None:
59
- self.is_float_slider = self._check_values_are_float(value, min, max, step)
60
- else:
61
- self.is_float_slider = dtype is float
62
-
63
- def _check_values_are_float(self, value, min, max, step):
64
- return any(isinstance(n, float) for n in (value, min, max, step))
65
-
66
- def get(self, attr): # noqa: D102
67
- return getattr(self, attr)
@@ -1,81 +0,0 @@
1
- """Altair components."""
2
-
3
- import contextlib
4
-
5
- import solara
6
-
7
- with contextlib.suppress(ImportError):
8
- import altair as alt
9
-
10
-
11
- @solara.component
12
- def SpaceAltair(model, agent_portrayal, dependencies: list[any] | None = None):
13
- """A component that renders a Space using Altair.
14
-
15
- Args:
16
- model: a model instance
17
- agent_portrayal: agent portray specification
18
- dependencies: optional list of dependencies (currently not used)
19
-
20
- """
21
- space = getattr(model, "grid", None)
22
- if space is None:
23
- # Sometimes the space is defined as model.space instead of model.grid
24
- space = model.space
25
- chart = _draw_grid(space, agent_portrayal)
26
- solara.FigureAltair(chart)
27
-
28
-
29
- def _draw_grid(space, agent_portrayal):
30
- def portray(g):
31
- all_agent_data = []
32
- for content, (x, y) in g.coord_iter():
33
- if not content:
34
- continue
35
- if not hasattr(content, "__iter__"):
36
- # Is a single grid
37
- content = [content] # noqa: PLW2901
38
- for agent in content:
39
- # use all data from agent portrayal, and add x,y coordinates
40
- agent_data = agent_portrayal(agent)
41
- agent_data["x"] = x
42
- agent_data["y"] = y
43
- all_agent_data.append(agent_data)
44
- return all_agent_data
45
-
46
- all_agent_data = portray(space)
47
- invalid_tooltips = ["color", "size", "x", "y"]
48
-
49
- encoding_dict = {
50
- # no x-axis label
51
- "x": alt.X("x", axis=None, type="ordinal"),
52
- # no y-axis label
53
- "y": alt.Y("y", axis=None, type="ordinal"),
54
- "tooltip": [
55
- alt.Tooltip(key, type=alt.utils.infer_vegalite_type([value]))
56
- for key, value in all_agent_data[0].items()
57
- if key not in invalid_tooltips
58
- ],
59
- }
60
- has_color = "color" in all_agent_data[0]
61
- if has_color:
62
- encoding_dict["color"] = alt.Color("color", type="nominal")
63
- has_size = "size" in all_agent_data[0]
64
- if has_size:
65
- encoding_dict["size"] = alt.Size("size", type="quantitative")
66
-
67
- chart = (
68
- alt.Chart(
69
- alt.Data(values=all_agent_data), encoding=alt.Encoding(**encoding_dict)
70
- )
71
- .mark_point(filled=True)
72
- .properties(width=280, height=280)
73
- # .configure_view(strokeOpacity=0) # hide grid/chart lines
74
- )
75
- # This is the default value for the marker size, which auto-scales
76
- # according to the grid area.
77
- if not has_size:
78
- length = min(space.width, space.height)
79
- chart = chart.mark_point(size=30000 / length**2, filled=True)
80
-
81
- return chart
@@ -1,242 +0,0 @@
1
- """Support for using matplotlib to draw spaces."""
2
-
3
- from collections import defaultdict
4
-
5
- import networkx as nx
6
- import solara
7
- from matplotlib.figure import Figure
8
- from matplotlib.ticker import MaxNLocator
9
-
10
- import mesa
11
- from mesa.experimental.cell_space import VoronoiGrid
12
-
13
-
14
- @solara.component
15
- def SpaceMatplotlib(model, agent_portrayal, dependencies: list[any] | None = None):
16
- """A component for rendering a space using Matplotlib.
17
-
18
- Args:
19
- model: a model instance
20
- agent_portrayal: a specification of how to portray an agent.
21
- dependencies: list of dependencies.
22
-
23
- """
24
- space_fig = Figure()
25
- space_ax = space_fig.subplots()
26
- space = getattr(model, "grid", None)
27
- if space is None:
28
- # Sometimes the space is defined as model.space instead of model.grid
29
- space = model.space
30
- if isinstance(space, mesa.space.NetworkGrid):
31
- _draw_network_grid(space, space_ax, agent_portrayal)
32
- elif isinstance(space, mesa.space.ContinuousSpace):
33
- _draw_continuous_space(space, space_ax, agent_portrayal)
34
- elif isinstance(space, VoronoiGrid):
35
- _draw_voronoi(space, space_ax, agent_portrayal)
36
- else:
37
- _draw_grid(space, space_ax, agent_portrayal)
38
- solara.FigureMatplotlib(space_fig, format="png", dependencies=dependencies)
39
-
40
-
41
- # matplotlib scatter does not allow for multiple shapes in one call
42
- def _split_and_scatter(portray_data, space_ax):
43
- grouped_data = defaultdict(lambda: {"x": [], "y": [], "s": [], "c": []})
44
-
45
- # Extract data from the dictionary
46
- x = portray_data["x"]
47
- y = portray_data["y"]
48
- s = portray_data["s"]
49
- c = portray_data["c"]
50
- m = portray_data["m"]
51
-
52
- if not (len(x) == len(y) == len(s) == len(c) == len(m)):
53
- raise ValueError(
54
- "Length mismatch in portrayal data lists: "
55
- f"x: {len(x)}, y: {len(y)}, size: {len(s)}, "
56
- f"color: {len(c)}, marker: {len(m)}"
57
- )
58
-
59
- # Group the data by marker
60
- for i in range(len(x)):
61
- marker = m[i]
62
- grouped_data[marker]["x"].append(x[i])
63
- grouped_data[marker]["y"].append(y[i])
64
- grouped_data[marker]["s"].append(s[i])
65
- grouped_data[marker]["c"].append(c[i])
66
-
67
- # Plot each group with the same marker
68
- for marker, data in grouped_data.items():
69
- space_ax.scatter(data["x"], data["y"], s=data["s"], c=data["c"], marker=marker)
70
-
71
-
72
- def _draw_grid(space, space_ax, agent_portrayal):
73
- def portray(g):
74
- x = []
75
- y = []
76
- s = [] # size
77
- c = [] # color
78
- m = [] # shape
79
- for i in range(g.width):
80
- for j in range(g.height):
81
- content = g._grid[i][j]
82
- if not content:
83
- continue
84
- if not hasattr(content, "__iter__"):
85
- # Is a single grid
86
- content = [content]
87
- for agent in content:
88
- data = agent_portrayal(agent)
89
- x.append(i)
90
- y.append(j)
91
-
92
- # This is the default value for the marker size, which auto-scales
93
- # according to the grid area.
94
- default_size = (180 / max(g.width, g.height)) ** 2
95
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
96
- size = data.get("size", default_size)
97
- s.append(size)
98
- color = data.get("color", "tab:blue")
99
- c.append(color)
100
- mark = data.get("shape", "o")
101
- m.append(mark)
102
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
103
- return out
104
-
105
- space_ax.set_xlim(-1, space.width)
106
- space_ax.set_ylim(-1, space.height)
107
- _split_and_scatter(portray(space), space_ax)
108
-
109
-
110
- def _draw_network_grid(space, space_ax, agent_portrayal):
111
- graph = space.G
112
- pos = nx.spring_layout(graph, seed=0)
113
- nx.draw(
114
- graph,
115
- ax=space_ax,
116
- pos=pos,
117
- **agent_portrayal(graph),
118
- )
119
-
120
-
121
- def _draw_continuous_space(space, space_ax, agent_portrayal):
122
- def portray(space):
123
- x = []
124
- y = []
125
- s = [] # size
126
- c = [] # color
127
- m = [] # shape
128
- for agent in space._agent_to_index:
129
- data = agent_portrayal(agent)
130
- _x, _y = agent.pos
131
- x.append(_x)
132
- y.append(_y)
133
-
134
- # This is matplotlib's default marker size
135
- default_size = 20
136
- # establishing a default prevents misalignment if some agents are not given size, color, etc.
137
- size = data.get("size", default_size)
138
- s.append(size)
139
- color = data.get("color", "tab:blue")
140
- c.append(color)
141
- mark = data.get("shape", "o")
142
- m.append(mark)
143
- out = {"x": x, "y": y, "s": s, "c": c, "m": m}
144
- return out
145
-
146
- # Determine border style based on space.torus
147
- border_style = "solid" if not space.torus else (0, (5, 10))
148
-
149
- # Set the border of the plot
150
- for spine in space_ax.spines.values():
151
- spine.set_linewidth(1.5)
152
- spine.set_color("black")
153
- spine.set_linestyle(border_style)
154
-
155
- width = space.x_max - space.x_min
156
- x_padding = width / 20
157
- height = space.y_max - space.y_min
158
- y_padding = height / 20
159
- space_ax.set_xlim(space.x_min - x_padding, space.x_max + x_padding)
160
- space_ax.set_ylim(space.y_min - y_padding, space.y_max + y_padding)
161
-
162
- # Portray and scatter the agents in the space
163
- _split_and_scatter(portray(space), space_ax)
164
-
165
-
166
- def _draw_voronoi(space, space_ax, agent_portrayal):
167
- def portray(g):
168
- x = []
169
- y = []
170
- s = [] # size
171
- c = [] # color
172
-
173
- for cell in g.all_cells:
174
- for agent in cell.agents:
175
- data = agent_portrayal(agent)
176
- x.append(cell.coordinate[0])
177
- y.append(cell.coordinate[1])
178
- if "size" in data:
179
- s.append(data["size"])
180
- if "color" in data:
181
- c.append(data["color"])
182
- out = {"x": x, "y": y}
183
- # This is the default value for the marker size, which auto-scales
184
- # according to the grid area.
185
- out["s"] = s
186
- if len(c) > 0:
187
- out["c"] = c
188
-
189
- return out
190
-
191
- x_list = [i[0] for i in space.centroids_coordinates]
192
- y_list = [i[1] for i in space.centroids_coordinates]
193
- x_max = max(x_list)
194
- x_min = min(x_list)
195
- y_max = max(y_list)
196
- y_min = min(y_list)
197
-
198
- width = x_max - x_min
199
- x_padding = width / 20
200
- height = y_max - y_min
201
- y_padding = height / 20
202
- space_ax.set_xlim(x_min - x_padding, x_max + x_padding)
203
- space_ax.set_ylim(y_min - y_padding, y_max + y_padding)
204
- space_ax.scatter(**portray(space))
205
-
206
- for cell in space.all_cells:
207
- polygon = cell.properties["polygon"]
208
- space_ax.fill(
209
- *zip(*polygon),
210
- alpha=min(1, cell.properties[space.cell_coloring_property]),
211
- c="red",
212
- ) # Plot filled polygon
213
- space_ax.plot(*zip(*polygon), color="black") # Plot polygon edges in red
214
-
215
-
216
- @solara.component
217
- def PlotMatplotlib(model, measure, dependencies: list[any] | None = None):
218
- """A solara component for creating a matplotlib figure.
219
-
220
- Args:
221
- model: Model instance
222
- measure: measure to plot
223
- dependencies: list of additional dependencies
224
-
225
- """
226
- fig = Figure()
227
- ax = fig.subplots()
228
- df = model.datacollector.get_model_vars_dataframe()
229
- if isinstance(measure, str):
230
- ax.plot(df.loc[:, measure])
231
- ax.set_ylabel(measure)
232
- elif isinstance(measure, dict):
233
- for m, color in measure.items():
234
- ax.plot(df.loc[:, m], label=m, color=color)
235
- fig.legend()
236
- elif isinstance(measure, list | tuple):
237
- for m in measure:
238
- ax.plot(df.loc[:, m], label=m)
239
- fig.legend()
240
- # Set integer x axis
241
- ax.xaxis.set_major_locator(MaxNLocator(integer=True))
242
- solara.FigureMatplotlib(fig, dependencies=dependencies)
@@ -1,305 +0,0 @@
1
- """Epstein civil violence example using ABMSimulator."""
2
-
3
- import enum
4
- import math
5
-
6
- from mesa import Agent, Model
7
- from mesa.experimental.devs.simulator import ABMSimulator
8
- from mesa.space import SingleGrid
9
-
10
-
11
- 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)
23
- self.vision = vision
24
- self.movement = movement
25
-
26
-
27
- class AgentState(enum.IntEnum):
28
- """Agent states."""
29
-
30
- QUIESCENT = enum.auto()
31
- ARRESTED = enum.auto()
32
- ACTIVE = enum.auto()
33
-
34
-
35
- class Citizen(EpsteinAgent):
36
- """A member of the general population, may or may not be in active rebellion.
37
-
38
- Summary of rule: If grievance - risk > threshold, rebel.
39
-
40
- Attributes:
41
- unique_id: unique int
42
- model :
43
- hardship: Agent's 'perceived hardship (i.e., physical or economic
44
- privation).' Exogenous, drawn from U(0,1).
45
- regime_legitimacy: Agent's perception of regime legitimacy, equal
46
- across agents. Exogenous.
47
- risk_aversion: Exogenous, drawn from U(0,1).
48
- threshold: if (grievance - (risk_aversion * arrest_probability)) >
49
- threshold, go/remain Active
50
- vision: number of cells in each direction (N, S, E and W) that agent
51
- can inspect
52
- condition: Can be "Quiescent" or "Active;" deterministic function of
53
- greivance, perceived risk, and
54
- grievance: deterministic function of hardship and regime_legitimacy;
55
- how aggrieved is agent at the regime?
56
- arrest_probability: agent's assessment of arrest probability, given
57
- rebellion
58
- """
59
-
60
- def __init__(
61
- self,
62
- model,
63
- vision,
64
- movement,
65
- hardship,
66
- regime_legitimacy,
67
- risk_aversion,
68
- threshold,
69
- arrest_prob_constant,
70
- ):
71
- """Create a new Citizen.
72
-
73
- Args:
74
- 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
- hardship: Agent's 'perceived hardship (i.e., physical or economic
79
- privation).' Exogenous, drawn from U(0,1).
80
- regime_legitimacy: Agent's perception of regime legitimacy, equal
81
- across agents. Exogenous.
82
- risk_aversion: Exogenous, drawn from U(0,1).
83
- threshold: if (grievance - (risk_aversion * arrest_probability)) >
84
- threshold, go/remain Active
85
- arrest_prob_constant : agent's assessment of arrest probability
86
-
87
- """
88
- super().__init__(model, vision, movement)
89
- self.hardship = hardship
90
- self.regime_legitimacy = regime_legitimacy
91
- self.risk_aversion = risk_aversion
92
- self.threshold = threshold
93
- self.condition = AgentState.QUIESCENT
94
- self.grievance = self.hardship * (1 - self.regime_legitimacy)
95
- self.arrest_probability = None
96
- self.arrest_prob_constant = arrest_prob_constant
97
-
98
- def step(self):
99
- """Decide whether to activate, then move if applicable."""
100
- self.update_neighbors()
101
- self.update_estimated_arrest_probability()
102
- net_risk = self.risk_aversion * self.arrest_probability
103
- if self.grievance - net_risk > self.threshold:
104
- self.condition = AgentState.ACTIVE
105
- else:
106
- self.condition = AgentState.QUIESCENT
107
- if self.movement and self.empty_neighbors:
108
- new_pos = self.random.choice(self.empty_neighbors)
109
- self.model.grid.move_agent(self, new_pos)
110
-
111
- def update_neighbors(self):
112
- """Look around and see who my neighbors are."""
113
- self.neighborhood = self.model.grid.get_neighborhood(
114
- self.pos, moore=True, radius=self.vision
115
- )
116
- self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood)
117
- self.empty_neighbors = [
118
- c for c in self.neighborhood if self.model.grid.is_cell_empty(c)
119
- ]
120
-
121
- 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)."""
123
- cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)])
124
- actives_in_vision = 1.0 # citizen counts herself
125
- for c in self.neighbors:
126
- if isinstance(c, Citizen) and c.condition == AgentState.ACTIVE:
127
- actives_in_vision += 1
128
- self.arrest_probability = 1 - math.exp(
129
- -1 * self.arrest_prob_constant * (cops_in_vision / actives_in_vision)
130
- )
131
-
132
- def sent_to_jail(self, value):
133
- """Sent agent to jail.
134
-
135
- Args:
136
- value: duration of jail sentence
137
-
138
- """
139
- self.model.active_agents.remove(self)
140
- self.condition = AgentState.ARRESTED
141
- self.model.simulator.schedule_event_relative(self.release_from_jail, value)
142
-
143
- def release_from_jail(self):
144
- """Release agent from jail."""
145
- self.model.active_agents.add(self)
146
- self.condition = AgentState.QUIESCENT
147
-
148
-
149
- class Cop(EpsteinAgent):
150
- """A cop for life. No defection.
151
-
152
- Summary of rule: Inspect local vision and arrest a random active agent.
153
-
154
- Attributes:
155
- unique_id: unique int
156
- x, y: Grid coordinates
157
- vision: number of cells in each direction (N, S, E and W) that cop is
158
- able to inspect
159
- """
160
-
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)
171
- self.max_jail_term = max_jail_term
172
-
173
- def step(self):
174
- """Inspect local vision and arrest a random active agent. Move if applicable."""
175
- self.update_neighbors()
176
- active_neighbors = []
177
- for agent in self.neighbors:
178
- if isinstance(agent, Citizen) and agent.condition == "Active":
179
- active_neighbors.append(agent)
180
- if active_neighbors:
181
- arrestee = self.random.choice(active_neighbors)
182
- arrestee.sent_to_jail(self.random.randint(0, self.max_jail_term))
183
- if self.movement and self.empty_neighbors:
184
- new_pos = self.random.choice(self.empty_neighbors)
185
- self.model.grid.move_agent(self, new_pos)
186
-
187
- def update_neighbors(self):
188
- """Look around and see who my neighbors are."""
189
- self.neighborhood = self.model.grid.get_neighborhood(
190
- self.pos, moore=True, radius=self.vision
191
- )
192
- self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood)
193
- self.empty_neighbors = [
194
- c for c in self.neighborhood if self.model.grid.is_cell_empty(c)
195
- ]
196
-
197
-
198
- class EpsteinCivilViolence(Model):
199
- """Model 1 from "Modeling civil violence: An agent-based computational approach," by Joshua Epstein.
200
-
201
- http://www.pnas.org/content/99/suppl_3/7243.full
202
- Attributes:
203
- height: grid height
204
- width: grid width
205
- citizen_density: approximate % of cells occupied by citizens.
206
- cop_density: approximate % of cells occupied by cops.
207
- citizen_vision: number of cells in each direction (N, S, E and W) that
208
- citizen can inspect
209
- cop_vision: number of cells in each direction (N, S, E and W) that cop
210
- can inspect
211
- legitimacy: (L) citizens' perception of regime legitimacy, equal
212
- across all citizens
213
- max_jail_term: (J_max)
214
- active_threshold: if (grievance - (risk_aversion * arrest_probability))
215
- > threshold, citizen rebels
216
- arrest_prob_constant: set to ensure agents make plausible arrest
217
- probability estimates
218
- movement: binary, whether agents try to move at step end
219
- max_iters: model may not have a natural stopping point, so we set a
220
- max.
221
- """
222
-
223
- def __init__(
224
- self,
225
- width=40,
226
- height=40,
227
- citizen_density=0.7,
228
- cop_density=0.074,
229
- citizen_vision=7,
230
- cop_vision=7,
231
- legitimacy=0.8,
232
- max_jail_term=1000,
233
- active_threshold=0.1,
234
- arrest_prob_constant=2.3,
235
- movement=True,
236
- max_iters=1000,
237
- seed=None,
238
- ):
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
- super().__init__(seed)
257
- if cop_density + citizen_density > 1:
258
- raise ValueError("Cop density + citizen density must be less than 1")
259
-
260
- self.width = width
261
- self.height = height
262
- self.citizen_density = citizen_density
263
- self.cop_density = cop_density
264
-
265
- self.max_iters = max_iters
266
-
267
- self.grid = SingleGrid(self.width, self.height, torus=True)
268
-
269
- for _, pos in self.grid.coord_iter():
270
- if self.random.random() < self.cop_density:
271
- agent = Cop(
272
- self,
273
- cop_vision,
274
- movement,
275
- max_jail_term,
276
- )
277
- elif self.random.random() < (self.cop_density + self.citizen_density):
278
- agent = Citizen(
279
- self,
280
- citizen_vision,
281
- movement,
282
- hardship=self.random.random(),
283
- regime_legitimacy=legitimacy,
284
- risk_aversion=self.random.random(),
285
- threshold=active_threshold,
286
- arrest_prob_constant=arrest_prob_constant,
287
- )
288
- else:
289
- continue
290
- self.grid.place_agent(agent, pos)
291
-
292
- self.active_agents = self.agents
293
-
294
- def step(self):
295
- """Run one step of the model."""
296
- self.active_agents.shuffle_do("step")
297
-
298
-
299
- if __name__ == "__main__":
300
- model = EpsteinCivilViolence(seed=15)
301
- simulator = ABMSimulator()
302
-
303
- simulator.setup(model)
304
-
305
- simulator.run_for(time_delta=100)