diff --git a/cara/models.py b/cara/models.py index 2b4b66b9..35e5d6b7 100644 --- a/cara/models.py +++ b/cara/models.py @@ -432,6 +432,9 @@ class Virus: #: viable-to-RNA virus ratio as a function of the viral load viable_to_RNA_ratio: _VectorisedFloat + #: Reported increase of transmissibility of a VOC + transmissibility_VOC: float + #: Pre-populated examples of Viruses. types: typing.ClassVar[typing.Dict[str, "Virus"]] @@ -469,22 +472,26 @@ Virus.types = { # 50 comes from Buonanno et al. infectious_dose=50., viable_to_RNA_ratio = 0.5, + transmissibility_VOC=1.0, ), 'SARS_CoV_2_B117': SARSCoV2( # also called VOC-202012/01 viral_load_in_sputum=1e9, infectious_dose=30., viable_to_RNA_ratio = 0.5, + transmissibility_VOC=0.6, ), 'SARS_CoV_2_P1': SARSCoV2( viral_load_in_sputum=1e9, infectious_dose=1/0.045, viable_to_RNA_ratio = 0.5, + transmissibility_VOC=0.45, ), 'SARS_CoV_2_B16172': SARSCoV2( viral_load_in_sputum=1e9, infectious_dose=30/1.6, viable_to_RNA_ratio = 0.5, + transmissibility_VOC=0.38, ), } @@ -995,8 +1002,10 @@ class ExposureModel: exposure * self.fraction_deposited * f_inf ) - # Probability of infection. - return (1 - np.exp(-(inf_aero/self.concentration_model.virus.infectious_dose))) * 100 + infectious_dose = 1.44 * self.concentration_model.virus.infectious_dose * self.concentration_model.virus.transmissibility_VOC * (1 / (1 - self.concentration_model.infected.host_immunity)) + + # Probability of infection. + return (1 - np.exp(-(inf_aero/infectious_dose))) * 100 def expected_new_cases(self) -> _VectorisedFloat: prob = self.infection_probability() diff --git a/cara/monte_carlo/data.py b/cara/monte_carlo/data.py index ec03a23b..f9f3c92c 100644 --- a/cara/monte_carlo/data.py +++ b/cara/monte_carlo/data.py @@ -111,21 +111,25 @@ virus_distributions = { viral_load_in_sputum=symptomatic_vl_frequencies, infectious_dose=infectious_dose_distribution, viable_to_RNA_ratio=viable_to_RNA_ratio_distribution, + transmissibility_VOC=1., ), 'SARS_CoV_2_B117': mc.SARSCoV2( viral_load_in_sputum=symptomatic_vl_frequencies, infectious_dose=infectious_dose_distribution, viable_to_RNA_ratio=viable_to_RNA_ratio_distribution, + transmissibility_VOC=0.6, ), 'SARS_CoV_2_P1': mc.SARSCoV2( viral_load_in_sputum=symptomatic_vl_frequencies, infectious_dose=infectious_dose_distribution, viable_to_RNA_ratio=viable_to_RNA_ratio_distribution, + transmissibility_VOC=0.45, ), 'SARS_CoV_2_B16172': mc.SARSCoV2( viral_load_in_sputum=symptomatic_vl_frequencies, infectious_dose=infectious_dose_distribution, viable_to_RNA_ratio=viable_to_RNA_ratio_distribution, + transmissibility_VOC=0.38, ), } diff --git a/cara/tests/models/test_concentration_model.py b/cara/tests/models/test_concentration_model.py index 7d7a6755..27bb21f0 100644 --- a/cara/tests/models/test_concentration_model.py +++ b/cara/tests/models/test_concentration_model.py @@ -45,6 +45,7 @@ def test_concentration_model_vectorisation(override_params): viable_to_RNA_ratio = 0.5, ), expiration=models._ExpirationBase.types['Breathing'], + host_immunity=0., ) ) concentrations = c_model.concentration(10) @@ -65,6 +66,7 @@ def simple_conc_model(): activity=models.Activity.types['Seated'], virus=models.Virus.types['SARS_CoV_2'], expiration=models.Expiration.types['Breathing'], + host_immunity=0., ) ) diff --git a/cara/tests/models/test_exposure_model.py b/cara/tests/models/test_exposure_model.py index 9d3ef966..e120ad29 100644 --- a/cara/tests/models/test_exposure_model.py +++ b/cara/tests/models/test_exposure_model.py @@ -193,7 +193,8 @@ def test_infectious_dose_vectorisation(): infectious_dose=np.array([50, 20, 30]), viable_to_RNA_ratio = 0.5, ), - expiration=models.Expiration.types['Talking'] + expiration=models.Expiration.types['Talking'], + host_immunity=0., ) cm = known_concentrations(lambda t: 1.2) cm = replace(cm, infected=infected_population) diff --git a/cara/tests/test_infected_population.py b/cara/tests/test_infected_population.py index df087aac..4aafbef3 100644 --- a/cara/tests/test_infected_population.py +++ b/cara/tests/test_infected_population.py @@ -35,6 +35,7 @@ def test_infected_population_vectorisation(override_params): viable_to_RNA_ratio = 0.5, ), expiration=cara.models._ExpirationBase.types['Breathing'], + host_immunity=0., ) emission_rate = infected.emission_rate(10) assert isinstance(emission_rate, np.ndarray) diff --git a/cara/tests/test_monte_carlo.py b/cara/tests/test_monte_carlo.py index fa0e5781..383aed34 100644 --- a/cara/tests/test_monte_carlo.py +++ b/cara/tests/test_monte_carlo.py @@ -54,6 +54,7 @@ def baseline_mc_model() -> cara.monte_carlo.ConcentrationModel: mask=cara.models.Mask.types['No mask'], activity=cara.models.Activity.types['Light activity'], expiration=cara.models.Expiration.types['Breathing'], + host_immunity=0., ), ) return mc_model diff --git a/cara/tests/test_monte_carlo_full_models.py b/cara/tests/test_monte_carlo_full_models.py index b13ea0c7..f6b22054 100644 --- a/cara/tests/test_monte_carlo_full_models.py +++ b/cara/tests/test_monte_carlo_full_models.py @@ -45,6 +45,7 @@ def shared_office_mc(): mask=models.Mask(η_inhale=0.3), activity=activity_distributions['Seated'], expiration=build_expiration({'Talking': 0.3, 'Breathing': 0.7}), + host_immunity=0., ), ) return mc.ExposureModel( @@ -86,6 +87,7 @@ def classroom_mc(): mask=models.Mask.types['No mask'], activity=activity_distributions['Light activity'], expiration=expiration_distributions['Talking'], + host_immunity=0., ), ) return mc.ExposureModel( @@ -117,6 +119,7 @@ def ski_cabin_mc(): mask=models.Mask(η_inhale=0.3), activity=activity_distributions['Moderate activity'], expiration=expiration_distributions['Talking'], + host_immunity=0., ), ) return mc.ExposureModel( @@ -150,6 +153,7 @@ def gym_mc(): mask=models.Mask.types["No mask"], activity=activity_distributions['Heavy exercise'], expiration=expiration_distributions['Breathing'], + host_immunity=0., ), ) return mc.ExposureModel( @@ -181,7 +185,8 @@ def waiting_room_mc(): presence=mc.SpecificInterval(((0., 2.),)), mask=models.Mask.types["No mask"], activity=activity_distributions['Seated'], - expiration=build_expiration({'Talking': 0.3, 'Breathing': 0.7}) + expiration=build_expiration({'Talking': 0.3, 'Breathing': 0.7}), + host_immunity=0., ), ) return mc.ExposureModel( @@ -215,6 +220,7 @@ def skagit_chorale_mc(): mask=models.Mask.types["No mask"], activity=activity_distributions['Light activity'], expiration=expiration_distribution((5., 5., 5.)), + host_immunity=0., ), ) return mc.ExposureModel( @@ -288,6 +294,7 @@ def test_small_shared_office_Geneva(mask_type, month, expected_pi, mask=models.Mask.types[mask_type], activity=activity_distributions['Seated'], expiration=build_expiration({'Talking': 0.33, 'Breathing': 0.67}), + host_immunity=0., ), ) exposure_mc = mc.ExposureModel(