diff --git a/cara/models.py b/cara/models.py index 296c260c..ddc9f238 100644 --- a/cara/models.py +++ b/cara/models.py @@ -560,8 +560,11 @@ class Expiration(_ExpirationBase): # speaking, singing, or shouting). BLO_factors: typing.Tuple[float, float, float] + #: diameter of the aerosol in microns + diameter: _VectorisedFloat + @cached() - def aerosols(self, mask: Mask): + def aerosols_(self, mask: Mask): """ Result is in mL.cm^-3 """ def volume(d): return (np.pi * d**3) / 6. @@ -590,6 +593,15 @@ class Expiration(_ExpirationBase): # final result converted from microns^3/cm3 to mL/cm^3 return scipy.integrate.quad(integrand, 0.1, 30.)[0]*1e-12 + @cached() + def aerosols(self, mask: Mask): + """ Result is in mL.cm^-3 """ + def volume(d): + return (np.pi * d**3) / 6. + + return (volume(self.diameter) * + (1 - mask.exhale_efficiency(self.diameter))) * 1e-12 + @dataclass(frozen=True) class MultipleExpiration(_ExpirationBase): @@ -616,10 +628,11 @@ class MultipleExpiration(_ExpirationBase): _ExpirationBase.types = { - 'Breathing': Expiration((1., 0., 0.)), - 'Talking': Expiration((1., 1., 1.)), - 'Shouting': Expiration((1., 5., 5.)), - 'Singing': Expiration((1., 5., 5.)), + 'Breathing': Expiration((1., 0., 0.), 1.3844), + 'Talking': Expiration((1., 1., 1.), 5.8925), + 'Shouting': Expiration((1., 5., 5.), 10.0411), + 'Singing': Expiration((1., 5., 5.), 10.0411), + 'Superspreading event': Expiration((np.inf, 0., 0.), 10.0411), } @@ -748,7 +761,7 @@ class ConcentrationModel: def infectious_virus_removal_rate(self, time: float) -> _VectorisedFloat: # Particle deposition on the floor (value from CERN-OPEN-2021-04) - vg = 1.88e-4 + vg = 1.88e-4 # value corresponding to a diameter of 2.5 microns # Height of the emission source to the floor - i.e. mouth/nose (m) h = 1.5 # Deposition rate (h^-1) diff --git a/cara/tests/test_expiration.py b/cara/tests/test_expiration.py index 434caf8c..f82bbd63 100644 --- a/cara/tests/test_expiration.py +++ b/cara/tests/test_expiration.py @@ -9,7 +9,7 @@ from cara import models def test_multiple_wrong_weight_size(): weights = (1., 2., 3.) - e_base = models.Expiration((1., 0., 0.)) + e_base = models.Expiration((1., 0., 0.), 2.5) with pytest.raises( ValueError, match=re.escape("expirations and weigths should contain the" @@ -20,9 +20,9 @@ def test_multiple_wrong_weight_size(): def test_multiple(): weights = (1., 1.) - e1 = models.Expiration((1., 0., 0.)) - e2 = models.Expiration((4., 5., 5.)) - e_expected = models.Expiration((2.5, 2.5, 2.5)) + e1 = models.Expiration((1., 0., 0.), 2.5) + e2 = models.Expiration((4., 5., 5.), 2.5) + e_expected = models.Expiration((2.5, 2.5, 2.5), 2.5) e = models.MultipleExpiration([e1, e2], weights) mask = models.Mask.types['Type I'] npt.assert_almost_equal(e_expected.aerosols(mask), e.aerosols(mask)) @@ -30,14 +30,14 @@ def test_multiple(): # expected values obtained from another code @pytest.mark.parametrize( - "BLO_weights, expected_aerosols", + "expiration_type, expected_aerosols", [ - [(1.,0.,0.), 1.38924e-12], - [(1.,1.,1.), 1.07129e-10], - [(1.,5.,5.), 5.30088e-10], + ['Breathing', 1.38924e-12], + ['Talking', 1.07129e-10], + ['Shouting', 5.30088e-10], ], ) -def test_expiration_aerosols(BLO_weights, expected_aerosols): +def test_expiration_aerosols(expiration_type, expected_aerosols): mask = models.Mask.types['No mask'] - e = models.Expiration(BLO_weights) + e = models._ExpirationBase.types[expiration_type] npt.assert_allclose(e.aerosols(mask), expected_aerosols, rtol=1e-4)