Skip to content

Commit 2270116

Browse files
committed
Add advanced example models
2 parents 3773e4d + e897839 commit 2270116

39 files changed

+2357
-0
lines changed

examples/advanced/epstein_civil_violence/Epstein Civil Violence.ipynb

Lines changed: 116 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Epstein Civil Violence Model
2+
3+
## Summary
4+
5+
This model is based on Joshua Epstein's simulation of how civil unrest grows and is suppressed. Citizen agents wander the grid randomly, and are endowed with individual risk aversion and hardship levels; there is also a universal regime legitimacy value. There are also Cop agents, who work on behalf of the regime. Cops arrest Citizens who are actively rebelling; Citizens decide whether to rebel based on their hardship and the regime legitimacy, and their perceived probability of arrest.
6+
7+
The model generates mass uprising as self-reinforcing processes: if enough agents are rebelling, the probability of any individual agent being arrested is reduced, making more agents more likely to join the uprising. However, the more rebelling Citizens the Cops arrest, the less likely additional agents become to join.
8+
9+
## How to Run
10+
11+
To run the model interactively, run ``EpsteinCivilViolenceServer.py`` in this directory. e.g.
12+
13+
```
14+
$ python EpsteinCivilViolenceServer.py
15+
```
16+
17+
Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press Reset, then Run.
18+
19+
## Files
20+
21+
* ``EpsteinCivilViolence.py``: Core model and agent code.
22+
* ``EpsteinCivilViolenceServer.py``: Sets up the interactive visualization.
23+
* ``Epstein Civil Violence.ipynb``: Jupyter notebook conducting some preliminary analysis of the model.
24+
25+
## Further Reading
26+
27+
This model is based adapted from:
28+
29+
[Epstein, J. “Modeling civil violence: An agent-based computational approach”, Proceedings of the National Academy of Sciences, Vol. 99, Suppl. 3, May 14, 2002](http://www.pnas.org/content/99/suppl.3/7243.short)
30+
31+
A similar model is also included with NetLogo:
32+
33+
Wilensky, U. (2004). NetLogo Rebellion model. http://ccl.northwestern.edu/netlogo/models/Rebellion. Center for Connected Learning and Computer-Based Modeling, Northwestern University, Evanston, IL.

examples/advanced/epstein_civil_violence/epstein_civil_violence/__init__.py

Whitespace-only changes.
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import math
2+
3+
import mesa
4+
5+
6+
class EpsteinAgent(mesa.experimental.cell_space.CellAgent):
7+
def update_neighbors(self):
8+
"""
9+
Look around and see who my neighbors are
10+
"""
11+
self.neighborhood = self.cell.get_neighborhood(radius=self.vision)
12+
13+
self.neighbors = self.neighborhood.agents
14+
self.empty_neighbors = [c for c in self.neighborhood if c.is_empty]
15+
16+
17+
class Citizen(EpsteinAgent):
18+
"""
19+
A member of the general population, may or may not be in active rebellion.
20+
Summary of rule: If grievance - risk > threshold, rebel.
21+
22+
Attributes:
23+
hardship: Agent's 'perceived hardship (i.e., physical or economic
24+
privation).' Exogenous, drawn from U(0,1).
25+
regime_legitimacy: Agent's perception of regime legitimacy, equal
26+
across agents. Exogenous.
27+
risk_aversion: Exogenous, drawn from U(0,1).
28+
threshold: if (grievance - (risk_aversion * arrest_probability)) >
29+
threshold, go/remain Active
30+
vision: number of cells in each direction (N, S, E and W) that agent
31+
can inspect
32+
condition: Can be "Quiescent" or "Active;" deterministic function of
33+
greivance, perceived risk, and
34+
grievance: deterministic function of hardship and regime_legitimacy;
35+
how aggrieved is agent at the regime?
36+
arrest_probability: agent's assessment of arrest probability, given
37+
rebellion
38+
"""
39+
40+
def __init__(
41+
self,
42+
model,
43+
hardship,
44+
regime_legitimacy,
45+
risk_aversion,
46+
threshold,
47+
vision,
48+
):
49+
"""
50+
Create a new Citizen.
51+
Args:
52+
model: the model to which the agent belongs
53+
hardship: Agent's 'perceived hardship (i.e., physical or economic
54+
privation).' Exogenous, drawn from U(0,1).
55+
regime_legitimacy: Agent's perception of regime legitimacy, equal
56+
across agents. Exogenous.
57+
risk_aversion: Exogenous, drawn from U(0,1).
58+
threshold: if (grievance - (risk_aversion * arrest_probability)) >
59+
threshold, go/remain Active
60+
vision: number of cells in each direction (N, S, E and W) that
61+
agent can inspect. Exogenous.
62+
model: model instance
63+
"""
64+
super().__init__(model)
65+
self.hardship = hardship
66+
self.regime_legitimacy = regime_legitimacy
67+
self.risk_aversion = risk_aversion
68+
self.threshold = threshold
69+
self.condition = "Quiescent"
70+
self.vision = vision
71+
self.jail_sentence = 0
72+
self.grievance = self.hardship * (1 - self.regime_legitimacy)
73+
self.arrest_probability = None
74+
75+
def step(self):
76+
"""
77+
Decide whether to activate, then move if applicable.
78+
"""
79+
if self.jail_sentence:
80+
self.jail_sentence -= 1
81+
return # no other changes or movements if agent is in jail.
82+
self.update_neighbors()
83+
self.update_estimated_arrest_probability()
84+
net_risk = self.risk_aversion * self.arrest_probability
85+
if self.grievance - net_risk > self.threshold:
86+
self.condition = "Active"
87+
else:
88+
self.condition = "Quiescent"
89+
90+
if self.model.movement and self.empty_neighbors:
91+
new_cell = self.random.choice(self.empty_neighbors)
92+
self.move_to(new_cell)
93+
94+
def update_estimated_arrest_probability(self):
95+
"""
96+
Based on the ratio of cops to actives in my neighborhood, estimate the
97+
p(Arrest | I go active).
98+
"""
99+
cops_in_vision = len([c for c in self.neighbors if isinstance(c, Cop)])
100+
actives_in_vision = 1.0 # citizen counts herself
101+
for c in self.neighbors:
102+
if (
103+
isinstance(c, Citizen)
104+
and c.condition == "Active"
105+
and c.jail_sentence == 0
106+
):
107+
actives_in_vision += 1
108+
self.arrest_probability = 1 - math.exp(
109+
-1 * self.model.arrest_prob_constant * (cops_in_vision / actives_in_vision)
110+
)
111+
112+
113+
class Cop(EpsteinAgent):
114+
"""
115+
A cop for life. No defection.
116+
Summary of rule: Inspect local vision and arrest a random active agent.
117+
118+
Attributes:
119+
unique_id: unique int
120+
x, y: Grid coordinates
121+
vision: number of cells in each direction (N, S, E and W) that cop is
122+
able to inspect
123+
"""
124+
125+
def __init__(self, model, vision):
126+
"""
127+
Create a new Cop.
128+
Args:
129+
x, y: Grid coordinates
130+
vision: number of cells in each direction (N, S, E and W) that
131+
agent can inspect. Exogenous.
132+
model: model instance
133+
"""
134+
super().__init__(model)
135+
self.vision = vision
136+
137+
def step(self):
138+
"""
139+
Inspect local vision and arrest a random active agent. Move if
140+
applicable.
141+
"""
142+
self.update_neighbors()
143+
active_neighbors = []
144+
for agent in self.neighbors:
145+
if (
146+
isinstance(agent, Citizen)
147+
and agent.condition == "Active"
148+
and agent.jail_sentence == 0
149+
):
150+
active_neighbors.append(agent)
151+
if active_neighbors:
152+
arrestee = self.random.choice(active_neighbors)
153+
sentence = self.random.randint(0, self.model.max_jail_term)
154+
arrestee.jail_sentence = sentence
155+
arrestee.condition = "Quiescent"
156+
if self.model.movement and self.empty_neighbors:
157+
new_pos = self.random.choice(self.empty_neighbors)
158+
self.move_to(new_pos)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import mesa
2+
3+
from .agent import Citizen, Cop
4+
5+
6+
class EpsteinCivilViolence(mesa.Model):
7+
"""
8+
Model 1 from "Modeling civil violence: An agent-based computational
9+
approach," by Joshua Epstein.
10+
http://www.pnas.org/content/99/suppl_3/7243.full
11+
Attributes:
12+
height: grid height
13+
width: grid width
14+
citizen_density: approximate % of cells occupied by citizens.
15+
cop_density: approximate % of cells occupied by cops.
16+
citizen_vision: number of cells in each direction (N, S, E and W) that
17+
citizen can inspect
18+
cop_vision: number of cells in each direction (N, S, E and W) that cop
19+
can inspect
20+
legitimacy: (L) citizens' perception of regime legitimacy, equal
21+
across all citizens
22+
max_jail_term: (J_max)
23+
active_threshold: if (grievance - (risk_aversion * arrest_probability))
24+
> threshold, citizen rebels
25+
arrest_prob_constant: set to ensure agents make plausible arrest
26+
probability estimates
27+
movement: binary, whether agents try to move at step end
28+
max_iters: model may not have a natural stopping point, so we set a
29+
max.
30+
"""
31+
32+
def __init__(
33+
self,
34+
width=40,
35+
height=40,
36+
citizen_density=0.7,
37+
cop_density=0.074,
38+
citizen_vision=7,
39+
cop_vision=7,
40+
legitimacy=0.8,
41+
max_jail_term=1000,
42+
active_threshold=0.1,
43+
arrest_prob_constant=2.3,
44+
movement=True,
45+
max_iters=1000,
46+
):
47+
super().__init__()
48+
self.width = width
49+
self.height = height
50+
self.citizen_density = citizen_density
51+
self.cop_density = cop_density
52+
self.citizen_vision = citizen_vision
53+
self.cop_vision = cop_vision
54+
self.legitimacy = legitimacy
55+
self.max_jail_term = max_jail_term
56+
self.active_threshold = active_threshold
57+
self.arrest_prob_constant = arrest_prob_constant
58+
self.movement = movement
59+
self.max_iters = max_iters
60+
self.iteration = 0
61+
62+
self.grid = mesa.experimental.cell_space.OrthogonalMooreGrid(
63+
(width, height), capacity=1, torus=True
64+
)
65+
66+
model_reporters = {
67+
"Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"),
68+
"Active": lambda m: self.count_type_citizens(m, "Active"),
69+
"Jailed": self.count_jailed,
70+
"Cops": self.count_cops,
71+
}
72+
agent_reporters = {
73+
"x": lambda a: a.cell.coordinate[0],
74+
"y": lambda a: a.cell.coordinate[1],
75+
"breed": lambda a: type(a).__name__,
76+
"jail_sentence": lambda a: getattr(a, "jail_sentence", None),
77+
"condition": lambda a: getattr(a, "condition", None),
78+
"arrest_probability": lambda a: getattr(a, "arrest_probability", None),
79+
}
80+
self.datacollector = mesa.DataCollector(
81+
model_reporters=model_reporters, agent_reporters=agent_reporters
82+
)
83+
if self.cop_density + self.citizen_density > 1:
84+
raise ValueError("Cop density + citizen density must be less than 1")
85+
86+
for cell in self.grid.all_cells:
87+
if self.random.random() < self.cop_density:
88+
cop = Cop(self, vision=self.cop_vision)
89+
cop.move_to(cell)
90+
91+
elif self.random.random() < (self.cop_density + self.citizen_density):
92+
citizen = Citizen(
93+
self,
94+
hardship=self.random.random(),
95+
regime_legitimacy=self.legitimacy,
96+
risk_aversion=self.random.random(),
97+
threshold=self.active_threshold,
98+
vision=self.citizen_vision,
99+
)
100+
citizen.move_to(cell)
101+
102+
self.running = True
103+
self.datacollector.collect(self)
104+
105+
def step(self):
106+
"""
107+
Advance the model by one step and collect data.
108+
"""
109+
self.agents.shuffle_do("step")
110+
# collect data
111+
self.datacollector.collect(self)
112+
self.iteration += 1
113+
if self.iteration > self.max_iters:
114+
self.running = False
115+
116+
@staticmethod
117+
def count_type_citizens(model, condition, exclude_jailed=True):
118+
"""
119+
Helper method to count agents by Quiescent/Active.
120+
"""
121+
citizens = model.agents_by_type[Citizen]
122+
123+
if exclude_jailed:
124+
return len(
125+
[
126+
c
127+
for c in citizens
128+
if (c.condition == condition) and (c.jail_sentence == 0)
129+
]
130+
)
131+
else:
132+
return len([c for c in citizens if c.condition == condition])
133+
134+
@staticmethod
135+
def count_jailed(model):
136+
"""
137+
Helper method to count jailed agents.
138+
"""
139+
return len([a for a in model.agents_by_type[Citizen] if a.jail_sentence > 0])
140+
141+
@staticmethod
142+
def count_cops(model):
143+
"""
144+
Helper method to count jailed agents.
145+
"""
146+
return len(model.agents_by_type[Cop])
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from .agent import Citizen, Cop
2+
3+
COP_COLOR = "#000000"
4+
AGENT_QUIET_COLOR = "#0066CC"
5+
AGENT_REBEL_COLOR = "#CC0000"
6+
JAIL_COLOR = "#757575"
7+
8+
9+
def citizen_cop_portrayal(agent):
10+
if agent is None:
11+
return
12+
13+
portrayal = {
14+
"Shape": "circle",
15+
"x": agent.pos[0],
16+
"y": agent.pos[1],
17+
"Filled": "true",
18+
}
19+
20+
if isinstance(agent, Citizen):
21+
color = (
22+
AGENT_QUIET_COLOR if agent.condition == "Quiescent" else AGENT_REBEL_COLOR
23+
)
24+
color = JAIL_COLOR if agent.jail_sentence else color
25+
portrayal["Color"] = color
26+
portrayal["r"] = 0.8
27+
portrayal["Layer"] = 0
28+
29+
elif isinstance(agent, Cop):
30+
portrayal["Color"] = COP_COLOR
31+
portrayal["r"] = 0.5
32+
portrayal["Layer"] = 1
33+
return portrayal

0 commit comments

Comments
 (0)