<jupyter_start><jupyter_text>In `def __init_run(self)`:<jupyter_code># count indirect infections
        self.tot_inf_num = 0
        self.inf_num = 0
        self.indir_inf_num = 0
        self.full_indir_inf_num = 0<jupyter_output><empty_output><jupyter_text>where `self.tot_inf_num` is the total number of infections, `self.inf_num` is the number of infections caused by a contact, direct or indirect, with an infected individual (thus excluding other possible sources of infection), `self.indir_inf_num` is the number of indirect infections that happened at a time where the infector and the infected where not in the same place, and `self.full_indir_inf_num` is the number of indirect infections that happened at a time where the infector and the infected where not in the same place and the two individual where never in direct contact during that contact.  In `def __process_exposure_event(self, *, t, i, parent, contact)`:<jupyter_code>def __process_exposure_event(self, *, t, i, parent, contact):
        """
        Mark person `i` as exposed at time `t`
        Push asymptomatic or presymptomatic queue event
        """
        
        self.tot_inf_num += 1

        ### LINES ###

        # record which contact caused this exposure event (to check if it was traced for TP/FP/TN/FN computation)
        if contact is not None:
            self.inf_num += 1
            
            if contact.t_to_direct < t:
                assert contact.t_to >= t
                self.indir_inf_num += 1
                if contact.t_from > contact.t_to_direct:
                    assert t >= contact.t_from
                    self.full_indir_inf_num += 1
            
            assert(self.contact_caused_expo[i] is None)
            self.contact_caused_expo[i] = contact<jupyter_output><empty_output><jupyter_text>And after the main loop:<jupyter_code>print('End main loop')
        
        print('Total number of infections:', self.tot_inf_num)
        print('Infections from contacts', self.inf_num)
        print('Infections from indirect contacts', self.indir_inf_num)
        print('Infections from pure indirect contacts', self.full_indir_inf_num)<jupyter_output><empty_output><jupyter_text>The functions that push the `'expo'` event are:<jupyter_code>def __push_contact_exposure_events(self, *, t, infector, base_rate, tmax):
        """
        Pushes all exposure events that person `i` causes
        for other people via contacts, using `base_rate` as basic infectivity
        of person `i` (equivalent to `\mu` in model definition)
        """

        # compute all delta-contacts of `infector` with any other individual
        infectors_contacts = self.mob.find_contacts_of_indiv(indiv=infector, tmin=t, tmax=tmax)

        # iterate over contacts and store contact of with each individual `indiv_i` that is still susceptible 
        valid_contacts = set()
        for contact in infectors_contacts:
            if self.state['susc'][contact.indiv_i]:
                if contact not in self.mob.contacts[contact.indiv_i][infector]:
                    self.mob.contacts[contact.indiv_i][infector].update([contact])
                valid_contacts.add(contact.indiv_i)

        # generate potential exposure event for `j` from contact with `infector`
        for j in valid_contacts:
            self.__push_contact_exposure_infector_to_j(t=t, infector=infector, j=j, base_rate=base_rate, tmax=tmax)


    def __push_contact_exposure_infector_to_j(self, *, t, infector, j, base_rate, tmax):
        """
        Pushes the next exposure event that person `infector` causes for person `j`
        using `base_rate` as basic infectivity of person `i` 
        (equivalent to `\mu` in model definition)
        """
        tau = t
        sampled_event = False
        Z = self.__kernel_term(- self.delta, 0.0, 0.0)

        # sample next arrival from non-homogeneous point process
        while self.mob.will_be_in_contact(indiv_i=j, indiv_j=infector, t=tau, site=None) and not sampled_event and tau < min(tmax, self.max_time):
            
            # check if j could get infected from infector at current `tau`
            # i.e. there is `delta`-contact from infector to j (i.e. non-zero intensity)
            has_infectious_contact, _ = self.mob.is_in_contact(indiv_i=j, indiv_j=infector, t=tau, site=None)

            # if yes: do nothing
            if has_infectious_contact:
                pass 

            # if no:       
            else: 
                # directly jump to next contact start of a `delta`-contact (memoryless property)
                next_contact = self.mob.next_contact(indiv_i=j, indiv_j=infector, t=tau, site=None)

                assert(next_contact is not None) # (while loop invariant)
                tau = next_contact.t_from

            # sample event with maximum possible rate (in hours)
            lambda_max = max(self.betas.values()) * base_rate * Z
            lambda_max = max(lambda_max, 1e-8) # lambda_max = 0 is invalid
            tau += np.random.exponential(scale=1.0 / lambda_max)

            # thinning step: compute current lambda(tau) and do rejection sampling
            sampled_at_infectious_contact, sampled_at_contact = self.mob.is_in_contact(indiv_i=j, indiv_j=infector, t=tau, site=None)

            # 1) reject w.p. 1 if there is no more infectious contact at the new time (lambda(tau) = 0)
            if not sampled_at_infectious_contact:
                continue
            
            # 2) compute infectiousness integral in lambda(tau)
            # a. query times that infector was in [tau - delta, tau] at current site `site`
            site = sampled_at_contact.site
            infector_present = self.mob.list_intervals_in_window_individual_at_site(
                indiv=infector, site=site, t0=tau - self.delta, t1=tau)

            # b. compute contributions of infector being present in [tau - delta, tau]
            intersections = [(max(tau - self.delta, interv.left), min(tau, interv.right))
                for interv in infector_present]
            beta_k = self.betas[self.site_dict[self.site_type[site]]]
            p = (beta_k * base_rate * sum([self.__kernel_term(v[0], v[1], tau) for v in intersections])) \
                / lambda_max

            assert(p <= 1 + 1e-8 and p >= 0)

            # accept w.prob. lambda(t) / lambda_max
            u = np.random.uniform()
            if u <= p and tau < min(tmax, self.max_time):
                self.queue.push(
                    (tau, 'expo', j, infector, site, 
                    sampled_at_contact), # meta info: contact causing infection
                    priority=tau)
                sampled_event = True
        
        # DEBUG
        # all_contacts = []
        # tdebug = t
        # while self.mob.will_be_in_contact(indiv_i=j, indiv_j=infector, t=tdebug, site=None) and tdebug < min(tmax, self.max_time):
        #     c = self.mob.next_contact(indiv_i=j, indiv_j=infector, t=tdebug, site=None)
        #     all_contacts.append(c)
        #     tdebug = c.t_to + 1e-3

        # survival = self.__compute_empirical_survival_probability(
        #     t=tdebug, i=infector, j=j, contacts_i_j=all_contacts, base_rate=base_rate, ignore_sites=False)

        # risk = 1 - survival
        # bucket = np.digitize(risk, np.linspace(0.0, 1.0, num=10, endpoint=False))
        # if sampled_event:
        #     self.risk_got_exposed[bucket] += 1
        # else:
        #     self.risk_got_not_exposed[bucket] += 1
        
    def __push_household_exposure_events(self, *, t, infector, base_rate, tmax):
        """
        Pushes all exposure events that person `i` causes
        in the household, using `base_rate` as basic infectivity
        of person `i` (equivalent to `\mu` in model definition)
        """

        def valid_j():
            '''Generates indices j where `infector` is present
            at least `self.delta` hours before j '''
            for j in self.households[self.people_household[infector]]:
                if self.state['susc'][j]:
                    yield j

        # generate potential exposure event for `j` from contact with `infector`
        for j in valid_j():
            self.__push_household_exposure_infector_to_j(t=t, infector=infector, j=j, base_rate=base_rate, tmax=tmax)

    def __push_household_exposure_infector_to_j(self, *, t, infector, j, base_rate, tmax):
        """
        Pushes the next exposure event that person `infector` causes for person `j`,
        who lives in the same household, using `base_rate` as basic infectivity of 
        person `i` (equivalent to `\mu` in model definition)

        We ignore the kernel for households infections since households members
        will overlap for long periods of time at home
        """

        lambda_household = self.beta_household * base_rate * self.__kernel_term(- self.delta, 0.0, 0.0)
        tau = t + np.random.exponential(scale=1.0 / lambda_household)

        # site = -1 means it is a household infection
        # thinning is done at exposure time if needed
        if tau < min(tmax, self.max_time):
            self.queue.push((tau, 'expo', j, infector, -1, None), priority=tau)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09002274943126422, 'fraction_numerical': 0.005199870003249919, 'mean_word_length': 2.4358020096762187, 'pattern_counts': {'":': 0, '<': 24, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '192125', 'license': 'MIT', 'n_tokens_mistral': 2900, 'n_tokens_neox': 2719, 'n_words': 799, 'path': 'indirect_contacts.ipynb', 'repo_name': 'matcolgit/simulator_sib', 'size': '9231'}