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))}
Beispiel #15
0
    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()