diff --git a/pyEpiabm/pyEpiabm/core/person.py b/pyEpiabm/pyEpiabm/core/person.py index d1c8edff2..8d128e794 100644 --- a/pyEpiabm/pyEpiabm/core/person.py +++ b/pyEpiabm/pyEpiabm/core/person.py @@ -54,6 +54,8 @@ def __init__(self, microcell, age_group=None): self.secondary_infections_counts = [] self.time_of_recovery = None self.num_times_infected = 0 + self.exposure_period = None + self.serial_interval_dict = {} self.care_home_resident = False self.key_worker = False self.date_positive = None @@ -283,10 +285,56 @@ def increment_secondary_infections(self): """Increments the number of secondary infections the given person has for this specific infection period (i.e. if the given person has been infected multiple times, then we only increment the current secondary - infection count) + infection count). """ try: self.secondary_infections_counts[-1] += 1 except IndexError: raise RuntimeError("Cannot call increment_secondary_infections " "while secondary_infections_counts is empty") + + def set_exposure_period(self, exposure_period: float): + """Sets the exposure period (we define here as the time between a + primary case infection and a secondary case exposure, with the current + `Person` being the secondary case). We store this to be added to the + latent period of the infection to give a serial interval. + + Parameters + ---------- + exposure_period : float + The time between the infector's time of infection and the time + of exposure to the current Person + """ + self.exposure_period = exposure_period + + def store_serial_interval(self, latent_period: float): + """Adds this `latent_period` to the current `exposure_period` to give + a `serial_interval`, which will be stored in the + `serial_interval_dict`. The serial interval is the time between a + primary case infection and a secondary case infection. This method + is called immediately after a person becomes exposed. + + Parameters + ---------- + latent_period : float + The period between this `Person`'s time of exposure and this + `Person`'s time of infection + """ + # This method has been called erroneously if the exposure period is + # None + if self.exposure_period is None: + raise RuntimeError("Cannot call store_serial_interval while the" + " exposure_period is None") + + serial_interval = self.exposure_period + latent_period + # The reference day is the day the primary case was first infected + # This is what we will store in the dictionary + reference_day = self.time_of_status_change - serial_interval + try: + (self.serial_interval_dict[reference_day] + .append(serial_interval)) + except KeyError: + self.serial_interval_dict[reference_day] = [serial_interval] + + # Reset the exposure period for the next infection + self.exposure_period = None diff --git a/pyEpiabm/pyEpiabm/routine/simulation.py b/pyEpiabm/pyEpiabm/routine/simulation.py index df7253860..dbdf1bbb1 100644 --- a/pyEpiabm/pyEpiabm/routine/simulation.py +++ b/pyEpiabm/pyEpiabm/routine/simulation.py @@ -65,6 +65,8 @@ def configure(self, a csv file containing infectiousness (viral load) values * `secondary_infections_output`: Boolean to determine whether we \ need a csv file containing secondary infections and R_t values + * `serial_interval_output`: Boolean to determine whether we \ + need a csv file containing serial interval data * `compress`: Boolean to determine whether we compress \ the infection history csv files @@ -87,12 +89,12 @@ def configure(self, inf_history_params : dict This is short for 'infection history file parameters' and we will use the abbreviation 'ih' to refer to infection history throughout - this class. If `status_output`, `infectiousness_output` and - `secondary_infections_output` are all False, then no infection - history csv files are produced (or if the dictionary is None). - These files contain the infection status, infectiousness and - secondary infection counts of each person every time step - respectively. The EpiOS tool + this class. If `status_output`, `infectiousness_output`, + `secondary_infections_output` and `serial_interval_output` are all + False, then no infection history csv files are produced (or if the + dictionary is None). These files contain the infection status, + infectiousness and secondary infection counts of each person every + time step respectively. The EpiOS tool (https://github.com/SABS-R3-Epidemiology/EpiOS) samples data from these files to mimic real life epidemic sampling techniques. These files can be compressed when 'compress' is True, reducing the size @@ -154,9 +156,11 @@ def configure(self, self.status_output = False self.infectiousness_output = False self.secondary_infections_output = False + self.serial_interval_output = False self.ih_status_writer = None self.ih_infectiousness_writer = None self.secondary_infections_writer = None + self.serial_interval_writer = None self.compress = False if inf_history_params: @@ -168,19 +172,28 @@ def configure(self, .get("infectiousness_output") self.secondary_infections_output = inf_history_params\ .get("secondary_infections_output") + self.serial_interval_output = inf_history_params \ + .get("serial_interval_output") self.compress = inf_history_params.get("compress", False) person_ids = [] person_ids += [person.id for cell in population.cells for person in cell.persons] self.ih_output_titles = ["time"] + person_ids self.Rt_output_titles = ["time"] + person_ids + ["R_t"] + ts = 1 / Parameters.instance().time_steps_per_day + times = np.arange(self.sim_params["simulation_start_time"], + self.sim_params["simulation_end_time"] + ts, + ts).tolist() + self.si_output_titles = times ih_folder = os.path.join(os.getcwd(), inf_history_params["output_dir"]) if not (self.status_output or self.infectiousness_output - or self.secondary_infections_output): - logging.warning("status_output, infectiousness_output and " - + "secondary_infections_output are False. " + or self.secondary_infections_output + or self.serial_interval_output): + logging.warning("status_output, infectiousness_output, " + + "secondary_infections_output and " + + "serial_interval_output are False. " + "No infection history csvs will be created.") if self.status_output: @@ -219,6 +232,18 @@ def configure(self, self.Rt_output_titles ) + if self.serial_interval_output: + + file_name = "serial_intervals.csv" + logging.info( + f"Set serial interval location to " + f"{os.path.join(ih_folder, file_name)}") + + self.serial_interval_writer = _CsvDictWriter( + ih_folder, file_name, + self.si_output_titles + ) + @log_exceptions() def run_sweeps(self): """Iteration step of the simulation. First the initialisation sweeps @@ -264,6 +289,8 @@ def run_sweeps(self): logging.info(f"Final time {t} days reached") if self.secondary_infections_writer: self.write_to_Rt_file(times) + if self.serial_interval_writer: + self.write_to_serial_interval_file(times) def write_to_file(self, time): """Records the count number of a given list of infection statuses @@ -404,6 +431,46 @@ def write_to_Rt_file(self, times: np.array): # Write each time step in dictionary form self.secondary_infections_writer.write(dict_row) + def write_to_serial_interval_file(self, times: np.array): + """Records the intervals between an infector and an infectee getting + infected to provide an overall serial interval for each time-step of + the epidemic. This can be used as a histogram of values for each + time step. + + Parameters + ---------- + times : np.array + An array of all time steps of the simulation + """ + # Initialise the dataframe + all_times = np.hstack((np.array(self + .sim_params["simulation_start_time"]), + times)) + data_dict = {time: [] for time in all_times} + for cell in self.population.cells: + for person in cell.persons: + # For every time the person was infected, add their list of + # serial intervals to the timepoint at which their infector + # became infected + for t_inf, intervals in person.serial_interval_dict.items(): + data_dict[t_inf] += intervals + + # Here we will fill out the rest of the dataframe with NaN values, + # as all lists will have different lengths + max_list_length = max([len(intervals) + for intervals in data_dict.values()]) + for t in data_dict.keys(): + data_dict[t] += ([np.nan] * (max_list_length - len(data_dict[t]))) + + # Change to dataframe to get the data in a list of dicts format + df = pd.DataFrame(data_dict) + + # The below is a list of dictionaries for each time step + list_of_dicts = df.to_dict(orient='records') + for dict_row in list_of_dicts: + # Write each time step in dictionary form + self.serial_interval_writer.write(dict_row) + def add_writer(self, writer: AbstractReporter): self.writers.append(writer) diff --git a/pyEpiabm/pyEpiabm/sweep/host_progression_sweep.py b/pyEpiabm/pyEpiabm/sweep/host_progression_sweep.py index 9d317838f..f9b59401b 100644 --- a/pyEpiabm/pyEpiabm/sweep/host_progression_sweep.py +++ b/pyEpiabm/pyEpiabm/sweep/host_progression_sweep.py @@ -324,6 +324,13 @@ def update_time_status_change(self, person: Person, time: float): person.time_of_status_change = time + transition_time + # Finally, if the person is Exposed, we can store their latency period + # as the transition_time. This can be used for calculating the serial + # interval + if person.infection_status == InfectionStatus.Exposed: + latent_period = transition_time + person.store_serial_interval(latent_period) + def _updates_infectiousness(self, person: Person, time: float): """Updates infectiousness. Scales using the initial infectiousness if the person is in an infectious state. Updates the infectiousness to diff --git a/pyEpiabm/pyEpiabm/sweep/household_sweep.py b/pyEpiabm/pyEpiabm/sweep/household_sweep.py index 7b04d07ac..4d53b8e82 100644 --- a/pyEpiabm/pyEpiabm/sweep/household_sweep.py +++ b/pyEpiabm/pyEpiabm/sweep/household_sweep.py @@ -56,3 +56,9 @@ def __call__(self, time: float): # Increment the infector's # secondary_infections_count infector.increment_secondary_infections() + # Set the time between infector's infection time and + # the infectee's exposure time (current time) to be + # the exposure period of the infectee + inf_to_exposed = (time - + infector.infection_start_times[-1]) + infectee.set_exposure_period(inf_to_exposed) diff --git a/pyEpiabm/pyEpiabm/sweep/place_sweep.py b/pyEpiabm/pyEpiabm/sweep/place_sweep.py index fe31b803b..61c82648e 100644 --- a/pyEpiabm/pyEpiabm/sweep/place_sweep.py +++ b/pyEpiabm/pyEpiabm/sweep/place_sweep.py @@ -57,6 +57,12 @@ def __call__(self, time: float): # Increment the infector's # secondary_infections_count infector.increment_secondary_infections() + # Set the time between infector's infection time + # and the infectee's exposure time (current time) + # to be the exposure period of the infectee + inf_to_exposed = \ + (time - infector.infection_start_times[-1]) + infectee.set_exposure_period(inf_to_exposed) # Otherwise number of infectees is binomially # distributed. Not sure if covidsim considers only @@ -97,3 +103,10 @@ def __call__(self, time: float): # Increment the infector's # secondary_infections_count infector.increment_secondary_infections() + # Set the time between infector's infection + # time and the infectee's exposure time + # (current time) to be the exposure period of + # the infectee + inf_to_exposed = \ + (time - infector.infection_start_times[-1]) + infectee.set_exposure_period(inf_to_exposed) diff --git a/pyEpiabm/pyEpiabm/sweep/spatial_sweep.py b/pyEpiabm/pyEpiabm/sweep/spatial_sweep.py index e76d9139f..d231b0ab5 100644 --- a/pyEpiabm/pyEpiabm/sweep/spatial_sweep.py +++ b/pyEpiabm/pyEpiabm/sweep/spatial_sweep.py @@ -251,6 +251,12 @@ def do_infection_event(self, infector: Person, infectee: Person, infectee.microcell.cell.enqueue_person(infectee) # Increment the infector's secondary_infections_count infector.increment_secondary_infections() + # Set the time between infector's infection time and + # the infectee's exposure time (current time) to be + # the exposure period of the infectee + inf_to_exposed = (time - + infector.infection_start_times[-1]) + infectee.set_exposure_period(inf_to_exposed) def bind_population(self, population): super().bind_population(population) diff --git a/pyEpiabm/pyEpiabm/tests/test_unit/test_core/test_person.py b/pyEpiabm/pyEpiabm/tests/test_unit/test_core/test_person.py index 01844af29..46a9e8966 100644 --- a/pyEpiabm/pyEpiabm/tests/test_unit/test_core/test_person.py +++ b/pyEpiabm/pyEpiabm/tests/test_unit/test_core/test_person.py @@ -149,6 +149,41 @@ def test_increment_secondary_infections(self): self.assertListEqual(self.person.secondary_infections_counts, [2, 5, 2]) + def test_set_exposure_period(self): + self.person.set_exposure_period(exposure_period=5) + self.assertEqual(self.person.exposure_period, 5) + + def test_store_serial_interval_erroneous(self): + with self.assertRaises(RuntimeError) as ve_1: + self.person.store_serial_interval(4.0) + self.assertEqual(str(ve_1.exception), + "Cannot call store_serial_interval while the" + " exposure_period is None") + # Infect once with an exposure period for successful method call + self.person.set_exposure_period(1.0) + self.person.time_of_status_change = 2.0 + self.person.store_serial_interval(latent_period=1.0) + # Do not add the exposure period for failure + self.person.time_of_status_change = 19.0 + with self.assertRaises(RuntimeError) as ve_2: + self.person.store_serial_interval(latent_period=4.0) + self.assertEqual(str(ve_2.exception), + "Cannot call store_serial_interval while the" + " exposure_period is None") + + def test_store_serial_interval(self): + self.person.set_exposure_period(1.0) + self.person.time_of_status_change = 2.0 + self.person.store_serial_interval(latent_period=1.0) + self.person.set_exposure_period(2.0) + self.person.time_of_status_change = 6.0 + self.person.store_serial_interval(latent_period=4.0) + self.person.set_exposure_period(5.0) + self.person.time_of_status_change = 19.0 + self.person.store_serial_interval(latent_period=4.0) + self.assertDictEqual(self.person.serial_interval_dict, + {0.0: [2.0, 6.0], 10.0: [9.0]}) + if __name__ == '__main__': unittest.main() diff --git a/pyEpiabm/pyEpiabm/tests/test_unit/test_routine/test_simulation.py b/pyEpiabm/pyEpiabm/tests/test_unit/test_routine/test_simulation.py index d5733c476..d53d5f1fb 100644 --- a/pyEpiabm/pyEpiabm/tests/test_unit/test_routine/test_simulation.py +++ b/pyEpiabm/pyEpiabm/tests/test_unit/test_routine/test_simulation.py @@ -36,7 +36,8 @@ def setUpClass(cls) -> None: cls.inf_history_params = {"output_dir": cls.mock_output_dir, "status_output": False, "infectiousness_output": False, - "secondary_infections_output": False} + "secondary_infections_output": False, + "serial_interval_output": False} cls.spatial_file_params = dict(cls.file_params) cls.spatial_file_params["age_stratified"] = True cls.spatial_file_params["spatial_output"] = True @@ -73,9 +74,11 @@ def test_configure(self, mock_mkdir): self.assertEqual(test_sim.status_output, False) self.assertEqual(test_sim.infectiousness_output, False) self.assertEqual(test_sim.secondary_infections_output, False) + self.assertEqual(test_sim.serial_interval_output, False) self.assertEqual(test_sim.ih_status_writer, None) self.assertEqual(test_sim.ih_infectiousness_writer, None) self.assertEqual(test_sim.secondary_infections_writer, None) + self.assertEqual(test_sim.serial_interval_writer, None) self.assertEqual(test_sim.include_waning, True) self.assertEqual(test_sim.compress, False) @@ -83,6 +86,7 @@ def test_configure(self, mock_mkdir): del test_sim.ih_status_writer del test_sim.ih_infectiousness_writer del test_sim.secondary_infections_writer + del test_sim.serial_interval_writer mo.assert_called_with(filename, 'w', newline='') @patch('os.makedirs') @@ -92,6 +96,7 @@ def test_configure_ih_status_infectiousness_false(self, mock_warning, self.inf_history_params["infectiousness_output"] = False self.inf_history_params["status_output"] = False self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = False mo = mock_open() with patch('pyEpiabm.output._csv_dict_writer.open', mo): test_sim = pe.routine.Simulation() @@ -99,8 +104,9 @@ def test_configure_ih_status_infectiousness_false(self, mock_warning, self.sweeps, self.sim_params, self.file_params, self.inf_history_params) mock_warning.assert_called_once_with("status_output, " - "infectiousness_output and " + "infectiousness_output, " "secondary_infections_output " + "and serial_interval_output " "are False. No infection " "history csvs will be " "created.") @@ -111,6 +117,7 @@ def test_configure_ih_status(self, mock_mkdir): self.inf_history_params["infectiousness_output"] = False self.inf_history_params["status_output"] = True self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): filename = os.path.join(os.getcwd(), self.inf_history_params["output_dir"], @@ -127,11 +134,13 @@ def test_configure_ih_status(self, mock_mkdir): # infectiousness_output is False self.assertEqual(test_sim.ih_infectiousness_writer, None) self.assertEqual(test_sim.secondary_infections_writer, None) + self.assertEqual(test_sim.serial_interval_writer, None) del test_sim.writer del test_sim.ih_status_writer del test_sim.ih_infectiousness_writer del test_sim.secondary_infections_writer + del test_sim.serial_interval_writer mo.assert_called_with(filename, 'w', newline='') @patch('os.makedirs') @@ -140,6 +149,7 @@ def test_configure_ih_infectiousness(self, mock_mkdir): self.inf_history_params["infectiousness_output"] = True self.inf_history_params["status_output"] = False self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): filename = os.path.join(os.getcwd(), self.inf_history_params["output_dir"], @@ -156,11 +166,13 @@ def test_configure_ih_infectiousness(self, mock_mkdir): # infectiousness_output is False self.assertEqual(test_sim.ih_status_writer, None) self.assertEqual(test_sim.secondary_infections_writer, None) + self.assertEqual(test_sim.serial_interval_writer, None) del test_sim.writer del test_sim.ih_status_writer del test_sim.ih_infectiousness_writer del test_sim.secondary_infections_writer + del test_sim.serial_interval_writer mo.assert_called_with(filename, 'w', newline='') @patch('os.makedirs') @@ -169,6 +181,7 @@ def test_configure_secondary_infections(self, mock_mkdir): self.inf_history_params["infectiousness_output"] = False self.inf_history_params["status_output"] = False self.inf_history_params["secondary_infections_output"] = True + self.inf_history_params["serial_interval_output"] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): filename = os.path.join(os.getcwd(), self.inf_history_params["output_dir"], @@ -186,11 +199,46 @@ def test_configure_secondary_infections(self, mock_mkdir): # infectiousness_output is False self.assertEqual(test_sim.ih_status_writer, None) self.assertEqual(test_sim.ih_infectiousness_writer, None) + self.assertEqual(test_sim.serial_interval_writer, None) del test_sim.writer del test_sim.ih_status_writer del test_sim.ih_infectiousness_writer del test_sim.secondary_infections_writer + del test_sim.serial_interval_writer + mo.assert_called_with(filename, 'w', newline='') + + @patch('os.makedirs') + def test_configure_serial_interval(self, mock_mkdir): + mo = mock_open() + self.inf_history_params["infectiousness_output"] = False + self.inf_history_params["status_output"] = False + self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = True + with patch('pyEpiabm.output._csv_dict_writer.open', mo): + filename = os.path.join(os.getcwd(), + self.inf_history_params["output_dir"], + "serial_intervals.csv") + test_sim = pe.routine.Simulation() + + # Test that the output titles are correct + test_sim.configure(self.test_population, self.initial_sweeps, + self.sweeps, self.sim_params, self.file_params, + self.inf_history_params) + + self.assertEqual(test_sim.si_output_titles, + [0.0, 1.0]) + # Test that the ih_status_writer is None, as + # infectiousness_output is False + self.assertEqual(test_sim.ih_status_writer, None) + self.assertEqual(test_sim.ih_infectiousness_writer, None) + self.assertEqual(test_sim.secondary_infections_writer, None) + + del test_sim.writer + del test_sim.ih_status_writer + del test_sim.ih_infectiousness_writer + del test_sim.secondary_infections_writer + del test_sim.serial_interval_writer mo.assert_called_with(filename, 'w', newline='') @patch('logging.exception') @@ -256,6 +304,7 @@ def test_run_sweeps_ih_status(self, mock_mkdir, patch_write): self.inf_history_params["infectiousness_output"] = False self.inf_history_params["status_output"] = True self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): time_write = self.sim_params["simulation_end_time"] test_sim = pe.routine.Simulation() @@ -273,6 +322,7 @@ def test_run_sweeps_ih_infectiousness(self, mock_mkdir, patch_write): self.inf_history_params["infectiousness_output"] = True self.inf_history_params["status_output"] = False self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): time_write = self.sim_params["simulation_end_time"] test_sim = pe.routine.Simulation() @@ -291,6 +341,24 @@ def test_run_sweeps_secondary_infections(self, mock_mkdir, patch_write): self.inf_history_params["infectiousness_output"] = False self.inf_history_params["status_output"] = False self.inf_history_params["secondary_infections_output"] = True + self.inf_history_params["serial_interval_output"] = False + with patch('pyEpiabm.output._csv_dict_writer.open', mo): + test_sim = pe.routine.Simulation() + test_sim.configure(self.test_population, self.initial_sweeps, + self.sweeps, self.sim_params, self.file_params, + self.inf_history_params) + test_sim.run_sweeps() + patch_write.assert_called_with(np.array([1])) + + @patch('pyEpiabm.routine.simulation.tqdm', notqdm) + @patch('pyEpiabm.routine.Simulation.write_to_serial_interval_file') + @patch('os.makedirs') + def test_run_sweeps_serial_interval(self, mock_mkdir, patch_write): + mo = mock_open() + self.inf_history_params["infectiousness_output"] = False + self.inf_history_params["status_output"] = False + self.inf_history_params["secondary_infections_output"] = False + self.inf_history_params["serial_interval_output"] = True with patch('pyEpiabm.output._csv_dict_writer.open', mo): test_sim = pe.routine.Simulation() test_sim.configure(self.test_population, self.initial_sweeps, @@ -468,6 +536,7 @@ def test_write_to_ih_file_status_no_infectiousness(self, mock_mkdir, self.inf_history_params['status_output'] = True self.inf_history_params['infectiousness_output'] = False self.inf_history_params['secondary_infections_output'] = False + self.inf_history_params['serial_interval_output'] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): test_sim = pe.routine.Simulation() test_sim.configure(self.test_population, self.initial_sweeps, @@ -497,6 +566,7 @@ def test_write_to_ih_file_no_status_infectiousness(self, mock_mkdir, self.inf_history_params['status_output'] = False self.inf_history_params['infectiousness_output'] = True self.inf_history_params['secondary_infections_output'] = False + self.inf_history_params['serial_interval_output'] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): test_sim = pe.routine.Simulation() test_sim.configure(self.test_population, self.initial_sweeps, @@ -526,6 +596,7 @@ def test_write_to_ih_file_status_infectiousness(self, mock_mkdir, time=1): self.inf_history_params['status_output'] = True self.inf_history_params['infectiousness_output'] = True self.inf_history_params['secondary_infections_output'] = False + self.inf_history_params['serial_interval_output'] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): test_sim = pe.routine.Simulation() test_sim.configure(self.test_population, self.initial_sweeps, @@ -564,6 +635,7 @@ def test_write_to_Rt_file(self, mock_mkdir, time=1): self.inf_history_params['status_output'] = False self.inf_history_params['infectiousness_output'] = False self.inf_history_params['secondary_infections_output'] = True + self.inf_history_params['serial_interval_output'] = False with patch('pyEpiabm.output._csv_dict_writer.open', mo): test_sim = pe.routine.Simulation() test_sim.configure(self.rt_test_population, self.initial_sweeps, @@ -616,6 +688,66 @@ def test_write_to_Rt_file(self, mock_mkdir, time=1): mock_mkdir.assert_called_with( os.path.join(os.getcwd(), self.inf_history_params["output_dir"])) + @patch('os.makedirs') + def test_write_to_serial_interval_file(self, mock_mkdir, time=1): + + if os.path.exists(self.inf_history_params["output_dir"]): + os.rmdir(self.inf_history_params["output_dir"]) + + mo = mock_open() + self.inf_history_params['status_output'] = False + self.inf_history_params['infectiousness_output'] = False + self.inf_history_params['secondary_infections_output'] = False + self.inf_history_params['serial_interval_output'] = True + with patch('pyEpiabm.output._csv_dict_writer.open', mo): + test_sim = pe.routine.Simulation() + test_sim.configure(self.rt_test_population, self.initial_sweeps, + self.sweeps, self.sim_params, self.file_params, + self.inf_history_params) + person1 = self.rt_test_population.cells[0].persons[0] + person1.num_times_infected = 2 + person1.infection_start_times = [2.0, 10.0] + person1.serial_interval_dict = {0.0: [2.0], 1.0: [9.0]} + person2 = self.rt_test_population.cells[0].persons[1] + person2.num_times_infected = 1 + person2.infection_start_times = [5.0] + person2.serial_interval_dict = {2.0: [3.0]} + person3 = self.rt_test_population.cells[0].persons[2] + person3.num_times_infected = 1 + person3.infection_start_times = [3.0, 11.0, 21.0] + person3.serial_interval_dict = {0.0: [3.0, 11.0], 2.0: [19.0]} + dict_0 = {0.0: 2.0, 1.0: 9.0, 2.0: 3.0} + dict_1 = {0.0: 3.0, 1.0: np.nan, 2.0: 19.0} + dict_2 = {0.0: 11.0, 1.0: np.nan, 2.0: np.nan} + + with patch('pyEpiabm.output._csv_dict_writer' + '._CsvDictWriter.write') as mock_write: + test_sim.write_to_serial_interval_file(np.array([1.0, 2.0])) + calls = mock_write.call_args_list + # Need to use np.testing for the NaNs + # Need to test keys and values separately in case we are using + # python 3.7 (for which np.testing.assert_equal will not work) + major, minor = sys.version_info[0], sys.version_info[1] + if major >= 4 or (major == 3 and minor >= 8): + actual_dict_0 = calls[0].args[0] + for key in dict_0: + self.assertTrue(key in actual_dict_0) + np.testing.assert_array_equal(dict_0[key], + actual_dict_0[key]) + actual_dict_1 = calls[1].args[0] + for key in dict_1: + self.assertTrue(key in actual_dict_1) + np.testing.assert_array_equal(dict_1[key], + actual_dict_1[key]) + actual_dict_2 = calls[2].args[0] + for key in dict_2: + self.assertTrue(key in actual_dict_2) + np.testing.assert_array_equal(dict_2[key], + actual_dict_2[key]) + self.assertEqual(mock_write.call_count, 3) + mock_mkdir.assert_called_with( + os.path.join(os.getcwd(), self.inf_history_params["output_dir"])) + @patch('os.makedirs') def test_compress_no_compression(self, mock_mkdir): mo = mock_open() diff --git a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_host_progression_sweep.py b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_host_progression_sweep.py index 72929ae8b..63ad6bdc3 100644 --- a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_host_progression_sweep.py +++ b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_host_progression_sweep.py @@ -364,6 +364,8 @@ def test_update_time_status_change_no_age(self, current_time=100.0): continue # Method should not be used to infect people test_sweep.update_next_infection_status(person) + if person.infection_status.name == 'Exposed': + person.set_exposure_period(2.0) test_sweep.update_time_status_change(person, current_time) time_of_status_change = person.time_of_status_change if person.infection_status.name in ['Recovered', 'Dead', @@ -376,6 +378,32 @@ def test_update_time_status_change_no_age(self, current_time=100.0): else: self.assertLessEqual(current_time, time_of_status_change) + def test_update_time_status_change_serial_interval(self): + """Tests that an exposed person has their latency period stored and + added to their exposure_period to give a serial interval (to be stored) + This occurs at the end of the update_time_status_change method + """ + test_sweep = pe.sweep.HostProgressionSweep() + # 3 days between their infector becoming infected and now (the time + # of the infection event) + self.person1.set_exposure_period(3.0) + self.person1.update_status(InfectionStatus.Exposed) + self.person1.next_infection_status = InfectionStatus.InfectMild + current_time = 5.0 + test_sweep.update_time_status_change(self.person1, current_time) + time_of_status_change = self.person1.time_of_status_change + # The time_of_status_change to InfectMild minus the current_time + # gives the length of time that the person is latent + latent_period = time_of_status_change - current_time + # The reference time is the time in which their infector became + # infected + reference_time = 5.0 - 3.0 + # The serial_interval_dict records their serial_interval (defined as + # the time between the infector having Infect status and the current + # person having Infect status) at the reference time + self.assertDictEqual(self.person1.serial_interval_dict, + {reference_time: [3.0 + latent_period]}) + def test_update_time_status_change_waning_immunity(self): """Tests that the time to status change of a Recovered person with waning immunity is equal to the output of the InverseCDF method. This @@ -656,6 +684,7 @@ def test_call_main(self, mock_next_time, mock_param, mock_param.return_value.infectiousness_prof = self.mock_inf_prog # First check that people progress through the # infection stages correctly. + self.person2.set_exposure_period(1.0) self.person2.update_status(pe.property.InfectionStatus.Exposed) self.person2.time_of_status_change = 1.0 self.person2.next_infection_status = \ @@ -670,6 +699,7 @@ def test_call_main(self, mock_next_time, mock_param, # Tests population bound successfully. self.assertEqual(test_sweep._population.cells[0].persons[1]. infection_status, pe.property.InfectionStatus.Exposed) + self.person1.set_exposure_period(1.0) test_sweep(1.0) self.assertEqual(self.person2.infection_status, pe.property.InfectionStatus.InfectMild) @@ -797,6 +827,7 @@ def test_multiple_transitions_in_one_time_step(self, mock_param, mock_param.return_value.rate_multiplier_params = self.multipliers mock_next_time.return_value = 0.0 self.person1.time_of_status_change = 1.0 + self.person1.set_exposure_period(1.0) self.person1.update_status(InfectionStatus.Susceptible) self.person1.next_infection_status = InfectionStatus.Exposed test_sweep = pe.sweep.HostProgressionSweep() diff --git a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_household_sweep.py b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_household_sweep.py index ed95ed9d6..c25f1f70e 100644 --- a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_household_sweep.py +++ b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_household_sweep.py @@ -64,18 +64,21 @@ def test__call__(self, mock_force): # Add one susceptible to the population, with the mocked infectiousness # ensuring they are added to the infected queue. self.person.infection_status = pe.property.InfectionStatus.InfectMild + self.person.infection_start_times = [0.0] test_queue = Queue() new_person = pe.Person(self.microcell) new_person.household = self.house self.house.persons.append(new_person) self.house.susceptible_persons.append(new_person) self.pop.cells[0].persons.append(new_person) + self.assertEqual(new_person.exposure_period, None) test_queue.put(new_person) self.test_sweep.bind_population(self.pop) self.test_sweep(self.time) self.assertEqual(self.cell.person_queue.qsize(), 1) self.assertListEqual(self.person.secondary_infections_counts, [1]) + self.assertEqual(new_person.exposure_period, 1.0) # Change the additional person to recovered, and assert the queue # is empty. @@ -87,6 +90,7 @@ def test__call__(self, mock_force): self.test_sweep(self.time) self.assertTrue(self.cell.person_queue.empty()) self.assertListEqual(self.person.secondary_infections_counts, [1]) + self.assertEqual(new_person.exposure_period, 1.0) def test_no_households(self): pop_nh = pe.Population() # Population without households diff --git a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_place_sweep.py b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_place_sweep.py index c1555e5ed..69db6b3e8 100644 --- a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_place_sweep.py +++ b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_place_sweep.py @@ -60,13 +60,16 @@ def test__call__(self, mock_inf, mock_force): # ensuring they are added to the infected queue and the infector's # secondary_infections_counts are incremented. self.person1.update_status(pe.property.InfectionStatus.InfectMild) + self.person1.infection_start_times = [0.0] self.person1.secondary_infections_counts = [0] + self.assertEqual(self.new_person.exposure_period, None) self.place.add_person(self.new_person) self.cell.person_queue = Queue() self.test_sweep.bind_population(self.pop) self.test_sweep(time) self.assertEqual(self.cell.person_queue.qsize(), 1) self.assertListEqual(self.person1.secondary_infections_counts, [1]) + self.assertEqual(self.new_person.exposure_period, 1.0) # Change the additional person to recovered, and assert the queue # is empty. @@ -76,6 +79,7 @@ def test__call__(self, mock_inf, mock_force): self.test_sweep(time) self.assertTrue(self.cell.person_queue.empty()) self.assertListEqual(self.person1.secondary_infections_counts, [1]) + self.assertEqual(self.new_person.exposure_period, 1.0) # Now test when binomial dist is activated. mock_inf.return_value = 1 @@ -86,6 +90,7 @@ def test__call__(self, mock_inf, mock_force): self.test_sweep(time) self.assertTrue(self.cell.person_queue.empty()) self.assertListEqual(self.person1.secondary_infections_counts, [1]) + self.assertEqual(self.new_person.exposure_period, 1.0) # Change the additional person to susceptible. self.new_person.update_status(pe.property.InfectionStatus.Susceptible) @@ -95,6 +100,7 @@ def test__call__(self, mock_inf, mock_force): self.test_sweep(time) self.assertEqual(self.cell.person_queue.qsize(), 1) self.assertListEqual(self.person1.secondary_infections_counts, [2]) + self.assertEqual(self.new_person.exposure_period, 1.0) if __name__ == '__main__': diff --git a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_spatial_sweep.py b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_spatial_sweep.py index 875000e4c..849b39a61 100644 --- a/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_spatial_sweep.py +++ b/pyEpiabm/pyEpiabm/tests/test_unit/test_sweep/test_spatial_sweep.py @@ -255,9 +255,12 @@ def test__call__(self, mock_inf, mock_foi, mock_poisson, mock_inf_list, # Assert a basic population test_pop = self.pop test_sweep.bind_population(test_pop) + self.infector.infection_start_times = [0.0] + self.assertEqual(self.infectee.exposure_period, None) test_sweep(time) self.assertTrue(self.cell_inf.person_queue.empty()) self.assertListEqual(self.infector.secondary_infections_counts, [1]) + self.assertEqual(self.infectee.exposure_period, 1.0) mock_inf.assert_called_once_with(self.cell_inf, time) mock_foi.assert_called_once_with(self.cell_inf, self.cell_susc, @@ -266,26 +269,31 @@ def test__call__(self, mock_inf, mock_foi, mock_poisson, mock_inf_list, # Change infector's status to infected self.infector.update_status(InfectionStatus.InfectMild) + self.infector.infection_start_times = [0.0] test_sweep(time) self.assertEqual(self.cell_inf.person_queue.qsize(), 0) self.assertListEqual(self.infector.secondary_infections_counts, [2]) + self.assertEqual(self.infectee.exposure_period, 1.0) Parameters.instance().do_CovidSim = True self.cell_susc.person_queue = Queue() test_sweep(time) self.assertEqual(self.cell_susc.person_queue.qsize(), 1) self.assertListEqual(self.infector.secondary_infections_counts, [3]) + self.assertEqual(self.infectee.exposure_period, 1.0) # Check when we have an infector but no infectees self.infectee.update_status(InfectionStatus.Recovered) self.cell_susc.person_queue = Queue() test_sweep(time) self.assertEqual(self.cell_susc.person_queue.qsize(), 0) self.assertListEqual(self.infector.secondary_infections_counts, [3]) + self.assertEqual(self.infectee.exposure_period, 1.0) # Test parameters break-out clause Parameters.instance().infection_radius = 0 test_sweep(time) self.assertEqual(self.cell_susc.person_queue.qsize(), 0) self.assertListEqual(self.infector.secondary_infections_counts, [3]) + self.assertEqual(self.infectee.exposure_period, 1.0) mock_inf_list.assert_called_with(self.cell_inf, [self.cell_susc], time) mock_list_covid.assert_called_with(self.infector, [self.cell_susc], @@ -323,17 +331,22 @@ def test_do_infection_event(self, mock_random): fake_infectee = microcell_susc.persons[1] fake_infectee.update_status(InfectionStatus.Recovered) actual_infectee = microcell_susc.persons[0] + self.infector.infection_start_times = [0.0] self.assertTrue(cell_susc.person_queue.empty()) test_sweep.do_infection_event(self.infector, fake_infectee, 1) self.assertFalse(mock_random.called) # Should have already returned self.assertTrue(cell_susc.person_queue.empty()) self.assertListEqual(self.infector.secondary_infections_counts, [0]) + self.assertEqual(fake_infectee.exposure_period, None) + self.assertEqual(actual_infectee.exposure_period, None) test_sweep.do_infection_event(self.infector, actual_infectee, 1) mock_random.assert_called_once() self.assertEqual(cell_susc.person_queue.qsize(), 1) self.assertListEqual(self.infector.secondary_infections_counts, [1]) + self.assertEqual(fake_infectee.exposure_period, None) + self.assertEqual(actual_infectee.exposure_period, 1.0) if __name__ == '__main__': diff --git a/python_examples/basic_infection_history_simulation/simulation_outputs/output.csv b/python_examples/basic_infection_history_simulation/simulation_outputs/output.csv index 0929fc32e..4ee88e603 100644 --- a/python_examples/basic_infection_history_simulation/simulation_outputs/output.csv +++ b/python_examples/basic_infection_history_simulation/simulation_outputs/output.csv @@ -1,62 +1,62 @@ time,InfectionStatus.Susceptible,InfectionStatus.Exposed,InfectionStatus.InfectASympt,InfectionStatus.InfectMild,InfectionStatus.InfectGP,InfectionStatus.InfectHosp,InfectionStatus.InfectICU,InfectionStatus.InfectICURecov,InfectionStatus.Recovered,InfectionStatus.Dead,InfectionStatus.Vaccinated -0,90,0,0,10,0,0,0,0,0,0,0 -1.0,90,0,0,8,0,0,0,0,2,0,0 -2.0,75,17,0,8,0,0,0,0,0,0,0 -3.0,67,21,2,9,1,0,0,0,0,0,0 -4.0,66,20,3,7,2,0,0,0,2,0,0 -5.0,68,18,5,7,2,0,0,0,0,0,0 -6.0,66,14,8,10,1,0,0,0,1,0,0 -7.0,66,14,8,8,0,0,0,0,4,0,0 -8.0,67,15,10,6,0,0,0,0,2,0,0 -9.0,65,16,11,6,0,0,0,0,2,0,0 -10.0,66,16,12,4,0,0,0,0,2,0,0 -11.0,66,14,13,4,2,0,0,0,1,0,0 -12.0,66,13,15,4,2,0,0,0,0,0,0 -13.0,63,14,14,5,2,0,0,0,2,0,0 -14.0,65,12,13,6,3,0,0,0,1,0,0 -15.0,63,12,14,5,5,0,0,0,1,0,0 -16.0,62,13,13,4,6,0,0,0,2,0,0 -17.0,64,10,13,5,4,0,0,0,4,0,0 -18.0,68,6,12,4,7,0,0,0,3,0,0 -19.0,67,6,11,6,7,1,0,0,2,0,0 -20.0,65,9,11,6,7,1,0,0,1,0,0 -21.0,63,10,12,7,6,1,0,0,1,0,0 -22.0,64,8,12,7,5,1,0,0,3,0,0 -23.0,67,4,11,10,6,1,0,0,1,0,0 -24.0,67,5,8,8,5,2,0,0,5,0,0 -25.0,70,6,9,8,2,2,0,0,3,0,0 -26.0,72,5,10,9,2,2,0,0,0,0,0 -27.0,72,4,11,7,2,2,0,0,2,0,0 -28.0,70,4,10,6,5,2,0,0,3,0,0 -29.0,71,5,10,5,6,1,0,0,2,0,0 -30.0,69,8,9,5,6,1,0,0,2,0,0 -31.0,68,9,7,6,6,1,0,0,3,0,0 -32.0,70,6,8,6,5,1,0,0,4,0,0 -33.0,74,4,9,6,5,1,0,0,1,0,0 -34.0,74,3,7,6,3,2,0,0,5,0,0 -35.0,77,4,7,7,3,2,0,0,0,0,0 -36.0,72,7,6,8,4,2,0,0,1,0,0 -37.0,72,6,7,9,4,1,0,0,1,0,0 -38.0,73,5,6,9,5,1,0,0,1,0,0 -39.0,72,7,6,7,4,0,0,0,3,1,0 -40.0,74,7,6,8,2,0,0,0,2,1,0 -41.0,75,7,5,7,3,0,0,0,2,1,0 -42.0,77,5,5,7,3,0,0,0,2,1,0 -43.0,78,5,5,7,3,0,0,0,1,1,0 -44.0,77,5,6,6,3,0,0,0,2,1,0 -45.0,76,8,6,5,3,0,0,0,1,1,0 -46.0,75,8,7,4,2,0,0,0,3,1,0 -47.0,78,5,8,6,2,0,0,0,0,1,0 -48.0,76,6,8,5,3,0,0,0,1,1,0 -49.0,77,2,8,6,4,0,0,0,2,1,0 -50.0,78,3,8,6,3,0,0,0,1,1,0 -51.0,79,2,8,5,3,0,0,0,2,1,0 -52.0,80,2,6,2,4,0,0,0,5,1,0 -53.0,85,2,6,0,3,0,0,0,3,1,0 -54.0,87,2,6,1,3,0,0,0,0,1,0 -55.0,84,5,6,1,3,0,0,0,0,1,0 -56.0,83,6,6,1,3,0,0,0,0,1,0 -57.0,83,5,5,2,3,0,0,0,1,1,0 -58.0,84,5,3,2,2,0,0,0,3,1,0 -59.0,87,3,3,4,2,0,0,0,0,1,0 -60.0,87,2,4,3,2,0,0,0,1,1,0 +0,99,0,0,1,0,0,0,0,0,0,0 +1.0,99,0,0,1,0,0,0,0,0,0,0 +2.0,99,0,0,1,0,0,0,0,0,0,0 +3.0,99,0,0,1,0,0,0,0,0,0,0 +4.0,99,0,0,1,0,0,0,0,0,0,0 +5.0,99,0,0,0,0,0,0,0,1,0,0 +6.0,99,0,0,0,0,0,0,0,1,0,0 +7.0,99,0,0,0,0,0,0,0,1,0,0 +8.0,99,0,0,0,0,0,0,0,1,0,0 +9.0,99,0,0,0,0,0,0,0,1,0,0 +10.0,99,0,0,0,0,0,0,0,1,0,0 +11.0,99,0,0,0,0,0,0,0,1,0,0 +12.0,99,0,0,0,0,0,0,0,1,0,0 +13.0,99,0,0,0,0,0,0,0,1,0,0 +14.0,99,0,0,0,0,0,0,0,1,0,0 +15.0,99,0,0,0,0,0,0,0,1,0,0 +16.0,99,0,0,0,0,0,0,0,1,0,0 +17.0,99,0,0,0,0,0,0,0,1,0,0 +18.0,99,0,0,0,0,0,0,0,1,0,0 +19.0,99,0,0,0,0,0,0,0,1,0,0 +20.0,99,0,0,0,0,0,0,0,1,0,0 +21.0,99,0,0,0,0,0,0,0,1,0,0 +22.0,99,0,0,0,0,0,0,0,1,0,0 +23.0,99,0,0,0,0,0,0,0,1,0,0 +24.0,99,0,0,0,0,0,0,0,1,0,0 +25.0,99,0,0,0,0,0,0,0,1,0,0 +26.0,99,0,0,0,0,0,0,0,1,0,0 +27.0,99,0,0,0,0,0,0,0,1,0,0 +28.0,99,0,0,0,0,0,0,0,1,0,0 +29.0,99,0,0,0,0,0,0,0,1,0,0 +30.0,99,0,0,0,0,0,0,0,1,0,0 +31.0,99,0,0,0,0,0,0,0,1,0,0 +32.0,99,0,0,0,0,0,0,0,1,0,0 +33.0,99,0,0,0,0,0,0,0,1,0,0 +34.0,99,0,0,0,0,0,0,0,1,0,0 +35.0,99,0,0,0,0,0,0,0,1,0,0 +36.0,99,0,0,0,0,0,0,0,1,0,0 +37.0,99,0,0,0,0,0,0,0,1,0,0 +38.0,99,0,0,0,0,0,0,0,1,0,0 +39.0,99,0,0,0,0,0,0,0,1,0,0 +40.0,99,0,0,0,0,0,0,0,1,0,0 +41.0,99,0,0,0,0,0,0,0,1,0,0 +42.0,99,0,0,0,0,0,0,0,1,0,0 +43.0,99,0,0,0,0,0,0,0,1,0,0 +44.0,99,0,0,0,0,0,0,0,1,0,0 +45.0,99,0,0,0,0,0,0,0,1,0,0 +46.0,99,0,0,0,0,0,0,0,1,0,0 +47.0,99,0,0,0,0,0,0,0,1,0,0 +48.0,99,0,0,0,0,0,0,0,1,0,0 +49.0,99,0,0,0,0,0,0,0,1,0,0 +50.0,99,0,0,0,0,0,0,0,1,0,0 +51.0,99,0,0,0,0,0,0,0,1,0,0 +52.0,99,0,0,0,0,0,0,0,1,0,0 +53.0,99,0,0,0,0,0,0,0,1,0,0 +54.0,99,0,0,0,0,0,0,0,1,0,0 +55.0,99,0,0,0,0,0,0,0,1,0,0 +56.0,99,0,0,0,0,0,0,0,1,0,0 +57.0,99,0,0,0,0,0,0,0,1,0,0 +58.0,99,0,0,0,0,0,0,0,1,0,0 +59.0,99,0,0,0,0,0,0,0,1,0,0 +60.0,99,0,0,0,0,0,0,0,1,0,0 diff --git a/python_examples/basic_infection_history_simulation/triple_simulation_flow.py b/python_examples/basic_infection_history_simulation/triple_simulation_flow.py new file mode 100644 index 000000000..3cc31ecb7 --- /dev/null +++ b/python_examples/basic_infection_history_simulation/triple_simulation_flow.py @@ -0,0 +1,75 @@ +# +# Example simulation script with data output and visualisation +# + +import os +import logging +import pandas as pd +import matplotlib.pyplot as plt + +import pyEpiabm as pe + +# Setup output for logging file +logging.basicConfig(filename='sim.log', filemode='w+', level=logging.DEBUG, + format=('%(asctime)s - %(name)s' + + '- %(levelname)s - %(message)s')) + +# Set config file for Parameters +pe.Parameters.set_file(os.path.join(os.path.dirname(__file__), + "simple_parameters.json")) + +# Method to set the seed at the start of the simulation, for reproducibility + +pe.routine.Simulation.set_random_seed(seed=42) + +# Pop_params are used to configure the population structure being used in this +# simulation. + +pop_params = {"population_size": 1000, "cell_number": 1, + "microcell_number": 1, "household_number": 1, + "place_number": 2} + +# Create a population based on the parameters given. +population = pe.routine.ToyPopulationFactory().make_pop(pop_params) + +# sim_ and file_params give details for the running of the simulations and +# where output should be written to. +sim_params = {"simulation_start_time": 0, "simulation_end_time": 20, + "initial_infected_number": 1} + +file_params = {"output_file": "output.csv", + "output_dir": os.path.join(os.path.dirname(__file__), + "triple_simulation_outputs"), + "spatial_output": False, + "age_stratified": False} + +# Create a simulation object, configure it with the parameters given, then +# run the simulation. +sim = pe.routine.Simulation() +sim.configure( + population, + [pe.sweep.InitialInfectedSweep()], + [pe.sweep.TripleSweep(), pe.sweep.QueueSweep(), + pe.sweep.HostProgressionSweep()], + sim_params, + file_params) +sim.run_sweeps() + +# Need to close the writer object at the end of each simulation. +del sim.writer +del sim + +# Creation of a plot of results (without logging matplotlib info) +logging.getLogger("matplotlib").setLevel(logging.WARNING) +filename = os.path.join(os.path.dirname(__file__), "triple_simulation_outputs", + "output.csv") +df = pd.read_csv(filename) +df.plot(x="time", y=["InfectionStatus.Susceptible", + "InfectionStatus.InfectMild", + "InfectionStatus.Recovered"]) +plt.savefig( + os.path.join(os.path.dirname(__file__), + "triple_simulation_outputs", + "triple_simulation_flow_SIR_plot.png") +) +# Default file format is .png, but can be changed to .pdf, .svg, etc. diff --git a/python_examples/basic_infection_history_simulation/triple_simulation_outputs/output.csv b/python_examples/basic_infection_history_simulation/triple_simulation_outputs/output.csv new file mode 100644 index 000000000..f06181c20 --- /dev/null +++ b/python_examples/basic_infection_history_simulation/triple_simulation_outputs/output.csv @@ -0,0 +1,22 @@ +time,InfectionStatus.Susceptible,InfectionStatus.Exposed,InfectionStatus.InfectASympt,InfectionStatus.InfectMild,InfectionStatus.InfectGP,InfectionStatus.InfectHosp,InfectionStatus.InfectICU,InfectionStatus.InfectICURecov,InfectionStatus.Recovered,InfectionStatus.Dead,InfectionStatus.Vaccinated +0,999,0,0,1,0,0,0,0,0,0,0 +1.0,996,3,0,1,0,0,0,0,0,0,0 +2.0,996,2,1,1,0,0,0,0,0,0,0 +3.0,993,4,1,1,1,0,0,0,0,0,0 +4.0,990,7,1,1,1,0,0,0,0,0,0 +5.0,990,6,1,1,1,0,0,0,1,0,0 +6.0,987,8,1,2,1,0,0,0,1,0,0 +7.0,984,7,4,2,1,0,0,0,2,0,0 +8.0,972,18,4,2,2,0,0,0,2,0,0 +9.0,969,18,5,3,3,0,0,0,2,0,0 +10.0,960,23,6,5,4,0,0,0,2,0,0 +11.0,948,33,7,4,4,0,0,0,4,0,0 +12.0,942,31,10,5,6,1,0,0,5,0,0 +13.0,918,49,14,5,7,1,0,0,6,0,0 +14.0,900,60,14,8,9,1,0,0,8,0,0 +15.0,882,65,18,15,9,1,0,0,10,0,0 +16.0,843,95,20,16,8,1,0,0,17,0,0 +17.0,816,105,23,22,13,0,0,0,21,0,0 +18.0,766,139,24,31,16,0,0,0,24,0,0 +19.0,721,147,39,39,22,0,0,0,32,0,0 +20.0,618,224,49,48,24,2,0,0,35,0,0 diff --git a/python_examples/basic_infection_history_simulation/triple_simulation_outputs/triple_simulation_flow_SIR_plot.png b/python_examples/basic_infection_history_simulation/triple_simulation_outputs/triple_simulation_flow_SIR_plot.png new file mode 100644 index 000000000..9b7d18bf4 Binary files /dev/null and b/python_examples/basic_infection_history_simulation/triple_simulation_outputs/triple_simulation_flow_SIR_plot.png differ