From 1564b9ea656af7d3268e1f95732fd2a505b2cd8d Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Tue, 11 May 2021 18:17:43 +0200 Subject: [PATCH 1/5] Adding tests on vectorisation of Activity inhalation_rate and exhalation_rate --- cara/tests/models/test_exposure_model.py | 27 +++++++++++++++++++----- cara/tests/test_infected_population.py | 6 ++++-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/cara/tests/models/test_exposure_model.py b/cara/tests/models/test_exposure_model.py index 34ed03af..464f25a9 100644 --- a/cara/tests/models/test_exposure_model.py +++ b/cara/tests/models/test_exposure_model.py @@ -46,20 +46,37 @@ populations = [ 10, halftime, models.Mask(0.95, 0.15, np.array([0.3, 0.35])), models.Activity.types['Standing'], ), + # A population with some array component for inhalation_rate. + models.Population( + 10, halftime, models.Mask.types['Type I'], + models.Activity(np.array([0.51,0.57]), 0.57), + ), ] @pytest.mark.parametrize( - "population, cm, expected_exposure",[ - [populations[1], KnownConcentrations(lambda t: 1.2), np.array([14.4, 14.4])], - [populations[0], KnownConcentrations(lambda t: np.array([1.2, 2.4])), np.array([14.4, 28.8])], - [populations[1], KnownConcentrations(lambda t: np.array([1.2, 2.4])), np.array([14.4, 28.8])], + "population, cm, expected_exposure, expected_probability",[ + [populations[1], KnownConcentrations(lambda t: 1.2), + np.array([14.4, 14.4]), np.array([99.6803184113, 99.5181053773])], + + [populations[2], KnownConcentrations(lambda t: 1.2), + np.array([14.4, 14.4]), np.array([99.4146994564, 99.6803184113])], + + [populations[0], KnownConcentrations(lambda t: np.array([1.2, 2.4])), + np.array([14.4, 28.8]), np.array([99.6803184113, 99.9989780368])], + + [populations[1], KnownConcentrations(lambda t: np.array([1.2, 2.4])), + np.array([14.4, 28.8]), np.array([99.6803184113, 99.9976777757])], ]) -def test_exposure_model_ndarray(population, cm, expected_exposure): +def test_exposure_model_ndarray(population, cm, + expected_exposure, expected_probability): model = ExposureModel(cm, population) np.testing.assert_almost_equal( model.quanta_exposure(), expected_exposure ) + np.testing.assert_almost_equal( + model.infection_probability(), expected_probability, decimal=10 + ) assert isinstance(model.infection_probability(), np.ndarray) assert isinstance(model.expected_new_cases(), np.ndarray) diff --git a/cara/tests/test_infected_population.py b/cara/tests/test_infected_population.py index ab9e36d3..39fbc039 100644 --- a/cara/tests/test_infected_population.py +++ b/cara/tests/test_infected_population.py @@ -3,13 +3,14 @@ import pytest 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])}, {'η_exhale': np.array([0.92, 0.95])}, {'η_leaks': np.array([0.15, 0.20])}, - + {'exhalation_rate': np.array([0.75, 0.81])}, ] ) def test_infected_population_vectorisation(override_params): @@ -19,6 +20,7 @@ def test_infected_population_vectorisation(override_params): 'coefficient_of_infectivity': 0.02, 'η_exhale': 0.95, 'η_leaks': 0.15, + 'exhalation_rate': 0.75, } defaults.update(override_params) @@ -33,7 +35,7 @@ def test_infected_population_vectorisation(override_params): ), activity=cara.models.Activity( 0.51, - 0.75, + defaults['exhalation_rate'], ), virus=cara.models.Virus( halflife=defaults['virus_halflife'], From 17ffc4f6f85bdf0df48b3d63088a460e2ec07cc1 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Tue, 11 May 2021 18:18:19 +0200 Subject: [PATCH 2/5] Vectorisation of Activity --- cara/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cara/models.py b/cara/models.py index ec81b37c..291dcfc1 100644 --- a/cara/models.py +++ b/cara/models.py @@ -526,8 +526,8 @@ Expiration.types = { @dataclass(frozen=True) class Activity: - inhalation_rate: float - exhalation_rate: float + inhalation_rate: _VectorisedFloat + exhalation_rate: _VectorisedFloat #: Pre-populated examples of activities. types: typing.ClassVar[typing.Dict[str, "Activity"]] From b6bb878fc267b9418304ae9b46d811decf40897f Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Tue, 11 May 2021 20:00:40 +0200 Subject: [PATCH 3/5] Adding predefined Activity distributions --- cara/models.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/cara/models.py b/cara/models.py index 291dcfc1..1fa3fbe7 100644 --- a/cara/models.py +++ b/cara/models.py @@ -37,6 +37,7 @@ import typing import numpy as np from scipy.interpolate import interp1d +import scipy.stats as sct if not typing.TYPE_CHECKING: from memoization import cached @@ -55,6 +56,18 @@ _VectorisedFloat = typing.Union[float, np.ndarray] _VectorisedInt = typing.Union[int, np.ndarray] +def _lognormal_distribution(csi: float, lamb: float, samples: int) -> np.ndarray: + """ + Generates a number of samples from a specified lognormal distribution + :param csi: parameter used to specify the lognormal distribution + :param lamb: parameter used to specify the lognormal distribution + :param samples: number of samples to be generated + :return: numpy-array containing the samples + """ + sf_norm = sct.norm.sf(np.random.normal(size=samples)) + return sct.lognorm.isf(sf_norm, csi, loc=0, scale=np.exp(lamb)) + + @dataclass(frozen=True) class Room: #: The total volume of the room @@ -532,6 +545,9 @@ class Activity: #: Pre-populated examples of activities. types: typing.ClassVar[typing.Dict[str, "Activity"]] + #: Pre-defined examples of activity distributions. + distributions: typing.ClassVar[typing.Dict[str, typing.Callable[[int], "Activity"]]] + Activity.types = { 'Seated': Activity(0.51, 0.51), @@ -541,6 +557,29 @@ Activity.types = { 'Heavy exercise': Activity(3.30, 3.30), } +def _generate_activity_distribution(csi: float, lamb: float, samples: int): + return Activity( + _lognormal_distribution(csi, lamb, samples), + _lognormal_distribution(csi, lamb, samples), + ) + +Activity.distributions = { + 'Seated': lambda n: _generate_activity_distribution( + 0.10498338229297108, -0.6872121723362303, n), + + 'Standing': lambda n: _generate_activity_distribution( + 0.09373162411398223, -0.5742377578494785, n), + + 'Light activity': lambda n: _generate_activity_distribution( + 0.09435378091059601, 0.21380242785625422, n), + + 'Moderate activity': lambda n: _generate_activity_distribution( + 0.1894616357138137, 0.551771330362601, n), + + 'Heavy exercise': lambda n: _generate_activity_distribution( + 0.21744554768657565, 1.1644665696723049, n), +} + @dataclass(frozen=True) class Population: From 57c7c78117ff96f85564f1062418af1197ea16f1 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 14 May 2021 12:38:35 +0200 Subject: [PATCH 4/5] caching _generate_activity_distribution (for Activity models) --- cara/models.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cara/models.py b/cara/models.py index 1fa3fbe7..9b1185ad 100644 --- a/cara/models.py +++ b/cara/models.py @@ -557,27 +557,29 @@ Activity.types = { 'Heavy exercise': Activity(3.30, 3.30), } -def _generate_activity_distribution(csi: float, lamb: float, samples: int): +@cached +def _generate_activity_distribution(params: typing.Tuple[float, float, int]) -> Activity: + csi, lamb, samples = params return Activity( _lognormal_distribution(csi, lamb, samples), _lognormal_distribution(csi, lamb, samples), ) Activity.distributions = { - 'Seated': lambda n: _generate_activity_distribution( - 0.10498338229297108, -0.6872121723362303, n), + 'Seated': lambda n: _generate_activity_distribution(( + 0.10498338229297108, -0.6872121723362303, n)), - 'Standing': lambda n: _generate_activity_distribution( - 0.09373162411398223, -0.5742377578494785, n), + 'Standing': lambda n: _generate_activity_distribution(( + 0.09373162411398223, -0.5742377578494785, n)), - 'Light activity': lambda n: _generate_activity_distribution( - 0.09435378091059601, 0.21380242785625422, n), + 'Light activity': lambda n: _generate_activity_distribution(( + 0.09435378091059601, 0.21380242785625422, n)), - 'Moderate activity': lambda n: _generate_activity_distribution( - 0.1894616357138137, 0.551771330362601, n), + 'Moderate activity': lambda n: _generate_activity_distribution(( + 0.1894616357138137, 0.551771330362601, n)), - 'Heavy exercise': lambda n: _generate_activity_distribution( - 0.21744554768657565, 1.1644665696723049, n), + 'Heavy exercise': lambda n: _generate_activity_distribution(( + 0.21744554768657565, 1.1644665696723049, n)), } From d76e7a98d50846038d7cff72264f9a2b00a0f05f Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 21 May 2021 11:03:53 +0200 Subject: [PATCH 5/5] Removing pre-defined distributions for Activity --- cara/models.py | 41 ----------------------------------------- 1 file changed, 41 deletions(-) diff --git a/cara/models.py b/cara/models.py index 9b1185ad..291dcfc1 100644 --- a/cara/models.py +++ b/cara/models.py @@ -37,7 +37,6 @@ import typing import numpy as np from scipy.interpolate import interp1d -import scipy.stats as sct if not typing.TYPE_CHECKING: from memoization import cached @@ -56,18 +55,6 @@ _VectorisedFloat = typing.Union[float, np.ndarray] _VectorisedInt = typing.Union[int, np.ndarray] -def _lognormal_distribution(csi: float, lamb: float, samples: int) -> np.ndarray: - """ - Generates a number of samples from a specified lognormal distribution - :param csi: parameter used to specify the lognormal distribution - :param lamb: parameter used to specify the lognormal distribution - :param samples: number of samples to be generated - :return: numpy-array containing the samples - """ - sf_norm = sct.norm.sf(np.random.normal(size=samples)) - return sct.lognorm.isf(sf_norm, csi, loc=0, scale=np.exp(lamb)) - - @dataclass(frozen=True) class Room: #: The total volume of the room @@ -545,9 +532,6 @@ class Activity: #: Pre-populated examples of activities. types: typing.ClassVar[typing.Dict[str, "Activity"]] - #: Pre-defined examples of activity distributions. - distributions: typing.ClassVar[typing.Dict[str, typing.Callable[[int], "Activity"]]] - Activity.types = { 'Seated': Activity(0.51, 0.51), @@ -557,31 +541,6 @@ Activity.types = { 'Heavy exercise': Activity(3.30, 3.30), } -@cached -def _generate_activity_distribution(params: typing.Tuple[float, float, int]) -> Activity: - csi, lamb, samples = params - return Activity( - _lognormal_distribution(csi, lamb, samples), - _lognormal_distribution(csi, lamb, samples), - ) - -Activity.distributions = { - 'Seated': lambda n: _generate_activity_distribution(( - 0.10498338229297108, -0.6872121723362303, n)), - - 'Standing': lambda n: _generate_activity_distribution(( - 0.09373162411398223, -0.5742377578494785, n)), - - 'Light activity': lambda n: _generate_activity_distribution(( - 0.09435378091059601, 0.21380242785625422, n)), - - 'Moderate activity': lambda n: _generate_activity_distribution(( - 0.1894616357138137, 0.551771330362601, n)), - - 'Heavy exercise': lambda n: _generate_activity_distribution(( - 0.21744554768657565, 1.1644665696723049, n)), -} - @dataclass(frozen=True) class Population: