class Population(Model): """Population Adapted from https://www.medrxiv.org/content/10.1101/2020.03.18.20037994v1.full.pdf Model Parameters: spread_chance: probability of infection based on contact gamma: mean incubation period alpha: probability of become asymptomatic vs symptomatic gamma_AR: infectious period for asymptomatic people gamma_YR: infectious period for symptomatic people delta: death rate due to disease The social distancing func takes time passed -> new interaction multiplier """ def __init__(self, graph, model_parameters, social_distancing_func=lambda x: 1): # Model initialization self.population_size = model_parameters['population size'] self.initial_outbreak_size = model_parameters['initial outbreak size'] self.graph = graph self.grid = NetworkGrid(self.graph) self.schedule = SimultaneousActivation(self) self.social_distancing_func = social_distancing_func self.datacollector = DataCollector({ # "Exposed": count_exposed, "Susceptible": count_susceptible, "Recovered": count_recovered, # "Asymptomatic": count_asymptomatic, # "Symptomatic": count_symptomatic, "Diseased": count_diseased, "Removed": count_removed }) self.model_parameters = model_parameters for i, node in enumerate(self.graph.nodes()): a = Person(i, self, State.SUSCEPTIBLE, model_parameters, social_distancing_func) self.schedule.add(a) self.grid.place_agent(a, i) if i % 100 == 0: logger.info("Finished with agent " + str(i)) infected_nodes = self.random.sample(self.graph.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.status = State.EXPOSED self.datacollector.collect(self) print("Model initialized...\n") def step(self): self.datacollector.collect(self) self.schedule.step() def run(self, n): for i in range(n): logger.info("Steps Completed: " + str(i)) self.step() def run_until_stable(self): max_steps = 1e3 steps = 0 window_size = 50 while steps < max_steps: self.step() logger.info("Steps Completed:" + str(steps)) if steps > window_size: data = self.get_data() last_value = int(data.tail(1)['Diseased']) if last_value == 0: break window_average = np.mean( data.tail(window_size) ['Diseased']) # Window for determining stopping rule if abs(last_value - window_average) / window_average < 0.005: break steps += 1 def cross_influence(self, influence_coefficients, ratios): for inf_coeff, ratio in zip(influence_coefficients, ratios): susceptibles = list( filter(lambda x: x.status == State.SUSCEPTIBLE, self.grid.get_all_cell_contents())) to_flip = self.random.sample( susceptibles, int(inf_coeff * ratio * len(susceptibles))) for agent in to_flip: agent.status = State.EXPOSED def clear_social_distancing_func(self): """ Clears the social distancing function of the model and all its agents for pickling :return: None """ self.social_distancing_func = None for agent in self.grid.get_all_cell_contents(): agent.social_distancing_func = None def reinstate_social_distancing_func(self, social_distancing_func=lambda x: 1): """ Re-adds the social distancing func to the model and all its agents :param social_distancing_func: social distancing func to be re-added :return: None """ self.social_distancing_func = social_distancing_func for agent in self.grid.get_all_cell_contents(): agent.social_distancing_func = social_distancing_func def save_model(self, filename): """ Save the model to a pickle and dill file :param filename: filename (without extension) to save to :return: None """ with open(filename + ".dil", 'wb') as f: dill.dump(self.social_distancing_func, f) self.clear_social_distancing_func() with open(filename + ".pkl", 'wb') as f: pickle.dump(self, f) def get_data(self): return self.datacollector.get_model_vars_dataframe() '''
class Themepark(Model): def __init__(self, N_attr, N_cust, width, height, strategy, theme, max_time, weight, adaptive): self.max_time = max_time self.N_attr = N_attr self.penalty_per = PENALTY_PERCENTAGE self.weight = weight self.adaptive = adaptive self.strategies = STRATEGIES self.x_list, self.y_list, self.positions = xlist, ylist, positions self.x_list, self.y_list, self.positions = get_attraction_coordinates( WIDTH, HEIGHT, self.N_attr, theme) self.happinesses = [] self.path_coordinates = get_coordinates(WIDTH, HEIGHT, NUM_OBSTACLES, self.N_attr, theme) self.N_cust = N_cust # num of customer agents self.total_steps = 0 self.cust_ids = N_cust self.strategy = strategy self.grid = MultiGrid(width, height, torus=False) self.schedule = BaseScheduler(self) self.schedule_Attraction = BaseScheduler(self) self.schedule_Customer = BaseScheduler(self) self.totalTOTAL = 0 #TODO: DEZE NAAM VERANDEREN self.attractions = self.make_attractions() self.attraction_history = self.make_attr_hist() self.park_score = [] self.data_dict = {} self.hist_random_strat = [] self.hist_close_strat = [] self.all_rides_list = [] self.strategy_composition = self.make_strategy_composition() self.customers = self.add_customers(self.N_cust) self.monitor = Monitor(self.max_time, self.N_attr, self.positions) self.running = True # TODO: ALS HET GOED IS KUNNEN AL DEZE INITS WEG, MAAR DIT MOETEN WE WEL NOG EVEN DUBBEL CHECKEN # --> dit is gecheckt op het runnen van main_cluster_random_noise # self.theme = theme # self.starting_positions = [[int((WIDTH/2)-1), 0], [int(WIDTH/2), 0], [int((WIDTH/2)+1), 0]] # self.width = width # self.height = height # self.data = [] # self.data_customers = [] # self.memory = 5 # self.customer_score = [] # self.only_random = False # self.total_waited_time = 0 # Initialize dictionary of attractions for attraction in self.get_attractions(): self.data_dict[attraction.unique_id] = ({ "id": attraction.unique_id, "length": attraction.attraction_duration, "waiting_list": [] }) # TODO ADD COMMENT (SNAP NIET WAT DE DATACOLLECTOR IS) if len(self.strategies) == 6: self.datacollector = DataCollector({ "Random": lambda m: self.strategy_counter(self.strategies[0]), "0.00": lambda m: self.strategy_counter(self.strategies[1]), "0.25": lambda m: self.strategy_counter(self.strategies[2]), "0.50": lambda m: self.strategy_counter(self.strategies[3]), "0.75": lambda m: self.strategy_counter(self.strategies[4]), "1.00": lambda m: self.strategy_counter(self.strategies[5]), }) else: self.datacollector = DataCollector({ "0.00": lambda m: self.strategy_counter(self.strategies[0]), "0.25": lambda m: self.strategy_counter(self.strategies[1]), "0.50": lambda m: self.strategy_counter(self.strategies[2]), "0.75": lambda m: self.strategy_counter(self.strategies[3]), "1.00": lambda m: self.strategy_counter(self.strategies[4]), }) self.datacollector2 = DataCollector( {"score": lambda m: self.make_score()}) def make_score(self): """ Get the efficiency score """ ideal = {} cust_in_row = 0 for i in range(len(self.get_attractions())): ideal[i] = self.N_cust / self.N_attr cust_in_row += self.get_attractions()[i].N_current_cust tot_difference = 0 for i in range(len(self.get_attractions())): difference = abs(cust_in_row / self.N_attr - self.get_attractions()[i].N_current_cust) tot_difference += difference fraction_not_right = (tot_difference / self.N_cust) return abs(1 - (fraction_not_right)) * cust_in_row / self.N_cust def make_attr_hist(self): """ Initialize a dictionary in which the history of the attractions can be added in. """ attraction_history = {} for attraction in self.get_attractions(): attraction_history[attraction] = [0] * (self.max_time + 1) return attraction_history def strategy_counter(self, strategy): """ Count how many customers of different strategies are at the attractions """ counter_total = {} for attraction_pos in self.positions: agents = self.grid.get_neighbors(attraction_pos, moore=True, radius=0, include_center=True) counter = 0 for agent in self.customers: if agent.weight == strategy: counter += 1 return counter def make_strategy_composition(self): """ TODO: ANNEMIJN KAN JIJ HIER COMMENTS BIJ DOEN? + RANDOM_TEST_4 WEGHALEN """ if self.strategy == "Random_test_4": self.strategies = ["Random_test_4", 0.0, 0.25, 0.50, 0.75, 1.0] dict = { self.strategies[0]: 1 / 6, self.strategies[1]: 0.20, self.strategies[2]: 0.20, self.strategies[3]: 0.20, self.strategies[4]: 0.20, self.strategies[5]: 0.20 } composition_list = [] for i in range(len(self.strategies)): if i == 0: dict[self.strategies[i]] = FRACTION_RANDOM continue else: composition_list.append(random.randint(0, 100)) sum_comp = sum(composition_list) sum_comp = sum_comp - sum_comp * FRACTION_RANDOM for i in range(len(self.strategies)): if i == 0: continue else: dict[self.strategies[i]] = composition_list[i - 1] / sum_comp else: dict = { self.strategies[0]: 0.20, self.strategies[1]: 0.20, self.strategies[2]: 0.20, self.strategies[3]: 0.20, self.strategies[4]: 0.20 } composition_list = [] for i in range(len(self.strategies)): composition_list.append(random.randint(0, 100)) sum_comp = sum(composition_list) sum_comp = sum_comp for i in range(len(self.strategies)): dict[self.strategies[i]] = composition_list[i - 1] / sum_comp return dict def make_attractions(self): """ Initialize attractions on fixed position. """ attractions = {} for i in range(self.N_attr): pos = (self.x_list[i], self.y_list[i]) if self.grid.is_cell_empty(pos): name = str(i) a = Attraction(i, self, pos, name, self.N_cust, self.weight) attractions[i] = a self.schedule_Attraction.add(a) self.grid.place_agent(a, pos) return attractions def get_attractions(self): """ Get a list with all attractions. """ agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) attractions = [] for agent in agents: if type(agent) == Attraction: attractions.append(agent) return attractions def calculate_people(self): """ Calculate how many customers are in which attraction. """ counter_total = {} for attraction_pos in self.positions: agents = self.grid.get_neighbors(attraction_pos, moore=True, radius=0, include_center=True) counter = 0 for agent in agents: if type(agent) is Customer: counter += 1 else: attraction = agent attraction.N_current_cust = counter counter_total[attraction.unique_id] = counter return list(counter_total.values()) def add_customers(self, N_cust, added=False): """ Initialize customers on random positions. """ weights_list = [] if self.adaptive is True: for j in self.strategy_composition.keys(): for i in range(round(N_cust * self.strategy_composition[j])): weights_list.append(j) if len(weights_list) < self.N_cust: rand = random.choice(self.strategies) weights_list.append(rand) elif len(weights_list) > self.N_cust: rand = random.choice(weights_list) weights_list.remove(rand) else: # if the strategy is not random add weights to weights_list if self.strategy is not "Random": for i in range(round(N_cust)): weights_list.append(self.weight) cust_list = [] for i in range(N_cust): pos_temp = [ random.randint(0, WIDTH - 1), random.randint(0, HEIGHT - 1) ] rand_x, rand_y = pos_temp[0], pos_temp[1] pos = (rand_x, rand_y) if added is True: i = self.cust_ids if self.strategy == "Random_test_4": if weights_list[i] == "Random_test_4": strategy = "Random_test_4" else: strategy = "Closest_by" else: strategy = self.strategy # Set weight if weights_list == []: weight = None else: weight = weights_list[i] # Initialize customer and add customer to the model a = Customer(i, self, pos, self.x_list, self.y_list, self.positions, strategy, weight, self.adaptive) self.schedule_Customer.add(a) self.grid.place_agent(a, pos) cust_list.append(a) return cust_list def calc_waiting_time(self): """ Calculate the waitingtime per attraction """ counter_total = {} attractions = self.get_attractions() for attraction in attractions: counter_total[ attraction.unique_id] = attraction.current_waitingtime return counter_total def calculate_people_sorted(self): """ Calculate how many customers are in which attraction. Returns a SORTED LIST. For example: indexes = [3, 2, 5, 1, 4] indicates that attraction3 has the least people waiting. """ counter_total = {} for attraction_pos in self.positions: agents = self.grid.get_neighbors(attraction_pos, moore=True, radius=0, include_center=True) counter = 0 for agent in agents: if type(agent) is Customer: counter += 1 else: attraction = agent attraction.N_current_cust = counter self.attraction_history[attraction][self.totalTOTAL] = counter counter_total[attraction.unique_id] = counter return counter_total def make_route(self): """ Draw coordinates of a possible path. """ for i in range(len(self.path_coordinates)): pos = self.path_coordinates[i] if pos not in self.positions: # Create path agent path = Route(i, self, pos) self.schedule.add(path) self.grid.place_agent(path, pos) # TODO: THEMEPARK SCORE GEBRUIKEN WE NIET MEER, DIT ALLEMAAL VERWIJDEREN OVERAL? def get_themepark_score(self): """ Get score of a themepark based on: - A total of all waitingtimes for every customer - The total number of rides taken """ attractions = self.get_attractions() total_wait, total_rides = 0, 0 for attraction in attractions: total_wait += attraction.current_waitingtime if attraction.current_a is not None: total_rides += 1 if total_rides == 0: return total_rides return (total_wait / total_rides) def get_strategy_history(self): """ Update history with how many customers chose which strategy. """ customers = self.get_customers() randomstrat, closebystrat = 0, 0 for customer in customers: if customer.strategy == "Random" or customer.strategy == "Random_test_4": randomstrat += 1 elif customer.strategy == "Closest_by": closebystrat += 1 self.hist_random_strat.append(randomstrat) self.hist_close_strat.append(closebystrat) def get_customers(self): """ Returns a list of all the customers in the themepark. """ agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) customers = [] # Add customers to list for agent in agents: if type(agent) == Customer: customers.append(agent) return customers def get_data_customers(self): """ Return dictionary with data of customers. """ data = {} agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) for agent in agents: if type(agent) is Customer: data[agent.unique_id] = { "totalwaited": agent.total_ever_waited, "visited_attractions": agent.nmbr_attractions, "strategy": agent.strategy, "swapped_strat": agent.strategy_swap_hist } return data # TODO: NET ALS THEMEPARK SCORE GEBRUIKEN WE DIT NEIT MEER TOCH???? def calc_hapiness(self): """ Calculate mean hapiness of all customers, based on: - How many rides were taken - Number of times in the same attraction - Total waiting time """ customers = self.get_customers() scores = [] for customer in customers: history = customer.history values = list(history.values()) total_rides = sum(values) if total_rides != 0: scores.append(total_rides / self.N_attr - self.totalTOTAL / customer.total_ever_waited) else: return None scores = np.interp(scores, (min(scores), max(scores)), (1, 10)) return np.mean(scores) def get_history_list(self): """ Create a list with the history of the customers. """ customers = self.get_customers() histories = {} for customer in customers: history = customer.history values = list(history.values()) histories[customer.unique_id] = values return histories def final(self): """ End run and return data. """ # Create list with at every time step the amount of attractions that are active attractions = self.get_attractions() self.all_rides_list = [0] * len(attractions[0].in_attraction_list) for attraction in attractions: for i in range(len(attraction.in_attraction_list)): self.all_rides_list[i] += attraction.in_attraction_list[i] # Change the all_rides_list into fractions for i in range(len(self.all_rides_list)): self.all_rides_list[i] /= self.N_attr # Initialize history list and get agents hist_list = [] agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) # Save history of all customers cust_data = self.get_data_customers() for agent in agents: if type(agent) is Customer: sum_attr = sum(agent.history.values()) if sum_attr > 0: hist_list.append(agent.strategy_swap_hist) else: hist_list.append(agent.strategy_swap_hist) histories = self.get_history_list() # Save data try: pickle.dump(self.datacollector.get_model_vars_dataframe(), open("../data/strategy_history.p", 'wb')) pickle.dump(self.datacollector2.get_model_vars_dataframe(), open("../data/eff_score_history.p", 'wb')) pickle.dump(cust_data, open("../data/customers.p", 'wb')) pickle.dump(self.park_score[-1], open("../data/park_score.p", "wb")) pickle.dump(self.happinesses, open("../data/hapiness.p", "wb")) pickle.dump(histories, open("../data/cust_history.p", 'wb')) except: pickle.dump(self.datacollector.get_model_vars_dataframe(), open("data/strategy_history.p", 'wb')) pickle.dump(self.datacollector2.get_model_vars_dataframe(), open("data/eff_score_history.p", 'wb')) pickle.dump(cust_data, open("data/customers.p", 'wb')) pickle.dump(self.park_score[-1], open("data/park_score.p", "wb")) pickle.dump(self.happinesses, open("data/hapiness.p", "wb")) pickle.dump(histories, open("data/cust_history.p", 'wb')) try: pickle.dump(self.all_rides_list, open("../data/all_rides.p", "wb")) except: pickle.dump(self.all_rides_list, open("data/all_rides.p", "wb")) print() print("RUN HAS ENDED") print() def save_data(self): """ Save data of all attractions and customers. """ # Get info waitinglines = self.calc_waiting_time() for i in range(len(self.attractions)): self.data_dict[i]["waiting_list"].append(waitinglines.get(i)) self.park_score.append(sum(waitinglines.values())) self.happinesses.append(self.calc_hapiness()) def step(self): """ Advance the model by one step. """ # Take a step if max steps is not reached if self.totalTOTAL < self.max_time: self.totalTOTAL += 1 self.schedule.step() self.datacollector.collect(self) self.datacollector2.collect(self) self.schedule_Attraction.step() self.schedule_Customer.step() self.total_steps += 1 self.save_data() self.get_strategy_history() # Stop simulation else: for key in self.attraction_history.keys(): y = self.attraction_history[key] x = list(range(0, self.max_time)) self.final()
class EcoModel(Model): ''' Represents the landscape in the dryland model as a two-dimensional square grid ''' def __init__(self, b, m, config_file): ''' Create a new grid. Args: b: plant establishment probability. m: mortality probability of a vegetated site config_file: a .json file with remaining parameters ''' # open json file with parameters params = json.load(open(config_file)) # Initialize model variables self.height = params["height"] self.width = params["width"] self.num_agents = self.width * self.height self.schedule = SimultaneousActivation(self) self.delta = params["delta"] self.c = params["c"] self.r = params["r"] self.d = params["d"] self.f = params["f"] self.m = m self.b_base = b self.b = b self.emp_dens = params["Empty sites density"] self.deg_dens = params["Degraded sites density"] self.rho_veg = 1 - self.emp_dens - self.deg_dens # Set up flowlength parameters self.use_fl = params["Use Flowlength"] if self.use_fl: self.patch_size = params["Patch size"] # patch side size in meters self.L = self.height self.theta = np.radians(params["Theta"]) self.d_s = self.patch_size / np.cos(self.theta) self.max_fl = self.d_s * (self.height + 1) / 2 self.alpha_feedback = params["alpha_feedback"] self.alpha_bare = 1 q = self.alpha_bare * (1 - self.rho_veg) self.fl = (1 - self.rho_veg) * ((1 - q) * self.L - q * (1 - q**self.L)) * self.d_s / ( (1 - q)**2 * self.L) self.b = self.b_base * ( 1 - self.alpha_feedback * self.fl / self.max_fl) # variables for infrequent rainfall self.infr_rain = params["Use infrequent rain"] if self.infr_rain: self.rain_period = params["Rain period"] self.no_rain_period = params["No rain period"] self.is_raining = True self.water_on = 0 self.water_off = self.no_rain_period self.count_veg = int(self.rho_veg * self.num_agents) # Set up the model and data collection self.grid = Grid(self.height, self.width, torus=params["Use Torus"]) if self.fl: self.datacollector = DataCollector({ "Empty": lambda m: self.count_type(m, "Empty"), "Vegetated": lambda m: self.count_type(m, "Vegetated"), "Degraded": lambda m: self.count_type(m, "Degraded"), "qplusplus": lambda m: self.calculate_local_densities(m)[0], "qminusplus": lambda m: self.calculate_local_densities(m)[1], "qminusminus": lambda m: self.calculate_local_densities(m)[2], "flowlength": lambda m: self.fl, "b": lambda m: self.b }) else: self.datacollector = DataCollector({ "Empty": lambda m: self.count_type(m, "Empty"), "Vegetated": lambda m: self.count_type(m, "Vegetated"), "Degraded": lambda m: self.count_type(m, "Degraded"), "qplusplus": lambda m: self.calculate_local_densities(m)[0], "qminusplus": lambda m: self.calculate_local_densities(m)[1], "qminusminus": lambda m: self.calculate_local_densities(m)[2], "b": lambda m: self.b }) # Define patches for x in range(self.width): for y in range(self.height): rand_num = random.random() if rand_num < self.deg_dens: new_patch = Patch(self, (x, y), "Degraded") self.grid[y][x] = new_patch self.schedule.add(new_patch) elif rand_num < self.emp_dens + self.deg_dens: new_patch = Patch(self, (x, y), "Empty") self.grid[y][x] = new_patch self.schedule.add(new_patch) else: new_patch = Patch(self, (x, y), "Vegetated") # Create a patch self.grid[y][x] = new_patch self.schedule.add(new_patch) self.datacollector.collect(self) self.running = True def step(self): ''' Advance the model by one step. ''' # Calculate vegetation density self.count_veg = self.count_type(self, "Vegetated") self.rho_veg = self.count_veg / self.num_agents # Calculate Flowlength index if self.use_fl: # calculate probability of having neighbouring non-vegetated sites rho_minusminus = float(self.datacollector.get_model_vars_dataframe( ).qminusminus.tail(1)) * (1 - self.rho_veg) self.alpha_bare = rho_minusminus / (1 - self.rho_veg)**2 q_flowlength = self.alpha_bare * (1 - self.rho_veg) # calculate Flowlength index based on the connectivity of non-vegetated patches self.fl = (1 - self.rho_veg) * ((1 - q_flowlength) * self.L - q_flowlength * (1 - q_flowlength ** self.L))\ * self.d_s / ((1 - q_flowlength) ** 2 * self.L) # get the establishment probability as the function of Flowlength and connectivity strength self.b = self.b_base * ( 1 - self.alpha_feedback * self.fl / self.max_fl) # calculations involving infrequent rain if self.infr_rain: if self.is_raining: if self.water_on < self.rain_period - 1: self.water_on += 1 else: self.is_raining = False self.water_off = 0 self.b = self.b_base else: if self.water_off < self.no_rain_period - 1: self.water_off += 1 self.b = self.b_base else: self.is_raining = True self.water_on = 0 self.schedule.step() self.datacollector.collect(self) @staticmethod def count_type(model, patch_condition): ''' Helper method to count patches with a given condition in a given model ''' count = 0 for patch in model.schedule.agents: if patch.condition == patch_condition: count += 1 return count @staticmethod def calculate_local_densities(model): ''' Helper method to calculate global averages for conditional probabilities for having vegetated or degraded neighbors given the vegetated state, and for having non-vegetated neighbors given the non-vegetated state. (non-vegetated = empty or degraded) ''' qplusplus = [] qminusplus = [] qminusminus = [] for patch in model.schedule.agents: if patch.condition == "Vegetated": qplusplus.append(patch.get_q()) qminusplus.append(patch.get_q_minus()) else: qminusminus.append(patch.get_q_nonveg()) return (np.mean(qplusplus), np.mean(qminusplus), np.mean(qminusminus))
class ExplorationArea(Model): def __init__( self, nrobots, wifi_range, radar_radius=6, alpha=8.175, gamma=0.65, inj_pri=0, ninjured=None, ncells=None, obstacles_dist=None, load_file=None, dump_datas=True, # enable data collection alpha_variation=False, # record datas for alpha variation studies alpha_csv=alpha_csv, # aggregate datas alpha_step_csv=alpha_step_csv, # single step datas gamma_variation=False, # record datas for gamma variation studies gamma_csv=gamma_csv, optimization_task=False, # enable a small part of data collection for optimization task time_csv=number_of_steps_csv, robot_status_csv=robot_status_csv): # checking params consistency if not load_file and (not ncells or not obstacles_dist or not ninjured): print("Invalid params") sys.exit(-1) # used in server start self.running = True self.nrobots = nrobots self.radar_radius = radar_radius self.ncells = ncells self.obstacles_dist = obstacles_dist self.wifi_range = wifi_range self.alpha = alpha self.gamma = gamma self.ninjured = ninjured self.inj_pri = inj_pri self.dump_datas = dump_datas self.optimization_task = optimization_task self.frontier = set() self.broken_beans = 0 # Data collection tools if self.dump_datas: # it represents the sum of the difficulties of every cell self.total_difficulty = 0 self.dc_robot_status = DataCollector({ "idling": lambda m: self.get_number_robots_status(m, "idling"), "travelling": lambda m: self.get_number_robots_status(m, "travelling"), "exploring": lambda m: self.get_number_robots_status(m, "exploring"), "deploying_bean": lambda m: self.get_number_robots_status(m, "deploying_bean"), "step": lambda m: self.get_step(m) }) self.time_csv = time_csv self.robot_status_csv = robot_status_csv if self.optimization_task: self.total_idling_time = 0 self.alpha_variation = alpha_variation self.gamma_variation = gamma_variation if self.alpha_variation: self.costs_each_path = list() self.alpha_csv = alpha_csv self.alpha_step = dict() self.alpha_step_csv = alpha_step_csv if self.gamma_variation: self.gamma_df = pd.DataFrame(columns=["step", "mean", "std"]) self.gamma_csv = gamma_csv self.schedule = RandomActivation(self) # unique counter for agents self.agent_counter = 1 self.nobstacle = 0 # graph of seen cells self.seen_graph = nx.DiGraph() rnd.seed() # place a cell agent for store data and visualization on each cell of the grid # if map is not taken from file, create it if load_file == None: self.grid = MultiGrid(ncells + 2, ncells + 2, torus=False) for i in self.grid.coord_iter(): if i[1] != 0 and i[2] != 0 and i[1] != self.ncells + 1 and i[ 2] != self.ncells + 1: rand = np.random.random_sample() obstacle = True if rand < self.obstacles_dist else False # if obstacle if obstacle: self.nobstacle += 1 difficulty = math.inf explored = -1 priority = 0 utility = -math.inf # if free else: difficulty = np.random.randint(low=1, high=13) if self.dump_datas: self.total_difficulty += difficulty explored = 0 priority = 0 utility = 1.0 # if contour cell else: difficulty = np.random.randint(low=1, high=13) explored = -2 priority = -math.inf utility = -math.inf # place the agent in the grid a = Cell(self.agent_counter, self, i[1:], difficulty, explored, priority, utility) self.grid.place_agent(a, i[1:]) self.agent_counter += 1 # create injured agents valid_coord = [] for i in self.grid.coord_iter(): cell = [ e for e in self.grid.get_cell_list_contents(i[1:]) if isinstance(e, Cell) ][0] if cell.explored == 0: valid_coord.append(cell.pos) for i in range(0, ninjured): inj_index = rnd.choice(valid_coord) a = Injured(self.agent_counter, self, inj_index) self.schedule.add(a) self.grid.place_agent(a, inj_index) self.agent_counter += 1 else: # load map from file try: with open(load_file, 'r') as f: file = f.read() except: print("file not found") sys.exit(-1) exported_map = literal_eval(file) self.ncells = int(math.sqrt(len(exported_map["Cell"].keys()))) - 2 self.grid = MultiGrid(self.ncells + 2, self.ncells + 2, torus=False) for index in exported_map["Cell"].keys(): cell = exported_map["Cell"][index] difficulty = cell[2] explored = cell[3] priority = cell[4] utility = cell[5] if difficulty == "inf": difficulty = math.inf if priority == "-inf": priority = -math.inf if utility == "-inf": utility = -math.inf if explored == -1: self.nobstacle += 1 if self.dump_datas and utility == 1: self.total_difficulty += difficulty a = Cell(self.agent_counter, self, index, difficulty, explored, priority, utility) self.grid.place_agent(a, index) self.agent_counter += 1 for index in exported_map["Injured"].keys(): a = Injured(self.agent_counter, self, index) self.schedule.add(a) self.grid.place_agent(a, index) self.agent_counter += 1 # create robotic agents row = 0 starting_coord = [] # data collection number of beans requested if self.dump_datas: self.deployed_beans_at_start = 0 # generating the list for the starting position of robots for c in range(self.grid.width): # take the agent cell cell = [ e for e in self.grid.get_cell_list_contents(tuple([c, row])) if isinstance(e, Cell) ][0] if cell.explored != -1: starting_coord.append(c) for i in range(0, self.nrobots): column = rnd.choice(starting_coord) a = Robot(self.agent_counter, self, tuple([column, row]), self.radar_radius) self.schedule.add(a) self.grid.place_agent(a, (column, row)) self.agent_counter += 1 # create initial frontier: add cell in front of the robot if valid and not obstacles cell = [ e for e in self.grid.get_cell_list_contents( tuple([column, row + 1])) if isinstance(e, Cell) ][0] if cell.explored == 0: self.frontier.add(tuple([column, row + 1])) try: cell = [ e for e in self.grid.get_cell_list_contents( tuple([column + 1, row + 1])) if isinstance(e, Cell) ][0] if cell.explored == 0: self.frontier.add(tuple([column + 1, row + 1])) except: pass try: cell = [ e for e in self.grid.get_cell_list_contents( tuple([column - 1, row + 1])) if isinstance(e, Cell) ][0] if cell.explored == 0: self.frontier.add(tuple([column - 1, row + 1])) except: pass cell = [ e for e in self.grid.get_cell_list_contents(tuple([column, row])) if isinstance(e, Cell) ][0] # in the cell where some robots are deployed, only one bean is deployed if not cell.wifi_bean: cell.wifi_bean = True for index in self.grid.get_neighborhood( cell.pos, "moore", include_center=False, radius=self.wifi_range): cell = [ e for e in self.grid.get_cell_list_contents(index) if isinstance(e, Cell) ][0] cell.wifi_covered = True if self.dump_datas: self.deployed_beans_at_start += 1 # what the model does at each time step def step(self): # data collection for alpha variation if self.alpha_variation: sim_step = self.get_step(self) self.alpha_step[sim_step] = list() # call step function for all of the robots in random order self.schedule.step() if self.dump_datas: self.dc_robot_status.collect(self) if self.optimization_task: self.total_idling_time += self.get_number_robots_status( self, "idling") if self.gamma_variation: distances = self.compute_robot_distances(self) self.gamma_df = self.gamma_df.append( { "step": self.get_step(self), "mean": distances[0], "std": distances[1] }, ignore_index=True, sort=False) # if all seen cells have benn explored, stop the simulation # we do this so if there are unreachable cells, the cannot be seen, so the simulation stops anyway stop_exploration_done = True for node in self.seen_graph.nodes(): cell = [ obj for obj in self.grid.get_cell_list_contents(node) if isinstance(obj, Cell) ][0] if cell.explored == 0 or cell.explored == 1: stop_exploration_done = False stop_no_robots = False if len([x for x in self.schedule.agents if isinstance(x, Robot)]) == 0: stop_no_robots = True stop = stop_exploration_done or stop_no_robots if stop: print("Simultation ended") # Data collection if self.dump_datas: df = pd.read_csv(self.time_csv) df = df.append( { "nrobots": self.nrobots, "ncells": self.ncells, "steps": self.schedule.steps, "total_difficulty": self.total_difficulty, "beans_deployed": self.get_number_bean_deployed(self) }, ignore_index=True) df.to_csv(self.time_csv, index=False) df_robots_status = self.dc_robot_status.get_model_vars_dataframe( ) df = pd.read_csv(self.robot_status_csv) if len(df["sim_id"]) == 0: df_robots_status["sim_id"] = 0 else: df_robots_status["sim_id"] = df["sim_id"][df.index[-1]] + 1 df = df.append(df_robots_status, ignore_index=True, sort=False) df.to_csv(self.robot_status_csv, index=False) if self.alpha_variation: mean = round(np.mean(self.costs_each_path), 3) std = round(np.std(self.costs_each_path), 3) df = pd.read_csv(self.alpha_csv) df = df.append( { "nrobots": self.nrobots, "radar_radius": self.radar_radius, "alpha": self.alpha, "gamma": self.gamma, "mean": mean, "std": std }, ignore_index=True) df.to_csv(self.alpha_csv, index=False) tmp_df = pd.DataFrame(columns=["step", "cost"]) for s, costs in zip(self.alpha_step.keys(), self.alpha_step.values()): if not costs: tmp_df = tmp_df.append({ "step": s, "cost": -1 }, ignore_index=True, sort=False) continue for c in costs: tmp_df = tmp_df.append({ "step": s, "cost": c }, ignore_index=True, sort=False) df = pd.read_csv(self.alpha_step_csv) if len(df["sim_id"]) == 0: tmp_df["sim_id"] = 0 else: tmp_df["sim_id"] = df["sim_id"][df.index[-1]] + 1 tmp_df["nrobots"] = self.nrobots tmp_df["radar_radius"] = self.radar_radius tmp_df["alpha"] = self.alpha tmp_df["gamma"] = self.gamma df = df.append(tmp_df, ignore_index=True, sort=False) df.to_csv(self.alpha_step_csv, index=False) if self.gamma_variation: df = pd.read_csv(self.gamma_csv) if len(df["sim_id"]) == 0: self.gamma_df["sim_id"] = 0 else: self.gamma_df["sim_id"] = df["sim_id"][df.index[-1]] + 1 self.gamma_df["nrobots"] = self.nrobots self.gamma_df["radar_radius"] = self.radar_radius self.gamma_df["alpha"] = self.alpha self.gamma_df["gamma"] = self.gamma df = df.append(self.gamma_df, ignore_index=True, sort=False) df.to_csv(self.gamma_csv, index=False) self.running = False def run_model(self): while (True): # search for unexplored cells stop = True for node in self.seen_graph.nodes(): cell = [ obj for obj in self.grid.get_cell_list_contents(node) if isinstance(obj, Cell) ][0] if cell.explored == 0 or cell.explored == 1: stop = False # if all seen cells have benn explored, stop the simulation # we do this so if there are unreachable cells, the cannot be seen, so the simulation stops anyway if stop: self.running = False break else: self.step() # Data collection utilities @staticmethod def get_step(m): return m.schedule.steps # these two should go faster since cells are not in the scheduler anymore @staticmethod def get_number_robots_status(m, status): status_value = { "idling": 0, "travelling": 1, "exploring": 2, "deploying_bean": 3 } return len([ x for x in m.schedule.agents if isinstance(x, Robot) and x.status == status_value[status] ]) @staticmethod def get_number_bean_deployed(m): return sum([ x.number_bean_deployed for x in m.schedule.agents if isinstance(x, Robot) ]) + m.deployed_beans_at_start # function for gamma variation @staticmethod def compute_robot_distances(m): nrobots = m.nrobots T_up = np.full( (nrobots, nrobots), 0.0) # if it's only zero, numpy represents only integers # didn't dig deep in numpy doc but it looks like it handles triangualr matrices as "normal" matrices, so # i just initilize a full matrix and then i'll use it as a triangular. robots = [x for x in m.schedule.agents if isinstance(x, Robot)] # the order of the robots in robots can change from step to step (due to the random scheduler), # This shouldn't create any type of problem, but to avoid a lot of problems with indexes later on # we sort them basing on the unique_id robots.sort(key=lambda x: x.unique_id) # I need the lowest id to shift back the ids to fit the matrices coordinations lowest_id = robots[0].unique_id for r in robots: matrix_id_row = r.unique_id - lowest_id # the distance of a robot to itself is zero by definition y1, x1 = r.pos for i in range(matrix_id_row + 1, nrobots): r2 = robots[i] # i can do this because they are sorted y2, x2 = r2.pos T_up[matrix_id_row][i] = distance.euclidean([x1, y1], [x2, y2]) mean_dist_robots = list() # the robot 0 has only the rows mean_robot_zero = sum(T_up[0, 1:nrobots]) mean_dist_robots.append(mean_robot_zero) for i in range(1, nrobots - 1): # the last row has no values, i iters the rows mean_robot = (sum(T_up[0:i, i]) + sum(T_up[i, i + 1:nrobots])) / (nrobots - 1) mean_dist_robots.append(mean_robot) # last robot has only the columns mean_last_robot = sum(T_up[0:nrobots - 1, nrobots - 1]) mean_dist_robots.append(mean_last_robot) return tuple([ round(np.mean(mean_dist_robots), 3), round(np.std(mean_dist_robots), 3) ])
class World(Model): """ Model for the electricity landscape world """ def __init__( self, initialization_year, scenario_file=None, fitting_params=None, long_term_fitting_params=None, future_price_uncertainty_m=None, future_price_uncertainty_c=None, carbon_price_scenario=None, demand_change=None, demand_distribution=None, number_of_steps=8, total_demand=None, number_of_agents=None, market_time_splices=8, data_folder="ElecSim_Output", time_run=False, nuclear_subsidy=None, highest_demand=None, log_level="warning", client_rl=None, distribution_name=None, dropbox=None, gencos_rl=[], write_data_to_file=True, rl_port_number=9920, ): """ Initialize an electricity market in a particular country. Provides the ability to change scenarios from this constructor. :param int initialization_year: Year to begin simulation. :param list: float carbon_price_scenario: Scenario containing carbon price for each year of simulation. :param list: float demand_change: Scenario containing change in demand between each year of simulation. :param int number_of_steps: Total number of years to run scenario. :param int total_demand: Total size of country's demand. :param str data_folder: Directory and folder to save run data to :param bool time_run: :param str log_level: """ self.start = perf_counter() logger.info("start: {}".format(self.start)) # Set up model objects self.year_number = initialization_year self.years_from_start = 0 self.step_number = 0 self.unique_id_generator = 0 self.time_run = time_run self.max_number_of_steps = number_of_steps self.average_electricity_price = 0 self.market_time_splices = market_time_splices self.nuclear_subsidy = nuclear_subsidy self.dropbox = dropbox self.gencos_rl = gencos_rl self.write_data_to_file = write_data_to_file self.set_log_level(log_level) self.scenario_file = scenario_file self.overwrite_scenario_file(scenario_file) self.override_highest_demand(highest_demand) self.override_carbon_scenario(carbon_price_scenario) self.override_demand_change(demand_change) self.demand_distribution_uncertainty = demand_distribution self.distribution_name = distribution_name self.override_total_demand(total_demand, number_of_agents) self.schedule = OrderedActivation(self) # Import company data including financials and plant data plant_data = elecsim.scenario.scenario_data.power_plants financial_data = elecsim.scenario.scenario_data.company_financials if self.gencos_rl: self.bidding_client = PolicyClient( "http://127.0.0.1:{}".format(rl_port_number) ) # Initialize generation companies using financial and plant data self.initialize_gencos(financial_data, plant_data, gencos_rl) self.last_added_plant = None self.last_added_plant_bids = None # Create PowerExchange if self.market_time_splices == 1: self.PowerExchange = PowerExchange(self, demand_distribution) self.demand = Demand( self, self.unique_id_generator, elecsim.scenario.scenario_data.segment_time, elecsim.scenario.scenario_data.segment_demand_diff, ) elif self.market_time_splices > 1: self.PowerExchange = PowerExchange(self, demand_distribution) # self.PowerExchange = HighTemporalExchange(self) self.demand = MultiDayDemand( self, self.unique_id_generator, elecsim.scenario.scenario_data.multi_year_data, ) else: raise ValueError("market_time_splices must be equal to or larger than 1.") self.running = True self.beginning_of_year = False self.continue_investing = 0 self.over_invested = False self.unique_id_generator += 1 self.schedule.add(self.demand) self.create_data_loggers(data_folder) self.client = client_rl if ( elecsim.scenario.scenario_data.investment_mechanism == "RL" and self.client is not None and self.gencos_rl ): # self.client = PolicyClient("http://rllibserver:9900") self.eid = self.client.start_episode(training_enabled=True) self.intial_obs = LatestMarketData(self).get_RL_investment_observations() # logger.info("self.intial_obs: {}".format(self.intial_obs)) elif elecsim.scenario.scenario_data.investment_mechanism == "future_price_fit": self.future_price_uncertainty_m = future_price_uncertainty_m self.future_price_uncertainty_c = future_price_uncertainty_c if fitting_params is not None: self.fitting_params = fitting_params elif long_term_fitting_params is not None: self.long_term_fitting_params = long_term_fitting_params self.fitting_params = None else: raise ValueError( "If using future_price_fit you must enter a value for long_term_fitting_params or fitting_params in the constructor of World" ) def step(self, carbon_price=None): """Advance model by one step""" self.beginning_of_year = False if self.step_number % self.market_time_splices == 0: self.start = time.perf_counter() self.operate_constructed_plants() if self.step_number != 0: self.year_number += 1 self.years_from_start += 1 # self.operate_constructed_plants() self.beginning_of_year = True # logger.info("year: {}".format(self.year_number)) print("{}:".format(self.year_number), end="", flush=True) else: print("{}:".format(self.year_number), end="", flush=True) obs = self.schedule.step() self.operate_constructed_plants() if self.over_invested: return self.datacollector.get_model_vars_dataframe(), self.over_invested self.continue_investing = 0 if carbon_price is not None: elecsim.scenario.scenario_data.carbon_price_scenario[ self.year_number + 1 ] = carbon_price else: elecsim.scenario.scenario_data.carbon_price_scenario = ( elecsim.scenario.scenario_data.carbon_price_scenario ) if self.beginning_of_year: self.dismantle_old_plants() self.dismantle_unprofitable_plants() self.average_electricity_price = self.PowerExchange.tender_bids( self.demand.segment_hours, self.demand.segment_consumption ).accepted_price.mean() self.PowerExchange.price_duration_curve = [] carbon_emitted = self.get_carbon_emitted(self) self.settle_gencos_financials() self.datacollector.collect(self) self.delete_old_bids() self.step_number += 1 print(".", end="", flush=True) if self.write_data_to_file: self.write_scenario_data() if isinstance(self.average_electricity_price, np.ndarray): self.average_electricity_price = self.average_electricity_price[0] if self.step_number % self.market_time_splices == 0: end = time.perf_counter() print("time taken: {}".format(end - self.start)) # get_capacity_factor.cache_clear() if ( self.step_number == self.max_number_of_steps and elecsim.scenario.scenario_data.investment_mechanism == "RL" and self.gencos_rl ): obs = LatestMarketData(self).get_RL_investment_observations() self.client.end_episode(self.eid, observation=obs) del self.client logger.debug(self.datacollector.get_model_vars_dataframe()) return abs(self.average_electricity_price), abs(carbon_emitted) # return self.datacollector.get_model_vars_dataframe(), self.over_invested def initialize_gencos(self, financial_data, plant_data, gencos_rl): """ Creates generation company agents based on financial data and power plants owned. Estimates cost parameters of each power plant if data not for power plant not available. :param financial_data: Data containing information about generation company's financial status :param plant_data: Data containing information about generation company's plants owned, start year and name. """ financial_data = pd.merge(financial_data, plant_data, on="Company", how="inner") financial_data = financial_data[["Company", "cash_in_bank"]] # Initialising generator company data financial_data.cash_in_bank = financial_data.cash_in_bank.replace("nan", np.nan) financial_data.cash_in_bank = financial_data.cash_in_bank.fillna(0) companies_groups = plant_data.groupby("Company") company_financials = financial_data.groupby("Company") logger.info("Initialising generation companies with their power plants.") # Initialize generation companies with their respective power plants for gen_id, ((name, data), (_, financials)) in enumerate( zip(companies_groups, company_financials), 0 ): rl_bidding = False if financials.Company.iloc[0] != name: raise ValueError( "Company financials name ({}) and agent name ({}) do not match.".format( financials.Company.iloc[0], name ) ) elif financials.Company.iloc[0] in gencos_rl: rl_bidding = True gen_co = GenCo( unique_id=gen_id, model=self, difference_in_discount_rate=round(uniform(-0.03, 0.03), 3), look_back_period=randint(3, 7), name=name, money=financials.cash_in_bank.iloc[0], rl_bidding=rl_bidding, ) self.unique_id_generator += 1 # Add power plants to generation company portfolio # parent_directory = os.path.dirname(os.getcwd()) pickle_directory = ( "{}/../elecsim/data/processed/pickled_data/power_plants/".format( ROOT_DIR ) ) for plant in data.itertuples(): try: power_plant = pickle.load( open( "{}{}-{}.pickle".format( pickle_directory, plant.Name, plant.Start_date ), "rb", ) ) except (OSError, IOError, FileNotFoundError, EOFError) as e: logger.info("plant: {}".format(plant)) power_plant = create_power_plant( plant.Name, plant.Start_date, plant.Simplified_Type, plant.Capacity, ) pickle.dump( power_plant, open( "{}{}-{}.pickle".format( pickle_directory, plant.Name, plant.Start_date ), "wb", ), ) gen_co.plants.append(power_plant) logger.debug("Adding generation company: {}".format(gen_co.name)) self.schedule.add(gen_co) logger.info("Added generation companies.") def get_running_plants(self, plants): for plant in plants: # if plant.name in elecsim.scenario.scenario_data.known_plant_retirements: if ( plant.construction_year <= 1990 and plant.name != "invested_plant" and plant.name not in elecsim.scenario.scenario_data.known_plant_retirements ): # Reset old plants that have been modernised with new construction year plant.construction_year = randint( self.year_number - 15, self.year_number ) # yield plant elif plant.name in elecsim.scenario.scenario_data.known_plant_retirements: plant.construction_year = ( elecsim.scenario.scenario_data.known_plant_retirements[plant.name] - ( plant.operating_period + plant.construction_period + plant.pre_dev_period ) - 1 ) logger.info( "plant.name: {}, plant.construction_year: {}".format( plant.name, plant.construction_year ) ) if ( plant.construction_year + plant.operating_period + plant.construction_period + plant.pre_dev_period >= self.year_number ): yield plant else: logger.debug( "Taking the plant '{}' out of service, year of construction: {}".format( plant.name, plant.construction_year ) ) def dismantle_old_plants(self): """ Remove plants that are past their lifetime agent from each agent from their plant list """ gencos = self.get_gencos() for genco in gencos: plants_filtered = list(self.get_running_plants(genco.plants)) genco.plants = plants_filtered def dismantle_unprofitable_plants(self): gencos = self.get_gencos() for genco in gencos: profitable_plants = list(self.filter_plants_with_no_income(genco.plants)) genco.plants = profitable_plants def filter_plants_with_no_income(self, plants): for plant in plants: if (self.year_number > 7) and ( plant.get_year_of_operation() + 7 < self.year_number ): historic_bids = plant.historical_bids # logger.info("historic_bids {}".format(historic_bids)) # years_to_look_into = list(range(self.year_number,self.year_number-7,-1)) seven_years_previous = self.year_number - 7 if historic_bids: if historic_bids[-1].year_of_bid > seven_years_previous: yield plant else: logger.debug( "Plant {}, type {} is unprofitable. Last accepted bid: {}".format( plant.name, plant.plant_type, historic_bids[-1].year_of_bid, ) ) for bid in plant.accepted_bids: del bid else: logger.debug( "Plant {}, type {} is unprofitable.".format( plant.name, plant.plant_type ) ) for bid in plant.accepted_bids: del bid # bids_to_check = list(filter(lambda x: x.year_of_bid in years_to_look_into, historic_bids)) # total_income_in_previous_years = sum(bid.price_per_mwh for bid in bids_to_check) # for bids in reversed(historic_bids): # logger.info("bids.year_of_bid: {}".format(bids.year_of_bid)) # if total_income_in_previous_years > 0: # yield plant # else: # logger.debug("Taking plant: {} out of service.".format(plant.name)) else: yield plant def get_profitable_plants(self, plants): for plant in plants: if ( self.step_number > 7 and plant.get_year_of_operation() + 7 > self.year_number ): historic_bids = plant.historical_bids pass def operate_constructed_plants(self, minimum_operation_year=2018): gencos = self.get_gencos() logger.debug("gencos: {}".format(gencos)) for genco in gencos: logger.debug("genco plants: {}".format(genco.plants)) for plant in genco.plants: # logger.debug("plant: {}, year_number: {}, construction year+constructioon_period+predev: {}".format(plant, self.year_number, plant.construction_year + plant.construction_period + plant.pre_dev_period)) if plant.construction_year <= minimum_operation_year: plant.is_operating = True elif (plant.is_operating is False) and ( self.year_number >= plant.construction_year + plant.construction_period + plant.pre_dev_period ): plant.is_operating = True def overwrite_scenario_file(self, scenario_file): if scenario_file: split_directory = scenario_file.split("/") file_name = split_directory[-1] spec = importlib.util.spec_from_file_location(file_name, scenario_file) scenario_import = importlib.util.module_from_spec(spec) spec.loader.exec_module(scenario_import) scen_mod.overwrite_scenario_file(scenario_import) def settle_gencos_financials(self): gencos = self.get_gencos() for genco in gencos: genco.settle_accounts() # genco.delete_old_bids() def delete_old_bids(self): gencos = self.get_gencos() for genco in gencos: genco.delete_old_bids() def get_gencos(self): gencos = [genco for genco in self.schedule.agents if isinstance(genco, GenCo)] return gencos def clear_all_bids(self): gencos = self.get_gencos() for genco in gencos: genco.delete_old_bids() @staticmethod def get_capacity_of_plants(model, plant_type): gencos = model.get_gencos() plants = [ plant for genco in gencos for plant in genco.plants if plant.plant_type == plant_type and plant.is_operating ] total_capacity = sum(plant.capacity_mw for plant in plants) return total_capacity @staticmethod def get_all_plants(model): gencos = model.get_gencos() plants = [plant for genco in gencos for plant in genco.plants] return plants @staticmethod def get_current_carbon_tax(model): carbon_tax = elecsim.scenario.scenario_data.carbon_price_scenario[ model.years_from_start ] return carbon_tax @staticmethod def get_genco_wealth(model): gencos = model.get_gencos() total_wealth = 0 for genco in gencos: total_wealth += genco.money return total_wealth @staticmethod def get_electricity_cost(model): return model.average_electricity_price @staticmethod def get_carbon_emitted(model): gencos = model.get_gencos() bids = World.get_accepted_bids(gencos, FuelPlant) carbon_emitted = sum( bid.capacity_bid * bid.plant.fuel.co2_density for bid in bids if isinstance(bid.plant, FuelPlant) ) return carbon_emitted @staticmethod def get_accepted_bid_capacity(model, plant_type): gencos = model.get_gencos() plants = [ plant for genco in gencos for plant in genco.plants if plant.plant_type == plant_type and plant.is_operating ] capacity_contributed = sum( bid.capacity_bid for plant in plants for bid in plant.accepted_bids ) return capacity_contributed @staticmethod def get_accepted_bid_capacity_per_segment_hour(model): gencos = model.get_gencos() plants = [ plant for genco in gencos for plant in genco.plants if plant.is_operating ] # capacity_contributed = [ if bid.segment_hours==] bids_dataframe = [ bid.to_dict() for plant in plants for bid in plant.accepted_bids ] return bids_dataframe @staticmethod def get_accepted_bids(gencos, plant_type=None): if plant_type: bids = list( accepted_bids for genco in gencos for plants in genco.plants for accepted_bids in plants.accepted_bids if isinstance(plants, plant_type) ) else: bids = list( accepted_bids for genco in gencos for plants in genco.plants for accepted_bids in plants.accepted_bids ) return bids def stratify_data(self, demand): power_plants = elecsim.scenario.scenario_data.power_plants frac_to_scale = demand / power_plants.Capacity.sum() stratified_sample = power_plants.groupby(["Fuel"], as_index=False).apply( lambda x: x.sample(frac=frac_to_scale, replace=True) ) return stratified_sample def set_log_level(self, log_level): if log_level.lower() == "warning": logging.basicConfig(level=logging.WARNING) elif log_level.lower() == "info": logging.basicConfig(level=logging.INFO) elif log_level.lower() == "debug": logging.basicConfig(level=logging.DEBUG) else: raise ValueError( "log_level must be warning, info or debug and not {}".format(log_level) ) def create_data_loggers(self, data_folder): self.data_folder = data_folder self.datacollector = DataCollector( model_reporters={ "contributed_CCGT": lambda m: self.get_accepted_bid_capacity(m, "CCGT"), "contributed_Coal": lambda m: self.get_accepted_bid_capacity(m, "Coal"), "contributed_Onshore": lambda m: self.get_accepted_bid_capacity( m, "Onshore" ), "contributed_Offshore": lambda m: self.get_accepted_bid_capacity( m, "Offshore" ), "contributed_PV": lambda m: self.get_accepted_bid_capacity(m, "PV"), "contributed_Nuclear": lambda m: self.get_accepted_bid_capacity( m, "Nuclear" ), "contributed_Recip_gas": lambda m: self.get_accepted_bid_capacity( m, "Recip_gas" ), "contributed_Biomass": lambda m: self.get_accepted_bid_capacity( m, "Biomass" ), # "hourly_accepted_bids": lambda m: self.get_accepted_bid_capacity_per_segment_hour(m), "total_CCGT": lambda m: self.get_capacity_of_plants(m, "CCGT"), "total_Coal": lambda m: self.get_capacity_of_plants(m, "Coal"), "total_Onshore": lambda m: self.get_capacity_of_plants(m, "Onshore"), "total_Offshore": lambda m: self.get_capacity_of_plants(m, "Offshore"), "total_PV": lambda m: self.get_capacity_of_plants(m, "PV"), "total_Nuclear": lambda m: self.get_capacity_of_plants(m, "Nuclear"), "total_Recip_gas": lambda m: self.get_capacity_of_plants( m, "Recip_gas" ), "Carbon_tax": lambda m: self.get_current_carbon_tax(m), "total_genco_wealth": lambda m: self.get_genco_wealth(m), "Electricity_cost": lambda m: self.get_electricity_cost(m), "Carbon_emitted": lambda m: self.get_carbon_emitted(m), } ) def override_total_demand(self, total_demand, number_of_agents=None): self.total_demand = total_demand if total_demand is not None: elecsim.scenario.scenario_data.power_plants = self.stratify_data( total_demand ) demand_modifier = ( elecsim.scenario.scenario_data.power_plants.Capacity.sum() / elecsim.scenario.scenario_data.segment_demand_diff[-1] ) / 1.6 logger.info("demand_modifier: {}".format(demand_modifier)) logger.info( "total available capacity: {}".format( elecsim.scenario.scenario_data.power_plants.Capacity.sum() ) ) elecsim.scenario.scenario_data.segment_demand_diff = [ demand_modifier * demand for demand in elecsim.scenario.scenario_data.segment_demand_diff ] if number_of_agents is not None: total_plants = len(elecsim.scenario.scenario_data.power_plants) fraction_to_replace = total_plants / number_of_agents company_names = sample( list(elecsim.scenario.scenario_data.power_plants.Company.unique()), number_of_agents, ) company_name_repeated = np.repeat( company_names, int(fraction_to_replace) ) company_name_repeated = np.append( company_name_repeated, np.array( ["company_{}".format(number_of_agents - 1) for i in range(100)] ), ) elecsim.scenario.scenario_data.power_plants.Company = ( company_name_repeated[:total_plants] ) def override_highest_demand(self, highest_demand): if highest_demand: elecsim.scenario.scenario_data.initial_max_demand_size = highest_demand def override_demand_change(self, demand_change): if demand_change: elecsim.scenario.scenario_data.yearly_demand_change = demand_change[1:] self.demand_change_name = str(demand_change[0]).replace(".", "") else: self.demand_change_name = "none" def override_carbon_scenario(self, carbon_price_scenario): if carbon_price_scenario: elecsim.scenario.scenario_data.carbon_price_scenario = ( carbon_price_scenario[1:] ) self.carbon_scenario_name = str(carbon_price_scenario[0]).replace(".", "") else: self.carbon_scenario_name = "none" def write_scenario_data(self): if self.step_number == self.max_number_of_steps: parent_directory = os.path.dirname(os.getcwd()) directory = "{}/{}/".format(parent_directory, self.data_folder) if not os.path.exists(directory): os.makedirs(directory) filename = "demand_{}-carbon_{}-datetime_{}-capacity_{}-demand_distribution_{}.csv".format( self.demand_change_name, self.carbon_scenario_name, dt.datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), 1, self.distribution_name, # self.scenario_file.split("/")[-1].split(".")[0], ) directory_filename = "{}/{}.csv".format(directory, filename) results_df = self.datacollector.get_model_vars_dataframe() results_df.to_csv(directory_filename) class TransferData: def __init__(self, access_token): self.access_token = access_token def upload_file(self, file_from, file_to): """upload a file to Dropbox using API v2""" dbx = dropbox.Dropbox(self.access_token) with open(file_from, "rb") as f: dbx.files_upload(f.read(), file_to) if self.dropbox: transferData = TransferData(access_token) file_from = "/{}".format(directory_filename) file_to = "/{}".format(filename) # API v2 transferData.upload_file(file_from, file_to) if self.step_number == self.max_number_of_steps: end = perf_counter() time_elapased = end - self.start self.write_timing_results(end, time_elapased) def write_timing_results(self, end, time_elapased): if self.time_run: timings_data = pd.DataFrame( { "time": [time_elapased], "carbon": [elecsim.scenario.scenario_data.carbon_price_scenario[0]], "installed_capacity": [ elecsim.scenario.scenario_data.power_plants.Capacity.sum() ], "datetime": [dt.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")], } ) parent_directory = os.path.dirname(os.getcwd()) with open("{}/{}/timing.csv".format(ROOT_DIR, self.data_folder), "a") as f: timings_data.to_csv(f, header=False) logger.info("end: {}".format(end)) logger.info( "time_elapsed: {}, carbon: {}, size: {}".format( time_elapased, elecsim.scenario.scenario_data.carbon_price_scenario[0], elecsim.scenario.scenario_data.power_plants.Capacity.sum(), ) )
class VirusModel(Model): """A virus model with some number of agents""" def __init__(self): # self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) # self.G = nx.erdos_renyi_graph(n=3, p=0.5) self.G = nx.Graph() self.G.add_node(0) self.G.add_node(1) self.G.add_node(2) self.G.add_node(3) self.G.add_node(4) self.G.add_node(4) self.G.add_edge(0, 1) self.G.add_edge(0, 2) self.G.add_edge(0, 3) self.G.add_edge(0, 4) self.G.add_edge(0, 5) self.G.add_edge(1, 4) self.G.add_edge(4, 5) self.grid = NetworkGrid(self.G) self.rooms = {} self.rooms[0] = {"name": "Wejście", "rates": {}} self.rooms[1] = {"name": "Czytelnia", "rates": {"Nauka": 2}} self.rooms[2] = {"name": "Chillout", "rates": {"Relaks": 10}} self.rooms[3] = {"name": "Biuro", "rates": {"Praca": 1.5}} self.rooms[4] = {"name": "Toaleta", "rates": {"Toaleta": 30}} self.rooms[5] = { "name": "Kawiarnia", "rates": { "Jedzenie": 12, "Kultura": 0.5 } } collector_dict = {} for i, room in enumerate(self.rooms): collector_dict[self.rooms[i]["name"]] = lambda model, i=i: len( model.grid.get_cell_list_contents([i])) - 1 self.datacollector = DataCollector(collector_dict) self.schedule = RandomActivation(self) # Create agents for i, node in enumerate(self.G.nodes()): r = RoomAgent(i, self, self.rooms[i]["name"], self.rooms[i]["rates"]) self.schedule.add(r) # Add the agent to the node self.grid.place_agent(r, node) self.prob_needs = { "Jedzenie": [4, 0.6], "Toaleta": [2, 0.6], "Relaks": [5, 1] } self.prob_studs = { "Nauka": [2, 1.5], "Praca": [0, 0.5], "Kultura": [0, 1.0] } self.prob_works = { "Nauka": [0, 0.3], "Praca": [6, 1.0], "Kultura": [0, 0.2] } self.prob_tours = { "Nauka": [0, 0.3], "Praca": [0, 0.5], "Kultura": [1, 1.0] } self.prob_local = { "Nauka": [1, 0.7], "Praca": [2, 0.9], "Kultura": [1, 1.0] } # godziny 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 self.rate_studs = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 ] self.rate_works = [ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0 ] self.rate_tours = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 4, 3, 2, 0, 0 ] self.rate_local = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 4, 4, 2, 2, 4, 5, 6, 6, 6, 3, 0, 0, 0 ] self.running = True self.datacollector.collect(self) self.tm = 0 * 60 self.count = 0 def get_sample(self, probs): ret = {} for k, [m, s] in probs.items(): tm = int(np.clip(np.random.normal(m, s) * 60, 15, 600)) ret[k] = tm return ret def step(self): # prepare list for the satisfied agents self.satisfied = [] # add new agents hour = int(self.tm / 60) if (hour > 23): hour = 0 for i in range(self.rate_studs[hour]): a = HumanAgent(100 + self.count, self, self.get_sample(self.prob_needs), self.get_sample(self.prob_studs)) self.schedule.add(a) self.grid.place_agent(a, 0) self.count += 1 for i in range(self.rate_works[hour]): a = HumanAgent(100 + self.count, self, self.get_sample(self.prob_needs), self.get_sample(self.prob_works)) self.schedule.add(a) self.grid.place_agent(a, 0) self.count += 1 # update system self.schedule.step() # collect data self.datacollector.collect(self) # make time step self.tm = self.tm + 1 if (self.tm > 24 * 60): self.datacollector.get_model_vars_dataframe().to_csv("one_day.csv") self.tm = 0 # remove satisfied agents from the system for a in self.satisfied: print(a.unique_id, a.goals, "is satisfied") self.grid.move_agent(a, 0) self.grid._remove_agent(a, 0) self.schedule.remove(a) def run_model(self, n): for i in range(n): self.step() def find_best_room(self, goal): #print("Looking for room for", goal) for i, room in enumerate(self.rooms): #print("Room", room, self.rooms[room]["rates"]) if goal in self.rooms[room]["rates"]: return room return -1
class Model(Model): def __init__(self, N, B, T, Treg, width, height): self.num_myelin = N * 8 self.num_agents = N + B + T + Treg + self.num_myelin self.num_neurons = N self.num_myelin = 4 self.num_limfocytB = B self.num_active_B = 0 self.num_infected_B = 0 self.num_limfocytT = T self.num_activeT = 0 self.num_limfocytTreg = Treg self.grid = MultiGrid(width, height, True) self.schedule = RandomActivation(self) self.available_ids = set() self.dead_agents = set() self.new_agents = set() self.max_id = 0 self.step_count = 1 self.cytokina = 0 self.cytokina_prev = 0 self.sum = 0 self.B = 0 self.a = 0.80 self.Ymax = 100 open('new_and_dead.txt', 'w').close() # Create agents neuron_positions = [[3, 3], [3, 10], [3, 20], [3, 27], [10, 3], [10, 10], [10, 20], [10, 27], [19, 3], [19, 10], [19, 20], [19, 27], [26, 3], [26, 10], [26, 20], [26, 27], [14, 15]] for i in range(self.num_neurons): a = Neuron(i, self, "Neuron") self.schedule.add(a) #Add agent to a random grid cell #x=self.random.randrange(self.grid.width) #y=self.random.randrange(self.grid.height) #self.grid.place_agent(a,(x,y)) pos = neuron_positions[i] self.grid.place_agent(a, (pos[0], pos[1])) cells = self.grid.get_neighborhood(a.pos, True, False, 1) id = self.num_agents - i * 8 for cell in cells: m = Myelin(id, self, "Myelin") self.schedule.add(m) self.grid.place_agent(m, cell) id -= 1 #dodawanie różnych typów agentów zgodnie z ich liczbą podaną przy inicjacji modelu for i in range(self.num_limfocytB): a = LimfocytB(i + self.num_neurons, self, "LimfocytB") self.schedule.add(a) #Add agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) for i in range(self.num_limfocytT): a = LimfocytT(i + N + B, self, "LimfocytT") self.schedule.add(a) #Add agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) for i in range(self.num_limfocytTreg): a = LimfocytTreg(i + N + B + T, self, "LimfocytTreg") self.schedule.add(a) #Add agent to a random grid cell x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) self.max_id = self.num_agents - 1 self.datacollector_population = DataCollector( model_reporters={"Populacja": compute_population}) self.datacollector_T_population = DataCollector( model_reporters={"Populacja Limfocytów T": T_population}) #self.datacollector_T_precentage=DataCollector( #model_reporters={"Precentage Limfocyt T": T_popualtion_precentage}) self.datacollector_B_population = DataCollector( model_reporters={"Populacja Limfocytów B": B_population}) self.datacollector_Treg_population = DataCollector( model_reporters={"Populacja Limfocytów Treg": Treg_population}) self.datacollector_B_active_population = DataCollector( model_reporters={ "Populacja Aktywnych Limfocytów B": B_activated_population }) self.datacollector_T_active_population = DataCollector( model_reporters={ "Populacja Aktywnych Limfocytów T": T_activated_population }) self.datacollector_B_infected_population = DataCollector( model_reporters={ "Populacja Zainfekowanych Limfocytów B": B_infected_population }) self.datacollector_myelin_population = DataCollector( model_reporters={ "Populacja osłonek mielinowych": myelin_population }) self.datacollector_myelin_healths = DataCollector( model_reporters={ "Suma punktów życia osłonek mielinowych": myelin_healths }) def step(self): #print("self running: "+str(self.running())) self.schedule.step() self.datacollector_population.collect(self) self.datacollector_T_population.collect(self) #self.datacollector_T_precentage.collect(self) self.datacollector_B_population.collect(self) self.datacollector_Treg_population.collect(self) self.datacollector_B_active_population.collect(self) self.datacollector_T_active_population.collect(self) self.datacollector_B_infected_population.collect(self) self.datacollector_myelin_population.collect(self) self.datacollector_myelin_healths.collect(self) self.adding_removing() self.datacollector_myelin_healths.get_model_vars_dataframe().to_csv( r'Data/myelin_healths25.txt', sep=' ', mode='w') self.datacollector_T_population.get_model_vars_dataframe().to_csv( r'Data/T_population25.txt', sep=' ', mode='w') self.datacollector_B_population.get_model_vars_dataframe().to_csv( r'Data/B_population25.txt', sep=' ', mode='w') self.datacollector_Treg_population.get_model_vars_dataframe().to_csv( r'Data/Treg_population25.txt', sep=' ', mode='w') self.datacollector_B_active_population.get_model_vars_dataframe( ).to_csv(r'Data/B_active_population25.txt', sep=' ', mode='w') self.datacollector_T_active_population.get_model_vars_dataframe( ).to_csv(r'Data/T_active_population25.txt', sep=' ', mode='w') self.datacollector_B_infected_population.get_model_vars_dataframe( ).to_csv(r'Data/B_infected_population25.txt', sep=' ', mode='w') print("Liczba agentów: " + str(self.num_agents)) print("MaxID: " + str(self.max_id)) self.cytokina = max( min((self.B + self.cytokina_prev), self.Ymax) * self.a, 0) print("Cytokina " + str(self.cytokina)) print("Cytokina_prev " + str(self.cytokina_prev)) f = open("agents.txt", 'a') f.write("======Step : " + str(self.step_count) + "\n") for agent in self.schedule.agents: f.write("Agent: " + str(agent.type) + " " + str(agent.unique_id) + str(agent.pos) + "\n") f.close() self.cytokina_prev = self.cytokina self.B = 0 def running(self): self.step() def adding_removing( self): #funckja odpowiedzialna za dodawanie i usuwanie agentów #print("AddingRemoving") f = open("new_and_dead.txt", 'a') f.write("Step " + str(self.step_count) + "\n") f.write("======Dead agents======: " + "\n") for d in self.dead_agents: try: self.schedule.remove(d) self.num_agents -= 1 self.available_ids.add(d.unique_id) except KeyError: continue try: self.grid._remove_agent(d.pos, d) except KeyError: continue f.write(str(d.unique_id) + " " + d.type + "\n") #if d.type=="AktywowanyLimfocytT": # self.cytokina-=1 self.dead_agents.clear() f.write("======New Agents=====: " + "\n") for n in self.new_agents: self.schedule.add(n) self.num_agents += 1 self.grid.place_agent(n, n.pos) if n.unique_id in self.available_ids: self.available_ids.remove(n.unique_id) f.write(str(n.unique_id) + " " + n.type + "\n") #if n.type=="AktywowanyLimfocytT": # self.cytokina+=1 self.new_agents.clear() m = 1 n = 0 for agent in self.schedule.agents: if agent.unique_id > m: m = agent.unique_id if (agent.type == "LimfocytT") or (agent.type == "AktywowanyLimfocytT"): n += 1 self.max_id = m self.num_limfocytT = 0 self.deficiencies() f.close() def deficiencies(self): n = B_population(self) if n == 0: if len(self.available_ids) == 0: self.max_id += 1 id = self.max_id else: id = min(self.available_ids) self.available_ids.remove(id) agent = LimfocytB(id, self, "LimfocytB") self.schedule.add(agent) x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) self.grid.place_agent(agent, (x, y)) self.num_agents += 1 n = T_population(self) if n == 0: for i in range(10): if len(self.available_ids) == 0: self.max_id += 1 id = self.max_id else: id = min(self.available_ids) self.available_ids.remove(id) agent = LimfocytB(id, self, "LimfocytT") self.schedule.add(agent) x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) self.grid.place_agent(agent, (x, y)) self.num_agents += 1 n = Treg_population(self) if n == 0: if len(self.available_ids) == 0: self.max_id += 1 id = self.max_id else: id = min(self.available_ids) self.available_ids.remove(id) agent = LimfocytB(id, self, "LimfocytTreg") self.schedule.add(agent) x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) self.grid.place_agent(agent, (x, y)) self.num_agents += 1
class Themepark(Model): def __init__(self, N_attr, N_cust, width, height, strategy, theme, max_time, weight, adaptive): self.theme = theme self.max_time = max_time self.N_attr = N_attr self.penalty_per = PENALTY_PERCENTAGE self.weight = weight self.adaptive = adaptive self.strategies = STRATEGIES self.x_list, self.y_list, self.positions = xlist, ylist, positions self.x_list, self.y_list, self.positions = get_attraction_coordinates(WIDTH, HEIGHT, self.N_attr, theme) self.happinesses = [] self.starting_positions = [[int((WIDTH/2)-1), 0], [int(WIDTH/2), 0], [int((WIDTH/2)+1), 0]] self.path_coordinates = get_coordinates(WIDTH, HEIGHT, NUM_OBSTACLES, self.N_attr, theme) self.N_attr = N_attr # num of attraction agents self.N_cust = N_cust # num of customer agents self.width = width self.height = height self.total_steps = 0 self.cust_ids = N_cust self.strategy = strategy self.grid = MultiGrid(width, height, torus=False) self.schedule = BaseScheduler(self) self.schedule_Attraction = BaseScheduler(self) self.schedule_Customer = BaseScheduler(self) self.totalTOTAL = 0 self.attractions = self.make_attractions() self.attraction_history = self.make_attr_hist() self.running = True self.data = [] self.data_customers = [] self.park_score = [] self.data_dict = {} self.hist_random_strat = [] self.hist_close_strat = [] self.all_rides_list = [] self.strategy_composition = self.make_strategy_composition() self.memory = 5 self.customer_score = [] self.customers = self.add_customers(self.N_cust) self.only_random = False for attraction in self.get_attractions(): self.data_dict[attraction.unique_id] = ({ "id": attraction.unique_id, "length": attraction.attraction_duration, "waiting_list": []}) if len(self.strategies) == 6: self.datacollector = DataCollector( {"Random": lambda m: self.strategy_counter(self.strategies[0]), "0.00": lambda m: self.strategy_counter(self.strategies[1]), "0.25": lambda m: self.strategy_counter(self.strategies[2]), "0.50": lambda m: self.strategy_counter(self.strategies[3]), "0.75": lambda m: self.strategy_counter(self.strategies[4]), "1.00": lambda m: self.strategy_counter(self.strategies[5]), }) else: self.datacollector = DataCollector( {"0.00": lambda m: self.strategy_counter(self.strategies[0]), "0.25": lambda m: self.strategy_counter(self.strategies[1]), "0.50": lambda m: self.strategy_counter(self.strategies[2]), "0.75": lambda m: self.strategy_counter(self.strategies[3]), "1.00": lambda m: self.strategy_counter(self.strategies[4]), }) self.datacollector2 = DataCollector( {"score": lambda m: self.make_score()}) self.total_waited_time = 0 self.monitor = Monitor(self.max_time, self.N_attr, self.positions) def make_score(self): ideal = {} cust_in_row = 0 for i in range(len(self.get_attractions())): ideal[i] = self.N_cust/self.N_attr cust_in_row += self.get_attractions()[i].N_current_cust tot_difference = 0 for i in range(len(self.get_attractions())): difference = abs(cust_in_row/self.N_attr - self.get_attractions()[i].N_current_cust) tot_difference += difference fraction_not_right = (tot_difference/self.N_cust) return abs(1-(fraction_not_right)) * cust_in_row/self.N_cust def make_attr_hist(self): attraction_history = {} for attraction in self.get_attractions(): attraction_history[attraction] = [0] * (self.max_time + 1) return attraction_history def strategy_counter(self, strategy): counter_total = {} for attraction_pos in self.positions: agents = self.grid.get_neighbors( attraction_pos, moore=True, radius=0, include_center=True ) counter = 0 for agent in self.customers: if agent.weight == strategy: counter += 1 return counter def make_strategy_composition(self): if self.strategy == "Random_test_4": self.strategies = ["Random_test_4", 0.0, 0.25, 0.50, 0.75, 1.0] dict = {self.strategies[0]: 1/6, self.strategies[1]:0.20, self.strategies[2]:0.20, self.strategies[3]:0.20, self.strategies[4]:0.20, self.strategies[5]: 0.20} composition_list = [] for i in range(len(self.strategies)): if i == 0: dict[self.strategies[i]] = FRACTION_RANDOM continue else: composition_list.append(random.randint(0,100)) sum_comp = sum(composition_list) sum_comp = sum_comp - sum_comp * FRACTION_RANDOM for i in range(len(self.strategies)): if i == 0: continue else: dict[self.strategies[i]] = composition_list[i-1] /sum_comp else: dict = {self.strategies[0]: 0.20, self.strategies[1]:0.20, self.strategies[2]:0.20, self.strategies[3]:0.20, self.strategies[4]:0.20} composition_list = [] for i in range(len(self.strategies)): composition_list.append(random.randint(0,100)) sum_comp = sum(composition_list) sum_comp = sum_comp for i in range(len(self.strategies)): dict[self.strategies[i]] = composition_list[i-1] /sum_comp return dict def make_attractions(self): """ Initialize attractions on fixed position.""" attractions = {} for i in range(self.N_attr): pos = (self.x_list[i], self.y_list[i]) if self.grid.is_cell_empty(pos): name = str(i) a = Attraction(i, self, pos, name, self.N_cust, self.weight) attractions[i] = a self.schedule_Attraction.add(a) self.grid.place_agent(a, pos) return attractions def get_attractions(self): """ Get a list with all attractions """ agents = self.grid.get_neighbors( mid_point, moore=True, radius=RADIUS, include_center=True) attractions = [] for agent in agents: if type(agent) == Attraction: attractions.append(agent) return attractions def add_customers(self, N_cust, added=False): """ Initialize customers on random positions.""" weights_list = [] if self.adaptive is True: for j in self.strategy_composition.keys(): for i in range(round(N_cust*self.strategy_composition[j])): weights_list.append(j) if len(weights_list) < self.N_cust: rand = random.choice(self.strategies) weights_list.append(rand) elif len(weights_list) > self.N_cust: rand = random.choice(weights_list) weights_list.remove(rand) else: if self.strategy is not "Random": # do what normally is done for i in range(round(N_cust)): weights_list.append(self.weight) cust_list = [] # weight_counter = 0 # pick_weight = 0 for i in range(N_cust): # pos_temp = random.choice(self.starting_positions) pos_temp = [random.randint(0,WIDTH-1), random.randint(0,HEIGHT-1)] rand_x, rand_y = pos_temp[0], pos_temp[1] pos = (rand_x, rand_y) if added is True: i = self.cust_ids if self.strategy == "Random_test_4": if weights_list[i] == "Random_test_4": strategy = "Random_test_4" else: strategy = "Closest_by" else: strategy = self.strategy # Deze if is omdat bij alleen random self.weight none is! if weights_list == []: weight = None else: weight = weights_list[i] a = Customer(i, self, pos, self.x_list, self.y_list, self.positions, strategy, weight, self.adaptive) self.schedule_Customer.add(a) self.grid.place_agent(a, pos) cust_list.append(a) return cust_list def calculate_people(self): """Calculate how many customers are in which attraction.""" counter_total = {} for attraction_pos in self.positions: agents = self.grid.get_neighbors( attraction_pos, moore=True, radius=0, include_center=True ) counter = 0 for agent in agents: if type(agent) is Customer: counter += 1 else: attraction = agent attraction.N_current_cust = counter counter_total[attraction.unique_id] = counter return list(counter_total.values()) def calc_waiting_time(self): counter_total = {} attractions = self.get_attractions() for attraction in attractions: counter_total[attraction.unique_id] = attraction.current_waitingtime return counter_total def calculate_people_sorted(self): """ Calculate how many customers are in which attraction. Returns a SORTED LIST. For example: indexes = [3, 2, 5, 1, 4] indicates that attraction3 has the least people waiting. """ counter_total = {} for attraction_pos in self.positions: agents = self.grid.get_neighbors( attraction_pos, moore=True, radius=0, include_center=True ) counter = 0 for agent in agents: if type(agent) is Customer: counter += 1 else: attraction = agent attraction.N_current_cust = counter self.attraction_history[attraction][self.totalTOTAL] = counter counter_total[attraction.unique_id] = counter return counter_total def make_route(self): """Draw coordinates of a possible path.""" for i in range(len(self.path_coordinates)): pos = self.path_coordinates[i] if pos not in self.positions: # Create path agent path = Route(i, self, pos) self.schedule.add(path) self.grid.place_agent(path, pos) def get_themepark_score(self): """ Get score of a themepark based on: - A total of all waitingtimes for every customer - TODO """ attractions = self.get_attractions() total_wait, total_rides = 0, 0 for attraction in attractions: total_wait += attraction.current_waitingtime if attraction.current_a is not None: total_rides += 1 if total_rides == 0: return total_rides return (total_wait / total_rides) def get_strategy_history(self): """ Update history with how many customers chose which strategy """ customers = self.get_customers() randomstrat, closebystrat = 0, 0 for customer in customers: if customer.strategy == "Random" or customer.strategy == "Random_test_4": randomstrat += 1 elif customer.strategy == "Closest_by": closebystrat += 1 self.hist_random_strat.append(randomstrat) self.hist_close_strat.append(closebystrat) def get_customers(self): agents = self.grid.get_neighbors( mid_point, moore=True, radius=RADIUS, include_center=True) customers = [] # Count customer agents for agent in agents: if type(agent) == Customer: customers.append(agent) return customers def get_data_customers(self): """ Return dictionary with data of customers """ data = {} agents = self.grid.get_neighbors( mid_point, moore=True, radius=RADIUS, include_center=True) for agent in agents: if type(agent) is Customer: data[agent.unique_id] = { "totalwaited": agent.total_ever_waited, "visited_attractions": agent.nmbr_attractions, "strategy": agent.strategy, "swapped_strat": agent.strategy_swap_hist } return data def calc_hapiness(self): """ Calculate mean hapiness of all customers, based on: - How many rides were taken - Number of times in the same attraction - Total waiting time """ customers = self.get_customers() scores = [] for customer in customers: history = customer.history values = list(history.values()) total_rides = sum(values) if total_rides != 0: scores.append(total_rides / self.N_attr - self.totalTOTAL / customer.total_ever_waited) else: return None scores = np.interp(scores, (min(scores), max(scores)), (1, 10)) return np.mean(scores) def get_history_list(self): customers = self.get_customers() histories = {} for customer in customers: history = customer.history values = list(history.values()) histories[customer.unique_id] = values return histories def final(self): """ Return data """ hist_list = [] agents = self.grid.get_neighbors( mid_point, moore=True, radius=RADIUS, include_center=True) attractions = self.get_attractions() self.all_rides_list = [0] * len(attractions[0].in_attraction_list) for attraction in attractions: for i in range(len(attraction.in_attraction_list)): self.all_rides_list[i] += attraction.in_attraction_list[i] for i in range(len(self.all_rides_list)): self.all_rides_list[i] /= self.N_attr print("ALL RIDES LIST", self.all_rides_list) cust_data = self.get_data_customers() for agent in agents: if type(agent) is Customer: sum_attr = sum(agent.history.values()) if sum_attr > 0: hist_list.append(agent.strategy_swap_hist) else: hist_list.append(agent.strategy_swap_hist) # print("swap:",agent.strategy_swap_hist , "sum:",sum_attr) # plt.hist(hist_list) # plt.show() histories = self.get_history_list() # save data try: pickle.dump(self.datacollector.get_model_vars_dataframe(), open("../data/strategy_history.p", 'wb')) pickle.dump(self.datacollector2.get_model_vars_dataframe(), open("../data/eff_score_history.p", 'wb')) pickle.dump(cust_data, open("../data/customers.p", 'wb')) pickle.dump(self.park_score[-1], open("../data/park_score.p", "wb")) pickle.dump(self.happinesses, open("../data/hapiness.p", "wb")) pickle.dump(histories, open("../data/cust_history.p", 'wb')) except: pickle.dump(self.datacollector.get_model_vars_dataframe(), open("data/strategy_history.p", 'wb')) pickle.dump(self.datacollector2.get_model_vars_dataframe(), open("data/eff_score_history.p", 'wb')) pickle.dump(cust_data, open("data/customers.p", 'wb')) pickle.dump(self.park_score[-1], open("data/park_score.p", "wb")) pickle.dump(self.happinesses, open("data/hapiness.p", "wb")) pickle.dump(histories, open("data/cust_history.p", 'wb')) try: pickle.dump(self.all_rides_list, open("../data/all_rides.p", "wb")) except: pickle.dump(self.all_rides_list, open("data/all_rides.p", "wb")) print() print("RUN HAS ENDED") print() def save_data(self): """Save data of all attractions and customers.""" # Get info waitinglines = self.calc_waiting_time() for i in range(len(self.attractions)): self.data_dict[i]["waiting_list"].append(waitinglines.get(i)) self.park_score.append(sum(waitinglines.values())) self.happinesses.append(self.calc_hapiness()) def step(self): """Advance the model by one step.""" if self.totalTOTAL < self.max_time: self.totalTOTAL += 1 self.schedule.step() self.datacollector.collect(self) self.datacollector2.collect(self) self.schedule_Attraction.step() self.schedule_Customer.step() # update memory of attractions # attractions = self.get_attractions() # for attraction in attractions: # attraction.update_memory() self.total_steps += 1 self.save_data() self.get_strategy_history() else: for key in self.attraction_history.keys(): y = self.attraction_history[key] x = list(range(0, self.max_time)) self.final()
class PolicyEmergenceSM(Model): ''' Simplest Model for the policy emergence model. ''' def __init__(self, SM_inputs, height=20, width=20): self.height = height self.width = width self.SM_inputs = SM_inputs self.stepCount = 0 self.agenda_PC = None self.agenda_PF = None self.policy_implemented = None self.policy_implemented_number = None self.policy_formulation_run = False # True if an agenda is found self.schedule = RandomActivation(self) self.grid = SingleGrid(height, width, torus=True) # creation of the datacollector vector self.datacollector = DataCollector( # Model-level variables model_reporters = { "step": "stepCount", "AS_PF": get_problem_policy_chosen, "agent_attributes": get_agents_attributes}, # Agent-level variables agent_reporters = { "x": lambda a: a.pos[0], "y": lambda a: a.pos[1], "Agent type": lambda a:type(a), "Issuetree": lambda a: getattr(a, 'issuetree', [None])[a.unique_id if isinstance(a, ActiveAgent) else 0]} ) # , "agenda_PC":"agenda_PC", "agenda_PF":"agenda_PF", "policy_implemented": "policy_implemented" # "x": lambda a: a.pos[0], "y": lambda a: a.pos[1] # "z": lambda a:a.issuetree # belief tree properties self.len_S, self.len_PC, self.len_DC, self.len_CR = issue_tree_input(self) # print(self.len_S, self.len_PC, self.len_DC, self.len_CR) # issue tree properties self.policy_instruments, self.len_ins_1, self.len_ins_2, self.len_ins_all, self.PF_indices = policy_instrument_input(self, self.len_PC) # Set up active agents init_active_agents(self, self.len_S, self.len_PC, self.len_DC, self.len_CR, self.len_PC, self.len_ins_1, self.len_ins_2, self.len_ins_all, self.SM_inputs) # Set up passive agents init_electorate_agents(self, self.len_S, self.len_PC, self.len_DC, self.SM_inputs) # Set up truth agent init_truth_agent(self, self.len_S, self.len_PC, self.len_DC, self.len_ins_1, self.len_ins_2, self.len_ins_all) # the issue tree will need to be updated at a later stage witht he values from the system/policy context # print("Schedule has : ", len(self.schedule.agents), " agents.") # print(self.schedule.agents) # print(" ") # for agent in self.schedule.agent_buffer(shuffled=False): # print(' ') # print(agent) # print(type(agent)) # if isinstance(agent, ActiveAgent): # print(agent.unique_id, " ", agent.pos, " ", agent.agent_type, " ", agent.resources, " ", agent.affiliation, " ", agent.issuetree[agent.unique_id], " ", agent.policytree[agent.unique_id][0]) # if isinstance(agent, ElectorateAgent): # print(agent.unique_id, " ", agent.pos, " ", agent.affiliation, " ", agent.issuetree) # if isinstance(agent, TruthAgent): # print(agent.pos, " ", agent.issuetree) self.running = True self.numberOfAgents = self.schedule.get_agent_count() self.datacollector.collect(self) def step(self, KPIs): print(" ") print("Step +1 - Policy emergence model") print("Step count: ", self.stepCount) ''' Main steps of the Simplest Model for policy emergence: 0. Module interface - Input Obtention of the beliefs from the system/policy context !! This is to be implemented at a later stage 1. Agenda setting step 2. Policy formulation step 3. Module interface - Output Implementation of the policy instrument selected ''' # saving the attributes self.KPIs = KPIs # 0. self.module_interface_input(self.KPIs) ''' TO DO: - Introduce the transfer of information between the external parties and the truth agent relates to the policy impacts ''' # 1. self.agenda_setting() # 2. if self.policy_formulation_run: self.policy_formulation() else: self.policy_implemented = self.policy_instruments[-1] # 3. # self.module_interface_output() # end of step actions: # iterate the steps counter self.stepCount += 1 # collect data self.datacollector.collect(self) print("step ends") print(" ") # print(self.datacollector.get_agent_vars_dataframe()) print(self.datacollector.get_model_vars_dataframe()) return self.policy_implemented def module_interface_input(self, KPIs): ''' The module interface input step consists of actions related to the module interface and the policy emergence model Missing: - Electorate actions ''' # selection of the Truth agent policy tree and issue tree for agent in self.schedule.agent_buffer(shuffled=True): if isinstance(agent, TruthAgent): truth_policytree = agent.policytree_truth for issue in range(self.len_DC+self.len_PC+self.len_S): agent.issuetree_truth[issue] = KPIs[issue] truth_issuetree = agent.issuetree_truth # Transferring policy impact to active agents for agent in self.schedule.agent_buffer(shuffled=True): if isinstance(agent, ActiveAgent): # replacing the policy family likelihoods for PFj in range(self.len_PC): for PFij in range(self.len_PC): agent.policytree[agent.unique_id][PFj][PFij] = truth_policytree[PFj][PFij] # replacing the policy instruments impacts for insj in range(self.len_ins_1 + self.len_ins_2 + self.len_ins_all): agent.policytree[agent.unique_id][self.len_PC+insj][0:self.len_S] = truth_policytree[self.len_PC+insj] # replacing the issue beliefs from the KPIs for issue in range(self.len_DC+self.len_PC+self.len_S): agent.issuetree[agent.unique_id][issue][0] = truth_issuetree[issue] self.preference_update(agent, agent.unique_id) def agenda_setting(self): ''' The agenda setting step is the first step in the policy process conceptualised in this model. The steps are given as follows: 1. Active agents policy core issue selection 2. Active agents policy family selection 3. Active agents actions [to be detailed later] 4. Active agents policy core issue selection update 5. Active agents policy family selection update 6. Agenda selection ''' # 1. & 2. for agent in self.schedule.agent_buffer(shuffled=False): if isinstance(agent, ActiveAgent): # considering only active agents agent.selection_PC() agent.selection_PF() # print("PC and PF selected for agent", agent.unique_id, ": ", agent.selected_PC, agent.selected_PF) # 3. # 4. & 5. for agent in self.schedule.agent_buffer(shuffled=False): if isinstance(agent, ActiveAgent): # considering only active agents agent.selection_PC() agent.selection_PF() # 6. # All active agents considered selected_PC_list = [] selected_PF_list = [] number_ActiveAgents = 0 for agent in self.schedule.agent_buffer(shuffled=False): if isinstance(agent, ActiveAgent): # considering only policy makers selected_PC_list.append(agent.selected_PC) selected_PF_list.append(agent.selected_PF) number_ActiveAgents += 1 # finding the most common policy core issue and its frequency d = defaultdict(int) for i in selected_PC_list: d[i] += 1 result = max(d.items(), key=lambda x: x[1]) agenda_PC_temp = result[0] agenda_PC_temp_frequency = result[1] # finding the most common policy family issue and its frequency d = defaultdict(int) for i in selected_PF_list: d[i] += 1 result = max(d.items(), key=lambda x: x[1]) agenda_PF_temp = result[0] agenda_PF_temp_frequency = result[1] # checking for majority if agenda_PC_temp_frequency > int(number_ActiveAgents/2) and agenda_PF_temp_frequency > int(number_ActiveAgents/2): self.agenda_PC = agenda_PC_temp self.agenda_PF = agenda_PF_temp self.policy_formulation_run = True print("The agenda consists of PC", self.agenda_PC, " and PF", self.agenda_PF, ".") else: self.policy_formulation_run = False print("No agenda was formed, moving to the next step.") def policy_formulation(self): ''' The policy formulation step is the second step in the policy process conceptualised in this model. The steps are given as follows: 0. Detailing of policy instruments that can be considered 1. Active agents deep core issue selection 2. Active agents policy instrument selection 3. Active agents actions [to be detailed later] 4. Active agents policy instrument selection update 5. Policy instrument selection NOTE: THIS CODE DOESNT CONSIDER MAJORITY WHEN MORE THAN THREE POLICY MAKERS ARE INCLUDED, IT CONSIDERS THE MAXIMUM. THIS NEEDS TO BE ADAPTED TO CONSIDER 50% OR MORE! ''' print("Policy formulation being introduced") # 0. possible_PI = self.PF_indices[self.agenda_PF] # 1. & 2. for agent in self.schedule.agent_buffer(shuffled=False): if isinstance(agent, ActiveAgent): # considering only active agents agent.selection_S() agent.selection_PI() # 3. # 4. & 5. for agent in self.schedule.agent_buffer(shuffled=False): if isinstance(agent, ActiveAgent): # considering only active agents agent.selection_PI() # 6. # Only policy makers considered selected_PI_list = [] number_PMs = 0 for agent in self.schedule.agent_buffer(shuffled=False): if isinstance(agent, ActiveAgent) and agent.agent_type == 'policymaker': # considering only policy makers selected_PI_list.append(agent.selected_PI) number_PMs += 1 # finding the most common secondary issue and its frequency d = defaultdict(int) for i in selected_PI_list: d[i] += 1 result = max(d.items(), key=lambda x: x[1]) self.policy_implemented_number = result[0] policy_implemented_number_frequency = result[1] # check for the majority and implemented if satisfied if policy_implemented_number_frequency > int(number_PMs/2): print("The policy instrument selected is policy instrument ", self.policy_implemented_number, ".") self.policy_implemented = self.policy_instruments[self.policy_implemented_number] else: print("No consensus on a policy instrument.") self.policy_implemented = self.policy_instruments[-1] # selecting last policy instrument which is the no instrument policy instrument def module_interface_output(self): print("Module interface output not introduced yet") def preference_update(self, agent, who): self.preference_update_DC(agent, who) self.preference_update_PC(agent, who) self.preference_update_S(agent, who) def preference_update_DC(self, agent, who): """ The preference update function (DC) =========================== This function is used to update the preferences of the deep core issues of agents in their respective belief trees. agent - this is the owner of the belief tree who - this is the part of the belieftree that is considered - agent.unique_id should be used for this - this is done to also include partial knowledge preference calculation """ len_DC = self.len_DC len_PC = self.len_PC len_S = self.len_S ##### # 1.5.1. Preference calculation for the deep core issues # 1.5.1.1. Calculation of the denominator PC_denominator = 0 for h in range(len_DC): if agent.issuetree[who][h][1] == None or agent.issuetree[who][h][0] == None: PC_denominator = 0 else: PC_denominator = PC_denominator + abs(agent.issuetree[who][h][1] - agent.issuetree[who][h][0]) # print('The denominator is given by: ' + str(PC_denominator)) # 1.5.1.2. Selection of the numerator and calculation of the preference for i in range(len_DC): # There are rare occasions where the denominator could be 0 if PC_denominator != 0: agent.issuetree[who][i][2] = abs(agent.issuetree[who][i][1] - agent.issuetree[who][i][0]) / PC_denominator else: agent.issuetree[who][i][2] = 0 def preference_update_PC(self, agent, who): """ The preference update function (PC) =========================== This function is used to update the preferences of the policy core issues of agents in their respective belief trees. agent - this is the owner of the belief tree who - this is the part of the belieftree that is considered - agent.unique_id should be used for this - this is done to also include partial knowledge preference calculation """ len_DC = self.len_DC len_PC = self.len_PC len_S = self.len_S ##### # 1.5.2 Preference calculation for the policy core issues PC_denominator = 0 # 1.5.2.1. Calculation of the denominator for j in range(len_PC): # print('Selection PC' + str(j+1)) # print('State of the PC' + str(j+1) + ': ' + str(agent.issuetree[0][len_DC + j][0])) # the state printed # Selecting the causal relations starting from PC for k in range(len_DC): # Contingency for partial knowledge issues if agent.issuetree[who][k][1] == None or agent.issuetree[who][k][0] == None or agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0] == None: PC_denominator += 0 else: # print('Causal Relation PC' + str(j+1) + ' - PC' + str(k+1) + ': ' + str(agent.issuetree[0][len_DC+len_PC+len_S+j+(k*len_PC)][1])) # print('Gap of PC' + str(k+1) + ': ' + str((agent.issuetree[0][k][1] - agent.issuetree[0][k][0]))) # Check if causal relation and gap are both positive of both negative # print('agent.issuetree[' + str(who) + '][' + str(len_DC+len_PC+len_S+j+(k*len_PC)) + '][0]: ' + str(agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0])) if (agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0] < 0 and (agent.issuetree[who][k][1] - agent.issuetree[who][k][0]) < 0) or (agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0] > 0 and (agent.issuetree[who][k][1] - agent.issuetree[who][k][0]) > 0): PC_denominator = PC_denominator + abs(agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0]*(agent.issuetree[who][k][1] - agent.issuetree[who][k][0])) # print('This is the PC numerator: ' + str(PC_denominator)) else: PC_denominator = PC_denominator # 1.5.2.2. Addition of the gaps of the associated mid-level issues for i in range(len_PC): # Contingency for partial knowledge issues if agent.issuetree[who][len_DC + i][1] == None or agent.issuetree[who][len_DC + i][0] == None: PC_denominator = PC_denominator else: # print('This is the gap for the PC' + str(i+1) + ': ' + str(agent.issuetree[0][len_DC + i][1] - agent.issuetree[0][len_DC + i][0])) PC_denominator += abs(agent.issuetree[who][len_DC + i][1] - agent.issuetree[who][len_DC + i][0]) # print('This is the S denominator: ' + str(PC_denominator)) # 1.5.2.3 Calculation the numerator and the preference # Select one by one the PC for j in range(len_PC): # 1.5.2.3.1. Calculation of the right side of the numerator PC_numerator = 0 # print('Selection PC' + str(j+1)) # print('State of the PC' + str(j+1) + ': ' + str(agent.issuetree[0][len_DC + j][0])) # the state printed # Selecting the causal relations starting from DC for k in range(len_DC): # Contingency for partial knowledge issues if agent.issuetree[who][k][1] == None or agent.issuetree[who][k][0] == None or agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0] == None: PC_numerator += 0 else: # print('Causal Relation PC' + str(j+1) + ' - DC' + str(k+1) + ': ' + str(agent.issuetree[0][len_DC+len_PC+len_S+j+(k*len_PC)][1])) # print('Gap of DC' + str(k+1) + ': ' + str((agent.issuetree[0][k][1] - agent.issuetree[0][k][0]))) # Check if causal relation and gap are both positive of both negative if (agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0] < 0 and (agent.issuetree[who][k][1] - agent.issuetree[who][k][0]) < 0) or (agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0] > 0 and (agent.issuetree[who][k][1] - agent.issuetree[who][k][0]) > 0): PC_numerator = PC_numerator + abs(agent.issuetree[who][len_DC+len_PC+len_S+j+(k*len_PC)][0]*(agent.issuetree[who][k][1] - agent.issuetree[who][k][0])) # print('This is the PC numerator: ' + str(PC_numerator)) else: PC_numerator = PC_numerator # 1.5.2.3.2. Addition of the gap to the numerator # Contingency for partial knowledge issues if agent.issuetree[who][len_DC + j][1] == None or agent.issuetree[who][len_DC + j][0] == None: PC_numerator += 0 else: # print('This is the gap for the PC' + str(j+1) + ': ' + str(agent.issuetree[0][len_DC + j][1] - agent.issuetree[0][len_DC + j][0])) PC_numerator += abs(agent.issuetree[who][len_DC + j][1] - agent.issuetree[who][len_DC + j][0]) # print('The numerator is equal to: ' + str(PC_numerator)) # print('The denominator is equal to: ' + str(PC_denominator)) # 1.5.2.3.3. Calculation of the preference if PC_denominator != 0: agent.issuetree[who][len_DC+j][2] = round(PC_numerator/PC_denominator,3) # print('The new preference of the policy core PC' + str(j+1) + ' is: ' + str(agent.issuetree[0][len_DC+j][2])) else: agent.issuetree[who][len_DC+j][2] = 0 def preference_update_S(self, agent, who): """ The preference update function (S) =========================== This function is used to update the preferences of secondary issues the agents in their respective belief trees. agent - this is the owner of the belief tree who - this is the part of the belieftree that is considered - agent.unique_id should be used for this - this is done to also include partial knowledge preference calculation """ len_DC = self.len_DC len_PC = self.len_PC len_S = self.len_S ##### # 1.5.3 Preference calculation for the secondary issues S_denominator = 0 # 1.5.2.1. Calculation of the denominator for j in range(len_S): # print('Selection S' + str(j+1)) # print('State of the S' + str(j+1) + ': ' + str(agent.issuetree[0][len_DC + len_PC + j][0])) # the state printed # Selecting the causal relations starting from S for k in range(len_PC): # Contingency for partial knowledge issues if agent.issuetree[who][len_DC + k][1] == None or agent.issuetree[who][len_DC + k][0] == None or agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0] == None: S_denominator += 0 else: # print('Causal Relation S' + str(j+1) + ' - PC' + str(k+1) + ': ' + str(agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0])) # print('Gap of PC' + str(k+1) + ': ' + str((agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0]))) # Check if causal relation and gap are both positive of both negative # print('agent.issuetree[' + str(who) + '][' + str(len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)) + '][0]: ' + str(agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0])) if (agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0] < 0 and (agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0]) < 0) or (agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0] > 0 and (agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0]) > 0): S_denominator += abs(agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0]*(agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0])) # print('This is the PC numerator: ' + str(S_denominator)) else: S_denominator = S_denominator # 1.5.2.2. Addition of the gaps of the associated secondary issues for j in range(len_S): # Contingency for partial knowledge issues if agent.issuetree[who][len_DC+len_PC+j][1] == None or agent.issuetree[who][len_DC+len_PC+j][0] == None: S_denominator = S_denominator else: # print('This is the gap for the PC' + str(i+1) + ': ' + str(agent.issuetree[0][len_DC + len_PC + i][1] - agent.issuetree[0][len_DC + len_PC + i][0])) S_denominator += abs(agent.issuetree[who][len_DC+len_PC+j][1] - agent.issuetree[who][len_DC+len_PC+j][0]) # print('This is the PC denominator: ' + str(S_denominator)) # 1.5.2.3 Calculation the numerator and the preference # Select one by one the S for j in range(len_S): # 1.5.2.3.1. Calculation of the right side of the numerator S_numerator = 0 # print('Selection S' + str(j+1)) # print('State of the S' + str(j+1) + ': ' + str(agent.issuetree[who][len_DC + len_PC + j][0])) # the state printed # Selecting the causal relations starting from PC for k in range(len_PC): # Contingency for partial knowledge issues if agent.issuetree[who][len_DC + k][1] == None or agent.issuetree[who][len_DC + k][0] == None or agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0] == None: S_numerator = 0 else: # print('Causal Relation S' + str(j+1) + ' - PC' + str(k+1) + ': ' + str(agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0])) # print('Gap of PC' + str(k+1) + ': ' + str((agent.issuetree[who][len_DC + k][1] - agent.issuetree[who][len_DC + k][0]))) # Check if causal relation and gap are both positive of both negative if (agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0] < 0 and (agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0]) < 0) or (agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0] > 0 and (agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0]) > 0): S_numerator += abs(agent.issuetree[who][len_DC+len_PC+len_S+len_DC*len_PC+j+(k*len_S)][0]*(agent.issuetree[who][len_DC+k][1] - agent.issuetree[who][len_DC+k][0])) # print('This is the PC numerator: ' + str(S_numerator)) else: S_numerator = S_numerator # 1.5.2.3.2. Addition of the gap to the numerator # Contingency for partial knowledge issues if agent.issuetree[who][len_DC+len_PC+j][1] == None or agent.issuetree[who][len_DC+len_PC+j][0] == None: S_numerator += 0 else: # print('This is the gap for the PC' + str(j+1) + ': ' + str(agent.issuetree[who][len_DC+len_PC+j][1] - agent.issuetree[who][len_DC+len_PC+j][0])) S_numerator += abs(agent.issuetree[who][len_DC+len_PC+j][1] - agent.issuetree[who][len_DC+len_PC+j][0]) # print('The numerator is equal to: ' + str(S_numerator)) # print('The denominator is equal to: ' + str(S_denominator)) # 1.5.2.3.3. Calculation of the preference if S_denominator != 0: agent.issuetree[who][len_DC+len_PC+j][2] = round(S_numerator/S_denominator,3) # print('The new preference of the policy core PC' + str(j+1) + ' is: ' + str(agent.issuetree[0][len_DC+j][2])) else: agent.issuetree[who][len_DC+len_PC+j][2] = 0
class Themepark(Model): def __init__(self, N_attr, N_cust, width, height, strategy, theme, max_time, weight, adaptive): """ Args: N_attr (int): the amount of attractions in the theme park N_cust (int): the amout of customers in the theme park width (int): the width of the theme park grid height (int): the height of the theme park grid strategy (str): the strategy of this run (random agents, adaptive agents or adaptive agents with noise) theme (str): the setup of the park (circle, random or clustered) max_time (int): the number of time steps the park will do in one run weight (float): if the customer agents are non-adaptive, this is the strategy they are using adaptive (bool): whether customer agents are able to switch strategies """ self.theme = theme self.max_time = max_time self.N_attr = N_attr self.penalty_per = PENALTY_PERCENTAGE self.weight = weight self.adaptive = adaptive self.strategies = STRATEGIES self.happinesses = [] self.N_attr = N_attr self.N_cust = N_cust self.width = width self.height = height self.total_steps = 0 self.cust_ids = N_cust self.strategy = strategy self.grid = MultiGrid(width, height, torus=False) self.schedule = BaseScheduler(self) self.schedule_Attraction = BaseScheduler(self) self.schedule_Customer = BaseScheduler(self) # Cluster or circle coordinates if self.theme == "cluster": self.x_list, self.y_list, self.positions = xlist, ylist, positions elif self.theme == "circle": self.x_list, self.y_list, self.positions = get_attraction_coordinates( width, height, N_attr, "circle") # keeps up the current time self.totalTOTAL = 0 # add attractions to park self.attractions = self.make_attractions() self.attraction_history = self.make_attr_hist() self.running = True self.data_dict = {} # keep up a history of the strategies self.hist_random_strat = [] self.hist_close_strat = [] # keep up a history of the occupation of the rides self.all_rides_list = [] # distribution of strategies throughout population self.strategy_composition = self.make_strategy_composition() # add customers to park self.customers = self.add_customers(self.N_cust) # keep up park score throughout time self.park_score = [] for attraction in self.get_attractions(): self.data_dict[attraction.unique_id] = ({ "id": attraction.unique_id, "length": attraction.attraction_duration, "waiting_list": [] }) # datacollector for the visualisation of the data if self.strategy == "Random_test_4": self.datacollector = DataCollector({ "Random": lambda m: self.strategy_counter(self.strategies[0]), "0.00": lambda m: self.strategy_counter(self.strategies[1]), "0.25": lambda m: self.strategy_counter(self.strategies[2]), "0.50": lambda m: self.strategy_counter(self.strategies[3]), "0.75": lambda m: self.strategy_counter(self.strategies[4]), "1.00": lambda m: self.strategy_counter(self.strategies[5]), }) elif self.strategy == "Random": self.datacollector = DataCollector( {"Random": lambda m: self.N_cust}) else: self.datacollector = DataCollector({ "0.00": lambda m: self.strategy_counter(self.strategies[0]), "0.25": lambda m: self.strategy_counter(self.strategies[1]), "0.50": lambda m: self.strategy_counter(self.strategies[2]), "0.75": lambda m: self.strategy_counter(self.strategies[3]), "1.00": lambda m: self.strategy_counter(self.strategies[4]), }) self.datacollector2 = DataCollector( {"score": lambda m: self.make_score()}) def make_score(self): """ Calculates and returns an efficiency score for the enire theme park. """ # calculates the ideal distribution of customers over the attractions per attraction ideal = {} cust_in_row = 0 for i in range(len(self.get_attractions())): ideal[i] = self.N_cust / self.N_attr cust_in_row += self.get_attractions()[i].N_current_cust # calculates the difference between the ideal and the real situation tot_difference = 0 for i in range(len(self.get_attractions())): difference = abs(cust_in_row / self.N_attr - self.get_attractions()[i].N_current_cust) tot_difference += difference # the fraction of customers that is not optimally distributed fraction_not_right = (tot_difference / self.N_cust) # add a penalty by multiplying by the fraction of people currently in a line score = abs(1 - (fraction_not_right)) * cust_in_row / self.N_cust return score def make_attr_hist(self): """ Make history of attractions that are visited for each customer. """ attraction_history = {} for attraction in self.get_attractions(): attraction_history[attraction] = [0] * (self.max_time + 1) return attraction_history def strategy_counter(self, strategy): """ Input is a strategy, for example "0.25" or "Random", output is the number of strategies are adopted at a current time step. """ for attraction_pos in self.positions: counter = 0 for agent in self.customers: if agent.weight == strategy: counter += 1 return counter def make_strategy_composition(self): """ Make a composition of all strategies over the entire theme park. Returns a dictionary with strategies as keys and the fraction of people using this strategy as value. """ # if this is a run with adaptive agents and noise if self.strategy == "Random_test_4": self.strategies = ["Random_test_4", 0.0, 0.25, 0.50, 0.75, 1.0] # make dictionary with fractions (values can be ignored!) dict = { self.strategies[0]: 0, self.strategies[1]: 0.20, self.strategies[2]: 0.20, self.strategies[3]: 0.20, self.strategies[4]: 0.20, self.strategies[5]: 0.20 } composition_list = [] for i in range(len(self.strategies)): if i == 0: dict[self.strategies[i]] = FRACTION_RANDOM continue else: # choose a random number composition_list.append(random.randint(0, 100)) sum_comp = sum(composition_list) sum_comp = sum_comp - sum_comp * FRACTION_RANDOM for i in range(len(self.strategies)): if i == 0: continue else: # determine the fraction of customer agents with this strategy dict[self.strategies[i]] = composition_list[i - 1] / sum_comp # runs without noise else: # make dictionary with fractions (values can be ignored!) dict = { self.strategies[0]: 0.20, self.strategies[1]: 0.20, self.strategies[2]: 0.20, self.strategies[3]: 0.20, self.strategies[4]: 0.20 } composition_list = [] for i in range(len(self.strategies)): # choose a random number composition_list.append(random.randint(0, 100)) sum_comp = sum(composition_list) for i in range(len(self.strategies)): # determine the fraction of agents with this strategy dict[self.strategies[i]] = composition_list[i - 1] / sum_comp return dict def make_attractions(self): """ Initialize attractions on fixed positions, defined in the global variables x_list and y_list. Returns a dictionary of attractions """ attractions = {} for i in range(self.N_attr): pos = (self.x_list[i], self.y_list[i]) # place attraction if grid cell is empty if self.grid.is_cell_empty(pos): name = str(i) a = Attraction(i, self, pos, name) attractions[i] = a self.schedule_Attraction.add(a) self.grid.place_agent(a, pos) return attractions def get_attractions(self): """ Return a list with all attractions. """ agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) attractions = [] for agent in agents: if type(agent) == Attraction: attractions.append(agent) return attractions def add_customers(self, N_cust, added=False): """ Initialize customers on random positions. Returns a list of all customers """ # a list of weights of which the indices correspond to the id of the agent # to who this weight is given weights_list = [] # Adaptive strategy if self.adaptive is True: # Decide weights for every customer for j in self.strategy_composition.keys(): for i in range(round(N_cust * self.strategy_composition[j])): weights_list.append(j) # Add weights for the random strategy if len(weights_list) < self.N_cust: rand = random.choice(self.strategies) weights_list.append(rand) elif len(weights_list) > self.N_cust: rand = random.choice(weights_list) weights_list.remove(rand) else: if self.strategy is not "Random": for i in range(round(N_cust)): print(self.weight) weights_list.append(self.weight) cust_list = [] for i in range(N_cust): # Get random location within grid height and width pos_temp = [ random.randint(0, WIDTH - 1), random.randint(0, HEIGHT - 1) ] rand_x, rand_y = pos_temp[0], pos_temp[1] pos = (rand_x, rand_y) if added is True: i = self.cust_ids if self.strategy == "Random_test_4": if weights_list[i] == "Random_test_4": strategy = "Random_test_4" else: strategy = "Closest_by" else: strategy = self.strategy if weights_list == []: weight = None else: weight = weights_list[i] # make customer a = Customer(i, self, pos, strategy, weight, self.adaptive) self.schedule_Customer.add(a) self.grid.place_agent(a, pos) cust_list.append(a) return cust_list def calculate_people(self): """ Calculate how many customers are in which attraction. Returns a list of which the indices correspond to the ids of the atttraction """ counter_total = {} # loop through attractions for attraction_pos in self.positions: agents = self.grid.get_neighbors(attraction_pos, moore=True, radius=0, include_center=True) counter = 0 # find customers in this attraction for agent in agents: if type(agent) is Customer: counter += 1 else: attraction = agent # update the amount of customers in this attraction attraction.N_current_cust = counter counter_total[attraction.unique_id] = counter return list(counter_total.values()) def calc_waiting_time(self): """ Return a dictionary of watingtimes for every attraction """ counter_total = {} attractions = self.get_attractions() for attraction in attractions: counter_total[ attraction.unique_id] = attraction.current_waitingtime return counter_total def calculate_people_sorted(self): """ Calculate how many customers are in which attraction. Returns a dictionary with attraction-ids as keys and customers in line as values. """ counter_total = {} # loop through attractions for attraction_pos in self.positions: agents = self.grid.get_neighbors(attraction_pos, moore=True, radius=0, include_center=True) counter = 0 # find customers in this attraction for agent in agents: if type(agent) is Customer: counter += 1 else: attraction = agent # update the amount of customers in this attraction attraction.N_current_cust = counter self.attraction_history[attraction][self.totalTOTAL] = counter counter_total[attraction.unique_id] = counter return counter_total def get_strategy_history(self): """ Update history with how many customers chose which strategy """ customers = self.get_customers() randomstrat, closebystrat = 0, 0 for customer in customers: if customer.strategy == "Random" or customer.strategy == "Random_test_4": randomstrat += 1 elif customer.strategy == "Closest_by": closebystrat += 1 self.hist_random_strat.append(randomstrat) self.hist_close_strat.append(closebystrat) def get_customers(self): """Return a list of all customers in the theme park.""" agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) customers = [] # Count customer agents for agent in agents: if type(agent) == Customer: customers.append(agent) return customers def get_data_customers(self): """ Return dictionary with data of customers """ data = {} agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) for agent in agents: if type(agent) is Customer: data[agent.unique_id] = { "totalwaited": agent.total_ever_waited, "visited_attractions": agent.nmbr_attractions, "strategy": agent.strategy, "swapped_strat": agent.strategy_swap_hist } return data def calc_hapiness(self): """ Calculate mean hapiness of all customers, based on: - How many rides were taken - Number of times in the same attraction - Total waiting time Returns the mean happiness of all customers """ customers = self.get_customers() scores = [] for customer in customers: history = customer.history values = list(history.values()) total_rides = sum(values) if total_rides != 0: scores.append(total_rides / self.N_attr - self.totalTOTAL / customer.total_ever_waited) else: return None scores = np.interp(scores, (min(scores), max(scores)), (1, 10)) return np.mean(scores) def get_history_list(self): """ Return a dictionary of the history of visited attraction for each customer. """ customers = self.get_customers() histories = {} for customer in customers: history = customer.history values = list(history.values()) histories[customer.unique_id] = values return histories def final(self): """ Return and save data at the end of the run.""" hist_list = [] agents = self.grid.get_neighbors(mid_point, moore=True, radius=RADIUS, include_center=True) attractions = self.get_attractions() # Make a list for the fraction of occupied rides at all time steps self.all_rides_list = [0] * len(attractions[0].in_attraction_list) for attraction in attractions: for i in range(len(attraction.in_attraction_list)): self.all_rides_list[i] += attraction.in_attraction_list[i] for i in range(len(self.all_rides_list)): self.all_rides_list[i] /= self.N_attr cust_data = self.get_data_customers() for agent in agents: if type(agent) is Customer: sum_attr = sum(agent.history.values()) if sum_attr > 0: hist_list.append(agent.strategy_swap_hist) else: hist_list.append(agent.strategy_swap_hist) histories = self.get_history_list() # save data try: pickle.dump(self.datacollector.get_model_vars_dataframe(), open("../data/strategy_history.p", 'wb')) pickle.dump(self.datacollector2.get_model_vars_dataframe(), open("../data/eff_score_history.p", 'wb')) pickle.dump(cust_data, open("../data/customers.p", 'wb')) pickle.dump(self.park_score[-1], open("../data/park_score.p", "wb")) pickle.dump(self.happinesses, open("../data/hapiness.p", "wb")) pickle.dump(histories, open("../data/cust_history.p", 'wb')) pickle.dump(self.all_rides_list, open("../data/all_rides.p", "wb")) except: pickle.dump(self.datacollector.get_model_vars_dataframe(), open("data/strategy_history.p", 'wb')) pickle.dump(self.datacollector2.get_model_vars_dataframe(), open("data/eff_score_history.p", 'wb')) pickle.dump(cust_data, open("data/customers.p", 'wb')) pickle.dump(self.park_score[-1], open("data/park_score.p", "wb")) pickle.dump(self.happinesses, open("data/hapiness.p", "wb")) pickle.dump(histories, open("data/cust_history.p", 'wb')) pickle.dump(self.all_rides_list, open("data/all_rides.p", "wb")) print() print("RUN HAS ENDED") print() def save_data(self): """Save data of all attractions and customers.""" # Get info waitinglines = self.calc_waiting_time() for i in range(len(self.attractions)): self.data_dict[i]["waiting_list"].append(waitinglines.get(i)) self.park_score.append(sum(waitinglines.values())) self.happinesses.append(self.calc_hapiness()) def step(self): """Advance the model by one step.""" if self.totalTOTAL < self.max_time: self.totalTOTAL += 1 self.schedule.step() # Collect data for every step self.datacollector.collect(self) self.datacollector2.collect(self) # Step for both attraction and customer self.schedule_Attraction.step() self.schedule_Customer.step() self.total_steps += 1 # Save data and update strategy history self.save_data() self.get_strategy_history() else: for key in self.attraction_history.keys(): y = self.attraction_history[key] x = list(range(0, self.max_time)) self.final()
class FireEvacuation(Model): MIN_HEALTH = 0.75 MAX_HEALTH = 1 MIN_SPEED = 1 MAX_SPEED = 2 MIN_NERVOUSNESS = 1 MAX_NERVOUSNESS = 10 MIN_EXPERIENCE = 1 MAX_EXPERIENCE = 10 MIN_VISION = 1 # MAX_VISION is simply the size of the grid def __init__(self, floor_plan_file, human_count, collaboration_percentage, fire_probability, visualise_vision, random_spawn, save_plots): # Load floorplan # floorplan = np.genfromtxt(path.join("fire_evacuation/floorplans/", floor_plan_file)) with open(os.path.join("fire_evacuation/floorplans/", floor_plan_file), "rt") as f: floorplan = np.matrix([line.strip().split() for line in f.readlines()]) # Rotate the floorplan so it's interpreted as seen in the text file floorplan = np.rot90(floorplan, 3) # Check what dimension our floorplan is width, height = np.shape(floorplan) # Init params self.width = width self.height = height self.human_count = human_count self.collaboration_percentage = collaboration_percentage self.visualise_vision = visualise_vision self.fire_probability = fire_probability self.fire_started = False # Turns to true when a fire has started self.save_plots = save_plots # Set up model objects self.schedule = RandomActivation(self) self.grid = MultiGrid(height, width, torus=False) # Used to start a fire at a random furniture location self.furniture_list = [] # Used to easily see if a location is a FireExit or Door, since this needs to be done a lot self.fire_exit_list = [] self.door_list = [] # If random spawn is false, spawn_list will contain the list of possible spawn points according to the floorplan self.random_spawn = random_spawn self.spawn_list = [] # Load floorplan objects for (x, y), value in np.ndenumerate(floorplan): value = str(value) floor_object = None if value is "W": floor_object = Wall((x, y), self) elif value is "E": floor_object = FireExit((x, y), self) self.fire_exit_list.append((x, y)) self.door_list.append((x, y)) # Add fire exits to doors as well, since, well, they are elif value is "F": floor_object = Furniture((x, y), self) self.furniture_list.append((x, y)) elif value is "D": floor_object = Door((x, y), self) self.door_list.append((x, y)) elif value is "S": self.spawn_list.append((x, y)) if floor_object: self.grid.place_agent(floor_object, (x, y)) self.schedule.add(floor_object) # Create a graph of traversable routes, used by agents for pathing self.graph = nx.Graph() for agents, x, y in self.grid.coord_iter(): pos = (x, y) # If the location is empty, or a door if not agents or any(isinstance(agent, Door) for agent in agents): neighbors = self.grid.get_neighborhood(pos, moore=True, include_center=True, radius=1) for neighbor in neighbors: # If there is contents at this location and they are not Doors or FireExits, skip them if not self.grid.is_cell_empty(neighbor) and neighbor not in self.door_list: continue self.graph.add_edge(pos, neighbor) # Collects statistics from our model run self.datacollector = DataCollector( { "Alive": lambda m: self.count_human_status(m, Human.Status.ALIVE), "Dead": lambda m: self.count_human_status(m, Human.Status.DEAD), "Escaped": lambda m: self.count_human_status(m, Human.Status.ESCAPED), "Incapacitated": lambda m: self.count_human_mobility(m, Human.Mobility.INCAPACITATED), "Normal": lambda m: self.count_human_mobility(m, Human.Mobility.NORMAL), "Panic": lambda m: self.count_human_mobility(m, Human.Mobility.PANIC), "Verbal Collaboration": lambda m: self.count_human_collaboration(m, Human.Action.VERBAL_SUPPORT), "Physical Collaboration": lambda m: self.count_human_collaboration(m, Human.Action.PHYSICAL_SUPPORT), "Morale Collaboration": lambda m: self.count_human_collaboration(m, Human.Action.MORALE_SUPPORT) } ) # Calculate how many agents will be collaborators number_collaborators = int(round(self.human_count * (self.collaboration_percentage / 100))) # Start placing human agents for i in range(0, self.human_count): if self.random_spawn: # Place human agents randomly pos = self.grid.find_empty() else: # Place human agents at specified spawn locations pos = random.choice(self.spawn_list) if pos: # Create a random human health = random.randint(self.MIN_HEALTH * 100, self.MAX_HEALTH * 100) / 100 speed = random.randint(self.MIN_SPEED, self.MAX_SPEED) if number_collaborators > 0: collaborates = True number_collaborators -= 1 else: collaborates = False # Vision statistics obtained from http://www.who.int/blindness/GLOBALDATAFINALforweb.pdf vision_distribution = [0.0058, 0.0365, 0.0424, 0.9153] vision = int(np.random.choice(np.arange(self.MIN_VISION, self.width + 1, (self.width / len(vision_distribution))), p=vision_distribution)) nervousness_distribution = [0.025, 0.025, 0.1, 0.1, 0.1, 0.3, 0.2, 0.1, 0.025, 0.025] # Distribution with slight higher weighting for above median nerovusness nervousness = int(np.random.choice(range(self.MIN_NERVOUSNESS, self.MAX_NERVOUSNESS + 1), p=nervousness_distribution)) # Random choice starting at 1 and up to and including 10 experience = random.randint(self.MIN_EXPERIENCE, self.MAX_EXPERIENCE) belief_distribution = [0.9, 0.1] # [Believes, Doesn't Believe] believes_alarm = np.random.choice([True, False], p=belief_distribution) human = Human(pos, health=health, speed=speed, vision=vision, collaborates=collaborates, nervousness=nervousness, experience=experience, believes_alarm=believes_alarm, model=self) self.grid.place_agent(human, pos) self.schedule.add(human) else: print("No tile empty for human placement!") self.running = True # Plots line charts of various statistics from a run def save_figures(self): DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) OUTPUT_DIR = DIR + "/output" results = self.datacollector.get_model_vars_dataframe() dpi = 100 fig, axes = plt.subplots(figsize=(1920 / dpi, 1080 / dpi), dpi=dpi, nrows=1, ncols=3) status_results = results.loc[:, ['Alive', 'Dead', 'Escaped']] status_plot = status_results.plot(ax=axes[0]) status_plot.set_title("Human Status") status_plot.set_xlabel("Simulation Step") status_plot.set_ylabel("Count") mobility_results = results.loc[:, ['Incapacitated', 'Normal', 'Panic']] mobility_plot = mobility_results.plot(ax=axes[1]) mobility_plot.set_title("Human Mobility") mobility_plot.set_xlabel("Simulation Step") mobility_plot.set_ylabel("Count") collaboration_results = results.loc[:, ['Verbal Collaboration', 'Physical Collaboration', 'Morale Collaboration']] collaboration_plot = collaboration_results.plot(ax=axes[2]) collaboration_plot.set_title("Human Collaboration") collaboration_plot.set_xlabel("Simulation Step") collaboration_plot.set_ylabel("Successful Attempts") collaboration_plot.set_ylim(ymin=0) timestr = time.strftime("%Y%m%d-%H%M%S") plt.suptitle("Percentage Collaborating: " + str(self.collaboration_percentage) + "%, Number of Human Agents: " + str(self.human_count), fontsize=16) plt.savefig(OUTPUT_DIR + "/model_graphs/" + timestr + ".png") plt.close(fig) # Starts a fire at a random piece of furniture with file_probability chance def start_fire(self): rand = random.random() if rand < self.fire_probability: fire_furniture = random.choice(self.furniture_list) fire = Fire(fire_furniture, self) self.grid.place_agent(fire, fire_furniture) self.schedule.add(fire) self.fire_started = True print("Fire started at:", fire_furniture) def step(self): """ Advance the model by one step. """ self.schedule.step() # If there's no fire yet, attempt to start one if not self.fire_started: self.start_fire() self.datacollector.collect(self) # If no more agents are alive, stop the model and collect the results if self.count_human_status(self, Human.Status.ALIVE) == 0: self.running = False if self.save_plots: self.save_figures() @staticmethod def count_human_collaboration(model, collaboration_type): """ Helper method to count the number of collaborations performed by Human agents in the model """ count = 0 for agent in model.schedule.agents: if isinstance(agent, Human): if collaboration_type == Human.Action.VERBAL_SUPPORT: count += agent.get_verbal_collaboration_count() elif collaboration_type == Human.Action.MORALE_SUPPORT: count += agent.get_morale_collaboration_count() elif collaboration_type == Human.Action.PHYSICAL_SUPPORT: count += agent.get_physical_collaboration_count() return count @staticmethod def count_human_status(model, status): """ Helper method to count the status of Human agents in the model """ count = 0 for agent in model.schedule.agents: if isinstance(agent, Human): if agent.get_status() == status: count += 1 return count @staticmethod def count_human_mobility(model, mobility): """ Helper method to count the mobility of Human agents in the model """ count = 0 for agent in model.schedule.agents: if isinstance(agent, Human): if agent.get_mobility() == mobility: count += 1 return count
class Simple_Language_Model(Model): def __init__(self, num_people, width=5, height=5, max_people_factor=5, init_lang_distrib=[0.25, 0.65, 0.1], num_cities=10, lang_ags_sorted_by_dist=True, lang_ags_sorted_in_clust=True): self.num_people = num_people self.grid_width = width self.grid_height = height self.max_people_factor = max_people_factor self.init_lang_distrib = init_lang_distrib self.num_cities = num_cities self.lang_ags_sorted_by_dist = lang_ags_sorted_by_dist self.lang_ags_sorted_in_clust = lang_ags_sorted_in_clust self.clust_centers = None self.cluster_sizes = None # define grid and schedule self.grid = MultiGrid(height, width, False) self.schedule = RandomActivation(self) ## RANDOMLY DEFINE ALL CITY-CENTERS COORDS (CITY == HOMES, JOB CENTERS and SCHOOLS) # first define available points as pct of squared grid length grid_pct_list = np.linspace(0.1, 0.9, 100) # avoid edges # now generate the cluster centers (CITIES-VILLAGES) self.clust_centers = np.random.choice(grid_pct_list,size=(self.num_cities, 2),replace=False) if lang_ags_sorted_by_dist: self.clust_centers = sorted(self.clust_centers, key=lambda x:pdist([x,[0,0]]) ) # INITIALIZE KNOWN PEOPLE NETWORK => label is lang spoken self.known_people_network = nx.DiGraph() # self.known_people_network.add_edge('A','B', lang_spoken = 'cat') # self.known_people_network.add_edge('A','C', lang_spoken = 'spa') # self.known_people_network['A']['C']['lang_spoken'] # self.known_people_network['B']['A'] = 'cat' # INITIALIZE FRIENDSHIP NETWORK self.friendship_network = nx.Graph() # sort by friendship intensity # sorted(self.friendship_network[n_1].items(), # key=lambda edge: edge[1]['link_strength'], # reverse = True) # INITIALIZE FAMILY NETWORK self.family_network = nx.DiGraph() # ADD ALL AGENTS TO GRID AND SCHEDULE S = 0.5 if (not lang_ags_sorted_by_dist) and (not lang_ags_sorted_in_clust): for id_ in range(self.num_people): x = random.randrange(self.grid_width) y = random.randrange(self.grid_height) coord = (x,y) lang = np.random.choice([0,1,2], p=self.init_lang_distrib) ag = Simple_Language_Agent(self, id_, lang, S) self.add_agent(ag, coord) else: self.create_lang_agents() # DATA COLLECTOR self.datacollector = DataCollector( model_reporters={"count_spa": lambda m: m.get_lang_stats(0), "count_bil": lambda m: m.get_lang_stats(1), "count_cat": lambda m: m.get_lang_stats(2), "total_num_agents": lambda m:len(m.schedule.agents), "biling_evol_h": lambda m:m.get_bilingual_global_evol('heard'), "biling_evol_s": lambda m: m.get_bilingual_global_evol('spoken')} ) def add_agent(self, a, coords): """Method to add a given agent to grid, schedule and system networks Arguments: * a : agent class instance * coords : agent location on grid (2-D tuple of integers) """ # add agent to grid and schedule self.schedule.add(a) self.grid.place_agent(a, (coords[0], coords[1])) ## add agent node to all networks self.known_people_network.add_node(a) self.friendship_network.add_node(a) self.family_network.add_node(a) def compute_cluster_sizes(self, min_size=20, small_large_pcts=[0.6, 0.4]): """ Method to compute sizes of each agent cluster Arguments: * min_size: minimum accepted cluster size ( integer) * small_large_pcts: percentages of small and large cities over total ( list of floats 0<x<1) Returns: * list of integers representing cluster sizes """ if min_size * self.num_cities >= self.num_people: raise ValueError('num_people should be greater than min_size * num_cities ') size_choices = [max(int(self.num_people / (10 * self.num_cities)), min_size), max(int(self.num_people / self.num_cities), min_size)] city_sizes = np.random.choice(size_choices, p=small_large_pcts, size=self.num_cities - 1) last_city_size = self.num_people - city_sizes.sum() city_sizes = np.append(city_sizes, last_city_size) pcts = np.random.dirichlet(city_sizes) return np.random.multinomial(city_sizes.sum(), pcts) def generate_cluster_points_coords(self, pct_grid_w, pct_grid_h, clust_size): """ Using binomial ditribution, this method generates initial coordinates for a given cluster, defined via its center and its size. Cluster size as well as cluster center coords (in grid percentage) must be provided Arguments: * pct_grid_w: positive float < 1 to define clust_center along grid width * pct_grid_h: positive float < 1 to define clust_center along grid height * clust_size: desired size of the cluster being generated Returns: * cluster_coordinates: two numpy arrays with x and y coordinates respectively """ ## use binomial generator to get clusters in width * height grid ## n = grid_width, p = pct_grid, size = num_experim x_coords = np.random.binomial(self.grid_width, pct_grid_w, size=clust_size) for idx, elem in enumerate(x_coords): if elem >= self.grid_width: x_coords[idx] = self.grid_width - 1 y_coords = np.random.binomial(self.grid_height, pct_grid_h, size=clust_size) for idx, elem in enumerate(y_coords): if elem >= self.grid_width: y_coords[idx] = self.grid_height - 1 return x_coords, y_coords def create_lang_agents(self): """ Method to instantiate all agents Arguments: * sort_lang_types_by_dist: boolean to specify if agents must be sorted by distance to global origin * sort_sub_types_within_clust: boolean to specify if agents must be sorted by distance to center of cluster they belong to """ self.cluster_sizes = self.compute_cluster_sizes() array_langs = np.random.choice([0, 1, 2], p=self.init_lang_distrib, size=self.num_people) if self.lang_ags_sorted_by_dist: array_langs.sort() idxs_to_split = self.cluster_sizes.cumsum() langs_per_clust = np.split(array_langs, idxs_to_split) if (not self.lang_ags_sorted_by_dist) and (self.lang_ags_sorted_in_clust): for subarray in langs_per_clust: subarray.sort() # invert if needed ids = set(range(self.num_people)) for clust_idx, (clust_size, clust_c_coords) in enumerate(zip(self.cluster_sizes, self.clust_centers)): x_cs, y_cs = self.generate_cluster_points_coords(clust_c_coords[0], clust_c_coords[1], clust_size) if (not self.lang_ags_sorted_by_dist) and (self.lang_ags_sorted_in_clust): clust_p_coords = sorted(list(zip(x_cs, y_cs)), key=lambda x:pdist([x, [self.grid_width*self.clust_centers[clust_idx][0], self.grid_height*self.clust_centers[clust_idx][1]] ]) ) x_cs, y_cs = list(zip(*clust_p_coords)) for ag_lang, x, y in zip(langs_per_clust[clust_idx], x_cs, y_cs): ag = Simple_Language_Agent(self, ids.pop(), ag_lang, 0.5) self.add_agent(ag, (x, y)) def get_lang_stats(self, i): """Method to get counts of each type of lang agent Arguments: * i : integer from [0,1,2] hat specifies agent lang type Returns: * lang type count as percentage of total """ ag_lang_list = [ag.language for ag in self.schedule.agents] num_ag = len(ag_lang_list) lang_counts = Counter(ag_lang_list) return lang_counts[i]/num_ag def get_bilingual_global_evol(self, lang_typology): """Method to compute internal linguistic structure of all bilinguals, expressed as average amount of Catalan heard or spoken as % of total Arguments: * lang_typology: string that can take either of two values 'heard' or 'spoken' Returns: * float representing the AVERAGE percentage of Catalan in bilinguals """ list_biling = [(ag.lang_freq['cat_pct_h'], ag.lang_freq['cat_pct_s']) for ag in self.schedule.agents if ag.language == 1] if lang_typology == 'heard': if list_biling: return np.array(list(zip(*list_biling))[0]).mean() else: if self.get_lang_stats(2) > self.get_lang_stats(0): return 1 else: return 0 else: if list_biling: return np.array(list(zip(*list_biling))[1]).mean() else: if self.get_lang_stats(2) > self.get_lang_stats(0): return 1 else: return 0 def step(self): self.datacollector.collect(self) self.schedule.step() def run_model(self, steps, save_frames_freq=0): pbar = pyprind.ProgBar(steps) for _ in range(steps): self.step() if save_frames_freq: if not self.schedule.steps%save_frames_freq: self.show_results(step=self.schedule.steps, plot_results=False, save_fig=True) pbar.update() def create_agents_attrs_data(self, ag_attr, plot=False): ag_and_coords = [(getattr(ag, ag_attr), ag.pos[0], ag.pos[1]) for ag in self.schedule.agents] ag_and_coords = np.array(ag_and_coords) df_attrs = pd.DataFrame({'values': ag_and_coords[:, 0], 'x': ag_and_coords[:, 1], 'y': ag_and_coords[:, 2]}) self.df_attrs_avg = df_attrs.groupby(['x', 'y']).mean() if plot: s = plt.scatter(self.df_attrs_avg.reset_index()['x'], self.df_attrs_avg.reset_index()['y'], c=self.df_attrs_avg.reset_index()['values'], vmin=0, vmax=2, s=30, cmap='viridis') plt.colorbar(s) plt.show() def show_results(self, ag_attr='language', step=None, plot_results=True, plot_type='imshow', save_fig=False): grid_size = (3, 5) self.create_agents_attrs_data(ag_attr) data_2_plot = self.datacollector.get_model_vars_dataframe()[:step] data_2D = self.df_attrs_avg.reset_index() ax1 = plt.subplot2grid(grid_size, (0, 3), rowspan=1, colspan=2) data_2_plot[["count_bil", "count_cat", "count_spa"]].plot(ax=ax1, title='lang_groups') ax1.xaxis.tick_bottom() ax1.legend(loc='best', prop={'size': 8}) ax2 = plt.subplot2grid(grid_size, (1, 3), rowspan=1, colspan=2) data_2_plot['total_num_agents'].plot(ax=ax2, title='num_agents') ax3 = plt.subplot2grid(grid_size, (2, 3), rowspan=1, colspan=2) data_2_plot[['biling_evol_h', 'biling_evol_s']].plot(ax=ax3, title='biling_quality') ax3.legend(loc='best', prop={'size': 8}) ax4 = plt.subplot2grid(grid_size, (0, 0), rowspan=3, colspan=3) if plot_type == 'imshow': s = ax4.imshow(self.df_attrs_avg.unstack('x'), vmin=0, vmax=2, cmap='viridis', interpolation='nearest', origin='lower') else: s = ax4.scatter(data_2D['x'], data_2D['y'], c=data_2D['values'], vmin=0, vmax=2, s=35, cmap='viridis') ax4.text(0.02, 0.95, 'time = %.1f' % self.schedule.steps, transform=ax4.transAxes) plt.colorbar(s) plt.tight_layout() if save_fig: plt.savefig('step' + str(step) + '.png') if plot_results: plt.show() def run_and_animate(self, steps, plot_type='imshow'): fig = plt.figure() grid_size = (3, 5) ax1 = plt.subplot2grid(grid_size, (0, 3), rowspan=1, colspan=2) ax1.set_xlim(0, steps) ax1.set_ylim(0, 1) ax1.xaxis.tick_bottom() line10, = ax1.plot([], [], lw=2, label='count_spa') line11, = ax1.plot([], [], lw=2, label='count_bil') line12, = ax1.plot([], [], lw=2, label='count_cat') ax1.legend(loc='best', prop={'size': 8}) ax1.set_title("lang_groups") ax2 = plt.subplot2grid(grid_size, (1, 3), rowspan=1, colspan=2) ax2.set_xlim(0, steps) ax2.set_ylim(0, self.max_people_factor * self.num_people) line2, = ax2.plot([], [], lw=2, label = "total_num_agents") ax2.legend(loc='best', prop={'size': 8}) ax2.set_title("num_agents") ax3 = plt.subplot2grid(grid_size, (2, 3), rowspan=1, colspan=2) ax3.set_xlim(0, steps) ax3.set_ylim(0, 1) line30, = ax3.plot([], [], lw=2, label='biling_evol_h') line31, = ax3.plot([], [], lw=2, label='biling_evol_s') ax3.legend(loc='best', prop={'size': 8}) ax3.set_title("biling_quality") ax4 = plt.subplot2grid(grid_size, (0, 0), rowspan=3, colspan=3) ax4.set_xlim(0, self.grid_width-1) ax4.set_ylim(0, self.grid_height-1) if plot_type == 'imshow': im_2D = ax4.imshow(np.zeros((self.grid_width, self.grid_height)), vmin=0, vmax=2, cmap='viridis', interpolation='nearest', origin='lower') fig.colorbar(im_2D) elif plot_type == 'scatter': dots = ax4.scatter([], [], c=[], vmin=0, vmax=2, cmap='viridis') fig.colorbar(dots) time_text = ax4.text(0.02, 0.95, '', transform=ax4.transAxes) def init_show(): if plot_type == 'imshow': im_2D.set_array(np.random.choice([np.nan, 0], p=[1, 0], size=(self.grid_width, self.grid_height))) return im_2D, elif plot_type == 'scatter': dots.set_offsets([0,0]) return dots, def run_and_update(i): #run model step self.step() #create plots and data for 1D plots data = self.datacollector.get_model_vars_dataframe() line10.set_data(data.index, data['count_spa']) line11.set_data(data.index, data['count_bil']) line12.set_data(data.index, data['count_cat']) line2.set_data(data.index, data['total_num_agents']) line30.set_data(data.index, data['biling_evol_h']) line31.set_data(data.index, data['biling_evol_s']) # generate data for 2D representation self.create_agents_attrs_data('language') # create 2D plot time_text.set_text('time = %.1f' % i) if plot_type == 'imshow': im_2D.set_array(self.df_attrs_avg.unstack('x')) return line10, line11, line12, line2, line30, line31, im_2D, time_text else: data = np.hstack((self.df_attrs_avg.reset_index()['x'][:, np.newaxis], self.df_attrs_avg.reset_index()['y'][:, np.newaxis])) dots.set_offsets(data) dots.set_array(self.df_attrs_avg.reset_index()['values']) return line10, line11, line12, line2, line30, line31, dots, time_text # generate persistent animation object ani = animation.FuncAnimation(fig, run_and_update,init_func=init_show, frames=steps, interval=100, blit=True, repeat=False) plt.tight_layout() plt.show() def save_model_data(self): self.model_data = {'initial_conditions':{'cluster_sizes': self.cluster_sizes, 'cluster_centers': self.clust_centers, 'init_num_people': self.num_people, 'grid_width': self.grid_width, 'grid_height': self.grid_height, 'init_lang_distrib': self.init_lang_distrib, 'num_cities': self.num_cities, 'sort_by_dist': self.lang_ags_sorted_by_dist, 'sort_within_clust': self.lang_ags_sorted_in_clust}, 'model_results': self.datacollector.get_model_vars_dataframe()} dd.io.save('model_data.h5', self.model_data) def load_model_data(self, data_filename, key='/' ): return dd.io.load(data_filename,key)
class CDAmodel(Model): """Continuous Double Auction model with some number of agents.""" def __init__(self, supply, demand, s_strategy, b_strategy, highest_ask=100, lowest_ask=0): self.supply = supply self.demand = demand self.num_sellers = supply.num_agents self.num_buyers = demand.num_agents self.initialize_spread() self.market_price = None self.history = Order_history() self.num_traded = 0 self.num_step = 0 # history records an order as a bid or ask only if it updates # the spread self.num_period = 1 self.loc_period = [0] # where each period happens # Sometimes trade does not happen within a period, # so we need variables to indicate them. self.no_trade = False # When a shift happens, I need to know it so that # I can calculate efficiency properly. self.shifted = False self.shifted_period = -1 self.new_supply = None self.new_demand = None # How agents are activated at each step self.schedule = RandomChoiceActivation(self) # Create agents for i, cost in enumerate(self.supply.price_schedule): self.schedule.add( b_strategy(i, self, "seller", cost, supply.q_per_agent, highest_ask, lowest_ask)) for i, value in enumerate(self.demand.price_schedule): j = self.num_sellers + i self.schedule.add( s_strategy(j, self, "buyer", value, demand.q_per_agent, highest_ask, lowest_ask)) # Collecting data # self.datacollector = DataCollector( # model_reporters={"Period": "num_period", # "OB": "outstanding_bid", # "OBer": get_bidder_id, # "OA": "outstanding_ask", # "OAer": get_asker_id, # "MarketPrice": "market_price", # "Traded": "traded", # "Order": lambda x: x.history.get_last_action(), # "Efficiency": compute_efficiency}, # agent_reporters={"Period": lambda x: x.model.num_period, # "Type": lambda x: type(x), # "Role": "role", # "Value": "value", # "Good": "good", # "Right": "right", # "Surplus": "surplus"} # ) self.datacollector = DataCollector(model_reporters={ "Step": "num_step", "Period": "num_period", "TransNum": "num_traded", "OB": "outstanding_bid", "OA": "outstanding_ask", "MarketPrice": "market_price", "Traded": "traded", "CumulativeActualSurplus": compute_actual_surplus, "TheoreticalSurplus": compute_theoretical_surplus, "CumulativeTS": compute_acc_theoretical_surplus, "Efficiency": compute_efficiency }, agent_reporters={ "Period": lambda x: x.model.num_period, "Type": lambda x: type(x), "Role": "role", "Value": "value", "Good": "good", "Right": "right", "Surplus": "surplus" }) def initialize_spread(self): # Initialize outstanding bid and ask self.outstanding_bid = 0 self.outstanding_bidder = None self.outstanding_ask = math.inf self.outstanding_asker = None self.traded = 0 self.market_price = None def update_ob(self, bidder, price): if price > self.outstanding_bid: # Update the outstanding bid self.outstanding_bid = price self.outstanding_bidder = bidder if price > self.outstanding_ask: # a transaction happens contract_price = self.outstanding_ask self.execute_contract(contract_price) self.history.accept_bid(bidder, self.outstanding_asker, contract_price) else: self.history.submit_bid(bidder, price) else: # null order self.history.submit_null(bidder, None, price) def update_oa(self, asker, price): if price < self.outstanding_ask: # Update the outstanding ask self.outstanding_ask = price self.outstanding_asker = asker if price < self.outstanding_bid: contract_price = self.outstanding_bid self.execute_contract(contract_price) self.history.accept_ask(self.outstanding_bidder, asker, contract_price) else: # only updated the outstanding ask self.history.submit_ask(asker, price) else: # null order self.history.submit_null(None, asker, price) def execute_contract(self, contract_price): self.outstanding_bidder.buy(contract_price) self.outstanding_asker.sell(contract_price) self.market_price = contract_price self.traded = 1 self.num_traded += 1 def next_period(self, new_supply=None, new_demand=None): if self.num_traded == 0: self.no_trade = True if new_supply and new_demand: self.new_supply = new_supply self.new_demand = new_demand self.shifted = True self.shifted_period = self.num_period + 1 if self.shifted: supply = self.new_supply demand = self.new_demand else: supply = self.supply demand = self.demand # Making sure the schedule is ordered as it was initialized. self.schedule.agents.sort(key=lambda x: x.unique_id) for i, cost in enumerate(supply.price_schedule): self.schedule.agents[i].good = supply.q_per_agent self.schedule.agents[i].value = cost self.schedule.agents[i].active = True for i, value in enumerate(demand.price_schedule): j = self.num_sellers + i self.schedule.agents[j].right = demand.q_per_agent self.schedule.agents[j].value = value self.schedule.agents[j].active = True self.num_period += 1 self.num_traded = 0 self.loc_period.append(self.num_step) self.history.next_period() def step(self): if self.traded == 1: self.initialize_spread() # print("step:", self.num_step) self.schedule.step() self.num_step += 1 self.datacollector.collect(self) def plot_model(self): data = self.datacollector.get_model_vars_dataframe() data = data[data.Traded == 1] f = plt.figure(1) ax = f.add_subplot(111) plt.plot(data.MarketPrice) plt.axhline(y=self.supply.equilibrium_price, color='black', linestyle='dashed') plt.text(0.8, 0.9, round(compute_efficiency(self), 3), fontsize=20, transform=ax.transAxes) for i in range(self.num_period): plt.axvline(x=self.loc_period[i], color='black', linestyle='dashed') plt.show()
class FishingModel(Model): ''' Lokta-Volterra Type Model for Fishermen and Fish ''' food_bool = True no_fish_zone_bool = True quotum_bool = False no_fish_size = 0.25 quotum = 3000 step_count = 5000 def __init__( self, height=40, width=40, initial_fish=300, initial_fishermen=100, initial_school_size=100, split_size=200, fish_reproduction_number=1.15, initial_wallet=100, catch_rate=0.5, max_load=30, full_catch_reward=100, initial_wallet_survival=4 * 12, beta_fisherman_spawn=1, energy_gain=5, energy_loss=1, track_n_rolling_gains=4 * 3, initial_energy=10, regrowth_time=10, food_bool=True, no_fish_zone_bool=False, quotum_bool=False, no_fish_size=0, quotum=0, ): super().__init__() # Initialization self.height = height self.width = width self.initial_fish = initial_fish self.initial_fishermen = initial_fishermen self.initial_wallet = initial_wallet self.initial_wallet_survival = initial_wallet_survival self.initial_school_size = initial_school_size self.cumulative_gain = 0 # Booleans self.food_bool = food_bool self.no_fish_zone_bool = no_fish_size > 0 self.quotum_bool = quotum > 0 # Fish self.fish_reproduction_number = fish_reproduction_number self.split_size = split_size self.fish_cap = 5 * initial_fish * initial_school_size self.this_avg_school_size = initial_school_size # Fisherman self.max_load = max_load self.full_catch_reward = full_catch_reward self.catch_rate = catch_rate * self.max_load if self.quotum_bool == True: self.yearly_quotum = quotum else: self.yearly_quotum = 1000000000 self.total_yearly_caught = 0 self.total_yearly_caught_prev = 8000 self.recruitment_switch = True self.beta_fisherman_spawn = beta_fisherman_spawn self.this_avg_wallet = initial_wallet self.track_n_rolling_gains = track_n_rolling_gains # food self.energy_gain = energy_gain if not self.food_bool: self.energy_loss = 0 else: self.energy_loss = energy_loss self.initial_energy = initial_energy self.regrowth_time = regrowth_time self.food_amount = height * width # No fish zone if self.no_fish_zone_bool: self.no_fish_size = int(no_fish_size * (1 / no_fish_size)**(1 / 2) * width) else: self.no_fish_size = 0 # Add a schedule for fish and fishermen seperately to prevent race-conditions self.schedule_Fish = RandomActivation(self) self.schedule_Fisherman = RandomActivation(self) if self.food_bool: self.schedule_Food = RandomActivation(self) self.grid = MultiGrid(self.width, self.height, torus=True) if self.food_bool: self.datacollector = DataCollector({ "time": lambda m: self.schedule_Fish.time, "Fish schools": lambda m: self.schedule_Fish.get_agent_count(), "Fishermen": lambda m: self.schedule_Fisherman.get_agent_count(), "Average wallet": lambda m: self.this_avg_wallet, "Average school size": lambda m: self.this_avg_school_size, "Total fish": lambda m: self.schedule_Fish.get_agent_count() * self. this_avg_school_size * 0.01, "Available food": lambda m: self.food_amount, "Cumulative gain": lambda m: self.cumulative_gain, "Fish price": lambda m: self.full_catch_reward }) else: self.datacollector = DataCollector({ "time": lambda m: self.schedule_Fish.time, "Fish schools": lambda m: self.schedule_Fish.get_agent_count(), "Fishermen": lambda m: self.schedule_Fisherman.get_agent_count(), "Average wallet": lambda m: self.this_avg_wallet, "Average school size": lambda m: self.this_avg_school_size, "Total fish": lambda m: self.schedule_Fish.get_agent_count() * self. this_avg_school_size * 0.01, "Cumulative gain": lambda m: self.cumulative_gain, "Fish price": lambda m: self.full_catch_reward }) # Keep a list of all agents self.agents = [] # Create fish and fishermen self.init_population(Fish, self.initial_fish) self.init_population(Fisherman, self.initial_fishermen) if self.food_bool == True: self.init_food() # This is required for the datacollector to work self.running = True self.datacollector.collect(self) # full run statistics self.fish_mean = -666 self.fish_slope = -666 self.fish_variance = -666 def init_food(self): ''' Fills grid with fish food ''' for i in range(self.width): for j in range(self.height): self.new_agent(Food, (i, j), 0, 0, True, self.regrowth_time, True, 0) def init_population(self, agent_type, n): ''' Method that provides an easy way of making a bunch of agents at once. ''' for i in range(int(n)): x = random.randrange(self.width) y = random.randrange(self.height) if agent_type == Fish: self.new_agent(agent_type, (x, y), self.initial_school_size, self.initial_energy, True, 0, False, self.energy_loss) else: self.new_agent(agent_type, (x, 0), 0, self.initial_wallet, True, 0, False, 0) def new_agent(self, agent_type, pos, size, wallet, switch, regrowth_time, food, energy_loss): ''' Method that creates a new agent, and adds it to the correct scheduler. ''' agent = agent_type(self.next_id(), self, pos, size, wallet, switch, regrowth_time, food, energy_loss) self.grid.place_agent(agent, pos) self.agents.append(agent) getattr(self, f'schedule_{agent_type.__name__}').add(agent) def remove_agent(self, agent): ''' Method that removes an agent from the grid and the correct scheduler. ''' self.grid.remove_agent(agent) self.agents.remove(agent) getattr(self, f'schedule_{type(agent).__name__}').remove(agent) def recruit_fisherman(self): ''' Method that spawns new fisherman based on the average gains of the existing fisherman. ''' # make sure that at least one fisherman exists, who wouldn't try a new bussiness in a new field? if self.schedule_Fisherman.get_agent_count() == 0: self.init_population(Fisherman, 1) rolling_mean_gains = statistics.mean([ statistics.mean(fisherman.rolling_gains) for fisherman in self.schedule_Fisherman.agents ]) # add new fisherman proportional to the profitability if rolling_mean_gains > 0: n_new_fisherman = int( np.random.poisson(lam=rolling_mean_gains * self.beta_fisherman_spawn, size=1)) self.init_population(Fisherman, n_new_fisherman) def get_fish_stats(self): ''' Method that computes information about fishes. ''' fish_size = [fish.size for fish in self.schedule_Fish.agents] if len(fish_size) == 0: self.this_avg_school_size = 0 else: self.this_avg_school_size = sum(fish_size) / len(fish_size) def get_food_stats(self): ''' Method that computes the current amount of available food. ''' food_amount = 0 for agent in self.schedule_Food.agents: if agent.food == True: food_amount += 1 self.food_amount = food_amount def calc_fish_reproduction(self): """ Calculates the reproduction ratio for the fish per year, which caps the total fish pop. """ total_fish = self.schedule_Fish.get_agent_count( ) * self.this_avg_school_size percentage = 1 - (self.fish_cap - total_fish) / self.fish_cap self.fish_reproduction_number = 0.2 / (1 + 0.00005** (-(percentage - 0.5))) + 1 # the function doesn't cap at 1 exactly, so making sure it does here if percentage == 1: self.fish_reproduction_number = 1 def get_fisherman_stats(self): ''' Method that computes information about fisherman. ''' if len(self.schedule_Fisherman.agents) == 0: self.this_avg_wallet = 0 else: fisherman_wallet = [ fisherman.wallet for fisherman in self.schedule_Fisherman.agents ] if len(fisherman_wallet) == 0: self.this_avg_wallet = 0 else: self.this_avg_wallet = sum(fisherman_wallet) / len( fisherman_wallet) def get_fish_price(self): ''' Method for computing reward for fish based on number of previous fish caught ''' if (self.schedule_Fish.time + 1) % (4 * 12) == 0: if self.total_yearly_caught == 0: self.full_catch_reward = 20 else: self.full_catch_reward = 100 * self.total_yearly_caught_prev / self.total_yearly_caught if self.full_catch_reward < 20: self.full_catch_reward = 20 if self.full_catch_reward > 250: self.full_catch_reward = 250 if self.total_yearly_caught_prev == 0: self.full_catch_reward = 250 if self.total_yearly_caught == 0: self.full_catch_reward = 250 self.total_yearly_caught_prev = self.total_yearly_caught self.total_yearly_caught = 0 self.recruitment_switch = True def get_model_stats(self): ''' Method for computing full model run statistics ''' final_data = self.datacollector.get_model_vars_dataframe() final_total_fish = final_data["Total fish"] mod = sm.OLS(final_total_fish, sm.add_constant(final_data["time"])) res = mod.fit() self.fish_mean = res.params[0] self.fish_slope = res.params[1] self.fish_variance = np.var(final_total_fish) def step(self): ''' Method that calls the step method for each of the sheep, and then for each of the wolves. ''' if self.food_bool == False: self.calc_fish_reproduction() self.get_fish_price() self.schedule_Fish.step() self.schedule_Fisherman.step() self.get_fish_stats() self.get_fisherman_stats() if self.food_bool == True: self.schedule_Food.step() self.get_food_stats() if self.recruitment_switch == True and (self.schedule_Fish.time + 1) % 4 * 3 == 0: self.recruit_fisherman() # Save the statistics if (self.schedule_Fish.time + 4 * 6) % (4 * 12) == 0: self.datacollector.collect(self) def run_model(self, step_count): ''' Method that runs the model for a specific amount of steps. ''' for i in range(step_count): if self.total_yearly_caught >= self.yearly_quotum: self.recruitment_switch = False self.step() self.get_model_stats()
class EvacuationModel(Model): """A Mesa ABM model to simulate evacuation during a flood Args: hazard: Spatial table of flood hazard zones in WGS84 output_path: Path to output files without extension domain: Polygon used to select OSM data, required if the graph, agents or targets are not specified target_types: List of OSM amenity values to use as targets, defaults to school network: Undirected network generated from OSM road network targets: Spatial table of OSM amenities target_capacity: The number of agents that can be evacuated to each target agents: Spatial table of agent starting locations seed: Seed value for random number generation Attributes: output_path (str): Path to output files without extension schedule (RandomActivation): Scheduler which activates each agent once per step, in random order, with the order reshuffled every step hazard (GeoDataFrame): Spatial table of flood hazard zones in WGS84 G (Graph): Undirected network generated from OSM road network nodes (GeoDataFrame): Spatial table of nodes in G edges (GeoDataFrame): Spatial table edges in G grid (NetworkGrid): Network grid for agents to travel around based on G data_collector (DataCollector): Stores the model state at each time step target_nodes (Series): Series of nodes to evacuate to target_capacity (int): The number of agents that can be evacuated to each target igraph: Duplicate of G as an igraph object to speed up routing """ def __init__( self, hazard: GeoDataFrame, output_path: str, domain: Optional[Polygon] = None, target_types: Iterable[str] = tuple(['school']), network: Optional[Graph] = None, targets: Optional[GeoDataFrame] = None, target_capacity: int = 100, agents: Optional[GeoDataFrame] = None, seed: Optional[int] = None): super().__init__() self._seed = seed self.output_path = output_path self.hazard = hazard self.schedule = RandomActivation(self) self.target_capacity = target_capacity if network is None: self.G = osmnx.graph_from_polygon(domain, simplify=False) self.G = self.G.to_undirected() else: self.G = network self.nodes: GeoDataFrame self.edges: GeoDataFrame self.nodes, self.edges = osmnx.save_load.graph_to_gdfs(self.G) if agents is None: agents = GeoDataFrame(geometry=create_footprints_gdf(domain).centroid) if targets is None: targets = osmnx.pois_from_polygon(domain, amenities=list(target_types)) # Query can return polygons as well as points, only using the points targets = targets[targets.geometry.geom_type == 'Point'] output_gpkg = output_path + '.gpkg' driver = 'GPKG' targets.crs, agents.crs = [self.nodes.crs] * 2 nodes_tree = cKDTree(np.transpose([self.nodes.geometry.x, self.nodes.geometry.y])) # Prevents warning about CRS not being the same self.hazard.crs = self.nodes.crs self.hazard.to_file(output_gpkg, layer='hazard', driver=driver) agents_in_hazard_zone: GeoDataFrame = sjoin(agents, self.hazard) agents_in_hazard_zone = agents_in_hazard_zone.loc[~agents_in_hazard_zone.index.duplicated(keep='first')] agents_in_hazard_zone.geometry.to_file(output_gpkg, layer='agents', driver=driver) assert len(agents_in_hazard_zone) > 0, 'There are no agents within the hazard zone' targets_in_hazard_zone: GeoDataFrame = sjoin(targets, self.hazard) targets_in_hazard_zone = targets_in_hazard_zone.loc[~targets_in_hazard_zone.index.duplicated(keep='first')] targets_outside_hazard_zone = targets[~targets.index.isin(targets_in_hazard_zone.index.values)] targets_outside_hazard_zone.to_file(output_gpkg, layer='targets', driver=driver) assert len(targets_outside_hazard_zone) > 0, 'There are no targets outside the hazard zone' _, node_idx = nodes_tree.query( np.transpose([agents_in_hazard_zone.geometry.x, agents_in_hazard_zone.geometry.y])) _, target_node_idx = nodes_tree.query( np.transpose([targets_outside_hazard_zone.geometry.x, targets_outside_hazard_zone.geometry.y])) for (_, row), nearest_node in zip(targets_outside_hazard_zone.iterrows(), self.nodes.index[target_node_idx]): if not self.G.has_node(row.osmid): self.G.add_edge(nearest_node, row.osmid, length=0) self.G.nodes[row.osmid]['osmid'] = row.osmid self.G.nodes[row.osmid]['x'] = row.geometry.x self.G.nodes[row.osmid]['y'] = row.geometry.y self.nodes, self.edges = osmnx.save_load.graph_to_gdfs(self.G) self.nodes[['osmid', 'geometry']].to_file(output_gpkg, layer='nodes', driver=driver) self.edges[['osmid', 'geometry']].to_file(output_gpkg, layer='edges', driver=driver) output_gml = output_path + '.gml' osmnx.nx.write_gml(self.G, path=output_gml) self.igraph = igraph.read(output_gml) self.target_nodes = targets_outside_hazard_zone.osmid self.grid = NetworkGrid(self.G) # Create agents for i, idx in enumerate(node_idx): a = agent.EvacuationAgent(i, self) self.schedule.add(a) self.grid.place_agent(a, self.nodes.index[idx]) a.update_route() a.update_location() self.data_collector = DataCollector( model_reporters={ 'evacuated': evacuated, 'stranded': stranded }, agent_reporters={'position': 'pos', 'reroute_count': 'reroute_count', 'lat': 'lat', 'lon': 'lon', 'highway': 'highway', 'status': status}) def step(self): """Advances the model by one step and then stores the current state in data_collector""" self.schedule.step() self.data_collector.collect(self) def run(self, steps: int): """Runs the model for the given number of steps` Args: steps: number of steps to run the model for Returns: DataFrame: the agent vars dataframe """ self.data_collector.collect(self) for _ in range(steps): self.step() if self.data_collector.model_vars['evacuated'][-1] + self.data_collector.model_vars['stranded'][-1] == len( self.schedule.agents): # Continue for 5 steps after all agents evacuated or stranded for _ in range(5): self.step() break self.data_collector.get_agent_vars_dataframe().astype({'highway': pd.Int64Dtype()}).to_csv( self.output_path + '.agent.csv') self.data_collector.get_model_vars_dataframe().to_csv(self.output_path + '.model.csv') return self.data_collector.get_agent_vars_dataframe()
class LoveMatch(Model): ''' Love-match market Model: En este modelo, cada individuo recorre de manera aleatoria el lugar, al encontrarse con un match (agente del sexo opuesto con parámetros de belleza y riqueza coincidentes con lo deseado) desaparece del modelo. El objetivo es observar la distribución de perfiles de belleza y riqueza a lo largo del tiempo hasta ver quienes no logran encontrar pareja. ''' def __init__( self, height=50, width=50, density=0.8, HM_pc=0.2, entry_rate=1, max_agents=750 ): # Aquí establecemos el tamaño del Grid donde se desarrolla el modelo, además de los parámetros iniciales. self.height = height self.width = width self.density = density self.HM_pc = HM_pc self.entry_rate = 5 self.schedule = RandomActivation(self) self.grid = MultiGrid(height, width, torus=False) self.max_agents = max_agents self.parejas = 0 self.hombres = 0 self.mujeres = 0 self.unhappy = 0 self.idcounter = 0 # En esta sección, etiquetamos a cada agente según su tipo for cell in self.grid.coord_iter(): x = cell[1] y = cell[2] if self.random.random() < self.density: if self.random.random() < self.HM_pc: gender = 1 self.hombres += 1 else: gender = 0 self.mujeres += 1 #Creamos a cada agente y asignamos su ID, cad vez que se crea un agente, se agrega uno al contador de ID's #Nota: La distribución de las características las modelamos con una distribución log-normal. Esto nos permite tener solo valores positivos y este ranking de belleza/riqueza se concentra de 0 a 1 self.idcounter += 1 agent = miAgente((x, y), self, gender, beauty=np.random.lognormal(0.5, 0.30), wealth=np.random.lognormal(0.5, 0.30), desired_beauty=np.random.lognormal(0.5, 0.3), desired_wealth=np.random.lognormal(0.5, 0.3), time_to_critical=random.randint(10, 30), sojourn=-1, is_critical=0, myid=self.idcounter) #coloca a los agentes en el modelo self.schedule.add(agent) self.grid.place_agent(agent, (x, y)) #Corre el modelo self.running = True #Colecciona los datos relevantes para el agente y para el modelo self.datacollector = DataCollector(model_reporters={ 'density': 'density', 'parejas': 'parejas', 'unhappy': 'unhappy', 'hombres': 'hombres', 'mujeres': 'mujeres' }, agent_reporters={ 'myid': 'myid', 'wealth': 'wealth', 'gender': 'gender', 'beauty': 'beauty', 'desired_beauty': 'desired_beauty', 'desired_wealth': 'desired_wealth', 'time_to_critical': 'time_to_critical', 'is_critical': 'is_critical', 'sojourn': 'sojourn' }) self.datacollector.collect(self) def update(self): if self.schedule.get_agent_count() < self.max_agents: for i in range(self.entry_rate): x = self.random.randrange(self.grid.width) y = self.random.randrange(self.grid.height) if self.random.random() < self.HM_pc: gender = 1 self.hombres += 1 else: gender = 0 self.mujeres += 1 agent = miAgente(i, self, gender, beauty=random.g(4, 2), wealth=random.gauss(4, 3), desired_beauty=random.gauss(4, 3), desired_wealth=random.gauss(3, 2), time_to_critical=random.gauss(20, 5), sojourn=-1, is_critical=0) self.schedule.add(agent) self.grid.place_agent(agent, (x, y)) def step( self ): # Este step permite que el modelo siga corriendo hasta que todos los agentes tengan pareja self.schedule.step() # Por fines gráficos, recolectamos la información sobre la cantidad de parejas self.datacollector.collect(self) ### Guarda la información relevante dentro de tablas en csv's. self.datacollector.get_agent_vars_dataframe().to_csv("test_me_a.csv") self.datacollector.get_model_vars_dataframe().to_csv("test_me_m.csv") ### Finalmente, el modelo se detiene si el número de agentes es cero if self.schedule.get_agent_count() == 0: self.running = False
class Modelo(Model): #Algunas constantes SUSCEPTIBLE = 0 EXPUESTO = 1 INFECTADO = 2 RECUPERADO = 3 salud_to_str = { 0: 'Susceptible', 1: 'Expuesto', 2: 'Infectado', 3: 'Recuperado' } pp_dia = 4 ## Son los pasos dados por dia simulado def __init__(self, world_object, agent_object, params, ind_attrs, rand_seed=None): super().__init__() self.rand = Random(rand_seed) self.params = params self.mundo = world_object(self, agent_object) self.movilidad = obtener_movilidad() self.dia_cero = params['dia_cero'] #self.prop_inf_exp = params['prop_inf_exp'] #Proporcion entre infectaros y suceptibles a la fecha self.un_dia = datetime.timedelta(days=1) self.ind_attrs = ind_attrs self.schedule = RandomActivation(self) self.generar_regiones() self.dia = 0 self.n_paso = 0 ## Se define el grid que se representará en la #self.grid = self.ciudad.nodes['ciudad']['espacio'] model_reporters = { 'Fecha': lambda x: x.dia_cero + x.dia * x.un_dia, 'Susceptibles': self.conteo_func(self.SUSCEPTIBLE), 'Expuestos': self.conteo_func(self.EXPUESTO), 'Infectados': self.conteo_func(self.INFECTADO), 'Recuperados': self.conteo_func(self.RECUPERADO) } reg_reporters = {k: self.conteo_por_reg(k) for k in self.regiones} self.datacollector = DataCollector({ **model_reporters, **reg_reporters }) self.conteo_instantaneo = self.conteo() def generar_regiones(self): datos = self.leer_regiones('Datos/datos.pk') conexiones = self.generar_lista_de_aristas('Datos/adyacencia.pk', list(datos.keys())) #infectados = self.obtener_infectados('Datos/infectados.csv', # list(datos.keys())) #historico = leer_historico() #infectados = historico[(self.dia_cero, 'Activos')] #fecha = self.params['dia_cero']################################3 ids_start = 0 self.regiones = {} for region in datos: #if region not in seleccionadas: continue self.regiones[region] = datos[region] tamano = self.params['area'] pob = ceil(self.regiones[region]['pob'] // self.params['inds_x_agente']) ids = [i for i in range(ids_start, ids_start + pob)] ids_start += pob individuos = self.mundo.generar_individuos(pob, ids=ids, attrs=self.ind_attrs) """ n_infectados = ceil(infectados[region]/self.params['inds_x_agente'])\ if infectados.get(region, None) is not None else 0 n_susceptibles = ceil(n_infectados*self.prop_inf_exp) #print(f'{region}: {pob} agentes, {n_infectados} infectados, {n_susceptibles} susceptibles') """ if region == 'Mérida': for i in range(self.params['expuestos_iniciales']): individuos[i].salud = self.EXPUESTO for ind in individuos: """ if n_infectados>0: ind.salud = self.INFECTADO n_infectados -= 1 #print(f'\tSe agrega un infectado ind {ind.unique_id}') elif n_infectados==0 and n_susceptibles>0: ind.salud = self.EXPUESTO n_susceptibles -= 1 #print(f'\tSe agrega un expuesto ind {ind.unique_id}') """ self.schedule.add(ind) #print(f'La región {region} tiene {len(individuos)}') self.mundo.crear_nodo(region, 'municipio', ocupantes=individuos, tamano=tamano, ind_pos_def='aleatorio') #print('Se crean las aristas') self.mundo.add_weighted_edges_from(conexiones, weight='peso') #print([a.salud for a in self.schedule.agents if a.salud==self.EXPUESTO]) #posiciones = {k: list(self.regiones[k]['centro'])[::-1] for k in self.regiones} #self.mundo.visualizar(pos = posiciones, with_labels = True) def norm_coord(self, coord): coord = np.array(coord) esq1 = np.array([21.670833, -90.621414]) esq2 = np.array([19.546208, -87.449881]) delta = esq2 - esq1 return (coord - esq1) / delta def conteo(self): #Una función para contar los casos actuales en la ciudad self.conteo_instantaneo = [0, 0, 0, 0] for a in self.schedule.agents: self.conteo_instantaneo[a.salud] += 1 return self.conteo_instantaneo def conteo_func(self, tipo): def contar(modelo): return modelo.conteo_instantaneo[tipo] return contar def conteo_por_reg(self, reg): def contar(modelo): ags = modelo.mundo.obtener_agentes_en_nodo(reg) conteo = [0, 0, 0, 0] for a in ags: conteo[a.salud] += 1 return conteo return contar def leer_regiones(self, path): with open(path, 'rb') as f: datos = pk.load(f) return datos def generar_lista_de_aristas(self, path, regiones): conexiones = [] with open(path, 'rb') as f: datos = pk.load(f) assert len(datos) == len( regiones), f'{len(datos)}!={len(regiones)}' a_agregar = [] for region in datos: a_agregar = [(region, nueva, peso)\ for nueva, peso in datos[region]] conexiones.extend(a_agregar) return conexiones def obtener_infectados(self, path, regiones): infectados = {} with open(path, 'r') as f: for line in f.readlines()[4:]: datos = line.split(',') if datos[1] not in regiones: print(f'{datos[1]} no está en regiones') else: infectados[datos[1]] = int(datos[5]) return infectados def step(self): self.dia = self.n_paso // self.pp_dia #es el momento del dia if self.dia == 4: ##En el cuarto día, que corresponde al primer caso en #Yucatán, se planta un infectado. Esto para asegurar que #siempre habrá un infectado agentes = self.mundo.obtener_agentes_en_nodo('Mérida') agentes[0].salud = self.INFECTADO self.conteo() self.datacollector.collect(self) self.schedule.step() self.n_paso += 1 def correr(self, n_steps, show=False): bloques = int(n_steps * 0.1) if show: print('---- Corriendo simulación ----') for i in range(n_steps): self.step() if show and int(i % bloques) == 0: print('%d%% ... ' % (int(i / n_steps * 100)), end='\n') if show: print('100%') def plot(self, names=None): data = self.datacollector.get_model_vars_dataframe() if names is None: data.plot() elif isinstance(names, list): data[names].plot() else: print('Se debe ingresar los nombres de las columnas en una lista') plt.show(block=True)
class DCGame(Model): def __init__(self, adjMat, numVisibleColorNodes, numAdversarialNodes, inertia): self.adjMat = adjMat self.numVisibleColorNodes = numVisibleColorNodes self.numAdversarialNodes = numAdversarialNodes # self.adversarialNodes = [] self.visibleColorNodes = [] self.regularNodes = [] self.schedule = RandomActivation(self) self.numAgents = len(adjMat) self.inertia = inertia # if there are 20 consensus colors then a # terminal state is reached self.terminate = False self.time = 0 # logging information self.log = Log() ## temporarily added this for figuring out ## why visible nodes have no help self.hasConflict = False # convert adjMat to adjList def getAdjList(adjMat): adjList = {key: [] for key in range(self.numAgents)} for node in range(self.numAgents): adjList[node] = [ idx for idx, value in enumerate(adjMat[node]) if value == True ] return adjList self.adjList = getAdjList(self.adjMat) ############# designate adversarial ############# # (node, degree) node_deg = [(idx, count(adjMat[idx])) for idx in range(self.numAgents)] # select the top-k nodes with largest degrees as adversarial node_deg.sort(key=lambda x: x[1], reverse=True) self.adversarialNodes = [ item[0] for item in node_deg[:self.numAdversarialNodes] ] ############# designate visible nodes ############# availableNodes = shuffled(node_deg[self.numAdversarialNodes:]) self.visibleColorNodes = [ item[0] for item in availableNodes[:self.numVisibleColorNodes] ] self.regularNodes = [ n for n in range(self.numAgents) if n not in self.adversarialNodes ] # make sure we have 20 regular nodes assert len(self.regularNodes) == 20 # adversarial nodes and regular nodes should not overlap assert set(self.adversarialNodes) & set(self.regularNodes) == set() # visible nodes should belong to regular nodes assert set(self.visibleColorNodes) & set(self.regularNodes) == set( self.visibleColorNodes) # logging simulation configuration self.log.add("#visible nodes: " + str(self.visibleColorNodes)) self.log.add("#adversarial nodes: " + str(self.adversarialNodes)) self.log.add("#regular nodes: " + str(self.regularNodes) + '\n') ############# initialize all agents ############# for i in range(self.numAgents): # if i is a visible node isVisibleNode = i in self.visibleColorNodes # if i is an adversarial isAdversarial = i in self.adversarialNodes # make sure adversarial nodes are not intersected with visible nodes assert isVisibleNode & isAdversarial == False neighbors = self.adjList[i] # visible color nodes in i's neighbors vNode = list(set(neighbors) & set(self.visibleColorNodes)) # if i == 6: # print(vNode) inertia = self.inertia # print("Add agent:", (i, visibleNode, adversarial, neighbors, visibleColorNodes)) a = GameAgent(i, isVisibleNode, isAdversarial, neighbors, vNode, inertia, self) self.schedule.add(a) # instantiate all nodes' neighbors and visibleColorNodes for agent in self.schedule.agents: agent.instantiateNeighbors(self) agent.instantiateVisibleColorNodes(self) self.datacollector = DataCollector( model_reporters={ "red": getRed, "green": getGreen }, agent_reporters={"agent_color": lambda a: a.color}) # simulate the whole model for one step def step(self): # # # if either red or green reaches consensus, terminates! # # in terminal state we do not collect data if not self.terminate: self.datacollector.collect(self) self.schedule.step() return self.terminate def simulate(self, simulationTimes): for i in range(simulationTimes): # update model's time self.updateTime(i) terminate = self.step() if terminate: break # output log file to disk self.log.outputLog('result/simResult.txt') simulatedResult = self.datacollector.get_model_vars_dataframe() return simulatedResult # update model's clock def updateTime(self, t): self.time = t def setTerminal(self): assert self.terminate == False self.terminate = True def addRecord(self, msg): self.log.add(msg) # for degub purpose only def outputAdjMat(self, path): with open(path, 'w') as fid: for line in self.adjMat: # convert list of boolean values to string values tline = ["1" if item else "0" for item in line] fid.write(' '.join(tline) + '\n')
class ForestFire(Model): """ Simple Forest Fire model. """ def __init__(self, height=100, width=100, density=0.65, server=True, num_steps=1000): """ Create a new forest fire model. Args: height, width: The size of the grid to model density: What fraction of grid cells have a tree in them. """ # Initialize model parameters self.height = height self.width = width self.density = density self.server = server # Set up model objects self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=False) self.num_steps = num_steps self.datacollector = DataCollector({ "Fine": lambda m: self.count_type(m, "Fine"), "On Fire": lambda m: self.count_type(m, "On Fire"), "Burned Out": lambda m: self.count_type(m, "Burned Out") }) # Place a tree in each cell with Prob = density for (contents, x, y) in self.grid.coord_iter(): if self.random.random() < self.density: # Create a tree new_tree = TreeCell((x, y), self) # Set all trees in the first column on fire. if x == 0: new_tree.condition = "On Fire" self.grid._place_agent((x, y), new_tree) self.schedule.add(new_tree) self.running = True self.datacollector.collect(self) def step(self): """ Advance the model by one step. """ self.schedule.step() # collect data self.datacollector.collect(self) # Halt if no more fire if self.count_type(self, "On Fire") == 0: self.running = False @staticmethod def count_type(model, tree_condition): """ Helper method to count trees in a given condition in a given model. """ count = 0 for tree in model.schedule.agents: if tree.condition == tree_condition: count += 1 return count def run_model(self, n=None, export_agent_data=False, export_model_data=False): if self.server == False: if not n: for _ in range(self.num_steps): self.step() if export_agent_data: return self.datacollector.get_agent_vars_dataframe() elif export_model_data: return self.datacollector.get_model_vars_dataframe() elif export_model_data and export_agent_data: return self.datacollector.get_model_vars_dataframe, self.datacollector.get_agent_vars_dataframe else: self.num_steps = n for _ in range(self.num_steps): self.step() # if export_agent_data: # return self.datacollector.get_agent_vars_dataframe() # elif export_model_data: # return self.datacollector.get_model_vars_dataframe() # elif export_model_data == True and export_agent_data == True: # return self.datacollector.get_model_vars_dataframe, self.datacollector.get_agent_vars_dataframe return self else: from .server import server server.launch()
class BankReservesModel(Model): # grid height grid_h = 20 # grid width grid_w = 20 """init parameters "init_people", "rich_threshold", and "reserve_percent" are all UserSettableParameters""" def __init__( self, height=grid_h, width=grid_w, init_people=2, rich_threshold=10, reserve_percent=50, ): self.height = height self.width = width self.init_people = init_people self.schedule = RandomActivation(self) self.grid = MultiGrid(self.width, self.height, torus=True) # rich_threshold is the amount of savings a person needs to be considered "rich" self.rich_threshold = rich_threshold self.reserve_percent = reserve_percent # see datacollector functions above self.datacollector = DataCollector( model_reporters={ "Rich": get_num_rich_agents, "Poor": get_num_poor_agents, "Middle Class": get_num_mid_agents, "Savings": get_total_savings, "Wallets": get_total_wallets, "Money": get_total_money, "Loans": get_total_loans }, agent_reporters={"Wealth": lambda x: x.wealth}) # create a single bank for the model self.bank = Bank(1, self, self.reserve_percent) # create people for the model according to number of people set by user for i in range(self.init_people): # set x coordinate as a random number within the width of the grid x = random.randrange(self.width) # set y coordinate as a random number within the height of the grid y = random.randrange(self.height) p = Person(i, (x, y), self, True, self.bank, self.rich_threshold) # place the Person object on the grid at coordinates (x, y) self.grid.place_agent(p, (x, y)) # add the Person object to the model schedule self.schedule.add(p) self.running = True def step(self): # collect data self.datacollector.collect(self) # tell all the agents in the model to run their step function self.schedule.step() # if the step count is in the list then create a data file of model state if self.schedule.steps in [100, 500, 1000]: model_data = self.datacollector.get_model_vars_dataframe() model_data.to_csv("BankReservesModel_Step_Data_Single_Run" + str(self.schedule.steps) + ".csv") def run_model(self): for i in range(self.run_time): self.step()
class MModel(Model): #A model with some number of agents # Inherits 'Model' from Mesa, and 'Location' from other class #this allow instances of this class to run the 'Location' methods def __init__(self, N, width, height): super().__init__() self.num_agents = N self.grid = MultiGrid(width, height, True) self.schedule = RandomActivation(self) self.running = True # create the agents for i in range(self.num_agents): a = MAgent(i, self) self.schedule.add(a) # add agents to random point on grid x = random.randrange(self.grid.width) y = random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) print('place agent in pos', a.pos) #collect data for charts self.datacollector = DataCollector( agent_reporters={ "Wealth": "wealth", "Location": "xy" }, # An agent level stat collector model_reporters={ 'Total_Wealth_All_Models': total_wealth, 'Assignment': assignments }) # model level stat collector def pay(self): # obtain the amounts of agents per pool, work out how much they are paid #then assign new $$ to them based on the pool they are in model_wealth = self.datacollector.get_model_vars_dataframe() assign = model_wealth['Assignment'].iloc[-1] #print ( 'There are the numbers of agents per poll', assign) p = Payout(assign) self.pay_list = p.pay_out() #print ( 'The is amount agents receive on a per pool Payout', self.pay_list) for agent in self.schedule.agents: #print (agent.unique_id, agent.wealth, agent.pos , 'agents stats') if agent.pos == (1, 0): #Low Pool agent.wealth = agent.wealth + self.pay_list[0] if agent.xy == (2, 0): #Stable Pool agent.wealth = agent.wealth + self.pay_list[1] if agent.xy == (3, 0): #high agent.wealth = agent.wealth + self.pay_list[2] return def step(self): #advance the model by one step self.datacollector.collect(self) self.schedule.step() self.pay() #calculate model per model data each step print('Model Wealth =', model_wealth(self))
class DCGame(Model): def __init__(self, adjMat, G, numVisibleColorNodes, numAdversarialNodes, inertia, beta, delay, visibles, adversaries): self.adjMat = adjMat self.numVisibleColorNodes = numVisibleColorNodes self.numAdversarialNodes = numAdversarialNodes # self.adversarialNodes = [] self.visibleColorNodes = [] self.regularNodes = [] self.schedule = FollowVisibleActivation(self) self.numAgents = len(adjMat) self.inertia = inertia # if there are 20 consensus colors then a # terminal state is reached self.terminate = False self.time = 0 # logging information self.log = Log() ## temporarily added this for figuring out ## why visible nodes have no help self.hasConflict = False # randomize regular players (exclude visibles) # decision self.beta = beta # a amount of time to delay ordinary players' decision # ordinary players = players who are neither visibles # nor has any visibles in their neighbor self.delay = delay # total number of color changes in a game self.colorChanges = 0 # addded by Yifan self.reach_of_adversaries = 0 self.reach_of_visibles = 0 self.total_cnt_of_adversaries = 0 self.total_cnt_of_visibles = 0 self.graph = G # convert adjMat to adjList def getAdjList(adjMat): adjList = {key: [] for key in range(self.numAgents)} for node in range(self.numAgents): #adjList[node] = [idx for idx, value in enumerate(adjMat[node]) if value == True] adjList[node] = [ idx for idx, value in enumerate(adjMat[node]) if value == 'True' ] return adjList self.adjList = getAdjList(self.adjMat) #return the subset of L availableNodes in G with the largest number of distinct neighbors def getSubsetWithMaxDistinctNeighbors(availableNodes, G, L): acc = [] max_cnt = 0 local_cnt = 0 hasBeenConsidered = [False for i in range(self.numAgents)] graph = nx.convert.to_dict_of_lists(G) for subset in itertools.combinations(availableNodes, L): upper_bound = 0 for agent in subset: upper_bound += len(graph[agent]) if upper_bound < max_cnt: continue # compute reach for agent in subset: for neighbor in G.neighbors(agent): if neighbor not in subset and hasBeenConsidered[ neighbor] == False: local_cnt += 1 hasBeenConsidered[neighbor] = True if local_cnt > max_cnt: max_cnt = local_cnt acc.clear() for agent in subset: acc.append(agent) local_cnt = 0 hasBeenConsidered = [False for i in range(self.numAgents)] return acc ############# designate visible ############# # node_deg = [(idx, count(adjMat[idx])) for idx in range(self.numAgents)] # availableNodes = [item[0] for item in node_deg] # random.shuffle(availableNodes) # availableNodes.sort(key=lambda x : x) # self.visibleColorNodes = getSubsetWithMaxDistinctNeighbors(availableNodes, G, numVisibleColorNodes) # self.visibleColorNodes = [item for item in availableNodes[:self.numVisibleColorNodes]] self.visibleColorNodes = visibles # for visibleNode in self.visibleColorNodes: # availableNodes.remove(visibleNode) ############# designate adversarial ############### # self.adversarialNodes = getSubsetWithMaxDistinctNeighbors(availableNodes, G, numAdversarialNodes) # self.adversarialNodes = [item for item in availableNodes[:self.numAdversarialNodes]] self.adversarialNodes = adversaries # ================ prev version: designate adversarial and visible nodes =========== # node_deg = [(idx, count(adjMat[idx])) for idx in range(self.numAgents)] # all_nodes = [item[0] for item in node_deg] # random.shuffle(node_deg) # self.adversarialNodes = [item[0] for item in node_deg[:self.numAdversarialNodes]] # reach_of_adversaries = 0 # total_cnt_of_adversaries = 0 # hasBeenReached = dict.fromkeys(all_nodes, False) # for adversarialNode in self.adversarialNodes: # for neighbor in G.neighbors(adversarialNode): # if neighbor not in self.adversarialNodes: # total_cnt_of_adversaries += 1 # if neighbor not in self.adversarialNodes and hasBeenReached[neighbor] == False: # reach_of_adversaries += 1 # hasBeenReached[neighbor] = True # self.reach_of_adversaries = reach_of_adversaries # self.total_cnt_of_adversaries = total_cnt_of_adversaries # ############# designate visible nodes ############# # availableNodes = shuffled(node_deg[self.numAdversarialNodes:]) # self.visibleColorNodes = [item[0] for item in availableNodes[:self.numVisibleColorNodes]] # reach_of_visibles = 0 # total_cnt_of_visibles = 0 # hasBeenReached = dict.fromkeys(all_nodes, False) # for visibleColorNode in self.visibleColorNodes: # for neighbor in G.neighbors(visibleColorNode): # if neighbor not in self.adversarialNodes and neighbor not in self.visibleColorNodes: # total_cnt_of_visibles += 1 # if neighbor not in self.adversarialNodes and neighbor not in self.visibleColorNodes and hasBeenReached[neighbor] == False: # reach_of_visibles += 1 # hasBeenReached[neighbor] = True # self.reach_of_visibles = reach_of_visibles # self.total_cnt_of_visibles = total_cnt_of_visibles # =============================== self.regularNodes = [ n for n in range(self.numAgents) if n not in self.adversarialNodes ] # make sure we have 20 regular nodes # assert len(self.regularNodes) ==20 assert set(self.adversarialNodes) & set( self.visibleColorNodes) == set() # adversarial nodes and regular nodes should not overlap assert set(self.adversarialNodes) & set(self.regularNodes) == set() # visible nodes should belong to regular nodes assert set(self.visibleColorNodes) & set(self.regularNodes) == set( self.visibleColorNodes) # logging simulation configuration self.log.add("#visible nodes: " + str(self.visibleColorNodes)) self.log.add("#adversarial nodes: " + str(self.adversarialNodes)) self.log.add("#regular nodes: " + str(self.regularNodes) + '\n') ############# initialize all agents ############# for i in range(self.numAgents): # if i is a visible node isVisibleNode = i in self.visibleColorNodes # if i is an adversarial isAdversarial = i in self.adversarialNodes # make sure adversarial nodes are not intersected with visible nodes assert isVisibleNode & isAdversarial == False neighbors = self.adjList[i] # visible color nodes in i's neighbors vNode = list(set(neighbors) & set(self.visibleColorNodes)) inertia = self.inertia beta = self.beta # print("Add agent:", (i, visibleNode, adversarial, neighbors, visibleColorNodes)) a = GameAgent(i, isVisibleNode, isAdversarial, neighbors, vNode, inertia, beta, self) self.schedule.add(a) # instantiate all nodes' neighbors and visibleColorNodes for agent in self.schedule.agents: agent.instantiateNeighbors(self) agent.instantiateVisibleColorNodes(self) self.datacollector = DataCollector( model_reporters={ "red": getRed, "green": getGreen }, agent_reporters={"agent_color": lambda a: a.color}) def getReachOfAdversaries(self): return self.reach_of_adversaries def getReachOfVisibles(self): return self.reach_of_visibles def getTotalCntOfAdversaries(self): return self.total_cnt_of_adversaries def getTotalCntOfVisibles(self): return self.total_cnt_of_visibles # simulate the whole model for one step def step(self): # # # if either red or green reaches consensus, terminates! # # in terminal state we do not collect data if not self.terminate: self.datacollector.collect(self) self.schedule.step(self.delay) return self.terminate def simulate(self, simulationTimes): for i in range(simulationTimes): # update model's time # print("simulation time: " + str(i)) self.updateTime(i) terminate = self.step() if terminate: break #added by Yifan isRegWhite = False # output log file to disk if not terminate: # did not reach consensus for agent in self.schedule.agents: if not agent.isAdversarial and not agent.isVisibleNode and agent.color == "white": #at least one regular player remained white isRegWhite = True self.log.outputLog('result/simResult.txt') simulatedResult = self.datacollector.get_model_vars_dataframe() # print(simulatedResult) return (simulatedResult, isRegWhite) # update model's clock def updateTime(self, t): self.time = t def setTerminal(self): assert self.terminate == False self.terminate = True def addRecord(self, msg): self.log.add(msg) # for degub purpose only def outputAdjMat(self, path): with open(path, 'w') as fid: for line in self.adjMat: # convert list of boolean values to string values tline = ["1" if item else "0" for item in line] fid.write(' '.join(tline) + '\n')
class WorldModel(Model): """ Model for representing the world """ def __init__(self): """ Create a new WorldModel with the given parameters """ super().__init__() # Read config.cfg config = configparser.ConfigParser() config.read('./config.ini') self.background_image_source = config.get('Grid', 'image', fallback='./delivery/visualization/images/a_city500x500.jpg') if type(self.background_image_source) is not str: print("[Configuration] The image is not valid.") sys.exit(1) try: test_background_image_source = self.background_image_source.split("/").pop() test_background_image_source = test_background_image_source.split(".") if test_background_image_source[len(test_background_image_source) - 1] != "jpg": raise ValueError except Exception: print("[Configuration] The image is not valid.") sys.exit(1) # Read landscape try: background_image = Image.open(self.background_image_source) background = background_image.load() except FileNotFoundError: print("[Configuration] The image could not be found.") sys.exit(1) # Configure schedule for UAVs and BaseStations self.schedule = Schedule(self) # Configure schedule for items self.item_schedule = Schedule(self) # Set parameters for ... # ... Grid self.width, self.height = background_image.size try: self.pixel_ratio = config.getint('Grid', 'pixel_ratio', fallback=10) except ValueError: print("[Configuration] The pixel_ratio is not valid.") sys.exit(1) try: self.max_altitude = config.getint('Grid', 'max_altitude', fallback=4) except ValueError: print("[Configuration] The max_altitude is not valid.") sys.exit(1) # ... BaseStations try: self.range_of_base_station = config.getint('Base_station', 'range_of_base_station', fallback=125) except ValueError: print("[Configuration] The range_of_base_station is not valid.") sys.exit(1) try: self.number_of_uavs_per_base_station = config.getint('UAV', 'number_of_uavs_per_base_station', fallback=2) except ValueError: print("[Configuration] The number_of_uavs_per_base_station is not valid.") sys.exit(1) # ... UAV try: self.max_charge = config.getint('UAV', 'max_charge', fallback=1000) except ValueError: print("[Configuration] The max_charge is not valid.") sys.exit(1) try: self.battery_low = config.getint('UAV', 'battery_low', fallback=500) except ValueError: print("[Configuration] The battery_low is not valid.") sys.exit(1) try: self.battery_decrease_per_step = config.getint('UAV', 'battery_decrease_per_step', fallback=1) except ValueError: print("[Configuration] The battery_decrease_per_step is not valid.") sys.exit(1) try: self.battery_increase_per_step = config.getint('UAV', 'battery_increase_per_step', fallback=10) except ValueError: print("[Configuration] The battery_increase_per_step is not valid.") sys.exit(1) try: self.sensor_range = config.getint('UAV', 'sensor_range', fallback=5) except ValueError: print("[Configuration] The sensor_range is not valid.") sys.exit(1) # Counter for number of steps self.steps = 0 # Store the agent that should be send to the client for more details self.details_for = None # Create the StaticGrid that contains the landscape (obstacles, base stations, ...) self.landscape = StaticGrid(self.width, self.height, background) # Add data collector self.datacollector = DataCollector( { "UAVS": lambda m: m.schedule.get_type_count(Uav), "Items (Waiting)": self.compute_number_of_items, "Items (Picked up)": self.compute_number_of_picked_up_items, "Items (Delivered)": self.compute_number_of_delivered_items, "Average Delivery Walk Length": self.compute_average_walk_length, "Standard Deviation of Average Walk Lengths": self.compute_standard_deviation_walk_lengths, "Walklength Divided by Distance": self.compute_walk_length_divided_by_distance, "Average lifetime of item": self.compute_item_average_lifetime, } ) # In the beginning there are no delivered Items self.number_of_delivered_items = 0 try: # Populate the grid with obstacles and BaseStations and UAVs self.populate_grid() except RuntimeError as error: print(error) sys.exit(1) self.running = True def step(self): """ Advance the model one step """ print("Step {}".format(self.steps)) self.schedule.step() # Increase number of steps self.steps += 1 self.item_schedule.step() self.datacollector.collect(self) dataframe = self.datacollector.get_model_vars_dataframe() dataframe.to_csv('out.csv') def populate_grid(self): """ Populate the grid with obstacles, BaseStations and UAVs """ # Populate the background with static obstacles self.landscape.populate_grid() image = Image.new("RGBA", (self.width, self.height)) for x in range(0, self.width): for y in range(0, self.height): image.putpixel((x, self.height - y - 1), self.landscape.get_obstacle_color((x, y))) image.putpixel((x, self.height - 1), self.landscape.get_obstacle_color((x, 1))) file_name = self.background_image_source.split("/").pop() new_file_name = file_name.split(".") new_file_name.pop() new_file_name = new_file_name.pop() new_file_name += "_obstacles.png" image.save("./delivery/visualization/images/" + new_file_name) print("Obstacles done") # Create base stations base_stations = self.create_base_stations() print("BaseStations done") # Create UAVs uid = 0 for base_station in base_stations: for i in range(self.number_of_uavs_per_base_station): uid += 1 self.create_uav(uid, base_station) print("UAVs done") def create_base_stations(self): """ Calculate how many BaseStations need to be created and create them :returns A list of BaseStations """ base_stations = [] width = 2 * self.range_of_base_station height = 2 * self.range_of_base_station number_of_base_stations = int((self.width * self.height) / (width * height)) x = width y = height for i in range(0, number_of_base_stations): base_stations.append(self.create_base_station(i, (round(x - self.range_of_base_station), round(y - self.range_of_base_station)))) if x + width > self.width: y += height x = width else: x += width return base_stations def create_base_station(self, bid, pos): """ Create a BaseStation at a given position or close to it :param bid: unique identifier of the BaseStation :param pos: Tuple of coordinates :return The created BaseStation """ x, y = pos # Store available cells available_cells = set() # To check if the coordinates are already stored available_cells_helper = set() radius = 1 # If the center is an empty cell while not available_cells: # ... get neighboring cells and center cell neighborhood = self.landscape.get_neighborhood(pos, True, radius) # ... search the neighborhood and center for coordinates in neighborhood: # ... check if there is an obstacle for altitude in range(self.max_altitude, 0, -1): if self.landscape.is_obstacle_at_exact(coordinates, altitude): if coordinates not in available_cells_helper: # ... and add the cell to the list of available cells if at least one neighboring cell is not # filled with an obstacle temp_neighborhood = self.landscape.get_neighborhood(coordinates, False, 1) for temp_coordinates in temp_neighborhood: if not self.landscape.is_obstacle_at_exact(temp_coordinates, altitude): available_cells.add(coordinates + (altitude,)) available_cells_helper.add(coordinates) # Increase the search radius if there are no possible cells radius += 1 if radius > self.range_of_base_station: raise RuntimeError( 'There is no obstacle that fulfills the requirement to be a valid location for a base ' 'station. A base station needs to be place on top of an obstacle and has to have at least ' 'one neighboring cell that is not occupied by an obstacle.') # If there are available cells, choose one at random pos_x, pos_y, pos_z = random.sample(available_cells, 1)[0] # Create the BaseStation base_station = BaseStation(model=self, pos=(pos_x, pos_y, pos_z), bid=bid, center=(x, y), range_of_base_station=self.range_of_base_station) # Place the BaseStation on the landscape self.landscape.place_base_station((pos_x, pos_y)) # Add the BaseStation to the schedule self.schedule.add(base_station) return base_station def create_uav(self, uid, base_station): """ Create a UAV :param uid: unique identifier of the Uav :param base_station: the assigned BaseStation """ pos_x, pos_y, pos_z = base_station.get_pos() # Create the UAV position = (pos_x, pos_y, pos_z) uav = Uav(self, pos=position, uid=uid, max_charge=self.max_charge, battery_low=self.battery_low, base_station=base_station, battery_decrease_per_step=self.battery_decrease_per_step, battery_increase_per_step=self.battery_increase_per_step, max_altitude=self.max_altitude, sensor_range=self.sensor_range) # Add the UAV to the schedule self.schedule.add(uav) def get_details_for(self, pos): """ Pick an agent based on the position :param pos: Tuple of coordinates (not normalized) :return: An agent, if there is an agent at the requested position. Otherwise, None """ pos_x, pos_y = pos pos_x = math.floor(pos_x / self.pixel_ratio) pos_y = math.floor(pos_y / self.pixel_ratio) # Search for BaseStations for baseStation in self.schedule.agents_by_type[BaseStation]: if pos_x == baseStation.pos[0] and pos_y == baseStation.pos[1]: return baseStation # Search for UAVs for UAV in self.schedule.agents_by_type[Uav]: if pos_x == UAV.pos[0] and pos_y == UAV.pos[1]: return UAV # Search for Items for item in self.item_schedule.agents_by_type[Item]: if pos_x == item.pos[0] and pos_y == item.pos[1]: return item return None @staticmethod def compute_number_of_items(model): """ Compute the number of items that are currently in a base station :return: number of items located in all base stations """ number_of_items = 0 for base_station in model.schedule.agents_by_type[BaseStation]: number_of_items += base_station.get_number_of_items() return number_of_items @staticmethod def compute_number_of_picked_up_items(model): """ Compute the number of items that are currently delivered :return: number of items that are currently delivered """ number_of_picked_up_items = 0 for base_station in model.schedule.agents_by_type[BaseStation]: number_of_picked_up_items += base_station.get_number_of_items(picked_up=True) return number_of_picked_up_items @staticmethod def compute_number_of_delivered_items(model): """ Computer the number of items that are already delivered :param model: The model that the calculation is for :return: Number of items that are already delivered """ return model.number_of_delivered_items @staticmethod def compute_average_walk_length(model): """ Compute the average walk length for all UAVs :param model: The model that the calculation is for :return: The average walk length """ average_walks = [] for uav in model.schedule.agents_by_type[Uav]: for elem in uav.get_walk_lengths(): average_walks.append(elem) if len(average_walks) > 0: return sum(average_walks) / len(average_walks) else: return 0 @staticmethod def compute_standard_deviation_walk_lengths(model): """ Compute the standard deviation in walk lengths of all UAVs :param model: The model that the calculation is for :return: The standard deviation of all walk length """ walks = [] for uav in model.schedule.agents_by_type[Uav]: for elem in uav.get_walk_lengths(): walks.append(elem) if len(walks) > 0: return np.std(walks) else: return 0 @staticmethod def compute_walk_length_divided_by_distance(model): """ Compute the ratio between the actual walk and the initial calculated distance :param model: The model that the calculation is for :return: The ratio between the actual walk and the initial distance """ initial_length_by_distance = [] for uav in model.schedule.agents_by_type[Uav]: for elem in uav.get_walk_length_divided_by_initial_distance(): initial_length_by_distance.append(elem) if len(initial_length_by_distance) > 0: return sum(initial_length_by_distance) / len(initial_length_by_distance) else: return 0 @staticmethod def compute_item_average_lifetime(model): """ Compute the average lifetime of an Item :param model: The model that the calculation is for :return: The average lifetime of an Item """ result = 0 if not model.item_schedule.agents_by_type[Item] == []: for item in model.item_schedule.agents: result = result + item.lifetime return result / len(model.item_schedule.agents) else: return 0
class DCGame(Model): def __init__(self, adjMat, visibles, adversaries): self.adjMat = adjMat #matrix that keeps track of all players and their neighbors self.schedule = SimultaneousActivation( self ) # An activation in which players' states are effectively updated simultaneously as opposed to sequentially self.numAgents = len(adjMat) self.terminate = False # if all non-adversarial players have reached consensus, terminal state is achieved self.time = 0 # logging information self.log = Log() # total number of color changes in a game self.colorChanges = 0 # convert adjMat to adjList def getAdjList(adjMat): adjList = {key: [] for key in range(self.numAgents)} for node in range(self.numAgents): #adjList[node] = [idx for idx, value in enumerate(adjMat[node]) if value == True] adjList[node] = [ idx for idx, value in enumerate(adjMat[node]) if value == 'True' ] return adjList self.adjList = getAdjList(self.adjMat) ############# designate visible, adversarial, and consensus nodes ############# self.visibleColorNodes = visibles self.adversarialNodes = adversaries self.consensusNodes = [ n for n in range(self.numAgents) if n not in self.adversarialNodes ] # adversarial nodes and regular nodes should not overlap assert set(self.adversarialNodes) & set(self.consensusNodes) == set() # visible nodes should belong to regular nodes assert set(self.visibleColorNodes) & set(self.consensusNodes) == set( self.visibleColorNodes) # logging simulation configuration self.log.add("#visible nodes: " + str(self.visibleColorNodes)) self.log.add("#adversarial nodes: " + str(self.adversarialNodes)) self.log.add("#consensus nodes: " + str(self.consensusNodes) + '\n') ############# initialize all agents ############# for i in range(self.numAgents): # if i is a visible node isVisibleNode = i in self.visibleColorNodes # if i is an adversarial isAdversarial = i in self.adversarialNodes # make sure adversarial nodes are not intersected with visible nodes assert isVisibleNode & isAdversarial == False neighbors = self.adjList[i] # visible color nodes in i's neighbors vNode = list(set(neighbors) & set(self.visibleColorNodes)) a = GameAgent(i, isVisibleNode, isAdversarial, neighbors, vNode, self) self.schedule.add(a) # instantiate all nodes' neighbors and visibleColorNodes for agent in self.schedule.agents: agent.instantiateNeighbors(self) agent.instantiateVisibleColorNodes(self) self.datacollector = DataCollector( model_reporters={ "red": getRed, "green": getGreen }, agent_reporters={"agent_color": lambda a: a.color}) # simulate the whole model for one step def step(self): # # # if either red or green reaches consensus, terminates! # # in terminal state we do not collect data if not self.terminate: self.datacollector.collect(self) self.schedule.step() return self.terminate def simulate(self, simulationTimes): for i in range(simulationTimes): self.updateTime(i) # update model's time terminate = self.step() if terminate: break #added by Yifan hasWhitePlayers = False if not terminate: # if consensus was not reached in the simulation for agent in self.schedule.agents: if not agent.isAdversarial and not agent.isVisibleNode and agent.color == "white": #at least one consensus player remained white hasWhitePlayers = True # output log file to disk self.log.outputLog('result/simResult.txt') simulatedResult = self.datacollector.get_model_vars_dataframe() return (simulatedResult, hasWhitePlayers) # update model's clock def updateTime(self, t): self.time = t def setTerminal(self): assert self.terminate == False self.terminate = True def addRecord(self, msg): self.log.add(msg) # for degub purpose only def outputAdjMat(self, path): with open(path, 'w') as fid: for line in self.adjMat: # convert list of boolean values to string values tline = ["1" if item else "0" for item in line] fid.write(' '.join(tline) + '\n')