class TotC(Model): """ Main model for the tragedy of the commons """ def __init__(self, initial_herdsmen=5, initial_sheep_per_herdsmen=0, initial_edges=5, l_coop=0, l_fairself=0, l_fairother=0, l_negrecip=0, l_posrecip=0, l_conf=0): super().__init__() self.width = GRID_SIZE self.height = GRID_SIZE self.grass = [] self.herdsmen = [] self.initial_herdsmen = initial_herdsmen self.initial_sheep_per_herdsmen = initial_sheep_per_herdsmen self.sheep_survival_rate = [] self.l_coop = l_coop self.l_fairself = l_fairself self.l_fairother = l_fairother self.l_negrecip = l_negrecip self.l_posrecip = l_posrecip self.l_conf = l_conf self.G = nx.gnm_random_graph(initial_herdsmen, initial_edges) Sheep.sheepdeaths = 0 Herdsman.i = 0 Herdsman.x = np.zeros(initial_herdsmen, dtype=np.int8) self.schedule_Grass = RandomActivation(self) self.schedule_Herdsman = StagedActivation( self, stage_list=["advance", "step"], shuffle=True) self.schedule_Sheep = RandomActivation(self) self.schedule = RandomActivation(self) self.grid = MultiGrid(self.width, self.height, torus=True) # "Grass" is the number of sheep that the grass can sustain self.datacollector = DataCollector({ "Grass": lambda m: self.get_expected_grass_growth() / .5, "Sheep": lambda m: self.get_sheep_count(), "Sheep deaths": lambda m: Sheep.sheepdeaths }) self.init_population() # required for the datacollector to work self.running = True self.datacollector.collect(self) def add_herdsman(self): """ At a herdsman at a random position on the grid. """ x = random.randrange(self.width) y = random.randrange(self.height) herdsman = self.new_agent(Herdsman, (x, y)) self.herdsmen.append(herdsman) def init_grass(self): """ Initialise a patch of grass at every square on the grid. """ for agent, x, y in self.grid.coord_iter(): self.grass.append(self.new_agent(Grass, (x, y))) def init_herdsman(self): """ Spawn `initial_herdsmen' herdsmen on the field. """ for i in range(getattr(self, "initial_herdsmen")): self.add_herdsman() def init_node_attr(self): """ Assign the unique herdsman ID as attribute to graph nodes for the social network. """ N = self.get_herdsman_count() for i in range(getattr(self, "initial_herdsmen")): for j in range(getattr(self, "initial_herdsmen")): if i is not j: if nx.has_path(self.G, source=i, target=j) == True: if nx.shortest_path_length(self.G, source=i, target=j) == 1: if sum(nx.common_neighbors(self.G, i, j)) > 5: self.herdsmen[i].friendship_weights.append(1) else: self.herdsmen[i].friendship_weights.append( .75 + .05 * sum(nx.common_neighbors(self.G, i, j))) elif nx.shortest_path_length(self.G, source=i, target=j) == 1: if sum(nx.common_neighbors(self.G, i, j)) > 10: self.herdsmen[i].friendship_weights.append(1) else: self.herdsmen[i].friendship_weights.append( .5 + .05 * sum(nx.common_neighbors(self.G, i, j))) else: self.herdsmen[i].friendship_weights.append( 1 / nx.shortest_path_length( self.G, source=i, target=j)) else: self.herdsmen[i].friendship_weights.append(1 / N) def init_population(self): """ Initialise grass, herdsmen, sheep, and the social network """ self.init_grass() self.init_herdsman() self.init_node_attr() def get_expected_grass_growth(self): """ Get an estimate of the expected grass growth for the next timestep. If grass is fully grown it will return 0.0123 (the average grass growth over its lifetime. """ return sum([ grass.next_density() if grass.density < 0.99 else 0.0123 for grass in self.grass ]) def get_grass_count(self): """ Get a sum of all grass densities. """ return sum([grass.density for grass in self.grass]) def get_herdsman_count(self): return self.schedule_Herdsman.get_agent_count() def get_sheep_count(self): return self.schedule_Sheep.get_agent_count() def new_agent(self, agent_type, pos): """ Create new agent, and add it to the scheduler. """ agent = agent_type(self.next_id(), self, pos) self.grid.place_agent(agent, pos) getattr(self, f"schedule_{agent_type.__name__}").add(agent) return agent def remove_agent(self, agent): """ Remove agent from the grid and the scheduler. """ self.grid.remove_agent(agent) getattr(self, f"schedule_{type(agent).__name__}").remove(agent) def run_model(self, step_count=200): """ Runs the model for a specific amount of steps. """ for i in range(step_count): self.step() def step(self): """ Calls the step method for grass and sheep. """ self.schedule_Grass.step() a = self.get_sheep_count() self.schedule_Sheep.step() b = self.get_sheep_count() c = b / a if a > 0 else 0 self.sheep_survival_rate.append(c) self.schedule_Herdsman.step() self.schedule.step() # save statistics self.datacollector.collect(self)
class World(Model): def __init__(self, N, coop, e_prob, width=100, height=100): self.num_agents = N self.grid = MultiGrid(width, height, False) self.schedule = RandomActivation(self) self.running = True self.population_center_x = np.random.randint( POP_MARGIN, self.grid.width - POP_MARGIN) self.population_center_y = np.random.randint( POP_MARGIN, self.grid.height - POP_MARGIN) self.num_energy_resources = RESERVE_SIZE self.num_explorers = 0 self.num_exploiters = 0 self.datacollector = DataCollector(model_reporters={ "Num_Explorer": "num_explorers", "Num_Exploiter": "num_exploiters" }) self.bases = self.init_base() self.ages = [] self.memoryLens = [ ] # This will store average memory length at death for agent self.expected_ages = [] self.member_tracker = [] self.energy_tracker = [] self.decay_rates = self.init_decay_rates() # add social agents to the world for i in range(1, self.num_agents + 1): if np.random.uniform() <= EXPLORER_RATIO: # create a new explorer a = Explorer("explorer_%d" % (i), self, coop) self.num_explorers += 1 else: # create a new exploiter a = Exploiter("exploiter_%d" % (i), self, coop, e_prob) self.num_exploiters += 1 # keep society members confined at beginning x = np.random.randint(self.population_center_x - POP_SPREAD, self.population_center_x + POP_SPREAD) y = np.random.randint(self.population_center_y - POP_SPREAD, self.population_center_y + POP_SPREAD) self.grid.place_agent(a, (x, y)) # add agent to scheduler self.schedule.add(a) self.expected_ages.append(a.energy / a.living_cost) # add energy reserves to the world for i in range(self.num_energy_resources): a = EnergyResource("energy_reserve_%d" % (i), self, self.decay_rates[i]) # decide location of energy reserve x = np.random.randint(0, self.grid.width) y = np.random.randint(0, self.grid.height) self.grid.place_agent(a, (x, y)) self.schedule.add(a) def init_base(self): pos = (self.population_center_x, self.population_center_y) return self.grid.get_neighborhood(pos, True, radius=POP_SPREAD) def init_decay_rates(self): decay_rates = list( np.random.uniform(-0.1, 0.02, self.num_energy_resources)) np.random.shuffle(decay_rates) return decay_rates def step(self): self.datacollector.collect(self) self.schedule.step() self.member_tracker.append((self.num_explorers, self.num_exploiters)) # keep track of total energy in world if self.schedule.time % 50 == 0: energies = [ e.reserve for e in self.schedule.agents if isinstance(e, EnergyResource) ] if len(energies) > 0: mean_energy = np.mean(energies) else: mean_energy = 0 self.energy_tracker.append(mean_energy) # change location of energy resources every 500 steps randomly # and change decay_rate to opposite every 100 steps if self.schedule.time % 50 == 0: for e in self.schedule.agents: if isinstance(e, EnergyResource) and np.random.uniform() < 0.1: # change location e.decay_rate *= -1 if self.schedule.time % 500 == 0: self.grid.remove_agent(e) x = np.random.randint(0, self.grid.width) y = np.random.randint(0, self.grid.height) self.grid.place_agent(e, (x, y))