class ForestFire(Model): ''' Simple Forest Fire model. ''' def __init__(self, height, width, density): ''' Create a new forest fire model. Args: height, width: The size of the grid to model density: What fraction of grid cells have a tree in them. ''' # Initialize model parameters self.height = height self.width = width self.density = density # Set up model objects self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=False) self.datacollector = DataCollector( {"Fine": lambda m: self.count_type(m, "Fine"), "On Fire": lambda m: self.count_type(m, "On Fire"), "Burned Out": lambda m: self.count_type(m, "Burned Out")}) # Place a tree in each cell with Prob = density for (contents, x, y) in self.grid.coord_iter(): if random.random() < self.density: # Create a tree new_tree = TreeCell((x, y)) # Set all trees in the first column on fire. if x == 0: new_tree.condition = "On Fire" self.grid._place_agent((x, y), new_tree) self.schedule.add(new_tree) self.running = True def step(self): ''' Advance the model by one step. ''' self.schedule.step() self.datacollector.collect(self) # Halt if no more fire if self.count_type(self, "On Fire") == 0: self.running = False @staticmethod def count_type(model, tree_condition): ''' Helper method to count trees in a given condition in a given model. ''' count = 0 for tree in model.schedule.agents: if tree.condition == tree_condition: count += 1 return count
class WorldModel(Model): def __init__(self, N, width, height): self.grid = SingleGrid(height, width, True) self.schedule = RandomActivation(self) self.num_agents = N self.running = True for i in range(self.num_agents): ethnicity = random.choice(Ethnicities) a = PersonAgent(unique_id=i, model=self, ethnicity=int(ethnicity) ) self.schedule.add(a) # Add the agent to a random grid cell self.grid.position_agent(a) self.datacollector = DataCollector( agent_reporters={ "Nationalism": lambda a: a.nationalism, "X": lambda a: a.pos[0], "Y": lambda a: a.pos[1] } ) def step(self): self.datacollector.collect(self) self.schedule.step()
class BoltzmannWealthModelNetwork(Model): """A model with some number of agents.""" def __init__(self, num_agents=7, num_nodes=10): self.num_agents = num_agents self.num_nodes = num_nodes if num_nodes >= self.num_agents else self.num_agents self.G = nx.erdos_renyi_graph(n=self.num_nodes, p=0.5) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.datacollector = DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": lambda _: _.wealth} ) list_of_random_nodes = self.random.sample(self.G.nodes(), self.num_agents) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) self.schedule.add(a) # Add the agent to a random node self.grid.place_agent(a, list_of_random_nodes[i]) self.running = True self.datacollector.collect(self) 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 MoneyModel(Model): """A model with some number of agents.""" def __init__(self, N, width, height): self.num_agents = N self.running = True self.grid = MultiGrid(height, width, True) self.schedule = RandomActivation(self) self.datacollector = DataCollector(model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": lambda a: a.wealth}) # Create agents for i in range(self.num_agents): a = MoneyAgent(i) self.schedule.add(a) # Add the agent to a random grid cell x = random.randrange(self.grid.width) y = random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) def step(self): self.datacollector.collect(self) self.schedule.step() def run_model(self, n): for i in range(n): self.step()
class Money_Model(Model): def __init__(self, N, width=50, height=50, torus=True): self.num_agents = N self.schedule = RandomActivation(self) self.grid = MultiGrid(height, width, torus) self.create_agents() self.dc = DataCollector({"Gini": lambda m: m.compute_gini()}, {"Wealth": lambda a: a.wealth}) self.running = True def create_agents(self): for i in range(self.num_agents): a = Money_Agent(i) self.schedule.add(a) x = random.randrange(self.grid.width) y = random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) def step(self): self.dc.collect(self) self.schedule.step() def run_model(self, steps): for i in range(steps): self.step() def compute_gini(self): agent_wealths = [agent.wealth for agent in self.schedule.agents] x = sorted(agent_wealths) N = self.num_agents B = sum( xi * (N-i) for i,xi in enumerate(x) ) / (N*sum(x)) return (1 + (1/N) - 2*B)
class MoneyModel(Model): """A simple model of an economy where agents exchange currency at random. All the agents begin with one unit of currency, and each time step can give a unit of currency to another agent. Note how, over time, this produces a highly skewed distribution of wealth. """ def __init__(self, N, width, height): self.num_agents = N self.running = True self.grid = MultiGrid(height, width, True) self.schedule = RandomActivation(self) self.datacollector = DataCollector( model_reporters={"Gini": compute_gini}, agent_reporters={"Wealth": lambda a: a.wealth} ) # Create agents for i in range(self.num_agents): a = MoneyAgent(i, self) self.schedule.add(a) # Add the agent to a random grid cell x = random.randrange(self.grid.width) y = random.randrange(self.grid.height) self.grid.place_agent(a, (x, y)) def step(self): self.datacollector.collect(self) self.schedule.step() def run_model(self, n): for i in range(n): self.step()
class SchellingModel(Model): ''' Model class for the Schelling segregation model. ''' def __init__(self, height, width, density, minority_pc, homophily): ''' ''' self.height = height self.width = width self.density = density self.minority_pc = minority_pc self.homophily = homophily self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=True) self.happy = 0 self.datacollector = DataCollector( {"happy": lambda m: m.happy}, # Model-level count of happy agents # For testing purposes, agent's individual x and y {"x": lambda a: a.x, "y": lambda a: a.y}) self.running = True # Set up agents for x in range(self.width): for y in range(self.height): if random.random() < self.density: if random.random() < self.minority_pc: agent_type = 1 else: agent_type = 0 agent = SchellingAgent((x,y), x, y, agent_type) self.grid[y][x] = agent self.schedule.add(agent) def get_empty(self): ''' Get a list of coordinate tuples of currently-empty cells. ''' empty_cells = [] for x in range(self.width): for y in range(self.height): if self.grid[y][x] is None: empty_cells.append((x, y)) return empty_cells def step(self): ''' Run one step of the model. If All agents are happy, halt the model. ''' self.happy = 0 # Reset counter of happy agents self.schedule.step() self.datacollector.collect(self) if self.happy == self.schedule.get_agent_count(): self.running = False
class Charts(Model): # grid height grid_h = 20 # grid width grid_w = 20 """init parameters "init_people", "rich_threshold", and "reserve_percent" are all UserSettableParameters""" def __init__(self, height=grid_h, width=grid_w, init_people=2, rich_threshold=10, reserve_percent=50,): self.height = height self.width = width self.init_people = init_people self.schedule = RandomActivation(self) self.grid = MultiGrid(self.width, self.height, torus=True) # rich_threshold is the amount of savings a person needs to be considered "rich" self.rich_threshold = rich_threshold self.reserve_percent = reserve_percent # see datacollector functions above self.datacollector = DataCollector(model_reporters={ "Rich": get_num_rich_agents, "Poor": get_num_poor_agents, "Middle Class": get_num_mid_agents, "Savings": get_total_savings, "Wallets": get_total_wallets, "Money": get_total_money, "Loans": get_total_loans}, agent_reporters={ "Wealth": lambda x: x.wealth}) # create a single bank for the model self.bank = Bank(1, self, self.reserve_percent) # create people for the model according to number of people set by user for i in range(self.init_people): # set x, y coords randomly within the grid x = self.random.randrange(self.width) y = self.random.randrange(self.height) p = Person(i, (x, y), self, True, self.bank, self.rich_threshold) # place the Person object on the grid at coordinates (x, y) self.grid.place_agent(p, (x, y)) # add the Person object to the model schedule self.schedule.add(p) self.running = True self.datacollector.collect(self) def step(self): # tell all the agents in the model to run their step function self.schedule.step() # collect data self.datacollector.collect(self) def run_model(self): for i in range(self.run_time): self.step()
class Foraging(Model): number_of_bean = 0 number_of_corn = 0 number_of_soy = 0 def __init__(self, width=50, height=50, torus=True, num_bug=50, seed=42, strategy=None): super().__init__(seed=seed) self.number_of_bug = num_bug if not(strategy in ["stick", "switch"]): raise TypeError("'strategy' must be one of {stick, switch}") self.strategy = strategy self.grid = SingleGrid(width, height, torus) self.schedule = RandomActivation(self) data = {"Bean": lambda m: m.number_of_bean, "Corn": lambda m: m.number_of_corn, "Soy": lambda m: m.number_of_soy, "Bug": lambda m: m.number_of_bug, } self.datacollector = DataCollector(data) # create foods self._populate(Bean) self._populate(Corn) self._populate(Soy) # create bugs for i in range(self.number_of_bug): pos = self.grid.find_empty() bug = Bug(i, self) bug.strategy = self.strategy self.grid.place_agent(bug, pos) self.schedule.add(bug) def step(self): self.schedule.step() self.datacollector.collect(self) if not(self.grid.exists_empty_cells()): self.running = False def _populate(self, food_type): prefix = "number_of_{}" counter = 0 while counter < food_type.density * (self.grid.width * self.grid.height): pos = self.grid.find_empty() food = food_type(counter, self) self.grid.place_agent(food, pos) self.schedule.add(food) food_name = food_type.__name__.lower() attr_name = prefix.format(food_name) val = getattr(self, attr_name) val += 1 setattr(self, attr_name, val) counter += 1
class Schelling(Model): ''' Model class for the Schelling segregation model. ''' def __init__(self, height=20, width=20, density=0.8, minority_pc=0.2, homophily=3): ''' ''' self.height = height self.width = width self.density = density self.minority_pc = minority_pc self.homophily = homophily self.schedule = RandomActivation(self) self.grid = SingleGrid(height, width, torus=True) self.happy = 0 self.datacollector = DataCollector( {"happy": "happy"}, # Model-level count of happy agents # For testing purposes, agent's individual x and y {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]}) # Set up agents # We use a grid iterator that returns # the coordinates of a cell as well as # its contents. (coord_iter) for cell in self.grid.coord_iter(): x = cell[1] y = cell[2] if self.random.random() < self.density: if self.random.random() < self.minority_pc: agent_type = 1 else: agent_type = 0 agent = SchellingAgent((x, y), self, agent_type) self.grid.position_agent(agent, (x, y)) self.schedule.add(agent) self.running = True self.datacollector.collect(self) def step(self): ''' Run one step of the model. If All agents are happy, halt the model. ''' self.happy = 0 # Reset counter of happy agents self.schedule.step() # collect data self.datacollector.collect(self) if self.happy == self.schedule.get_agent_count(): self.running = False
class SchellingModel(Model): """ Model class for the Schelling segregation model. """ def __init__(self, height, width, density, minority_pc, homophily): """ """ self.height = height self.width = width self.density = density self.minority_pc = minority_pc self.homophily = homophily self.schedule = RandomActivation(self) self.grid = SingleGrid(height, width, torus=True) self.happy = 0 self.total_agents = 0 self.datacollector = DataCollector( {"unhappy": lambda m: m.total_agents - m.happy}, # For testing purposes, agent's individual x and y {"x": lambda a: a.pos[X], "y": lambda a: a.pos[Y]}, ) self.running = True # Set up agents # We use a grid iterator that returns # the coordinates of a cell as well as # its contents. (coord_iter) for cell, x, y in self.grid.coord_iter(): if random.random() < self.density: if random.random() < self.minority_pc: agent_type = 1 else: agent_type = 0 agent = SchellingAgent(self.total_agents, agent_type) self.grid.position_agent(agent, x, y) self.schedule.add(agent) self.total_agents += 1 def step(self): """ Run one step of the model. If All agents are happy, halt the model. """ self.happy = 0 # Reset counter of happy agents self.schedule.step() self.datacollector.collect(self) if self.happy == self.total_agents: self.running = False
class PD_Model(Model): ''' Model class for iterated, spatial prisoner's dilemma model. ''' schedule_types = {"Sequential": BaseScheduler, "Random": RandomActivation, "Simultaneous": SimultaneousActivation} # This dictionary holds the payoff for this agent, # keyed on: (my_move, other_move) payoff = {("C", "C"): 1, ("C", "D"): 0, ("D", "C"): 1.6, ("D", "D"): 0} def __init__(self, height, width, schedule_type, payoffs=None): ''' Create a new Spatial Prisoners' Dilemma Model. Args: height, width: Grid size. There will be one agent per grid cell. schedule_type: Can be "Sequential", "Random", or "Simultaneous". Determines the agent activation regime. payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. ''' self.running = True self.grid = SingleGrid(height, width, torus=True) self.schedule_type = schedule_type self.schedule = self.schedule_types[self.schedule_type](self) # Create agents for x in range(width): for y in range(height): agent = PD_Agent((x, y), self) self.grid.place_agent(agent, (x, y)) self.schedule.add(agent) self.datacollector = DataCollector({ "Cooperating_Agents": lambda m: len([a for a in m.schedule.agents if a.move == "C"]) }) def step(self): self.datacollector.collect(self) self.schedule.step() def run(self, n): ''' Run the model for a certain number of steps. ''' for _ in range(n): self.step()
class InspectionModel(Model): ''' Simple Restaurant Inspection model. ''' def __init__(self, height, width, density): ''' Create a new restaurant inspection model. Args: height, width: The size of the grid to model density: What fraction of grid cells have a restaurant in them. ''' # Initialize model parameters self.height = height self.width = width self.density = density # Set up model objects self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=False) self.datacollector = DataCollector( {"Good": lambda m: self.count_type(m, "Good"), "Bad": lambda m: self.count_type(m, "Bad")}) # Place a restaurant in each cell with Prob = density for (contents, x, y) in self.grid.coord_iter(): if random.random() < self.density: # Create a restaurant new_restaurant = RestaurantCell((x, y)) self.grid._place_agent((x, y), new_restaurant) self.schedule.add(new_restaurant) self.running = True def step(self): ''' Advance the model by one step. ''' self.schedule.step() self.datacollector.collect(self) @staticmethod def count_type(model, restaurant_hygiene): ''' Helper method to count restaurants in a given condition in a given model. ''' count = 0 for restaurant in model.schedule.agents: if restaurant.hygiene == restaurant_hygiene and restaurant.rating != 'Closed': count += 1 return count
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 SchellingModel(Model): ''' Model class for the Schelling segregation model. ''' def __init__(self, height, width, density, type_pcs=[.2, .2, .2, .2, .2]): ''' ''' self.height = height self.width = width self.density = density self.type_pcs = type_pcs self.schedule = RandomActivation(self) self.grid = SingleGrid(height, width, torus=False) self.happy = 0 self.datacollector = DataCollector( {"happy": lambda m: m.happy}, # Model-level count of happy agents # For testing purposes, agent's individual x and y {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1]}) self.running = True # Set up agents # We use a grid iterator that returns # the coordinates of a cell as well as # its contents. (coord_iter) total_agents = self.height * self.width * self.density agents_by_type = [total_agents*val for val in self.type_pcs] for loc, types in enumerate(agents_by_type): for i in range(int(types)): pos = self.grid.find_empty() agent = SchellingAgent(pos, self, loc) self.grid.position_agent(agent, pos) self.schedule.add(agent) def step(self): ''' Run one step of the model. If All agents are happy, halt the model. ''' self.happy = 0 # Reset counter of happy agents self.schedule.step() self.datacollector.collect(self) if self.happy == self.schedule.get_agent_count(): self.running = False
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 __init__(self, width=50, height=50, torus=True, num_bug=50, seed=42, strategy=None): super().__init__(seed=seed) self.number_of_bug = num_bug if not(strategy in ["stick", "switch"]): raise TypeError("'strategy' must be one of {stick, switch}") self.strategy = strategy self.grid = SingleGrid(width, height, torus) self.schedule = RandomActivation(self) data = {"Bean": lambda m: m.number_of_bean, "Corn": lambda m: m.number_of_corn, "Soy": lambda m: m.number_of_soy, "Bug": lambda m: m.number_of_bug, } self.datacollector = DataCollector(data) # create foods self._populate(Bean) self._populate(Corn) self._populate(Soy) # create bugs for i in range(self.number_of_bug): pos = self.grid.find_empty() bug = Bug(i, self) bug.strategy = self.strategy self.grid.place_agent(bug, pos) self.schedule.add(bug)
def __init__(self, height, width, schedule_type, payoffs=None): """ Create a new Spatial Prisoners' Dilemma Model. Args: height, width: Grid size. There will be one agent per grid cell. schedule_type: Can be "Sequential", "Random", or "Simultaneous". Determines the agent activation regime. payoffs: (optional) Dictionary of (move, neighbor_move) payoffs. """ self.running = True self.grid = SingleGrid(height, width, torus=True) self.schedule_type = schedule_type self.schedule = self.schedule_types[self.schedule_type](self) # Create agents for x in range(width): for y in range(height): agent = PD_Agent((x, y)) self.grid.place_agent(agent, (x, y)) self.schedule.add(agent) self.datacollector = DataCollector( {"Cooperating_Agents": lambda m: len([a for a in m.schedule.agents if a.move == "C"])} )
def __init__(self, g=None, outbreak_size=3, si_trans=0.025): self.schedule = SimultaneousActivation(self) if g is None: g = nx.random_graphs.watts_strogatz_graph(100, 4, 0.05) self.si_trans = si_trans nodes = g.nodes() agent_nodes = list(map(lambda x: SI_Agent(x), nodes)) n_map = dict(zip(nodes, agent_nodes)) agent_edges = list(map(lambda e: (n_map[e[0]], n_map[e[1]]) , g.edges())) for agent in agent_nodes: self.schedule.add(agent) # set the initial outbreak for node in sample(list(agent_nodes), outbreak_size): node.state = State.infected self.network = NetworkSpace(agent_nodes, agent_edges) self.dc = DataCollector({"susceptible": lambda m: self.count_state(m, State.susceptible), "infected": lambda m: self.count_state(m, State.infected)}, {"state": lambda a: a.state.value} ) self.dc.collect(self) #initial state self.running = True
def __init__(self, agent_class, agent_count, agent_args={}, seed=None): ''' Instantiate a new CrisisWorld model. Args: agent_class: Class to instantiate the agents agent_count: How many agents to instantiate with. agent_args: Dictionary of arguments to pass to all agents. seed: Random seed to launch the model with. ''' self.agent_class = agent_class self.agent_count = agent_count self.agent_args = agent_args super().__init__(self.model_class, agents_per_model=2, seed=seed) # Instantiate data collector self.dc = DataCollector(tables={ "Interactions": ["Step", "A", "B", "Outcome", "SPE", "quality"], "Agents": ["Name", "Assets", "Capability", "Bloc"] }) for agent in self.agents: row = {"Name": agent.name, "Assets": agent.assets, "Capability": agent.mil_strength, "Bloc": agent.bloc} self.dc.add_table_row("Agents", row)
def __init__(self, height, width, density, minority_pc, homophily): ''' ''' self.height = height self.width = width self.density = density self.minority_pc = minority_pc self.homophily = homophily self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=True) self.happy = 0 self.datacollector = DataCollector( {"happy": lambda m: m.happy}, # Model-level count of happy agents # For testing purposes, agent's individual x and y {"x": lambda a: a.x, "y": lambda a: a.y}) self.running = True # Set up agents for x in range(self.width): for y in range(self.height): if random.random() < self.density: if random.random() < self.minority_pc: agent_type = 1 else: agent_type = 0 agent = SchellingAgent((x,y), x, y, agent_type) self.grid[y][x] = agent self.schedule.add(agent)
def __init__(self, height, width, density): ''' Create a new restaurant inspection model. Args: height, width: The size of the grid to model density: What fraction of grid cells have a restaurant in them. ''' # Initialize model parameters self.height = height self.width = width self.density = density # Set up model objects self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=False) self.datacollector = DataCollector( {"Good": lambda m: self.count_type(m, "Good"), "Bad": lambda m: self.count_type(m, "Bad")}) # Place a restaurant in each cell with Prob = density for (contents, x, y) in self.grid.coord_iter(): if random.random() < self.density: # Create a restaurant new_restaurant = RestaurantCell((x, y)) self.grid._place_agent((x, y), new_restaurant) self.schedule.add(new_restaurant) self.running = True
def __init__(self, height, width, density): ''' Create a new forest fire model. Args: height, width: The size of the grid to model density: What fraction of grid cells have a tree in them. ''' # Initialize model parameters self.height = height self.width = width self.density = density # Set up model objects self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=False) self.datacollector = DataCollector( {"Fine": lambda m: self.count_type(m, "Fine"), "On Fire": lambda m: self.count_type(m, "On Fire"), "Burned Out": lambda m: self.count_type(m, "Burned Out")}) # Place a tree in each cell with Prob = density for (contents, x, y) in self.grid.coord_iter(): if random.random() < self.density: # Create a tree new_tree = TreeCell((x, y)) # Set all trees in the first column on fire. if x == 0: new_tree.condition = "On Fire" self.grid._place_agent((x, y), new_tree) self.schedule.add(new_tree) self.running = True
def __init__(self, N, width=50, height=50, torus=True): self.num_agents = N self.schedule = RandomActivation(self) self.grid = MultiGrid(height, width, torus) self.create_agents() self.dc = DataCollector({"Gini": lambda m: m.compute_gini()}, {"Wealth": lambda a: a.wealth}) self.running = True
class SIR_Network_Model(Model): def __init__(self, g=None, outbreak_size=3, si_trans=0.025, ir_trans=0.05): self.schedule = SimultaneousActivation(self) if g is None: g = nx.random_graphs.watts_strogatz_graph(100, 4, 0.05) self.si_trans = si_trans self.ir_trans = ir_trans nodes = g.nodes() agent_nodes = list(map(lambda x: SIR_Agent(x), nodes)) n_map = dict(zip(nodes, agent_nodes)) agent_edges = list(map(lambda e: (n_map[e[0]], n_map[e[1]]) , g.edges())) for agent in agent_nodes: self.schedule.add(agent) # set the initial outbreak for node in sample(list(agent_nodes), outbreak_size): node.state = State.infected self.network = NetworkSpace(agent_nodes, agent_edges) self.dc = DataCollector({"susceptible": lambda m: self.count_state(m, State.susceptible), "infected": lambda m: self.count_state(m, State.infected), "resistant": lambda m: self.count_state(m, State.resistant)}, {"state": lambda a: a.state.value} ) self.dc.collect(self) #initial state self.running = True def step(self): self.schedule.step() self.dc.collect(self) # disease dead? if self.count_state(self, State.infected) == 0: self.running = False @staticmethod def count_state(model,state): count = 0 for agent in model.schedule.agents: if agent.state == state: count +=1 return count
def __init__(self, height, width, citizen_density, cop_density, citizen_vision, cop_vision, legitimacy, max_jail_term, active_threshold=.1, arrest_prob_constant=2.3, movement=True, max_iters=1000): super(CivilViolenceModel, self).__init__() self.height = height self.width = width self.citizen_density = citizen_density self.cop_density = cop_density self.citizen_vision = citizen_vision self.cop_vision = cop_vision self.legitimacy = legitimacy self.max_jail_term = max_jail_term self.active_threshold = active_threshold self.arrest_prob_constant = arrest_prob_constant self.movement = movement self.running = True self.max_iters = max_iters self.iteration = 0 self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=True) model_reporters = { "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), "Active": lambda m: self.count_type_citizens(m, "Active"), "Jailed": lambda m: self.count_jailed(m)} agent_reporters = { "x": lambda a: a.pos[0], "y": lambda a: a.pos[1], 'breed': lambda a: a.breed, "jail_sentence": lambda a: getattr(a, 'jail_sentence', None), "condition": lambda a: getattr(a, "condition", None), "arrest_probability": lambda a: getattr(a, "arrest_probability", None) } self.dc = DataCollector(model_reporters=model_reporters, agent_reporters=agent_reporters) unique_id = 0 if self.cop_density + self.citizen_density > 1: raise ValueError( 'Cop density + citizen density must be less than 1') for (contents, x, y) in self.grid.coord_iter(): if random.random() < self.cop_density: cop = Cop(unique_id, (x, y), vision=self.cop_vision, model=self) unique_id += 1 self.grid[y][x] = cop self.schedule.add(cop) elif random.random() < ( self.cop_density + self.citizen_density): citizen = Citizen(unique_id, (x, y), hardship=random.random(), regime_legitimacy=self.legitimacy, risk_aversion=random.random(), threshold=self.active_threshold, vision=self.citizen_vision, model=self) unique_id += 1 self.grid[y][x] = citizen self.schedule.add(citizen)
def __init__(self, N): self.running = True self.num_agents = N self.schedule = RandomActivation(self) self.create_agents() agent_reporters = {"Wealth": lambda a: a.wealth} model_reporters = {"Gini": compute_gini} self.dc = DataCollector(model_reporters=model_reporters, agent_reporters=agent_reporters)
def __init__(self): self.schedule = BaseScheduler(self) for i in range(10): a = MockAgent(i, i) self.schedule.add(a) self.datacollector = DataCollector( {"total_agents": lambda m: m.schedule.get_agent_count()}, {"value": lambda a: a.val}, {"Final_Values": ["agent_id", "final_value"]})
class MockModel(Model): ''' Minimalistic model for testing purposes. ''' schedule = BaseScheduler(None) def __init__(self): self.schedule = BaseScheduler(self) for i in range(10): a = MockAgent(i, i) self.schedule.add(a) self.datacollector = DataCollector( {"total_agents": lambda m: m.schedule.get_agent_count()}, {"value": lambda a: a.val}, {"Final_Values": ["agent_id", "final_value"]}) def step(self): self.schedule.step() self.datacollector.collect(self)
def __init__(self): self.schedule = BaseScheduler(self) self.model_val = 100 for i in range(10): a = MockAgent(i, self, val=i) self.schedule.add(a) self.datacollector = DataCollector( {"total_agents": lambda m: m.schedule.get_agent_count(), "model_value": "model_val"}, {"value": lambda a: a.val, "value2": "val2"}, {"Final_Values": ["agent_id", "final_value"]})
class AlternativeModel(Model): def __init__( self, n=50, j=5, m=30, p_1=0.1, p_2=0.9, p_3=0.9, q_h1=0.1, q_h2=0.1, q_ml=0.5, alpha=10, p_turb=0.1, q_ml_scaling="off", ): # reset random seeds prior to each iteration np.random.seed() random.seed() # save configuration self.conf = { "n": n, "j": j, "m": m, "p_1": p_1, "p_2": p_2, "p_3": p_3, "q_h1": q_h1, "q_h2": q_h2, "q_ml": q_ml, "q_ml_basic": q_ml, "alpha_ml": alpha, "p_turb": p_turb, "q_ml_scaling": q_ml_scaling, } self.running = True self.schedule = BaseScheduler(self) # init environment and data collector self.init_env() self.init_dc() def get_config(self, param, *args): return self.conf[param] # necessary in order to satisfy data collector interface get_m = partialmethod(get_config, "m") get_n = partialmethod(get_config, "n") get_j = partialmethod(get_config, "j") get_p_1 = partialmethod(get_config, "p_1") get_p_2 = partialmethod(get_config, "p_2") get_p_3 = partialmethod(get_config, "p_3") get_q_h1 = partialmethod(get_config, "q_h1") get_q_h2 = partialmethod(get_config, "q_h2") get_q_ml = partialmethod(get_config, "q_ml") get_q_ml_basic = partialmethod(get_config, "q_ml_basic") get_alpha_ml = partialmethod(get_config, "alpha_ml") get_p_turb = partialmethod(get_config, "p_turb") get_q_ml_scaling = partialmethod(get_config, "q_ml_scaling") def get_time(self, *args): return int(self.schedule.time) def init_env(self): # init reality r = Reality("R1", self) self.schedule.add(r) # init humans for i in range(self.conf["n"]): h = Human(f"H{i+1}", self) self.schedule.add(h) # init ML agents # determine ML dimensions self.conf["ml_dims"] = random_dims(self.conf["m"], self.conf["j"]) # init one agent per dimension for i in self.conf["ml_dims"]: m = MLAgent(f"ML{i+1}", self, i) self.schedule.add(m) # init organization o = OrganizationalCode("O1", self) self.schedule.add(o) return def init_dc(self): # data collector enables tracking of metric at each time step self.datacollector = DataCollector( model_reporters={ "time": self.get_time, "m": self.get_m, "n": self.get_n, "j": self.get_j, "p_1": self.get_p_1, "p_2": self.get_p_2, "p_3": self.get_p_3, "q_h1": self.get_q_h1, "q_h2": self.get_q_h2, "q_ml": self.get_q_ml_basic, "alpha_ml": self.get_alpha_ml, "p_turb": self.get_p_turb, "q_ml_scaling": self.get_q_ml_scaling, "avg_q_ml": calc_avg_q_ml, "code_kl": calc_code_kl, "human_kl": calc_human_kl, "human_kl_var": calc_kl_var, "human_kl_dissim": calc_dissim, }) # collect metrics for time step 0 self.datacollector.collect(self) return def get_exp_grp(self): # get list of humans with higher KL than code humans = self.get_human_agents() code = self.get_org_code() return list(filter(lambda h: (h.kl > code.kl), humans)) def get_ml(self, dim): # loop through MLs to find the one with suitable dimension mls = self.get_ml_agents() ml = None for m in mls: if m.state["dim"] == dim: ml = m break return ml def get_reality(self): return self.schedule.agents[0] def get_org_code(self): return self.schedule.agents[-1] def get_human_agents(self): return self.schedule.agents[1:(1 + self.conf["n"])] def get_ml_agents(self): return self.schedule.agents[(1 + self.conf["n"]):-1] def update_kls(self): for h in self.get_human_agents(): h.update_kl() for ml in self.get_ml_agents(): ml.update_kl() code = self.get_org_code() code.update_kl() return def scale_q_ml(self): # for avg. human knowledge related manipulation scaling = self.conf["q_ml_scaling"] if scaling == "on": # for belief related manipulation humans = self.get_human_agents() ml_dims = self.conf["ml_dims"] q_ml = [] for dim in ml_dims: # get knowledgeable group humans_dim = list(filter(lambda h: (h.state[dim] != 0), humans)) # get basic parameters reality = self.get_reality().state[dim] q_ml_basic = self.conf["q_ml_basic"] if len(humans_dim) > 0: # if knowledgeable group exists, count correct and incorrect beliefs votes = [h.state[dim] for h in humans_dim] c = Counter(votes) if len(c) > 1: # if knowledgeable group has correct and incorrect beliefs calculate difference k = c.get(reality) - c.get((-1) * reality) else: if votes[0] == reality: # all human beliefs are correct k = c.get(reality) else: # all human beliefs are incorrect k = c.get((-1) * reality) # scale q_ml parameter according to sigmoid function alpha = self.conf["alpha_ml"] beta = math.log((1 - q_ml_basic) / q_ml_basic) q_ml.append( round(1 / (1 + math.e**(((-1) * k / alpha) + beta)), 3)) else: # if there are no knowledgeable humans, use basic q_ml value q_ml.append(q_ml_basic) self.conf["q_ml"] = q_ml return def environmental_turbulence(self): reality = self.get_reality() reality.turbulence() return def step(self): try: # calculate knowledge levels self.update_kls() # determine expert group for this time step self.exp_grp = self.get_exp_grp() # scale q_ml according to human KL self.scale_q_ml() # update all agents self.schedule.step() # update reality according to turbulence self.environmental_turbulence() # collect metrics for this time step self.datacollector.collect(self) except Exception as e: # log potential erros, but continue with next iteration print("The following error occurred:") print(e) print("Model configuration:") print(self.conf)
def __init__(self, supply, demand, s_strategy, b_strategy, highest_ask=100, lowest_ask=0): self.supply = supply self.demand = demand self.num_sellers = supply.num_agents self.num_buyers = demand.num_agents self.initialize_spread() self.market_price = None self.history = Order_history() self.num_traded = 0 self.num_step = 0 # history records an order as a bid or ask only if it updates # the spread self.num_period = 1 self.loc_period = [0] # where each period happens # Sometimes trade does not happen within a period, # so we need variables to indicate them. self.no_trade = False # When a shift happens, I need to know it so that # I can calculate efficiency properly. self.shifted = False self.shifted_period = -1 self.new_supply = None self.new_demand = None # How agents are activated at each step self.schedule = RandomChoiceActivation(self) # Create agents for i, cost in enumerate(self.supply.price_schedule): self.schedule.add( b_strategy(i, self, "seller", cost, supply.q_per_agent, highest_ask, lowest_ask)) for i, value in enumerate(self.demand.price_schedule): j = self.num_sellers + i self.schedule.add( s_strategy(j, self, "buyer", value, demand.q_per_agent, highest_ask, lowest_ask)) # Collecting data # self.datacollector = DataCollector( # model_reporters={"Period": "num_period", # "OB": "outstanding_bid", # "OBer": get_bidder_id, # "OA": "outstanding_ask", # "OAer": get_asker_id, # "MarketPrice": "market_price", # "Traded": "traded", # "Order": lambda x: x.history.get_last_action(), # "Efficiency": compute_efficiency}, # agent_reporters={"Period": lambda x: x.model.num_period, # "Type": lambda x: type(x), # "Role": "role", # "Value": "value", # "Good": "good", # "Right": "right", # "Surplus": "surplus"} # ) self.datacollector = DataCollector(model_reporters={ "Step": "num_step", "Period": "num_period", "TransNum": "num_traded", "OB": "outstanding_bid", "OA": "outstanding_ask", "MarketPrice": "market_price", "Traded": "traded", "CumulativeActualSurplus": compute_actual_surplus, "TheoreticalSurplus": compute_theoretical_surplus, "CumulativeTS": compute_acc_theoretical_surplus, "Efficiency": compute_efficiency }, agent_reporters={ "Period": lambda x: x.model.num_period, "Type": lambda x: type(x), "Role": "role", "Value": "value", "Good": "good", "Right": "right", "Surplus": "surplus" })
class HITLAdopt(Model): #modify init method to accout for new parameters and constants def __init__(self, height, width, density, wms, wts, wus, td, ve, ae): # Initialize model parameters self.height = height self.width = width self.density = density self.weeklyCampaignSpend = wms self.weeklyTrainingSpend = wts self.weeklyUsabilitySpend = wus #initialize HITL related parameters self.trainingDataWeeklyInput = td self.vizEffect = ve self.learningRate = 0 self.dataInstances = 10000 self.algoAccuracy = ae self.algoEffect = self.algoAccuracy *0.1 # Set up model objects #this sets the activation order of the agents (when they make their moves) to be random each step self.schedule = RandomActivation(self) #this creates the physical grid we are using to simulate word of mouth spread of the model self.grid = Grid(height, width, torus=False) #these use the Mesa DataCollector method to create several trackers to collect data from the model run self.dc_output = DataCollector(model_reporters={"Avg Output Value Per Person Per Week": compute_avg_output}) self.dc_tracker = DataCollector(model_reporters={"Average IA": compute_avg_ia}) self.dc_adoption = DataCollector({"Potential Trialer": lambda m: self.count_type(m, "Potential Trialer"), "Trialer": lambda m: self.count_type(m, "Trialer"), "Adopter": lambda m: self.count_type(m, "Adopter"), "Defector": lambda m: self.count_type(m, "Defector"), "Evangelist": lambda m: self.count_type(m, "Evangelist")}) self.dc_trialers =DataCollector({"Trialer": lambda m: self.count_type(m, "Trialer")}) self.dc_algo = DataCollector({"Learning Rate": compute_learning_rate}) self.dc_master=DataCollector({"Potential Trialer": lambda m: self.count_type(m, "Potential Trialer"), "Trialer": lambda m: self.count_type(m, "Trialer"), "Adopter": lambda m: self.count_type(m, "Adopter"), "Defector": lambda m: self.count_type(m, "Defector"), "Evangelist": lambda m: self.count_type(m, "Evangelist"), "Avg Output Value Per Person": compute_avg_output, "Total Differential in Population": hitl_adv_differential, "Algo Accuracy": compute_algo_accuracy, "Algo Accuracy Increase": compute_learning_rate, "Total Dataset Size": compute_data_instances, "Algorithm Effect": compute_algo_effect, "Avg Data Collection Ouput": compute_avg_dc, "Avg Data Interpretation/Analysis Output": compute_avg_di, "Avg Interpreting Actions Output": compute_avg_ia, "Avg Coaching Output": compute_avg_coaching, "Avg Review Output": compute_avg_review}) #the logic for the creation of the agents, as well as setting the initial values of the agent parameters for x in range(self.width): for y in range(self.height): if random.random() < self.density: new_consultant = Consultant(self, (x, y), np.random.normal(60, 10), np.random.normal(70, 10), 0) if y == 0: new_consultant.condition = "Trialer" self.grid[y][x] = new_consultant self.schedule.add(new_consultant) #run the model when the class is called self.running = True #define logic of what happens with each time step model-wide def step(self): ##update algorithm accuracy, data instances, and effect for new weekly data input self.learningRate = 10000/self.dataInstances/13 - ((self.count_type(self, "Trialer") + self.count_type(self, "Adopter") + self.count_type(self, "Evangelist"))/2000000) self.algoAccuracy += self.learningRate self.dataInstances += self.trainingDataWeeklyInput self.algoEffect = self.algoAccuracy * 0.1 #logic for adoption from marketing, For every 1000 of additional weekly marketing spend, we get 1 new trialer consultant each week for i in range (1,(int(self.weeklyCampaignSpend/100))): prospect = random.choice(self.schedule.agents) #change that agent's state to the next one up if prospect.condition == "Potential Trialer": prospect.condition = "Trialer" if prospect.condition == "Trialer": prospect.condition = "Adopter" #an abandoned idea for trial abandonment...I instead decided to model this at an agent level in that class (See above) '''for i in range (1,(int(self.weeklyMarketingSpend/50))): #take a random agent that's a trialer and log their x y location prospect = random.choice(model.schedule.agents) #change that agent's state to the next one up depending on what it is random_num = np.random.randint(1,100) if prospect.condition == "Trialer": if (prospect.techFluencyScore <70): if random_num > 50: prospect.condition = "PotentialTrialer" ''' #sets the step count self.schedule.step() #logs appropriate data in each of the data collectors self.dc_output.collect(self) self.dc_adoption.collect(self) self.dc_tracker.collect(self) self.dc_algo.collect(self) self.dc_trialers.collect(self) self.dc_master.collect(self) #abandoned logic for stopping model - it was originally when there were no more trialers, now we just give it a set number of steps #method for counting the agents and their conditions so we can track adoption methods @staticmethod def count_type(model, consultant_condition): count = 0 for consultant in model.schedule.agents: if consultant.condition == consultant_condition: count += 1 return count
class EpsteinCivilViolence(Model): """ Model 1 from "Modeling civil violence: An agent-based computational approach," by Joshua Epstein http://www.pnas.org/content/99/suppl_3/7243.full Attributes: height: grid height width: grid width citizen_density: approximate % of cells occupied by citizens. cop_density: approximate % of calles occupied by cops. citizen_vision: number of cells in each direction (N, S, E and W) that citizen can inspect cop_vision: number of cells in each direction (N, S, E and W) that cop can inspect legitimacy: (L) citizens' perception of regime legitimacy, equal across all citizens max_jail_term: (J_max) active_threshold: if (grievance - (risk_aversion * arrest_probability)) > threshold, citizen rebels arrest_prob_constant: set to ensure agents make plausible arrest probability estimates movement: binary, whether agents try to move at step end max_iters: model may not have a natural stopping point, so we set a max. """ def __init__( self, height=40, width=40, citizen_density=0.7, cop_density=0.074, citizen_vision=7, cop_vision=7, legitimacy=0.8, max_jail_term=1000, active_threshold=0.1, arrest_prob_constant=2.3, movement=True, initial_unemployment_rate=0.1, corruption_level=0.1, susceptible_level=0.3, honest_level=0.6, max_iters=1000, ): super().__init__() self.height = height self.width = width self.citizen_density = citizen_density self.cop_density = cop_density self.citizen_vision = citizen_vision self.cop_vision = cop_vision self.legitimacy = legitimacy self.max_jail_term = max_jail_term self.active_threshold = active_threshold self.arrest_prob_constant = arrest_prob_constant self.movement = movement self.initial_unemployment_rate = initial_unemployment_rate self.corruption_level = corruption_level self.susceptible_level = susceptible_level self.max_iters = max_iters self.iteration = 0 self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=True) model_reporters = { "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), "Active": lambda m: self.count_type_citizens(m, "Active"), "Jailed": lambda m: self.count_jailed(m), "Employed": lambda m: self.count_employed(m), "Corrupted": lambda m: self.count_moral_type_citizens(m, "Corrupted"), "Honest": lambda m: self.count_moral_type_citizens(m, "Honest"), "Susceptible": lambda m: self.count_moral_type_citizens(m, "Susceptible") } agent_reporters = { "x": lambda a: a.pos[0], "y": lambda a: a.pos[1], "breed": lambda a: a.breed, "jail_sentence": lambda a: getattr(a, "jail_sentence", None), "condition": lambda a: getattr(a, "condition", None), "arrest_probability": lambda a: getattr(a, "arrest_probability", None), "is_employed": lambda a: getattr(a, "is_employed", None), "moral_condition": lambda a: getattr(a, "moral_condition", None), } self.datacollector = DataCollector(model_reporters=model_reporters, agent_reporters=agent_reporters) unique_id = 0 if self.cop_density + self.citizen_density > 1: raise ValueError( "Cop density + citizen density must be less than 1") if self.initial_unemployment_rate > 1: raise ValueError( "initial_unemployment_rate must be between [0,1] ") if self.corruption_level + self.susceptible_level > 1: raise ValueError("moral level must be less than 1 ") for (contents, x, y) in self.grid.coord_iter(): if self.random.random() < self.cop_density: cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 self.grid[y][x] = cop self.schedule.add(cop) elif self.random.random() < (self.cop_density + self.citizen_density): moral_state = "Honest" is_employed = 1 if self.random.random() < self.initial_unemployment_rate: is_employed = 0 p = self.random.random() if p < self.corruption_level: moral_state = "Corrupted" elif p < self.corruption_level + self.susceptible_level: moral_state = "Susceptible" citizen = Citizen( unique_id, self, (x, y), #updated hardship formula: if agent is employed hardship is alleviated hardship=self.random.random() - (is_employed * self.random.uniform(0.05, 0.15)), legitimacy=self.legitimacy, #updated regime legitimacy, so inital corruption rate is taken into consideration regime_legitimacy=self.legitimacy - self.corruption_level, risk_aversion=self.random.random(), active_threshold=self.active_threshold, #updated threshold: if agent is employed threshold for rebelling is raised threshold=self.active_threshold + (is_employed * self.random.uniform(0.05, 0.15)), vision=self.citizen_vision, is_employed=is_employed, moral_state=moral_state, ) unique_id += 1 self.grid[y][x] = citizen self.schedule.add(citizen) self.running = True self.datacollector.collect(self) def step(self): """ Advance the model by one step and collect data. """ self.schedule.step() # collect data self.datacollector.collect(self) self.iteration += 1 if self.iteration > self.max_iters: self.running = False @staticmethod def count_type_citizens(model, condition, exclude_jailed=False): """ Helper method to count agents by Quiescent/Active. """ count = 0 for agent in model.schedule.agents: if agent.breed == "cop": continue if exclude_jailed and agent.jail_sentence: continue if agent.condition == condition: count += 1 return count @staticmethod def count_moral_type_citizens(model, moral_condition, exclude_jailed=False): """ Helper method to count agents by Quiescent/Active. """ count = 0 for agent in model.schedule.agents: if agent.breed == "cop": continue if exclude_jailed and agent.jail_sentence: continue if agent.moral_state == moral_condition: count += 1 return count @staticmethod def count_jailed(model): """ Helper method to count jailed agents. """ count = 0 for agent in model.schedule.agents: if agent.breed == "citizen" and agent.jail_sentence: count += 1 return count @staticmethod def count_employed(model): """ Helper method to count employed agents. """ count = 0 for agent in model.schedule.agents: if agent.breed == "cop": continue if agent.is_employed == 1: count += 1 return count @staticmethod def count_corrupted(model): """ Helper method to count corrupted agents. """ count = 0 for agent in model.schedule.agents: if agent.breed == "cop": continue if agent.is_corrupted == 1: count += 1 return count
class Schelling(Model): """Model class for the Schelling segregation model.""" def __init__( self, height=20, width=20, density=0.8, minority_pc=0.2, homophily=3, education_boost=0, education_pc=0.2, seed=None, ): """Seed is used to set randomness in the __new__ function of the Model superclass.""" # pylint: disable-msg=unused-argument,super-init-not-called self.height = height self.width = width self.density = density self.minority_pc = minority_pc self.homophily = homophily self.education_boost = education_boost self.education_pc = education_pc self.schedule = RandomActivation(self) self.grid = SingleGrid(height, width, torus=True) self.happy = 0 self.datacollector = DataCollector( {"happy": "happy"}, # Model-level count of happy agents # For testing purposes, agent's individual x and y { "x": lambda a: a.pos[0], "y": lambda a: a.pos[1] }, ) # Set up agents # We use a grid iterator that returns # the coordinates of a cell as well as # its contents. (coord_iter) for cell in self.grid.coord_iter(): x_coord = cell[1] y_coord = cell[2] if self.random.random() < self.density: if self.random.random() < self.minority_pc: agent_type = 1 else: agent_type = 0 agent_homophily = homophily if self.random.random() < self.education_pc: agent_homophily += self.education_boost agent = SchellingAgent((x_coord, y_coord), self, agent_type, agent_homophily) self.grid.position_agent(agent, (x_coord, y_coord)) self.schedule.add(agent) self.running = True self.datacollector.collect(self) def step(self): """Run one step of the model. If All agents are happy, halt the model.""" self.happy = 0 # Reset counter of happy agents self.schedule.step() # collect data self.datacollector.collect(self) if self.happy == self.schedule.get_agent_count(): self.running = False
def __init__(self, height = 50, width = 50, initial_population =200, \ Moore = False, torus = True, regrow = 1, seed = None): ''' Args: height - y axis of grid_size width - x axis of grid size initial_population - number of agents starting moore - type of neighborhood torus - whether or no world wraps regrow - amout each resource grows bas each step process - Number of additonal proces by agents 0 = Movement/Survive; 1 = +trade, 2 = + Initial Parameters: Multigrid ActivationbyBreed (see schedule) Num_Agents counter to account for each agent number timekeeper - dictionary to keep track of time for each section start_time - create initial time datacollector to collect agent data of model ''' self.step_num = 0 self.height = height self.width = width self.initial_population = initial_population self.num_agents = 0 #Mesa Agent Scheduler #self.schedule = schedule.RandomActivationByBreed(self) self.schedule = RandomActivationByBreed(self) self.grid = MultiGrid(self.height, self.width, torus=True) self.regrow = regrow self.running = True self.price_record = defaultdict(list) ''' Recorders Start datacollector Start time recorder ''' self.start_time = time.time() self.datacollector = DataCollector(\ tables = {"Time":["Time Per Step"]}) ''' Creates the landscape: Fours mounds 2 sugar, 2 spice located- 1 in each quadrant imports landscape module to account for various landscape sizes ''' self.resource_dict = {} landscape = Landscape.create_landscape(height, width) #places resources from landscape on grid for k, v in landscape.items(): resource = R.resource(k, self, v, self.regrow) self.grid.place_agent(resource, (resource.pos[0], resource.pos[1])) #POINT self.schedule.add(resource) #fills in empty grids with null value resource agent #Deviation from GrAS -- in empty cells has random resource from 0 to 4 for a, x, y in self.grid.coord_iter(): if a == set(): resource = R.resource((x,y), self, \ (self.random.randrange(0,2), \ self.random.randrange(0,2)),self.regrow) self.grid.place_agent(resource, (resource.pos[0], resource.pos[1])) #POINT self.schedule.add(resource) ''' Creates the agents: ''' pos_array = list(self.schedule.agents_by_breed['resource'].keys()) self.random.shuffle(pos_array) vision_array = np.random.randint(4, 6, self.initial_population) spice_array = np.random.randint(1, 6, self.initial_population) sugar_array = np.random.randint(1, 6, self.initial_population) for n in range(self.initial_population): #x = 0 #y = 0 #print ("position: ", (n, x,y)) #GrAS p. 108 sugar = self.random.randrange(25, 50) spice = self.random.randrange(25, 50) #GrAS p.108 sug_bolism = sugar_array[n] spice_bolism = spice_array[n] #GrAS p. 108 vision = vision_array[n] neighbors = Moore a = N.NetAgent(n, pos_array[n], self, \ {"sug_bolism": sug_bolism, \ "spice_bolism": spice_bolism}, \ {1 : sugar, 2: spice}, {"vision": vision}, \ neighbors) #POINT self.schedule.add(a) self.grid.place_agent(a, pos_array[n])
def __init__(self, init_seed, height, width, density, minority_pc, homophily, virtuous_homophily, nudge_amount, num_to_argue, num_to_convert, convert_prob, convinced_threshold \ , random_move_prob, traditionless_life_decrease, vir_a, vir_b, vir_c, emo_a, emo_b \ , emo_c , emo_bias_a, emo_bias_b, emo_bias_c , strongest_belief_weight, count_extra_pow, count_extra_det \ , count_extra_det_pow, extra_pow, extra_det , belief_of_extra_pow, belief_of_extra_det, belief_of_extra_det_pow): # uncomment to make runs reproducible super().__init__(seed=init_seed) self.height = height self.width = width self.density = density self.minority_pc = minority_pc # segregation logic taken from the Schelling segregation model example self.homophily = homophily self.virtuous_homophily = virtuous_homophily self.convert_prob = convert_prob self.num_to_convert = num_to_convert self.traditionless_life_decrease = traditionless_life_decrease self.steps_since = 0 self.schedule = RandomActivation(self) self.grid = SingleGrid(height, width, torus=True) self.happy = 0 self.convinced = 0 self.virtuous_count = 0 self.emotivist_count = 0 self.virtuous_death_count = 0 self.datacollector = DataCollector( # Model-level variables for graphs {"happy": "happy", "convinced": "convinced", "emotivist_count": "emotivist_count" \ , "virtuous_count": "virtuous_count", "virtuous_death_count": "virtuous_death_count"}, {"x": lambda a: a.pos[0], "y": lambda a: a.pos[1], "happy": lambda a: a.happy, # Agent-level variables "convinced": lambda a: a.convinced, "strongest_belief": lambda a: a.strongest_belief() , "beliefs": lambda a: a.beliefs_string(), "type": lambda a: 0 if isinstance(a, EmotivistAgent) else 1}) self.nudge_amount = nudge_amount self.num_to_argue = num_to_argue self.convinced_threshold = convinced_threshold self.random_move_prob = random_move_prob population = [ "A", "B", "C" ] # defined here because of dependencies on the visualization side (emo_bias in server.py) self.population = population probs_emotivist = np.array([emo_a, emo_b, emo_c]) probs_emotivist = probs_emotivist / np.sum( probs_emotivist) # normalize probs to 1.0 probs_virtuous = np.array([vir_a, vir_b, vir_c]) probs_virtuous = probs_virtuous / np.sum(probs_virtuous) initial_bias_emotivist = { "A": emo_bias_a, "B": emo_bias_b, "C": emo_bias_c } self.strongest_belief_weight = strongest_belief_weight # Set up agents # We get a list of cells in order from the grid iterator # and randomize the list cell_list = list(self.grid.coord_iter()) random.shuffle(cell_list) total_num_agents = self.density * self.width * self.height det_emotivists_added = 0 pow_emotivists_added = 0 det_pow_emotivists_added = 0 # pregenerate a list of strongest_beliefs in random order according to distribution # currently only works with a population of 3 beliefs list_emotivist_choices = [] emo_length = ceil((1.0 - self.minority_pc) * total_num_agents) for i in range(0, emo_length): if (float(i) / emo_length < probs_emotivist[0]): list_emotivist_choices.append(population[0]) elif (float(i) / emo_length < probs_emotivist[0] + probs_emotivist[1]): list_emotivist_choices.append(population[1]) else: list_emotivist_choices.append(population[2]) random.shuffle(list_emotivist_choices) list_virtuous_choices = [] vir_length = ceil(self.minority_pc * total_num_agents) for i in range(0, vir_length): if (float(i) / vir_length < probs_virtuous[0]): list_virtuous_choices.append(population[0]) elif (float(i) / vir_length < probs_virtuous[0] + probs_virtuous[1]): list_virtuous_choices.append(population[1]) else: list_virtuous_choices.append(population[2]) random.shuffle(list_virtuous_choices) # create agents and add to grid i = 0 for cell in cell_list: x = cell[1] y = cell[2] if i < total_num_agents: if i < self.minority_pc * total_num_agents: #initial_strongestbelief_virtuous = random.choices(population, weights=probs_virtuous, k=1)[0] initial_strongestbelief_virtuous = list_virtuous_choices.pop( ) initial_beliefs_virtuous = {} for belief in population: if (belief == initial_strongestbelief_virtuous): initial_beliefs_virtuous[ belief] = strongest_belief_weight else: initial_beliefs_virtuous[belief] = ( 1 - strongest_belief_weight) / ( len(population) - 1) agent = VirtuousAgent(i, (x, y), self, initial_beliefs_virtuous) self.virtuous_count += 1 else: #agent_type = 0 #initial_strongestbelief_emotivist = random.choices(population, weights=probs_emotivist, k=1)[0] initial_strongestbelief_emotivist = list_emotivist_choices.pop( ) initial_beliefs_emotivist = {} det = 0.0 pow = 1.0 if (initial_strongestbelief_emotivist == belief_of_extra_det and det_emotivists_added < count_extra_det): det = extra_det det_emotivists_added += 1 elif (initial_strongestbelief_emotivist == belief_of_extra_pow and pow_emotivists_added < count_extra_pow): pow = extra_pow pow_emotivists_added += 1 elif (initial_strongestbelief_emotivist == belief_of_extra_det_pow and det_pow_emotivists_added < count_extra_det_pow): pow = extra_pow det_pow_emotivists_added += 1 for belief in population: if (belief == initial_strongestbelief_emotivist): initial_beliefs_emotivist[ belief] = strongest_belief_weight else: initial_beliefs_emotivist[belief] = ( 1 - strongest_belief_weight) / ( len(population) - 1) agent = EmotivistAgent(i, (x, y), self, initial_beliefs_emotivist, initial_bias_emotivist, pow, det) self.emotivist_count += 1 i += 1 self.grid.position_agent(agent, (x, y)) self.schedule.add(agent) self.last_agent_id = i # update message with starting probs self.message = "Emotivist probs: " + str(list(map(lambda x: "{:.2f}".format(x), probs_emotivist.tolist()))) \ + ", Virtuous probs: " + str(list(map(lambda x: "{:.2f}".format(x), probs_virtuous.tolist()))) self.running = True self.datacollector.collect(self)
def __init__(self, no_people, total_area, no_agents, Nc_N, n, all_x, all_y, centers, infection_rate, city_label, first_infected): self.num_agents = no_agents grid_size = round( math.sqrt((self.num_agents / no_people) * total_area) * 100) self.grid = MultiGrid(grid_size, grid_size, False) self.schedule = RandomActivation(self) self.running = True flux_store = np.zeros((1, 3)) home_store1 = np.zeros((self.num_agents, 2)) for i in range(round(len(centers) / 2)): print(i, datetime.datetime.now() - begin_time) n_cities = random.sample(range(1, round(len(centers) / 2)), n) for j in range(len(n_cities)): mi = np.count_nonzero(city_label == i + 1) nj = np.count_nonzero(city_label == n_cities[j]) radius = math.sqrt( (centers[i, 0] - centers[n_cities[j], 0])**2 + (centers[i, 1] - centers[n_cities[j], 1])**2) sij = 0 for k in range(len(all_x)): if (all_x[k] - centers[i, 0])**2 + ( all_y[k] - centers[i, 1])**2 < radius**2: sij += 1 sij = sij - mi - nj if sij < 0: sij = 0 try: Tij = (mi * Nc_N * mi * nj) / ((mi + sij) * (mi + nj + sij)) * 10 except ZeroDivisionError: Tij = 0 if Tij > 75: Tij = 75 if Tij > 1 and (i != n_cities[j]): flux_store = np.vstack( (flux_store, (Tij, i + 1, n_cities[j]))) work_place = np.zeros(self.num_agents) work_store1 = np.zeros((num, 2)) flux_store = np.delete(flux_store, 0, 0) for i in np.unique(flux_store[:, 1]): place = np.where(flux_store[:, 1] == i)[0] place1 = np.where(city_label == i)[0] for j in place1: for k in place: if random.uniform(0, 100) < flux_store[k, 0]: work_place[j] = flux_store[k, 2] for i in range(len(work_store1)): if work_place[i] != 0: n = int(work_place[i]) work_store1[i, :] = centers[n, 0], centers[n, 1] for i in range(self.num_agents): home_store1[i, :] = int(all_x[i]), int(all_y[i]) work_store = np.int64(work_store1) home_store = np.int64(home_store1) for i in range(self.num_agents): a = Agent(i, self, infection_rate, work_store, home_store) self.schedule.add(a) self.grid.place_agent(a, (int(all_x[i]), int(all_y[i]))) if i == first_infected: a.infected = 1 self.datacollector = DataCollector( model_reporters={"Tot informed": compute_informed}, agent_reporters={"Infected": "infected"})
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 SEIRX(Model): ''' A model with a number of different agents that reproduces the SEIRX dynamics of pandemic spread in a facility. Note: all times are set to correspond to days G: networkx undirected graph, interaction graph between agents. Edges have to have edge the edge attribute 'contact_type' specifying the closeness of contacts, which can be ['very far', 'far', 'intermediate' and 'close']. Nodes have to have the node attribute 'type' which specifies the agent type of the given node (for example 'student' or 'teacher' in a school scenario). In addition, nodes can have the attribute 'unit', which assigns them to a unit in space (for example a 'class' in a school scenario). verbosity: integer in [0, 1, 2], controls text output to std out to track simulation progress and transmission dynamics. Default = 0. testing, default = 'diagnostic' 'diagnostic': only diagnostic tests for symptomatic agents 'background': adds background screens of all agents after a positive diagnostic test 'preventive': adds preventive screens of agent groups in time intervals specified separately for each agent group in the variable 'screening_interval' infection_duration, default = 11 NOTE: includes the time an agent is exposed but not yet infectious at the beginning of an infection positive integer: mean or median of the infection duration in days list of two floats: mean and standard deviation of a distribution specifying the infection duration in days. These numbers will be used to construct a Weibull distribution from which the infection duration will be drawn for every agent individually exposure_duration, default = 4. Sets the time from transmission to becoming infectious positive integer: mean or median of the exposure duration in days list of two floats: mean and standard deviation of a distribution specifying the exposure duration in days. These numbers will be used to construct a Weibull distributoin from which the exposure duration will be drawn for every agent individually. time_until_symptoms, default = 6. Sets the time from transmission to (potentially) developing symptoms. Symptom probability has to be set for each agent group individually using the parameter 'symptom_probability' positive integer: mean or median of the time until symptoms in days list of two floats: mean and standard deviation of a distribution specifying the time until symptoms in days. These numbers will be used to construct a Weibull distribution from which the time until symptoms will be drawn for every agent individually. quarantine_duration, default = 14. Positive integer, sets the time a positively tested agent is quarantined in days infection_risk_contact_type_weights: dictionary of the form {'very_far':float, 'far':float, 'intermediate':float, 'close':float} that sets transmission risk multipliers for different contact types of agents specified in the contact network G. Default: {'very_far': 0.1, 'far': 0.5, 'intermediate': 1, 'close': 3} subclinical_modifier: default = 1.0. Float, modifies the infectiousness of asymptomatic cases. Example: if subclinical_modifier = 0.5, the infectiousness of an asymptomatic case will be reduced to 50%. K1_contact_types: list of strings from ['very_far', 'far', 'intermediate', 'close']. Definition of contact types for which agents are considered "K1 contact persons" if they had contact to a positively tested person wtith a specified contact intensity. Default = ['close']. diagnostic_test_type, default = 'one_day_PCR'. String, specifies the test technology and test result turnover time used for diagnostic testing. For example 'same_day_antigen' or 'two_day_PCR'. See module "Testing" for different implemented testing techologies. preventive_screening_test_type:, default = 'one_day_PCR', String, specifies the test technology and test result turnover time used for preventive sreening. For example 'same_day_antigen' or 'two_day_PCR'. See module "Testing" for different implemented testing techologies. follow_up_testing_interval, default = None. Positive integer, sets the time a follow-up screen (background screen) is initiated after an initial screen triggered by a positive test result. Only applies if the testing strategy is 'background' or preventive. liberating_testing, default = False. Boolean, flag that specifies, whether or not an agent is released from quarantine after returning a negative test result. index_case, default = 'employee' (nursing home scenario) or 'teacher' (school scenario). Specifies how infections are introduced into the facility. agent_type: If an agent type (for example 'student' or 'teacher' in the school scenario) is specified, a single randomly chosen agent from this agent group will become the index case and no further index cases will be introduced into the scenario. 'continuous': In this case, agents have a continuous risk to become index cases in every simulation step. The risk has to be specified for every agent group individually, using the 'index_probability' parameter. If only a single agent group has a non-zero index probability, then only agents from this group can become index cases. agent_types: dictionary of the structure { agent type: { screening interval : integer, number of days between each preventive screen in this agent group index probability : float in the range [0, 1], sets the probability to become an index case in each time step mask : bool whether or not the agent type is wearing a mask } } The dictionary's keys are the names of the agent types which have to correspond to the node attributes in the contact graph. The screening interval sets the time-delay between preventive screens of this agent group, the index probability sets the probability of a member of this agent group becoming an index case in every time step seed: positive integer, fixes the seed of the simulation to enable repeatable simulation runs. If seed = None, the simulation will be initialized at random. ''' def __init__(self, G, verbosity = 0, base_transmission_risk = 0.05, testing='diagnostic', exposure_duration = [5.0, 1.9], time_until_symptoms = [6.4, 0.8], infection_duration = [10.91, 3.95], quarantine_duration = 10, subclinical_modifier = 0.6, infection_risk_contact_type_weights = { 'very_far': 0.1, 'far': 0.25, 'intermediate': 0.5, 'close': 1}, K1_contact_types = ['close'], diagnostic_test_type = 'one_day_PCR', preventive_screening_test_type = 'same_day_antigen', follow_up_testing_interval = None, liberating_testing = False, index_case = 'teacher', agent_types = { 'teacher': {'screening_interval': None, 'index_probability': 0, 'mask':False}, 'student': {'screening_interval': None, 'index_probability': 0, 'mask':False}, 'family_member':{'screening_interval': None, 'index_probability': 0, 'mask':False}}, age_transmission_risk_discount = \ {'slope':-0.02, 'intercept':1}, age_symptom_discount = \ {'slope':-0.02545, 'intercept':0.854545}, mask_filter_efficiency = {'exhale':0, 'inhale':0}, transmission_risk_ventilation_modifier = 0, seed = None): # mesa models already implement fixed seeds through their own random # number generations. Sadly, we need to use the Weibull distribution # here, which is not implemented in mesa's random number generation # module. Therefore, we need to initialize the numpy random number # generator with the given seed as well if seed != None: np.random.seed(seed) # sets the (daily) transmission risk for a household contact without # any precautions. Target infection ratios are taken from literature # and the value of the base_transmission_risk is calibrated such that # the simulation produces the correct infection ratios in a household # setting with the given distributions for epidemiological parameters # of agents self.base_transmission_risk = base_transmission_risk # sets the level of detail of text output to stdout (0 = no output) self.verbosity = check_positive_int(verbosity) # flag to turn off the testing & tracing strategy self.testing = check_testing(testing) self.running = True # needed for the batch runner implemented by mesa # set the interaction mode to simultaneous activation self.schedule = SimultaneousActivation(self) # internal step counter used to launch screening tests self.Nstep = 0 # since we may have weekday-specific contact networks, we need # to keep track of the day of the week. Since the index case # per default is introduced at step 0 in index case mode, we # need to offset the starting weekday by a random number of weekdays # to prevent artifacts from always starting on the same day of the week self.weekday_offset = self.random.randint(1, 8) self.weekday = self.Nstep + self.weekday_offset ## epidemiological parameters: can be either a single integer or the # mean and standard deviation of a distribution self.epi_params = {} # counter to track the number of pathological parameter combinations # that had to be re-rolled (only here for debugging and control reasons) self.param_rerolls = 0 for param, param_name in zip( [exposure_duration, time_until_symptoms, infection_duration], ['exposure_duration', 'time_until_symptoms', 'infection_duration' ]): if isinstance(param, int): self.epi_params[param_name] = check_positive_int(param) elif isinstance(param, list) and len(param) == 2: mu = check_positive(param[0]) var = check_positive(param[1]**2) shape = root_scalar(get_weibull_shape, args=(mu, var), method='toms748', bracket=[0.2, 500]).root scale = get_weibull_scale(mu, shape) self.epi_params[param_name] = [shape, scale] else: print('{} format not recognized, should be either a single '+\ 'int or a tuple of two positive numbers'.format(param_name)) # duration of quarantine self.quarantine_duration = check_positive_int(quarantine_duration) self.infection_risk_area_weights = check_contact_type_dict( infection_risk_contact_type_weights) # modifier for infectiosness for asymptomatic cases self.subclinical_modifier = check_positive(subclinical_modifier) # modifiers for the infection risk, depending on contact type self.infection_risk_contact_type_weights = infection_risk_contact_type_weights # discounts for age-dependent transmission and reception risks and # symptom probabilities self.age_transmission_risk_discount = \ check_discount(age_transmission_risk_discount) self.age_symptom_discount = \ check_discount(age_symptom_discount) self.mask_filter_efficiency = mask_filter_efficiency self.transmission_risk_ventilation_modifier = \ transmission_risk_ventilation_modifier ## agents and their interactions # interaction graph of agents self.G = check_graph(G) # add weights as edge attributes so they can be visualised easily if type(self.G) == nx.MultiGraph: for (u, v, key, contact_type) in self.G.edges(keys=True, data='contact_type'): self.G[u][v][key]['weight'] = \ self.infection_risk_contact_type_weights[contact_type] else: for e in G.edges(data=True): G[e[0]][e[1]]['weight'] = self.infection_risk_contact_type_weights\ [G[e[0]][e[1]]['contact_type']] # extract the different agent types from the contact graph self.agent_types = list(agent_types.keys()) # dictionary of available agent classes with agent types and classes self.agent_classes = {} if 'resident' in agent_types: from scseirx.agent_resident import resident self.agent_classes['resident'] = resident if 'employee' in agent_types: from scseirx.agent_employee import employee self.agent_classes['employee'] = employee if 'student' in agent_types: from scseirx.agent_student import student self.agent_classes['student'] = student if 'teacher' in agent_types: from scseirx.agent_teacher import teacher self.agent_classes['teacher'] = teacher if 'family_member' in agent_types: from scseirx.agent_family_member import family_member self.agent_classes['family_member'] = family_member ## set agent characteristics for all agent groups # list of agent characteristics params = [ 'screening_interval', 'index_probability', 'mask', 'voluntary_testing_rate' ] # default values that are used in case a characteristic is not specified # for an agent group defaults = { 'screening_interval': None, 'index_probability': 0, 'mask': False, 'voluntary_testing_rate': 1 } # sanity checks that are applied to parameters passed to the class # constructor to make sure they conform to model expectations check_funcs = [ check_positive_int, check_probability, check_bool, check_probability ] # member dicts that store the parameter values for each agent group self.screening_intervals = {} self.index_probabilities = {} self.masks = {} self.voluntary_testing_rates = {} param_dicts = [ self.screening_intervals, self.index_probabilities, self.masks, self.voluntary_testing_rates ] # iterate over all possible agent parameters and agent groups: set the # respective value to the value passed through the constructor or to # the default value if no value has been passed for param, param_dict, check_func in zip(params, param_dicts, check_funcs): for at in self.agent_types: try: param_dict.update({at: check_func(agent_types[at][param])}) except KeyError: param_dict.update({at: defaults[param]}) # pass all parameters relevant for the testing strategy to the testing # class. NOTE: this separation is not a strictly necessary design # decision but I like to keep the parameters related to testing and # tracing in a separate place self.Testing = Testing(self, diagnostic_test_type, preventive_screening_test_type, check_positive_int(follow_up_testing_interval), self.screening_intervals, check_bool(liberating_testing), check_K1_contact_types(K1_contact_types), verbosity) # specifies either continuous probability for index cases in agent # groups based on the 'index_probability' for each agent group, or a # single (randomly chosen) index case in the passed agent group self.index_case = check_index_case(index_case, self.agent_types) self.num_agents = {} ## add agents # extract the agent nodes from the graph and add them to the scheduler for agent_type in self.agent_types: IDs = [x for x, y in G.nodes(data=True) if y['type'] == agent_type] self.num_agents.update({agent_type: len(IDs)}) # get the agent locations (units) from the graph node attributes units = [self.G.nodes[ID]['unit'] for ID in IDs] for ID, unit in zip(IDs, units): tmp_epi_params = {} # for each of the three epidemiological parameters, check if # the parameter is an integer (if yes, pass it directly to the # agent constructor), or if it is specified by the shape and # scale parameters of a Weibull distribution. In the latter # case, draw a new number for every agent from the distribution # NOTE: parameters drawn from the distribution are rounded to # the nearest integer while True: for param_name, param in self.epi_params.items(): if isinstance(param, int): tmp_epi_params[param_name] = param else: tmp_epi_params[param_name] = \ round(weibull_two_param(param[0], param[1])) if tmp_epi_params['exposure_duration'] > 0 and \ tmp_epi_params['time_until_symptoms'] >= \ tmp_epi_params['exposure_duration'] and\ tmp_epi_params['infection_duration'] > \ tmp_epi_params['exposure_duration']: break else: self.param_rerolls += 1 if verbosity > 1: print('pathological epi-param case found!') print(tmp_epi_params) # check if the agent participates in voluntary testing p = self.voluntary_testing_rates[agent_type] voluntary_testing = np.random.choice([True, False], p=[p, 1 - p]) a = self.agent_classes[agent_type]( ID, unit, self, tmp_epi_params['exposure_duration'], tmp_epi_params['time_until_symptoms'], tmp_epi_params['infection_duration'], voluntary_testing, verbosity) self.schedule.add(a) # infect the first agent in single index case mode if self.index_case != 'continuous': infection_targets = [ a for a in self.schedule.agents if a.type == index_case ] # pick a random agent to infect in the selected agent group target = self.random.randint(0, len(infection_targets) - 1) infection_targets[target].exposed = True if self.verbosity > 0: print('{} exposed: {}'.format(index_case, infection_targets[target].ID)) # list of agents that were tested positive this turn self.newly_positive_agents = [] # flag that indicates if there were new positive tests this turn self.new_positive_tests = False # dictionary of flags that indicate whether a given agent group has # been creened this turn self.screened_agents = { 'reactive': {agent_type: False for agent_type in self.agent_types}, 'follow_up': {agent_type: False for agent_type in self.agent_types}, 'preventive': {agent_type: False for agent_type in self.agent_types} } # dictionary of counters that count the days since a given agent group # was screened. Initialized differently for different index case modes if (self.index_case == 'continuous') or \ (not np.any(list(self.Testing.screening_intervals.values()))): self.days_since_last_agent_screen = { agent_type: 0 for agent_type in self.agent_types } # NOTE: if we initialize these variables with 0 in the case of a single # index case, we introduce a bias since in 'single index case mode' the # first index case will always become exposed in step 0. To realize # random states of the preventive sceening procedure with respect to the # incidence of the index case, we have to randomly pick the days since # the last screen for the agent group from which the index case is else: self.days_since_last_agent_screen = {} for agent_type in self.agent_types: if self.Testing.screening_intervals[agent_type] != None: self.days_since_last_agent_screen.update({ agent_type: self.random.choice( range( 0, self.Testing.screening_intervals[agent_type] + 1)) }) else: self.days_since_last_agent_screen.update({agent_type: 0}) # dictionary of flags that indicates whether a follow-up screen for a # given agent group is scheduled self.scheduled_follow_up_screen = { agent_type: False for agent_type in self.agent_types } # counters self.number_of_diagnostic_tests = 0 self.number_of_preventive_screening_tests = 0 self.positive_tests = { self.Testing.preventive_screening_test_type: {agent_type: 0 for agent_type in self.agent_types}, self.Testing.diagnostic_test_type: {agent_type: 0 for agent_type in self.agent_types} } self.undetected_infections = 0 self.predetected_infections = 0 self.pending_test_infections = 0 self.quarantine_counters = { agent_type: 0 for agent_type in agent_types.keys() } self.false_negative = 0 # data collectors to save population counts and agent states every # time step self.datacollector = DataCollector( model_reporters= { 'N_diagnostic_tests':get_N_diagnostic_tests, 'N_preventive_screening_tests':get_N_preventive_screening_tests, 'diagnostic_test_detected_infections_student':\ get_diagnostic_test_detected_infections_student, 'diagnostic_test_detected_infections_teacher':\ get_diagnostic_test_detected_infections_teacher, 'diagnostic_test_detected_infections_family_member':\ get_diagnostic_test_detected_infections_family_member, 'preventive_test_detected_infections_student':\ get_preventive_test_detected_infections_student, 'preventive_test_detected_infections_teacher':\ get_preventive_test_detected_infections_teacher, 'preventive_test_detected_infections_family_member':\ get_preventive_test_detected_infections_family_member, 'undetected_infections':get_undetected_infections, 'predetected_infections':get_predetected_infections, 'pending_test_infections':get_pending_test_infections }, agent_reporters= { 'infection_state': get_infection_state, 'quarantine_state': get_quarantine_state }) ## transmission risk modifiers def get_transmission_risk_contact_type_modifier(self, source, target): # construct the edge key as combination between agent IDs and weekday n1 = source.ID n2 = target.ID tmp = [n1, n2] tmp.sort() n1, n2 = tmp key = n1 + n2 + 'd{}'.format(self.weekday) contact_weight = self.G.get_edge_data(n1, n2, key)['weight'] # the link weight is a multiplicative modifier of the link strength. # contacts of type "close" have, by definition, a weight of 1. Contacts # of type intermediate, far or very far have a weight < 1 and therefore # are less likely to transmit an infection. For example, if the contact # type far has a weight of 0.2, a contact of type far has only a 20% # chance of transmitting an infection, when compared to a contact of # type close. To calculate the probability of success p in the Bernoulli # trial, we need to reduce the base risk (or base probability of success) # by the modifications introduced by preventive measures. These # modifications are formulated in terms of "probability of failure", or # "q". A low contact weight has a high probability of failure, therefore # we return q = 1 - contact_weight here. q1 = 1 - contact_weight return q1 def get_transmission_risk_age_modifier_transmission(self, source): '''linear function such that at age 18 the risk is that of an adult (=1). The slope of the line needs to be calibrated. ''' age = source.age max_age = 18 if age <= max_age: age_weight = self.age_transmission_risk_discount['slope'] * \ np.abs(age - max_age) + self.age_transmission_risk_discount['intercept'] # The age weight can be interpreted as multiplicative factor that # reduces the chance for transmission with decreasing age. The slope # of the age_transmission_discount function is the decrease (in % of # the transmission risk for an 18 year old or above) of transmission # risk with every year a person is younger than 18 (the intercept is # 1 by definition). # To calculate the probability of success p in the Bernoulli # trial, we need to reduce the base risk (or base probability of success) # by the modifications introduced by preventive measures. These # modifications are formulated in terms of "probability of failure", or # "q". A low age weight has a high probability of failure, therefore # we return q = 1 - age_weight here. q2 = 1 - age_weight else: q2 = 0 return q2 def get_transmission_risk_age_modifier_reception(self, target): '''linear function such that at age 18 the risk is that of an adult (=1). The slope of the line needs to be calibrated. ''' age = target.age max_age = 18 if age <= max_age: age_weight = self.age_transmission_risk_discount['slope'] * \ np.abs(age - max_age) + self.age_transmission_risk_discount['intercept'] # see description in get_transmission_risk_age_modifier_transmission q3 = 1 - age_weight else: q3 = 0 return q3 # infectiousness is constant and high until symptom onset and then # decreases monotonically until agents are not infectious anymore # at the end of the infection_duration def get_transmission_risk_progression_modifier(self, source): if source.days_since_exposure < source.exposure_duration: progression_weight = 0 elif source.days_since_exposure <= source.time_until_symptoms: progression_weight = 1 elif source.days_since_exposure > source.time_until_symptoms and \ source.days_since_exposure <= source.infection_duration: # we add 1 in the denominator, such that the source is also # (slightly) infectious on the last day of the infection_duration progression_weight = \ (source.days_since_exposure - source.time_until_symptoms) / \ (source.infection_duration - source.time_until_symptoms + 1) else: progression_weight = 0 # see description in get_transmission_risk_age_modifier_transmission q4 = 1 - progression_weight return q4 def get_transmission_risk_subclinical_modifier(self, source): if source.symptomatic_course == False: subclinical_weight = self.subclinical_modifier else: subclinical_weight = 1 # see description in get_transmission_risk_age_modifier_transmission q5 = 1 - subclinical_weight return q5 def get_transmission_risk_exhale_modifier(self, source): if source.mask: exhale_weight = self.mask_filter_efficiency['exhale'] else: exhale_weight = 1 # see description in get_transmission_risk_age_modifier_transmission q6 = 1 - exhale_weight return q6 def get_transmission_risk_inhale_modifier(self, target): if target.mask: inhale_weight = self.mask_filter_efficiency['inhale'] else: inhale_weight = 1 # see description in get_transmission_risk_age_modifier_transmission q7 = 1 - inhale_weight return q7 def get_transmission_risk_ventilation_modifier(self): ventilation_weight = self.transmission_risk_ventilation_modifier # see description in get_transmission_risk_age_modifier_transmission q8 = 1 - ventilation_weight return q8 def test_agent(self, a, test_type): a.tested = True a.pending_test = test_type if test_type == self.Testing.diagnostic_test_type: self.number_of_diagnostic_tests += 1 else: self.number_of_preventive_screening_tests += 1 if a.exposed: # tests that happen in the period of time in which the agent is # exposed but not yet infectious if a.days_since_exposure >= self.Testing.tests[test_type][ 'time_until_testable']: if self.verbosity > 1: print( '{} {} sent positive sample (even though not infectious yet)' .format(a.type, a.ID)) a.sample = 'positive' self.predetected_infections += 1 self.positive_tests[test_type][a.type] += 1 else: if self.verbosity > 1: print('{} {} sent negative sample'.format(a.type, a.ID)) a.sample = 'negative' elif a.infectious: # tests that happen in the period of time in which the agent is # infectious and the infection is detectable by a given test if a.days_since_exposure >= self.Testing.tests[test_type]['time_until_testable'] and \ a.days_since_exposure <= self.Testing.tests[test_type]['time_testable']: if self.verbosity > 1: print('{} {} sent positive sample'.format(a.type, a.ID)) a.sample = 'positive' self.positive_tests[test_type][a.type] += 1 # track the undetected infections to assess how important they are # for infection spread else: if self.verbosity > 1: print( '{} {} sent negative sample (even though infectious)'. format(a.type, a.ID)) a.sample = 'negative' self.undetected_infections += 1 else: if self.verbosity > 1: print('{} {} sent negative sample'.format(a.type, a.ID)) a.sample = 'negative' # for same-day testing, immediately act on the results of the test if a.days_since_tested >= self.Testing.tests[test_type][ 'time_until_test_result']: a.act_on_test_result() def screen_agents(self, agent_group, test_type, screen_type): # only test agents that have not been tested already in this simulation # step and that are not already known positive cases if self.verbosity > 0: print('initiating {} {} screen'\ .format(screen_type, agent_group)) untested_agents = [ a for a in self.schedule.agents if (a.tested == False and a.known_positive == False and a.type == agent_group) ] if len(untested_agents) > 0: self.screened_agents[screen_type][agent_group] = True self.days_since_last_agent_screen[agent_group] = 0 # only test agents if they participate in voluntary testing if screen_type == 'preventive': for a in untested_agents: if a.voluntary_testing: self.test_agent(a, test_type) else: if self.verbosity > 1: print('not testing {} {}, not participating in voluntary testing'\ .format(agent_group, a.ID)) else: for a in untested_agents: self.test_agent(a, test_type) if self.verbosity > 0: print() else: if self.verbosity > 0: print( 'no agents tested because all agents have already been tested' ) # the type of the test used in the pending test result is stored in the # variable pending_test def collect_test_results(self): agents_with_test_results = [ a for a in self.schedule.agents if (a.pending_test and a.days_since_tested >= self.Testing.tests[ a.pending_test]['time_until_test_result']) ] return agents_with_test_results def trace_contacts(self, a): if a.quarantined == False: a.quarantined = True a.quarantine_start = self.Nstep if self.verbosity > 0: print('qurantined {} {}'.format(a.type, a.ID)) # find all agents that share edges with the agent # that are classified as K1 contact types in the testing # strategy K1_contacts = [ e[1] for e in self.G.edges(a.ID, data=True) if e[2]['contact_type'] in self.Testing.K1_contact_types ] K1_contacts = [a for a in self.schedule.agents if a.ID in K1_contacts] for K1_contact in K1_contacts: if self.verbosity > 0: print('quarantined {} {} (K1 contact of {} {})'.format( K1_contact.type, K1_contact.ID, a.type, a.ID)) K1_contact.quarantined = True K1_contact.quarantine_start = self.Nstep def test_symptomatic_agents(self): # find symptomatic agents that have not been tested yet and are not # in quarantine and test them newly_symptomatic_agents = np.asarray([ a for a in self.schedule.agents if (a.symptoms == True and a.tested == False and a.quarantined == False) ]) for a in newly_symptomatic_agents: # all symptomatic agents are quarantined by default if self.verbosity > 0: print('quarantined: {} {}'.format(a.type, a.ID)) a.quarantined = True a.quarantine_start = self.Nstep self.test_agent(a, self.Testing.diagnostic_test_type) def quarantine_contacts(self): # trace and quarantine contacts of newly positive agents if len(self.newly_positive_agents) > 0: if self.verbosity > 0: print('new positive test(s) from {}'.format( [a.ID for a in self.newly_positive_agents])) # send all K1 contacts of positive agents into quarantine for a in self.newly_positive_agents: self.trace_contacts(a) # indicate that a screen should happen because there are new # positive test results self.new_positive_tests = True self.newly_positive_agents = [] else: self.new_positive_tests = False def step(self): self.weekday = (self.Nstep + self.weekday_offset) % 7 + 1 # if the connection graph is time-resloved, set the graph that is # used to determine connections in this step to the sub-graph corres- # ponding to the current day of the week if self.dynamic_connections: self.G = self.weekday_connections[self.weekday] if self.verbosity > 0: print('weekday {}'.format(self.weekday)) if self.testing: for agent_type in self.agent_types: for screen_type in ['reactive', 'follow_up', 'preventive']: self.screened_agents[screen_type][agent_type] = False if self.verbosity > 0: print('* testing and tracing *') self.test_symptomatic_agents() # collect and act on new test results agents_with_test_results = self.collect_test_results() for a in agents_with_test_results: a.act_on_test_result() self.quarantine_contacts() # screening: # a screen should take place if # (a) there are new positive test results # (b) as a follow-up screen for a screen that was initiated because # of new positive cases # (c) if there is a preventive screening policy and it is time for # a preventive screen in a given agent group # (a) if (self.testing == 'background' or self.testing == 'preventive')\ and self.new_positive_tests == True: for agent_type in self.screening_agents: self.screen_agents(agent_type, self.Testing.diagnostic_test_type, 'reactive') self.scheduled_follow_up_screen[agent_type] = True # (b) elif (self.testing == 'background' or self.testing == 'preventive') and \ self.Testing.follow_up_testing_interval != None and \ sum(list(self.scheduled_follow_up_screen.values())) > 0: for agent_type in self.screening_agents: if self.scheduled_follow_up_screen[agent_type] and\ self.days_since_last_agent_screen[agent_type] >=\ self.Testing.follow_up_testing_interval: self.screen_agents(agent_type, self.Testing.diagnostic_test_type, 'follow_up') else: if self.verbosity > 0: print('not initiating {} follow-up screen (last screen too close)'\ .format(agent_type)) # (c) elif self.testing == 'preventive' and \ np.any(list(self.Testing.screening_intervals.values())): for agent_type in self.screening_agents: interval = self.Testing.screening_intervals[agent_type] assert interval in [7, 3, 2, None], \ 'testing interval {} for agent type {} not supported!'\ .format(interval, agent_type) # (c.1) testing every 7 days = testing on Mondays if interval == 7 and self.weekday == 1: self.screen_agents(agent_type, self.Testing.preventive_screening_test_type,\ 'preventive') # (c.2) testing every 3 days = testing on Mo & Turs elif interval == 3 and self.weekday in [1, 4]: self.screen_agents(agent_type, self.Testing.preventive_screening_test_type,\ 'preventive') # (c.3) testing every 2 days = testing on Mo, Wed & Fri elif interval == 2 and self.weekday in [1, 3, 5]: self.screen_agents(agent_type, self.Testing.preventive_screening_test_type,\ 'preventive') # No interval specified = no testing, even if testing # mode == preventive elif interval == None: pass else: if self.verbosity > 0: print('not initiating {} preventive screen (wrong weekday)'\ .format(agent_type)) else: # do nothing pass for agent_type in self.agent_types: if not (self.screened_agents['reactive'][agent_type] or \ self.screened_agents['follow_up'][agent_type] or \ self.screened_agents['preventive'][agent_type]): self.days_since_last_agent_screen[agent_type] += 1 if self.verbosity > 0: print('* agent interaction *') self.datacollector.collect(self) self.schedule.step() self.Nstep += 1
def __init__(self, diffusivity, timestep, filename, grid_pickle=None): ''' LanguageModels contain LanguageAgents and other objects to run the model. Args: diffusivity: filename: grid_pickle: ''' super().__init__() self.num_agents = 0 self.grid = NeighborList(neighborhood_size=8, loadpickle=grid_pickle) self.schedule = SimultaneousActivation(self) self.diffusion = np.array(diffusivity) self.pop_data = self.read_file(filename) self.agent_pop = {} self.timestep = timestep # for loc in self.pop_data.loc[:]['location_id']: for row in self.pop_data.itertuples(index=True): # print('row: ' + str(row)) # read in population data self.agent_pop.update({int(row[1]): [int(x) for x in row[6:]]}) # print(self.agent_pop[row[1]]) self.num_agents += 1 # Create agents, add them to scheduler if float(row[11]) == 0: a = LanguageAgent(self, str(row[2]), int(row[1]), [0, 1]) else: a = LanguageAgent(self, str(row[2]), int(row[1]), [float(row[5]), 1 - (float(row[5]))]) self.schedule.add(a) # add the agent at position (x,y) # print('lat: ' + str(self.pop_data.loc[idx]['latitude'])) # print('long ' + str(self.pop_data.loc[idx]['longitude'])) print('id:' + str(a.unique_id)) self.grid.add_agent( (float(self.pop_data.loc[a.unique_id - 1]['latitude']), float(self.pop_data.loc[a.unique_id - 1]['longitude'])), a) # print('added') if grid_pickle is None: self.grid.calc_neighbors() self.datacollector = DataCollector( model_reporters={}, agent_reporters={ "pop": lambda x: x.population, "p_p_german": lambda x: x.p_probability[0], "p_p_slovene": lambda x: x.p_probability[1], "p_german": lambda x: x.probability[0], "p_slovene": lambda x: x.probability[1], "p_diff": lambda x: np.sum(np.abs(x.probability - x.p_probability)), "lat": lambda x: x.pos[0], "long": lambda x: x.pos[1] })
def __init__(self, G, verbosity = 0, base_transmission_risk = 0.05, testing='diagnostic', exposure_duration = [5.0, 1.9], time_until_symptoms = [6.4, 0.8], infection_duration = [10.91, 3.95], quarantine_duration = 10, subclinical_modifier = 0.6, infection_risk_contact_type_weights = { 'very_far': 0.1, 'far': 0.25, 'intermediate': 0.5, 'close': 1}, K1_contact_types = ['close'], diagnostic_test_type = 'one_day_PCR', preventive_screening_test_type = 'same_day_antigen', follow_up_testing_interval = None, liberating_testing = False, index_case = 'teacher', agent_types = { 'teacher': {'screening_interval': None, 'index_probability': 0, 'mask':False}, 'student': {'screening_interval': None, 'index_probability': 0, 'mask':False}, 'family_member':{'screening_interval': None, 'index_probability': 0, 'mask':False}}, age_transmission_risk_discount = \ {'slope':-0.02, 'intercept':1}, age_symptom_discount = \ {'slope':-0.02545, 'intercept':0.854545}, mask_filter_efficiency = {'exhale':0, 'inhale':0}, transmission_risk_ventilation_modifier = 0, seed = None): # mesa models already implement fixed seeds through their own random # number generations. Sadly, we need to use the Weibull distribution # here, which is not implemented in mesa's random number generation # module. Therefore, we need to initialize the numpy random number # generator with the given seed as well if seed != None: np.random.seed(seed) # sets the (daily) transmission risk for a household contact without # any precautions. Target infection ratios are taken from literature # and the value of the base_transmission_risk is calibrated such that # the simulation produces the correct infection ratios in a household # setting with the given distributions for epidemiological parameters # of agents self.base_transmission_risk = base_transmission_risk # sets the level of detail of text output to stdout (0 = no output) self.verbosity = check_positive_int(verbosity) # flag to turn off the testing & tracing strategy self.testing = check_testing(testing) self.running = True # needed for the batch runner implemented by mesa # set the interaction mode to simultaneous activation self.schedule = SimultaneousActivation(self) # internal step counter used to launch screening tests self.Nstep = 0 # since we may have weekday-specific contact networks, we need # to keep track of the day of the week. Since the index case # per default is introduced at step 0 in index case mode, we # need to offset the starting weekday by a random number of weekdays # to prevent artifacts from always starting on the same day of the week self.weekday_offset = self.random.randint(1, 8) self.weekday = self.Nstep + self.weekday_offset ## epidemiological parameters: can be either a single integer or the # mean and standard deviation of a distribution self.epi_params = {} # counter to track the number of pathological parameter combinations # that had to be re-rolled (only here for debugging and control reasons) self.param_rerolls = 0 for param, param_name in zip( [exposure_duration, time_until_symptoms, infection_duration], ['exposure_duration', 'time_until_symptoms', 'infection_duration' ]): if isinstance(param, int): self.epi_params[param_name] = check_positive_int(param) elif isinstance(param, list) and len(param) == 2: mu = check_positive(param[0]) var = check_positive(param[1]**2) shape = root_scalar(get_weibull_shape, args=(mu, var), method='toms748', bracket=[0.2, 500]).root scale = get_weibull_scale(mu, shape) self.epi_params[param_name] = [shape, scale] else: print('{} format not recognized, should be either a single '+\ 'int or a tuple of two positive numbers'.format(param_name)) # duration of quarantine self.quarantine_duration = check_positive_int(quarantine_duration) self.infection_risk_area_weights = check_contact_type_dict( infection_risk_contact_type_weights) # modifier for infectiosness for asymptomatic cases self.subclinical_modifier = check_positive(subclinical_modifier) # modifiers for the infection risk, depending on contact type self.infection_risk_contact_type_weights = infection_risk_contact_type_weights # discounts for age-dependent transmission and reception risks and # symptom probabilities self.age_transmission_risk_discount = \ check_discount(age_transmission_risk_discount) self.age_symptom_discount = \ check_discount(age_symptom_discount) self.mask_filter_efficiency = mask_filter_efficiency self.transmission_risk_ventilation_modifier = \ transmission_risk_ventilation_modifier ## agents and their interactions # interaction graph of agents self.G = check_graph(G) # add weights as edge attributes so they can be visualised easily if type(self.G) == nx.MultiGraph: for (u, v, key, contact_type) in self.G.edges(keys=True, data='contact_type'): self.G[u][v][key]['weight'] = \ self.infection_risk_contact_type_weights[contact_type] else: for e in G.edges(data=True): G[e[0]][e[1]]['weight'] = self.infection_risk_contact_type_weights\ [G[e[0]][e[1]]['contact_type']] # extract the different agent types from the contact graph self.agent_types = list(agent_types.keys()) # dictionary of available agent classes with agent types and classes self.agent_classes = {} if 'resident' in agent_types: from scseirx.agent_resident import resident self.agent_classes['resident'] = resident if 'employee' in agent_types: from scseirx.agent_employee import employee self.agent_classes['employee'] = employee if 'student' in agent_types: from scseirx.agent_student import student self.agent_classes['student'] = student if 'teacher' in agent_types: from scseirx.agent_teacher import teacher self.agent_classes['teacher'] = teacher if 'family_member' in agent_types: from scseirx.agent_family_member import family_member self.agent_classes['family_member'] = family_member ## set agent characteristics for all agent groups # list of agent characteristics params = [ 'screening_interval', 'index_probability', 'mask', 'voluntary_testing_rate' ] # default values that are used in case a characteristic is not specified # for an agent group defaults = { 'screening_interval': None, 'index_probability': 0, 'mask': False, 'voluntary_testing_rate': 1 } # sanity checks that are applied to parameters passed to the class # constructor to make sure they conform to model expectations check_funcs = [ check_positive_int, check_probability, check_bool, check_probability ] # member dicts that store the parameter values for each agent group self.screening_intervals = {} self.index_probabilities = {} self.masks = {} self.voluntary_testing_rates = {} param_dicts = [ self.screening_intervals, self.index_probabilities, self.masks, self.voluntary_testing_rates ] # iterate over all possible agent parameters and agent groups: set the # respective value to the value passed through the constructor or to # the default value if no value has been passed for param, param_dict, check_func in zip(params, param_dicts, check_funcs): for at in self.agent_types: try: param_dict.update({at: check_func(agent_types[at][param])}) except KeyError: param_dict.update({at: defaults[param]}) # pass all parameters relevant for the testing strategy to the testing # class. NOTE: this separation is not a strictly necessary design # decision but I like to keep the parameters related to testing and # tracing in a separate place self.Testing = Testing(self, diagnostic_test_type, preventive_screening_test_type, check_positive_int(follow_up_testing_interval), self.screening_intervals, check_bool(liberating_testing), check_K1_contact_types(K1_contact_types), verbosity) # specifies either continuous probability for index cases in agent # groups based on the 'index_probability' for each agent group, or a # single (randomly chosen) index case in the passed agent group self.index_case = check_index_case(index_case, self.agent_types) self.num_agents = {} ## add agents # extract the agent nodes from the graph and add them to the scheduler for agent_type in self.agent_types: IDs = [x for x, y in G.nodes(data=True) if y['type'] == agent_type] self.num_agents.update({agent_type: len(IDs)}) # get the agent locations (units) from the graph node attributes units = [self.G.nodes[ID]['unit'] for ID in IDs] for ID, unit in zip(IDs, units): tmp_epi_params = {} # for each of the three epidemiological parameters, check if # the parameter is an integer (if yes, pass it directly to the # agent constructor), or if it is specified by the shape and # scale parameters of a Weibull distribution. In the latter # case, draw a new number for every agent from the distribution # NOTE: parameters drawn from the distribution are rounded to # the nearest integer while True: for param_name, param in self.epi_params.items(): if isinstance(param, int): tmp_epi_params[param_name] = param else: tmp_epi_params[param_name] = \ round(weibull_two_param(param[0], param[1])) if tmp_epi_params['exposure_duration'] > 0 and \ tmp_epi_params['time_until_symptoms'] >= \ tmp_epi_params['exposure_duration'] and\ tmp_epi_params['infection_duration'] > \ tmp_epi_params['exposure_duration']: break else: self.param_rerolls += 1 if verbosity > 1: print('pathological epi-param case found!') print(tmp_epi_params) # check if the agent participates in voluntary testing p = self.voluntary_testing_rates[agent_type] voluntary_testing = np.random.choice([True, False], p=[p, 1 - p]) a = self.agent_classes[agent_type]( ID, unit, self, tmp_epi_params['exposure_duration'], tmp_epi_params['time_until_symptoms'], tmp_epi_params['infection_duration'], voluntary_testing, verbosity) self.schedule.add(a) # infect the first agent in single index case mode if self.index_case != 'continuous': infection_targets = [ a for a in self.schedule.agents if a.type == index_case ] # pick a random agent to infect in the selected agent group target = self.random.randint(0, len(infection_targets) - 1) infection_targets[target].exposed = True if self.verbosity > 0: print('{} exposed: {}'.format(index_case, infection_targets[target].ID)) # list of agents that were tested positive this turn self.newly_positive_agents = [] # flag that indicates if there were new positive tests this turn self.new_positive_tests = False # dictionary of flags that indicate whether a given agent group has # been creened this turn self.screened_agents = { 'reactive': {agent_type: False for agent_type in self.agent_types}, 'follow_up': {agent_type: False for agent_type in self.agent_types}, 'preventive': {agent_type: False for agent_type in self.agent_types} } # dictionary of counters that count the days since a given agent group # was screened. Initialized differently for different index case modes if (self.index_case == 'continuous') or \ (not np.any(list(self.Testing.screening_intervals.values()))): self.days_since_last_agent_screen = { agent_type: 0 for agent_type in self.agent_types } # NOTE: if we initialize these variables with 0 in the case of a single # index case, we introduce a bias since in 'single index case mode' the # first index case will always become exposed in step 0. To realize # random states of the preventive sceening procedure with respect to the # incidence of the index case, we have to randomly pick the days since # the last screen for the agent group from which the index case is else: self.days_since_last_agent_screen = {} for agent_type in self.agent_types: if self.Testing.screening_intervals[agent_type] != None: self.days_since_last_agent_screen.update({ agent_type: self.random.choice( range( 0, self.Testing.screening_intervals[agent_type] + 1)) }) else: self.days_since_last_agent_screen.update({agent_type: 0}) # dictionary of flags that indicates whether a follow-up screen for a # given agent group is scheduled self.scheduled_follow_up_screen = { agent_type: False for agent_type in self.agent_types } # counters self.number_of_diagnostic_tests = 0 self.number_of_preventive_screening_tests = 0 self.positive_tests = { self.Testing.preventive_screening_test_type: {agent_type: 0 for agent_type in self.agent_types}, self.Testing.diagnostic_test_type: {agent_type: 0 for agent_type in self.agent_types} } self.undetected_infections = 0 self.predetected_infections = 0 self.pending_test_infections = 0 self.quarantine_counters = { agent_type: 0 for agent_type in agent_types.keys() } self.false_negative = 0 # data collectors to save population counts and agent states every # time step self.datacollector = DataCollector( model_reporters= { 'N_diagnostic_tests':get_N_diagnostic_tests, 'N_preventive_screening_tests':get_N_preventive_screening_tests, 'diagnostic_test_detected_infections_student':\ get_diagnostic_test_detected_infections_student, 'diagnostic_test_detected_infections_teacher':\ get_diagnostic_test_detected_infections_teacher, 'diagnostic_test_detected_infections_family_member':\ get_diagnostic_test_detected_infections_family_member, 'preventive_test_detected_infections_student':\ get_preventive_test_detected_infections_student, 'preventive_test_detected_infections_teacher':\ get_preventive_test_detected_infections_teacher, 'preventive_test_detected_infections_family_member':\ get_preventive_test_detected_infections_family_member, 'undetected_infections':get_undetected_infections, 'predetected_infections':get_predetected_infections, 'pending_test_infections':get_pending_test_infections }, agent_reporters= { 'infection_state': get_infection_state, 'quarantine_state': get_quarantine_state })
def __init__(self, width, height): # Model attributes initialization self.workers_number = 10 self.agents = [] self.workers = [] self.average_stress = 0 self.running = True #SOBA configuration.settings.init() configuration.defineOccupancy.init() configuration.defineMap.init() self.clock = Time() #Vars of control self.num_occupants = 0 self.day = self.clock.day self.NStep = 0 self.placeByStateByTypeAgent = {} self.agentsWorkingByStep = [] self.agentsIn = 0 # Schedule self.schedule = BaseScheduler(self) self.grid = MultiGrid(width, height, False) #Create the map self.createRooms() self.setMap(width, height) self.createDoors() self.createWalls() #Create agents self.setAgents() # Create timer agent self.timer = TimeAgent(len(self.agents), self) self.schedule.add(self.timer) self.agents.append(self.timer) # Create sensor agent self.sensor = SensorAgent(len(self.agents), self) self.schedule.add(self.sensor) self.agents.append(self.sensor) ''' # Create workers agents for i in range(self.workers_number): worker = WorkerAgent(i+len(self.agents), self) self.schedule.add(worker) self.workers.append(worker) ''' # Create data collectors self.model_collector = DataCollector( model_reporters={"Average Stress": lambda a: a.average_stress}) self.worker_collector = WorkerCollector( agent_reporters={ "Stress": lambda a: a.stress, "Event Stress": lambda a: a.event_stress, "Time Pressure": lambda a: a.time_pressure, "Effective Fatigue": lambda a: a.effective_fatigue, "Productivity": lambda a: a.productivity, 'Emails read': lambda a: a.emails_read, 'Pending tasks': lambda a: len(a.tasks), 'Overtime hours': lambda a: a.overtime_hours, 'Rest at work hours': lambda a: a.rest_at_work_hours, 'Tasks completed': lambda a: a.tasks_completed }) self.sensor_collector = SensorCollector( agent_reporters={ "Temperature": lambda a: a.wbgt, "Noise": lambda a: a.noise, "Luminosity": lambda a: a.luminosity }) self.time_collector = TimeCollector(agent_reporters={ "Day": lambda a: a.days, "Time": lambda a: a.clock })
class SOMENModel(Model): def __init__(self, width, height): # Model attributes initialization self.workers_number = 10 self.agents = [] self.workers = [] self.average_stress = 0 self.running = True #SOBA configuration.settings.init() configuration.defineOccupancy.init() configuration.defineMap.init() self.clock = Time() #Vars of control self.num_occupants = 0 self.day = self.clock.day self.NStep = 0 self.placeByStateByTypeAgent = {} self.agentsWorkingByStep = [] self.agentsIn = 0 # Schedule self.schedule = BaseScheduler(self) self.grid = MultiGrid(width, height, False) #Create the map self.createRooms() self.setMap(width, height) self.createDoors() self.createWalls() #Create agents self.setAgents() # Create timer agent self.timer = TimeAgent(len(self.agents), self) self.schedule.add(self.timer) self.agents.append(self.timer) # Create sensor agent self.sensor = SensorAgent(len(self.agents), self) self.schedule.add(self.sensor) self.agents.append(self.sensor) ''' # Create workers agents for i in range(self.workers_number): worker = WorkerAgent(i+len(self.agents), self) self.schedule.add(worker) self.workers.append(worker) ''' # Create data collectors self.model_collector = DataCollector( model_reporters={"Average Stress": lambda a: a.average_stress}) self.worker_collector = WorkerCollector( agent_reporters={ "Stress": lambda a: a.stress, "Event Stress": lambda a: a.event_stress, "Time Pressure": lambda a: a.time_pressure, "Effective Fatigue": lambda a: a.effective_fatigue, "Productivity": lambda a: a.productivity, 'Emails read': lambda a: a.emails_read, 'Pending tasks': lambda a: len(a.tasks), 'Overtime hours': lambda a: a.overtime_hours, 'Rest at work hours': lambda a: a.rest_at_work_hours, 'Tasks completed': lambda a: a.tasks_completed }) self.sensor_collector = SensorCollector( agent_reporters={ "Temperature": lambda a: a.wbgt, "Noise": lambda a: a.noise, "Luminosity": lambda a: a.luminosity }) self.time_collector = TimeCollector(agent_reporters={ "Day": lambda a: a.days, "Time": lambda a: a.clock }) #SOBA def setAgents(self): self.lights = [] id_light = 0 for room in self.rooms: if room.typeRoom != 'out' and room.light == False: light = Light(id_light, self, room) self.lights.append(light) id_light = id_light + 1 room.light = light for room2 in self.rooms: if room.name.split(r".")[0] == room2.name.split(r".")[0]: room2.light = light # Height and Width height = self.grid.height width = self.grid.width # CREATE AGENTS self.agents = [] # Create occupants for n_type_occupants in configuration.defineOccupancy.occupancy_json: self.placeByStateByTypeAgent[ n_type_occupants['type']] = n_type_occupants['states'] n_agents = n_type_occupants['N'] for i in range(0, n_agents): a = WorkerAgent(i + len(self.agents) + 1000, self, n_type_occupants) self.workers.append(a) self.schedule.add(a) self.grid.place_agent(a, self.outBuilding.pos) self.pushAgentRoom(a, self.outBuilding.pos) self.num_occupants = self.num_occupants + 1 self.schedule.add(self.clock) for light in self.lights: self.schedule.add(light) def isConected(self, pos): nextRoom = False for room in self.rooms: if room.pos == pos: nextRoom = room if nextRoom == False: return False for x in range(0, width): for y in range(0, height): self.pos_out_of_map.append(x, y) for room in self.rooms: self.pos_out_of_map.remove(room.pos) def createRooms(self): rooms = configuration.defineMap.rooms_json self.rooms = [] for room in rooms: newRoom = 0 name = room['name'] typeRoom = room['type'] if typeRoom != 'out': conectedTo = room.get('conectedTo') entrance = room.get('entrance') measures = room['measures'] dx = measures['dx'] dy = measures['dy'] newRoom = Room(name, typeRoom, conectedTo, dx, dy) newRoom.entrance = entrance else: newRoom = Room( name, typeRoom, None, 0, 0, ) self.outBuilding = newRoom self.rooms.append(newRoom) for room1 in self.rooms: if room1.conectedTo is not None: for otherRooms in list(room1.conectedTo.values()): for room2 in self.rooms: if room2.name == otherRooms: room1.roomsConected.append(room2) room2.roomsConected.append(room1) for room in self.rooms: room.roomsConected = list(set(room.roomsConected)) sameRoom = {} for room in self.rooms: if sameRoom.get(room.name.split(r".")[0]) is None: sameRoom[room.name.split(r".")[0]] = 1 else: sameRoom[room.name.split(r".") [0]] = sameRoom[room.name.split(r".")[0]] + 1 def setMap(self, width, height): rooms_noPos = self.rooms rooms_using = [] rooms_used = [] for room in self.rooms: if room.entrance is not None: room.pos = (int(1), 1) rooms_using.append(room) rooms_used.append(room) rooms_noPos.remove(room) break while len(rooms_noPos) > 0: for roomC in rooms_using: xc, yc = roomC.pos rooms_conected = roomC.conectedTo rooms_using.remove(roomC) if rooms_conected is not None: orientations = list(rooms_conected.keys()) for orientation in orientations: if orientation == 'R': for room in rooms_noPos: if room.name == rooms_conected['R']: room.pos = (int(xc + 1), yc) rooms_noPos.remove(room) rooms_used.append(room) rooms_using.append(room) elif orientation == 'U': for room in rooms_noPos: if room.name == rooms_conected['U']: room.pos = (xc, int(yc + 1)) rooms_noPos.remove(room) rooms_used.append(room) rooms_using.append(room) elif orientation == 'D': for room in rooms_noPos: if room.name == rooms_conected['D']: room.pos = (xc, int(yc - 1)) rooms_noPos.remove(room) rooms_used.append(room) rooms_using.append(room) elif orientation == 'L': for room in rooms_noPos: if room.name == rooms_conected['L']: room.pos = (int(xc - 1), yc) rooms_noPos.remove(room) rooms_used.append(room) rooms_using.append(room) else: pass self.rooms = rooms_used def createDoors(self): self.doors = [] for roomC in self.rooms: roomsConected = roomC.roomsConected for room in roomsConected: door_created = False same_corridor = False if room.name != roomC.name: for door in self.doors: if (door.room1.name == roomC.name and door.room2.name == room.name) or ( door.room2.name == roomC.name and door.room1.name == room.name): door_created = True if room.name.split(r".")[0] == roomC.name.split( r".")[0]: same_corridor = True if door_created == False and same_corridor == False: d = Door(roomC, room) self.doors.append(d) room.doors.append(d) roomC.doors.append(d) def createWalls(self): for room in self.rooms: if room.typeRoom != 'out': walls = [] xr, yr = room.pos roomA = self.getRoom((xr, yr + 1)) if roomA != False: if roomA.name.split(r".")[0] == room.name.split(r".")[0]: pass else: wall = Wall(room, roomA) walls.append(wall) else: wall = Wall(room) walls.append(wall) roomB = self.getRoom((xr, yr - 1)) if roomB != False: if roomB.name.split(r".")[0] == room.name.split(r".")[0]: pass else: wall = Wall(room, roomB) walls.append(wall) else: wall = Wall(room) walls.append(wall) roomC = self.getRoom((xr + 1, yr)) if roomC != False: if roomC.name.split(r".")[0] == room.name.split(r".")[0]: pass else: wall = Wall(room, roomC) walls.append(wall) else: wall = Wall(room) walls.append(wall) roomD = self.getRoom((xr - 1, yr)) if roomD != False: if roomD.name.split(r".")[0] == room.name.split(r".")[0]: pass else: wall = Wall(room, roomD) walls.append(wall) else: wall = Wall(room) walls.append(wall) room.walls = walls def getPosState(self, name, typeA): placeByStateByTypeAgent = self.placeByStateByTypeAgent n = 0 for state in self.placeByStateByTypeAgent[typeA]: if state.get('name') == name: pos1 = state.get('position') if isinstance(pos1, dict): for k, v in pos1.items(): if v > 0: placeByStateByTypeAgent[typeA][n]['position'][ k] = v - 1 self.placeByStateByTypeAgent = placeByStateByTypeAgent return k return list(pos1.keys())[-1] else: return pos1 n = n + 1 def thereIsClosedDoor(self, beforePos, nextPos): oldRoom = False newRoom = False for room in rooms: if room.pos == beforePos: oldRoom = room if room.pos == nextPos: newRoom = room for door in self.doors: if (door.room1.name == oldRoom.name and door.room2.name == newRoom.name) or (door.room2.name == oldRoom.name and door.room1.name == newRoom.name): if door.state == False: return True return False def thereIsOccupant(self, pos): possible_occupant = self.grid.get_cell_list_contents([pos]) if (len(possible_occupant) > 0): for occupant in possible_occupant: if isinstance(occupant, WorkerAgent): return True return False def ThereIsOtherOccupantInRoom(self, room, agent): for roomAux in self.rooms: possible_occupant = [] if roomAux.name.split(r".")[0] == room.name.split(r".")[0]: possible_occupant = self.grid.get_cell_list_contents( roomAux.pos) for occupant in possible_occupant: if isinstance(occupant, WorkerAgent) and occupant != agent: return True return False def ThereIsSomeOccupantInRoom(self, room): for roomAux in self.rooms: possible_occupant = [] if roomAux.name.split(r".")[0] == room.name.split(r".")[0]: possible_occupant = self.grid.get_cell_list_contents( roomAux.pos) for occupant in possible_occupant: if isinstance(occupant, WorkerAgent): return True return False def thereIsOccupantInRoom(self, room, agent): for roomAux in self.rooms: possible_occupant = [] if roomAux.name.split(r".")[0] == room.name.split(r".")[0]: possible_occupant = self.grid.get_cell_list_contents( roomAux.pos) for occupant in possible_occupant: if isinstance(occupant, WorkerAgent) and occupant == agent: return True return False def getRoom(self, pos): for room in self.rooms: if room.pos == pos: return room return False def pushAgentRoom(self, agent, pos): room = self.getRoom(pos) room.agentsInRoom.append(agent) def popAgentRoom(self, agent, pos): room = self.getRoom(pos) room.agentsInRoom.remove(agent) def openDoor(self, agent, room1, room2): for door in self.doors: if ((door.room1 == room1 and door.room2 == room2) or (door.room1 == room2 and door.room2 == room1)): door.state = False def closeDoor(self, agent, room1, room2): numb = random.randint(0, 10) for door in self.doors: if ((door.room1 == room1 and door.room2 == room2) or (door.room1 == room2 and door.room2 == room1)): if 7 >= numb: door.state = False else: door.state = True def getMatrix(self, agent): new_matrix = configuration.defineOccupancy.returnMatrix( agent, self.clock.clock) agent.markov_matrix = new_matrix def getTimeInState(self, agent): matrix_time_in_state = configuration.defineOccupancy.getTimeInState( agent, self.clock.clock) return matrix_time_in_state def sobaStep(self): aw = 0 for agent in self.agents: if agent.state == 'working in my workplace': aw = aw + 1 self.agentsWorkingByStep.append(aw) self.schedule.step() if (self.clock.day > self.day): self.day = self.day + 1 self.NStep = self.NStep + 1 if self.clock.clock > 17: model.ramenScript.generateJSON() while (True): pass def step(self): self.sobaStep() if self.timer.new_day: self.addTasks() self.createEmailsDistribution() self.average_stress = sum( worker.stress for worker in self.workers) / len(self.workers) if self.timer.new_hour: self.worker_collector.collect(self) self.sensor_collector.collect(self) self.time_collector.collect(self) self.model_collector.collect(self) def addTasks(self): ''' Add tasks to workers ''' # Get task distribution params mu, sigma = workload_settings.tasks_arriving_distribution_params tasks_arriving_distribution = np.random.normal( mu, sigma, self.workers_number * 10) for worker in self.workers: # Get number of tasks to add tasks_number = math.floor( abs(tasks_arriving_distribution[random.randint( 0, 10 * self.workers_number - 1)])) worker.tasks_completed = 0 # Add tasks for i in range(tasks_number): worker.addTask(Task()) worker.calculateAverageDailyTasks(self.timer.days) worker.calculateEventStress(tasks_number) # worker.printTasksNumber() # worker.printAverageDailyTasks() # worker.printEventStress() def createEmailsDistribution(self): '''Create emails distribution''' # Get emails distribution mu, sigma = email_settings.emails_read_distribution_params emails_read_distribution = np.random.normal(mu, sigma, self.workers_number * 10) for worker in self.workers: emails_received = math.floor( abs(emails_read_distribution[random.randint( 0, 10 * self.workers_number - 1)])) emails_distribution_over_time = np.random.choice( [0, 1], size=(480, ), p=[(480 - emails_received) / 480, emails_received / 480]) worker.emails_read = 0 worker.email_read_distribution_over_time = emails_distribution_over_time
def __init__(self, N, width, height): self.num_agents = N self.width = width self.height = height self.grid = MultiGrid(height, width, False) #non toroidal grid self.schedule = SimultaneousActivation(self) self.datacollector = DataCollector( model_reporters={"Coverage": compute_coverage}, agent_reporters={"Wealth": "wealth"}) # Create agents self.coveredArea = [] self.interactionCount = 0 self.interactionRateAverage = 0 self.coveragePercentage = 0 self.coveragePercentageAverage = 0 #for ordered walk self.orientation = np.ones((self.height, self.width)) self.orientation[:1] = np.full((1, self.width), 2) self.orientation[:self.height, self.width - 1:] = np.full( (self.height, 1), 3) self.orientation[self.height - 1:, ] = np.full((1, self.width), 4) self.orientation[:self.height, :1] = np.full((self.height, 1), 1) self.orientation[0][0] = 2 # distribute the agents evently areaNum = ceil(sqrt(self.num_agents)) areaDistx = self.width / (sqrt(self.num_agents)) areaDistx = floor(areaDistx) areaDisty = self.height / (sqrt(self.num_agents)) areaDisty = floor(areaDisty) self.dtx = areaDistx self.dty = areaDisty for i in range(self.num_agents): xlow = (i % areaNum) * areaDistx xup = xlow + areaDistx - 1 ylow = floor(i / areaNum) * areaDisty yup = ylow + areaDisty - 1 x = floor((xlow + xup) / 2) + 1 y = floor((ylow + yup) / 2) + 1 xlow = x - 1 xup = x + 1 ylow = y - 1 yup = y + 1 # create and add agent with id number i to the scheduler a = MoniAgent(i, self, xup, xlow, yup, ylow) self.schedule.add(a) #place agent at the center of its limit coor self.grid.place_agent(a, (x, y)) # Add the agent to a random grid cell # this part is for visualization only self.running = True self.datacollector.collect(self)
def __init__(self, background_opinion=Opinion.NO, seeded_opinion=Opinion.YES, num_people=50, num_subcultures=10, avg_node_degree=5, initial_seed_size=3, opinion_change_chance=0.4, opinion_check_frequency=0.4): self.num_people = num_people self.num_subcultures = num_subcultures total_degrees = avg_node_degree * self.num_people subculture_degrees = [] for i in range(self.num_subcultures): degrees_left = total_degrees - sum(subculture_degrees) subcultures_left = self.num_subcultures - i subculture_degrees.append( random.randrange(1, degrees_left - subcultures_left ) if subcultures_left > 1 else degrees_left) self.G = bipartite.configuration_model( [avg_node_degree] * self.num_people, subculture_degrees) people, subcultures = bipartite.sets(self.G) self.grid = NetworkGrid(self.G) self.schedule = RandomActivation(self) self.background_opinion = next( (opinion for opinion in Opinion if opinion.name == background_opinion), None) self.seeded_opinion = next( (opinion for opinion in Opinion if opinion.name == seeded_opinion), None) self.initial_seed_size = min(initial_seed_size, self.num_people + self.num_subcultures) self.opinion_change_chance = opinion_change_chance self.opinion_check_frequency = opinion_check_frequency self.datacollector = DataCollector({ "Neutral": lambda model: opinion_count(model, Opinion.NEUTRAL), "Yes": lambda model: opinion_count(model, Opinion.YES), "No": lambda model: opinion_count(model, Opinion.NO) }) # Create People for i, node in enumerate(people): p = Person(i, self, self.background_opinion, self.opinion_change_chance, self.opinion_check_frequency) self.schedule.add(p) # Add the Person to the node self.grid.place_agent(p, node) # Create SubCultures for i, node in enumerate(subcultures): s = SubCulture(i, self) self.schedule.add(s) # Add the SubCulture to the node self.grid.place_agent(s, node) # Seed some opinions seed = self.random.sample(subcultures, 1)[0] seed_network = nx.algorithms.traversal.depth_first_search.dfs_preorder_nodes( self.G, seed) for node in islice(seed_network, initial_seed_size): person = self.G.nodes[node]['agent'][0] person.opinion = self.seeded_opinion self.running = True self.datacollector.collect(self)
class MoniModel(Model): """A simple model of an economy where agents exchange currency at random. All the agents begin with one unit of currency, and each time step can give a unit of currency to another agent. Note how, over time, this produces a highly skewed distribution of wealth. """ def __init__(self, N, width, height): self.num_agents = N self.width = width self.height = height self.grid = MultiGrid(height, width, False) #non toroidal grid self.schedule = SimultaneousActivation(self) self.datacollector = DataCollector( model_reporters={"Coverage": compute_coverage}, agent_reporters={"Wealth": "wealth"}) # Create agents self.coveredArea = [] self.interactionCount = 0 self.interactionRateAverage = 0 self.coveragePercentage = 0 self.coveragePercentageAverage = 0 #for ordered walk self.orientation = np.ones((self.height, self.width)) self.orientation[:1] = np.full((1, self.width), 2) self.orientation[:self.height, self.width - 1:] = np.full( (self.height, 1), 3) self.orientation[self.height - 1:, ] = np.full((1, self.width), 4) self.orientation[:self.height, :1] = np.full((self.height, 1), 1) self.orientation[0][0] = 2 # distribute the agents evently areaNum = ceil(sqrt(self.num_agents)) areaDistx = self.width / (sqrt(self.num_agents)) areaDistx = floor(areaDistx) areaDisty = self.height / (sqrt(self.num_agents)) areaDisty = floor(areaDisty) self.dtx = areaDistx self.dty = areaDisty for i in range(self.num_agents): xlow = (i % areaNum) * areaDistx xup = xlow + areaDistx - 1 ylow = floor(i / areaNum) * areaDisty yup = ylow + areaDisty - 1 x = floor((xlow + xup) / 2) + 1 y = floor((ylow + yup) / 2) + 1 xlow = x - 1 xup = x + 1 ylow = y - 1 yup = y + 1 # create and add agent with id number i to the scheduler a = MoniAgent(i, self, xup, xlow, yup, ylow) self.schedule.add(a) #place agent at the center of its limit coor self.grid.place_agent(a, (x, y)) # Add the agent to a random grid cell # this part is for visualization only self.running = True self.datacollector.collect(self) def step(self): self.interactionCount = 0 self.schedule.step() # collect data self.datacollector.collect(self) with open('reportRandomStride.csv', 'a') as reportFile: coverage = compute_coverage(self) percentage = ceil( 10000 * coverage / self.width / self.height) / 100 interactionRate = self.interactionCount / self.num_agents / ( self.num_agents - 1) #number of interaction/possible interaction /2 for double counting interactionRate = ceil(10000 * interactionRate) / 100 self.interactionRateAverage = ( self.interactionRateAverage * (self.schedule.steps - 1) + interactionRate) / self.schedule.steps self.interactionRateAverage = ceil( 100 * self.interactionRateAverage) / 100 self.coveragePercentageAverage = (self.coveragePercentageAverage * (self.schedule.steps - 1) + percentage) / self.schedule.steps self.coveragePercentageAverage = ceil( 100 * self.coveragePercentageAverage) / 100 rewriter = csv.writer(reportFile, delimiter=' ', quotechar='"', quoting=csv.QUOTE_MINIMAL) rewriter.writerow([ "step:", self.schedule.steps, "InteractionRate:", interactionRate, '%', "AvgInteractionRate", self.interactionRateAverage, '%', "Coverage:", coverage, percentage, '%', self.coveragePercentageAverage, '%' ]) # def initPosTest(self) def run_model(self, n): for i in range(n): # self.initPos() self.step()
class Home(Model): """ A Home Violence Simulation Model """ verbose = True # Print-monitoring description = 'A model for simulating the victim aggressor interaction mediated by presence of violence.' def __init__(self, height=40, width=40, initial_families=1000, metro='BRASILIA', gender_stress=.8, under_influence=.1, has_gun=.1, is_working_pct=.8, chance_changing_working_status=.05, pct_change_wage=.05, model_scale=1000, quarantine=False, dissuasion=True, data_year=2010): """ A violence violence model of Brazilian metropolis """ super().__init__() # Set parameters self.height = height self.width = width self.initial_families = initial_families self.metro = metro self.gender_stress = gender_stress self.under_influence = under_influence self.has_gun = has_gun self.is_working_pct = is_working_pct self.chance_changing_working_status = chance_changing_working_status self.pct_change_wage = pct_change_wage self.model_scale = model_scale self.quarantine = quarantine self.dissuasion = dissuasion self.data_year = data_year self.neighborhood_stress = dict() self.schedule = RandomActivationByBreed(self) self.grid = MultiGrid(self.height, self.width, torus=True) # Model reporters. How they work: lambda function passes model and other parameters. # Then another function may iterate over each 'agent in model.schedule.agents' # Then server is going to collect info using the keys in model_reporters dictionary model_reporters = { "Denounce": lambda m: self.count_type_citizens(m, 'denounce'), "Got attacked": lambda m: self.count_type_citizens(m, 'got_attacked'), "Females": lambda m: self.count_type_citizens(m, 'female'), "Stress": lambda m: self.count_stress(m)} self.datacollector = DataCollector(model_reporters=model_reporters) # Create people --------------------------------------------------- # Parameters to choose metropolitan region # More details available at input/population params = dict() params['PROCESSING_ACPS'] = [self.metro] params['MEMBERS_PER_FAMILY'] = 2.5 params['INITIAL_FAMILIES'] = self.initial_families params['DATA_YEAR'] = self.data_year people, families = generator.main(params=params) # General random data for each individual n = sum([len(f) for f in families]) working = np.random.choice([True, False], n, p=[self.is_working_pct, 1 - self.is_working_pct]) influence = np.random.choice([True, False], n, p=[self.under_influence, 1 - self.under_influence]) gun = np.random.choice([True, False], n, p=[self.has_gun, 1 - self.has_gun]) i = 0 for fam in families: # 1. Create a family. x = self.random.randrange(self.width) y = self.random.randrange(self.height) # Create a family family = Family(self.next_id(), self, (x, y)) # Add family to grid self.grid.place_agent(family, (x, y)) self.schedule.add(family) # Marriage procedures engaged = ['male_adult', 'female_adult'] to_marry = list() # Add people to families for each in fam: person = people.loc[each] doe = Person(self.next_id(), self, (x, y), # From IBGE's sampling gender=person.gender, age=person.age, color=person.cor, address=person.AREAP, years_study=person.years_study, reserve_wage=person.wage, # End of sampling is_working=working[i], under_influence=influence[i], has_gun=gun[i]) # Marrying if len(engaged) > 0: if person.category in engaged: engaged.remove(person.category) to_marry.append(doe) # Change position in the random list previously sampled i += 1 self.grid.place_agent(doe, (x, y)) self.schedule.add(doe) family.add_agent(doe) # 2. Marry the couple if len(to_marry) == 2: to_marry[0].assign_spouse(to_marry[1]) # 3. Update number of family members for member in family.members.values(): member.num_members_family = len(family.members) self.running = True self.datacollector.collect(self) def step(self): self.update_neighborhood_stress() self.schedule.step() # collect data self.datacollector.collect(self) # Check if step will happen at individual levels or at family levels or BOTH if self.verbose: print([self.schedule.time, self.schedule.get_breed_count(Person)]) # New condition to stop the model if self.schedule.get_breed_count(Person) == 0: self.running = False def update_neighborhood_stress(self): # Start from scratch every step self.neighborhood_stress = dict() counter = dict() for agent in self.schedule.agents: if isinstance(agent, Family): self.neighborhood_stress[agent.address] = self.neighborhood_stress.get(agent.address, 0) + \ agent.context_stress counter[agent.address] = counter.get(agent.address, 0) + 1 for key in self.neighborhood_stress.keys(): self.neighborhood_stress[key] = self.neighborhood_stress[key] / counter[key] # print(f'This neighborhood {key} stress levels is at {self.neighborhood_stress[key]}') @staticmethod def count_type_citizens(model, condition): """ Helper method to count agents by Type. """ count = 0 for agent in model.schedule.agents: if isinstance(agent, Person): if 'denounce' == condition: count += agent.denounce elif 'got_attacked' == condition: count += agent.got_attacked elif 'female' == condition: if agent.gender == 'female': if agent.age > 18: count += 1 return count @staticmethod def count_stress(model): count, size = 0, 0 for agent in model.schedule.agents: if isinstance(agent, Family): count += agent.context_stress size += 1 return count / size def run_model(self, step_count=200): if self.verbose: print('Initial number of people: ', self.schedule.get_breed_count(Person)) # Steps are not being set here, but on superclass. Changes should be made in the step function above! for i in range(step_count): self.step() if self.verbose: print('') print('Final number People: ', self.schedule.get_breed_count(Person))
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.uid = next(self.id_gen) 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, "Model Params": track_params, "Run": track_run }, agent_reporters={ "name": lambda x: x.name, "sex": lambda x: x.S, "attractiveness": lambda x: x.A, "relationship": lambda x: x.R, "Model Params": lambda x: track_params(x.model), "Run": lambda x: track_run(x.model) }) # 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)
class CDAmodel(Model): """Continuous Double Auction model with some number of agents.""" def __init__(self, supply, demand, s_strategy, b_strategy, highest_ask=100, lowest_ask=0): self.supply = supply self.demand = demand self.num_sellers = supply.num_agents self.num_buyers = demand.num_agents self.initialize_spread() self.market_price = None self.history = Order_history() self.num_traded = 0 self.num_step = 0 # history records an order as a bid or ask only if it updates # the spread self.num_period = 1 self.loc_period = [0] # where each period happens # Sometimes trade does not happen within a period, # so we need variables to indicate them. self.no_trade = False # When a shift happens, I need to know it so that # I can calculate efficiency properly. self.shifted = False self.shifted_period = -1 self.new_supply = None self.new_demand = None # How agents are activated at each step self.schedule = RandomChoiceActivation(self) # Create agents for i, cost in enumerate(self.supply.price_schedule): self.schedule.add( b_strategy(i, self, "seller", cost, supply.q_per_agent, highest_ask, lowest_ask)) for i, value in enumerate(self.demand.price_schedule): j = self.num_sellers + i self.schedule.add( s_strategy(j, self, "buyer", value, demand.q_per_agent, highest_ask, lowest_ask)) # Collecting data # self.datacollector = DataCollector( # model_reporters={"Period": "num_period", # "OB": "outstanding_bid", # "OBer": get_bidder_id, # "OA": "outstanding_ask", # "OAer": get_asker_id, # "MarketPrice": "market_price", # "Traded": "traded", # "Order": lambda x: x.history.get_last_action(), # "Efficiency": compute_efficiency}, # agent_reporters={"Period": lambda x: x.model.num_period, # "Type": lambda x: type(x), # "Role": "role", # "Value": "value", # "Good": "good", # "Right": "right", # "Surplus": "surplus"} # ) self.datacollector = DataCollector(model_reporters={ "Step": "num_step", "Period": "num_period", "TransNum": "num_traded", "OB": "outstanding_bid", "OA": "outstanding_ask", "MarketPrice": "market_price", "Traded": "traded", "CumulativeActualSurplus": compute_actual_surplus, "TheoreticalSurplus": compute_theoretical_surplus, "CumulativeTS": compute_acc_theoretical_surplus, "Efficiency": compute_efficiency }, agent_reporters={ "Period": lambda x: x.model.num_period, "Type": lambda x: type(x), "Role": "role", "Value": "value", "Good": "good", "Right": "right", "Surplus": "surplus" }) def initialize_spread(self): # Initialize outstanding bid and ask self.outstanding_bid = 0 self.outstanding_bidder = None self.outstanding_ask = math.inf self.outstanding_asker = None self.traded = 0 self.market_price = None def update_ob(self, bidder, price): if price > self.outstanding_bid: # Update the outstanding bid self.outstanding_bid = price self.outstanding_bidder = bidder if price > self.outstanding_ask: # a transaction happens contract_price = self.outstanding_ask self.execute_contract(contract_price) self.history.accept_bid(bidder, self.outstanding_asker, contract_price) else: self.history.submit_bid(bidder, price) else: # null order self.history.submit_null(bidder, None, price) def update_oa(self, asker, price): if price < self.outstanding_ask: # Update the outstanding ask self.outstanding_ask = price self.outstanding_asker = asker if price < self.outstanding_bid: contract_price = self.outstanding_bid self.execute_contract(contract_price) self.history.accept_ask(self.outstanding_bidder, asker, contract_price) else: # only updated the outstanding ask self.history.submit_ask(asker, price) else: # null order self.history.submit_null(None, asker, price) def execute_contract(self, contract_price): self.outstanding_bidder.buy(contract_price) self.outstanding_asker.sell(contract_price) self.market_price = contract_price self.traded = 1 self.num_traded += 1 def next_period(self, new_supply=None, new_demand=None): if self.num_traded == 0: self.no_trade = True if new_supply and new_demand: self.new_supply = new_supply self.new_demand = new_demand self.shifted = True self.shifted_period = self.num_period + 1 if self.shifted: supply = self.new_supply demand = self.new_demand else: supply = self.supply demand = self.demand # Making sure the schedule is ordered as it was initialized. self.schedule.agents.sort(key=lambda x: x.unique_id) for i, cost in enumerate(supply.price_schedule): self.schedule.agents[i].good = supply.q_per_agent self.schedule.agents[i].value = cost self.schedule.agents[i].active = True for i, value in enumerate(demand.price_schedule): j = self.num_sellers + i self.schedule.agents[j].right = demand.q_per_agent self.schedule.agents[j].value = value self.schedule.agents[j].active = True self.num_period += 1 self.num_traded = 0 self.loc_period.append(self.num_step) self.history.next_period() def step(self): if self.traded == 1: self.initialize_spread() # print("step:", self.num_step) self.schedule.step() self.num_step += 1 self.datacollector.collect(self) def plot_model(self): data = self.datacollector.get_model_vars_dataframe() data = data[data.Traded == 1] f = plt.figure(1) ax = f.add_subplot(111) plt.plot(data.MarketPrice) plt.axhline(y=self.supply.equilibrium_price, color='black', linestyle='dashed') plt.text(0.8, 0.9, round(compute_efficiency(self), 3), fontsize=20, transform=ax.transAxes) for i in range(self.num_period): plt.axvline(x=self.loc_period[i], color='black', linestyle='dashed') plt.show()
class KalickHamilton(Model): """A model following Andre Grow's Netlogo tutorial of Kalick Hamilton 1986 replicated using Mesa""" # id generator to track run number in batch run data id_gen = itertools.count(1) 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.uid = next(self.id_gen) 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, "Model Params": track_params, "Run": track_run }, agent_reporters={ "name": lambda x: x.name, "sex": lambda x: x.S, "attractiveness": lambda x: x.A, "relationship": lambda x: x.R, "Model Params": lambda x: track_params(x.model), "Run": lambda x: track_run(x.model) }) # 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 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 = 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()
def __init__(self, height=40, width=40, initial_families=1000, metro='BRASILIA', gender_stress=.8, under_influence=.1, has_gun=.1, is_working_pct=.8, chance_changing_working_status=.05, pct_change_wage=.05, model_scale=1000, quarantine=False, dissuasion=True, data_year=2010): """ A violence violence model of Brazilian metropolis """ super().__init__() # Set parameters self.height = height self.width = width self.initial_families = initial_families self.metro = metro self.gender_stress = gender_stress self.under_influence = under_influence self.has_gun = has_gun self.is_working_pct = is_working_pct self.chance_changing_working_status = chance_changing_working_status self.pct_change_wage = pct_change_wage self.model_scale = model_scale self.quarantine = quarantine self.dissuasion = dissuasion self.data_year = data_year self.neighborhood_stress = dict() self.schedule = RandomActivationByBreed(self) self.grid = MultiGrid(self.height, self.width, torus=True) # Model reporters. How they work: lambda function passes model and other parameters. # Then another function may iterate over each 'agent in model.schedule.agents' # Then server is going to collect info using the keys in model_reporters dictionary model_reporters = { "Denounce": lambda m: self.count_type_citizens(m, 'denounce'), "Got attacked": lambda m: self.count_type_citizens(m, 'got_attacked'), "Females": lambda m: self.count_type_citizens(m, 'female'), "Stress": lambda m: self.count_stress(m)} self.datacollector = DataCollector(model_reporters=model_reporters) # Create people --------------------------------------------------- # Parameters to choose metropolitan region # More details available at input/population params = dict() params['PROCESSING_ACPS'] = [self.metro] params['MEMBERS_PER_FAMILY'] = 2.5 params['INITIAL_FAMILIES'] = self.initial_families params['DATA_YEAR'] = self.data_year people, families = generator.main(params=params) # General random data for each individual n = sum([len(f) for f in families]) working = np.random.choice([True, False], n, p=[self.is_working_pct, 1 - self.is_working_pct]) influence = np.random.choice([True, False], n, p=[self.under_influence, 1 - self.under_influence]) gun = np.random.choice([True, False], n, p=[self.has_gun, 1 - self.has_gun]) i = 0 for fam in families: # 1. Create a family. x = self.random.randrange(self.width) y = self.random.randrange(self.height) # Create a family family = Family(self.next_id(), self, (x, y)) # Add family to grid self.grid.place_agent(family, (x, y)) self.schedule.add(family) # Marriage procedures engaged = ['male_adult', 'female_adult'] to_marry = list() # Add people to families for each in fam: person = people.loc[each] doe = Person(self.next_id(), self, (x, y), # From IBGE's sampling gender=person.gender, age=person.age, color=person.cor, address=person.AREAP, years_study=person.years_study, reserve_wage=person.wage, # End of sampling is_working=working[i], under_influence=influence[i], has_gun=gun[i]) # Marrying if len(engaged) > 0: if person.category in engaged: engaged.remove(person.category) to_marry.append(doe) # Change position in the random list previously sampled i += 1 self.grid.place_agent(doe, (x, y)) self.schedule.add(doe) family.add_agent(doe) # 2. Marry the couple if len(to_marry) == 2: to_marry[0].assign_spouse(to_marry[1]) # 3. Update number of family members for member in family.members.values(): member.num_members_family = len(family.members) self.running = True self.datacollector.collect(self)
def __init__(self, monitored_statistic, show_schools, show_restaurants): """ Create a new InfectedModel """ self.schedule = BaseScheduler(self) self.grid = GeoSpace() self.steps = 0 self.counts = None self.reset_counts() self.monitored_statistic = 'infected-per-home-series' self.show_schools = show_schools self.show_restaurants = show_restaurants self.maximum = {self.monitored_statistic: 0} self.minimum = {self.monitored_statistic: sys.maxsize} with open(LOG_FILE) as json_file: self.simulation_data = json.load(json_file) #for key in self.simulation_data[self.monitored_statistic]: # for v in self.simulation_data[self.monitored_statistic][key]: # if v > self.maximum[self.monitored_statistic]: self.maximum[self.monitored_statistic] = v # if v < self.minimum[self.monitored_statistic]: self.minimum[self.monitored_statistic] = v self.minimum['infected-per-home-series'] = 0 self.maximum['infected-per-home-series'] = 500 self.running = True self.datacollector = DataCollector({ "infected": get_infected_count, "susceptible": get_susceptible_count, "recovered": get_recovered_count, "dead": get_dead_count, }) # Neighboorhoods AC = AgentCreator(NeighbourhoodAgent, {"model": self}) neighbourhood_agents = AC.from_file(geojson_neighborhoods, unique_id=self.unique_id) for agent in neighbourhood_agents: for neighborhood in NEIGHBORHOODS['features']: if agent.unique_id == neighborhood['properties']['NAME']: agent2feature[agent.unique_id] = neighborhood break self.grid.add_agents(neighbourhood_agents) # Schools AC = AgentCreator(SchoolAgent, {"model": self}) school_agents = AC.from_file(geojson_schools, unique_id=self.unique_id) for agent in school_agents: for school in SCHOOLS['features']: if agent.unique_id == school['properties']['NAME']: agent2feature[agent.unique_id] = school break self.grid.add_agents(school_agents) # Restaurants AC = AgentCreator(RestaurantAgent, {"model": self}) restaurant_agents = AC.from_file(geojson_restaurants, unique_id=self.unique_id) for agent in restaurant_agents: for restaurant in RESTAURANTS['features']: if agent.unique_id == restaurant['properties']['NAME']: agent2feature[agent.unique_id] = restaurant break self.grid.add_agents(restaurant_agents) for agent in neighbourhood_agents + school_agents: self.schedule.add(agent) self.datacollector.collect(self)
class ClimateMigrationModel(Model): def __init__( self, num_counties, preferences, network_type, \ climate_threshold, limited_radius=True, init_time=0): super().__init__() global TICK TICK = init_time self.num_agents = 0 self.agent_index = 0 self.preferences = preferences self.limited_radius = limited_radius self.upper_network_size = 3 self.network_type = network_type self.climate_threshold = climate_threshold self.schedule = SimultaneousActivation(self) self.G = create_graph() self.num_counties = num_counties self.nodes = self.G.nodes() self.grid = NetworkGrid(self.G) self.county_climate_ranking = [] self.county_population_list = [0] * self.num_counties self.county_flux = [0] * self.num_counties self.deaths = [] self.births = [] self.county_income = {} self.datacollector = DataCollector(model_reporters={"County Population": lambda m1: list(m1.county_population_list), "County Influx": lambda m2: list(m2.county_flux), "Deaths": lambda m3: m3.deaths, "Births": lambda m4: m4.births, "Total Population": lambda m5: m5.num_agents}) def add_agents(self): """ Adds agents based on 2013 ACS population data. """ cumulative_population_list = get_cumulative_population_list() self.county_population_list = get_population_list() county = 0 # keeps track of which county each agent should be placed in index = 0 # keeps track of each agent's unique_id # keep creating agents until county population is reached while index < cumulative_population_list[county]: # create agent agent = Household(index, self) # place agent in appropriate county self.grid.place_agent(agent, list(self.nodes)[county]) # add agent to schedule self.schedule.add(agent) # set agent's original_pos attribute agent.original_pos = agent.pos # initialize all other agent attributes agent.initialize_agent() # if running model with heterogeneous preferences, set agent preference if self.preferences: agent.initialize_preference() # update index index += 1 # if done with county and not at last county, increase county if index == cumulative_population_list[county] and county < self.num_counties - 1: county += 1 # after all agents are added, set model attributes self.num_agents = cumulative_population_list[self.num_counties-1] self.agent_index = cumulative_population_list[self.num_counties-1] def initialize_all_random_networks(self): """ Initializes random networks for all agents in model. """ for a in self.schedule.agents: a.initialize_random_network() def initialize_all_income_networks(self): """ Initializes income-based networks for all agents in model. """ for a in self.schedule.agents: a.initialize_income_network() def initialize_all_age_networks(self): """ Initializes age-based networks for all agents in model. """ for a in self.schedule.agents: a.initialize_age_network() def initialize_all_income_age_networks(self): """ Initializes income and age-based networks for all agents in model. """ for a in self.schedule.agents: a.initialize_income_age_network() def initialize_all_families(self): """ Initializes families for all agents in model. """ for a in self.schedule.agents: a.initialize_family() def update_population(self): """ Updates population by adding and removing agents. """ # keep track of number of deaths and births per county self.deaths = [0]*self.num_counties self.births = [0]*self.num_counties # remove agents (death) # loop through all agents for agent in self.schedule.agents: # source: https://www.ssa.gov/oact/STATS/table4c6.html#ss # calculate death probability by age in the united states if random.random() < 0.0001*(math.e**(0.075*agent.age)): # keep track of deaths by county self.deaths[agent.pos] += 1 # remove agent from model self.grid._remove_agent(agent, agent.pos) # remove agent from schedule self.schedule.remove(agent) # update number of agents self.num_agents -= 1 # add agents (birth) # loop through all counties for county in range(self.num_counties): # source: https://www.cdc.gov/nchs/fastats/births.htm # access current population current_population = self.county_population_list[county] # calculate how many agents should be added to_add = current_population//100 # birth rate # update number of agents self.num_agents += to_add # add specified number of agents for count in range(to_add): # update agent index self.agent_index += 1 # create new agent agent = Household(self.agent_index, self) # place agent in current county self.grid.place_agent(agent, county) # add agent to schedule self.schedule.add(agent) # initialize agent attributes and networks # agents are assumed to be 18 as that is the lower bound of a householder's age agent.age = 18 # based on age, income is assigned agent.initialize_income(random.random()) # based on income, tenure is assigned agent.initialize_tenure(random.random()) # input-specified network is initialized agent.initialize_network() # family is initialized agent.initialize_family() if self.preferences: agent.initialize_preference() # original position is set agent.original_pos = agent.pos # keep track of births by county self.births[agent.pos] += 1 # loop through counties, update population counts for county in self.nodes: self.county_population_list[county] = len(self.G.node[county]['agent']) def update_climate(self): """ Update climate variables based on NOAA's predictions. Index 1 represents number of days above 90 degrees Fahrenheit. Index 4 represents number of days with < 1 inch of rain. Index 7 represents number of days without rain. Indexes 3, 6, and 9 are the yearly increases/decreases for these estimates. The update function is a simple linear function. Note: More accurate climate data could be integrated by importing more climate explorer data. """ for n in self.nodes: self.G.node[n]['climate'][1] += self.G.node[n]['climate'][3] self.G.node[n]['climate'][4] += self.G.node[n]['climate'][6] self.G.node[n]['climate'][7] += self.G.node[n]['climate'][9] def rank_by_climate(self): """ Create an ordered list of counties, from least hot/dry climate to most hot/dry climate. """ # initialize lists to store data heat_data = [] dry_data = [] heat_dry_data = [] # loop through counties in order for county in self.nodes: # access and store all heat/dry data heat_data.append(self.G.node[county]['climate'][1]) dry_data.append(self.G.node[county]['climate'][7]) # find max heat/dry data max_heat = max(heat_data) max_dry = max(dry_data) # normalize data based on max value heat_data = [(e/max_heat) for e in heat_data] dry_data = [(e/max_dry) for e in dry_data] # add normalized data for county in range(self.num_counties): heat_dry_data.append(heat_data[county] + dry_data[county]) # convert to numpy array heat_dry_data = np.array(heat_dry_data) # returns indices that would sort an array (in this case, # returns county id's from best to worst climate) county_climate_rank = np.argsort(heat_dry_data) # convert to list, update model attribute self.county_climate_ranking = list(county_climate_rank) def update_income_counts(self): """ Update income distribution by county. """ # loop through counties in order for county in range(self.num_counties): # initialize list self.county_income[county] = [0]*10 # loop through agents for agent in self.schedule.agents: # update dictionary based on agent data self.county_income[agent.pos][agent.income-1] += 1 # income counts are printed at the beginning and end of run print(self.county_income) def get_preference_distribution(self): """ TODO: docstring """ preference_list = [0]*5 for agent in self.schedule.agents: preference_list[agent.preference] += 1 print(preference_list) def step(self): """ Advance the model by one step. """ global TICK # update climate ranking self.rank_by_climate() # advance all agents by one step self.schedule.step() # update population self.update_population() # update climate self.update_climate() # collect data self.datacollector.collect(self) # update step counter TICK += 1
class InfectedModel(Model): """Model class for a simplistic infection model.""" # Geographical parameters for desired map MAP_COORDS = COORDS[CITY] unique_id = "NAME" def __init__(self, monitored_statistic, show_schools, show_restaurants): """ Create a new InfectedModel """ self.schedule = BaseScheduler(self) self.grid = GeoSpace() self.steps = 0 self.counts = None self.reset_counts() self.monitored_statistic = 'infected-per-home-series' self.show_schools = show_schools self.show_restaurants = show_restaurants self.maximum = {self.monitored_statistic: 0} self.minimum = {self.monitored_statistic: sys.maxsize} with open(LOG_FILE) as json_file: self.simulation_data = json.load(json_file) #for key in self.simulation_data[self.monitored_statistic]: # for v in self.simulation_data[self.monitored_statistic][key]: # if v > self.maximum[self.monitored_statistic]: self.maximum[self.monitored_statistic] = v # if v < self.minimum[self.monitored_statistic]: self.minimum[self.monitored_statistic] = v self.minimum['infected-per-home-series'] = 0 self.maximum['infected-per-home-series'] = 500 self.running = True self.datacollector = DataCollector({ "infected": get_infected_count, "susceptible": get_susceptible_count, "recovered": get_recovered_count, "dead": get_dead_count, }) # Neighboorhoods AC = AgentCreator(NeighbourhoodAgent, {"model": self}) neighbourhood_agents = AC.from_file(geojson_neighborhoods, unique_id=self.unique_id) for agent in neighbourhood_agents: for neighborhood in NEIGHBORHOODS['features']: if agent.unique_id == neighborhood['properties']['NAME']: agent2feature[agent.unique_id] = neighborhood break self.grid.add_agents(neighbourhood_agents) # Schools AC = AgentCreator(SchoolAgent, {"model": self}) school_agents = AC.from_file(geojson_schools, unique_id=self.unique_id) for agent in school_agents: for school in SCHOOLS['features']: if agent.unique_id == school['properties']['NAME']: agent2feature[agent.unique_id] = school break self.grid.add_agents(school_agents) # Restaurants AC = AgentCreator(RestaurantAgent, {"model": self}) restaurant_agents = AC.from_file(geojson_restaurants, unique_id=self.unique_id) for agent in restaurant_agents: for restaurant in RESTAURANTS['features']: if agent.unique_id == restaurant['properties']['NAME']: agent2feature[agent.unique_id] = restaurant break self.grid.add_agents(restaurant_agents) for agent in neighbourhood_agents + school_agents: self.schedule.add(agent) self.datacollector.collect(self) def reset_counts(self): self.counts = { "susceptible": 0, "infected": 0, "recovered": 0, "dead": 0, "safe": 0, "hotspot": 0, } def step(self): #if self.steps >= len(self.simulation_data['infected-series']) - 2: if self.steps > 50: self.running = False return self.steps += 1 self.reset_counts() self.schedule.step() self.grid._recreate_rtree( ) # Recalculate spatial tree, because agents are moving self.datacollector.collect(self) return True
def __init__( self, height=40, width=40, citizen_density=0.7, cop_density=0.074, citizen_vision=7, cop_vision=7, legitimacy=0.8, max_jail_term=1000, active_threshold=0.1, arrest_prob_constant=2.3, movement=True, initial_unemployment_rate=0.1, corruption_level=0.1, susceptible_level=0.3, honest_level=0.6, max_iters=1000, ): super().__init__() self.height = height self.width = width self.citizen_density = citizen_density self.cop_density = cop_density self.citizen_vision = citizen_vision self.cop_vision = cop_vision self.legitimacy = legitimacy self.max_jail_term = max_jail_term self.active_threshold = active_threshold self.arrest_prob_constant = arrest_prob_constant self.movement = movement self.initial_unemployment_rate = initial_unemployment_rate self.corruption_level = corruption_level self.susceptible_level = susceptible_level self.max_iters = max_iters self.iteration = 0 self.schedule = RandomActivation(self) self.grid = Grid(height, width, torus=True) model_reporters = { "Quiescent": lambda m: self.count_type_citizens(m, "Quiescent"), "Active": lambda m: self.count_type_citizens(m, "Active"), "Jailed": lambda m: self.count_jailed(m), "Employed": lambda m: self.count_employed(m), "Corrupted": lambda m: self.count_moral_type_citizens(m, "Corrupted"), "Honest": lambda m: self.count_moral_type_citizens(m, "Honest"), "Susceptible": lambda m: self.count_moral_type_citizens(m, "Susceptible") } agent_reporters = { "x": lambda a: a.pos[0], "y": lambda a: a.pos[1], "breed": lambda a: a.breed, "jail_sentence": lambda a: getattr(a, "jail_sentence", None), "condition": lambda a: getattr(a, "condition", None), "arrest_probability": lambda a: getattr(a, "arrest_probability", None), "is_employed": lambda a: getattr(a, "is_employed", None), "moral_condition": lambda a: getattr(a, "moral_condition", None), } self.datacollector = DataCollector(model_reporters=model_reporters, agent_reporters=agent_reporters) unique_id = 0 if self.cop_density + self.citizen_density > 1: raise ValueError( "Cop density + citizen density must be less than 1") if self.initial_unemployment_rate > 1: raise ValueError( "initial_unemployment_rate must be between [0,1] ") if self.corruption_level + self.susceptible_level > 1: raise ValueError("moral level must be less than 1 ") for (contents, x, y) in self.grid.coord_iter(): if self.random.random() < self.cop_density: cop = Cop(unique_id, self, (x, y), vision=self.cop_vision) unique_id += 1 self.grid[y][x] = cop self.schedule.add(cop) elif self.random.random() < (self.cop_density + self.citizen_density): moral_state = "Honest" is_employed = 1 if self.random.random() < self.initial_unemployment_rate: is_employed = 0 p = self.random.random() if p < self.corruption_level: moral_state = "Corrupted" elif p < self.corruption_level + self.susceptible_level: moral_state = "Susceptible" citizen = Citizen( unique_id, self, (x, y), #updated hardship formula: if agent is employed hardship is alleviated hardship=self.random.random() - (is_employed * self.random.uniform(0.05, 0.15)), legitimacy=self.legitimacy, #updated regime legitimacy, so inital corruption rate is taken into consideration regime_legitimacy=self.legitimacy - self.corruption_level, risk_aversion=self.random.random(), active_threshold=self.active_threshold, #updated threshold: if agent is employed threshold for rebelling is raised threshold=self.active_threshold + (is_employed * self.random.uniform(0.05, 0.15)), vision=self.citizen_vision, is_employed=is_employed, moral_state=moral_state, ) unique_id += 1 self.grid[y][x] = citizen self.schedule.add(citizen) self.running = True self.datacollector.collect(self)
def __init__(self, height, width, density, wms, wts, wus, td, ve, ae): # Initialize model parameters self.height = height self.width = width self.density = density self.weeklyCampaignSpend = wms self.weeklyTrainingSpend = wts self.weeklyUsabilitySpend = wus #initialize HITL related parameters self.trainingDataWeeklyInput = td self.vizEffect = ve self.learningRate = 0 self.dataInstances = 10000 self.algoAccuracy = ae self.algoEffect = self.algoAccuracy *0.1 # Set up model objects #this sets the activation order of the agents (when they make their moves) to be random each step self.schedule = RandomActivation(self) #this creates the physical grid we are using to simulate word of mouth spread of the model self.grid = Grid(height, width, torus=False) #these use the Mesa DataCollector method to create several trackers to collect data from the model run self.dc_output = DataCollector(model_reporters={"Avg Output Value Per Person Per Week": compute_avg_output}) self.dc_tracker = DataCollector(model_reporters={"Average IA": compute_avg_ia}) self.dc_adoption = DataCollector({"Potential Trialer": lambda m: self.count_type(m, "Potential Trialer"), "Trialer": lambda m: self.count_type(m, "Trialer"), "Adopter": lambda m: self.count_type(m, "Adopter"), "Defector": lambda m: self.count_type(m, "Defector"), "Evangelist": lambda m: self.count_type(m, "Evangelist")}) self.dc_trialers =DataCollector({"Trialer": lambda m: self.count_type(m, "Trialer")}) self.dc_algo = DataCollector({"Learning Rate": compute_learning_rate}) self.dc_master=DataCollector({"Potential Trialer": lambda m: self.count_type(m, "Potential Trialer"), "Trialer": lambda m: self.count_type(m, "Trialer"), "Adopter": lambda m: self.count_type(m, "Adopter"), "Defector": lambda m: self.count_type(m, "Defector"), "Evangelist": lambda m: self.count_type(m, "Evangelist"), "Avg Output Value Per Person": compute_avg_output, "Total Differential in Population": hitl_adv_differential, "Algo Accuracy": compute_algo_accuracy, "Algo Accuracy Increase": compute_learning_rate, "Total Dataset Size": compute_data_instances, "Algorithm Effect": compute_algo_effect, "Avg Data Collection Ouput": compute_avg_dc, "Avg Data Interpretation/Analysis Output": compute_avg_di, "Avg Interpreting Actions Output": compute_avg_ia, "Avg Coaching Output": compute_avg_coaching, "Avg Review Output": compute_avg_review}) #the logic for the creation of the agents, as well as setting the initial values of the agent parameters for x in range(self.width): for y in range(self.height): if random.random() < self.density: new_consultant = Consultant(self, (x, y), np.random.normal(60, 10), np.random.normal(70, 10), 0) if y == 0: new_consultant.condition = "Trialer" self.grid[y][x] = new_consultant self.schedule.add(new_consultant) #run the model when the class is called self.running = True
def __init__(self, height=115, width=85, config_file='inputs/dieba_init.csv', econ_file='inputs/econ_init.csv', tree_file='inputs/tree.csv', draftprice=250000, livestockprice=125000, defaultrot=['C', 'M', 'G'], tract=0, tractfile='inputs/tractor_costs.csv', rentcap=0, rentprice=0, rentin=0, rentout=0, rentpct=0, laborcost=30000): ''' Create a new model with the given parameters. Args: height, width: dimensions of area config_file: path to initial owner config csv (must have 1 header row) econ_file: path to economics/harvest data ''' # Set parameters self.height = height self.width = width self.draftprice = draftprice self.livestockprice = livestockprice self.defaultrot = defaultrot self.tract = tract self.tractcost = pd.read_csv(tractfile, index_col='type') self.rentin = rentin self.rentout = rentout self.rentcap = self.rentout #available to rent, updates within step self.rentpct = rentpct self.laborcost = laborcost self.config = np.genfromtxt(config_file, dtype=int, delimiter=',', skip_header=1) self.nowners = self.config.shape[0] self.econ = pd.read_csv(econ_file, index_col=['crop', 'mgt']) self.tree = pd.read_csv(tree_file, index_col=['crop', 'mgt', 'age']) self.schedule = ActivationByBreed(self) self.grid = MultiGrid(self.height, self.width, torus=False) self.Landcollector = breedDataCollector(breed=Land, agent_reporters={ "cultivated": lambda a: a.steps_cult, "fallow": lambda a: a.steps_fallow, "potential": lambda a: a.potential, "suitability": lambda a: a.suitability }) self.CropPlotcollector = breedDataCollector( breed=CropPlot, agent_reporters={ "owner": lambda a: a.owner, "plID": lambda a: a.plID, "crop": lambda a: a.crop, "mgt": lambda a: a.mgt, "harvest": lambda a: a.harvest, "GM": lambda a: a.GM, "pot": lambda a: a.get_land(a.pos).potential, "steps_cult": lambda a: a.get_land(a.pos).steps_cult, "suitability": lambda a: a.get_land(a.pos).suitability, "steps_fallow": lambda a: a.get_land(a.pos).steps_fallow }) self.TreePlotcollector = breedDataCollector(breed=TreePlot, agent_reporters={ "owner": lambda a: a.owner, "plID": lambda a: a.plID, "crop": lambda a: a.crop, "age": lambda a: a.age, "harvest": lambda a: a.harvest, "GM": lambda a: a.GM }) self.Ownercollector = breedDataCollector(breed=Owner, agent_reporters={ "owner": lambda a: a.owner, "hhsize": lambda a: a.hhsize, "cplots": lambda a: len(a.cplots), "trees": lambda a: len(a.trees), "wealth": lambda a: a.wealth, "expenses": lambda a: a.expenses, "income": lambda a: a.income, "draft": lambda a: a.draft, "livestock": lambda a: a.livestock, "tract": lambda a: a.tract, "rentout": lambda a: a.rentout, "rentin": lambda a: a.rentin, "full": lambda a: a.full }) self.Modelcollector = DataCollector(model_reporters={ "rentout": lambda m: m.rentout, "rentin": lambda m: m.rentin }) # Create land land_suitability = np.genfromtxt("inputs/db_suitability.csv", delimiter=',') land_feasibility = np.genfromtxt("inputs/db_feasibility_nop.csv", delimiter=',') for _, x, y in self.grid.coord_iter(): suitability = land_suitability[x, y] feasibility = land_feasibility[x, y] fallow = random.randrange(10) land = Land((x, y), self, suitability, feasibility, steps_fallow=fallow) self.grid.place_agent(land, (x, y)) self.schedule.add(land) #Create Owner agents: for i in range(self.nowners): x = random.randrange(20, self.height - 20) y = random.randrange(20, self.width - 20) owner = i nplots = self.config[i, 1] wealth = self.config[i, 2] hhsize = self.config[i, 3] draft = self.config[i, 4] livestock = self.config[i, 5] expenses = self.config[i, 6] trees = self.config[i, 7] tract = self.config[i, 8] owneragent = Owner((x, y), self, owner, wealth, hhsize, draft, livestock, expenses, trees, tract) self.grid.place_agent(owneragent, (x, y)) self.schedule.add(owneragent) for j in range(nplots): #Create CropPlots for each owner: # place near owner pos then move plotowner = owneragent.owner plID = j crop = self.defaultrot[random.randint( 0, (len(self.defaultrot) - 1))] # cpx = x+random.randrange(-1*owneragent.vision,owneragent.vision) # cpy = y+random.randrange(-1*owneragent.vision,owneragent.vision) croppl = CropPlot(owneragent.pos, self, plotowner, plID, crop) owneragent.cplots.append(croppl) self.grid.place_agent(croppl, (x, y)) croppl.move() #move to best pos'n near owner while croppl.get_land(croppl.pos).potential == 0: croppl.move() croppl.get_land(croppl.pos).steps_cult = random.randrange(10) self.schedule.add(croppl) for k in range(trees): plotowner = owneragent.owner plID = k treepl = TreePlot(owneragent.pos, self, plotowner, plID) owneragent.trees.append(treepl) self.grid.place_agent(treepl, (x, y)) treepl.move() self.schedule.add(treepl) self.running = True
class BankReservesModel(Model): # grid height grid_h = 20 # grid width grid_w = 20 """init parameters "init_people", "rich_threshold", and "reserve_percent" are all UserSettableParameters""" def __init__( self, height=grid_h, width=grid_w, init_people=2, rich_threshold=10, reserve_percent=50, ): self.height = height self.width = width self.init_people = init_people self.schedule = RandomActivation(self) self.grid = MultiGrid(self.width, self.height, torus=True) # rich_threshold is the amount of savings a person needs to be considered "rich" self.rich_threshold = rich_threshold self.reserve_percent = reserve_percent # see datacollector functions above self.datacollector = DataCollector( model_reporters={ "Rich": get_num_rich_agents, "Poor": get_num_poor_agents, "Middle Class": get_num_mid_agents, "Savings": get_total_savings, "Wallets": get_total_wallets, "Money": get_total_money, "Loans": get_total_loans }, agent_reporters={"Wealth": lambda x: x.wealth}) # create a single bank for the model self.bank = Bank(1, self, self.reserve_percent) # create people for the model according to number of people set by user for i in range(self.init_people): # set x coordinate as a random number within the width of the grid x = random.randrange(self.width) # set y coordinate as a random number within the height of the grid y = random.randrange(self.height) p = Person(i, (x, y), self, True, self.bank, self.rich_threshold) # place the Person object on the grid at coordinates (x, y) self.grid.place_agent(p, (x, y)) # add the Person object to the model schedule self.schedule.add(p) self.running = True def step(self): # collect data self.datacollector.collect(self) # tell all the agents in the model to run their step function self.schedule.step() def run_model(self): for i in range(self.run_time): self.step()