def init_sim(self, sim): super().init_sim(sim) self.clock = sim.clock self.scale_factor = sim.world.scale_factor self.home_activity_type = sim.activity_manager.as_int( self.config['home_activity_type']) self.max_tests_per_day = self.config['max_tests_per_day'] self.do_test_to_test_results_ticks = \ int(sim.clock.days_to_ticks(self.config['do_test_to_test_results_days'])) self.infected_states = self.config['incubating_states'] + self.config[ 'contagious_states'] self.agents = sim.world.agents # Collect data on agents for telemetry purposes for agent in self.agents: home_location = agent.locations_for_activity( self.home_activity_type)[0] self.home_locations_dict[agent] = home_location if home_location.typ in self.border_countries: self.resident_dict[agent] = False else: self.resident_dict[agent] = True self.test_result_events = DeferredEventPool(self.bus, self.clock) self.bus.subscribe("request.testing.start", self.start_test, self) self.bus.subscribe("notify.time.midnight", self.reset_daily_counter, self)
def init_sim(self, sim): super().init_sim(sim) self.test_events = DeferredEventPool(self.bus, sim.clock) self.bus.subscribe("request.testing.book_test", self.handle_book_test, self) # Time between selection for test and the time at which the test will take place self.time_to_arrange_test_no_symptoms = \ int(sim.clock.days_to_ticks(self.config['test_booking_to_test_sample_days_no_symptoms'])) self.time_to_arrange_test_symptoms = \ int(sim.clock.days_to_ticks(self.config['test_booking_to_test_sample_days_symptoms']))
class LargeScaleTesting(Intervention): """Randomly select a number of people per day for testing.""" def __init__(self, config, init_enabled): super().__init__(config, init_enabled) self.register_variable('invitations_per_day') def init_sim(self, sim): super().init_sim(sim) self.scale_factor = sim.world.scale_factor self.invitations_per_day = self.config['invitations_per_day'] self.test_booking_events = DeferredEventPool(self.bus, sim.clock) self.world = sim.world self.current_day = None self.bus.subscribe("notify.time.midnight", self.midnight, self) # Assign booking delays to each agents. This is the time an agent will wait between # being invited to test and booking a test: self.invitation_to_test_booking_delay = {} delay_distribution = self.config['invitation_to_test_booking_days'] for agent in self.world.agents: delay_days = self.prng.multinoulli_dict(delay_distribution) delay_ticks = int(sim.clock.days_to_ticks(int(delay_days))) self.invitation_to_test_booking_delay[agent] = delay_ticks def midnight(self, clock, t): """At midnight, book agents in for testing after a given delay by queuing up events that request a test be booked. This is equivalent to agents being notified that they should book, but not doing so immediately.""" if not self.enabled: return if self.invitations_per_day == 0: return num_invitations = math.ceil(self.scale_factor * self.invitations_per_day) # Invite for testing by random selection: test_agents_random = self.prng.random_sample(self.world.agents, num_invitations) for agent in test_agents_random: self.test_booking_events.add( "request.testing.book_test", self.invitation_to_test_booking_delay[agent], agent)
class TestBooking(Intervention): """Consume a 'request to book test' signal and wait a bit whilst getting around to it. Represents the process of booking a test, where testing may be limited and not available immediately.""" def __init__(self, config, init_enabled): super().__init__(config, init_enabled) self.symptomatic_states = config['symptomatic_states'] self.agents_awaiting_test = set() def init_sim(self, sim): super().init_sim(sim) self.test_events = DeferredEventPool(self.bus, sim.clock) self.bus.subscribe("request.testing.book_test", self.handle_book_test, self) # Time between selection for test and the time at which the test will take place self.time_to_arrange_test_no_symptoms = \ int(sim.clock.days_to_ticks(self.config['test_booking_to_test_sample_days_no_symptoms'])) self.time_to_arrange_test_symptoms = \ int(sim.clock.days_to_ticks(self.config['test_booking_to_test_sample_days_symptoms'])) def handle_book_test(self, agent): """Someone has been selected for testing. Insert a delay between the booking of the test and the test""" # If disabled, prevent new bookings. Old bookings will still complete if not self.enabled: return if agent not in self.agents_awaiting_test: if agent.health in self.symptomatic_states: self.test_events.add(self.send_agent_for_test, self.time_to_arrange_test_symptoms, agent) else: self.test_events.add(self.send_agent_for_test, self.time_to_arrange_test_no_symptoms, agent) self.agents_awaiting_test.add(agent) def send_agent_for_test(self, agent): """Send an event requesting a test for the given agent.""" self.agents_awaiting_test.remove(agent) # Update index self.bus.publish("request.testing.start", agent)
def init_sim(self, sim): super().init_sim(sim) self.prob_test_symptoms_symptomatic = self.config[ 'prob_test_symptoms_symptomatic'] self.prob_test_symptoms_asymptomatic = self.config[ 'prob_test_symptoms_asymptomatic'] self.onset_of_symptoms_to_test_booking = \ int(sim.clock.days_to_ticks(self.config['onset_of_symptoms_to_test_booking_days'])) self.symptomatic_states = self.config['symptomatic_states'] self.asymptomatic_states = self.config['asymptomatic_states'] self.test_booking_events = DeferredEventPool(self.bus, sim.clock) self.bus.subscribe("notify.agent.health", self.handle_health_change, self)
class PrescriptionTesting(Intervention): """This refers to situations where an agent books a test without having been directed to do so by any of the other interventions. Chief among these are the situations in which an agent voluntarily books a test having developed symptoms.""" def init_sim(self, sim): super().init_sim(sim) self.prob_test_symptoms_symptomatic = self.config[ 'prob_test_symptoms_symptomatic'] self.prob_test_symptoms_asymptomatic = self.config[ 'prob_test_symptoms_asymptomatic'] self.onset_of_symptoms_to_test_booking = \ int(sim.clock.days_to_ticks(self.config['onset_of_symptoms_to_test_booking_days'])) self.symptomatic_states = self.config['symptomatic_states'] self.asymptomatic_states = self.config['asymptomatic_states'] self.test_booking_events = DeferredEventPool(self.bus, sim.clock) self.bus.subscribe("notify.agent.health", self.handle_health_change, self) def handle_health_change(self, agent, old_health): """When an agent changes health state to a symptomatic state, there is a certain chance that they book a test. Booking a test takes time, so this method queues up the test booking event.""" if not self.enabled: return # If no change, skip if old_health == agent.health: return # If moving from an asymptomatic state to a symtomatic state if old_health not in self.symptomatic_states and agent.health in self.symptomatic_states: if self.prng.boolean(self.prob_test_symptoms_symptomatic): self.test_booking_events.add("request.testing.book_test", \ self.onset_of_symptoms_to_test_booking, agent) # If moving from to an asymptomatic state if old_health not in self.asymptomatic_states and agent.health in self.asymptomatic_states: if self.prng.boolean(self.prob_test_symptoms_asymptomatic): self.test_booking_events.add("request.testing.book_test", \ self.onset_of_symptoms_to_test_booking, agent)
def init_sim(self, sim): super().init_sim(sim) self.default_duration_days = self.config['default_duration_days'] self.default_duration_ticks = int( sim.clock.days_to_ticks(self.default_duration_days)) early_end_days = self.config[ 'negative_test_result_to_end_quarantine_days'] self.early_end_ticks = int(sim.clock.days_to_ticks(early_end_days)) self.location_blacklist = self.config['location_blacklist'] self.home_activity_type = sim.activity_manager.as_int( self.config['home_activity_type']) self.disable_releases_immediately = self.config[ 'disable_releases_immediately'] self.health_states = sim.disease_model.states self.clock = sim.clock self.end_quarantine_events = DeferredEventPool(self.bus, sim.clock) self.agents_in_quarantine = set() # What to do this tick self.agents_to_add = [] self.agents_to_remove = [] # Enter/leave quarantine self.bus.subscribe("notify.time.tick", self.update_quarantine_status, self) # Queue people up to enter/leave quarantine this tick self.bus.subscribe("notify.testing.result", self.handle_test_result, self) self.bus.subscribe("request.quarantine.start", self.handle_start_quarantine, self) self.bus.subscribe("request.quarantine.stop", self.handle_end_quarantine, self) # Respond to requested location changes by moving people home self.bus.subscribe("request.agent.location", self.handle_location_change, self) self.bus.subscribe("notify.time.midnight", self.record_number_in_quarantine, self) self.register_variable('default_duration_days')
def init_sim(self, sim): super().init_sim(sim) self.scale_factor = sim.world.scale_factor self.invitations_per_day = self.config['invitations_per_day'] self.test_booking_events = DeferredEventPool(self.bus, sim.clock) self.world = sim.world self.current_day = None self.bus.subscribe("notify.time.midnight", self.midnight, self) # Assign booking delays to each agents. This is the time an agent will wait between # being invited to test and booking a test: self.invitation_to_test_booking_delay = {} delay_distribution = self.config['invitation_to_test_booking_days'] for agent in self.world.agents: delay_days = self.prng.multinoulli_dict(delay_distribution) delay_ticks = int(sim.clock.days_to_ticks(int(delay_days))) self.invitation_to_test_booking_delay[agent] = delay_ticks
class Laboratory(Intervention): """A testing laboratory.""" def __init__(self, config, init_enabled): super().__init__(config, init_enabled) self.prob_false_positive = config['prob_false_positive'] self.prob_false_negative = config['prob_false_negative'] self.border_countries = config['border_countries'] self.tests_performed_today = 0 self.home_locations_dict = {} self.resident_dict = {} self.register_variable('max_tests_per_day') def init_sim(self, sim): super().init_sim(sim) self.clock = sim.clock self.scale_factor = sim.world.scale_factor self.home_activity_type = sim.activity_manager.as_int( self.config['home_activity_type']) self.max_tests_per_day = self.config['max_tests_per_day'] self.do_test_to_test_results_ticks = \ int(sim.clock.days_to_ticks(self.config['do_test_to_test_results_days'])) self.infected_states = self.config['incubating_states'] + self.config[ 'contagious_states'] self.agents = sim.world.agents # Collect data on agents for telemetry purposes for agent in self.agents: home_location = agent.locations_for_activity( self.home_activity_type)[0] self.home_locations_dict[agent] = home_location if home_location.typ in self.border_countries: self.resident_dict[agent] = False else: self.resident_dict[agent] = True self.test_result_events = DeferredEventPool(self.bus, self.clock) self.bus.subscribe("request.testing.start", self.start_test, self) self.bus.subscribe("notify.time.midnight", self.reset_daily_counter, self) def reset_daily_counter(self, clock, t): """Reset daily test count""" self.tests_performed_today = 0 def start_test(self, agent): """Start the test. Agents are tested by selecting a weighted random result according to the class' config, and then queueing up a result to be sent out after a set amount of time. This delay represents the time taken to complete the test itself. """ # If disabled, don't start new tests. Tests underway will still complete if not self.enabled: return if self.tests_performed_today >= math.ceil( self.max_tests_per_day * self.scale_factor): return test_result = False if agent.health in self.infected_states: if self.prng.boolean(1 - self.prob_false_negative): test_result = True else: if self.prng.boolean(self.prob_false_positive): test_result = True self.tests_performed_today += 1 self.test_result_events.add("notify.testing.result", self.do_test_to_test_results_ticks, agent, test_result) self.report("notify.testing.result", self.clock, test_result, agent.age, agent.health, self.home_locations_dict[agent].uuid, self.home_locations_dict[agent].coord, self.resident_dict[agent])
class Quarantine(Intervention): """Intervention that applies quarantine rules. Agents are forced to return to certain locations when they request to move.""" def init_sim(self, sim): super().init_sim(sim) self.default_duration_days = self.config['default_duration_days'] self.default_duration_ticks = int( sim.clock.days_to_ticks(self.default_duration_days)) early_end_days = self.config[ 'negative_test_result_to_end_quarantine_days'] self.early_end_ticks = int(sim.clock.days_to_ticks(early_end_days)) self.location_blacklist = self.config['location_blacklist'] self.home_activity_type = sim.activity_manager.as_int( self.config['home_activity_type']) self.disable_releases_immediately = self.config[ 'disable_releases_immediately'] self.health_states = sim.disease_model.states self.clock = sim.clock self.end_quarantine_events = DeferredEventPool(self.bus, sim.clock) self.agents_in_quarantine = set() # What to do this tick self.agents_to_add = [] self.agents_to_remove = [] # Enter/leave quarantine self.bus.subscribe("notify.time.tick", self.update_quarantine_status, self) # Queue people up to enter/leave quarantine this tick self.bus.subscribe("notify.testing.result", self.handle_test_result, self) self.bus.subscribe("request.quarantine.start", self.handle_start_quarantine, self) self.bus.subscribe("request.quarantine.stop", self.handle_end_quarantine, self) # Respond to requested location changes by moving people home self.bus.subscribe("request.agent.location", self.handle_location_change, self) self.bus.subscribe("notify.time.midnight", self.record_number_in_quarantine, self) self.register_variable('default_duration_days') def record_number_in_quarantine(self, clock, t): """Record data on number of agents in quarantine and their health status""" self.default_duration_ticks = int( clock.days_to_ticks(self.default_duration_days)) num_in_quarantine = len(self.agents_in_quarantine) agents_in_quarantine_by_health_state = { str(hs): len([ agent for agent in self.agents_in_quarantine if agent.health == hs ]) for hs in self.health_states } total_age = sum([agent.age for agent in self.agents_in_quarantine]) self.report("quarantine_data", self.clock, num_in_quarantine, agents_in_quarantine_by_health_state, total_age) def update_quarantine_status(self, clock, t): """Take lists of things to do and apply them.""" for agent in self.agents_to_add: if agent not in self.agents_in_quarantine: self.agents_in_quarantine.add(agent) self.end_quarantine_events.add("request.quarantine.stop", \ self.default_duration_ticks, agent) self.bus.publish("notify.quarantine.start", agent) self.agents_to_add = [] for agent in self.agents_to_remove: if agent in self.agents_in_quarantine: self.agents_in_quarantine.remove(agent) self.bus.publish("notify.quarantine.end", agent) self.agents_to_remove = [] def handle_test_result(self, agent, result): """Respond to positive test results by starting quarantine. Respond to negative test results by ending quarantine.""" if agent in self.agents_in_quarantine and not result: self.end_quarantine_events.add("request.quarantine.stop", self.early_end_ticks, agent) elif agent not in self.agents_in_quarantine and result: self.agents_in_quarantine.add(agent) self.end_quarantine_events.add("request.quarantine.stop", self.default_duration_ticks, agent) def handle_start_quarantine(self, agent): """Queues up agents to start quarantine next time quarantine status is updated.""" # If intervention is disabled, don't ever put people in quarantine if not self.enabled: return if agent in self.agents_in_quarantine or agent in self.agents_to_add: return self.agents_to_add.append(agent) return MessageBus.CONSUME def handle_end_quarantine(self, agent): """Queues up agents to end quarantine next time quarantine status is updated.""" if agent in self.agents_in_quarantine and agent not in self.agents_to_remove: self.agents_to_remove.append(agent) return MessageBus.CONSUME def handle_location_change(self, agent, new_location): """Catch any location changes that will move quarantined agents out of their home, and rebroadcast an event to move them home again. """ # If we've been told to curtail all quarantines, allow people out. # They retain their quarantined status, so will be restricted again if quarantine # re-enables, but for now they're good. if self.disable_releases_immediately and not self.enabled: return if agent in self.agents_in_quarantine: home_location = agent.locations_for_activity( self.home_activity_type)[0] if new_location != home_location: if new_location.typ not in self.location_blacklist: self.bus.publish("request.agent.location", agent, home_location) return MessageBus.CONSUME
def init_sim(self, sim): super().init_sim(sim) # This controls how many first doses are able to be distributed per day. The total number # of doses per day will be this number plus the number of second doses delivered that day. self.scale_factor = sim.world.scale_factor self.max_first_doses_per_day = self.config['max_first_doses_per_day'] # A certain amount of time after the first dose, a second dose will be administered time_between_doses_days = int(self.config['time_between_doses_days']) self.time_between_doses_ticks = int( sim.clock.days_to_ticks(time_between_doses_days)) self.second_dose_events = DeferredEventPool(self.bus, sim.clock) self.bus.subscribe("notify.time.midnight", self.midnight, self) # self.bus.subscribe("notify.testing.result", self.update_vaccination_priority_list, self) self.bus.subscribe("request.vaccination.second_dose", self.administer_second_dose, self) # A list of agents to be vaccinated self.vaccination_priority_list = [] # A precomuted record of where agents live and work, for telemetry purposes self.home_location_type_dict = {} self.work_location_type_dict = {} # Order the agents according to the desired preferential scheme carehome_residents_workers = [] hospital_workers = [] other_agents = [] care_home_location_type = self.config['care_home_location_type'] hospital_location_type = self.config['hospital_location_type'] home_activity_type = sim.activity_manager.as_int( self.config['home_activity_type']) work_activity_type = sim.activity_manager.as_int( self.config['work_activity_type']) self.first_dose_successful = self.config['prob_first_dose_successful'] self.second_dose_successful = self.config[ 'prob_second_dose_successful'] min_age = self.config['min_age'] age_low = self.config['age_low'] age_high = self.config['age_high'] prob_low = self.config['prob_low'] prob_med = self.config['prob_med'] prob_high = self.config['prob_high'] # A dictionary of who doesn't refuse the vaccine self.agent_wants_vaccine = {} # Decide in advance who will refuse the vaccine for agent in sim.world.agents: if agent.age >= min_age: if agent.age < age_low: self.agent_wants_vaccine[agent] = self.prng.boolean( prob_low) if agent.age >= age_low and agent.age < age_high: self.agent_wants_vaccine[agent] = self.prng.boolean( prob_med) if agent.age >= age_high: self.agent_wants_vaccine[agent] = self.prng.boolean( prob_high) # Dictionaries of efficacy for each agent self.first_dose_effective = {} self.second_dose_effective = {} # Determine in advance the effecitveness of the vaccine on each agent for agent in sim.world.agents: if agent.age >= min_age: self.first_dose_effective[agent] = self.prng.boolean( self.first_dose_successful) self.second_dose_effective[agent] = self.prng.boolean( self.second_dose_successful) # Determine which agents live or work in carehomes and which agents work in hospitals. Note # that workplaces are assigned to everybody, so some agents will be assigned hospitals or # carehomes as places of work but, due to their routines, will not actually go to work at # these places due to not working at all. So this is somewhat approximate. for agent in sim.world.agents: if agent.age >= min_age: home_location = agent.locations_for_activity( home_activity_type)[0] self.home_location_type_dict[agent] = home_location.typ work_location = agent.locations_for_activity( work_activity_type)[0] self.work_location_type_dict[agent] = work_location.typ if home_location.typ in care_home_location_type or\ work_location.typ in care_home_location_type: carehome_residents_workers.append(agent) elif work_location.typ in hospital_location_type: hospital_workers.append(agent) else: other_agents.append(agent) # Sort the lists of agents by age, with the oldest first def return_age(agent): return agent.age carehome_residents_workers.sort(key=return_age, reverse=True) hospital_workers.sort(key=return_age, reverse=True) other_agents.sort(key=return_age, reverse=True) # Combine these lists together to get the order of agents to be vaccinated self.vaccination_priority_list = carehome_residents_workers + hospital_workers\ + other_agents
class Vaccination(Intervention): """Vaccinate agents using a two dose vaccine prioritizing certain agents first""" def __init__(self, config, init_enabled): super().__init__(config, init_enabled) # This represents the daily vaccination capacity self.register_variable('max_first_doses_per_day') def init_sim(self, sim): super().init_sim(sim) # This controls how many first doses are able to be distributed per day. The total number # of doses per day will be this number plus the number of second doses delivered that day. self.scale_factor = sim.world.scale_factor self.max_first_doses_per_day = self.config['max_first_doses_per_day'] # A certain amount of time after the first dose, a second dose will be administered time_between_doses_days = int(self.config['time_between_doses_days']) self.time_between_doses_ticks = int( sim.clock.days_to_ticks(time_between_doses_days)) self.second_dose_events = DeferredEventPool(self.bus, sim.clock) self.bus.subscribe("notify.time.midnight", self.midnight, self) # self.bus.subscribe("notify.testing.result", self.update_vaccination_priority_list, self) self.bus.subscribe("request.vaccination.second_dose", self.administer_second_dose, self) # A list of agents to be vaccinated self.vaccination_priority_list = [] # A precomuted record of where agents live and work, for telemetry purposes self.home_location_type_dict = {} self.work_location_type_dict = {} # Order the agents according to the desired preferential scheme carehome_residents_workers = [] hospital_workers = [] other_agents = [] care_home_location_type = self.config['care_home_location_type'] hospital_location_type = self.config['hospital_location_type'] home_activity_type = sim.activity_manager.as_int( self.config['home_activity_type']) work_activity_type = sim.activity_manager.as_int( self.config['work_activity_type']) self.first_dose_successful = self.config['prob_first_dose_successful'] self.second_dose_successful = self.config[ 'prob_second_dose_successful'] min_age = self.config['min_age'] age_low = self.config['age_low'] age_high = self.config['age_high'] prob_low = self.config['prob_low'] prob_med = self.config['prob_med'] prob_high = self.config['prob_high'] # A dictionary of who doesn't refuse the vaccine self.agent_wants_vaccine = {} # Decide in advance who will refuse the vaccine for agent in sim.world.agents: if agent.age >= min_age: if agent.age < age_low: self.agent_wants_vaccine[agent] = self.prng.boolean( prob_low) if agent.age >= age_low and agent.age < age_high: self.agent_wants_vaccine[agent] = self.prng.boolean( prob_med) if agent.age >= age_high: self.agent_wants_vaccine[agent] = self.prng.boolean( prob_high) # Dictionaries of efficacy for each agent self.first_dose_effective = {} self.second_dose_effective = {} # Determine in advance the effecitveness of the vaccine on each agent for agent in sim.world.agents: if agent.age >= min_age: self.first_dose_effective[agent] = self.prng.boolean( self.first_dose_successful) self.second_dose_effective[agent] = self.prng.boolean( self.second_dose_successful) # Determine which agents live or work in carehomes and which agents work in hospitals. Note # that workplaces are assigned to everybody, so some agents will be assigned hospitals or # carehomes as places of work but, due to their routines, will not actually go to work at # these places due to not working at all. So this is somewhat approximate. for agent in sim.world.agents: if agent.age >= min_age: home_location = agent.locations_for_activity( home_activity_type)[0] self.home_location_type_dict[agent] = home_location.typ work_location = agent.locations_for_activity( work_activity_type)[0] self.work_location_type_dict[agent] = work_location.typ if home_location.typ in care_home_location_type or\ work_location.typ in care_home_location_type: carehome_residents_workers.append(agent) elif work_location.typ in hospital_location_type: hospital_workers.append(agent) else: other_agents.append(agent) # Sort the lists of agents by age, with the oldest first def return_age(agent): return agent.age carehome_residents_workers.sort(key=return_age, reverse=True) hospital_workers.sort(key=return_age, reverse=True) other_agents.sort(key=return_age, reverse=True) # Combine these lists together to get the order of agents to be vaccinated self.vaccination_priority_list = carehome_residents_workers + hospital_workers\ + other_agents # def update_vaccination_priority_list(self, agent, test_result): # """Agents who have tested positive are removed from the list of agents to be vaccinated""" # if test_result: # try: # self.vaccination_priority_list.remove(agent) # except ValueError: # pass def administer_second_dose(self, agent): """Administers agents with a second dose of the vaccine""" if self.second_dose_effective[agent]: agent.vaccinated = True def midnight(self, clock, t): """At midnight, remove from the priority list agents who have tested positive that day and vaccinate an appropriate number of the remainder""" if not self.enabled: return if self.max_first_doses_per_day == 0: return max_rescaled = math.ceil(self.scale_factor * self.max_first_doses_per_day) num_to_vaccinate = min(max_rescaled, len(self.vaccination_priority_list)) agents_to_vaccinate = self.vaccination_priority_list[ 0:num_to_vaccinate] del self.vaccination_priority_list[0:num_to_vaccinate] agent_data = [] for agent in agents_to_vaccinate: if self.agent_wants_vaccine[agent]: if self.first_dose_effective[agent]: agent.vaccinated = True self.second_dose_events.add("request.vaccination.second_dose", self.time_between_doses_ticks, agent) # For telemetry agent_data.append([ agent.age, agent.health, agent.nationality, self.home_location_type_dict[agent], self.work_location_type_dict[agent] ]) self.report("notify.vaccination.first_doses", clock, agent_data)