From f07bffaa4a213144711c0e2ed54f499285674fb0 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 14 May 2021 09:11:38 +0200 Subject: [PATCH 1/7] Replacing coefficient_of_infectivity by qID in tests --- cara/tests/models/test_concentration_model.py | 6 +++--- cara/tests/test_infected_population.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cara/tests/models/test_concentration_model.py b/cara/tests/models/test_concentration_model.py index 52fb0546..c0be20fd 100644 --- a/cara/tests/models/test_concentration_model.py +++ b/cara/tests/models/test_concentration_model.py @@ -12,7 +12,7 @@ from cara import models {'air_change': np.array([100, 120])}, {'virus_halflife': np.array([1.1, 1.5])}, {'viral_load_in_sputum': np.array([5e8, 1e9])}, - {'coefficient_of_infectivity': np.array([0.02, 0.05])}, + {'qID': np.array([50, 20])}, {'η_exhale': np.array([0.92, 0.95])}, {'η_leaks': np.array([0.15, 0.20])}, ] @@ -23,7 +23,7 @@ def test_concentration_model_vectorisation(override_params): 'air_change': 100, 'virus_halflife': 1.1, 'viral_load_in_sputum': 1e9, - 'coefficient_of_infectivity': 0.02, + 'qID': 50, 'η_exhale': 0.95, 'η_leaks': 0.15, } @@ -48,7 +48,7 @@ def test_concentration_model_vectorisation(override_params): virus=models.Virus( halflife=defaults['virus_halflife'], viral_load_in_sputum=defaults['viral_load_in_sputum'], - coefficient_of_infectivity=defaults['coefficient_of_infectivity'], + qID=defaults['qID'], ), expiration=models.Expiration( ejection_factor=(0.084, 0.009, 0.003, 0.002), diff --git a/cara/tests/test_infected_population.py b/cara/tests/test_infected_population.py index 39fbc039..6ee9758b 100644 --- a/cara/tests/test_infected_population.py +++ b/cara/tests/test_infected_population.py @@ -7,7 +7,7 @@ import cara.models @pytest.mark.parametrize( "override_params", [ {'viral_load_in_sputum': np.array([5e8, 1e9])}, - {'coefficient_of_infectivity': np.array([0.02, 0.05])}, + {'qID': np.array([50, 20])}, {'η_exhale': np.array([0.92, 0.95])}, {'η_leaks': np.array([0.15, 0.20])}, {'exhalation_rate': np.array([0.75, 0.81])}, @@ -17,7 +17,7 @@ def test_infected_population_vectorisation(override_params): defaults = { 'virus_halflife': 1.1, 'viral_load_in_sputum': 1e9, - 'coefficient_of_infectivity': 0.02, + 'qID': 50, 'η_exhale': 0.95, 'η_leaks': 0.15, 'exhalation_rate': 0.75, @@ -40,7 +40,7 @@ def test_infected_population_vectorisation(override_params): virus=cara.models.Virus( halflife=defaults['virus_halflife'], viral_load_in_sputum=defaults['viral_load_in_sputum'], - coefficient_of_infectivity=defaults['coefficient_of_infectivity'], + qID=defaults['qID'], ), expiration=cara.models.Expiration( ejection_factor=(0.084, 0.009, 0.003, 0.002), From d5dcfa5a406753d1a07467af196e2072a3ca712c Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 14 May 2021 09:15:08 +0200 Subject: [PATCH 2/7] Virus class: input is now qID instead of coefficient_of_infectivity (which remains as a property), as in the CERN note --- cara/models.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cara/models.py b/cara/models.py index 1fa3fbe7..e5746155 100644 --- a/cara/models.py +++ b/cara/models.py @@ -432,8 +432,8 @@ class Virus: #: RNA copies / mL viral_load_in_sputum: _VectorisedFloat - #: Ratio between infectious aerosols and dose to cause infection. - coefficient_of_infectivity: _VectorisedFloat + #: RNA-copies per quantum + qID: _VectorisedFloat #: Pre-populated examples of Viruses. types: typing.ClassVar[typing.Dict[str, "Virus"]] @@ -443,26 +443,31 @@ class Virus: # Viral inactivation per hour (h^-1) return np.log(2) / self.halflife + @property + def coefficient_of_infectivity(self) -> _VectorisedFloat: + # Ratio between infectious aerosols and dose to cause infection. + return 1/self.qID + Virus.types = { 'SARS_CoV_2': Virus( halflife=1.1, viral_load_in_sputum=1e9, # No data on coefficient for SARS-CoV-2 yet. - # It is somewhere between 0.001 and 0.01 to have a 50% chance - # to cause infection. i.e. 1000 or 100 SARS-CoV viruses to cause infection. - coefficient_of_infectivity=0.02, + # It is somewhere between 1000 or 100 SARS-CoV viruses to have + # a 50% chance to cause infection. + qID=50., ), 'SARS_CoV_2_B117': Virus( # also called VOC-202012/01 halflife=1.1, viral_load_in_sputum=1e9, - coefficient_of_infectivity=1/30., + qID=30., ), 'SARS_CoV_2_P1': Virus( halflife=1.1, viral_load_in_sputum=1e9, - coefficient_of_infectivity=0.045, + qID=1/0.045, ), } From 64565264ea75f105746465fe064b879b44b439dd Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 14 May 2021 10:44:37 +0200 Subject: [PATCH 3/7] Draft Virus distributions (from M. Rognlien) - does not pass mypy tests and can't be cached yet --- cara/models.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cara/models.py b/cara/models.py index e5746155..961ac6b7 100644 --- a/cara/models.py +++ b/cara/models.py @@ -38,6 +38,8 @@ import typing import numpy as np from scipy.interpolate import interp1d import scipy.stats as sct +from sklearn.neighbors import KernelDensity + if not typing.TYPE_CHECKING: from memoization import cached @@ -438,6 +440,9 @@ class Virus: #: Pre-populated examples of Viruses. types: typing.ClassVar[typing.Dict[str, "Virus"]] + #: Pre-defined examples of virus distributions. + distributions: typing.ClassVar[typing.Dict[str, typing.Callable[[int], "Virus"]]] + @property def decay_constant(self) -> _VectorisedFloat: # Viral inactivation per hour (h^-1) @@ -471,6 +476,34 @@ Virus.types = { ), } +#@cached +def _generate_virus_distribution(samples: int, qID: float=100) -> Virus: + log_symptomatic_vl_frequencies = ((2.46032, 2.67431, 2.85434, 3.06155, 3.25856, 3.47256, 3.66957, 3.85979, 4.09927, 4.27081, + 4.47631, 4.66653, 4.87204, 5.10302, 5.27456, 5.46478, 5.6533, 5.88428, 6.07281, 6.30549, + 6.48552, 6.64856, 6.85407, 7.10373, 7.30075, 7.47229, 7.66081, 7.85782, 8.05653, 8.27053, + 8.48453, 8.65607, 8.90573, 9.06878, 9.27429, 9.473, 9.66152, 9.87552), + (0.001206885, 0.007851618, 0.008078144, 0.01502491, 0.013258014, 0.018528495, 0.020053765, + 0.021896167, 0.022047184, 0.018604005, 0.01547796, 0.018075445, 0.021503523, 0.022349217, + 0.025097721, 0.032875078, 0.030594727, 0.032573045, 0.034717482, 0.034792991, + 0.033267721, 0.042887485, 0.036846816, 0.03876473, 0.045016819, 0.040063473, 0.04883754, + 0.043944602, 0.048142864, 0.041588741, 0.048762031, 0.027921732, 0.033871788, + 0.022122693, 0.016927718, 0.008833228, 0.00478598, 0.002807662)) + kde_model = KernelDensity(kernel='gaussian', bandwidth=0.1) + kde_model.fit(np.asarray(log_symptomatic_vl_frequencies)[0, :][:, np.newaxis], + sample_weight=np.asarray(log_symptomatic_vl_frequencies)[1, :]) + viral_load_distribution = kde_model.sample(n_samples=samples)[:, 0] + return Virus( + halflife=1.1, + viral_load_in_sputum=viral_load_distribution, + qID=qID, + ) + +Virus.distributions = { + 'SARS_CoV_2': lambda n: _generate_virus_distribution(n, qID=100), + 'SARS_CoV_2_B117': lambda n: _generate_virus_distribution(n, qID=60), + 'SARS_CoV_2_P1': lambda n: _generate_virus_distribution(n, qID=100/2.25), +} + @dataclass(frozen=True) class Mask: From 2e659da45d184d296e9b76a938b2834667ece088 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 14 May 2021 12:49:17 +0200 Subject: [PATCH 4/7] caching _generate_virus_distribution and ignoring mypy type checking for KernelDensity --- cara/models.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cara/models.py b/cara/models.py index 961ac6b7..041c208b 100644 --- a/cara/models.py +++ b/cara/models.py @@ -38,7 +38,7 @@ import typing import numpy as np from scipy.interpolate import interp1d import scipy.stats as sct -from sklearn.neighbors import KernelDensity +from sklearn.neighbors import KernelDensity # type: ignore if not typing.TYPE_CHECKING: @@ -476,8 +476,9 @@ Virus.types = { ), } -#@cached -def _generate_virus_distribution(samples: int, qID: float=100) -> Virus: +@cached +def _generate_virus_distribution(params: typing.Tuple[int, float]) -> Virus: + samples , qID = params log_symptomatic_vl_frequencies = ((2.46032, 2.67431, 2.85434, 3.06155, 3.25856, 3.47256, 3.66957, 3.85979, 4.09927, 4.27081, 4.47631, 4.66653, 4.87204, 5.10302, 5.27456, 5.46478, 5.6533, 5.88428, 6.07281, 6.30549, 6.48552, 6.64856, 6.85407, 7.10373, 7.30075, 7.47229, 7.66081, 7.85782, 8.05653, 8.27053, @@ -499,9 +500,9 @@ def _generate_virus_distribution(samples: int, qID: float=100) -> Virus: ) Virus.distributions = { - 'SARS_CoV_2': lambda n: _generate_virus_distribution(n, qID=100), - 'SARS_CoV_2_B117': lambda n: _generate_virus_distribution(n, qID=60), - 'SARS_CoV_2_P1': lambda n: _generate_virus_distribution(n, qID=100/2.25), + 'SARS_CoV_2': lambda n: _generate_virus_distribution((n, 100)), + 'SARS_CoV_2_B117': lambda n: _generate_virus_distribution((n, 60)), + 'SARS_CoV_2_P1': lambda n: _generate_virus_distribution((n, 100/2.25)), } From de90292338e9fef6e620844372577b77a2ac8153 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 14 May 2021 12:52:38 +0200 Subject: [PATCH 5/7] Correcting viral load distributions (10** was missing) --- cara/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cara/models.py b/cara/models.py index 041c208b..5d5b7693 100644 --- a/cara/models.py +++ b/cara/models.py @@ -492,7 +492,7 @@ def _generate_virus_distribution(params: typing.Tuple[int, float]) -> Virus: kde_model = KernelDensity(kernel='gaussian', bandwidth=0.1) kde_model.fit(np.asarray(log_symptomatic_vl_frequencies)[0, :][:, np.newaxis], sample_weight=np.asarray(log_symptomatic_vl_frequencies)[1, :]) - viral_load_distribution = kde_model.sample(n_samples=samples)[:, 0] + viral_load_distribution = 10 ** kde_model.sample(n_samples=samples)[:, 0] return Virus( halflife=1.1, viral_load_in_sputum=viral_load_distribution, From 3e9652d012fa666df7360ad8aef9f00257409f6e Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Tue, 25 May 2021 16:25:30 +0200 Subject: [PATCH 6/7] Removing pre-defined distributions; removing coefficient_of_infectivity --- cara/models.py | 41 ++--------------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/cara/models.py b/cara/models.py index a55cb9d3..2d41f33b 100644 --- a/cara/models.py +++ b/cara/models.py @@ -425,19 +425,11 @@ class Virus: #: Pre-populated examples of Viruses. types: typing.ClassVar[typing.Dict[str, "Virus"]] - #: Pre-defined examples of virus distributions. - distributions: typing.ClassVar[typing.Dict[str, typing.Callable[[int], "Virus"]]] - @property def decay_constant(self) -> _VectorisedFloat: # Viral inactivation per hour (h^-1) return np.log(2) / self.halflife - @property - def coefficient_of_infectivity(self) -> _VectorisedFloat: - # Ratio between infectious aerosols and dose to cause infection. - return 1/self.qID - Virus.types = { 'SARS_CoV_2': Virus( @@ -461,35 +453,6 @@ Virus.types = { ), } -@cached -def _generate_virus_distribution(params: typing.Tuple[int, float]) -> Virus: - samples , qID = params - log_symptomatic_vl_frequencies = ((2.46032, 2.67431, 2.85434, 3.06155, 3.25856, 3.47256, 3.66957, 3.85979, 4.09927, 4.27081, - 4.47631, 4.66653, 4.87204, 5.10302, 5.27456, 5.46478, 5.6533, 5.88428, 6.07281, 6.30549, - 6.48552, 6.64856, 6.85407, 7.10373, 7.30075, 7.47229, 7.66081, 7.85782, 8.05653, 8.27053, - 8.48453, 8.65607, 8.90573, 9.06878, 9.27429, 9.473, 9.66152, 9.87552), - (0.001206885, 0.007851618, 0.008078144, 0.01502491, 0.013258014, 0.018528495, 0.020053765, - 0.021896167, 0.022047184, 0.018604005, 0.01547796, 0.018075445, 0.021503523, 0.022349217, - 0.025097721, 0.032875078, 0.030594727, 0.032573045, 0.034717482, 0.034792991, - 0.033267721, 0.042887485, 0.036846816, 0.03876473, 0.045016819, 0.040063473, 0.04883754, - 0.043944602, 0.048142864, 0.041588741, 0.048762031, 0.027921732, 0.033871788, - 0.022122693, 0.016927718, 0.008833228, 0.00478598, 0.002807662)) - kde_model = KernelDensity(kernel='gaussian', bandwidth=0.1) - kde_model.fit(np.asarray(log_symptomatic_vl_frequencies)[0, :][:, np.newaxis], - sample_weight=np.asarray(log_symptomatic_vl_frequencies)[1, :]) - viral_load_distribution = 10 ** kde_model.sample(n_samples=samples)[:, 0] - return Virus( - halflife=1.1, - viral_load_in_sputum=viral_load_distribution, - qID=qID, - ) - -Virus.distributions = { - 'SARS_CoV_2': lambda n: _generate_virus_distribution((n, 100)), - 'SARS_CoV_2_B117': lambda n: _generate_virus_distribution((n, 60)), - 'SARS_CoV_2_P1': lambda n: _generate_virus_distribution((n, 100/2.25)), -} - @dataclass(frozen=True) class Mask: @@ -621,10 +584,10 @@ class InfectedPopulation(Population): aerosols = self.expiration.aerosols(self.mask) ER = (self.virus.viral_load_in_sputum * - self.virus.coefficient_of_infectivity * self.activity.exhalation_rate * 10 ** 6 * - aerosols) + aerosols / + self.virus.qID) # For superspreading event, where ejection_factor is infinite we fix the ER # based on Miller et al. (2020). From ca1440bf5c153d0c6c2c78991bf5ed1b0a0bda9e Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 26 May 2021 08:55:38 +0200 Subject: [PATCH 7/7] Replacing qID by quantum_infectious_dose --- cara/models.py | 10 +++++----- cara/tests/models/test_concentration_model.py | 6 +++--- cara/tests/test_infected_population.py | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cara/models.py b/cara/models.py index 2d41f33b..96120de9 100644 --- a/cara/models.py +++ b/cara/models.py @@ -420,7 +420,7 @@ class Virus: viral_load_in_sputum: _VectorisedFloat #: RNA-copies per quantum - qID: _VectorisedFloat + quantum_infectious_dose: _VectorisedFloat #: Pre-populated examples of Viruses. types: typing.ClassVar[typing.Dict[str, "Virus"]] @@ -438,18 +438,18 @@ Virus.types = { # No data on coefficient for SARS-CoV-2 yet. # It is somewhere between 1000 or 100 SARS-CoV viruses to have # a 50% chance to cause infection. - qID=50., + quantum_infectious_dose=50., ), 'SARS_CoV_2_B117': Virus( # also called VOC-202012/01 halflife=1.1, viral_load_in_sputum=1e9, - qID=30., + quantum_infectious_dose=30., ), 'SARS_CoV_2_P1': Virus( halflife=1.1, viral_load_in_sputum=1e9, - qID=1/0.045, + quantum_infectious_dose=1/0.045, ), } @@ -587,7 +587,7 @@ class InfectedPopulation(Population): self.activity.exhalation_rate * 10 ** 6 * aerosols / - self.virus.qID) + self.virus.quantum_infectious_dose) # For superspreading event, where ejection_factor is infinite we fix the ER # based on Miller et al. (2020). diff --git a/cara/tests/models/test_concentration_model.py b/cara/tests/models/test_concentration_model.py index c0be20fd..03a042d6 100644 --- a/cara/tests/models/test_concentration_model.py +++ b/cara/tests/models/test_concentration_model.py @@ -12,7 +12,7 @@ from cara import models {'air_change': np.array([100, 120])}, {'virus_halflife': np.array([1.1, 1.5])}, {'viral_load_in_sputum': np.array([5e8, 1e9])}, - {'qID': np.array([50, 20])}, + {'quantum_infectious_dose': np.array([50, 20])}, {'η_exhale': np.array([0.92, 0.95])}, {'η_leaks': np.array([0.15, 0.20])}, ] @@ -23,7 +23,7 @@ def test_concentration_model_vectorisation(override_params): 'air_change': 100, 'virus_halflife': 1.1, 'viral_load_in_sputum': 1e9, - 'qID': 50, + 'quantum_infectious_dose': 50, 'η_exhale': 0.95, 'η_leaks': 0.15, } @@ -48,7 +48,7 @@ def test_concentration_model_vectorisation(override_params): virus=models.Virus( halflife=defaults['virus_halflife'], viral_load_in_sputum=defaults['viral_load_in_sputum'], - qID=defaults['qID'], + quantum_infectious_dose=defaults['quantum_infectious_dose'], ), expiration=models.Expiration( ejection_factor=(0.084, 0.009, 0.003, 0.002), diff --git a/cara/tests/test_infected_population.py b/cara/tests/test_infected_population.py index 6ee9758b..c237e251 100644 --- a/cara/tests/test_infected_population.py +++ b/cara/tests/test_infected_population.py @@ -7,7 +7,7 @@ import cara.models @pytest.mark.parametrize( "override_params", [ {'viral_load_in_sputum': np.array([5e8, 1e9])}, - {'qID': np.array([50, 20])}, + {'quantum_infectious_dose': np.array([50, 20])}, {'η_exhale': np.array([0.92, 0.95])}, {'η_leaks': np.array([0.15, 0.20])}, {'exhalation_rate': np.array([0.75, 0.81])}, @@ -17,7 +17,7 @@ def test_infected_population_vectorisation(override_params): defaults = { 'virus_halflife': 1.1, 'viral_load_in_sputum': 1e9, - 'qID': 50, + 'quantum_infectious_dose': 50, 'η_exhale': 0.95, 'η_leaks': 0.15, 'exhalation_rate': 0.75, @@ -40,7 +40,7 @@ def test_infected_population_vectorisation(override_params): virus=cara.models.Virus( halflife=defaults['virus_halflife'], viral_load_in_sputum=defaults['viral_load_in_sputum'], - qID=defaults['qID'], + quantum_infectious_dose=defaults['quantum_infectious_dose'], ), expiration=cara.models.Expiration( ejection_factor=(0.084, 0.009, 0.003, 0.002),