From 92e8f66e43af0eeea9c5a2f7b0d459334bcd8269 Mon Sep 17 00:00:00 2001 From: louky123 <62252363+louky123@users.noreply.github.com> Date: Wed, 13 Jan 2021 13:03:45 +0100 Subject: [PATCH] Add files via upload --- agent.py | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++ model.py | 145 ++++++++++++++++++++++++++++++++++++ portrayal.py | 33 +++++++++ server.py | 55 ++++++++++++++ 4 files changed, 436 insertions(+) create mode 100644 agent.py create mode 100644 model.py create mode 100644 portrayal.py create mode 100644 server.py diff --git a/agent.py b/agent.py new file mode 100644 index 00000000..8e8123df --- /dev/null +++ b/agent.py @@ -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) + ] diff --git a/model.py b/model.py new file mode 100644 index 00000000..f908a539 --- /dev/null +++ b/model.py @@ -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 diff --git a/portrayal.py b/portrayal.py new file mode 100644 index 00000000..80134adc --- /dev/null +++ b/portrayal.py @@ -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 diff --git a/server.py b/server.py new file mode 100644 index 00000000..3087127c --- /dev/null +++ b/server.py @@ -0,0 +1,55 @@ +from mesa.visualization.ModularVisualization import ModularServer +from mesa.visualization.modules import CanvasGrid + +from .model import EpsteinCivilViolence +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 type(agent) is 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 type(agent) is Cop: + portrayal["Color"] = COP_COLOR + portrayal["r"] = 0.5 + portrayal["Layer"] = 1 + return portrayal + + +model_params = dict( + 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, +) + +canvas_element = CanvasGrid(citizen_cop_portrayal, 40, 40, 480, 480) +server = ModularServer( + EpsteinCivilViolence, [canvas_element], "Epstein Civil Violence", model_params +)