Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import math

from mesa import Agent


class Citizen(Agent):
"""
A member of the general population, may or may not be in active rebellion.
Summary of rule: If grievance - risk > threshold, rebel.

Attributes:
unique_id: unique int
x, y: Grid coordinates
hardship: Agent's 'perceived hardship (i.e., physical or economic
privation).' Exogenous, drawn from U(0,1).
regime_legitimacy: Agent's perception of regime legitimacy, equal
across agents. Exogenous.
risk_aversion: Exogenous, drawn from U(0,1).
threshold: if (grievance - (risk_aversion * arrest_probability)) >
threshold, go/remain Active
vision: number of cells in each direction (N, S, E and W) that agent
can inspect
condition: Can be "Quiescent" or "Active;" deterministic function of
greivance, perceived risk, and
grievance: deterministic function of hardship and regime_legitimacy;
how aggrieved is agent at the regime?
arrest_probability: agent's assessment of arrest probability, given
rebellion

"""

def __init__(
self,
unique_id,
model,
pos,
hardship,
regime_legitimacy,
risk_aversion,
threshold,
vision,
):
"""
Create a new Citizen.
Args:
unique_id: unique int
x, y: Grid coordinates
hardship: Agent's 'perceived hardship (i.e., physical or economic
privation).' Exogenous, drawn from U(0,1).
regime_legitimacy: Agent's perception of regime legitimacy, equal
across agents. Exogenous.
risk_aversion: Exogenous, drawn from U(0,1).
threshold: if (grievance - (risk_aversion * arrest_probability)) >
threshold, go/remain Active
vision: number of cells in each direction (N, S, E and W) that
agent can inspect. Exogenous.
model: model instance
"""
super().__init__(unique_id, model)
self.breed = "citizen"
self.pos = pos
self.hardship = hardship
self.regime_legitimacy = regime_legitimacy
self.risk_aversion = risk_aversion
self.threshold = threshold
self.condition = "Quiescent"
self.vision = vision
self.jail_sentence = 0
self.grievance = self.hardship * (1 - self.regime_legitimacy)
self.arrest_probability = None

def step(self):
"""
Decide whether to activate, then move if applicable.
"""
if self.jail_sentence:
self.jail_sentence -= 1
#Louky: (4) after a jailed agent comes out of jail, her state is set to quiet instead of her previous state before arrest (active).
if not self.jail_sentence:
self.active = False

return # no other changes or movements if agent is in jail.
self.update_neighbors()
self.update_estimated_arrest_probability()
net_risk = self.risk_aversion * self.arrest_probability
if (
self.condition == "Quiescent"
and (self.grievance - net_risk) > self.threshold
):
self.condition = "Active"
elif (
self.condition == "Active" and (self.grievance - net_risk) <= self.threshold
):
self.condition = "Quiescent"
if self.model.movement and self.empty_neighbors:
new_pos = self.random.choice(self.empty_neighbors)
self.model.grid.move_agent(self, new_pos)

def update_neighbors(self):
"""
Look around and see who my neighbors are
"""
self.neighborhood = self.model.grid.get_neighborhood(
self.pos, moore=False, radius=self.vision
)
self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood)
self.empty_neighbors = [
c for c in self.neighborhood if self.model.grid.is_cell_empty(c)
]

def update_estimated_arrest_probability(self):
"""
Based on the ratio of cops to actives in my neighborhood, estimate the
p(Arrest | I go active).

"""
cops_in_vision = len([c for c in self.neighbors if c.breed == "cop"])
actives_in_vision = 1.0 # citizen counts herself
for c in self.neighbors:
if (
c.breed == "citizen"
and c.condition == "Active"
and c.jail_sentence == 0
):
actives_in_vision += 1

#LOUKY: rounding the actives to cops ratio to min integer (1)
self.ratio_c_a = int(cops_in_vision/ actives_in_vision)

self.arrest_probability = 1 - math.exp(
-1 * self.model.arrest_prob_constant * (self.ratio_c_a )
)


class Cop(Agent):
"""
A cop for life. No defection.
Summary of rule: Inspect local vision and arrest a random active agent.

Attributes:
unique_id: unique int
x, y: Grid coordinates
vision: number of cells in each direction (N, S, E and W) that cop is
able to inspect
"""

def __init__(self, unique_id, model, pos, vision):
"""
Create a new Cop.
Args:
unique_id: unique int
x, y: Grid coordinates
vision: number of cells in each direction (N, S, E and W) that
agent can inspect. Exogenous.
model: model instance
"""
super().__init__(unique_id, model)
self.breed = "cop"
self.pos = pos
self.vision = vision

def step(self):
"""
Inspect local vision and arrest a random active agent. Move if
applicable.
"""
self.update_neighbors()
active_neighbors = []



for agent in self.neighbors:
if (
agent.breed == "citizen"
and agent.condition == "Active"
and agent.jail_sentence == 0
):
active_neighbors.append(agent)

if active_neighbors:
arrestee = self.random.choice(active_neighbors)
sentence = self.random.randint(0, self.model.max_jail_term)
arrestee.jail_sentence = sentence
#Louky: Moving the cop to the arrested agent position (2)
if self.model.movement:
self.model.grid.move_agent(self, arrestee.pos)


elif self.model.movement and self.empty_neighbors:
new_pos = self.random.choice(self.empty_neighbors)
self.model.grid.move_agent(self, new_pos)

def update_neighbors(self):
"""
Look around and see who my neighbors are.
"""
self.neighborhood = self.model.grid.get_neighborhood(
self.pos, moore=False, radius=self.vision
)
self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood)
self.empty_neighbors = [
c for c in self.neighborhood if self.model.grid.is_cell_empty(c)
]
145 changes: 145 additions & 0 deletions model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from mesa import Model
from mesa.time import RandomActivation
from mesa.space import Grid
from mesa.datacollection import DataCollector

from .agent import Cop, Citizen


class EpsteinCivilViolence(Model):
"""
Model 1 from "Modeling civil violence: An agent-based computational
approach," by Joshua Epstein.
http://www.pnas.org/content/99/suppl_3/7243.full
Attributes:
height: grid height
width: grid width
citizen_density: approximate % of cells occupied by citizens.
cop_density: approximate % of calles occupied by cops.
citizen_vision: number of cells in each direction (N, S, E and W) that
citizen can inspect
cop_vision: number of cells in each direction (N, S, E and W) that cop
can inspect
legitimacy: (L) citizens' perception of regime legitimacy, equal
across all citizens
max_jail_term: (J_max)
active_threshold: if (grievance - (risk_aversion * arrest_probability))
> threshold, citizen rebels
arrest_prob_constant: set to ensure agents make plausible arrest
probability estimates
movement: binary, whether agents try to move at step end
max_iters: model may not have a natural stopping point, so we set a
max.

"""

def __init__(
self,
height=40,
width=40,
citizen_density=0.7,
cop_density=0.074,
citizen_vision=7,
cop_vision=7,
legitimacy=0.8,
max_jail_term=1000,
active_threshold=0.1,
arrest_prob_constant=2.3,
movement=True,
max_iters=1000,
):
super().__init__()
self.height = height
self.width = width
self.citizen_density = citizen_density
self.cop_density = cop_density
self.citizen_vision = citizen_vision
self.cop_vision = cop_vision
self.legitimacy = legitimacy
self.max_jail_term = max_jail_term
self.active_threshold = active_threshold
self.arrest_prob_constant = arrest_prob_constant
self.movement = movement
self.max_iters = max_iters
self.iteration = 0
self.schedule = RandomActivation(self)
self.grid = Grid(height, width, torus=True)
model_reporters = {
"Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"),
"Active": lambda m: self.count_type_citizens(m, "Active"),
"Jailed": lambda m: self.count_jailed(m),
}
agent_reporters = {
"x": lambda a: a.pos[0],
"y": lambda a: a.pos[1],
"breed": lambda a: a.breed,
"jail_sentence": lambda a: getattr(a, "jail_sentence", None),
"condition": lambda a: getattr(a, "condition", None),
"arrest_probability": lambda a: getattr(a, "arrest_probability", None),
}
self.datacollector = DataCollector(
model_reporters=model_reporters, agent_reporters=agent_reporters
)
unique_id = 0
if self.cop_density + self.citizen_density > 1:
raise ValueError("Cop density + citizen density must be less than 1")
for (contents, x, y) in self.grid.coord_iter():
if self.random.random() < self.cop_density:
cop = Cop(unique_id, self, (x, y), vision=self.cop_vision)
unique_id += 1
self.grid[y][x] = cop
self.schedule.add(cop)
elif self.random.random() < (self.cop_density + self.citizen_density):
citizen = Citizen(
unique_id,
self,
(x, y),
hardship=self.random.random(),
regime_legitimacy=self.legitimacy,
risk_aversion=self.random.random(),
threshold=self.active_threshold,
vision=self.citizen_vision,
)
unique_id += 1
self.grid[y][x] = citizen
self.schedule.add(citizen)

self.running = True
self.datacollector.collect(self)

def step(self):
"""
Advance the model by one step and collect data.
"""
self.schedule.step()
# collect data
self.datacollector.collect(self)
self.iteration += 1
if self.iteration > self.max_iters:
self.running = False

@staticmethod
def count_type_citizens(model, condition, exclude_jailed=True):
"""
Helper method to count agents by Quiescent/Active.
"""
count = 0
for agent in model.schedule.agents:
if agent.breed == "cop":
continue
if exclude_jailed and agent.jail_sentence:
continue
if agent.condition == condition:
count += 1
return count

@staticmethod
def count_jailed(model):
"""
Helper method to count jailed agents.
"""
count = 0
for agent in model.schedule.agents:
if agent.breed == "citizen" and agent.jail_sentence:
count += 1
return count
33 changes: 33 additions & 0 deletions portrayal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from .agent import Citizen, Cop

COP_COLOR = "#000000"
AGENT_QUIET_COLOR = "#0066CC"
AGENT_REBEL_COLOR = "#CC0000"
JAIL_COLOR = "#757575"


def citizen_cop_portrayal(agent):
if agent is None:
return

portrayal = {
"Shape": "circle",
"x": agent.pos[0],
"y": agent.pos[1],
"Filled": "true",
}

if isinstance(agent, Citizen):
color = (
AGENT_QUIET_COLOR if agent.condition == "Quiescent" else AGENT_REBEL_COLOR
)
color = JAIL_COLOR if agent.jail_sentence else color
portrayal["Color"] = color
portrayal["r"] = 0.8
portrayal["Layer"] = 0

elif isinstance(agent, Cop):
portrayal["Color"] = COP_COLOR
portrayal["r"] = 0.5
portrayal["Layer"] = 1
return portrayal
Loading