def antigen_test(self, inds, sym7d_sens=1.0, other_sens=1.0, specificity=1, loss_prob=0.0, sim=None): ''' Adapted from the test() method on sim.people to do antigen testing. Main change is that sensitivity is now broken into those symptomatic in the past week and others. Args: inds: indices of who to test sym7d_sens (float): probability of a true positive in a recently symptomatic individual (7d) other_sens (float): probability of a true positive in others loss_prob (float): probability of loss to follow-up delay (int): number of days before test results are ready ''' ppl = sim.people t = sim.t inds = np.unique(inds) # Antigen tests don't count towards stats (yet) #ppl.tested[inds] = True #ppl.date_tested[inds] = t # Only keep the last time they tested #ppl.date_results[inds] = t + delay # Keep date when next results will be returned is_infectious_not_dx = cv.itruei(ppl.infectious * ~ppl.diagnosed, inds) symp = cv.itruei(ppl.symptomatic, is_infectious_not_dx) recently_symp_inds = symp[t - ppl.date_symptomatic[symp] < 7] other_inds = np.setdiff1d(is_infectious_not_dx, recently_symp_inds) is_inf_pos = np.concatenate(( cv.binomial_filter( sym7d_sens, recently_symp_inds), # Higher sensitivity for <7 days cv.binomial_filter(other_sens, other_inds) # Lower sensitivity of otheres )) not_lost = cv.n_binomial(1.0 - loss_prob, len(is_inf_pos)) true_positive_uids = is_inf_pos[not_lost] # Store the date the person will be diagnosed, as well as the date they took the test which will come back positive # Not for antigen tests? date_diagnosed would interfere with later PCR. #ppl.date_diagnosed[true_positive_uids] = t + delay #ppl.date_pos_test[true_positive_uids] = t # False positivies if specificity < 1: non_infectious_uids = np.setdiff1d(inds, is_infectious_not_dx) false_positive_uids = cv.binomial_filter(1 - specificity, non_infectious_uids) else: false_positive_uids = np.empty(0, dtype=np.int64) # At low prevalence, true_positive_uids will likely outnumber false_positive_uids return np.concatenate((true_positive_uids, false_positive_uids))
def split_layer(self): ''' Split the layer into A- and B-sublayers ''' self.A_students = cv.binomial_filter(0.5, self.students) self.B_students = np.setdiff1d(self.students, self.A_students) self.A_group = np.concatenate( (self.A_students, self.teachers, self.staff)) self.B_group = np.concatenate( (self.B_students, self.teachers, self.staff)) A_layer = sc.dcp(self.base_layer) # start with the full layer rows = np.concatenate( ( # find all edges with a vertex in group B students np.isin(A_layer['p1'], self.B_students).nonzero()[0], np.isin(A_layer['p2'], self.B_students).nonzero()[0])) A_layer.pop_inds(rows) # remove these edges from layer A B_layer = sc.dcp(self.base_layer) # start with the full layer rows = np.concatenate( ( # find all edges with a vertex in group A students np.isin(B_layer['p1'], self.A_students).nonzero()[0], np.isin(B_layer['p2'], self.A_students).nonzero()[0])) B_layer.pop_inds(rows) # remove these edges from layer B return A_layer, B_layer
def subtarget_16_17(sim): inds = cv.true((sim.people.age >= 16) & (sim.people.age < 17)) if not hasattr(sim, 'subtarget_16_17'): sim.subtarget_16_17 = cv.binomial_filter( 0.9, inds) # 10% of 16-18 years olds vaccinated inds = np.setdiff1d(inds, sim.subtarget_16_17) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget_25_30(sim): inds = cv.true((sim.people.age >= 25) & (sim.people.age < 30)) if not hasattr(sim, 'subtarget_25_30'): sim.subtarget_25_30 = cv.binomial_filter( 0.1, inds) # 90% of 25-30 years olds vaccinated inds = np.setdiff1d(inds, sim.subtarget_25_30) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget_18_25(sim): inds = cv.true((sim.people.age >= 18) & (sim.people.age < 25)) if not hasattr(sim, 'subtarget_18_30'): sim.subtarget_18_25 = cv.binomial_filter( 0.1, inds) # 90% of 18-25 years vaccinated inds = np.setdiff1d(inds, sim.subtarget_18_25) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget_50_60(sim): inds = cv.true((sim.people.age >= 50) & (sim.people.age < 60)) if not hasattr(sim, 'subtarget_50_60'): sim.subtarget_50_60 = cv.binomial_filter( 0.1, inds) # 90% of 50-60 years olds vaccinated inds = np.setdiff1d(inds, sim.subtarget_50_60) return {'inds': inds, 'vals': 0.005 * np.ones(len(inds))}
def subtarget_40_45(sim): inds = cv.true((sim.people.age >= 40) & (sim.people.age < 45)) if not hasattr(sim, 'subtarget_40_45'): sim.subtarget_40_45 = cv.binomial_filter( 0.1, inds) # 90% of 40-45 years olds vaccinated inds = np.setdiff1d(inds, sim.subtarget_40_45) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget_75_100(sim): inds = cv.true(sim.people.age >= 75) if not hasattr(sim, 'subtarget_75_100'): sim.subtarget_75_100 = cv.binomial_filter( 0.05, inds) # 95% of 70+ years olds vaccinated inds = np.setdiff1d(inds, sim.subtarget_75_100) return {'inds': inds, 'vals': 0.02 * np.ones(len(inds))}
def subtarget_60_75(sim): inds = cv.true((sim.people.age >= 60) & (sim.people.age < 75)) if not hasattr(sim, 'subtarget_60_75'): sim.subtarget_60_75 = cv.binomial_filter( 0.05, inds) # 95% of 60+ years olds vaccinated inds = np.setdiff1d(inds, sim.subtarget_60_75) return {'inds': inds, 'vals': 0.02 * np.ones(len(inds))}
def screen(self, sim): ''' Screen those individuals who are arriving at school ''' # Inclusion criteria: diagnosed or symptomatic but not recovered and not dead inds_to_screen = cv.binomial_filter(self.screen_prob, self.uids_arriving_at_school) if len(inds_to_screen) == 0: return np.empty(0) ppl = sim.people symp = ppl.symptomatic[inds_to_screen] rec_or_dead = np.logical_or(ppl.recovered[inds_to_screen], ppl.dead[inds_to_screen]) screen_pos = np.logical_and(symp, ~rec_or_dead) screen_pos_uids = cv.itrue(screen_pos, inds_to_screen) # Add in screen positives from ILI amongst those who were screened negative if self.ili_prob is not None and self.ili_prob > 0: screen_neg_uids = cv.ifalse(screen_pos, inds_to_screen) n_ili = np.random.binomial(len(screen_neg_uids), self.ili_prob) # Poisson if n_ili > 0: ili_pos_uids = np.random.choice(screen_neg_uids, n_ili, replace=False) screen_pos_uids = np.concatenate( (screen_pos_uids, ili_pos_uids)) return screen_pos_uids
def subtarget_45_50(sim): inds = cv.true((sim.people.age >= 45) & (sim.people.age < 50)) if not hasattr(sim, 'subtarget_45_50'): sim.subtarget_45_50 = cv.binomial_filter( 0.25, inds ) # 30% of 40-50 years olds will not agree to get vaccinated inds = np.setdiff1d(inds, sim.subtarget_45_50) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget_30_40(sim): inds = cv.true((sim.people.age >= 30) & (sim.people.age < 40)) if not hasattr(sim, 'subtarget_30_40'): sim.subtarget_30_40 = cv.binomial_filter( 0.4, inds ) # 30% of 30-40+ years olds will not agree to get vaccinated inds = np.setdiff1d(inds, sim.subtarget_30_40) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget_12_17(sim): inds = cv.true((sim.people.age >= 12) & (sim.people.age < 17)) if not hasattr(sim, 'subtarget_12_17'): sim.subtarget_18_25 = cv.binomial_filter( 0.3, inds) # 30% of 18+ years olds will not agree to get vaccinated inds = np.setdiff1d(inds, sim.subtarget_12_17) return {'inds': inds, 'vals': 0.002 * np.ones(len(inds))}
def subtarget(sim, age=None, vx_scenario=None): rollout = vx_rollout[age] inds = cv.true((sim.people.age >= rollout['start_age']) * (sim.people.age < rollout['end_age'])) key = f'subtarget_{age}' if not hasattr(sim, key): prob = scendata[age][vx_scenario] vx_inds = cv.binomial_filter(prob, inds) setattr(sim, key, vx_inds) else: vx_inds = getattr(sim, key) inds = np.setdiff1d(inds, vx_inds) return {'inds': inds, 'vals': 0.65*np.ones(len(inds))}
return sim = cv.Sim(pop_type='hybrid', pop_size=2e4) sim.initialize() # Set alf parameters to look like household for key in ['contacts', 'dynam_layer', 'beta_layer', 'quar_eff']: sim[key]['alf'] = sim[key]['h'] pop_size = sim.pars['pop_size'] # Create the contacts for alf residents layer_keys = ['alf'] alf_inds = cv.binomial_filter(alf_prob, sc.findinds(sim.people.age >= min_alf_age)) assert max(alf_inds) < pop_size contacts_list = [{key: [] for key in layer_keys} for i in range(pop_size)] alf_contacts, _, clusters = cv.make_microstructured_contacts( len(alf_inds), {'alf': n_alf_contacts}) alf_dict = clusters['alf'] for i, ind in enumerate(alf_inds): contacts_list[ind]['alf'] = alf_inds[ alf_contacts[i]['alf']].tolist() # Copy over alf contacts if len(contacts_list[ind]['alf']): assert max(contacts_list[ind]['alf']) < pop_size # find the cluster that individual is in and replace with correct index for id, alf_members in alf_dict.items(): alf_dict[id] = sc.dcp(alf_inds[alf_members]).tolist()
def update(self, sim): ''' Check for testing today and conduct tests if needed. True positives return via date_diagnosed, false positives are returned via this function. Fields include: * 'start_date': '2020-08-29', * 'repeat': None, * 'groups': ['students', 'teachers', 'staff'], * 'coverage': 0.9, * 'sensitivity': 1, * 'delay': 1, * TODO: 'specificity': 1, ''' # false_positive_uids = [] ppl = sim.people t = sim.t ids_to_iso = {} for test in self.testing: if sim.t in test['t_vec']: undiagnosed_uids = cv.ifalsei(ppl.diagnosed, test['uids']) uids_to_test = cv.binomial_filter(test['coverage'], undiagnosed_uids) if self.school.verbose: print( sim.t, f'School {self.school.sid} of type {self.school.stype} is testing {len(uids_to_test)} today' ) if test['is_antigen']: self.n_tested['Antigen'] += len(uids_to_test) ag_pos_uids = self.antigen_test( uids_to_test, sim=sim, sym7d_sens=test['symp7d_sensitivity'], other_sens=test['other_sensitivity'], specificity=test['specificity']) pcr_fu_uids = cv.binomial_filter(test['PCR_followup_perc'], ag_pos_uids) ppl.test(pcr_fu_uids, test_sensitivity=1.0, test_delay=test['PCR_followup_delay']) #sim.results['new_tests'][t] += len(pcr_fu_uids) self.n_tested['PCR'] += len( pcr_fu_uids) # Also add follow-up PCR tests ids_to_iso = { uid: t + test['PCR_followup_delay'] for uid in pcr_fu_uids } non_pcr_uids = np.setdiff1d(ag_pos_uids, pcr_fu_uids) ids_to_iso.update({ uid: t + sim.pars['quar_period'] for uid in non_pcr_uids }) else: self.n_tested['PCR'] += len(uids_to_test) ppl.test(uids_to_test, test_sensitivity=test['sensitivity'], test_delay=test['delay']) #sim.results['new_tests'][t] += len(uids_to_test) # N.B. No false positives for PCR return ids_to_iso
def update(self, sim): ''' Process the day, return the school layer ''' # Even if a school is not yet open, consider testing in the population ids_to_iso = self.testing.update(sim) self.uids_at_home.update(ids_to_iso) # Look for newly diagnosed people (by PCR) newly_dx_inds = cv.itrue( sim.people.date_diagnosed[self.uids] == sim.t, self.uids) # Diagnosed this time step, time to trace if self.verbose and len(newly_dx_inds) > 0: print( sim.t, f'School {self.sid} has {len(newly_dx_inds)} newly diagnosed: {newly_dx_inds}', [sim.people.date_exposed[u] for u in newly_dx_inds], 'recovering', [sim.people.date_recovered[u] for u in newly_dx_inds]) # Isolate newly diagnosed individuals - could happen before school starts for uid in newly_dx_inds: self.uids_at_home[uid] = sim.t + sim.pars[ 'quar_period'] # Can come back after quarantine period # If any individuals are done with quarantine, return them to school self.uids_at_home = { uid: date for uid, date in self.uids_at_home.items() if date >= sim.t } # >= or =? # Check if school is open if not self.is_open: if sim.t == self.start_day: if self.verbose: print( sim.t, self.sid, f'School {self.sid} is opening today with {len(self.uids_at_home)} at home: {self.uids_at_home}' ) infectious_uids = cv.itrue( sim.people.infectious[self.uids], self.uids) print( sim.t, self.sid, 'Infectious:', len(cv.true(sim.people.infectious[self.uids])) * sim.rescale_vec[sim.t], len(infectious_uids)) print(sim.t, self.sid, 'Iuids:', infectious_uids) print( sim.t, self.sid, 'Itime:', [sim.people.date_exposed[u] for u in infectious_uids]) self.is_open = True else: # CLOSED SCHOOLS DO NOT PASS THIS POINT! return self.empty_layer date = sim.date(sim.t) self.scheduled_uids = self.ct_mgr.begin_day( date) # Call at the beginning of the update # Quarantine contacts of newly diagnosed individuals - # TODO: Schedule in a delay if len(newly_dx_inds) > 0: # Identify school contacts to quarantine uids_to_trace = np.array( cv.binomial_filter(self.trace_prob, newly_dx_inds), dtype='int64') # This has to be an int64 (the default type) uids_reached_by_tracing = self.ct_mgr.find_contacts( uids_to_trace ) # Assume all contacts of traced individuals will quarantine uids_to_quar = cv.binomial_filter(self.quar_prob, uids_reached_by_tracing) # Quarantine school contacts for uid in uids_to_quar: self.uids_at_home[uid] = sim.t + sim.pars[ 'quar_period'] # Can come back after quarantine period # N.B. Not intentionally testing those in quarantine other than what covasim already does # Determine who will arrive at school (used in screen() and stats.update()) self.uids_arriving_at_school = np.setdiff1d( self.scheduled_uids, np.fromiter(self.uids_at_home.keys(), dtype=int)) # Perform symptom screening screen_pos_ids = self.screen(sim) if len(screen_pos_ids) > 0: # Perform follow-up testing on some uids_to_test = cv.binomial_filter(self.test_prob, screen_pos_ids) sim.people.test(uids_to_test, test_delay=self.screen2pcr) #sim.results['new_tests'][t] += len(uids_to_test) self.testing.n_tested['PCR'] += len( uids_to_test ) # Ugly, move all testing in to the SchoolTesting class! # Send the screen positives home - quar_period if no PCR and otherwise the time to the PCR for uid in uids_to_test: self.uids_at_home[ uid] = sim.t + self.screen2pcr # Can come back after PCR results are in for uid in np.setdiff1d(screen_pos_ids, uids_to_test): self.uids_at_home[uid] = sim.t + sim.pars[ 'quar_period'] # Can come back after quarantine period # Determine (for tracking, mostly) who has arrived at school and passed symptom screening uids_at_home_array = np.fromiter(self.uids_at_home.keys(), dtype=int) self.uids_passed_screening = np.setdiff1d(self.scheduled_uids, uids_at_home_array) # Remove individuals at home from the network self.ct_mgr.remove_individuals(uids_at_home_array) self.stats.update(sim) # if sim.t == sim.npts-1: # self.stats.finalize() # Return what is left of the layer return self.ct_mgr.get_layer()