class ConveyModel(Model): """A model with some number of agents.""" def __init__(self, N, width, height): super().__init__() self.num_agents = N self.grid = MultiGrid(width, height, True) self.schedule = BaseScheduler(self) # Create agents for i in range(self.num_agents): a = ConwayAgent(i, self) # Add the agent to a random grid cell x = random.randrange(self.grid.width) y = random.randrange(self.grid.height) while(len(self.grid.get_cell_list_contents((x,y)))): x = random.randrange(self.grid.width) y = random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) self.schedule.add(a) self.i = i self.datacollector = DataCollector( agent_reporters={"State": lambda a: a.die}) def step(self): self.datacollector.collect(self) new_agents = [] for (x, y) in product(range(self.grid.width), range(self.grid.height)): ns = self.grid.iter_neighbors((x,y), True) neighbors = 0 for n in ns: if(n): neighbors += 1 if(self.grid[x][y]): # live cell if(neighbors < 2): # underpopulation list(self.grid[x][y])[0].die = 1 elif(neighbors > 3): # overpopulation list(self.grid[x][y])[0].die = 1 else: # dead cell if(neighbors == 3): new_agents.append((x, y)) for (x, y) in product(range(self.grid.width), range(self.grid.height)): if self.grid[x][y]: a = list(self.grid[x][y])[0] if a.die: self.grid.remove_agent(a) self.schedule.remove(a) for na in new_agents: self.i += 1 a = ConwayAgent(self.i, self) self.grid.place_agent(a, na) self.schedule.add(a)
class Board(Model): def __init__(self, width, height): self.running = True self.grid = MultiGrid(width, height, False) #true for toroidal grid self.schedule = BaseScheduler(self) self.adversary_pieces = [] self.agent_pieces = [] self.initialize_grid() def initialize_grid(self): ''' Here we initialize the d by d board with pits and pieces ''' #initialize the pits num_pits = (d // 3) - 1 for j in range(1, d - 1): for i in random.sample(list(range(0, d)), d)[0:num_pits]: pit = Pit('pit', self) self.grid.place_agent(pit, (i, j)) #init all pieces num_pieces = d // 3 for j in range(d): j_off = j + 1 if j_off % 3 == 1: piece_adv = Piece('wumpus', self, 'adversary') piece_agent = Piece('wumpus', self, 'agent') elif j_off % 3 == 2: piece_adv = Piece('hero', self, 'adversary') piece_agent = Piece('hero', self, 'agent') else: piece_adv = Piece('mage', self, 'adversary') piece_agent = Piece('mage', self, 'agent') self.grid.place_agent(piece_adv, (j, 0)) self.grid.place_agent(piece_agent, (j, d - 1)) self.adversary_pieces.append(piece_adv) self.agent_pieces.append(piece_agent) def get_adversary_piece(self): ''' Here is where player enters coordinates of piece they want to move. Checks for invalid inputs and protects against them ''' pos = '' agents = [] while not is_valid_position(pos) or agents == [] or agents[ 0].name == 'pit' or agents[0].player == 'agent': print("Enter position of piece you want to move (sep by spaces): ", end='') pos = input() if not is_valid_position(pos): print('invalid argument') continue x, y = int(pos.split()[0]) - 1, int(pos.split()[1]) - 1 agents = list(self.grid[x][y]) if agents != []: if agents[0].name == 'pit': print('cannot select a pit') continue if agents[0].player == 'adversary': piece = agents[0] return piece else: print('You cannot move agent\'s pieces') continue else: print('cannot select empty square') def get_agent_piece_and_move(self): ''' here we will call minimax from minimax.py. This will return the agent piece that will move along with the coordinate it will move to ''' start = time.time() _, _, piece, pos = minimax(self, depth, float('-inf'), float('inf'), True) print('time passed:', time.time() - start) return piece, pos def winner(self): ''' here we will implement the winning condition we will return the value True and the player who won. we want the game to stop when win() == True.. and we will implement this in the step function ''' return self.adversary_pieces == [] or self.agent_pieces == [] def collision_check(self, piece): x, y = piece.pos agents = list(self.grid[x][y]) #pop piece from agents agents.remove(piece) for agent in agents: if agent.name == 'pit': self.grid.remove_agent(piece) self._remove_piece_from_list(piece) elif agent.player != piece.player: if agent.name == piece.name: self.grid.remove_agent(piece) self.grid.remove_agent(agent) self._remove_piece_from_list(piece) self._remove_piece_from_list(agent) elif agent.name in ['hero', 'wumpus' ] and piece.name in ['hero', 'wumpus']: agent_to_remove = agent if agent.name == 'wumpus' else piece self.grid.remove_agent(agent_to_remove) self._remove_piece_from_list(agent_to_remove) elif piece.name in ['mage', 'hero' ] and agent.name in ['mage', 'hero']: agent_to_remove = agent if agent.name == 'hero' else piece self.grid.remove_agent(agent_to_remove) self._remove_piece_from_list(agent_to_remove) elif piece.name in ['mage', 'wumpus' ] and agent.name in ['mage', 'wumpus']: agent_to_remove = agent if agent.name == 'mage' else piece self.grid.remove_agent(agent_to_remove) self._remove_piece_from_list(agent_to_remove) def _remove_piece_from_list(self, piece): if piece.player == 'agent': self.agent_pieces.remove(piece) else: self.adversary_pieces.remove(piece) def distance_to_board(self): #calculates manhattan distance from each piece to all of other pieces then adds all those up #also weights attacking pieces and vulnerable pieces so that agent runs towards #pieces it can capture and runs away from pieces that can capture it opposing_offense = {'wumpus': 'mage', 'mage': 'hero', 'hero': 'wumpus'} opposing_defense = {j: i for i, j in opposing_offense.items()} total = 0 for ag_piece in self.agent_pieces: for adv_piece in self.adversary_pieces: weight = 1 x1, y1 = ag_piece.pos x2, y2 = adv_piece.pos if opposing_offense[ag_piece.name] == adv_piece.name: weight = 2 elif opposing_defense[ag_piece.name] == adv_piece.name: weight = -2 total += weight * (abs(x1 - x2) + abs(y1 - y2)) return total / len(self.agent_pieces) def evaluate(self): #returns the score for the whole board (made for agent's favor) try: return len(self.agent_pieces) - len(self.adversary_pieces) + ( eval_weight / self.distance_to_board()) except: return len(self.agent_pieces) - len(self.adversary_pieces) def step(self): ''' Each time step is called, player enters move and their piece moves. Agent also moves its piece that was returned from get_agent_piece_and_move(). This is essentially two turns of the game: player's turn and agent's turn. ''' if not self.winner(): adversary_piece = self.get_adversary_piece() self.schedule.add(adversary_piece) self.schedule.step() self.collision_check(adversary_piece) self.schedule.remove(adversary_piece) agent_piece, agent_pos = self.get_agent_piece_and_move() if agent_piece != None: agent_piece.move(pos=agent_pos) self.collision_check(agent_piece) else: print('Game Over')
class LandModel(Model): """ Main model """ def __init__(self, percentage_buyers, # Percentage of buyers to be added acording to the available land percentage_high_income, #Percentage that are high income rich_market_reach, # The amount of properties the rich can bid poor_market_reach, # The amount of properties the poor can bid base_price, # The land base price base_income, # The base income for all buyers rich_poor_std, #Standard deviation for the rich and poor num_cities, # The number of cities b, # Constant for the wtp formula urbanization_over_rural_roductivity, # Constant for the probability of selling size, # tuple: (width, height) alpha = 0.5,# The alpha parameter (preference) amenities = "uniform", #The amenities rural_productivity = "uniform", # The rural productivity buyer_mode = cons.BID_SCHEME.BEST, max_epochs = 10, #number of epochs max_urbanization = np.inf # Maximum number of urbanization units ): position = "CENTRAL" #print('') #print('Rich: ' + str(rich_market_reach) + ' Poor: ' + str(poor_market_reach)) #Variables for batch run self.num_epochs = 0 self.max_epochs = max_epochs self.max_urbanization = max_urbanization self.running = True #Scheduler self.schedule = BaseScheduler(self) #Assigns variables to the model self.base_price = base_price self.base_income = base_income self.rich_poor_std = rich_poor_std self.rich_market_reach = rich_market_reach self.poor_market_reach = poor_market_reach self.b = b self.w2 = urbanization_over_rural_roductivity self.w1 = 1 - self.w2 self.epyslon = 0 self.alpha = alpha self.buyer_mode = buyer_mode #Sets up the grid #Size of Grid self.width = size[0] self.height = size[1] self.grid = MultiGrid(self.width, self.height, True) # Multigrid - Agents can share a cell #Assign the shapes self.grid_shape = (self.width, self.height) #Distribution variables self.amenities = self.get_distribution(amenities) self.rural_productivity = self.get_distribution(rural_productivity) #Starts the patches as an empty matix self.patches = np.empty((self.width, self.height), dtype = object) #Invokes the city generator # Checks if will import from raster if str(num_cities).upper() == 'RASTER': centers, city_fun = CityGenerator.import_from_raster(self.grid_shape) else: # Handles the city creation (at random) and the corresponding centers centers, city_fun = CityGenerator.make_cities_fun(self.grid_shape, num_cities = num_cities, position = position) self.city_centers = centers #Calculates max city distance (for the city proximity function) self.max_city_distance = self.get_max_city_distance() #Starts land patches and city pathces (both are sets to guarantee O(1) lookup) self.land_patches = set() self.city_patches = set() #Iterates over each possible patch and assigns city or land for index in list(itertools.product(range(self.width), range(self.height))): patch = LandPatch("land_patch_" + str(index), self) patch.type = city_fun(index) if(patch.type == cons.LAND_TYPES.CITY): self.city_patches.add(patch) patch.convert_to_city() else: self.land_patches.add(patch) patch.convert_to_land() self.grid.place_agent(patch, index) self.patches[index] = patch patch.set_properties() # Initializes patch #The number of sellers will be the number of land patches num_sellers = len(self.land_patches) self.percentage_buyers = percentage_buyers if(percentage_buyers <= 0 or percentage_buyers > 100): raise ValueError('Percentage of buyers is incorrect: ' + str(percentage_buyers)) # Calculates the number of buyers self.percentage_high_income = percentage_high_income self.num_buyers = np.round((self.percentage_buyers/100)*len(self.land_patches)) # Sets up the number of poor and rich buyers self.num_rich_buyers = int(self.num_buyers*self.percentage_high_income/100) self.num_poor_buyers = int(self.num_buyers*(100- self.percentage_high_income)/100) #Number of Agents self.num_sellers = num_sellers self.num_buyers = self.num_rich_buyers + self.num_poor_buyers self.num_agents = self.num_sellers + self.num_buyers self.buyers = set() self.sellers = set() #To remove # At the end of each iteration will remove the sellers who sold self.to_remove = [] # adds the sellers (one for each land patch) added_sellers = 0 for pat in self.land_patches: seller = Seller("seller_" + str(added_sellers), self, pat) self.schedule.add(seller) #Adds the seller self.grid.place_agent(seller, pat.pos) self.sellers.add(seller) added_sellers += 1 # Adds the buyers self.add_rich_buyers(self.num_rich_buyers) self.add_poor_buyers(self.num_poor_buyers) # The data collector # For plotting oprions self.current_bids = [] self.datacollector = DataCollector( model_reporters={"Amenities": get_mean_amenities}) def step(self): """ Method that simulates the step of an epoch """ #Scehdule #print('Step ' + str(self.num_epochs)) #Calculates the sellers that will sell market = set() for seller in self.sellers: if(seller.will_sell()): market.add(seller) if(len(market) == 0): print('Nobody wants to sell') #All the buyer make their bids for buyer in self.buyers: buyer.make_bids(market, self.buyer_mode) #Asjust Epsylon self.epyslon = (len(self.buyers) - len(self.sellers))/(len(self.buyers) + len(self.sellers)) #Randomly, buyers and sellers interact sellers = random.sample(self.sellers, len(self.sellers)) for seller in sellers: sold = seller.sell() if(sold): #Removes seller self.grid._remove_agent(seller.pos, seller) self.sellers.remove(seller) self.current_bids = [] #removes all buyers self.remove_all_buyers() #Updates the number of buyers for next epoch self.num_buyers = np.round((self.percentage_buyers/100)*len(self.land_patches)) # Sets up the number of poor and rich buyers self.num_rich_buyers = int(self.num_buyers*self.percentage_high_income/100) self.num_poor_buyers = int(self.num_buyers*(100- self.percentage_high_income)/100) self.num_buyers = self.num_rich_buyers + self.num_poor_buyers #Adds the new generation self.add_rich_buyers(self.num_rich_buyers) self.add_poor_buyers(self.num_poor_buyers) #Collects data self.datacollector.collect(self) #Adjust progress variables (for batch process) self.num_epochs += 1 if(self.num_epochs == self.max_epochs or self.get_num_patches() == 0): self.running = False #Stops the process if the maximum urbanization limit is exceeded if(len(self.city_patches) >= self.max_urbanization): print(self.num_epochs) self.running = False def get_max_city_distance(self): """ Calculates the max city distance available in the greed (recall there can be multiple cities) """ indices = list(itertools.product(range(self.width),range(self.height))) positions = np.array(indices) centers = np.array(self.city_centers) dist_centers = metrics.pairwise.euclidean_distances(positions,centers) dist_centers = np.min(dist_centers, axis = 1) return(np.max(dist_centers)) def get_available_pos(self): """ Gets an available position (so agent don't end up in a city patch) Was design for when the agents bought around them. """ if(self.land_patches): land_patch = random.sample(self.land_patches,1)[0] return(land_patch.pos) return(None) def get_available_positions(self, k, replace = False): choices = np.random.choice(list(self.land_patches), size = k, replace = replace) return([land_patch.pos for land_patch in choices]) def get_num_patches(self): return(len(self.land_patches)) def remove_all_buyers(self): """ Removes all buyers from the model. At the end of every epoch, this method is called """ for agent in self.buyers: self.grid._remove_agent(agent.pos, agent) self.schedule.remove(agent) self.buyers = set() def add_rich_buyers(self, num_rich_buyers): """ Add rich buyers to the grid """ indices = self.get_available_positions(num_rich_buyers) for i in range(num_rich_buyers): income = max(0,(self.base_income + np.abs(np.random.normal(loc = 0, scale = self.rich_poor_std)))) buyer = Buyer("buyer_rich_" + str(i), self, income = income, market_reach = self.rich_market_reach, type = cons.AGENT_TYPES.RICH) self.schedule.add(buyer) #Gets an available position index = indices[i] #Adds the buyer self.grid.place_agent(buyer, index) self.buyers.add(buyer) def add_poor_buyers(self, num_poor_buyers): """ Add poor buyers to the grid """ indices = self.get_available_positions(num_poor_buyers) for i in range(num_poor_buyers): income = max(0,(self.base_income - np.abs(np.random.normal(loc = 0, scale = self.rich_poor_std)))) buyer = Buyer("buyer_poor_" + str(i), self, income = income, market_reach = self.poor_market_reach, type = cons.AGENT_TYPES.POOR) self.schedule.add(buyer) #Gets an available position index = indices[i] #Adds the buyer self.grid.place_agent(buyer, index) self.buyers.add(buyer) def get_distribution(self, name): """ Gets the corresponding function depending on the distribution """ if("CONSTANT" in str(name).upper()): name = name.replace(" ","") values = name.split("=") if(len(values) != 2): raise ValueError("If function is constant, should specify its value") constant = float(values[1]) return(lambda x: constant ) if(str(name).upper() == "UNIFORM"): return(lambda x: np.random.uniform()) if(str(name).upper() == "RASTER_AMENITIES"): return(CityGenerator.amenities_from_raster(self.grid_shape)) raise ValueError("No implementation for the distribution: " + str(name)) def get_tract_info(self, tract = 4): matrix = self.get_sold_type_matrix() array = get_tract_structure(matrix, tract) n = len(array) r = np.apply_along_axis(lambda x: np.sum(x == cons.AGENT_TYPES.RICH), 0, array) R = np.sum(matrix == cons.AGENT_TYPES.RICH) p = np.apply_along_axis(lambda x: np.sum(x == cons.AGENT_TYPES.POOR), 0, array) P = np.sum(matrix == cons.AGENT_TYPES.POOR) return(n,r,R,p,P) def get_sold_type_matrix(self): #Extract the matrix with the values matrix = np.array([[ self.patches[i,j].sold_type for j in range(self.patches.shape[1])] for i in range(self.patches.shape[0])]) return(matrix)
class CivModel(Model): def __init__(self, N): self.nb_agents = N # Nombre d'agents self.schedule = BaseScheduler(self) # Timeline basic # Initializations spatials des agents positions = self.spawn_clusters() #print(positions, len(positions)) for i in range(self.nb_agents): # positionne aléatoirement l'agent sur la grille agent = CivAgent(i, self) agent.x, agent.y, agent.z = positions[i] self.schedule.add(agent) # ajoute les agents à la timeline # Calcules les distances entre les agents self.distances_log = self.calculate_distance() # Timeline self.timeline = 0 self.connection_logs = {} # connections effectuées dict[tour[int] : list[connections]] self.removed_agents = {} # agents enlevés dict[tour[int] : list[agents]] # Chaîne de suspicion self.suspicions = {} # key : (agent1, agent2), value : suspicion_cooldown # Historique self.historique = {} # dict[tour[int], list[agents]] self.historique[0] = list(self.schedule._agents.values()) ### Utilitaires ############################################################### def remove_store(self, agent_id): """We use this function instead of the mesa remove() implemented method. It allows storing after supression""" self.removed_agents[self.timeline] = [] self.removed_agents[self.timeline].append( self.schedule._agents[agent_id]) self.schedule.remove(self.schedule._agents[agent_id]) def restitute_agents(self, time): """Permet de revenir à l'état du model à l'étape 'time'""" old_state = self.schedule._agents to_add = [v for k, v in self.removed_agents.items() if k >= time] # list[list]->list[int] n = [] for i in to_add: n += i to_add = n # print(to_add) for agent in to_add: old_state[agent.unique_id] = agent # print(old_state.keys()) return old_state ############################################################################### ### Localisation ############################################################## def spawn_clusters(self): """Répartis les civilisations en clusters de densité égale dans l'univers (peut être assimiler à des galaxies)""" coords = [] for i in range(nb_clusters): clx, cly, clz = random.randint(0, univers_scale), random.randint( 0, univers_scale), random.randint(0, univers_scale) # Créer un point de cluster # Créé un nombres de groupe d'agent équivalent à celui des clusters rd_nb_civ = int(self.nb_agents/nb_clusters)+1 # Répartis aléatoiremnt les civilisations autour de ce clusters dans un radius donné. for j in range(rd_nb_civ): r = clusters_scale pl_x, pl_y, pl_z = random.randint(max( 0, clx-r), min(univers_scale, clx+r)), random.randint(max(0, cly-r), min(univers_scale, cly+r)), random.randint(max(0, clz-r), min(univers_scale, clz+r)) coords.append((pl_x, pl_y, pl_z)) return coords def sort_dict(self, dic, first_term=False): """Tri le dictionnaire de distance de la valeur la plus grande à la plus faible""" sorted_dict = {} if first_term: for i in range(self.nb_agents): sorted_dict.update({k: v for k, v in sorted(dic.items(), key=lambda item: item[1]) if i is k[0]}) else: sorted_dict = {k: v for k, v in sorted( dic.items(), key=lambda item: item[1])} return sorted_dict def calculate_distance(self): """Créé un dictionnaire dont les keys sont la pair d'agent en question la la value la distance les séparant""" distances_log = {} for i in self.schedule._agents: #print(distances_log, len(distances_log)) if i in self.schedule._agents.keys(): agentA = self.schedule._agents[i] for j in range(i+1, len(self.schedule._agents)): if j in self.schedule._agents.keys(): agentB = self.schedule._agents[j] d = math.sqrt((agentA.x-agentB.x)**2 + (agentA.y-agentB.y)**2 + (agentA.z-agentB.z)**2) distances_log[(i, j)] = d return distances_log ############################################################################### ### Connection inter-agents ################################################### def random_connect(self, methodeTheo=False, seed=0): """Renvoie une liste de pair d'agent dans l'orde aléatoire""" random_id = random.sample( [k for k in range(len(list(self.schedule._agents)))], len(list(self.schedule._agents))) n_l = list(range(len(list(self.schedule._agents)))) connection = list(zip(n_l, random_id)) # print(connection) return connection def nn_connect(self): """Connect chaque agent avec les autres en partant du plus proches""" distances_dict = self.calculate_distance() for i in range(self.nb_agents): order_of_connection = self.sort_dict(distances_dict) # print(order_of_connection) return order_of_connection.keys() ############################################################################### ### Detection ################################################################# def detect_distance_check(self, distance, opacity_factor, threshold): """Utilise la fonction exp(-kx) pour simuler la difficulté des civilisations à communiquer par rapport à la distance qui les sépare""" d_scaled = distance/univers_scale return math.exp(-opacity_factor*d_scaled) def detect(self, agentA, agentB, both=True): """Renvoie si oui ou non l'agent a detect l'agent b Fonctionnement du système émission/réception: Plus un agent émet de signaux, plus il est repérable. Le niveau de reception définit la capacité d'un agent à détecter des signaux. Par conséquent plus le niveau d'emission d'un agent est haut plus il est facilement reperable par des agents dont le niveau de reception est bas. """ if agentA.reception == 0: return False # Cas ou reception = 0, L'agent ne peut rien reperer print("Agent", self.schedule._agents, "ne peut rien reperer") else: a = self.detect_distance_check(self.distances_log[(min(agentA.unique_id, agentB.unique_id), max( agentA.unique_id, agentB.unique_id))], opacity_factor, threshold) if not a >= threshold: print("Trop loin...") print(self.distances_log[(min(agentA.unique_id, agentB.unique_id), max( agentA.unique_id, agentB.unique_id))], a, threshold) return a >= threshold # max(Reception) + 1 return agentA.reception + agentB.emission >= reception_range+1 ############################################################################### ### Logique ################################################################### def contact(self, a, b): """Logique de contact entre deux agents a et b, soit deux agents, cette fonction compare leur self.type (0 pour Pacifique (P) et 1 pour aggressif (A)) et leur niveau technologique (self.tech_lvl) si besoin""" if a in list(self.schedule._agents) and b in list(self.schedule._agents): agent_a = self.schedule._agents[a] agent_b = self.schedule._agents[b] if a == b: print("Meme agent") return # Si A ne detect pas B, vérifie que B ne detect pas A puis passe elif not self.detect(agent_a, agent_b): print("Agent", a, "ne rentre pas en contact avec Agent", b, ": AR", agent_a.reception, "/ BE", agent_b.emission) return if not self.detect(agent_b, agent_a): print("Agent", b, "ne rentre pas en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) return else: print("Mais Agent", b, "rentre en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) else: return print("Agents", a, b, "entre en contact !",) if agent_a.type < agent_b.type: # Si b Aggressif et a Pacifist self.remove_store(a) # self.schedule.remove(agent_a) # remove a print("Agent", a, "destroyed by Agent", b) elif agent_a.type > agent_b.type: # Si b Pacifist et a Aggressif self.remove_store(b) # self.schedule.remove(agent_b) # remove b print("Agent", b, "destroyed by Agent", a) elif agent_a.type == 1 and agent_b.type == 1: # Si b Aggressif et a Aggressif, regarde le tech_lvl print("Agents", a, "and", b, "are both aggresive") # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe if agent_a.tech_lvl == agent_b.tech_lvl: print(a, "and", b, "have the same technological level, nothing appends") return elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.remove_store(a) # self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.remove_store(b) # self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) else: # Cas où les deux agents sont pacifistes et la chaine de suspicion s'engrange, à implémenter print("Agent", a, "and", b, "are both pacifists -> chaine de suspicion (implémenté)----------------------") if (a,b) not in self.suspicions: self.suspicions[(a,b)] = suspicion_cooldown print(self.suspicions) # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe if agent_a.tech_lvl == agent_b.tech_lvl: print(a, "and", b, "have the same technocontactal level, nothing appends") return elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.remove_store(a) # self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.remove_store(b) # self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) def step(self): """Agissement du model à chaque étape""" # Update historique self.historique[self.timeline] = list(self.schedule._agents.values()) #print("Historique à time =", self.timeline, [i.unique_id for liste in self.historique.values() for i in liste]) # Compte le nombre de tour print("Tour :", self.timeline) self.timeline += 1 # Connecte les agents r_l = self.random_connect() # Add the ongoing connection to the log (so it can be reused afterwards) self.connection_logs[self.timeline] = r_l print("Liste des connections", r_l) #Logique de contact et de self.resolve_suspicious() for a, b in r_l: self.contact(a, b) # Renvoie la liste des agents ayant survécu print("Survivors:") # affiche les agents restant self.schedule.step() print("Nombre d'agents restant", len(self.schedule._agents)) def resolve_suspicious(self): for agentA, agentB in self.suspicions.keys(): if self.suspicions[(agentA, agentB)] == 0: self.contact(agentA, agentB) print("Séquence suspicion terminer : ", agentA, agentB) continue self.suspicions[(agentA, agentB)] -= 1 print("Les agents", agentA, "et", agentB, "se méfient") print(self.suspicions) # delete toutes les chaines terminées to_del = list(self.suspicions.items()) for k, v in to_del: if v <= 0: del self.suspicions[k] print(self.suspicions) ############################################################################### ### Indicateurs ############################################################### def distance_moyenne(self, time): agent_ids = self.historique[time] sum_dist = 0 n = 0 for a, b in self.distances_log.key(): if a in agent_ids and b in agent_ids: sum_dist += self.distances_log[(a, b)] n +=1 return sum_dist/n def ratio_P(self, time): agent_ids = self.historique[time] tot_P = 0 for i in agent_ids: if self.schedule._agents[i] == 0: tot_P += 1 return tot_P/len(agent_ids) def ratio_A(self, time): return math.abs(1-self.ratio_P(time)) def ems_moyen(self, time): agent_ids = self.historique[time] sum_ems = 0 n = 0 for i in agent_ids: sum_ems += self.schedule._agents[i].emission n += 1 return sum_ems/n def rec_moyen(self, time): agent_ids = self.historique[time] sum_ems = 0 n = 0 for i in agent_ids: sum_ems += self.schedule._agents[i].reception n += 1 return sum_ems/n def tech_moyen(self, time): agent_ids = self.historique[time] sum_ems = 0 n = 0 for i in agent_ids: sum_ems += self.schedule._agents[i].tech_lvl n += 1 return sum_ems/n def nb_contact(self, time): return
class CivModel(Model): def __init__(self, N): self.nb_agents = N # Nombre d'agents self.schedule = BaseScheduler(self) # Timeline basic # Initializations spatials des agents positions = self.spawn_clusters() #print(positions, len(positions)) for i in range(self.nb_agents): # positionne aléatoirement l'agent sur la grille agent = CivAgent(i, self) agent.x, agent.y, agent.z = positions[i] self.schedule.add(agent) # ajoute les agents à la timeline # Calcules les distances entre les agents self.distances_log = self.calculate_distance() def spawn_clusters(self): """Répartis les civilisations en clusters de densité égale dans l'univers (peut être assimiler à des galaxies)""" coords = [] for i in range(nb_clusters): clx, cly, clz = random.randint(0, univers_scale), random.randint( 0, univers_scale), random.randint(0, univers_scale) # Créer un point de cluster # Créé un nombres de groupe d'agent équivalent à celui des clusters rd_nb_civ = int(self.nb_agents/nb_clusters)+1 # Répartis aléatoiremnt les civilisations autour de ce clusters dans un radius donné. for j in range(rd_nb_civ): r = clusters_scale pl_x, pl_y, pl_z = random.randint(max( 0, clx-r), min(univers_scale, clx+r)), random.randint(max(0, cly-r), min(univers_scale, cly+r)), random.randint(max(0, clz-r), min(univers_scale, clz+r)) coords.append((pl_x, pl_y, pl_z)) return coords def random_connect(self, methodeTheo=False, seed=0): """Renvoie une liste de pair d'agent dans l'orde aléatoire""" # random.seed(0) # Pas bien ! random_id = random.sample( [k for k in range(len(list(self.schedule._agents)))], len(list(self.schedule._agents))) n_l = list(range(len(list(self.schedule._agents)))) connection = list(zip(n_l, random_id)) # print(connection) return connection def sort_dict(self, dic, first_term=False): sorted_dict = {} if first_term: for i in range(self.nb_agents): sorted_dict.update({k: v for k, v in sorted(dic.items(), key=lambda item: item[1]) if i is k[0]}) else: sorted_dict = {k: v for k, v in sorted( dic.items(), key=lambda item: item[1])} return sorted_dict def nn_connect(self): """Connect chaque agent avec les autres en partant du plus proches""" distances_dict = self.calculate_distance() for i in range(self.nb_agents): order_of_connection = self.sort_dict(distances_dict) # print(order_of_connection) return order_of_connection.keys() def calculate_distance(self): """Créé un dictionnaire dont les keys sont la pair d'agent en question la la value la distance les séparant""" distances_log = {} for i in self.schedule._agents: #print(distances_log, len(distances_log)) if i in self.schedule._agents.keys(): agentA = self.schedule._agents[i] for j in range(i+1, len(self.schedule._agents)): if j in self.schedule._agents.keys(): agentB = self.schedule._agents[j] d = math.sqrt((agentA.x-agentB.x)**2 + (agentA.y-agentB.y)**2 + (agentA.z-agentB.z)**2) distances_log[(i, j)] = d return distances_log def detect_distance_check(self, distance, opacity_factor, threshold): """Utilise la fonction exp(-kx) pour simuler la difficulté des civilisations à communiquer par rapport à la distance qui les sépare""" d_scaled = distance/univers_scale return math.exp(-opacity_factor*d_scaled) def detect(self, agentA, agentB, both=True): """Renvoie si oui ou non l'agent a detect l'agent b Fonctionnement du système émission/réception: Plus un agent émet de signaux, plus il est repérable. Le niveau de reception définit la capacité d'un agent à détecter des signaux. Par conséquent plus le niveau d'emission d'un agent est haut plus il est facilement reperable par des agents dont le niveau de reception est bas. """ if agentA.reception == 0: return False # Cas ou reception = 0, L'agent ne peut rien reperer print("Agent", self.schedule._agents, "ne peut rien reperer") else: a = self.detect_distance_check(self.distances_log[(min(agentA.unique_id, agentB.unique_id), max( agentA.unique_id, agentB.unique_id))], opacity_factor, threshold) print(self.distances_log[(min(agentA.unique_id, agentB.unique_id), max( agentA.unique_id, agentB.unique_id))], a, threshold) if not a >= threshold: print("Trop loin...") # max(Reception) + 1, merci Théo return agentA.reception + agentB.emission >= reception_range+1 and a >= threshold def remove_store(self, agent_id, storage): storage.append(self.schedule._agents[agent_id]) self.schedule.remove(agent_id, storage) return storage def contact(self): """Première aspect de la logique utilisé, random_connect crée une liste de tuple qui relie deux CivAgent entre eux cette fonction compare leur self.type (0 pour Pacifique (P) et 1 pour aggressif (A)) et leur niveau technologique (self.tech_lvl) si besoin.""" # print(self.schedule._agents[2]) # print(list(self.schedule._agents)) r_l = self.random_connect() # print(r_l) for a, b in r_l: # print(a) # Si l'agent n'appartient déjà plus à self.schedule (qu'il a donc déjà été remove), passe à la prochaine itération if a in list(self.schedule._agents) and b in list(self.schedule._agents): agent_a = self.schedule._agents[a] agent_b = self.schedule._agents[b] if a == b: # Si A est B, passe print("Meme agent") continue # Si A ne detect pas B, vérifie que B ne detect pas A puis passe elif not self.detect(agent_a, agent_b): print("Agent", a, "ne rentre pas en contact avec Agent", b, ": AR", agent_a.reception, "/ BE", agent_b.emission) continue if not self.detect(agent_b, agent_a): print("Agent", b, "ne rentre pas en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) continue else: print("Mais Agent", b, "rentre en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) else: continue print("Agents", a, b, "entre en contact !",) if agent_a.type < agent_b.type: # Si b Aggressif et a Pacifist self.schedule.remove(agent_a) # remove a print("Agent", a, "destroyed by Agent", b) elif agent_a.type > agent_b.type: # Si b Pacifist et a Aggressif self.schedule.remove(agent_b) # remove b print("Agent", b, "destroyed by Agent", a) elif agent_a.type == 1 and agent_b.type == 1: # Si b Aggressif et a Aggressif, regarde le tech_lvl print("Agents", a, "and", b, "are both aggresive") # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe if agent_a.tech_lvl == agent_b.tech_lvl: print(a, "and", b, "have the same technological level, nothing appends") continue elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) else: # Cas où les deux agents sont pacifistes et la chaine de suspicion s'engrange, à implémenter print("Agent", a, "and", b, "are both pacifists -> chaine de suspicion (a implémenté, ne fait rien pour l'instant)") # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe if agent_a.tech_lvl == agent_b.tech_lvl: print(a, "and", b, "have the same technological level, nothing appends") continue elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) # Renvoie la liste des agents ayant survécu survivors = [index for index in list(self.schedule._agents)] print("Survivors:") # affiche les agents restant self.schedule.step() return survivors def step(self): # self.schedule.step() self.calculate_distance() self.contact() ############ Plotting Graphs ################################################## def plot_agents_positions(self, color): fig = plt.figure() ax = fig.add_subplot(projection='3d') for agent in self.schedule._agents.values(): ax.scatter(agent.x, agent.y, agent.z, color=color, marker='o') plt.show() def plot_agents_type(self): fig, axs = plt.subplots(1, 1, figsize=(5, 5)) types = [int(a.type) for a in self.schedule._agents.values()] return axs.hist(types, bins=2) def plot_agents_tech(self): fig, axs = plt.subplots(1, 1, figsize=(5, 5)) tech = [a.tech_lvl for a in self.schedule._agents.values()] return axs.hist(tech, bins=technological_range) def plot_agents_ems(self): fig, axs = plt.subplots(1, 1, figsize=(5, 5)) ems = [a.emission for a in self.schedule._agents.values()] return axs.hist(ems, bins=emission_range) def plot_agents_rec(self): fig, axs = plt.subplots(1, 1, figsize=(5, 5)) rec = [a.reception for a in self.schedule._agents.values()] return axs.hist(rec, bins=reception_range)
class CivModel(Model): """A model with some number of agents.""" def __init__(self, N, width=500, height=500): self.nb_agents = N self.grid = MultiGrid(width, height, True) self.schedule = BaseScheduler(self) # Créer une timeline for i in range(self.nb_agents): agent = CivAgent(i, self) self.schedule.add(agent) # ajoute N agent à la timeline # positionne aléatoirement l'agent sur la grille agent.x = self.random.randrange(self.grid.width) agent.y = self.random.randrange(self.grid.height) agent.pos = agent.x, agent.y self.grid.place_agent(agent, agent.pos) def random_connect(self, seed=0): """Renvoie une liste de pair d'agent dans l'orde aléatoire""" #random.seed(0) # Pas bien ! random_id = random.sample([k for k in range(self.nb_agents)], self.nb_agents) n_l = [k for k in range(self.nb_agents)] connection = list(zip(n_l, random_id)) print(connection) return connection def detect(self, agentA, agentB, both=True): """Renvoie si oui ou non l'agent a detect l'agent b Fonctionnement du système émission/réception: Plus un agent émet de signaux, plus il est repérable. Le niveau de reception définit la capacité d'un agent à détecter des signaux. Par conséquent plus le niveau d'emission d'un agent est haut plus il est facilement reperable par des agents dont le niveau de reception est bas. """ if agentA.reception == 0: return False print("Agent", self.schedule._agents, "ne peut rien reperer" ) # Cas ou reception = 0, L'agent ne peut rien reperer else: # Créé une liste par compréhension dont les élement sont les niveaux d'emissions reperables par l'agent A, # si l'agent B a un niveau d'emission appartement à cette liste alors A peut reperer B spotable_agents = [ k for k in range(emission_range - 1, emission_range - agentA.reception - 1, -1) ] print("Agent", agentA.unique_id, "R" + str(agentA.reception), "Agent", agentB.unique_id, "E" + str(agentB.emission), "l'agent peut reperer ceux dont l'emission est : ", spotable_agents) return agentB.emission in spotable_agents # renvoie vrai si l'agentB peut etre repere par A ou faux sinon def contact(self): """Première aspect de la logique utilisé, random_connect crée une liste de tuple qui relie deux CivAgent entre eux cette fonction compare leur self.type (0 pour Pacifique (P) et 1 pour aggressif (A)) et leur niveau technologique (self.tech_lvl) si besoin.""" #print(self.schedule._agents[2]) #print(list(self.schedule._agents)) r_l = self.random_connect(0) #print(r_l) for a, b in r_l: #print(a) # Si l'agent n'appartient déjà plus à self.schedule (qu'il a donc déjà été remove), passe à la prochaine itération if a in list(self.schedule._agents) and b in list( self.schedule._agents): agent_a = self.schedule._agents[a] agent_b = self.schedule._agents[b] if a == b: # Si A est B, passe print("Meme agent") continue elif not self.detect( agent_a, agent_b ): # Si A ne detect pas B, vérifie que B ne detect pas A puis passe print("Agent", a, "ne rentre pas en contact avec Agent", b, ": AR", agent_a.reception, "/ BE", agent_b.emission) continue if not self.detect(agent_b, agent_a): print("Agent", b, "ne rentre pas en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) continue else: print("Mais Agent", b, "rentre en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) else: continue print("Agents", a, b, "entre en contact !", "Agents restant", list(self.schedule._agents)) if agent_a.type < agent_b.type: # Si b Aggressif et a Pacifist self.schedule.remove(agent_a) # remove a print("Agent", a, "destroyed by Agent", b) elif agent_a.type > agent_b.type: # Si b Pacifist et a Aggressif self.schedule.remove(agent_b) # remove b print("Agent", b, "destroyed by Agent", a) elif agent_a.type == 1 and agent_b.type == 1: # Si b Aggressif et a Aggressif, regarde le tech_lvl print("Agents", a, "and", b, "are both aggresive") if agent_a.tech_lvl == agent_b.tech_lvl: # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe print( a, "and", b, "have the same technological level, nothing appends") continue elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) else: # Cas où les deux agents sont pacifistes et la chaine de suspicion s'engrange, à implémenter print( "Agent", a, "and", b, "are both pacifists -> chaine de suspicion (a implémenté, ne fait rien pour l'instant)" ) if agent_a.tech_lvl == agent_b.tech_lvl: # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe print( a, "and", b, "have the same technological level, nothing appends") continue elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) # Renvoie la liste des agents ayant survécu survivors = [index for index in list(self.schedule._agents)] print("Survivors:") #affiche les agents restant self.schedule.step() return survivors def step(self): #self.schedule.step() self.contact()
class School(Model): def __init__(self, map_path, schedule_path, grade_N, KG_N, preschool_N, special_education_N, faculty_N, seat_dist, init_patient=3, attend_rate=1, mask_prob=0.516, inclass_lunch=False, username="******"): # zipcode etc, for access of more realistic population from KG perhaps # model param init self.__mask_prob = mask_prob self.inclass_lunch = inclass_lunch self.seat_dist = math.ceil(seat_dist / (attend_rate**(1 / 2))) self.idle_teachers = [] # teachers to be assigned without a classroom self.init_patient = init_patient # mesa model init self.running = True self.grid = GeoSpace() self.schedule = BaseScheduler(self) #data collect init model_reporters = { "day": "day_count", "cov_positive": "infected_count" } agent_reporters = { "unique_id": "unique_id", "health_status": "health_status", "symptoms": "symptoms", "x": "x", "y": "y", "viral_load": "viral_load" } self.datacollector = datacollection.DataCollector( model_reporters=model_reporters, agent_reporters=agent_reporters) school_gdf = load_map(map_path) # room agent init self.room_agents = school_gdf.apply( lambda x: Classroom(unique_id=x["Id"], model=self, shape=x["geometry"], room_type=x["room_type"]), axis=1).tolist() self.grid.add_agents(self.room_agents) # stats tracking init self.infected_count = 0 self.step_count = 0 self.day_count = 0 self.num_exposed = 0 # student activity init self.schoolday_schedule = pd.read_csv(schedule_path) self.activity = None # id tracking init self.__teacher_id = 0 self.__student_id = 0 self.__faculty_N = faculty_N self.schedule_ids = self.schoolday_schedule.columns self.recess_yards = find_room_type(self.room_agents, 'recess_yard') def init_agents(room_type, N, partition=False): ''' batch initialize human agents into input room type rooms with equal partition size room_type: a valid string of room type: [None, 'restroom_grade_boys', 'lunch_room', 'classroom_grade', 'restroom_all', 'restroom_grade_girls', 'restroom_KG', 'classroom_KG', 'community_room', 'library', 'restroom_special_education', 'restroom_faculty', 'classroom_special_education', 'health_room', 'faculty_lounge', 'classroom_preschool', 'restroom_preschool'] ''' rooms = find_room_type(self.room_agents, room_type) # if student group should be seperated to different day schedules # assigning schedule_id to equally partitioned rooms # currently only grade 1-5 "grade" students need to be partitioned, partition_size = len(rooms) if partition: partition_size = math.ceil(partition_size / len(self.schedule_ids)) class_size = N // len(rooms) remaining_size = N % len(rooms) for i, classroom in zip(range(len(rooms)), rooms): classroom.generate_seats(class_size, self.seat_dist) classroom.schedule_id = self.schedule_ids[i // partition_size] for idx in range(class_size): pnt = classroom.seats[idx] mask_on = np.random.choice([True, False], p=[mask_prob, 1 - mask_prob]) agent_point = Student(model=self, shape=pnt, unique_id="S" + str(self.__student_id), room=classroom, mask_on=mask_on) self.grid.add_agents(agent_point) self.schedule.add(agent_point) self.__student_id += 1 # spread remaining student into all classrooms if remaining_size > 0: pnt = classroom.seats[class_size] mask_on = np.random.choice([True, False], p=[mask_prob, 1 - mask_prob]) agent_point = Student(model=self, shape=pnt, unique_id="S" + str(self.__student_id), room=classroom, mask_on=mask_on) self.grid.add_agents(agent_point) self.schedule.add(agent_point) self.__student_id += 1 remaining_size -= 1 #add teacher to class pnt = generate_random(classroom.shape) agent_point = Teacher(model=self, shape=pnt, unique_id="T" + str(self.__teacher_id), room=classroom) self.grid.add_agents(agent_point) self.schedule.add(agent_point) self.idle_teachers.append(agent_point) self.__teacher_id += 1 self.__faculty_N -= 1 # initialize all students and teachers in classrooms init_agents("classroom_grade", int(grade_N * attend_rate), partition=True) # keep track of student types #self.grade_students = [a for a in list(self.schedule.agents) if isinstance(a, Student)] init_agents("classroom_KG", int(KG_N * attend_rate)) init_agents("classroom_preschool", int(preschool_N * attend_rate)) #self.pkg_students = [a for a in list(set(self.schedule.agents).difference(self.grade_students)) if isinstance(a, Student)] init_agents("classroom_special_education", int(special_education_N * attend_rate)) # dump remaining teacher to faculty lounge for f_lounge in find_room_type(self.room_agents, "faculty_lounge"): f_lounge.schedule_id = self.schedule_ids[0] for i in range(self.__faculty_N): pnt = generate_random(f_lounge.shape) agent_point = Teacher(model=self, shape=pnt, unique_id="T" + str(self.__teacher_id), room=f_lounge) self.grid.add_agents(agent_point) self.schedule.add(agent_point) self.__teacher_id += 1 #self.people = list(self.schedule.agents) # add rooms to scheduler at last for room in self.room_agents: self.schedule.add(room) self.lunchroom = find_room_type(self.room_agents, 'lunch_room')[0] self.lunchroom.generate_seats_lunch(3, 12) def small_step(self): self.schedule.step() self.grid._recreate_rtree() def add_N_patient(self, N): patients = random.sample( [a for a in self.schedule.agents if isinstance(a, Student)], N) for p in patients: p.health_status = "exposed" p.asymptomatic = True p.infective = True def show(self): ''' plot current step visualization deprecated since end of model visualization update ''' # UPDATE 10/16: add deprecation warning message = "this function is no longer used for performance issues, check output_image.py for end of model visualization" warnings.warn(message, DeprecationWarning) school_geometry = gpd.GeoSeries([a.shape for a in self.room_agents]) school_map = gpd.GeoDataFrame( {"viral_load": [min(a.viral_load, 5) for a in self.room_agents]}) school_map.geometry = school_geometry basemap = school_map.plot(column="viral_load", cmap="Reds", alpha=0.5, vmin=0, vmax=5) school_map.boundary.plot(ax=basemap, color='k', linewidth=0.2) list( map(lambda a: a.plot(), [ a for a in self.schedule.agents if issubclass(type(a), Human) ])) hour = 9 + self.step_count * 5 // 60 # assume plot start at 9am minute = self.step_count * 5 % 60 plt.title("Iteration: Day {}, ".format(self.day_count + 1) + "%d:%02d" % (hour, minute), fontsize=30) def __update_day(self): ''' update incubation time, reset viral_load, remove symptomatic agents, etc for end of day ''' for a in self.schedule.agents[:]: if issubclass(type(a), Human): if a.symptoms: # remove agent if symptom onset if isinstance(a, Teacher): # assign a new teacher to position new_teacher = self.idle_teachers.pop() new_teacher.shape = a.shape new_teacher.room = a.room new_teacher.classroom = a.classroom self.schedule.remove(a) self.grid.remove_agent(a) # UPDATE 10/16: infectious made obsolete, end of day update rework elif a.health_status == "exposed": # UPDATE 10/17: update infective delay if agent is not infective by end of day a.infective = True a.symptom_countdown -= 1 # calculate when symptoms begin to show using 0-15 density if a.symptom_countdown <= 0: if a.symptom_countdown == 0: self.infected_count += 1 # update model stat for total infected # negative countdown means this agent is asymptomatic if not a.asymptomatic: # this is a really small chance, however possible # set symtoms to true # next day this agent will be removed from the model a.symptoms = True else: # reset viral_load of room agents a.viral_load = 0 def step(self): ''' simulate a day with school day schedule ''' if not self.schedule.steps: self.add_N_patient(self.init_patient) for i, row in self.schoolday_schedule.iterrows(): self.activity = row self.datacollector.collect(self) self.schedule.step() self.grid._recreate_rtree() self.step_count += 1 self.__update_day() self.grid._recreate_rtree() self.day_count += 1 self.step_count = 0
class Labor_Model(Model): def __init__(self,num_employee=1000,num_employer=10,num_jobseekers=10,wage_flexibility=(0.5,0.5),p_1=0.01,p_2=0.01,p_3=0.01): self.num_employer = num_employer self.employer_index = 0 self.num_employee = num_employee self.employee_index = 0 self.num_jobseekers = num_jobseekers self.wage_flexibility = wage_flexibility self.p_1 = p_1 self.p_2 = p_2 self.p_3 = p_3 self.p_4 = 1 self.schedule_employees = BaseScheduler(self) self.schedule_employers = BaseScheduler(self) self.job_seeker_pool = [] self.inactive_pool = [] self.employee_seeker_pool = [] self.datacollector = Data_Collector(self) self.create_employee(self.num_employee) self.create_employer(self.num_employer,self.num_employee,wage_flexibility) ## INIT ALLOCATING FIRMS TO WORKERS while(self.employee_seeker_pool or self.job_seeker_pool): employer = random.choice(self.employee_seeker_pool) for i in range(employer.firm_size): employee = random.choice(self.job_seeker_pool) self.change_employer(employee,employer) ## ADDING JOB SEEKERS for i in range(self.num_jobseekers): a = random.choice(self.schedule_employees.agents) while(a.employer == None): a = random.choice(self.schedule_employees.agents) self.change_employer(a,employer=None) ##FIND JOBS self.job_search() def create_employee(self,num): wage_list = self.create_wage_list(num) age_list = [a / 4.0 for a in range(80, 240)] for i in range(self.employee_index,self.employee_index + num): age = random.choice(age_list) a = Employee_Agent(i, self, wage_list[i-self.employee_index], age) self.job_seeker_pool.append(a) self.schedule_employees.add(a) self.employee_index += num def create_employer(self,num_employer,num_employee,wage_flexibility,add_vacancy_wage_list=False): firm_size_list = self.create_firm_size_list(num_employer,num_employee) for i in range(self.employer_index,self.employer_index + num_employer): a = Employer_Agent(i,self,firm_size_list[i-self.employer_index],wage_flexibility) self.employee_seeker_pool.append(a) self.schedule_employers.add(a) if(add_vacancy_wage_list): wage_list = self.create_wage_list(num_employee) a.vacancy_wage_list.extend(wage_list) self.employer_index += num_employer def create_wage_list(self,num): list = [] for i in range(num): list.append(np.random.lognormal(mean=100,sigma=0.7)) mean_val = float(sum(list)) / len(list) normalized_list = [(100 / mean_val) * list[i] for i in range(num)] return np.round(normalized_list).astype(int).tolist() def create_firm_size_list(self,num_employer,num_employee): list = [] for i in range(num_employer): list.append(np.random.power(1)) mean_val = float(sum(list)) / len(list) new_mean_val = num_employee / num_employer normalized_list = [(new_mean_val / mean_val) * list[i] for i in range(num_employer)] normalized_list = np.round(normalized_list,decimals=0).astype(int).tolist() if(sum(normalized_list) != num_employee): diff = num_employee - sum(normalized_list) normalized_list[normalized_list.index(max(normalized_list))] += diff return normalized_list def change_employer(self,employee,employer,change_past_employer=True,leaving_wf = False,employer_closed=False): ##If they are changing employers if(employer is not None): employer.employees.append(employee) employer.employee_wage_list.append(employee.wage) employee.employer = employer self.job_seeker_pool.remove(employee) if(len(employer.employees) == employer.firm_size): self.employee_seeker_pool.remove(employee.employer) ##If they are either leaving the WF or quitting/retrenched else: ##not leaving the work-fore if(not leaving_wf): self.job_seeker_pool.append(employee) employee.employer.employees.remove(employee) employee.employer.employee_wage_list.remove(employee.wage) employee.employer.vacancy_wage_list.append(employee.wage) if (not employee.employer in self.employee_seeker_pool and not employer_closed): self.employee_seeker_pool.append(employee.employer) if (change_past_employer): employee.past_employer = employee.employer employee.employer = None ##If they are leaving the workforce else: if(employee in self.job_seeker_pool): self.job_seeker_pool.remove(employee) else: employee.employer.employees.remove(employee) employee.employer.employee_wage_list.remove(employee.wage) employee.employer.vacancy_wage_list.append(employee.wage) if (not employee.employer in self.employee_seeker_pool and not employer_closed): self.employee_seeker_pool.append(employee.employer) if (change_past_employer): employee.past_employer = employee.employer employee.employer = None self.inactive_pool.append(employee) def job_search(self): list = [] for e in self.employee_seeker_pool: for wage in e.vacancy_wage_list: list.append((e,wage)) sortedlist = sorted(list,key=lambda x : x[1],reverse=True) for employer, wage in sortedlist: possible_candidates = [] for a in self.job_seeker_pool: allowable_wage = (a.wage * (1 - e.wage_flexibility[0]), a.wage * (1 + e.wage_flexibility[1])) if (allowable_wage[0] <= wage <= allowable_wage[1] and a.past_employer != employer): possible_candidates.append(a) if (possible_candidates): # print([str(x) for x in possible_candidates]) chosen_candidate = sorted(possible_candidates, key=lambda x: x.wage,reverse=True)[0] # print(chosen_candidate) chosen_candidate.wage = wage self.change_employer(chosen_candidate, employer) employer.vacancy_wage_list.remove(wage) def step(self): ##wipe inactive ppl and add in new peeps if(self.inactive_pool): for i in self.inactive_pool: self.schedule_employees.remove(i) self.create_employee(len(self.inactive_pool)) self.inactive_pool = [] ##Age everyone self.schedule_employees.step() retiree = [a for a in self.schedule_employees.agents if a.age >= 60.0] for r in retiree: self.change_employer(r,employer=None,leaving_wf=True) ##Employed & Unemployed peeps leaving WF employed_list = [x for x in self.schedule_employees.agents if x not in self.job_seeker_pool and x not in self.inactive_pool] for i in range(int(self.p_3 * len(employed_list))): a = random.choice(employed_list) self.change_employer(a,employer=None,leaving_wf=True) employed_list.remove(a) unemployed_list = [x for x in self.job_seeker_pool if x not in self.inactive_pool] for i in range(int(self.p_2 * len(unemployed_list))): a = random.choice(unemployed_list) self.change_employer(a, employer=None,leaving_wf=True) unemployed_list.remove(a) ##Employed peeps quitting (but not leaving the WF) employed_list = [x for x in self.schedule_employees.agents if x not in self.job_seeker_pool and x not in self.inactive_pool] for i in range(int(self.p_1) * len(employed_list)): a = random.choice(employed_list) self.change_employer(a,employer=None) employed_list.remove(a) ##Employers closing down firm_size = 0 for i in range(self.p_4): a = random.choice(self.schedule_employers.agents) firm_size += a.firm_size self.schedule_employers.remove(a) if(a in self.employee_seeker_pool): self.employee_seeker_pool.remove(a) employee_list = a.employees[:] for w in employee_list: self.change_employer(w,employer=None,employer_closed=True) ##Employers entering self.create_employer(self.p_4,firm_size,self.wage_flexibility,add_vacancy_wage_list=True) ##Job matching occurs self.job_search() self.datacollector.step()
class CivModel(Model): """A model with some number of agents.""" def __init__(self, N): self.nb_agents = N self.schedule = BaseScheduler(self) # Créer une timeline for i in range(self.nb_agents): agent = CivAgent(i, self) self.schedule.add(agent) # ajoute N agent à la timeline # positionne aléatoirement l'agent sur la grille self.distances_log = self.calculate_distance() def random_connect(self, methodeTheo=False, seed=0): """Renvoie une liste de pair d'agent dans l'orde aléatoire""" #random.seed(0) # Pas bien ! random_id = random.sample( [k for k in range(len(list(self.schedule._agents)))], len(list(self.schedule._agents))) n_l = list(range(len(list(self.schedule._agents)))) connection = list(zip(n_l, random_id)) print(connection) return connection def calculate_distance(self): distances_log = {} for i in self.schedule._agents: #print(distances_log, len(distances_log)) if i in self.schedule._agents.keys(): agentA = self.schedule._agents[i] for j in range(i + 1, len(self.schedule._agents)): if j in self.schedule._agents.keys(): agentB = self.schedule._agents[j] d = math.sqrt((agentA.x - agentB.x)**2 + (agentA.y - agentB.y)**2) distances_log[(i, j)] = d return distances_log def detect_distance_check(self, distance, opacity_factor, threshold): d_scaled = distance / univers_scale return math.exp(-opacity_factor * d_scaled) def detect(self, agentA, agentB, both=True): """Renvoie si oui ou non l'agent a detect l'agent b Fonctionnement du système émission/réception: Plus un agent émet de signaux, plus il est repérable. Le niveau de reception définit la capacité d'un agent à détecter des signaux. Par conséquent plus le niveau d'emission d'un agent est haut plus il est facilement reperable par des agents dont le niveau de reception est bas. """ if agentA.reception == 0: return False # Cas ou reception = 0, L'agent ne peut rien reperer print("Agent", self.schedule._agents, "ne peut rien reperer") else: a = self.detect_distance_check( self.distances_log[(min(agentA.unique_id, agentB.unique_id), max(agentA.unique_id, agentB.unique_id))], opacity_factor, threshold) print( self.distances_log[(min(agentA.unique_id, agentB.unique_id), max(agentA.unique_id, agentB.unique_id))], a, threshold) if not a >= threshold: print("Trop loin...") return agentA.reception + agentB.emission >= reception_range + 1 and a >= threshold # max(R) + 1 def contact(self): """Première aspect de la logique utilisé, random_connect crée une liste de tuple qui relie deux CivAgent entre eux cette fonction compare leur self.type (0 pour Pacifique (P) et 1 pour aggressif (A)) et leur niveau technologique (self.tech_lvl) si besoin.""" # print(self.schedule._agents[2]) # print(list(self.schedule._agents)) r_l = self.random_connect() # print(r_l) for a, b in r_l: # print(a) # Si l'agent n'appartient déjà plus à self.schedule (qu'il a donc déjà été remove), passe à la prochaine itération if a in list(self.schedule._agents) and b in list( self.schedule._agents): agent_a = self.schedule._agents[a] agent_b = self.schedule._agents[b] if a == b: # Si A est B, passe print("Meme agent") continue # Si A ne detect pas B, vérifie que B ne detect pas A puis passe elif not self.detect(agent_a, agent_b): print("Agent", a, "ne rentre pas en contact avec Agent", b, ": AR", agent_a.reception, "/ BE", agent_b.emission) continue if not self.detect(agent_b, agent_a): print("Agent", b, "ne rentre pas en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) continue else: print("Mais Agent", b, "rentre en contact avec Agent", a, ": BR", agent_b.reception, "/ AE", agent_a.emission) else: continue print("Agents", a, b, "entre en contact !", "Agents restant", list(self.schedule._agents)) if agent_a.type < agent_b.type: # Si b Aggressif et a Pacifist self.schedule.remove(agent_a) # remove a print("Agent", a, "destroyed by Agent", b) elif agent_a.type > agent_b.type: # Si b Pacifist et a Aggressif self.schedule.remove(agent_b) # remove b print("Agent", b, "destroyed by Agent", a) elif agent_a.type == 1 and agent_b.type == 1: # Si b Aggressif et a Aggressif, regarde le tech_lvl print("Agents", a, "and", b, "are both aggresive") # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe if agent_a.tech_lvl == agent_b.tech_lvl: print( a, "and", b, "have the same technological level, nothing appends") continue elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) else: # Cas où les deux agents sont pacifistes et la chaine de suspicion s'engrange, à implémenter print( "Agent", a, "and", b, "are both pacifists -> chaine de suspicion (a implémenté, ne fait rien pour l'instant)" ) # Niv technologique B == Niv technologique A <=> b Pacifist et a Pacifist, donc passe if agent_a.tech_lvl == agent_b.tech_lvl: print( a, "and", b, "have the same technological level, nothing appends") continue elif agent_a.tech_lvl < agent_b.tech_lvl: # Si B a un meilleur NT que A, B détruit A self.schedule.remove(agent_a) print("Agent", b, "stronger than Agent", a) print("Agent", a, "destroyed by Agent", b) else: self.schedule.remove(agent_b) # Same mais inverse print("Agent", a, "stronger than Agent", b) print("Agent", b, "destroyed by Agent", a) # Renvoie la liste des agents ayant survécu survivors = [index for index in list(self.schedule._agents)] print("Survivors:") # affiche les agents restant # self.schedule.step() return survivors def step(self): #self.schedule.step() self.calculate_distance() self.contact()