class EvacuationModel(Model): """ This is a simulation of a crowd evacuation from a building. Several variables are taken into account: the knowledge of the emergency exits, the age and weight of the agents and the presence of stewards that can guide agents toward the emergency exits. Agents have different strategies to escape the building such as taking the shortest path to an exit or a random one. The goal is to study which combinations of agent types are more likely to escape the building and save themselves and how the amount of casualties varies with respect to the different variables. """ def __init__(self, N=10, K=0, width=50, height=50, fire_x=1, fire_y=1, civil_info_exchange=True): self.num_civilians = N self.num_stewards = K self.civil_info_exchange = civil_info_exchange self.fire_initial_pos = (fire_x, fire_y) self.warning_UI = "" self.agents_alive = N + K # Agents alive and inside the building self.agents_saved = [] # Agents that managed to get out self.agents_killed = [] # Agents that perished during the evacuation self.grid = SingleGrid(height, width, False) self.graph = None # General graph representing walkable terrain self.schedule = RandomActivation( self) # Every tick, agents move in a different random order # Create exits self.pos_exits = [(0, 5), (0, 25), (0, 45)] for i in range(3): self.pos_exits.append((self.grid.width - 1, 14 + i)) self.draw_environment(self.pos_exits) self.graph = path_finding.create_graph(self) # Define data collector model_collector = { "Agents killed": lambda killed: len(self.agents_killed), "Agents saved": lambda saved: len(self.agents_saved) } for exit_pos in self.pos_exits: title = "Exit {}".format(exit_pos) model_collector[title] = partial(count_agents_saved, exit_pos) self.datacollector = DataCollector(model_reporters=model_collector) # Create fire # for pos in self.fire_initial_pos: # Only 1 source of fire since we are setting it from UI x, y = self.fire_initial_pos if not self.is_inside_square((x, y), (0, 29), (25, 39)) and not self.is_inside_square( (x, y), (0, 10), (25, 20)): pos = self.fire_initial_pos else: pos = (1, 1) self.warning_UI = "<b>WARNING:</b> Sorry but the position of the fire is outside of the building, " \ "change the setting and click reset simulation." fire_agent = FireAgent(pos, self) self.schedule.add(fire_agent) self.grid.place_agent(fire_agent, pos) # Create civilian agents for i in range(self.num_civilians): # a civilian agent will know at least the main entrance to the building known_exits = self.pos_exits[-3:] a = CivilianAgent(i, self, known_exits) self.schedule.add(a) # Add the agent to a random grid cell while True: # pick the random coordinate x = self.random.randrange(1, self.grid.width - 1) y = self.random.randrange(1, self.grid.height - 1) # check if the point is empty and inside of the building if self.grid.is_cell_empty((x, y)) and not self.is_inside_square((x, y), (0, 29), (25, 39)) \ and not self.is_inside_square((x, y), (0, 10), (25, 20)): break self.grid.place_agent(a, (x, y)) # Create steward agents for i in range(self.num_civilians, self.num_civilians + self.num_stewards): # a steward agent will know all exits. known_exits = self.pos_exits a = StewardAgent(i, self, known_exits) self.schedule.add(a) # Add the agent to a random grid cell while True: # pick the random coordinate x = self.random.randrange(1, self.grid.width - 1) y = self.random.randrange(1, self.grid.height - 1) # check if the point is empty and inside of the building if self.grid.is_cell_empty((x, y)) and not self.is_inside_square((x, y), (0, 29), (25, 39)) \ and not self.is_inside_square((x, y), (0, 10), (25, 20)): break self.grid.place_agent(a, (x, y)) self.running = True # Set this to false when we want to finish simulation (e.g. all agents are out of building) self.datacollector.collect(self) @staticmethod def is_inside_square(point, bottom_left, top_right): return bottom_left[0] <= point[0] <= top_right[0] and bottom_left[ 1] <= point[1] <= top_right[1] def step(self): self.schedule.step() # collect data self.datacollector.collect(self) # Halt if no more agents in the building if self.count_agents(self) == 0: self.running = False def remove_agent(self, agent, reason, **kwargs): """ Removes an agent from the simulation. Depending on the reason it can be Args: agent (Agent): reason (Reasons): Returns: None """ if reason == Reasons.SAVED: self.agents_saved.append(agent) elif reason == Reasons.KILLED_BY_FIRE: self.agents_killed.append(agent) self.agents_alive -= 1 self.schedule.remove(agent) self.grid.remove_agent(agent) def draw_environment(self, exits=None): length_E = int(self.grid.height / 5) # length of the vertical segments of the E depth_E = int(self.grid.width / 2) # length of the horizontal segments of the E for i in range(3): start = max(0, 2 * i * length_E) self.draw_wall((0, start), (0, start + length_E - 1)) for i in range(2): start = 2 * i * length_E + length_E self.draw_wall((depth_E, start), (depth_E, start + length_E - 1)) # Horizontal lines of the E (BB) aux_y_coord = [ length_E, 2 * length_E, 3 * length_E - 1, 4 * length_E - 1 ] for y in aux_y_coord: self.draw_wall((0, y), (depth_E, y)) top_left_corner = (0, self.grid.height - 1) top_right_corner = (self.grid.width - 1, self.grid.height - 1) bottom_right_corner = (self.grid.width - 1, 0) # Draw long contour lines E self.draw_wall((0, 0), bottom_right_corner) self.draw_wall(top_left_corner, top_right_corner) self.draw_wall(bottom_right_corner, top_right_corner) # Draw exits self.draw_exits(exits) def draw_wall(self, start, end): """ Draws a line that goes from start point to end point. Args: start (List): Coordinates of line's starting point end (List): Coordinates of line's end point Returns: None """ diff_x, diff_y = np.subtract(end, start) wall_coordinates = np.asarray(start) if self.grid.is_cell_empty(wall_coordinates.tolist()): w = WallAgent(wall_coordinates.tolist(), self) self.grid.place_agent(w, wall_coordinates.tolist()) while diff_x != 0 or diff_y != 0: if abs(diff_x) == abs(diff_y): # diagonal wall wall_coordinates[0] += np.sign(diff_x) wall_coordinates[1] += np.sign(diff_y) diff_x -= 1 diff_y -= 1 elif abs(diff_x) < abs(diff_y): # wall built in y dimension wall_coordinates[1] += np.sign(diff_y) diff_y -= 1 else: # wall built in x dimension wall_coordinates[0] += np.sign(diff_x) diff_x -= 1 if self.grid.is_cell_empty(wall_coordinates.tolist()): w = WallAgent(wall_coordinates.tolist(), self) self.grid.place_agent(w, wall_coordinates.tolist()) def draw_exits(self, exits_list): for ext in exits_list: e = ExitAgent(ext, self) if not self.grid.is_cell_empty(ext): # Only walls should exist in the grid at this time, so no need to remove it from scheduler agent = self.grid.get_cell_list_contents(ext) self.grid.remove_agent(agent[0]) # Place exit self.schedule.add(e) self.grid.place_agent(e, ext) def spread_fire(self, fire_agent): fire_neighbors = self.grid.get_neighborhood(fire_agent.pos, moore=True, include_center=False) for grid_space in fire_neighbors: if self.grid.is_cell_empty(grid_space): # Create new fire agent and add it to grid and scheduler new_fire_agent = FireAgent(grid_space, self) self.schedule.add(new_fire_agent) self.grid.place_agent(new_fire_agent, grid_space) else: # If human agents, eliminate them and spread anyway agent = self.grid.get_cell_list_contents(grid_space)[0] if isinstance(agent, (CivilianAgent, StewardAgent)): new_fire_agent = FireAgent(grid_space, self) self.remove_agent(agent, Reasons.KILLED_BY_FIRE) self.schedule.add(new_fire_agent) self.grid.place_agent(new_fire_agent, grid_space) @staticmethod def count_agents(model): """ Helper method to count agents alive and still in the building. """ count = 0 for agent in model.schedule.agents: agent_type = type(agent) if (agent_type == CivilianAgent) or (agent_type == StewardAgent): count += 1 return count
class Factory(Model): """The Factory model that maintains the state of the whole factory.""" def __init__(self, grid_w, grid_h, n_robots): """Initialize factory.""" # Initialize. self.orders = 0 self.n_robots = n_robots self.scheduler = RandomActivation(self) self.grid = SingleGrid(grid_w, grid_h, torus=False) self.init_astar() # Initialize departments. self.machine = Machine("machine", self, self.grid.find_empty()) self.store = Store("store", self, self.grid.find_empty()) self.packaging = Packaging("packaging", self, self.grid.find_empty()) self.dept_positions = [self.machine.pos, self.store.pos, self.packaging.pos] # Initialize robots. for i in range(self.n_robots): # Create robot. r = Robot(i, self) # Initialize random location. pos = self.grid.find_empty() self.grid.place_agent(r, pos) # Register with scheduler. self.scheduler.add(r) # Initialize visualization. plt.ion() def add_order(self): """Increment the number of orders to the factory.""" self.orders += 1 def step(self): """Advance the factory by one step.""" # Step through factory. Check for orders. if self.orders > 0: self.store.orders += 1 self.orders -= 1 # Step through departments. self.store.step() self.machine.step() self.packaging.step() # Step through robots. self.scheduler.step() # Visualize. self.visualize() def init_astar(self): """Initialize a-star resources so that it doesn't have to calculated for each robot. Initialized in such a way that: * A diagonal paths are allowed. * The path calculated takes into account all obstacles in the grid. """ def get_empty_neighborhood(pos): """A sub function to calculate empty neighbors of a point for a-star.""" neighbors = self.grid.get_neighborhood(pos=pos, moore=True) return [n for n in neighbors if self.grid.is_cell_empty(n)] # Initialize a path finder object once for the entire factory. self.path_finder = astar.pathfinder(neighbors=get_empty_neighborhood, distance=astar.absolute_distance, cost=astar.fixed_cost(1)) def find_nearest_aimless_robot(self, pos): """Find the nearest aimless robot to a given position in the factory.""" def is_aimless(robot, pos): """Check if the robot satisfied aimless condition.""" if robot.destination is None: return True else: return False aimless_robots = [robot for robot in self.scheduler.agents if is_aimless(robot, pos)] if len(aimless_robots) != 0: robot_distances = [astar.absolute_distance(pos, robot.pos) for robot in aimless_robots] nearest_index = np.argmin(robot_distances) return aimless_robots[nearest_index] else: return None def find_robot_at_position(self, pos): """Find robot that is at a given location in the factory that is not busy.""" for robot in self.scheduler.agents: if robot.pos == pos: return robot return None def find_next_position_towards_destination(self, curr_pos, dest_pos): """Find the next empty position to move in the direction of the destination.""" n_steps, path = self.path_finder(curr_pos, dest_pos) # Handles non-empty locations. # NOTE: We cannot find a valid path to the destination when: # 1) The destination has an another robot located inside it, which also occurs when curr_pos and # dest_pos are the same. # 2) The path is entirely blocked. # In these cases we return the next position to be the curr_pos, in order to wait until things # clear up. if n_steps is None or n_steps <= 0: # No valid path to destination next_pos = curr_pos print("[MOVE] Warning: No path to destination from {} --> {}".format(curr_pos, dest_pos)) # This mean there's a valid path to destination. else: # index 0, is the curr_pos, index 1 is the next position. next_pos = path[1] return next_pos def find_next_position_for_random_walk(self, curr_pos): """Find a valid location for a robot to just randomly walk into.""" def is_pos_empty(pos): """A sub function if a cell is empty for random walking.""" if self.grid.is_cell_empty(pos) and pos not in self.dept_positions: return True else: return False neighborhood = self.grid.get_neighborhood(curr_pos, moore=True) empty_neighborhood = [n for n in neighborhood if is_pos_empty(n)] if len(empty_neighborhood) > 0: next_index = np.random.randint(len(empty_neighborhood)) next_pos = empty_neighborhood[next_index] else: next_pos = curr_pos return next_pos def visualize(self): """A chess board type visualization.""" def heatmap(a): cMap = ListedColormap(['grey', 'black', 'green', 'orange', 'red', 'blue']) sns.heatmap(a, vmin=0, vmax=6, cmap=cMap, linewidths=1) plt.pause(0.15) plt.clf() g = np.zeros((self.grid.height, self.grid.width), dtype=int) g[self.store.pos] = 3 g[self.machine.pos] = 4 g[self.packaging.pos] = 5 for robot in self.scheduler.agents: if robot.destination is None: g[robot.pos] = 1 else: g[robot.pos] = 2 heatmap(g)
class DaisyModel(Model): """ "Daisys" grow, when the temperature is right. But they influence temperature themselves via their ability to block a certain amount of sunlight (albedo, indicated by color). They spread and they mutate (changing albedo) and thus adapt to different conditions.""" def __init__(self, N, width, height, luminosity, heat_radius, mutation_range, surface_albedo, daisy_lifespan, daisy_tmin, daisy_tmax, lum_model, lum_increase): # Setup parameter self.dimensions = (width, height) self.running = True # never stop! self.num_agents = min([N, (width * height)]) # never more agents than cells self.grid = SingleGrid(width, height, torus=True) self.schedule = RandomActivation(self) # Model parameter self.mutation_range = mutation_range # default: 0.05 self.luminosity = luminosity # default 1.35 self.heat_radius = heat_radius self.surface_albedo = surface_albedo # default: 0.4 self.lum_model = lum_model self.lum_increase = lum_increase # tried 0.001 # Daisy parameter self.daisy_lifespan = daisy_lifespan self.daisy_tmin = daisy_tmin self.daisy_tmax = daisy_tmax # to inhibit using same postition twice: draw from urn position_list = [] for i in range(width): # put positions in urn for j in range(height): position_list.append((i,j)) for i in range(self.num_agents): # draw from urn a = DaisyAgent(i, self, random.uniform(0.1, 0.9), # random starting albedo self.daisy_lifespan, self.daisy_tmin, self.daisy_tmax) self.schedule.add(a) pos = random.choice(position_list) self.grid.place_agent(a, pos) position_list.remove(pos) # Data collectors self.datacollector = DataCollector( model_reporters = {"Solar irradiance": get_irradiance, "Population": get_population, "Mean albedo": get_mean_albedo, "Population: North - South": get_north_south_population } ) def step(self): print(self.lum_model) if self.lum_model == 'linear increase': self.luminosity = linear_increase(self) self.datacollector.collect(self) self.schedule.step() def get_lat(self, pos): """ The grid is meant to be a sphere. This gets the latitude. Ranges from 0.0 (equator) to 1.0 (pole). """ return (pos[1] / self.dimensions[1]) def get_GNI(self, pos): """ gives solar irradiance, depending on latitude""" return self.luminosity * math.sin(self.get_lat(pos)*math.pi) def expand_positionlist(self, pos_list): """ expands a list of positions, adding neighboring positions """ expanded_list = [] for i in pos_list: expanded_list += self.grid.get_neighborhood(i, moore=True, include_center=False) return list(set(expanded_list)) def get_local_heat(self, pos): """ Global Horizontal Irradiance (without diffusive irradiance) from pole (lower border) to pole (upper border). model is torus! """ neighborhood = self.grid.get_neighborhood(pos, moore=True, include_center=True) if self.heat_radius > 1: # if radius of local temperature is >1, this expand the position list. for i in range(self.heat_radius): neighborhood = self.expand_positionlist(neighborhood) heat = [] for i in neighborhood: if self.grid.is_cell_empty(i): # empty cell: surface albedo heat.append(self.get_GNI(pos) * (1 - self.surface_albedo) ) else: inhabitant = self.grid.get_cell_list_contents(i)[0] heat.append(self.get_GNI(pos) * (1 - inhabitant.albedo) ) # cell with daisy return sum(heat)/ len(neighborhood)
class DiseaseModel(Model): """ A model with some number of agents. highS: Number of agents with high sociability. middleS: Number of agents with middle sociability. lowS: Number of agents with low sociability. width: Width of the grid. height: Height of the grid. edu_setting: If true, agents will follow a schedule and sit in classrooms, else they will move freely through an open grid. cureProb: Probability of agent getting better. cureProbFac: Factor of cureProb getting higher. mutateProb: Probability of a disease mutating. diseaseRate: Rate at which the disease spreads. """ def __init__(self, highS, middleS, lowS, width, height, edu_setting=True, cureProb=0.1, cureProbFac=2/1440, mutateProb=0.0050, diseaseRate=0.38): super().__init__() self.num_agents = highS + middleS + lowS self.lowS = lowS self.middleS = middleS self.highS = highS self.initialCureProb = cureProb self.cureProbFac = cureProbFac self.mutateProb = mutateProb self.diseaseRate = diseaseRate self.edu_setting = edu_setting self.maxDisease = 0 # amount of mutations self.counter = 540 # keeps track of timesteps self.removed = [] self.exit = (width - 1, floor(height / 2)) # Check if agents fit within grid if self.num_agents > width * height: raise ValueError("Number of agents exceeds grid capacity.") # Create grid with random activation self.grid = SingleGrid(width, height, True) self.schedule = RandomActivation(self) if edu_setting: # Create walls numberRooms = 3 self.add_walls(numberRooms, width, height) self.midWidthRoom = floor(width / numberRooms / 2) self.midHeightRoom = floor(height / numberRooms / 2) self.widthRoom = floor(width / numberRooms) self.heightRoom = floor(height / numberRooms) numberRows = floor((self.heightRoom) / 2) widthRows = self.widthRoom - 4 location = [[] for _ in range(numberRooms * 2)] for i in range(numberRooms): for j in range(0, numberRows, 2): startWidth = 2 + (i % 3) * self.widthRoom for currentWidth in range(widthRows): location[i] += [(startWidth + currentWidth, j)] for i in range(3, numberRooms * 2): for j in range(0, numberRows, 2): startWidth = 2 + (i % 3) * self.widthRoom for currentWidth in range(widthRows): location[i] += [(startWidth + currentWidth, height - 1 - j)] # Set 3 goals per roster self.roster = [[location[0], location[3], location[1]], [location[5], location[2], location[0]], [location[4], location[1], location[5]]] # Create agents self.addAgents(lowS, 0, 0) self.addAgents(middleS, lowS, 1) self.addAgents(highS, lowS + highS, 2) # set up data collecter self.datacollector = DataCollector( model_reporters={"diseasepercentage": disease_collector}, agent_reporters={"disease": "disease"}) def heuristic(self, start, goal): """ Returns manhattan distance. start: current location (x,y) goal: goal location (x,y) """ dx = abs(start[0] - goal[0]) dy = abs(start[1] - goal[1]) return dx + dy def get_vertex_neighbors(self, pos): """ Returns all neighbors. pos: current position """ n = self.grid.get_neighborhood(pos, moore=False) neighbors = [] for item in n: if not abs(item[0] - pos[0]) > 1 and not abs(item[1] - pos[1]) > 1: neighbors += [item] return neighbors def move_cost(self, location): """ Return the cost of a location. """ if self.grid.is_cell_empty(location): return 1 # Normal movement cost else: return 100 # Very difficult to go through walls def add_walls(self, n, widthGrid, heightGrid): """ Add walls in grid. n: number of rooms horizontally widthGrid: width of the grid heightGrid: height of the grid """ widthRooms = floor(widthGrid / n) heightRooms = floor(heightGrid / n) heightHall = heightGrid - 2 * heightRooms # Add horizontal walls for i in range(n - 1): for y in range(heightRooms): brick = wall(self.num_agents, self) self.grid.place_agent(brick, ((i + 1) * widthRooms, y)) self.grid.place_agent(brick, ((i + 1) * widthRooms, y + heightRooms + heightHall)) doorWidth = 2 # Add vertical walls for x in range(widthGrid): if (x % widthRooms) < (widthRooms - doorWidth): brick = wall(self.num_agents, self) self.grid.place_agent(brick, (x, heightRooms)) self.grid.place_agent(brick, (x, heightRooms + heightHall - 1)) def addAgents(self, n, startID, sociability): """ Add agents with a sociability. n: number of agents startID: ID of the first added agent sociability: sociability of the agents """ disease_list = np.random.randint(0, 2, n) for i in range(n): # Set schedule for every agent if educational setting if self.edu_setting: a_roster = [] rosterNumber = self.random.randrange(len(self.roster)) rooms = self.roster[rosterNumber] for roomNumber in range(len(rooms)): loc = self.random.choice(rooms[roomNumber]) a_roster += [loc] (self.roster[rosterNumber][roomNumber]).remove(loc) else: a_roster = [] a = DiseaseAgent(i + startID, sociability, self, disease_list[i], a_roster) self.schedule.add(a) # Set agent outside grid, ready to enter, if edu setting # else randomly place on empty spot on grid if self.edu_setting: self.removed += [a] a.pos = None else: self.grid.place_agent(a, self.grid.find_empty()) def step(self): """ Continue one step in simulation. """ self.counter += 1 self.datacollector.collect(self) self.schedule.step()
class DiseaseModel(Model): """ A model with some number of agents. highS: Number of agents with high sociability. middleS: Number of agents with middle sociability. lowS: Number of agents with low sociability. width: Width of the grid. height: Height of the grid. edu_setting: Classrooms and set schedule if true, else random free movement. cureProb: Probability of agent getting better. cureProbFac: Factor of cureProb getting higher. mutateProb: Probability of a disease mutating. diseaseRate: Rate at which the disease spreads. """ def __init__(self, highS, middleS, lowS, width, height, edu_setting=True, cureProb=0.1, cureProbFac=2/1440, mutateProb=0.0050, diseaseRate=0.38): super().__init__() self.num_agents = highS + middleS + lowS self.lowS = lowS self.middleS = middleS self.highS = highS self.initialCureProb = cureProb self.cureProbFac = cureProbFac self.mutateProb = mutateProb self.diseaseRate = diseaseRate self.edu_setting = edu_setting self.maxDisease = 0# amount of mutations self.counter = 540 # keeps track of timesteps self.removed = [] self.exit = (width-1,floor(height/2)) # Check if agents fit within grid if self.num_agents > width * height: raise ValueError("Number of agents exceeds grid capacity.") # Create grid with random activation self.grid = SingleGrid(width, height, True) self.schedule = RandomActivation(self) if edu_setting: # Create walls numberRooms = 3 self.add_walls(numberRooms, width, height) self.midWidthRoom = floor(width / numberRooms / 2) self.midHeightRoom = floor(height / numberRooms / 2) # Calculate the centers of the 6 rooms roomLeftDown = (5 * self.midWidthRoom, self.midHeightRoom) roomLeftMid = (3 * self.midWidthRoom, self.midHeightRoom) roomLeftUp = (self.midWidthRoom, self.midHeightRoom) roomRightDown = (5 * self.midWidthRoom, 5 * self.midHeightRoom, ) roomRightMid = (3 * self.midWidthRoom, 5 * self.midHeightRoom) roomRightUp = (self.midWidthRoom, 5 * self.midHeightRoom) # Set 3 goals per roster self.roster = [[roomLeftDown, roomLeftUp, roomRightMid], [roomRightMid, roomLeftDown, roomRightDown], [roomRightUp, roomRightDown, roomLeftUp]] # Create agents self.addAgents(lowS, 0, 0) self.addAgents(middleS, lowS, 1) self.addAgents(highS, lowS + highS, 2) self.datacollector = DataCollector( model_reporters={"diseasepercentage": disease_collector}, agent_reporters={"disease": "disease"}) def heuristic(self, start, goal): """ Returns manhattan distance. start: current location (x,y) goal: goal location (x,y) """ dx = abs(start[0] - goal[0]) dy = abs(start[1] - goal[1]) return dx + dy def get_vertex_neighbors(self, pos): """ Returns all neighbors. pos: current position """ n = self.grid.get_neighborhood(pos, moore=False) neighbors = [] for item in n: if not abs(item[0]-pos[0]) > 1 and not abs(item[1]-pos[1]) > 1: neighbors += [item] return neighbors def move_cost(self, location): """ Return the cost of a location. """ if self.grid.is_cell_empty(location): return 1 # Normal movement cost else: return 100 def add_walls(self, n, widthGrid, heightGrid): """ Add walls in grid. n: number of rooms horizontally widthGrid: width of the grid heightGrid: height of the grid """ widthRooms = floor(widthGrid/n) heightRooms = floor(heightGrid/n) widthHall = widthGrid - 2 * widthRooms heightHall = heightGrid - 2 * heightRooms # Add horizontal walls for i in range(n - 1): for y in range(heightRooms): brick = wall(self.num_agents, self) self.grid.place_agent(brick, ((i + 1) * widthRooms, y)) self.grid.place_agent(brick, ((i + 1) * widthRooms, y + heightRooms + heightHall)) doorWidth = 2 # Add vertical walls for x in range(widthGrid): if (x % widthRooms) < (widthRooms - doorWidth): brick = wall(self.num_agents, self) self.grid.place_agent(brick, (x, heightRooms)) self.grid.place_agent(brick, (x, heightRooms + heightHall - 1)) def addAgents(self, n, startID, sociability): """ Add agents with a sociability. n: number of agents startID: ID of the first added agent sociability: sociability of the agents """ disease_list = np.random.randint(0,2,n) for i in range(n): a = DiseaseAgent(i + startID, sociability,self,disease_list[i]) self.schedule.add(a) # Add the agent to a random grid cell location = self.grid.find_empty() self.grid.place_agent(a, location) def step(self): """ Continue one step in simulation. """ self.counter += 1 self.datacollector.collect(self) self.schedule.step()
class SeparationBarrierModel(Model): def __init__(self, height, width, palestinian_density, settlement_density, settlers_violence_rate, settlers_growth_rate, suicide_rate, greed_level, settler_vision=1, palestinian_vision=1, movement=True, max_iters=1000): super(SeparationBarrierModel, self).__init__() self.height = height self.width = width self.palestinian_density = palestinian_density self.settler_vision = settler_vision self.palestinian_vision = palestinian_vision self.settlement_density = settlement_density self.movement = movement self.running = True self.max_iters = max_iters self.iteration = 0 self.schedule = RandomActivation(self) self.settlers_violence_rate = settlers_violence_rate self.settlers_growth_rate = settlers_growth_rate self.suicide_rate = suicide_rate self.greed_level = greed_level self.total_violence = 0 self.grid = SingleGrid(height, width, torus=False) model_reporters = { } agent_reporters = { # "x": lambda a: a.pos[0], # "y": lambda a: a.pos[1], } self.dc = DataCollector(model_reporters=model_reporters, agent_reporters=agent_reporters) self.unique_id = 0 # Israelis and palestinans split the region in half for (contents, x, y) in self.grid.coord_iter(): if random.random() < self.palestinian_density: palestinian = Palestinian(self.unique_id, (x, y), vision=self.palestinian_vision, breed="Palestinian", model=self) self.unique_id += 1 self.grid.position_agent(palestinian, x,y) self.schedule.add(palestinian) elif ((y > (self.grid.height) * (1-self.settlement_density)) and random.random() < self.settlement_density): settler = Settler(self.unique_id, (x, y), vision=self.settler_vision, model=self, breed="Settler") self.unique_id += 1 self.grid.position_agent(settler, x,y) self.schedule.add(settler) def add_settler(self, pos): settler = Settler(self.unique_id, pos, vision=self.settler_vision, model=self, breed="Settler") self.unique_id += 1 self.grid.position_agent(settler, pos[0], pos[1]) self.schedule.add(settler) def set_barrier(self,victim_pos, violent_pos): #print("Set barrier - Greed level", self.greed_level) visible_spots = self.grid.get_neighborhood(victim_pos, moore=True, radius=self.greed_level + 1) furthest_empty = self.find_furthest_empty_or_palestinian(victim_pos, visible_spots) x,y = furthest_empty current = self.grid[y][x] #print ("Set barrier!!", pos, current) free = True if (current is not None and current.breed == "Palestinian"): #print ("Relocating Palestinian") free = self.relocate_palestinian(current, current.pos) if (free): barrier = Barrier(-1, furthest_empty, model=self) self.grid.position_agent(barrier, x,y) # Relocate the violent palestinian #violent_x, violent_y = violent_pos #if violent_pos != furthest_empty: # violent_palestinian = self.grid[violent_y][violent_x] # self.relocate_palestinian(violent_palestinian, furthest_empty) def relocate_palestinian(self, palestinian, destination): #print ("Relocating Palestinian in ", palestinian.pos, "To somehwhere near ", destination) visible_spots = self.grid.get_neighborhood(destination, moore=True, radius=palestinian.vision) nearest_empty = self.find_nearest_empty(destination, visible_spots) #print("First Nearest empty to ", palestinian.pos, " Is ", nearest_empty) if (nearest_empty): self.grid.move_agent(palestinian, nearest_empty) else: #print ("Moveing to random empty") if (self.grid.exists_empty_cells()): self.grid.move_to_empty(palestinian) else: return False return True def find_nearest_empty(self, pos, neighborhood): nearest_empty = None sorted_spots = self.sort_neighborhood_by_distance(pos, neighborhood) index = 0 while (nearest_empty is None and index < len(sorted_spots)): if self.grid.is_cell_empty(sorted_spots[index]): nearest_empty = sorted_spots[index] index += 1 return nearest_empty def find_furthest_empty_or_palestinian(self, pos, neighborhood): furthest_empty = None sorted_spots = self.sort_neighborhood_by_distance(pos, neighborhood) sorted_spots.reverse() index = 0 while (furthest_empty is None and index < len(sorted_spots)): spot = sorted_spots[index] if self.grid.is_cell_empty(spot) or self.grid[spot[1]][spot[0]].breed == "Palestinian" : furthest_empty = sorted_spots[index] index += 1 return furthest_empty def sort_neighborhood_by_distance(self, from_pos, neighbor_spots): from_x, from_y = from_pos return sorted(neighbor_spots, key = lambda spot: self.eucledean_distance(from_x, spot[0], from_y, spot[1], self.grid.width, self.grid.height)) def eucledean_distance(self, x1,x2,y1,y2,w,h): # http://stackoverflow.com/questions/2123947/calculate-distance-between-two-x-y-coordinates return math.sqrt(min(abs(x1 - x2), w - abs(x1 - x2)) ** 2 + min(abs(y1 - y2), h - abs(y1-y2)) ** 2) def step(self): """ Advance the model by one step and collect data. """ self.violence_count = 0 # for i in range(100): self.schedule.step() self.total_violence += self.violence_count # average = self.violence_count / 100 #print("Violence average %f " % average) print("Total Violence: ", self.total_violence)
class GTModel(Model): def __init__(self, debug, size, i_n_agents, i_strategy, i_energy, child_location, movement, k, T, M, p, d, strategies_to_count, count_tolerance, mutation_type, death_threshold, n_groups): self.grid = SingleGrid(size, size, torus=True) self.schedule = RandomActivation(self) self.running = True self.debug = debug self.size = size self.agent_idx = 0 self.i_energy = i_energy # Payoff matrix in the form (my_move, op_move) : my_reward self.payoff = { ('C', 'C'): 2, ('C', 'D'): -3, ('D', 'C'): 3, ('D', 'D'): -1, } # Constant for max population control (cost of surviving) self.k = k # Constant for controlling dying of old age self.M = M # Minimum lifespan self.T = T # Minimum energy level to reproduce self.p = p # Mutation "amplitude" self.d = d # Whether to spawn children near parents or randomly self.child_location = child_location # Specify the type of movement allowed for the agents self.movement = movement # Specify how the agents mutate self.mutation_type = mutation_type # The minimum total_energy needed for an agent to survive self.death_threshold = death_threshold # Vars regarding which strategies to look for self.strategies_to_count = strategies_to_count self.count_tolerance = count_tolerance # Add agents (one agent per cell) all_coords = [(x, y) for x in range(size) for y in range(size)] agent_coords = self.random.sample(all_coords, i_n_agents) for _ in range(i_n_agents): group_idx = (None if n_groups is None else self.random.choice( range(n_groups))) agent = GTAgent(self.agent_idx, group_idx, self, i_strategy.copy(), i_energy) self.agent_idx += 1 self.schedule.add(agent) self.grid.place_agent(agent, agent_coords.pop()) # Collect data self.datacollector = DataCollector( model_reporters={ **{ 'strategies': get_strategies, 'n_agents': total_n_agents, 'avg_agent_age': avg_agent_age, 'n_friendlier': n_friendlier, 'n_aggressive': n_aggressive, 'perc_cooperative_actions': perc_cooperative_actions, 'n_neighbors': n_neighbor_measure, 'avg_delta_energy': avg_delta_energy, 'perc_CC': perc_CC_interactions, 'lin_fit_NC': coop_per_neig, 'lin_fit_NC_intc': coop_per_neig_intc, }, **{ label: strategy_counter_factory(strategy, count_tolerance) for label, strategy in strategies_to_count.items() } }) def alpha(self): # Return the cost of surviving, alpha DC = self.payoff[('D', 'C')] CC = self.payoff[('C', 'C')] N = len(self.schedule.agents) return self.k + 4 * (DC + CC) * N / (self.size * self.size) def time_to_die(self, agent): # There is a chance every iteration to die of old age: (A - T) / M # There is a 100% to die if the agents total energy reaches 0 return (agent.total_energy < self.death_threshold or self.random.random() < (agent.age - self.T) / self.M) def get_child_location(self, agent): if self.child_location == 'global': return self.random.choice(sorted(self.grid.empties)) elif self.child_location == 'local': # Iterate over the radius, starting at 1 to find empty cells for rad in range(1, int(self.size / 2)): possible_steps = [ cell for cell in self.grid.get_neighborhood( agent.pos, moore=False, include_center=False, radius=rad, ) if self.grid.is_cell_empty(cell) ] if possible_steps: return self.random.choice(possible_steps) # If no free cells in radius size/2 pick a random empty cell return self.random.choice(sorted(self.grid.empties)) def maybe_mutate(self, agent): # Mutate by adding a random d to individual Pi's if self.mutation_type == 'stochastic': # Copy the damn list new_strategy = agent.strategy.copy() # There is a 20% chance of mutation if self.random.random() < 0.2: # Each Pi is mutated uniformly by [-d, d] for i in range(4): mutation = self.random.uniform(-self.d, self.d) new_val = new_strategy[i] + mutation # Keep probabilities in [0, 1] new_val = (0 if new_val < 0 else 1 if new_val > 1 else new_val) new_strategy[i] = new_val # Mutate by choosing a random strategy from the list set elif self.mutation_type == 'fixed': new_strategy = random.choice( list(self.strategies_to_count.values())) elif self.mutation_type == 'gaussian_sentimental': # Copy the damn list new_strategy = agent.strategy.copy() # There is a 20% chance of mutation if self.random.random() < 0.2: # Each Pi is mutated by a value drawn from a gaussian # with mean=delta_energy for i in range(4): mutation = self.random.normalvariate( (agent.delta_energy + self.alpha()) / 14, self.d) new_val = new_strategy[i] + mutation # Keep probabilities in [0, 1] new_val = (0 if new_val < 0 else 1 if new_val > 1 else new_val) new_strategy[i] = new_val return new_strategy def maybe_reproduce(self, agent): # If we have the energy to reproduce, do so if agent.total_energy >= self.p: # Create the child new_strategy = self.maybe_mutate(agent) child = GTAgent(self.agent_idx, agent.group_id, self, new_strategy, self.i_energy) self.agent_idx += 1 # Set parent and child energy levels to p/2 child.total_energy = self.p / 2 agent.total_energy = self.p / 2 # Place child (Remove agent argument for global child placement) self.schedule.add(child) self.grid.place_agent(child, self.get_child_location(agent)) def step(self): if self.debug: print('\n\n==================================================') print('==================================================') print('==================================================') pprint(vars(self)) # First collect data self.datacollector.collect(self) # Then check for dead agents and for new agents for agent in self.schedule.agent_buffer(shuffled=True): # First check if dead if self.time_to_die(agent): self.grid.remove_agent(agent) self.schedule.remove(agent) # Otherwise check if can reproduce else: self.maybe_reproduce(agent) # Finally, step each agent self.schedule.step() def check_strategy(self, agent): # Helper function to check which strategy an agent would count as def is_same(strategy, a_strategy): tol = self.count_tolerance return all(strategy[i] - tol < a_strategy[i] < strategy[i] + tol for i in range(4)) return [ name for name, strat in self.strategies_to_count.items() if is_same(strat, agent.strategy) ]