def apply(self, sim, t): ''' Perform testing ''' if self.n_tests[t]: # Compute weights for people who would test positive or negative positive_tests = np.zeros((sim.n, )) for i, person in enumerate(sim.people.values()): if person.infectious: positive_tests[i] = 1 negative_tests = 1 - positive_tests # Select the people to test in each category positive_inds = cv.choose_weighted(probs=positive_tests, n=min(sum(positive_tests), self.n_positive[t]), normalize=True) negative_inds = cv.choose_weighted( probs=negative_tests, n=min(sum(negative_tests), self.n_tests[t] - len(positive_inds)), normalize=True) # Todo - assess performance and optimize e.g. to reduce dict indexing for ind in positive_inds: person = sim.get_person(ind) person.test( t, test_sensitivity=1.0 ) # Sensitivity is 1 because the person is guaranteed to test positive self.results['n_tested'][t] += 1 self.results['n_diagnosed'][t] += 1 for ind in negative_inds: person = sim.get_person(ind) person.test(t, test_sensitivity=1.0) self.results['n_tested'][t] += 1
def test_choose(): sc.heading('Choose people') x1 = cova.choose(10, 5) with pytest.raises(Exception): cova.choose_weighted(10, 5) # Requesting mroe people than are available print(f'Uniform sample from 0-9: {x1}') return x1
def apply(self, sim): t = sim.t # Process daily tests -- has to be here rather than init so have access to the sim object if isinstance(self.daily_tests, (pd.Series, pd.DataFrame)): start_date = sim['start_day'] end_date = self.daily_tests.index[-1] dateindex = pd.date_range(start_date, end_date) self.daily_tests = self.daily_tests.reindex(dateindex, fill_value=0).to_numpy() # Check that there are still tests if t < len(self.daily_tests): n_tests = self.daily_tests[t] # Number of tests for this day if not (n_tests and pl.isfinite(n_tests)): # If there are no tests today, abort early return else: sim.results['new_tests'][t] += n_tests else: return test_probs = np.ones(sim.n) new_diagnoses = 0 for i,person in enumerate(sim.people): new_diagnoses += person.check_diagnosed(t) # Adjust testing probability based on what's happened to the person # NB, these need to be separate if statements, because a person can be both diagnosed and infectious/symptomatic if person.symptomatic: test_probs[i] *= self.sympt_test # They're symptomatic if person.quarantine: test_probs[i] *= self.quar_test # They're in quarantine if person.diagnosed: test_probs[i] = 0.0 test_inds = cv.choose_weighted(probs=test_probs, n=n_tests, normalize=True, unique=False) sim.results['new_diagnoses'][t] += new_diagnoses for test_ind in test_inds: person = sim.people[test_ind] person.test(t, self.sensitivity, test_delay=self.test_delay) return
def apply(self, sim): t = sim.t # Check that there are still tests if t < len(self.daily_tests): n_tests = self.daily_tests[t] # Number of tests for this day sim.results['new_tests'][t] += n_tests else: return # If there are no tests today, abort early if not (n_tests and pl.isfinite(n_tests)): return test_probs = np.ones(sim.n) new_diagnoses = 0 for i, person in enumerate(sim.people): new_diagnoses += person.check_diagnosed(t) # Adjust testing probability based on what's happened to the person # NB, these need to be separate if statements, because a person can be both diagnosed and infectious/symptomatic if person.symptomatic: test_probs[i] *= self.sympt_test # They're symptomatic if person.known_contact: test_probs[ i] *= self.trace_test # They've had contact with a known positive if person.diagnosed: test_probs[i] = 0.0 test_inds = cv.choose_weighted(probs=test_probs, n=n_tests, normalize=True) sim.results['new_diagnoses'][t] += new_diagnoses for test_ind in test_inds: person = sim.people[test_ind] person.test(t, self.sensitivity, test_delay=self.test_delay) return
def test_choose_weighted(): sc.heading('Choose weighted people') n = 100 samples = 5 lin = np.arange(n) lin = lin/lin.sum() x0 = cova.choose_weighted([0.01]*n, samples) x1 = cova.choose_weighted(lin, samples) x2 = cova.choose_weighted([1, 0, 0, 0, 0], 1) x3 = cova.choose_weighted([0.5, 0.5, 0, 0, 0], 1) assert x2[0] == 0 assert x3[0] in [0,1] assert len(x0) == len(x1) == samples with pytest.raises(Exception): cova.choose_weighted([0.5, 0, 0, 0, 0], 1) # Probabilities don't sum to 1 with pytest.raises(Exception): cova.choose_weighted([0.5, 0.5], 10) # Requesting mroe people than are available print(f'Uniform sample 0-99: x0 = {x0}, mean {x0.mean()}') print(f'Weighted sample 0-99: x1 = {x1}, mean {x1.mean()}') print(f'All weight on 0: x2 = {x2}') print(f'All weight on 0 or 1: x3 = {x3}') return x1
def run(self, seed_infections=1, verbose=None, calc_likelihood=False, do_plot=False, **kwargs): ''' Run the simulation ''' T = sc.tic() # Reset settings and results if verbose is None: verbose = self['verbose'] self.init_results() self.init_people( seed_infections=seed_infections) # Actually create the people daily_tests = self.data[ 'new_tests'] # Number of tests each day, from the data evacuated = self.data['evacuated'] # Number of people evacuated # Main simulation loop for t in range(self.npts): # Print progress if verbose >= 1: string = f' Running day {t:0.0f} of {self["n_days"]}...' if verbose >= 2: sc.heading(string) else: print(string) test_probs = { } # Store the probability of each person getting tested # Update each person for person in self.people.values(): # Count susceptibles if person.susceptible: self.results['n_susceptible'][t] += 1 continue # Don't bother with the rest of the loop # Handle testing probability if person.infectious: test_probs[person.uid] = self[ 'symptomatic'] # They're infectious: high probability of testing else: test_probs[person.uid] = 1.0 # If exposed, check if the person becomes infectious if person.exposed: self.results['n_exposed'][t] += 1 if not person.infectious and t >= person.date_infectious: # It's the day they become infectious person.infectious = True if verbose >= 2: print( f' Person {person.uid} became infectious!' ) # If infectious, check if anyone gets infected if person.infectious: # First, check for recovery if person.date_recovered and t >= person.date_recovered: # It's the day they become infectious person.exposed = False person.infectious = False person.recovered = True self.results['recoveries'][t] += 1 else: self.results['n_infectious'][ t] += 1 # Count this person as infectious n_contacts = cv.pt( person.contacts ) # Draw the number of Poisson contacts for this person contact_inds = cv.choose( max_n=len(self.people), n=n_contacts) # Choose people at random for contact_ind in contact_inds: exposure = cv.bt(self['r_contact'] ) # Check for exposure per person if exposure: target_person = self.people[contact_ind] if target_person.susceptible: # Skip people who are not susceptible self.results['infections'][t] += 1 target_person.susceptible = False target_person.exposed = True target_person.date_exposed = t incub_pars = dict(dist='normal_int', par1=self['incub'], par2=self['incub_std']) dur_pars = dict(dist='normal_int', par1=self['dur'], par2=self['dur_std']) incub_dist = cv.sample(**incub_pars) dur_dist = cv.sample(**dur_pars) target_person.date_infectious = t + incub_dist target_person.date_recovered = target_person.date_infectious + dur_dist if verbose >= 2: print( f' Person {person.uid} infected person {target_person.uid}!' ) # Count people who recovered if person.recovered: self.results['n_recovered'][t] += 1 # Implement testing -- this is outside of the loop over people, but inside the loop over time if t < len( daily_tests ): # Don't know how long the data is, ensure we don't go past the end n_tests = daily_tests.iloc[t] # Number of tests for this day if n_tests and not pl.isnan( n_tests): # There are tests this day self.results['tests'][ t] = n_tests # Store the number of tests test_probs = pl.array(list(test_probs.values())) test_probs /= test_probs.sum() test_inds = cv.choose_weighted(probs=test_probs, n=n_tests) uids_to_pop = [] for test_ind in test_inds: tested_person = self.people[test_ind] if tested_person.infectious and cv.bt( self['sensitivity'] ): # Person was tested and is true-positive self.results['diagnoses'][t] += 1 tested_person.diagnosed = True if self['evac_positives']: uids_to_pop.append(tested_person.uid) if verbose >= 2: print( f' Person {person.uid} was diagnosed!' ) for uid in uids_to_pop: # Remove people from the ship once they're diagnosed self.off_ship[uid] = self.people.pop(uid) # Implement quarantine if t == self['quarantine']: if verbose >= 1: print(f'Implementing quarantine on day {t}...') for person in self.people.values(): if 'quarantine_eff' in self.pars.keys(): quarantine_eff = self['quarantine_eff'] # Both else: if person.crew: quarantine_eff = self['quarantine_eff_c'] # Crew else: quarantine_eff = self['quarantine_eff_g'] # Guests person.contacts *= quarantine_eff # Implement testing change if t == self['testing_change']: if verbose >= 1: print(f'Implementing testing change on day {t}...') self['symptomatic'] *= self[ 'testing_symptoms'] # Reduce the proportion of symptomatic testing # Implement evacuations if t < len(evacuated): n_evacuated = evacuated.iloc[ t] # Number of evacuees for this day if n_evacuated and not pl.isnan( n_evacuated ): # There are evacuees this day # TODO -- refactor with n_tests if verbose >= 1: print(f'Implementing evacuation on day {t}') evac_inds = cv.choose(max_n=len(self.people), n=n_evacuated) uids_to_pop = [] for evac_ind in evac_inds: evac_person = self.people[evac_ind] if evac_person.infectious and cv.bt( self['sensitivity']): self.results['evac_diagnoses'][t] += 1 uids_to_pop.append(evac_person.uid) for uid in uids_to_pop: # Remove people from the ship once they're diagnosed self.off_ship[uid] = self.people.pop(uid) # Compute cumulative results self.results['cum_exposed'] = pl.cumsum(self.results['infections']) self.results['cum_tested'] = pl.cumsum(self.results['tests']) self.results['cum_diagnosed'] = pl.cumsum(self.results['diagnoses']) # Compute likelihood if calc_likelihood: self.likelihood() # Tidy up self.results['ready'] = True elapsed = sc.toc(T, output=True) if verbose >= 1: print(f'\nRun finished after {elapsed:0.1f} s.\n') summary = self.summary_stats() print(f"""Summary: {summary['n_susceptible']:5.0f} susceptible {summary['n_exposed']:5.0f} exposed {summary['n_infectious']:5.0f} infectious """) if do_plot: self.plot(**kwargs) return self.results