class TestSingleNetworkGrid(unittest.TestCase): GRAPH_SIZE = 10 def setUp(self): ''' Create a test network grid and populate with Mock Agents. ''' G = nx.complete_graph(TestSingleNetworkGrid.GRAPH_SIZE) self.space = NetworkGrid(G) self.agents = [] for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = MockAgent(i, None) self.agents.append(a) self.space.place_agent(a, pos) def test_agent_positions(self): ''' Ensure that the agents are all placed properly. ''' for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = self.agents[i] assert a.pos == pos def test_get_neighbors(self): assert len(self.space.get_neighbors( 0, include_center=True)) == TestSingleNetworkGrid.GRAPH_SIZE assert len(self.space.get_neighbors( 0, include_center=False)) == TestSingleNetworkGrid.GRAPH_SIZE - 1 def test_move_agent(self): initial_pos = 1 agent_number = 1 final_pos = TestSingleNetworkGrid.GRAPH_SIZE - 1 _agent = self.agents[agent_number] assert _agent.pos == initial_pos assert _agent in self.space.G.node[initial_pos]['agent'] assert _agent not in self.space.G.node[final_pos]['agent'] self.space.move_agent(_agent, final_pos) assert _agent.pos == final_pos assert _agent not in self.space.G.node[initial_pos]['agent'] assert _agent in self.space.G.node[final_pos]['agent'] def test_is_cell_empty(self): assert not self.space.is_cell_empty(0) assert self.space.is_cell_empty(TestSingleNetworkGrid.GRAPH_SIZE - 1) def test_get_cell_list_contents(self): assert self.space.get_cell_list_contents([0]) == [self.agents[0]] assert self.space.get_cell_list_contents( list(range(TestSingleNetworkGrid.GRAPH_SIZE))) == [ self.agents[0], self.agents[1], self.agents[2] ] def test_get_all_cell_contents(self): assert self.space.get_all_cell_contents() == [ self.agents[0], self.agents[1], self.agents[2] ]
class TestSingleNetworkGrid(unittest.TestCase): GRAPH_SIZE = 10 def setUp(self): ''' Create a test network grid and populate with Mock Agents. ''' G = nx.complete_graph(TestSingleNetworkGrid.GRAPH_SIZE) self.space = NetworkGrid(G) self.agents = [] for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = MockAgent(i, None) self.agents.append(a) self.space.place_agent(a, pos) def test_agent_positions(self): ''' Ensure that the agents are all placed properly. ''' for i, pos in enumerate(TEST_AGENTS_NETWORK_SINGLE): a = self.agents[i] assert a.pos == pos def test_get_neighbors(self): assert len(self.space.get_neighbors(0, include_center=True)) == TestSingleNetworkGrid.GRAPH_SIZE assert len(self.space.get_neighbors(0, include_center=False)) == TestSingleNetworkGrid.GRAPH_SIZE - 1 def test_move_agent(self): initial_pos = 1 agent_number = 1 final_pos = TestSingleNetworkGrid.GRAPH_SIZE - 1 _agent = self.agents[agent_number] assert _agent.pos == initial_pos assert _agent in self.space.G.node[initial_pos]['agent'] assert _agent not in self.space.G.node[final_pos]['agent'] self.space.move_agent(_agent, final_pos) assert _agent.pos == final_pos assert _agent not in self.space.G.node[initial_pos]['agent'] assert _agent in self.space.G.node[final_pos]['agent'] def test_is_cell_empty(self): assert not self.space.is_cell_empty(0) assert self.space.is_cell_empty(TestSingleNetworkGrid.GRAPH_SIZE - 1) def test_get_cell_list_contents(self): assert self.space.get_cell_list_contents([0]) == [self.agents[0]] assert self.space.get_cell_list_contents(list(range(TestSingleNetworkGrid.GRAPH_SIZE))) == [self.agents[0], self.agents[1], self.agents[2]] def test_get_all_cell_contents(self): assert self.space.get_all_cell_contents() == [self.agents[0], self.agents[1], self.agents[2]]
class VirusModel(Model): """A virus model with some number of agents""" def __init__(self, num_nodes, avg_node_degree, initial_outbreak_size, virus_spread_chance, virus_check_frequency, recovery_chance, gain_resistance_chance): self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.virus_spread_chance = virus_spread_chance self.virus_check_frequency = virus_check_frequency self.recovery_chance = recovery_chance self.gain_resistance_chance = gain_resistance_chance self.datacollector = DataCollector({ "Infected": number_infected, "Susceptible": number_susceptible, "Resistant": number_resistant }) # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent(i, self, State.SUSCEPTIBLE, self.virus_spread_chance, self.virus_check_frequency, self.recovery_chance, self.gain_resistance_chance) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes infected_nodes = random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = State.INFECTED self.running = True self.datacollector.collect(self) def resistant_susceptible_ratio(self): try: return number_state(self, State.RESISTANT) / number_state( self, State.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class VirusModel(Model): """A virus model with some number of agents""" def __init__(self, num_nodes, avg_node_degree, initial_outbreak_size, alpha, beta, gamma, k, n): self.num_nodes = num_nodes self.avg_node_degree = avg_node_degree self.G = nx.barabasi_albert_graph(n=self.num_nodes, m=avg_node_degree) # _graph(n=self.num_nodes, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.alpha = alpha self.beta = beta self.gamma = gamma self.k = k self.n = n self.datacollector = DataCollector({ "Infected": number_active, "Susceptible": number_susceptible, "Carrier": number_inactive, "Removed": number_removed }) # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent(i, self, State.SUSCEPTIBLE, self.alpha, self.beta, self.gamma, self.k, self.n) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes active_nodes = random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(active_nodes): a.state = State.ACTIVE self.running = True self.datacollector.collect(self) def removed_susceptible_ratio(self): try: return number_state(self, State.REMOVED) / number_state( self, State.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class VirusModel(Model): def __init__(self, num_nodes, avg_node_degree, initial_outbreak_size, alpha, beta): self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.alpha = alpha self.beta = beta self.G = nx.barabasi_albert_graph(n=self.num_nodes, m=3) # self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.datacollector = DataCollector({ "Susceptible": number_susceptible, "Infected": number_infected, "Removed": number_removed }) # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent( i, self, State.SUSCEPTIBLE, self.alpha, self.beta, ) self.schedule.add(a) self.grid.place_agent(a, node) # Infect some nodes infected_nodes = random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = State.INFECTED self.running = True self.datacollector.collect(self) def removed_susceptible_ratio(self): try: return number_state(self, State.REMOVED) / number_state( self, State.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
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 """ def __init__(self, graph, model_parameters): # 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.datacollector = DataCollector({ "Exposed": count_exposed, "Susceptible": count_susceptible, "Removed": count_removed, "Asymptomatic": count_asymptomatic, "Symptomatic": count_symptomatic }) self.model_parameters = model_parameters for i, node in enumerate(self.graph.nodes()): a = Person(i, self, State.SUSCEPTIBLE, model_parameters) 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", str(self.model_parameters)) 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)) #print("Steps completed: ", i) self.step()
class PopuNetwork(Model): """ The population network which initializes: - the number of agents - the number of incarcerated agents at start - the race of the simulated community - the average number of relationships per individual """ def __init__(self, num_nodes=1000, avg_node_degree=3, initial_outbreak_size=10, race="black"): self.num_nodes = num_nodes self.race = race self.months = 0 prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.sentence = generate_sentence(race) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.datacollector = DataCollector({ "Incarcerated": number_incarcerated, "Susceptible": number_susceptible, "Released": number_released }) for i, node in enumerate(self.G.nodes()): a = Person(i, self, State.SUSCEPTIBLE, self.sentence) self.schedule.add(a) self.grid.place_agent(a, node) # Begin with some portion of the population in prison infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = State.INCARCERATED self.running = True self.datacollector.collect(self) def step(self): self.schedule.step() self.months += 1 # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class VirusOnNetwork(Model): """A virus model with some number of agents""" def __init__(self, num_nodes=10, avg_node_degree=3, initial_outbreak_size=1, virus_spread_chance=0.4, virus_check_frequency=0.4, recovery_chance=0.3, gain_resistance_chance=0.5): self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.virus_spread_chance = virus_spread_chance self.virus_check_frequency = virus_check_frequency self.recovery_chance = recovery_chance self.gain_resistance_chance = gain_resistance_chance self.datacollector = DataCollector({"Infected": number_infected, "Susceptible": number_susceptible, "Resistant": number_resistant}) # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent(i, self, State.SUSCEPTIBLE, self.virus_spread_chance, self.virus_check_frequency, self.recovery_chance, self.gain_resistance_chance) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = State.INFECTED self.running = True self.datacollector.collect(self) def resistant_susceptible_ratio(self): try: return number_state(self, State.RESISTANT) / number_state(self, State.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class Population(Model): """Population""" def __init__(self, population_size, initial_outbreak_size, spread_chance): print("Beginning model setup...\n") self.population_size = population_size print("Creating graph...") self.graph = nx.powerlaw_cluster_graph(population_size, 100, 0.5) #self.graph = nx.complete_graph(population_size) print(len(self.graph.edges)) print("Initializing grid...") self.grid = NetworkGrid(self.graph) self.schedule = SimultaneousActivation(self) self.initial_outbreak_size = initial_outbreak_size self.spread_chance = spread_chance print("Initializing data collector...") self.datacollector = DataCollector({ "Infected:": count_infected, "Susceptible:": count_susceptible, "Removed:": count_removed }) for i, node in enumerate(self.graph.nodes()): a = Person(i, self, State.SUSCEPTIBLE, spread_chance) self.schedule.add(a) self.grid.place_agent(a, i) if i % 100 == 0: print("Finished with agent ", 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.INFECTED 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): print("Steps completed: ", i) self.step()
class InfectModel(Model): """A model with some number of agents.""" def __init__(self, N, limit_time, infected_init, neighbors): self.num_agents = N prob = neighbors / self.num_agents self.G = nx.erdos_renyi_graph(n=self.num_agents, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.datacollector = DataCollector( { "Infected": number_infected, "Susceptible": number_susceptible, "Resistant": number_resistant, } ) # Create agents for i, node in enumerate(self.G.nodes()): a = PeopleAgent(i,limit_time, self) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes infected_nodes = self.random.sample(self.G.nodes(), infected_init) for a in self.grid.get_cell_list_contents(infected_nodes): a.status = State.ACTIVE self.datacollector.collect(self) def step(self): '''Advance the model by one step.''' self.schedule.step() #collecting data self.datacollector.collect(self)
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 VirusModel(Model): def __init__(self, num_nodes, avg_node_degree, prob_rewire, initial_outbreak_size, alpha, beta, gamma, delta, k, n): self.num_nodes = num_nodes self.avg_node_degree = avg_node_degree self.prob_rewire = prob_rewire self.G = nx.watts_strogatz_graph(n=self.num_nodes, k=avg_node_degree, p=prob_rewire) # self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.alpha = alpha self.beta = beta self.gamma = gamma self.delta = delta self.k = k self.n = n # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent(i, self, State.SUSCEPTIBLE, self.alpha, self.beta, self.gamma, self.delta, self.k, self.n) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes active_nodes = random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(active_nodes): a.state = State.ACTIVE self.datacollector = DataCollector( model_reporters={ "Infected": number_active, "Susceptible": number_susceptible, "Carrier": number_inactive, "Removed": number_removed, "Active Clustering": infective_clustering, "Exposed Clustering": exposed_clustering, "Infective Spread": infective_spread, "Exposed Spread": exposed_spread }) self.running = True self.datacollector.collect(self) def removed_susceptible_ratio(self): try: return number_state(self, State.REMOVED) / number_state( self, State.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class InfoSpread(Model): """A virus model with some number of agents""" def __init__( self, num_nodes=10, avg_node_degree=3, rewire_prob=.1, initial_outbreak_size=1, threshold=2, ): self.num_nodes = num_nodes self.G = nx.watts_strogatz_graph( n=self.num_nodes, k=avg_node_degree, p=rewire_prob) #G generate graph structure self.grid = NetworkGrid( self.G ) #grid is the Masa native defintion of space: a coorindate with specified topology on which agents sits and interact self.schedule = SimultaneousActivation(self) self.initial_outbreak_size = (initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes) self.datacollector = DataCollector({ "Infected": number_infected, "Susceptible": number_susceptible, }) # Create agents for i, node in enumerate(self.G.nodes()): a = User(i, self, "susceptible", threshold) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = "infected" neighbors_nodes = self.grid.get_neighbors(a.pos) for n in self.grid.get_cell_list_contents(neighbors_nodes): n.state = 'infected' self.running = True self.datacollector.collect(self) def proportion_infected(self): try: return number_state(self, "infected") / self.num_nodes except ZeroDivisionError: return math.inf def step(self): self.schedule.step( ) #this model updates with symoutanous schedule, meaning, # collect data self.datacollector.collect(self) def run_model(self, n): ''' could experiment terminating model here too''' for i in range(n): self.step()
class VirusModel(Model): def __init__(self, num_nodes, avg_node_degree, initial_outbreak_size, alpha, beta, gamma, delta, k, n): self.num_nodes = num_nodes self.avg_node_degree = avg_node_degree self.G = nx.barabasi_albert_graph(n=self.num_nodes,m=avg_node_degree) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.alpha = alpha self.beta = beta self.gamma = gamma self.delta = delta self.k=k self.n=n # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent(i, self, State.SUSCEPTIBLE, self.alpha, self.beta, self.gamma, self.delta, self.k, self.n) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes active_nodes = random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(active_nodes): a.state = State.ACTIVE self.datacollector = DataCollector( model_reporters={ "Infected": number_active, "Susceptible": number_susceptible, "Carrier": number_inactive, "Removed": number_removed, "Active Clustering": infective_clustering, "Exposed Clustering": exposed_clustering, "Infective Diffusion": infective_diffusion, "Exposed Diffusion": exposed_diffusion } ) self.running = True self.datacollector.collect(self) def removed_susceptible_ratio(self): try: return number_state(self, State.REMOVED) / number_state(self, State.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def tyrant_remove(self): if number_active(self) > 0: if random.random() < 0.002: actives = [a for a in self.grid.get_all_cell_contents() if a.state is State.ACTIVE] node_for_removal = random.sample(actives, 1) for a in node_for_removal: a.state = State.REMOVED # for a in self.grid.get_cell_list_contents(active_nodes): # a.state = State.ACTIVE def step(self): self.tyrant_remove() self.schedule.step() self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class InfectionModel(Model): """Over the last decade, an intensive effort has been undertaken to develop global surveillance networks that combat pandemics of emergent infectious diseases. To better understand the spread of a specific virus, computational models have been created. In contemporary mathematical epidemiology, agent-based modeling (ABM) represents the state-of-the-art for simulating complex epidemic systems. Taking into account transportation infrastructure of the simulated area, the mobility of the population, and evolutionary aspects of the disease, ABMs are by nature a “brute force” method that details a bottom-up stochastic approach to approximating the dynamics of real-world cases, i.e. individual-level behaviors define system-level results. To mirror the 2020 coronavirus outbreak, a non-linear network structure similar to a SIR mathematical model is applied. Then, a step further is taken to connect the outbreak with its economic implications in a medical cost-effectiveness analysis. In this study, the agent-based model is utilized to model the epidemiological and economic implications of viral pandemics, specifically the SARS-cov-2 virus. """ #ABSTRACT def __init__(self, N=100, avg_degree = 3, initial_outbreak = 1, ptrans=0.03, severe_rate=0.3, death_rate=0.01, screening_freq = 0.4, recovery_rate = 0.98, recovery_days=21, recovery_sd=7, screening_cost = 300, hospitalization_cost = 30000): self.num_agents = N prob = avg_degree / N self.G = nx.erdos_renyi_graph(n=self.num_agents, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak = initial_outbreak self.ptrans = ptrans self.recovery_days = recovery_days self.recovery_sd = recovery_sd self.recovery_rate = recovery_rate self.death_rate = death_rate self.screening_freq = screening_freq self.screening_cost = screening_cost self.hospitalization_cost = hospitalization_cost self.cost = 0 self.datacollector = DataCollector({"Infected": number_infected, "Susceptible": number_susceptible, "Removed": number_removed}) # Create agents for i, node in enumerate(self.G.nodes()): a = MyAgent(i, self, State.SUSCEPTIBLE, ptrans, severe_rate, screening_freq, recovery_rate, screening_cost, hospitalization_cost) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Initial infection infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = State.INFECTED self.running = True self.datacollector.collect(self) def removed_percentage(self): try: return number_state(self, State.REMOVED) / self.num_agents except ZeroDivisionError: return math.inf def step(self): self.schedule.step() self.datacollector.collect(self) # Costs def total_cost(self): #check and start back up here 9/20 screening = sum_state(self, State.INFECTED, self.screening_freq*self.screening_cost) hospitalization = sum_state(self, State.SEVERE, self.hospitalization_cost) self.cost += screening + hospitalization return (self.cost) def run_model(self,n=100): for i in range(n): self.step()
class InfoSpread(Model): """A virus model with some number of agents""" def __init__(self, num_nodes=10, avg_node_degree=3, rewire_prob=.1, initial_outbreak_size=1, threshold_fake=2, threshold_real=-2, fake_p=1, real_p=1): self.fake_p = fake_p self.real_p = real_p self.num_nodes = num_nodes self.G = nx.watts_strogatz_graph( n=self.num_nodes, k=avg_node_degree, p=rewire_prob) #G generate graph structure self.grid = NetworkGrid( self.G ) #grid is the Masa native defintion of space: a coorindate with specified topology on which agents sits and interact self.schedule = SimultaneousActivation(self) self.initial_outbreak_size = (initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes) self.datacollector = DataCollector({ "Infected_fake": number_infected_fake, "Infected_real": number_infected_real, }) # Create agents for i, node in enumerate(self.G.nodes()): a = User( i, self, 0, #make the state a int threshold_fake, threshold_real) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes, initial infection bug free infected_nodes_fake = self.random.sample(self.G.nodes(), self.initial_outbreak_size) infected_nodes_real = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes_fake): a.state = 1 neighbors_nodes = self.grid.get_neighbors(a.pos) for n in self.grid.get_cell_list_contents(neighbors_nodes): n.state = 1 for a in self.grid.get_cell_list_contents(infected_nodes_real): a.state = -1 neighbors_nodes = self.grid.get_neighbors(a.pos) for n in self.grid.get_cell_list_contents(neighbors_nodes): n.state = -1 """ state measures fake score!! the more negative the less likely to spread fake news also this model assumes that 1 one piece of real news can cancel out one piece of fake news This model can be modulated by changing the value of fake and real 2 the inital braeakout size of fake and real news are the same This can be chaged by introducing a different initial breaksize for real and fake news however this score is kepet the same intentionally because too uch complexity is not good for modeling """ self.running = True self.datacollector.collect(self) def proportion_infected_fake(self): try: tot_fake = 0 for i in self.grid.get_cell_list_contents(self.G): if i.state == 1: tot_fake += 1 return tot_fake / self.num_nodes except ZeroDivisionError: return math.inf def proportion_infected_real(self): try: tot_real = 0 for i in self.grid.get_cell_list_contents(self.G): if i.state == -1: tot_real += 1 return tot_real / self.num_nodes except ZeroDivisionError: return math.inf def step(self): self.schedule.step( ) #this model updates with symoutanous schedule, meaning, # collect data self.datacollector.collect(self) def run_model(self, n): ''' could experiment terminating model here too''' for i in range(n): self.step()
class Change_Res_Network(Model): """A virus model with some number of agents""" def __init__( self, num_nodes=1740, avg_node_degree=3, initial_with_clean=174, change_clean_chance=0.03, check_frequency=1.0, switch_back_chance=0.02, gain_resistance_chance=0.0, ): self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_with_clean = ( initial_with_clean if initial_with_clean <= num_nodes else num_nodes ) self.change_clean_chance = change_clean_chance self.check_frequency = check_frequency self.switch_back_chance = switch_back_chance self.gain_resistance_chance = gain_resistance_chance self.datacollector = DataCollector( { "has_clean": number_has_clean, "not_clean": number_not_clean, "refuses_clean": number_refuses_clean, } ) # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent( i, self, State.not_clean, self.change_clean_chance, self.check_frequency, self.switch_back_chance, self.gain_resistance_chance, ) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes has_clean_nodes = self.random.sample(self.G.nodes(), self.initial_with_clean) for a in self.grid.get_cell_list_contents(has_clean_nodes): a.state = State.has_clean self.running = True self.datacollector.collect(self) def refuses_clean_not_clean_ratio(self): try: return number_state(self, State.refuses_clean) / number_state( self, State.not_clean ) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class HostNetwork(Model): # id generator to track run number in batch run data id_gen = itertools.count(1) rate_denominator = 1000000 def __init__( self, num_nodes, avg_node_degree, initial_outbreak_size, prob_spread_virus_gamma_shape, prob_spread_virus_gamma_scale, prob_spread_virus_gamma_loc, prob_spread_virus_gamma_magnitude_multiplier, prob_recover_gamma_shape, prob_recover_gamma_scale, prob_recover_gamma_loc, prob_recover_gamma_magnitude_multiplier, prob_virus_kill_host_gamma_shape, prob_virus_kill_host_gamma_scale, prob_virus_kill_host_gamma_loc, prob_virus_kill_host_gamma_magnitude_multiplier, prob_infectious_no_to_mild_symptom_gamma_shape, prob_infectious_no_to_mild_symptom_gamma_scale, prob_infectious_no_to_mild_symptom_gamma_loc, prob_infectious_no_to_mild_symptom_gamma_magnitude_multiplier, prob_infectious_no_to_severe_symptom_gamma_shape, prob_infectious_no_to_severe_symptom_gamma_scale, prob_infectious_no_to_severe_symptom_gamma_loc, prob_infectious_no_to_severe_symptom_gamma_magnitude_multiplier, prob_infectious_no_to_critical_symptom_gamma_shape, prob_infectious_no_to_critical_symptom_gamma_scale, prob_infectious_no_to_critical_symptom_gamma_loc, prob_infectious_no_to_critical_symptom_gamma_magnitude_multiplier, prob_infectious_mild_to_no_symptom_gamma_shape, prob_infectious_mild_to_no_symptom_gamma_scale, prob_infectious_mild_to_no_symptom_gamma_loc, prob_infectious_mild_to_no_symptom_gamma_magnitude_multiplier, prob_infectious_mild_to_severe_symptom_gamma_shape, prob_infectious_mild_to_severe_symptom_gamma_scale, prob_infectious_mild_to_severe_symptom_gamma_loc, prob_infectious_mild_to_severe_symptom_gamma_magnitude_multiplier, prob_infectious_mild_to_critical_symptom_gamma_shape, prob_infectious_mild_to_critical_symptom_gamma_scale, prob_infectious_mild_to_critical_symptom_gamma_loc, prob_infectious_mild_to_critical_symptom_gamma_magnitude_multiplier, prob_infectious_severe_to_no_symptom_gamma_shape, prob_infectious_severe_to_no_symptom_gamma_scale, prob_infectious_severe_to_no_symptom_gamma_loc, prob_infectious_severe_to_no_symptom_gamma_magnitude_multiplier, prob_infectious_severe_to_mild_symptom_gamma_shape, prob_infectious_severe_to_mild_symptom_gamma_scale, prob_infectious_severe_to_mild_symptom_gamma_loc, prob_infectious_severe_to_mild_symptom_gamma_magnitude_multiplier, prob_infectious_severe_to_critical_symptom_gamma_shape, prob_infectious_severe_to_critical_symptom_gamma_scale, prob_infectious_severe_to_critical_symptom_gamma_loc, prob_infectious_severe_to_critical_symptom_gamma_magnitude_multiplier, prob_infectious_critical_to_no_symptom_gamma_shape, prob_infectious_critical_to_no_symptom_gamma_scale, prob_infectious_critical_to_no_symptom_gamma_loc, prob_infectious_critical_to_no_symptom_gamma_magnitude_multiplier, prob_infectious_critical_to_mild_symptom_gamma_shape, prob_infectious_critical_to_mild_symptom_gamma_scale, prob_infectious_critical_to_mild_symptom_gamma_loc, prob_infectious_critical_to_mild_symptom_gamma_magnitude_multiplier, prob_infectious_critical_to_severe_symptom_gamma_shape, prob_infectious_critical_to_severe_symptom_gamma_scale, prob_infectious_critical_to_severe_symptom_gamma_loc, prob_infectious_critical_to_severe_symptom_gamma_magnitude_multiplier, prob_recovered_no_to_mild_complication, prob_recovered_no_to_severe_complication, prob_recovered_mild_to_no_complication, prob_recovered_mild_to_severe_complication, prob_recovered_severe_to_no_complication, prob_recovered_severe_to_mild_complication, prob_gain_immunity, hospital_bed_capacity_as_percent_of_population, hospital_bed_cost_per_day, icu_bed_capacity_as_percent_of_population, icu_bed_cost_per_day, ventilator_capacity_as_percent_of_population, ventilator_cost_per_day, drugX_capacity_as_percent_of_population, drugX_cost_per_day, ): self.uid = next(self.id_gen) self.set_network_seed = 888 # Setting: Accurately set to None or specific seed self.set_initial_infectious_node_seed = 888 # Setting: Accurately set to None or specific seed self._current_timer = 0 self._last_n_time_unit_for_mean_r0 = 10 # SETTING: Smoothing mean R0 self.num_nodes = num_nodes self.avg_node_degree = avg_node_degree prob = self.avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob, seed=self.set_network_seed) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes self.all_agents_new_infection_tracker = {} self.all_agents_new_tested_as_true_positive = [] self.cumulative_infectious_cases = self.initial_outbreak_size self.cumulative_dead_cases = 0 self.cumulative_test_done = 0 self.cumulative_infectious_test_confirmed_cases = 0 self.cumulative_dead_test_confirmed_cases = 0 self.cumulative_hospital_bed_use_in_new_host_counts = 0 self.cumulative_icu_bed_use_in_new_host_counts = 0 self.cumulative_ventilator_use_in_new_host_counts = 0 self.cumulative_drugX_use_in_new_host_counts = 0 self.cumulative_hospital_bed_use_in_days = 0 self.cumulative_icu_bed_use_in_days = 0 self.cumulative_ventilator_use_in_days = 0 self.cumulative_drugX_use_in_days = 0 self.prob_spread_virus_gamma_shape = prob_spread_virus_gamma_shape self.prob_spread_virus_gamma_scale = prob_spread_virus_gamma_scale self.prob_spread_virus_gamma_loc = prob_spread_virus_gamma_loc self.prob_spread_virus_gamma_magnitude_multiplier = prob_spread_virus_gamma_magnitude_multiplier self.prob_recover_gamma_shape = prob_recover_gamma_shape self.prob_recover_gamma_scale = prob_recover_gamma_scale self.prob_recover_gamma_loc = prob_recover_gamma_loc self.prob_recover_gamma_magnitude_multiplier = prob_recover_gamma_magnitude_multiplier self.prob_virus_kill_host_gamma_shape = prob_virus_kill_host_gamma_shape self.prob_virus_kill_host_gamma_scale = prob_virus_kill_host_gamma_scale self.prob_virus_kill_host_gamma_loc = prob_virus_kill_host_gamma_loc self.prob_virus_kill_host_gamma_magnitude_multiplier = prob_virus_kill_host_gamma_magnitude_multiplier self.prob_infectious_no_to_mild_symptom_gamma_shape = prob_infectious_no_to_mild_symptom_gamma_shape self.prob_infectious_no_to_mild_symptom_gamma_scale = prob_infectious_no_to_mild_symptom_gamma_scale self.prob_infectious_no_to_mild_symptom_gamma_loc = prob_infectious_no_to_mild_symptom_gamma_loc self.prob_infectious_no_to_mild_symptom_gamma_magnitude_multiplier = prob_infectious_no_to_mild_symptom_gamma_magnitude_multiplier self.prob_infectious_no_to_severe_symptom_gamma_shape = prob_infectious_no_to_severe_symptom_gamma_shape self.prob_infectious_no_to_severe_symptom_gamma_scale = prob_infectious_no_to_severe_symptom_gamma_scale self.prob_infectious_no_to_severe_symptom_gamma_loc = prob_infectious_no_to_severe_symptom_gamma_loc self.prob_infectious_no_to_severe_symptom_gamma_magnitude_multiplier = prob_infectious_no_to_severe_symptom_gamma_magnitude_multiplier self.prob_infectious_no_to_critical_symptom_gamma_shape = prob_infectious_no_to_critical_symptom_gamma_shape self.prob_infectious_no_to_critical_symptom_gamma_scale = prob_infectious_no_to_critical_symptom_gamma_scale self.prob_infectious_no_to_critical_symptom_gamma_loc = prob_infectious_no_to_critical_symptom_gamma_loc self.prob_infectious_no_to_critical_symptom_gamma_magnitude_multiplier = prob_infectious_no_to_critical_symptom_gamma_magnitude_multiplier self.prob_infectious_mild_to_no_symptom_gamma_shape = prob_infectious_mild_to_no_symptom_gamma_shape self.prob_infectious_mild_to_no_symptom_gamma_scale = prob_infectious_mild_to_no_symptom_gamma_scale self.prob_infectious_mild_to_no_symptom_gamma_loc = prob_infectious_mild_to_no_symptom_gamma_loc self.prob_infectious_mild_to_no_symptom_gamma_magnitude_multiplier = prob_infectious_mild_to_no_symptom_gamma_magnitude_multiplier self.prob_infectious_mild_to_severe_symptom_gamma_shape = prob_infectious_mild_to_severe_symptom_gamma_shape self.prob_infectious_mild_to_severe_symptom_gamma_scale = prob_infectious_mild_to_severe_symptom_gamma_scale self.prob_infectious_mild_to_severe_symptom_gamma_loc = prob_infectious_mild_to_severe_symptom_gamma_loc self.prob_infectious_mild_to_severe_symptom_gamma_magnitude_multiplier = prob_infectious_mild_to_severe_symptom_gamma_magnitude_multiplier self.prob_infectious_mild_to_critical_symptom_gamma_shape = prob_infectious_mild_to_critical_symptom_gamma_shape self.prob_infectious_mild_to_critical_symptom_gamma_scale = prob_infectious_mild_to_critical_symptom_gamma_scale self.prob_infectious_mild_to_critical_symptom_gamma_loc = prob_infectious_mild_to_critical_symptom_gamma_loc self.prob_infectious_mild_to_critical_symptom_gamma_magnitude_multiplier = prob_infectious_mild_to_critical_symptom_gamma_magnitude_multiplier self.prob_infectious_severe_to_no_symptom_gamma_shape = prob_infectious_severe_to_no_symptom_gamma_shape self.prob_infectious_severe_to_no_symptom_gamma_scale = prob_infectious_severe_to_no_symptom_gamma_scale self.prob_infectious_severe_to_no_symptom_gamma_loc = prob_infectious_severe_to_no_symptom_gamma_loc self.prob_infectious_severe_to_no_symptom_gamma_magnitude_multiplier = prob_infectious_severe_to_no_symptom_gamma_magnitude_multiplier self.prob_infectious_severe_to_mild_symptom_gamma_shape = prob_infectious_severe_to_mild_symptom_gamma_shape self.prob_infectious_severe_to_mild_symptom_gamma_scale = prob_infectious_severe_to_mild_symptom_gamma_scale self.prob_infectious_severe_to_mild_symptom_gamma_loc = prob_infectious_severe_to_mild_symptom_gamma_loc self.prob_infectious_severe_to_mild_symptom_gamma_magnitude_multiplier = prob_infectious_severe_to_mild_symptom_gamma_magnitude_multiplier self.prob_infectious_severe_to_critical_symptom_gamma_shape = prob_infectious_severe_to_critical_symptom_gamma_shape self.prob_infectious_severe_to_critical_symptom_gamma_scale = prob_infectious_severe_to_critical_symptom_gamma_scale self.prob_infectious_severe_to_critical_symptom_gamma_loc = prob_infectious_severe_to_critical_symptom_gamma_loc self.prob_infectious_severe_to_critical_symptom_gamma_magnitude_multiplier = prob_infectious_severe_to_critical_symptom_gamma_magnitude_multiplier self.prob_infectious_critical_to_no_symptom_gamma_shape = prob_infectious_critical_to_no_symptom_gamma_shape self.prob_infectious_critical_to_no_symptom_gamma_scale = prob_infectious_critical_to_no_symptom_gamma_scale self.prob_infectious_critical_to_no_symptom_gamma_loc = prob_infectious_critical_to_no_symptom_gamma_loc self.prob_infectious_critical_to_no_symptom_gamma_magnitude_multiplier = prob_infectious_critical_to_no_symptom_gamma_magnitude_multiplier self.prob_infectious_critical_to_mild_symptom_gamma_shape = prob_infectious_critical_to_mild_symptom_gamma_shape self.prob_infectious_critical_to_mild_symptom_gamma_scale = prob_infectious_critical_to_mild_symptom_gamma_scale self.prob_infectious_critical_to_mild_symptom_gamma_loc = prob_infectious_critical_to_mild_symptom_gamma_loc self.prob_infectious_critical_to_mild_symptom_gamma_magnitude_multiplier = prob_infectious_critical_to_mild_symptom_gamma_magnitude_multiplier self.prob_infectious_critical_to_severe_symptom_gamma_shape = prob_infectious_critical_to_severe_symptom_gamma_shape self.prob_infectious_critical_to_severe_symptom_gamma_scale = prob_infectious_critical_to_severe_symptom_gamma_scale self.prob_infectious_critical_to_severe_symptom_gamma_loc = prob_infectious_critical_to_severe_symptom_gamma_loc self.prob_infectious_critical_to_severe_symptom_gamma_magnitude_multiplier = prob_infectious_critical_to_severe_symptom_gamma_magnitude_multiplier self.prob_spread_virus_dist = GammaProbabilityGenerator( shape=self.prob_spread_virus_gamma_shape, scale=self.prob_spread_virus_gamma_scale, loc=self.prob_spread_virus_gamma_loc, magnitude_multiplier=self. prob_spread_virus_gamma_magnitude_multiplier, ) self.prob_recover_dist = GammaProbabilityGenerator( shape=self.prob_recover_gamma_shape, scale=self.prob_recover_gamma_scale, loc=self.prob_recover_gamma_loc, magnitude_multiplier=self.prob_recover_gamma_magnitude_multiplier, ) self.prob_virus_kill_host_dist = GammaProbabilityGenerator( shape=self.prob_virus_kill_host_gamma_shape, scale=self.prob_virus_kill_host_gamma_scale, loc=self.prob_virus_kill_host_gamma_loc, magnitude_multiplier=self. prob_virus_kill_host_gamma_magnitude_multiplier, ) self.prob_infectious_no_to_mild_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_no_to_mild_symptom_gamma_shape, scale=self.prob_infectious_no_to_mild_symptom_gamma_scale, loc=self.prob_infectious_no_to_mild_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_no_to_mild_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_no_to_severe_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_no_to_severe_symptom_gamma_shape, scale=self.prob_infectious_no_to_severe_symptom_gamma_scale, loc=self.prob_infectious_no_to_severe_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_no_to_severe_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_no_to_critical_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_no_to_critical_symptom_gamma_shape, scale=self.prob_infectious_no_to_critical_symptom_gamma_scale, loc=self.prob_infectious_no_to_critical_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_no_to_critical_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_mild_to_no_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_mild_to_no_symptom_gamma_shape, scale=self.prob_infectious_mild_to_no_symptom_gamma_scale, loc=self.prob_infectious_mild_to_no_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_mild_to_no_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_mild_to_severe_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_mild_to_severe_symptom_gamma_shape, scale=self.prob_infectious_mild_to_severe_symptom_gamma_scale, loc=self.prob_infectious_mild_to_severe_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_mild_to_severe_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_mild_to_critical_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_mild_to_critical_symptom_gamma_shape, scale=self.prob_infectious_mild_to_critical_symptom_gamma_scale, loc=self.prob_infectious_mild_to_critical_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_mild_to_critical_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_severe_to_no_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_severe_to_no_symptom_gamma_shape, scale=self.prob_infectious_severe_to_no_symptom_gamma_scale, loc=self.prob_infectious_severe_to_no_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_severe_to_no_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_severe_to_mild_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_severe_to_mild_symptom_gamma_shape, scale=self.prob_infectious_severe_to_mild_symptom_gamma_scale, loc=self.prob_infectious_severe_to_mild_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_severe_to_mild_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_severe_to_critical_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_severe_to_critical_symptom_gamma_shape, scale=self.prob_infectious_severe_to_critical_symptom_gamma_scale, loc=self.prob_infectious_severe_to_critical_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_severe_to_critical_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_critical_to_no_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_critical_to_no_symptom_gamma_shape, scale=self.prob_infectious_critical_to_no_symptom_gamma_scale, loc=self.prob_infectious_critical_to_no_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_critical_to_no_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_critical_to_mild_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_critical_to_mild_symptom_gamma_shape, scale=self.prob_infectious_critical_to_mild_symptom_gamma_scale, loc=self.prob_infectious_critical_to_mild_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_critical_to_mild_symptom_gamma_magnitude_multiplier, ) self.prob_infectious_critical_to_severe_symptom_dist = GammaProbabilityGenerator( shape=self.prob_infectious_critical_to_severe_symptom_gamma_shape, scale=self.prob_infectious_critical_to_severe_symptom_gamma_scale, loc=self.prob_infectious_critical_to_severe_symptom_gamma_loc, magnitude_multiplier=self. prob_infectious_critical_to_severe_symptom_gamma_magnitude_multiplier, ) self.prob_recovered_no_to_mild_complication = prob_recovered_no_to_mild_complication self.prob_recovered_no_to_severe_complication = prob_recovered_no_to_severe_complication self.prob_recovered_mild_to_no_complication = prob_recovered_mild_to_no_complication self.prob_recovered_mild_to_severe_complication = prob_recovered_mild_to_severe_complication self.prob_recovered_severe_to_no_complication = prob_recovered_severe_to_no_complication self.prob_recovered_severe_to_mild_complication = prob_recovered_severe_to_mild_complication self.prob_gain_immunity = prob_gain_immunity self.hospital_bed_capacity_as_percent_of_population = hospital_bed_capacity_as_percent_of_population self.hospital_bed_cost_per_day = hospital_bed_cost_per_day self.hospital_bed_current_load = 0 self.hospital_bed_use_day_tracker = 0 self.icu_bed_capacity_as_percent_of_population = icu_bed_capacity_as_percent_of_population self.icu_bed_cost_per_day = icu_bed_cost_per_day self.icu_bed_current_load = 0 self.icu_bed_use_day_tracker = 0 self.ventilator_capacity_as_percent_of_population = ventilator_capacity_as_percent_of_population self.ventilator_cost_per_day = ventilator_cost_per_day self.ventilator_current_load = 0 self.ventilator_use_day_tracker = 0 self.drugX_capacity_as_percent_of_population = drugX_capacity_as_percent_of_population self.drugX_cost_per_day = drugX_cost_per_day self.drugX_current_load = 0 self.drugX_use_day_tracker = 0 self.clinical_resource = ClinicalResource( 1, self, self.hospital_bed_capacity_as_percent_of_population, self.hospital_bed_cost_per_day, self.hospital_bed_current_load, self.hospital_bed_use_day_tracker, self.icu_bed_capacity_as_percent_of_population, self.icu_bed_cost_per_day, self.icu_bed_current_load, self.icu_bed_use_day_tracker, self.ventilator_capacity_as_percent_of_population, self.ventilator_cost_per_day, self.ventilator_current_load, self.ventilator_use_day_tracker, self.drugX_capacity_as_percent_of_population, self.drugX_cost_per_day, self.drugX_current_load, self.drugX_use_day_tracker, ) self.testing = Testing( 1, self, agent=None, prob_tested_for_no_symptom=[0.005, 0.01, 0.01], prob_tested_for_mild_symptom=[0.005, 0.01, 0.01], prob_tested_for_severe_symptom=[0.01, 0.03, 0.05], prob_tested_for_critical_symptom=[0.01, 0.03, 0.05], test_sensitivity=[0.89, 0.95, 0.95], test_specificity=[0.95, 0.99, 0.99], time_period=[(0, 25), (26, 60), (60, 999)], current_time=None, on_switch=True) self.social_distancing = SocialDistancing(1, self, edge_threshold=[0.25], time_period=[(50, 999)], current_time=None, on_switch=False) self.vaccine = Vaccine(1, self, agent=None, prob_vaccinated=[0.10], vaccine_success_rate=[0.80], time_period=[(50, 999)], current_time=None, on_switch=False) self.model_reporters_dict = { 'Time': return_time, 'Total N': return_total_n, 'Test done': number_test_done, 'Susceptible': number_susceptible, 'Recovered': number_recovered, 'Infectious': number_infectious, 'Dead': number_dead, 'Test-confirmed infectious': number_infectious_test_confirmed, 'Test-confirmed dead': number_dead_test_confirmed, 'Cumulative test done': cumulative_total_test_done, 'Cumulative infectious': cumulative_total_infectious, 'Cumulative dead': cumulative_total_dead, 'Cumulative test-confirmed infectious': cumulative_total_infectious_test_confirmed, 'Cumulative test-confirmed dead': cumulative_total_dead_test_confirmed, 'Rate per 1M cumulative test done': rate_cumulative_test_done, 'Rate per 1M cumulative infectious': rate_cumulative_infectious, 'Rate per 1M cumulative dead': rate_cumulative_dead, 'Rate per 1M cumulative test-confirmed infectious': rate_cumulative_infectious_test_confirmed, 'Rate per 1M cumulative test-confirmed dead': rate_cumulative_dead_test_confirmed, 'Infectious-no symptom': number_infectious_no_symptom, 'Infectious-mild symptom': number_infectious_mild_symptom, 'Infectious-severe symptom': number_infectious_severe_symptom, 'Infectious-critical symptom': number_infectious_critical_symptom, 'Infectious using non-ICU hospital bed': number_infectious_using_hospital_bed, 'Infectious using ICU hospital bed': number_infectious_using_icu_bed, 'Infectious using ventilator': number_infectious_using_ventilator, 'Recovered-no complication': number_recovered_no_complication, 'Recovered-mild complication': number_recovered_mild_complication, 'Recovered-severe complication': number_recovered_severe_complication, 'Recovered using DrugX': number_recovered_using_drugX, 'Mean R0': mean_r0, } self.datacollector = DataCollector( model_reporters=self.model_reporters_dict) # Create agents for i, node in enumerate(self.G.nodes()): agent = HostAgent( i, self, DiseaseHealthState.SUSCEPTIBLE, RecoveredImmunityState.TBD, self.prob_recovered_no_to_mild_complication, self.prob_recovered_no_to_severe_complication, self.prob_recovered_mild_to_no_complication, self.prob_recovered_mild_to_severe_complication, self.prob_recovered_severe_to_no_complication, self.prob_recovered_severe_to_mild_complication, self.prob_gain_immunity, self.clinical_resource, self.social_distancing, self.vaccine, self.testing, ) self.schedule.add(agent) # Add the agent to the node self.grid.place_agent(agent, node) # Assign random weights (float: 0 to 1) to each connection for u, v in self.G.edges(): self.G[u][v]['weight'] = random.random() # Infect some nodes if self.set_initial_infectious_node_seed: self.random.seed(self.set_initial_infectious_node_seed) infectious_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for agent in self.grid.get_cell_list_contents(infectious_nodes): agent.disease_health_state = DiseaseHealthState.INFECTIOUS agent._timer_since_beginning_of_last_infection = 0 if agent.disease_health_state is DiseaseHealthState.INFECTIOUS: agent.infectious_symptom_state = InfectiousSymptomState.NO_SYMPTOM self.running = True self.datacollector.collect(self) def ratio_infectious_susceptible(self): try: return number_disease_health_state( self, DiseaseHealthState.INFECTIOUS) / number_disease_health_state( self, DiseaseHealthState.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def ratio_recovered_susceptible(self): try: return number_disease_health_state( self, DiseaseHealthState.RECOVERED) / number_disease_health_state( self, DiseaseHealthState.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def ratio_dead_susceptible(self): try: return number_disease_health_state( self, DiseaseHealthState.DEAD) / number_disease_health_state( self, DiseaseHealthState.SUSCEPTIBLE) except ZeroDivisionError: return math.inf def count_total_host(self): return number_disease_health_state(self, DiseaseHealthState.SUSCEPTIBLE) + number_disease_health_state( self, DiseaseHealthState.INFECTIOUS) + \ number_disease_health_state(self, DiseaseHealthState.DEAD) + number_disease_health_state( self, DiseaseHealthState.RECOVERED) def count_total_living_host(self): return number_disease_health_state( self, DiseaseHealthState.SUSCEPTIBLE) + number_disease_health_state( self, DiseaseHealthState.INFECTIOUS) + number_disease_health_state( self, DiseaseHealthState.RECOVERED) def rate_infectious(self): return ( number_disease_health_state(self, DiseaseHealthState.INFECTIOUS) / self.count_total_host()) * self.rate_denominator def rate_recovered(self): return (number_disease_health_state(self, DiseaseHealthState.RECOVERED) / self.count_total_host()) * self.rate_denominator def rate_dead(self): return (number_disease_health_state(self, DiseaseHealthState.DEAD) / self.count_total_host()) * self.rate_denominator def rate_infectious_test_confirmed(self): return (number_disease_health_state_test_confirmed( self, DiseaseHealthState.INFECTIOUS) / self.count_total_host()) * self.rate_denominator def rate_dead_test_confirmed(self): return (number_disease_health_state_test_confirmed( self, DiseaseHealthState.DEAD) / self.count_total_host()) * self.rate_denominator def cumulative_total_test_done(self): return self.cumulative_test_done def cumulative_total_infectious(self): return self.cumulative_infectious_cases def cumulative_total_dead(self): return self.cumulative_dead_cases def cumulative_total_infectious_test_confirmed(self): return self.cumulative_infectious_test_confirmed_cases def cumulative_total_dead_test_confirmed(self): return self.cumulative_dead_test_confirmed_cases def cumulative_total_new_hosts_of_hospital_bed_use(self): return self.cumulative_hospital_bed_use_in_new_host_counts def cumulative_total_new_hosts_of_icu_bed_use(self): return self.cumulative_icu_bed_use_in_new_host_counts def cumulative_total_new_hosts_of_ventilator_use(self): return self.cumulative_ventilator_use_in_new_host_counts def cumulative_total_new_hosts_of_drugX_use(self): return self.cumulative_drugX_use_in_new_host_counts def cumulative_total_days_of_hospital_bed_use(self): return self.cumulative_hospital_bed_use_in_days def cumulative_total_days_of_icu_bed_use(self): return self.cumulative_icu_bed_use_in_days def cumulative_total_days_of_ventilator_use(self): return self.cumulative_ventilator_use_in_days def cumulative_total_days_of_drugX_use(self): return self.cumulative_drugX_use_in_days def cumulative_total_costs_in_hospital_bed_use(self): return self.cumulative_hospital_bed_use_in_days * self.hospital_bed_cost_per_day def cumulative_total_costs_in_icu_bed_use(self): return self.cumulative_icu_bed_use_in_days * self.icu_bed_cost_per_day def cumulative_total_costs_in_ventilator_use(self): return self.cumulative_ventilator_use_in_days * self.ventilator_cost_per_day def cumulative_total_costs_in_drugX_use(self): return self.cumulative_drugX_use_in_days * self.drugX_cost_per_day def rate_cumulative_test_done(self): return (self.cumulative_total_test_done() / self.count_total_host()) * self.rate_denominator def rate_cumulative_infectious(self): return (self.cumulative_total_infectious() / self.count_total_host()) * self.rate_denominator def rate_cumulative_dead(self): return (self.cumulative_total_dead() / self.count_total_host()) * self.rate_denominator def rate_cumulative_infectious_test_confirmed(self): return (self.cumulative_total_infectious_test_confirmed() / self.count_total_host()) * self.rate_denominator def rate_cumulative_dead_test_confirmed(self): return (self.cumulative_total_dead_test_confirmed() / self.count_total_host()) * self.rate_denominator def rate_infectious_any_symptom(self): return ((number_infectious_mild_symptom(self) + number_infectious_severe_symptom(self) + number_infectious_critical_symptom(self)) / self.count_total_living_host()) * self.rate_denominator def rate_recovered_any_complication(self): return ((number_recovered_mild_complication(self) + number_recovered_severe_complication(self)) / self.count_total_living_host()) * self.rate_denominator def rate_infectious_using_hospital_bed(self): try: return (number_infectious_using_hospital_bed(self) / number_infectious(self) * 1000) except ZeroDivisionError: return math.inf def rate_infectious_using_icu_bed(self): try: return (number_infectious_using_icu_bed(self) / number_infectious(self) * 1000) except ZeroDivisionError: return math.inf def rate_infectious_using_ventilator(self): try: return (number_infectious_using_ventilator(self) / number_infectious(self) * 1000) except ZeroDivisionError: return math.inf def rate_recovered_using_drugX(self): try: return (number_recovered_using_drugX(self) / number_recovered(self) * 1000) except ZeroDivisionError: return math.inf def mean_age(self): count = 0 total_age = 0 for agent in self.grid.get_cell_list_contents(self.G.nodes()): if agent.disease_health_state is not DiseaseHealthState.DEAD: count += 1 total_age += agent.age try: return total_age / count except ZeroDivisionError: return math.inf def proportion_sex(self): count = 0 total_male = 0 total_female = 0 for agent in self.grid.get_cell_list_contents(self.G.nodes()): if agent.disease_health_state is not DiseaseHealthState.DEAD: count += 1 if agent.sex is 'M': total_male += 1 elif agent.sex is 'F': total_female += 1 try: return {'M': total_male / count, 'F': total_female / count} except ZeroDivisionError: return {'M': math.inf, 'F': math.inf} def mean_r0(self): number_infectious_active_in_last_n_time_units = 0 number_new_infection_in_last_n_time_units = 0 last_n_time_unit = self._last_n_time_unit_for_mean_r0 for agent, content in self.all_agents_new_infection_tracker.items(): for time_of_new_infection, number_of_new_infection in content.items( ): initial_time = self._current_timer - last_n_time_unit if initial_time >= 0: if int(time_of_new_infection) in range( initial_time, self._current_timer): number_infectious_active_in_last_n_time_units += 1 number_new_infection_in_last_n_time_units += number_of_new_infection else: number_infectious_active_in_last_n_time_units += 1 number_new_infection_in_last_n_time_units += number_of_new_infection try: return number_new_infection_in_last_n_time_units / number_infectious_active_in_last_n_time_units except ZeroDivisionError: return 0 def step(self): self._current_timer += 1 self.schedule.step() self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class KalickHamilton(Model): """A model following Andre Grow's Netlogo tutorial of Kalick Hamilton 1986 replicated using Mesa""" def __init__(self, seed=None, num_nodes=50, preference='attractiveness', mean_male=5, sd_male=1, mean_female=5, sd_female=1, corr_results=pd.DataFrame()): self.num_nodes = num_nodes self.preference = preference self.mean_male = mean_male self.sd_male = sd_male self.mean_female = mean_female self.sd_female = sd_female self.corr_results = corr_results self.step_count = 0 self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=0) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.datacollector = DataCollector(model_reporters={ "number_single": number_single, "number_female": number_female, "number_union": number_union, "mean_attractiveness": mean_attractiveness, "corr_results": calculate_correlations }, agent_reporters={ "attractiveness": lambda x: x.A, "sex": lambda x: x.S, "relationship": lambda x: x.R, }) # Create agents for i, node in enumerate(self.G.nodes()): person = Human( i, self, "MALE", 0, "SINGLE", ) self.schedule.add(person) # Add the agent to the node self.grid.place_agent(person, node) # convert half of agents to "FEMALE" # this need number of agents to always be dividable by 2 # as set in the user-settable slider female_nodes = self.random.sample(self.G.nodes(), (int(self.num_nodes / 2))) for a in self.grid.get_cell_list_contents(female_nodes): a.S = "FEMALE" # here assign attractiveness based on normal distributions for a in self.schedule.agents: if a.S == 'MALE': A2use = np.random.normal(self.mean_male, self.sd_male, 1)[0] while A2use < 1 or A2use > 10: A2use = np.random.normal(self.mean_male, self.sd_male, 1)[0] a.A = A2use else: A2use = np.random.normal(self.mean_female, self.sd_female, 1)[0] while A2use < 1 or A2use > 10: A2use = np.random.normal(self.mean_female, self.sd_female, 1)[0] a.A = A2use self.running = True self.datacollector.collect(self) def single_union_ratio(self): try: return number_state(self, "SINGLE") / number_state(self, "UNION") except ZeroDivisionError: return math.inf def do_match_singles(self): for a in self.schedule.agents: if a.S == 'MALE' and a.R == 'SINGLE': a.date_someone() def do_calculate_decision_probabilities(self): for a in self.schedule.agents: if a.R == 'SINGLE': a.calculate_decision_probabilities() def do_union_decisions(self): for a in self.schedule.agents: if a.S == 'MALE' and a.R == 'SINGLE': a.take_union_decision() def step(self): # add to step counter self.step_count += 1 self.schedule.step() self.do_match_singles() self.do_calculate_decision_probabilities() self.do_union_decisions() # collect data self.datacollector.collect(self) # if all agents in union or 51 steps past, stop. if number_single(self) == 0 or self.step_count == 51: self.running = False def run_model(self, n): for i in range(n): self.step()
class SegregationModelModel(Model): def __init__(self, datacollector=None): super().__init__() # work from directory this file is in os.chdir(os.path.dirname(os.path.realpath(__file__))) self.schedule = SimultaneousActivation(self) self.G = nx.Graph() self.time = 0 # simple iteration counter self._generate_sites() self.grid = NetworkGrid(self.G) # make a dictionary of {hash: site} values for easy relation lookups in agent generation self.site_hashes = {h: s for s, h in dict( self.G.nodes.data('hash')).items()} self._generate_agents() self.vita_groups = [] self.datacollector = datacollector def step(self): if self.datacollector: self.datacollector.collect(self) else: warnings.warn('This Model has no DataCollector! You may want to add one in the `datacollector` attribute ' 'before running the model') self.schedule.step() while self.vita_groups: a = self.vita_groups.pop() a.unique_id = self.next_id() a.model = self self.schedule.add(a) for a in self.schedule.agents: if a.get('__void__', False): self.grid._remove_agent(a, a.pos) self.schedule.remove(a) self.time += 1 # ------------------------- INITIALIZATION HELPERS ------------------------- def _generate_agents(self): """ Called once during __init__ to create appropriate groups from the original simulation's model and add them to the model grid. Loads group data from the JSON file created during translation. """ with open("SegregationModelGroups.json", 'r') as file: j = json.load(file) for group in j: for _ in range(group['m']): a = SegregationModelAgent( self.next_id(), self, group['attr'], group['rel']) self.schedule.add(a) def _generate_sites(self): """ Called once during __init__ to load the original simulation's sites into the networkx graph. Loads site data from a JSON file created during translation. """ with open("SegregationModelSites.json", 'r') as file: j = json.load(file) for site in j: self.G.add_node( str(site['name']), hash=site['hash'], rel_name=site['rel_name']) for k, v in site['attr'].items(): self.G.nodes[str(site['name'])][k] = v # ------------------------- RUNTIME FUNCTIONS ------------------------- def get_attr(self, agent_or_node, name=None): """ Retrieves an attribute of a Mesa Agent or NetworkGrid node. :param name: A string containing the attribute to retrieve, or None :param agent_or_node: A Mesa Agent or a string corresponding to a node in the NetworkGrid :return: If agent_or_node is a string, returns the named attribute represented by it, or the node's entire attribute dictionary if name is None (note: this includes the special 'agent' attribute) If agent_or_node is an Agent, returns the named attribute of that Agent """ name = mpi(name) if name is not None else name if isinstance(agent_or_node, str): node_dict = self.grid.G.nodes[agent_or_node] return node_dict.get(name) if name is not None else node_dict elif isinstance(agent_or_node, Agent): # return getattr(agent_or_node, name, agent_or_node.namespace[name]) return agent_or_node.get(name) else: raise TypeError( f"get_attr expected a str or Agent for agent_or_node, but received {type(agent_or_node)}") def get_groups(self, node_or_model, qry=None): """ Returns a list of agents at the node or the entire model that satisfy the qry. :param node_or_model: A string corresponding to a node in the NetworkGrid, or a Mesa Model :param qry: a GroupQry namedtuple :return: a list of agents at the node satisfying the qry. """ if isinstance(node_or_model, Model): agents = node_or_model.schedule.agents elif isinstance(node_or_model, str): agents = self.grid.get_cell_list_contents([node_or_model]) else: raise TypeError( f"get_groups expects a str or Model for node_or_model, but received {type(node_or_model)}") return [a for a in agents if a.matches_qry(qry)] # if not qry: # return agents # # the code below is REALLY PAINFUL... replacing it with 'return agents` makes the code run like 20x faster # elif qry.full: # return [a for a in agents # if qry.attr.items() == {k: getattr(a, k) for k in a._attr}.items() # and qry.rel.items() == {k: getattr(a, k) for k in a._rel}.items() # and all([fn(a) for fn in qry.cond])] # else: # return [a for a in agents # if qry.attr.items() <= {k: getattr(a, k) for k in a._attr}.items() # and qry.rel.items() <= {k: getattr(a, k) for k in a._rel}.items() # and all([fn(a) for fn in qry.cond])] def get_mass(self, agent_node_model, qry=None): """ If agent_node_model is an agent, returns the number of agents with the same attributes as it, including itself. This ignores unique_id (and source_name). This is probably very unoptimized. If agent_node_model is a string corresponding to a node in the NetworkGrid, returns the number of agents at that node with the attributes specified in qry, or all agents at that node if qry is None. If agent_node_model is a Model, returns the total number of agents in the model. """ if isinstance(agent_node_model, str): return len(self.get_groups(agent_node_model, qry)) elif isinstance(agent_node_model, Agent): mod_dict = {k: v for k, v in agent_node_model.__dict__.items() if k not in ('unique_id', 'source_name')} # toss unique identifiers return sum([mod_dict == {k: v for k, v in a.__dict__.items() if k not in ('unique_id', 'source_name')} for a in self.schedule.agents]) elif isinstance(agent_node_model, Model): return len(agent_node_model.schedule.agents) else: raise TypeError(f"get_mass expects a str, Agent, or Model for agent_node_model, but received " f"{type(agent_node_model)}")
class SEmodel(Model): # manual model has parameters set by user interface def __init__( self, num_nodes=100, avg_node_degree=3, # taipei : 1.92 # telaviv : 2.16 # tallinn : 2.20, engagement=0.49, trustability=0.21, influenceability=0.53, recovery=0.63, experience=1, initial_opinion=0, opinion=0, public_sector_opinion=1, corpo_opinion=1, startup_opinion=1, academic_opinion=-1, civil_opinion=-1, media_opinion=-1): # set network layout self.num_nodes = num_nodes prob = avg_node_degree / self.num_nodes self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=prob) # set space and time of the model self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) # set model parameters self.engagement = engagement self.trustability = trustability self.influenceability = influenceability self.recovery = recovery self.experience = experience self.initial_opinion = initial_opinion self.opinion = initial_opinion self.public_sector_opinion = public_sector_opinion self.corpo_opinion = corpo_opinion self.startup_opinion = startup_opinion self.academic_opinion = academic_opinion self.civil_opinion = civil_opinion self.media_opinion = media_opinion # set data collection self.datacollector = DataCollector({ "Negative": num_negative, "Neutral": num_neutral, "Positive": num_positive, "Total Engagement": total_engagement, "Total Trustability": total_trustability, "Total Recovery": total_recovery, "Total Experience": total_experience, }) # create agents with average parameters taken on #city tweets for i, node in enumerate(self.G.nodes()): a = agent.Agent( i, self, self.engagement, self.trustability, self.influenceability, self.recovery, self.experience, self.initial_opinion, # fixed by interface self.opinion) self.schedule.add(a) # add the undetermined agents to the network self.grid.place_agent(a, node) # create 1 representative of each stakeholder category public_sector = self.random.sample(self.G.nodes(), 1) for a in self.grid.get_cell_list_contents(public_sector): a.engagement = 0.57 a.trustability = 0.53 a.influenceability = 0.59 a.recovery = 0.70 a.experience = 1 a.initial_opinion = public_sector_opinion # fixed by interface a.opinion = a.initial_opinion corporate = self.random.sample(self.G.nodes(), 1) for a in self.grid.get_cell_list_contents(corporate): a.engagement = 0.75 a.trustability = 0.49 a.influenceability = 0.68 a.recovery = 0.73 a.experience = 1 a.initial_opinion = corpo_opinion # fixed by interface a.opinion = a.initial_opinion startup = self.random.sample(self.G.nodes(), 1) for a in self.grid.get_cell_list_contents(startup): a.engagement = 0.69 a.trustability = 0.29 a.influenceability = 0.68 a.recovery = 0.97 a.experience = 1 a.initial_opinion = startup_opinion # fixed by interface a.opinion = a.initial_opinion academic = self.random.sample(self.G.nodes(), 1) for a in self.grid.get_cell_list_contents(academic): a.engagement = 0.49 a.trustability = 0.20 a.influenceability = 0.65 a.recovery = 0.75 a.experience = 1 a.initial_opinion = academic_opinion # fixed by interface a.opinion = a.initial_opinion civil = self.random.sample(self.G.nodes(), 1) for a in self.grid.get_cell_list_contents(civil): a.engagement = 0.43 a.trustability = 0.21 a.influenceability = 0.69 a.recovery = 0.72 a.experience = 1 a.initial_opinion = civil_opinion # fixed by interface a.opinion = a.initial_opinion media = self.random.sample(self.G.nodes(), 1) for a in self.grid.get_cell_list_contents(media): a.engagement = 0.50 a.trustability = 0.23 a.influenceability = 0.65 a.recovery = 0.71 a.experience = 1 a.initial_opinion = media_opinion # fixed by interface a.opinion = a.initial_opinion self.running = True self.datacollector.collect(self) print('Finished initialising model, network has %s nodes' % self.G.nodes) nx.draw_networkx(self.G) #plt.show() def positive_negative_ratio(self): try: return num_positive(self) / num_negative(self) except ZeroDivisionError: return 0.00 def step(self): # advance the model by one step and collect data self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()
class SPQRisiko(Model): """A SPQRisiko model with some number of players""" def __init__(self, n_players, points_limit, strategy, goal): super().__init__() self.players_goals = ["BE", "LA", "PP" ] # Definition of acronyms on `strategies.py` self.current_turn = 0 self.journal = [] # Keep track of main events self.reinforces_by_goal = {} self.tris_by_goal = {} # How many agent players wiil be self.n_players = n_players if n_players <= constants.MAX_PLAYERS else constants.MAX_PLAYERS # How many computer players will be self.n_computers = constants.MAX_PLAYERS - n_players # Creation of player, goals and computer agents goals = [] if goal == "Random": for i, player in enumerate(range(n_players)): goals.append(self.players_goals[i % 3]) else: goals = [goal for i in range(self.n_players)] self.players = [ Player(i, computer=False, strategy=self.get_strategy_setup(strategy, i), goal=goals[i], model=self) for i in range(self.n_players) ] for player in self.players: self.log("{} follows {} goal with a {} strategy".format( player.color, player.goal, player.strategy)) self.computers = [ Player(i, computer=True, strategy="Neutral", goal=self.random.choice(self.players_goals), model=self) for i in range(self.n_players, self.n_players + self.n_computers) ] self.points_limit = points_limit # limit at which one player wins self.deck = self.create_deck() self.random.shuffle(self.deck) self.trashed_cards = [] self.precompute_tris_reinforces_by_goal() # Initialize map self.G, self.territories_dict = self.create_graph_map() self.grid = NetworkGrid(self.G) self.datacollector = DataCollector( model_reporters={ "Winner": get_winner, "Turn": get_winner_turn, "Strategy": get_player_strategy, "Goal": get_player_goal }) # Schedule self.schedule = RandomActivation(self) # Subgraphs self.ground_areas = [] self.sea_areas = [] path = os.path.abspath((os.path.join(__file__, '..', '..'))) # Probabilities that the attacker wins on a ground combact with open(path + '/matrices/atta_wins_combact.pkl', 'rb') as f: self.atta_wins_combact = pickle.load(f) # Probabilities that the attacker wins on a combact by sea with open(path + '/matrices/atta_wins_combact_by_sea.pkl', 'rb') as f: self.atta_wins_combact_by_sea = pickle.load(f) territories = list(range(45)) random.shuffle(territories) """ If there're 4 players, Italia must be owned by the only computer player """ if self.n_players == 4: territories.remove(15) # Remove Italia from the territories t = GroundArea(*itemgetter("id", "name", "type", "coords")( self.territories_dict["territories"][15]), model=self) t.armies = 3 t.owner = self.computers[0] self.grid.place_agent(t, 15) self.ground_areas.append(self.grid.get_cell_list_contents([15])[0]) """ Connect nodes to territories and assign them to players """ for i, node in enumerate(territories): t = GroundArea(*itemgetter("id", "name", "type", "coords")( self.territories_dict["territories"][node]), model=self) if i < 9 * self.n_players: t.armies = 2 t.owner = self.players[i % self.n_players] else: t.armies = 3 t.owner = self.computers[i % self.n_computers] self.grid.place_agent(t, node) self.ground_areas.append( self.grid.get_cell_list_contents([node])[0]) """ Add sea area """ for i, node in enumerate(range(45, 57)): t = SeaArea(*itemgetter("id", "name", "type", "coords")( self.territories_dict["sea_areas"][i]), model=self) t.trireme = [0 for _ in range(self.n_players)] self.grid.place_agent(t, node) self.sea_areas.append(self.grid.get_cell_list_contents([node])[0]) self.ground_areas.sort(key=lambda x: x.unique_id) self.sea_areas.sort(key=lambda x: x.unique_id) self.running = True # self.datacollector.collect(self) def get_strategy_setup(self, strategy, i): strats = ["Aggressive", "Passive", "Neutral"] if strategy == "Random": strategy = strats[i % 3] return strategy @staticmethod def get_movable_armies_by_strategy(strategy, minimum, maximum): return round((maximum - minimum) * strategies.nomads_percentage[strategy] + minimum) @staticmethod def create_graph_map(): # Read map configuration from file with open( os.path.join(os.path.dirname(__file__), "config/territories.json"), "r") as f: territories_dict = json.load(f) graph_map = nx.Graph() for territory in territories_dict["territories"]: graph_map.add_node(territory["id"]) for sea in territories_dict['sea_areas']: graph_map.add_node(sea['id']) for edges in territories_dict["edges"]: graph_map.add_edge(edges[0], edges[1]) return graph_map, territories_dict @staticmethod def create_deck(custom_numbers=None): # Read deck from configuration file deck = [] with open(os.path.join(os.path.dirname(__file__), "config/cards.json"), "r") as f: cards = json.load(f) # custom cards' numbers if custom_numbers: # do something return deck for card in cards: c = { "type": card["type"], "adds_on_tris": card["adds_on_tris"], "image": card["image"] } for _ in range(card["number_in_deck"]): deck.append(c) return deck def draw_a_card(self): # if deck is empty, refill from trashed cards if len(self.deck) == 0: if len(self.trashed_cards) == 0: # We finished cards, players must do some tris to refill deck! return None self.deck.extend(self.trashed_cards) self.trashed_cards = [] # return last card from deck return self.deck.pop() @staticmethod def reinforces_from_tris(cards): # assert len(cards) == 3, "Wrong number of cards given to 'tris' method" if len(cards) != 3: return None cards_in_tris = set([card["type"] for card in cards]) # assert len(cards_in_tris) == 3 or len(cards_in_tris) == 1, \ # Tris must be composed of three different cards or three of the same type if len(cards_in_tris) != 3 and len(cards_in_tris) != 1: return None reinforces = { "legionaries": 8 if len(cards_in_tris) == 1 else 10, "centers": 0, "triremes": 0 } for card in cards: for key, value in card["adds_on_tris"].items(): reinforces[key] += value return reinforces def precompute_tris_reinforces_by_goal(self): # precompute tris and assign points based on strategy all_possible_tris = [ list(t) for t in itertools.combinations(self.deck, 3) ] all_reinforces = [ SPQRisiko.reinforces_from_tris(tris) for tris in all_possible_tris ] # Remove None from list real_tris = [ all_possible_tris[i] for i in range(len(all_reinforces)) if all_reinforces[i] ] all_reinforces = [i for i in all_reinforces if i] named_tris = {} for i, tris in enumerate(real_tris): name = self.get_tris_name(tris) named_tris[name] = all_reinforces[i] self.reinforces_by_goal[name] = {} for goal, value in strategies.strategies.items(): self.reinforces_by_goal[name][ goal] = self.get_reinforcements_score( all_reinforces[i], value["tris"]) # order tris name by score for goal, value in strategies.strategies.items(): self.tris_by_goal[goal] = [] for tris in real_tris: name = self.get_tris_name(tris) if name not in self.tris_by_goal[goal]: self.tris_by_goal[goal].append(name) self.tris_by_goal[goal] = sorted( self.tris_by_goal[goal], key=cmp_to_key(lambda a, b: self.reinforces_by_goal[b][goal] - self.reinforces_by_goal[a][goal])) self.reinforces_by_goal["average"] = {} for goal, value in strategies.strategies.items(): points, count = 0, 0 for tris in real_tris: count += 1 points += self.reinforces_by_goal[self.get_tris_name( tris)][goal] self.reinforces_by_goal["average"][goal] = float(points) / count def count_players_sea_areas(self): sea_areas = [0] * self.n_players for sea in self.sea_areas: m = max(sea.trireme) players_max_trireme = [ player for player, n_trireme in enumerate(sea.trireme) if n_trireme == m ] if len(players_max_trireme) == 1: sea_areas[players_max_trireme[0]] += 1 return sea_areas def count_players_territories_power_places(self): territories = [0] * self.n_players power_places = [0] * self.n_players for territory in self.ground_areas: if not territory.owner.computer: territories[territory.owner.unique_id] += 1 if territory.power_place: power_places[territory.owner.unique_id] += 1 return territories, power_places def get_weakest_power_place(self, player): weakest = None for territory in self.ground_areas: if not territory.owner.computer and territory.owner.unique_id == player.unique_id: if territory.power_place: if not weakest or territory.armies < weakest.armies: weakest = territory return weakest def get_weakest_adversary_power_place(self, player): weakest = None for territory in self.ground_areas: if territory.owner.computer or territory.owner.unique_id != player.unique_id: if territory.power_place: if not weakest or territory.armies < weakest.armies: weakest = territory return weakest def find_nearest(self, territory, player): # It's a BFS visit to get the node whose distance from territory is the lesser than any other for ground_area in self.ground_areas: ground_area.found = 0 for sea_area in self.sea_areas: sea_area.found = 0 territory.found = 1 visited = [territory] distances = [0] * 57 while len(visited) > 0: t = visited.pop(0) if distances[t.unique_id] > 4: break for neighbor in self.grid.get_neighbors(t.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.found == 0: neighbor.found = 1 distances[neighbor.unique_id] = distances[t.unique_id] + 1 visited.append(neighbor) if neighbor.type == "ground" and neighbor.owner.unique_id == player.unique_id: return neighbor return None def get_largest_empire(self, player): # It's another DFS visit in which we account for the membership of a node to a connected component def __dfs_visit__(territory, ground_areas, cc_num): territory.found = 1 ground_areas[territory.unique_id] = cc_num for neighbor in self.grid.get_neighbors(territory.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and \ neighbor.found == 0 and \ neighbor.owner.unique_id == player.unique_id: __dfs_visit__(neighbor, ground_areas, cc_num) cc_num = 0 ground_areas = [-1] * 45 for territory in self.ground_areas: territory.found = 0 for territory in self.ground_areas: if territory.type == "ground" and territory.found == 0 and territory.owner.unique_id == player.unique_id: __dfs_visit__(territory, ground_areas, cc_num) cc_num += 1 stats = list( collections.Counter([t for t in ground_areas if t != -1]).most_common()) if stats != []: return [ self.ground_areas[idx] for idx, cc in enumerate(ground_areas) if cc == stats[0][0] ] return stats def maximum_empires(self): # It's a DFS visit in which we account for # the length of every connected components def __dfs_visit__(territory, d): territory.found = 1 for neighbor in self.grid.get_neighbors(territory.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and \ neighbor.found == 0 and \ neighbor.owner.unique_id == territory.owner.unique_id: d = __dfs_visit__(neighbor, d) return d + 1 cc_lengths = [0] * self.n_players for territory in self.ground_areas: territory.found = 0 for territory in self.ground_areas: if not territory.owner.computer and territory.found == 0: distance = __dfs_visit__(territory, 0) if distance > cc_lengths[territory.owner.unique_id]: cc_lengths[territory.owner.unique_id] = distance return cc_lengths # Controlla se `player` ha vinto oppure se c'è un vincitore tra tutti def winner(self, player=None): if player is not None: if player.victory_points >= self.points_limit: return True return False max_points = -1 max_player = None for p in self.players: if p.victory_points > max_points: max_points = p.victory_points max_player = p won = True if max_points > self.points_limit else False return max_player, won def get_territories_by_player(self, player: Player, ground_type="ground"): if ground_type == "ground": return [ t for t in self.ground_areas if t.owner.unique_id == player.unique_id ] elif ground_type == "sea": return [ t for t in self.sea_areas if t.trireme[self.players.index(player)] > 0 or max(t.trireme) == 0 ] def get_sea_area_near_ground_area(self, player): sea_areas = [] for sea_area in self.sea_areas: for neighbor in self.grid.get_neighbors(sea_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if isinstance(neighbor, GroundArea) and \ neighbor.owner.unique_id == player.unique_id: sea_areas.append(sea_area) return sea_areas def n_power_places(self): n = 0 for area in self.ground_areas: if area.power_place: n += 1 return n def update_atta_wins_combact_matrix(self, attacker_armies, defender_armies, mat_type='combact'): print(attacker_armies, defender_armies, self.atta_wins_combact.shape, self.atta_wins_combact_by_sea.shape) if mat_type == 'combact': if attacker_armies > self.atta_wins_combact.shape[ 0] and defender_armies > self.atta_wins_combact.shape[1]: self.atta_wins_combact = get_probabilities_ground_combact( attacker_armies, defender_armies) elif attacker_armies > self.atta_wins_combact.shape[0]: self.atta_wins_combact = get_probabilities_ground_combact( attacker_armies, self.atta_wins_combact.shape[1]) elif defender_armies > self.atta_wins_combact.shape[1]: self.atta_wins_combact = get_probabilities_ground_combact( self.atta_wins_combact.shape[0], defender_armies) elif mat_type == 'combact_by_sea': if attacker_armies > self.atta_wins_combact_by_sea.shape[ 0] and defender_armies > self.atta_wins_combact_by_sea.shape[ 1]: self.atta_wins_combact_by_sea = get_probabilities_combact_by_sea( attacker_armies, defender_armies) elif attacker_armies > self.atta_wins_combact_by_sea.shape[0]: self.atta_wins_combact_by_sea = get_probabilities_combact_by_sea( attacker_armies, self.atta_wins_combact_by_sea.shape[1]) elif defender_armies > self.atta_wins_combact_by_sea.shape[1]: self.atta_wins_combact_by_sea = get_probabilities_combact_by_sea( self.atta_wins_combact_by_sea.shape[0], defender_armies) def step(self): self.current_turn += 1 for player in self.players: if not player.eliminated: can_draw = False territories, power_places = self.count_players_territories_power_places( ) player_territories = self.get_territories_by_player( player, "ground") sea_areas = self.count_players_sea_areas() empires = self.maximum_empires() # 1) Aggiornamento del punteggio player.update_victory_points(empires, territories, sea_areas, power_places) # 1.1) Controllo vittoria if self.winner(player): self.running = False print(player) print(get_winner(self)) print(get_winner_turn(self)) self.datacollector.collect(self) self.log("{} has won!".format(player.color)) return True # 2) Fase dei rinforzi print('\nREINFORCES') player.update_ground_reinforces_power_places() reinforces = Player.get_ground_reinforces(player_territories) self.log( "{} earns {} legionaries (he owns {} territories)".format( player.color, reinforces, territories[player.unique_id])) player.put_reinforces(self, reinforces) # player.sacrifice_trireme(sea_area_from, ground_area_to) # use card combination # displace ground, naval and/or power places on the ground tris = player.get_best_tris(self) if tris: reinforces = player.play_tris(self, tris) self.log("{} play tris {}".format( player.color, self.get_tris_name(tris))) player.put_reinforces(self, reinforces) # TODO: log where reinforces are put # 3) Movimento navale # player.naval_movement(sea_area_from, sea_area_to, n_trireme) # 4) Combattimento navale print('\nNAVAL COMBACT!!') # Get all sea_areas that the current player can attack attackable_sea_areas = [] for sea_area in self.get_territories_by_player( player, ground_type='sea'): # Choose the adversary that has the lower probability of winning the combact min_trireme = min(sea_area.trireme) if min_trireme > 0: adv_min_trireme = sea_area.trireme.index(min_trireme) # Check if the atta_wins_combact probabilities matrix needs to be recomputed # self.update_atta_wins_combact_matrix(sea_area.trireme[player.unique_id], sea_area.trireme[adv_min_trireme]) row = sea_area.trireme[player.unique_id] col = sea_area.trireme[adv_min_trireme] m = max(row, col) ratio = 100 / m if ratio < 1: row = min(round(ratio * row), 100) col = min(round(ratio * col), 100) if player.unique_id != adv_min_trireme and \ self.atta_wins_combact[row - 1, col - 1] >= strategies.probs_win[player.strategy]: attackable_sea_areas.append( [sea_area, adv_min_trireme]) for sea_area, adv in attackable_sea_areas: # Randomly select how many attack and defense trireme attacker_trireme = sea_area.trireme[player.unique_id] # The defender must always use the maximux number of armies to defend itself # n_defense_trireme = sea_area.trireme[adversary.unique_id] if sea_area.trireme[adversary.unique_id] <= 3 else 3 # Let's combact biatch!! print('Start battle!') print('Trireme in ' + sea_area.name + ': ', sea_area.trireme) print('Player ' + str(player.unique_id) + ' attacks Player ' + str(adv) + ' on ' + sea_area.name) player.naval_combact(sea_area, adv, attacker_trireme, strategies.probs_win[player.strategy], self.atta_wins_combact) # 5) Attacchi via mare print('\nCOMBACT BY SEA!!') for ground_area in self.ground_areas: ground_area.already_attacked_by_sea = False attacks = self.get_attackable_ground_areas_by_sea(player) # attacks.sort(key=lambda x: x["prob_win"], reverse=True) while 0 < len(attacks): attack = attacks[0] # if not attack['defender'].already_attacked_by_sea: attack['defender'].already_attacked_by_sea = True attacker_armies = attack["attacker"].armies - attack[ "armies_to_leave"] print( 'Battle: {} (player {}) with {} VS {} (player {}) with {}' .format(attack["attacker"].name, player.unique_id, attacker_armies, attack["defender"].name, attack["defender"].owner.unique_id, attack["defender"].armies)) conquered, min_moveable_armies = player.combact_by_sea( attack["attacker"], attack["defender"], attacker_armies) if conquered: # Move armies from attacker area to conquered max_moveable_armies = attack[ "attacker"].armies - attack["armies_to_leave"] nomads = SPQRisiko.get_movable_armies_by_strategy( player.strategy, min_moveable_armies, max_moveable_armies) attack["attacker"].armies -= nomads attack["defender"].armies = nomads can_draw = True # Remove from possible attacks all of those containing as defender the conquered territory # and update the probability attacks = self.update_attacks_by_sea(player, attacks) # 6) Attacchi terrestri print('\nGROUND COMBACT!!') attacks = [] attacks = self.get_attackable_ground_areas(player) # attacks.sort(key=lambda x: x["prob_win"], reverse=True) while 0 < len(attacks): attack = attacks[0] attacker_armies = attack["attacker"].armies - 1 print( 'Battle: {} (player {}) with {} VS {} (player {}) with {}' .format(attack["attacker"].name, player.unique_id, attacker_armies, attack["defender"].name, attack["defender"].owner.unique_id, attack["defender"].armies)) conquered, min_moveable_armies = player.combact( attack["attacker"], attack["defender"], attacker_armies, strategies.probs_win[player.strategy], self.atta_wins_combact) if conquered: # Move armies from attacker area to conquered max_moveable_armies = attack["attacker"].armies - 1 nomads = SPQRisiko.get_movable_armies_by_strategy( player.strategy, min_moveable_armies, max_moveable_armies) attack["attacker"].armies -= nomads attack["defender"].armies = nomads can_draw = True self.log( "{} conquered {} from {} and it moves {} armies there out of {}" .format(player.color, attack["defender"].name, attack["attacker"].name, nomads, max_moveable_armies)) # Re-sort newly attackable areas with newer probabilities attacks = self.get_attackable_ground_areas(player) # attacks.sort(key=lambda x: x["prob_win"], reverse=True) # Controllo se qualche giocatore è stato eliminato for adv in self.players: if adv.unique_id != player.unique_id and not adv.eliminated: territories = self.get_territories_by_player(adv) if len(territories) == 0: self.log("{} has been eliminated by {}".format( adv.color, player.color)) player.cards.extend(adv.cards) adv.cards = [] adv.eliminated = True for sea_area in self.get_territories_by_player( adv, ground_type="sea"): sea_area.trireme[adv.unique_id] = 0 # 7) Spostamento strategico di fine turno player.move_armies_by_goal(self) # 8) Presa della carta # Il giocatore può dimenticarsi di pescare la carta ahah sarebbe bello fare i giocatori smemorati if can_draw and random.random() <= 1: card = self.draw_a_card() if card: player.cards.append(card) self.schedule.step() return False def update_attacks_by_sea(self, player, future_attacks): attack_num = 0 last_attacker = future_attacks[0]['attacker'] del future_attacks[0] while attack_num < len(future_attacks): attack = future_attacks[attack_num] if attack['defender'].owner.unique_id == player.unique_id: print('Since the defender has been conquered, I delete it') del future_attacks[attack_num] elif attack['defender'].already_attacked_by_sea: print( 'Since the defender has already been attacked by sea, I delete it' ) del future_attacks[attack_num] elif attack['attacker'].unique_id == last_attacker.unique_id: print('The attacker may attack again') # Maybe it could change the armies to leave due to garrisons armies_to_leave = self.get_armies_to_leave(attack['attacker']) if attack['attacker'].armies - armies_to_leave >= min( 3, attack['defender'].armies): prob_win = self.atta_wins_combact_by_sea[ attack['attacker'].armies - armies_to_leave - 1, attack['defender'].armies - 1] if prob_win >= strategies.probs_win[player.strategy]: print('The attacker can attack again') attack['prob_win'] = prob_win attack_num += 1 else: print( 'Since the attacker has a lower prob to win, I delete it' ) del future_attacks[attack_num] else: print( 'Since the attacker hasn\'t the min required armies, I delete it' ) del future_attacks[attack_num] else: attack_num += 1 if player.goal == "PP": future_attacks.sort(key=lambda x: (x['defender'].power_place, x['prob_win']), reverse=True) else: future_attacks.sort(key=lambda x: x['prob_win'], reverse=True) return future_attacks def get_attackable_ground_areas_by_sea(self, player): attacks = [] for ground_area in self.get_territories_by_player(player): for neighbor in self.grid.get_neighbors(ground_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] # A player can attack a ground area through sea, only if it posesses a number of # trireme greater than the possible adversary. if isinstance( neighbor, SeaArea) and neighbor.trireme[player.unique_id] > min( neighbor.trireme): for sea_area_neighbor in self.grid.get_neighbors( neighbor.unique_id): sea_area_neighbor = self.grid.get_cell_list_contents( [sea_area_neighbor])[0] if isinstance(sea_area_neighbor, GroundArea) and \ ground_area.unique_id != sea_area_neighbor.unique_id and \ sea_area_neighbor.owner.unique_id != player.unique_id and \ (sea_area_neighbor.owner.computer or neighbor.trireme[player.unique_id] > neighbor.trireme[sea_area_neighbor.owner.unique_id]): armies_to_leave = self.get_armies_to_leave( ground_area) if ground_area.armies - armies_to_leave >= min( 3, sea_area_neighbor.armies): # self.update_atta_wins_combact_matrix(ground_area.armies - armies_to_leave, sea_area_neighbor.armies, mat_type='combact_by_sea') row = ground_area.armies - armies_to_leave col = sea_area_neighbor.armies m = max(row, col) ratio = 100 / m if ratio < 1: row = min(round(ratio * row), 100) col = min(round(ratio * col), 100) prob_win = self.atta_wins_combact_by_sea[row - 1, col - 1] if prob_win >= strategies.probs_win[ player.strategy]: attacks.append({ "defender": sea_area_neighbor, "attacker": ground_area, "armies_to_leave": armies_to_leave, "prob_win": prob_win }) if player.goal == "PP": attacks.sort(key=lambda x: (x['defender'].power_place, x['prob_win']), reverse=True) else: attacks.sort(key=lambda x: x['prob_win'], reverse=True) return attacks def get_armies_to_leave(self, ground_area): ground_area_neighbors = self.grid.get_neighbors(ground_area.unique_id) for ground_area_neighbor in ground_area_neighbors: ground_area_neighbor = self.grid.get_cell_list_contents( [ground_area_neighbor])[0] if isinstance(ground_area_neighbor, GroundArea) and \ ground_area_neighbor.owner.unique_id != ground_area.owner.unique_id: return 2 return 1 def get_attackable_ground_areas_from(self, ground_area): attacks = [] if ground_area.armies > 1: for neighbor in self.grid.get_neighbors(ground_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and \ neighbor.owner.unique_id != ground_area.owner.unique_id and \ ground_area.armies - 1 >= min(3, neighbor.armies): # self.update_atta_wins_combact_matrix(ground_area.armies - 1, neighbor.armies) row = ground_area.armies - 1 col = neighbor.armies m = max(row, col) ratio = 100 / m if ratio < 1: row = min(round(ratio * row), 100) col = min(round(ratio * col), 100) prob_win = self.atta_wins_combact[row - 1, col - 1] if prob_win >= strategies.probs_win[ ground_area.owner.strategy]: attacks.append({ "defender": neighbor, "attacker": ground_area, "prob_win": prob_win }) return attacks def get_attackable_ground_areas(self, player): attacks = [] for ground_area in self.get_territories_by_player(player): attackables = self.get_attackable_ground_areas_from(ground_area) if attackables != []: for attackable in attackables: attacks.append(attackable) if player.goal == "PP": attacks.sort(key=lambda x: (x['defender'].power_place, x['prob_win']), reverse=True) else: attacks.sort(key=lambda x: x['prob_win'], reverse=True) return attacks # Get non attackable areas wiht at least 2 armies and with an ally neighbor def non_attackable_areas(self, player, territories=None): non_attackables = [] if not territories: territories = self.get_territories_by_player(player) for ground_area in territories: if ground_area.armies > 1: attackable, has_ally_neighbor = False, False for neighbor in self.grid.get_neighbors(ground_area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.type == "ground" and neighbor.owner.unique_id != player.unique_id: attackable = True break has_ally_neighbor = True if not attackable and has_ally_neighbor: non_attackables.append(ground_area) return non_attackables def is_not_attackable(self, area): for neighbor in self.grid.get_neighbors(area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if isinstance( neighbor, GroundArea ) and neighbor.owner.unique_id != area.owner.unique_id: return False return True def get_strongest_ally_neighbor(self, area): strongest = None for neighbor in self.grid.get_neighbors(area.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if isinstance(neighbor, GroundArea) and ( not strongest or strongest.armies < neighbor.armies): strongest = neighbor return strongest def is_neighbor_of(self, area1, area2): for neighbor in self.grid.get_neighbors(area1.unique_id): neighbor = self.grid.get_cell_list_contents([neighbor])[0] if neighbor.owner.unique_id == area2.unique_id: return True return False def log(self, log): self.journal.append("Turn {}: ".format(self.current_turn) + log) def run_model(self, n): for _ in range(n): self.step() # Tris name is the ordered initial letters of cards type def get_tris_name(self, tris): if len(tris) != 3: raise Exception("tris name parameter error") return "-".join( [card[0] for card in sorted(set([card["type"] for card in tris]))]) def get_reinforcements_score(self, reinforces, multipliers): m, r = multipliers, reinforces return m[0] * r["legionaries"] + m[1] * r["triremes"] + m[2] * r[ "centers"] def get_n_armies_by_player(self, player=None): if player is not None: return sum( [t.armies for t in self.get_territories_by_player(player)]) else: sum_a = 0 for player in self.players: sum_a += sum( [t.armies for t in self.get_territories_by_player(player)]) return sum_a / len(self.players)
class VirusOnNetwork(Model): """A virus model with some number of agents""" def __init__( self, num_nodes=10, avg_node_degree=3, initial_outbreak_size=1, virus_spread_chance=0.4, virus_check_frequency=0.4, recovery_chance=0.3, gain_resistance_chance=0.5, ): self.G =nx.empty_graph(0) lis = [] for x in range(5): lis.append(range(random.randint(2,15))) correlative_num = 0 central = 0 for i,x in enumerate(lis): for y in lis[i]: if y > 0: self.G.add_node(correlative_num) self.G.add_edge(correlative_num, central) correlative_num+=1 else: self.G.add_node(correlative_num) correlative_num+=1 central = correlative_num print("Impresion del grapho") print(self.G.nodes) print(self.G.edges) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.initial_outbreak_size = ( initial_outbreak_size if initial_outbreak_size <= num_nodes else num_nodes ) self.virus_spread_chance = virus_spread_chance self.virus_check_frequency = virus_check_frequency self.recovery_chance = recovery_chance self.gain_resistance_chance = gain_resistance_chance self.datacollector = DataCollector( { "Infected": number_infected, "Susceptible": number_susceptible, "Resistant": number_resistant, } ) # Create agents for i, node in enumerate(self.G.nodes()): a = VirusAgent( i, self, State.SUSCEPTIBLE, self.virus_spread_chance, self.virus_check_frequency, self.recovery_chance, self.gain_resistance_chance, ) self.schedule.add(a) # Add the agent to the node self.grid.place_agent(a, node) # Infect some nodes infected_nodes = self.random.sample(self.G.nodes(), self.initial_outbreak_size) for a in self.grid.get_cell_list_contents(infected_nodes): a.state = State.INFECTED self.running = True self.datacollector.collect(self) def resistant_susceptible_ratio(self): try: return number_state(self, State.RESISTANT) / number_state( self, State.SUSCEPTIBLE ) except ZeroDivisionError: return math.inf def step(self): self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self, n): for i in range(n): self.step()