예제 #1
0
    def test_simu_run(self):
        """
            run one simulation and ensure json files are correctly populated and most of the users have activity
        """
        with NamedTemporaryFile() as f:
            n_people = 100
            run_simu(
                n_stores=2,
                n_people=n_people,
                n_parks=1,
                n_misc=2,
                init_percent_sick=0.1,
                start_time=datetime.datetime(2020, 2, 28, 0, 0),
                simulation_days=30,
                outfile=f.name
            )
            f.seek(0)

            # Ensure
            with open(f"{f.name}.pkl", 'rb') as f_output:
                data = pickle.load(f_output)

                self.assertTrue(len(data) > 0)

                self.assertTrue(Event.encounter in {d['event_type'] for d in data})
                self.assertTrue(Event.test in {d['event_type'] for d in data})

                self.assertTrue(len({d['human_id'] for d in data}) > n_people / 2)
예제 #2
0
    def test_sim_diff_seed(self):
        """
        Using different seeds should yield different output
        """

        with NamedTemporaryFile() as f1, NamedTemporaryFile() as f2:
            monitors1, _ = run_simu(
                n_people=self.n_people,
                init_percent_sick=self.init_percent_sick,
                start_time=self.start_time,
                simulation_days=self.simulation_days,
                outfile=f1.name,
                out_chunk_size=0,
                seed=self.test_seed
            )
            monitors1[0].dump()
            monitors1[0].join_iothread()
            f1.seek(0)

            monitors2, _ = run_simu(
                n_people=self.n_people,
                init_percent_sick=self.init_percent_sick,
                start_time=self.start_time,
                simulation_days=self.simulation_days,
                outfile=f2.name,
                out_chunk_size=0,
                seed=self.test_seed+1
            )
            monitors2[0].dump()
            monitors2[0].join_iothread()
            f2.seek(0)

            md5 = hashlib.md5()
            with zipfile.ZipFile(f"{f1.name}.zip", 'r') as zf:
                for pkl in zf.namelist():
                    md5.update(zf.read(pkl))
            md5sum1 = md5.hexdigest()

            md5 = hashlib.md5()
            with zipfile.ZipFile(f"{f2.name}.zip", 'r') as zf:
                for pkl in zf.namelist():
                    md5.update(zf.read(pkl))
            md5sum2 = md5.hexdigest()

            self.assertFalse(md5sum1 == md5sum2,
                             msg=f"Two simulations run with different seeds "
                             f"({self.test_seed},{self.test_seed+1}) yielded "
                             f"the same result")
예제 #3
0
    def test_simu_run(self):
        """
            run one simulation and ensure json files are correctly populated and most of the users have activity
        """
        with NamedTemporaryFile() as f:
            n_people = 100
            monitors, _ = run_simu(
                n_people=n_people,
                init_percent_sick=0.1,
                start_time=datetime.datetime(2020, 2, 28, 0, 0),
                simulation_days=30,
                outfile=f.name,
                out_chunk_size=500
            )
            monitors[0].dump()
            monitors[0].join_iothread()
            f.seek(0)

            # Ensure
            data = []
            with zipfile.ZipFile(f"{f.name}.zip", 'r') as zf:
                for pkl in zf.namelist():
                    data.extend(pickle.loads(zf.read(pkl)))

            self.assertTrue(len(data) > 0)

            self.assertTrue(Event.encounter in {d['event_type'] for d in data})
            self.assertTrue(Event.test in {d['event_type'] for d in data})

            self.assertTrue(len({d['human_id'] for d in data}) > n_people / 2)
예제 #4
0
    def test_sim_same_seed(self):
        """
        Run two simulations with the same seed and ensure we get the same output
        Note: If this test is failing, it is a good idea to load the data of both files and use DeepDiff to compare
        """
        with NamedTemporaryFile() as f1, NamedTemporaryFile() as f2:
            monitors1, _ = run_simu(
                n_people=self.n_people,
                init_percent_sick=self.init_percent_sick,
                start_time=self.start_time,
                simulation_days=self.simulation_days,
                outfile=f1.name,
                out_chunk_size=0,
                seed=self.test_seed
            )
            monitors1[0].dump()
            monitors1[0].join_iothread()
            f1.seek(0)

            monitors2, _ = run_simu(
                n_people=self.n_people,
                init_percent_sick=self.init_percent_sick,
                start_time=self.start_time,
                simulation_days=self.simulation_days,
                outfile=f2.name,
                out_chunk_size=0,
                seed=self.test_seed
            )
            monitors2[0].dump()
            monitors2[0].join_iothread()
            f2.seek(0)

            md5 = hashlib.md5()
            with zipfile.ZipFile(f"{f1.name}.zip", 'r') as zf:
                for pkl in zf.namelist():
                    md5.update(zf.read(pkl))
            md5sum1 = md5.hexdigest()

            md5 = hashlib.md5()
            with zipfile.ZipFile(f"{f2.name}.zip", 'r') as zf:
                for pkl in zf.namelist():
                    md5.update(zf.read(pkl))
            md5sum2 = md5.hexdigest()

            self.assertTrue(md5sum1 == md5sum2,
                            msg=f"Two simulations run with the same seed "
                            f"({self.test_seed}) yielded different results")
예제 #5
0
    def test_run(self):
        """
            run one simulation and ensure json files are correctly populated and most of the users have activity
        """
        with TemporaryDirectory() as preprocess_d:
            n_people = 100
            n_days = 30
            monitors, _ = run_simu(
                n_people=n_people,
                init_percent_sick=0.25,
                start_time=datetime.datetime(2020, 2, 28, 0, 0),
                simulation_days=n_days,
                outfile=os.path.join(preprocess_d, "output"),
                out_chunk_size=1,
                seed=0,
                n_jobs=4,
                port=6688)

            days_output = glob.glob(f"{preprocess_d}/daily_outputs/*/")
            days_output.sort()

            self.assertEqual(len(days_output), n_days)

            output = [None] * len(days_output)
            for day_output in days_output:
                pkls = glob.glob(f"{day_output}*/daily_human.pkl")
                pkls.sort()
                day_humans = []
                for pkl in pkls:
                    with open(pkl, 'rb') as f:
                        day_human = pickle.load(f)
                        day_humans.append(day_human)
                        self.assertEqual(day_human['current_day'],
                                         day_humans[0]['current_day'])
                self.assertGreaterEqual(len(day_humans), n_people)
                output[day_humans[0]['current_day']] = day_humans

            for i in range(1, len(output)):
                self.assertEqual(len(output[i - 1]), len(output[i]))

            stats = {'human_enc_ids': [0] * 256, 'humans': {}}

            for current_day, day_output in enumerate(output):
                for h_i, human in enumerate(day_output):
                    stats['humans'].setdefault(h_i, {})
                    stats['humans'][h_i].setdefault('candidate_encounters_cnt',
                                                    0)
                    stats['humans'][h_i].setdefault('has_exposure_day', 0)
                    stats['humans'][h_i].setdefault('has_recovery_day', 0)
                    stats['humans'][h_i].setdefault('exposure_encounter_cnt',
                                                    0)
                    stats['humans'][h_i].setdefault('infectiousness', 0)

                    self.assertEqual(current_day, human['current_day'])

                    observed = human['observed']
                    unobserved = human['unobserved']

                    if current_day == 0:
                        prev_observed = None
                        prev_unobserved = None
                    else:
                        prev_observed = output[current_day -
                                               1][h_i]['observed']
                        prev_unobserved = output[current_day -
                                                 1][h_i]['unobserved']

                    # Multi-hot arrays identifying the reported symptoms in the last 14 days
                    # Symptoms:
                    # ['aches', 'cough', 'fatigue', 'fever', 'gastro', 'loss_of_taste',
                    #  'mild', 'moderate', 'runny_nose', 'severe', 'trouble_breathing']
                    self.assertEqual(observed['reported_symptoms'].shape,
                                     (14, 28))
                    if observed['candidate_encounters'].size:
                        stats['humans'][h_i]['candidate_encounters_cnt'] += 1
                        # candidate_encounters[:, 0] is the other human's signature id
                        # candidate_encounters[:, 1] is the 4 bits new risk of getting contaminated during the encounter
                        # candidate_encounters[:, 2] is the length of the encounter
                        # candidate_encounters[:, 3] is the number of days since the encounter
                        self.assertEqual(
                            observed['candidate_encounters'].shape[1], 4)
                        self.assertGreaterEqual(
                            observed['candidate_encounters'][:, 0].min(), 0)
                        self.assertLess(
                            observed['candidate_encounters'][:, 0].max(), 256)
                        self.assertGreaterEqual(
                            observed['candidate_encounters'][:, 1].min(), 0)
                        self.assertLess(
                            observed['candidate_encounters'][:, 1].max(), 16)
                        self.assertGreaterEqual(
                            observed['candidate_encounters'][:, 2].min(), 0)
                        self.assertLess(
                            observed['candidate_encounters'][:, 2].max(),
                            10000)
                        self.assertLessEqual(
                            observed['candidate_encounters'][:, 3].max(),
                            current_day)
                        # # TODO: Expecting only the last 14 days
                        # self.assertLess(observed['candidate_encounters'][:, 3].max() -
                        #                 observed['candidate_encounters'][:, 3].min(), 14)
                        # TODO: Expecting the messages to be ordered by day and to be
                        #  in reverse chronological order
                        # self.assertLessEqual(observed['candidate_encounters'][0, 3],
                        #                      observed['candidate_encounters'][-1, 3])

                        for h_enc_id in observed['candidate_encounters'][:, 0]:
                            stats['human_enc_ids'][h_enc_id] += 1

                    # Has received a positive test result [index] days before today
                    self.assertEqual(observed['test_results'].shape, (14, ))
                    self.assertIn(observed['test_results'].min(), (0, 1))
                    self.assertIn(observed['test_results'].max(), (0, 1))
                    self.assertIn(observed['test_results'].sum(), (0, 1))

                    # Multihot encoding
                    self.assertIn(observed['preexisting_conditions'].min(),
                                  (0, 1))
                    self.assertIn(observed['preexisting_conditions'].max(),
                                  (0, 1))
                    self.assertGreaterEqual(observed['age'], -1)
                    self.assertGreaterEqual(observed['sex'], -1)

                    # Multi-hot arrays identifying the true symptoms in the last 14 days
                    # Symptoms:
                    # ['aches', 'cough', 'fatigue', 'fever', 'gastro', 'loss_of_taste',
                    #  'mild', 'moderate', 'runny_nose', 'severe', 'trouble_breathing']
                    self.assertEqual(unobserved['true_symptoms'].shape,
                                     (14, 28))
                    # Has been exposed or not
                    self.assertIn(unobserved['is_exposed'], (0, 1))
                    if unobserved['exposure_day'] is not None:
                        stats['humans'][h_i]['has_exposure_day'] = 1
                        # For how long has been exposed
                        self.assertTrue(0 <= unobserved['exposure_day'] < 14)
                    # Is recovered or not
                    self.assertIn(unobserved['is_recovered'], (0, 1))
                    if unobserved['recovery_day'] is not None:
                        stats['humans'][h_i]['has_recovery_day'] = 1
                        # For how long has been infectious
                        self.assertTrue(0 <= unobserved['recovery_day'] < 14)
                    if observed['candidate_encounters'].size:
                        stats['humans'][h_i]['exposure_encounter_cnt'] += 1
                        # Encounters responsible for exposition. Exposition can occur without being
                        # linked to an encounter
                        self.assertTrue(
                            len(unobserved['exposure_encounter'].shape) == 1)
                        self.assertTrue(
                            unobserved['exposure_encounter'].min() in (0, 1))
                        self.assertTrue(
                            unobserved['exposure_encounter'].max() in (0, 1))
                        self.assertTrue(
                            unobserved['exposure_encounter'].sum() in (0, 1))
                    if unobserved['infectiousness'].size:
                        stats['humans'][h_i]['infectiousness'] += 1
                        # Level of infectiousness / day
                        self.assertLessEqual(
                            unobserved['infectiousness'].shape[0], 14)
                        self.assertGreaterEqual(
                            unobserved['infectiousness'].min(), 0)
                        self.assertLessEqual(
                            unobserved['infectiousness'].max(), 10)

                    # Multihot encoding
                    self.assertIn(
                        unobserved['true_preexisting_conditions'].min(),
                        (0, 1))
                    self.assertIn(
                        unobserved['true_preexisting_conditions'].max(),
                        (0, 1))
                    self.assertGreaterEqual(unobserved['true_age'], -1)
                    self.assertGreaterEqual(unobserved['true_sex'], -1)

                    # observed['reported_symptoms'] is a subset of unobserved['true_symptoms']
                    # TODO: Test fails with some values of observed['reported_symptoms'] not being in
                    #  unobserved['true_symptoms'].
                    # self.assertTrue((unobserved['true_symptoms'] == observed['reported_symptoms'])
                    #                 [observed['reported_symptoms'].astype(np.bool)].all())

                    if unobserved['is_exposed'] or unobserved['is_recovered']:
                        self.assertNotEqual(unobserved['is_exposed'],
                                            unobserved['is_recovered'])

                    if observed['candidate_encounters'].size:
                        # exposure_encounter is the same length as candidate_encounters
                        self.assertEqual(
                            unobserved['exposure_encounter'].shape,
                            (observed['candidate_encounters'].shape[0], ))

                    # observed['preexisting_conditions'] is a subset of unobserved['true_preexisting_conditions']
                    self.assertTrue(
                        (unobserved['true_preexisting_conditions'] ==
                         observed['preexisting_conditions']
                         )[observed['preexisting_conditions'].astype(
                             np.bool)].all())
                    # If observed['age'] is set, unobserved['true_age'] should also be set to the same value
                    self.assertGreaterEqual(unobserved['true_age'],
                                            observed['age'])
                    # If observed['sex'] is set, unobserved['true_sex'] should also be set to the same value
                    self.assertGreaterEqual(unobserved['true_sex'],
                                            observed['sex'])

                    if prev_observed:
                        # TODO: Test fails with values updated in observed['reported_symptoms'][1].
                        #  Expecting only the first row to be updated
                        # self.assertTrue((observed['reported_symptoms'][1:] == prev_observed['reported_symptoms'][:13]).all())
                        if observed[
                                'candidate_encounters'].size and prev_observed[
                                    'candidate_encounters'].size:
                            # TODO: Can't validate rolling of the message because of new messages being added
                            current_day_mask = observed[
                                'candidate_encounters'][:,
                                                        3] < current_day  # Get the last 13 days excluding today
                            prev_day_mask = prev_observed[
                                'candidate_encounters'][:,
                                                        3] > current_day - 14  # Get the last 13 days including relative today (of yesterday)
                            masked = observed['candidate_encounters'][
                                current_day_mask][:, (0, 1, 3)]
                            prev_masked = prev_observed[
                                'candidate_encounters'][prev_day_mask][:,
                                                                       (0, 1,
                                                                        3)]
                            offset = 0
                            for i in range(prev_masked.shape[0]):
                                for j in range(i + offset, masked.shape[0]):
                                    if (prev_masked[i] == masked[j]).all():
                                        break
                                    # Skipping updated message
                                    offset += 1
                                else:
                                    self.assertFalse(
                                        False,
                                        msg=
                                        f"Could not find previous candidate_encounter {prev_masked[i]} "
                                        f"in current day.")
                        # TODO: Test fails with observed['test_results'] and prev_observed['test_results'] being the same array
                        # self.assertTrue((observed['test_results'][1:] == prev_observed['test_results'][:13]).all())

                        self.assertTrue(
                            (observed['preexisting_conditions'] ==
                             prev_observed['preexisting_conditions']).all())
                        self.assertEqual(observed['age'], prev_observed['age'])
                        self.assertEqual(observed['sex'], prev_observed['sex'])

                        # TODO: Test fails with values updated in unobserved['true_symptoms'][1].
                        #  Expecting only the first row to be updated
                        # self.assertTrue((unobserved['true_symptoms'][1:] == prev_unobserved['true_symptoms'][:13]).all())
                        # TODO: Test fails with unobserved['infectiousness'] being the same as prev_unobserved['infectiousness']
                        #  (appears to sometimes be not updated)
                        check = unobserved['infectiousness'][
                            1:] == prev_unobserved['infectiousness'][:13]
                        self.assertTrue(
                            check if isinstance(check, bool) else check.all())

                        if prev_unobserved['is_exposed'] and prev_unobserved[
                                'exposure_day'] < 13:
                            self.assertTrue(unobserved['is_exposed'])
                            self.assertEqual(
                                max(0, unobserved['exposure_day'] - 1),
                                prev_unobserved['exposure_day'])

                        if unobserved['is_exposed'] != prev_unobserved[
                                'is_exposed']:
                            if unobserved['is_exposed']:
                                # TODO: Test fails. Expecting the exposure day to be 0 since the human
                                #  just got exposed
                                # self.assertEqual(unobserved['exposure_day'], 0)
                                self.assertEqual(
                                    prev_unobserved['infectiousness'][0], 0)
                            else:
                                self.assertEqual(
                                    prev_unobserved['exposure_day'], 13)
                                self.assertFalse(unobserved['is_exposed'])
                                self.assertEqual(unobserved['exposure_day'],
                                                 None)

                        self.assertTrue(
                            (unobserved['true_preexisting_conditions'] ==
                             prev_unobserved['true_preexisting_conditions']
                             ).all())
                        self.assertEqual(unobserved['true_age'],
                                         prev_unobserved['true_age'])
                        self.assertEqual(unobserved['true_sex'],
                                         prev_unobserved['true_sex'])

            candidate_encounters_cnt = 0
            has_exposure_day = 0
            has_recovery_day = 0
            exposure_encounter_cnt = 0
            infectiousness = 0
            for _, human_stats in stats['humans'].items():
                candidate_encounters_cnt += human_stats[
                    'candidate_encounters_cnt']
                has_exposure_day += human_stats['has_exposure_day']
                has_recovery_day += human_stats['has_recovery_day']
                exposure_encounter_cnt += human_stats['exposure_encounter_cnt']
                infectiousness += human_stats['infectiousness']

            self.assertGreaterEqual(sum(stats['human_enc_ids']), n_people)
            self.assertGreaterEqual(candidate_encounters_cnt, n_people)
            self.assertGreaterEqual(has_exposure_day, n_people * 0.5)
            # TODO: Is it expected to have no recovery_days?
            # self.assertGreaterEqual(has_recovery_day, n_people)
            self.assertGreaterEqual(exposure_encounter_cnt, n_people)
            self.assertGreaterEqual(infectiousness, n_people)
예제 #6
0
    def test_run(self):
        """
            run one simulation and ensure json files are correctly populated and most of the users have activity
        """
        with NamedTemporaryFile(suffix='.zip') as logs_f, \
             TemporaryDirectory() as preprocess_d:
            n_people = 35
            monitors, _ = run_simu(
                n_people=n_people,
                init_percent_sick=0.25,
                start_time=datetime.datetime(2020, 2, 28, 0, 0),
                simulation_days=30,
                outfile=logs_f.name[:-len('.zip')],
                out_chunk_size=0,
                seed=0
            )
            monitors[0].dump()
            monitors[0].join_iothread()

            args = m_parser.parse_args([f'--data_path={logs_f.name}',
                                        f'--output_dir={preprocess_d}/',
                                        '--risk_model=tristan',
                                        '--seed=0', '--save_training_data',
                                        '--n_jobs=4'])
            m_main(args)

            preprocess_d = '/Users/satya/travail/MILA/CODE/covid_p2p_simulation/output/tmpn_fpii1d'

            days_output = glob.glob(f"{preprocess_d}/daily_outputs/*/")
            days_output.sort()

            output = []
            for day_output in days_output:
                pkls = glob.glob(f"{day_output}*/daily_human.pkl")
                pkls.sort()
                day_humans = []
                for pkl in pkls:
                    with open(pkl, 'rb') as f:
                        day_humans.append(pickle.load(f))
                output.append(day_humans)

            stats = {'humans': {}}

            for current_day, day_output in enumerate(output):
                for h_i, human in enumerate(day_output):
                    stats['humans'].setdefault(h_i, {})
                    stats['humans'][h_i].setdefault('candidate_encounters_cnt', 0)
                    stats['humans'][h_i].setdefault('has_exposure_day', 0)
                    stats['humans'][h_i].setdefault('has_infectious_day', 0)
                    stats['humans'][h_i].setdefault('has_recovery_day', 0)
                    stats['humans'][h_i].setdefault('exposure_encounter_cnt', 0)

                    self.assertEqual(current_day, human['current_day'])

                    observed = human['observed']
                    unobserved = human['unobserved']

                    if current_day == 0:
                        prev_observed = None
                        prev_unobserved = None
                    else:
                        prev_observed = output[current_day - 1][h_i]['observed']
                        prev_unobserved = output[current_day - 1][h_i]['unobserved']

                    # Multi-hot arrays identifying the reported symptoms in the last 14 days
                    # Symptoms:
                    # ['aches', 'cough', 'fatigue', 'fever', 'gastro', 'loss_of_taste',
                    #  'mild', 'moderate', 'runny_nose', 'severe', 'trouble_breathing']
                    self.assertEqual(observed['reported_symptoms'].shape, (14, 12))
                    if len(observed['candidate_encounters']):
                        stats['humans'][h_i]['candidate_encounters_cnt'] += 1
                        # candidate_encounters[:, 0] is the other human 4 bits id
                        # candidate_encounters[:, 1] is the risk of getting contaminated during the encounter?
                        # candidate_encounters[:, 2] is the number of days since the encounter
                        self.assertEqual(observed['candidate_encounters'].shape[1], 3)
                        self.assertGreaterEqual(observed['candidate_encounters'][:, 0].min(), 0)
                        self.assertLess(observed['candidate_encounters'][:, 0].max(), 16)
                        self.assertGreaterEqual(observed['candidate_encounters'][:, 1].min(), 0)
                        self.assertLess(observed['candidate_encounters'][:, 1].max(), 16)
                        self.assertGreaterEqual(observed['candidate_encounters'][:, 2].min(), 0)
                        self.assertLess(observed['candidate_encounters'][:, 2].max(), 14)
                    # Has received a positive test result [index] days before today
                    self.assertEqual(observed['test_results'].shape, (14,))
                    self.assertTrue(observed['test_results'].min() in (0, 1))
                    self.assertTrue(observed['test_results'].max() in (0, 1))
                    self.assertTrue(observed['test_results'].sum() in (0, 1))

                    # Multi-hot arrays identifying the true symptoms in the last 14 days
                    # Symptoms:
                    # ['aches', 'cough', 'fatigue', 'fever', 'gastro', 'loss_of_taste',
                    #  'mild', 'moderate', 'runny_nose', 'severe', 'trouble_breathing']
                    self.assertTrue(unobserved['true_symptoms'].shape == (14, 12))
                    # Has been exposed or not
                    self.assertTrue(unobserved['is_exposed'] in (0, 1))
                    if unobserved['exposure_day'] is not None:
                        stats['humans'][h_i]['has_exposure_day'] = 1
                        # For how long has been exposed
                        self.assertTrue(0 <= unobserved['exposure_day'] < 14)
                    # Is infectious or not
                    self.assertTrue(unobserved['is_infectious'] in (0, 1))
                    if unobserved['infectious_day'] is not None:
                        stats['humans'][h_i]['has_infectious_day'] = 1
                        # For how long has been infectious
                        self.assertTrue(0 <= unobserved['infectious_day'] < 14)
                    # Is recovered or not
                    self.assertTrue(unobserved['is_recovered'] in (0, 1))
                    if unobserved['infectious_day'] is not None:
                        stats['humans'][h_i]['has_recovery_day'] = 1
                        # For how long has been infectious
                        self.assertTrue(0 <= unobserved['recovery_day'] < 14)
                    if len(observed['candidate_encounters']):
                        stats['humans'][h_i]['exposure_encounter_cnt'] += 1
                        # Encounters responsible for exposition. Exposition can occur without being
                        # linked to an encounter
                        self.assertTrue(len(unobserved['exposure_encounter'].shape) == 1)
                        self.assertTrue(unobserved['exposure_encounter'].min() in (0, 1))
                        self.assertTrue(unobserved['exposure_encounter'].max() in (0, 1))
                        self.assertTrue(unobserved['exposure_encounter'].sum() in (0, 1))
                    # Level of infectiousness / day
                    self.assertTrue(unobserved['infectiousness'].shape == (14,))
                    self.assertTrue(unobserved['infectiousness'].min() >= 0)
                    self.assertTrue(unobserved['infectiousness'].max() <= 1)

                    # observed['reported_symptoms'] is a subset of unobserved['true_symptoms']
                    self.assertTrue((unobserved['true_symptoms'] == observed['reported_symptoms'])
                                    [observed['reported_symptoms'].astype(np.bool)].all())

                    if unobserved['is_infectious'] or unobserved['is_recovered']:
                        self.assertTrue(unobserved['is_infectious'] != unobserved['is_recovered'])

                    # exposure_encounter is the same length as candidate_encounters
                    self.assertTrue(unobserved['exposure_encounter'].shape == (observed['candidate_encounters'].shape[0],))

                    if prev_observed:
                        self.assertTrue((observed['reported_symptoms'][:13, :] == prev_observed['reported_symptoms'][-13:, :]).all())
                        self.assertTrue((observed['candidate_encounters'][observed['candidate_encounters'][:, 2] > 1][:, 0:2] ==
                                         prev_observed['candidate_encounters'][prev_observed['candidate_encounters'][:, 2] < 13][:, 0:2]).all())
                        self.assertTrue((observed['test_results'][:13, :] == prev_observed['test_results'][-13:, :]).all())

                        self.assertTrue((unobserved['true_symptoms'][:13, :] == prev_unobserved['true_symptoms'][-13:, :]).all())
                        self.assertTrue(unobserved['is_exposed'] if prev_unobserved['is_exposed'] else True)
                        self.assertTrue((unobserved['infectiousness'][:13, :] == prev_unobserved['infectiousness'][-13:, :]).all())

                        self.assertTrue(min(0, unobserved['exposure_day'] + 1) == prev_unobserved['exposure_day'])

                        if unobserved['is_exposed'] != prev_unobserved['is_exposed']:
                            self.assertTrue(unobserved['is_exposed'])
                            self.assertTrue(unobserved['exposure_day'] == 0)
                            self.assertTrue(prev_unobserved['infectiousness'][0] == 0)
    def test_run(self):
        """
            run one simulation and ensure json files are correctly populated and most of the users have activity
        """
        with NamedTemporaryFile(suffix='.zip') as logs_f, \
             TemporaryDirectory() as preprocess_d:
            n_people = 35
            n_days = 30
            monitors, _ = run_simu(n_people=n_people,
                                   init_percent_sick=0.25,
                                   start_time=datetime.datetime(
                                       2020, 2, 28, 0, 0),
                                   simulation_days=n_days,
                                   outfile=logs_f.name[:-len('.zip')],
                                   out_chunk_size=0,
                                   seed=0,
                                   n_jobs=4)

            days_output = glob.glob(f"{preprocess_d}/daily_outputs/*/")
            days_output.sort()

            # self.assertEqual(len(days_output), n_days)

            output = [None] * len(days_output)
            for day_output in days_output:
                pkls = glob.glob(f"{day_output}*/daily_human.pkl")
                pkls.sort()
                day_humans = []
                for pkl in pkls:
                    with open(pkl, 'rb') as f:
                        day_humans.append(pickle.load(f))
                self.assertGreaterEqual(len(day_humans), n_people)
                output[day_humans[0]['current_day']] = day_humans

            for i in range(1, len(output)):
                self.assertEqual(len(output[i - 1]), len(output[i]))

            stats = {'human_enc_ids': [0] * 256, 'humans': {}}

            for current_day, day_output in enumerate(output):
                for h_i, human in enumerate(day_output):
                    stats['humans'].setdefault(h_i, {})
                    stats['humans'][h_i].setdefault('candidate_encounters_cnt',
                                                    0)
                    stats['humans'][h_i].setdefault('updated_encounters_cnt',
                                                    0)
                    stats['humans'][h_i].setdefault('has_exposure_day', 0)
                    stats['humans'][h_i].setdefault('has_infectious_day', 0)
                    stats['humans'][h_i].setdefault('has_recovery_day', 0)
                    stats['humans'][h_i].setdefault('exposure_encounter_cnt',
                                                    0)

                    self.assertEqual(current_day, human['current_day'])

                    observed = human['observed']
                    unobserved = human['unobserved']

                    if current_day == 0:
                        prev_observed = None
                        prev_unobserved = None
                    else:
                        prev_observed = output[current_day -
                                               1][h_i]['observed']
                        prev_unobserved = output[current_day -
                                                 1][h_i]['unobserved']

                    # Multi-hot arrays identifying the reported symptoms in the last 14 days
                    # Symptoms:
                    # ['aches', 'cough', 'fatigue', 'fever', 'gastro', 'loss_of_taste',
                    #  'mild', 'moderate', 'runny_nose', 'severe', 'trouble_breathing']
                    self.assertEqual(observed['reported_symptoms'].shape,
                                     (14, 26))
                    if observed['candidate_encounters'].size:
                        stats['humans'][h_i]['candidate_encounters_cnt'] += 1
                        stats['humans'][h_i]['updated_encounters_cnt'] += (
                            observed['candidate_encounters'][:, 1] !=
                            observed['candidate_encounters'][:, 2]).sum()
                        # candidate_encounters[:, 0] is the other human 8 bits id
                        # candidate_encounters[:, 1] is the 4 bits new risk of getting contaminated during the encounter
                        # candidate_encounters[:, 2] is the 4 bits risk of getting contaminated during the encounter
                        # candidate_encounters[:, 3] is the number of days since the encounter
                        self.assertEqual(
                            observed['candidate_encounters'].shape[1], 4)
                        self.assertGreaterEqual(
                            observed['candidate_encounters'][:, 0].min(), 0)
                        self.assertLess(
                            observed['candidate_encounters'][:, 0].max(), 256)
                        self.assertLess(
                            observed['candidate_encounters'][:, 1].max(), 16)
                        self.assertGreaterEqual(
                            observed['candidate_encounters'][:, 1].min(), 0)
                        self.assertLess(
                            observed['candidate_encounters'][:, 2].max(), 16)
                        self.assertGreaterEqual(
                            observed['candidate_encounters'][:, 2].min(), 0)
                        self.assertLess(
                            observed['candidate_encounters'][:, 3].max() -
                            observed['candidate_encounters'][:, 3].min(), 14)

                        for h_enc_id in observed['candidate_encounters'][:, 0]:
                            stats['human_enc_ids'][h_enc_id] += 1

                    # Has received a positive test result [index] days before today
                    self.assertEqual(observed['test_results'].shape, (14, ))
                    self.assertTrue(observed['test_results'].min() in (0, 1))
                    self.assertTrue(observed['test_results'].max() in (0, 1))
                    self.assertTrue(observed['test_results'].sum() in (0, 1))

                    # Multihot encoding
                    self.assertTrue(
                        observed['preexisting_conditions'].min() in (0, 1))
                    self.assertTrue(
                        observed['preexisting_conditions'].max() in (0, 1))
                    self.assertGreaterEqual(observed['age'], -1)
                    self.assertGreaterEqual(observed['sex'], -1)

                    # Multi-hot arrays identifying the true symptoms in the last 14 days
                    # Symptoms:
                    # ['aches', 'cough', 'fatigue', 'fever', 'gastro', 'loss_of_taste',
                    #  'mild', 'moderate', 'runny_nose', 'severe', 'trouble_breathing']
                    self.assertTrue(unobserved['true_symptoms'].shape == (14,
                                                                          26))
                    # Has been exposed or not
                    self.assertTrue(unobserved['is_exposed'] in (0, 1))
                    if unobserved['exposure_day'] is not None:
                        stats['humans'][h_i]['has_exposure_day'] = 1
                        # For how long has been exposed
                        self.assertTrue(0 <= unobserved['exposure_day'] < 14)
                    # Is infectious or not
                    self.assertTrue(unobserved['is_infectious'] in (0, 1))
                    if unobserved['infectious_day'] is not None:
                        stats['humans'][h_i]['has_infectious_day'] = 1
                        # For how long has been infectious
                        self.assertTrue(0 <= unobserved['infectious_day'] < 14)
                    # Is recovered or not
                    self.assertTrue(unobserved['is_recovered'] in (0, 1))
                    if unobserved['recovery_day'] is not None:
                        stats['humans'][h_i]['has_recovery_day'] = 1
                        # For how long has been infectious
                        self.assertTrue(0 <= unobserved['recovery_day'] < 14)
                    if observed['candidate_encounters'].size:
                        stats['humans'][h_i]['exposure_encounter_cnt'] += 1
                        # Encounters responsible for exposition. Exposition can occur without being
                        # linked to an encounter
                        self.assertTrue(
                            len(unobserved['exposure_encounter'].shape) == 1)
                        self.assertTrue(
                            unobserved['exposure_encounter'].min() in (0, 1))
                        self.assertTrue(
                            unobserved['exposure_encounter'].max() in (0, 1))
                        self.assertTrue(
                            unobserved['exposure_encounter'].sum() in (0, 1))
                    # Level of infectiousness / day
                    self.assertTrue(
                        unobserved['infectiousness'].shape == (14, ))
                    self.assertTrue(unobserved['infectiousness'].min() >= 0)
                    # TODO: This test fails. Is it expected to have infectiousness higher than 1?
                    # self.assertTrue(unobserved['infectiousness'].max() <= 1)

                    # Multihot encoding
                    self.assertTrue(
                        unobserved['true_preexisting_conditions'].min() in (0,
                                                                            1))
                    self.assertTrue(
                        unobserved['true_preexisting_conditions'].max() in (0,
                                                                            1))
                    self.assertGreaterEqual(unobserved['true_age'], -1)
                    self.assertGreaterEqual(unobserved['true_sex'], -1)

                    # observed['reported_symptoms'] is a subset of unobserved['true_symptoms']
                    self.assertTrue((unobserved['true_symptoms'] ==
                                     observed['reported_symptoms']
                                     )[observed['reported_symptoms'].astype(
                                         np.bool)].all())

                    # TODO: Both unobserved['is_infectious'] and unobserved['is_recovered'] can apparently be True. Is this a bug
                    if (unobserved['is_infectious'] or unobserved['is_recovered']) \
                       and (not unobserved['is_infectious'] or not unobserved['is_recovered']):
                        self.assertTrue(unobserved['is_infectious'] !=
                                        unobserved['is_recovered'])

                    if observed['candidate_encounters'].size:
                        # exposure_encounter is the same length as candidate_encounters
                        self.assertTrue(
                            unobserved['exposure_encounter'].shape == (
                                observed['candidate_encounters'].shape[0], ))

                    # observed['preexisting_conditions'] is a subset of unobserved['true_preexisting_conditions']
                    self.assertTrue(
                        (unobserved['true_preexisting_conditions'] ==
                         observed['preexisting_conditions']
                         )[observed['preexisting_conditions'].astype(
                             np.bool)].all())
                    # If observed['age'] is set, unobserved['true_age'] should also be set to the same value
                    self.assertGreaterEqual(unobserved['true_age'],
                                            observed['age'])
                    # If observed['sex'] is set, unobserved['true_sex'] should also be set to the same value
                    self.assertGreaterEqual(unobserved['true_sex'],
                                            observed['sex'])

                    if prev_observed:
                        self.assertTrue(
                            (observed['reported_symptoms'][1:] ==
                             prev_observed['reported_symptoms'][:13]).all())
                        if observed[
                                'candidate_encounters'].size and prev_observed[
                                    'candidate_encounters'].size:
                            # TODO: Can't validate rolling of the message because of the update messages moving around
                            # current_day_mask = observed['candidate_encounters'][:, 3] < current_day  # Get the last 13 days excluding today
                            # prev_day_mask = prev_observed['candidate_encounters'][:, 3] > current_day - 14  # Get the last 13 days including relative today (of yesterday)
                            # check = (observed['candidate_encounters'][current_day_mask][:, (0, 2, 3)] ==
                            #          prev_observed['candidate_encounters'][prev_day_mask][:, (0, 2, 3)])
                            #
                            # mask = ~(~current_day_mask + (observed['candidate_encounters'][:, 1] != 0))  # Exclude update message
                            # prev_mask = ~(~prev_day_mask + (prev_observed['candidate_encounters'][:, 1] != 0))  # Exclude update message
                            # check_no_update_message = observed['candidate_encounters'][mask] == \
                            #                           prev_observed['candidate_encounters'][prev_mask]
                            # self.assertTrue((check if isinstance(check, bool) else check.all()) or
                            #                 (check_no_update_message if isinstance(check_no_update_message, bool)
                            #                  else check_no_update_message.all()))
                            pass
                        self.assertTrue(
                            (observed['test_results'][1:] ==
                             prev_observed['test_results'][:13]).all())

                        self.assertTrue(
                            (observed['preexisting_conditions'] ==
                             prev_observed['preexisting_conditions']).all())
                        self.assertEqual(observed['age'], prev_observed['age'])
                        self.assertEqual(observed['sex'], prev_observed['sex'])

                        self.assertTrue(
                            (unobserved['true_symptoms'][1:] ==
                             prev_unobserved['true_symptoms'][:13]).all())
                        self.assertTrue(
                            unobserved['is_exposed']
                            if prev_unobserved['is_exposed'] else True)
                        self.assertTrue(
                            (unobserved['infectiousness'][1:] ==
                             prev_unobserved['infectiousness'][:13]).all())

                        if prev_unobserved['is_exposed']:
                            self.assertTrue(
                                min(0, unobserved['exposure_day'] +
                                    1) == prev_unobserved['exposure_day'])

                        if unobserved['is_exposed'] != prev_unobserved[
                                'is_exposed']:
                            self.assertTrue(unobserved['is_exposed'])
                            self.assertTrue(unobserved['exposure_day'] == 0)
                            self.assertTrue(
                                prev_unobserved['infectiousness'][0] == 0)

                        self.assertTrue(
                            (unobserved['true_preexisting_conditions'] ==
                             prev_unobserved['true_preexisting_conditions']
                             ).all())
                        self.assertEqual(unobserved['true_age'],
                                         prev_unobserved['true_age'])
                        self.assertEqual(unobserved['true_sex'],
                                         prev_unobserved['true_sex'])

            print(stats)