Removing completely the previous BLO implementation for Expiration; tests updated accordingly
This commit is contained in:
parent
85f0f1708e
commit
ca6e70f2f9
3 changed files with 20 additions and 53 deletions
|
|
@ -552,56 +552,20 @@ class _ExpirationBase:
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Expiration(_ExpirationBase):
|
class Expiration(_ExpirationBase):
|
||||||
"""
|
"""
|
||||||
BLO model for the expiration (G. Johnson et al., Modality of human
|
Model for the expiration. For a given diameter of aerosol, provides
|
||||||
expired aerosol size distributions, Journal of Aerosol Science,
|
the aerosol volume, weighted by the mask outward efficiency when
|
||||||
vol. 42, no. 12, pp. 839 – 851, 2011,
|
applicable.
|
||||||
https://doi.org/10.1016/j.jaerosci.2011.07.009).
|
|
||||||
Here all diameters (d) are in microns.
|
|
||||||
"""
|
"""
|
||||||
#: factors assigned to resp. the B, L and O modes. They are
|
|
||||||
# charateristics of the kind of expiratory activity (e.g. breathing,
|
|
||||||
# speaking, singing, or shouting).
|
|
||||||
BLO_factors: typing.Tuple[float, float, float]
|
|
||||||
|
|
||||||
#: diameter of the aerosol in microns
|
#: diameter of the aerosol in microns
|
||||||
diameter: _VectorisedFloat
|
diameter: _VectorisedFloat
|
||||||
|
|
||||||
@cached()
|
|
||||||
def aerosols_(self, mask: Mask):
|
|
||||||
""" Result is in mL.cm^-3 """
|
|
||||||
def volume(d):
|
|
||||||
return (np.pi * d**3) / 6.
|
|
||||||
|
|
||||||
def _Bmode(d: float) -> float:
|
|
||||||
# B-mode (see ref. above).
|
|
||||||
return ( (1 / d) * (0.1 / (np.sqrt(2 * np.pi) * 0.262364)) *
|
|
||||||
np.exp(-1 * (np.log(d) - 0.989541) ** 2 / (2 * 0.262364 ** 2)))
|
|
||||||
|
|
||||||
def _Lmode(d: float) -> float:
|
|
||||||
# L-mode (see ref. above).
|
|
||||||
return ( (1 / d) * (1.0 / (np.sqrt(2 * np.pi) * 0.506818)) *
|
|
||||||
np.exp(-1 * (np.log(d) - 1.38629) ** 2 / (2 * 0.506818 ** 2)))
|
|
||||||
|
|
||||||
def _Omode(d: float) -> float:
|
|
||||||
# O-mode (see ref. above).
|
|
||||||
return ( (1 / d) * (0.0010008 / (np.sqrt(2 * np.pi) * 0.585005)) *
|
|
||||||
np.exp(-1 * (np.log(d) - 4.97673) ** 2 / (2 * 0.585005 ** 2)))
|
|
||||||
|
|
||||||
def integrand(d: float) -> float:
|
|
||||||
return (self.BLO_factors[0] * _Bmode(d) +
|
|
||||||
self.BLO_factors[1] * _Lmode(d) +
|
|
||||||
self.BLO_factors[2] * _Omode(d)
|
|
||||||
) * volume(d) * (1 - mask.exhale_efficiency(d))
|
|
||||||
|
|
||||||
# final result converted from microns^3/cm3 to mL/cm^3
|
|
||||||
return scipy.integrate.quad(integrand, 0.1, 30.)[0]*1e-12
|
|
||||||
|
|
||||||
@cached()
|
@cached()
|
||||||
def aerosols(self, mask: Mask):
|
def aerosols(self, mask: Mask):
|
||||||
""" Result is in mL.cm^-3 """
|
""" Result is in mL.cm^-3 """
|
||||||
def volume(d):
|
def volume(d):
|
||||||
return (np.pi * d**3) / 6.
|
return (np.pi * d**3) / 6.
|
||||||
|
|
||||||
|
# final result converted from microns^3/cm3 to mL/cm^3
|
||||||
return (volume(self.diameter) *
|
return (volume(self.diameter) *
|
||||||
(1 - mask.exhale_efficiency(self.diameter))) * 1e-12
|
(1 - mask.exhale_efficiency(self.diameter))) * 1e-12
|
||||||
|
|
||||||
|
|
@ -632,12 +596,14 @@ class MultipleExpiration(_ExpirationBase):
|
||||||
|
|
||||||
# Typical expirations. The aerosol diameter given is an equivalent
|
# Typical expirations. The aerosol diameter given is an equivalent
|
||||||
# diameter, chosen in such a way that the aerosol volume is
|
# diameter, chosen in such a way that the aerosol volume is
|
||||||
# the same as the total aerosol volume given by the full BLO model.
|
# the same as the total aerosol volume given by the full BLO model
|
||||||
|
# (integrated between 0.1 and 30 microns)
|
||||||
|
# The correspondence with the BlO coefficients is given.
|
||||||
_ExpirationBase.types = {
|
_ExpirationBase.types = {
|
||||||
'Breathing': Expiration((1., 0., 0.)),
|
'Breathing': Expiration(1.3844), # corresponds to B/L/O coefficients of (1, 0, 0)
|
||||||
'Talking': Expiration((1., 1., 1.)),
|
'Talking': Expiration(5.8925), # corresponds to B/L/O coefficients of (1, 1, 1)
|
||||||
'Shouting': Expiration((1., 5., 5.)),
|
'Shouting': Expiration(10.0411), # corresponds to B/L/O coefficients of (1, 5, 5)
|
||||||
'Singing': Expiration((1., 5., 5.)),
|
'Singing': Expiration(10.0411), # corresponds to B/L/O coefficients of (1, 5, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from cara import models
|
||||||
|
|
||||||
def test_multiple_wrong_weight_size():
|
def test_multiple_wrong_weight_size():
|
||||||
weights = (1., 2., 3.)
|
weights = (1., 2., 3.)
|
||||||
e_base = models.Expiration((1., 0., 0.), 2.5)
|
e_base = models.Expiration(2.5)
|
||||||
with pytest.raises(
|
with pytest.raises(
|
||||||
ValueError,
|
ValueError,
|
||||||
match=re.escape("expirations and weigths should contain the"
|
match=re.escape("expirations and weigths should contain the"
|
||||||
|
|
@ -20,12 +20,12 @@ def test_multiple_wrong_weight_size():
|
||||||
|
|
||||||
def test_multiple():
|
def test_multiple():
|
||||||
weights = (1., 1.)
|
weights = (1., 1.)
|
||||||
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']
|
mask = models.Mask.types['Type I']
|
||||||
npt.assert_almost_equal(e_expected.aerosols(mask), e.aerosols(mask))
|
e1 = models.Expiration.types['Breathing']
|
||||||
|
e2 = models.Expiration.types['Singing']
|
||||||
|
aerosol_expected = (e1.aerosols(mask) + e2.aerosols(mask))/2.
|
||||||
|
e = models.MultipleExpiration([e1, e2], weights)
|
||||||
|
npt.assert_almost_equal(aerosol_expected, e.aerosols(mask))
|
||||||
|
|
||||||
|
|
||||||
# expected values obtained from another code
|
# expected values obtained from another code
|
||||||
|
|
|
||||||
|
|
@ -218,10 +218,11 @@ def skagit_chorale_mc():
|
||||||
presence=mc.SpecificInterval(((0., 2.5),)),
|
presence=mc.SpecificInterval(((0., 2.5),)),
|
||||||
mask=models.Mask.types["No mask"],
|
mask=models.Mask.types["No mask"],
|
||||||
activity=activity_distributions['Light activity'],
|
activity=activity_distributions['Light activity'],
|
||||||
expiration=models.Expiration((5., 5., 5.), 10.0761),
|
expiration=models.Expiration(10.0761),
|
||||||
# The aerosol diameter given (10.0761 microns) is an equivalent
|
# The aerosol diameter given (10.0761 microns) is an equivalent
|
||||||
# diameter, chosen in such a way that the aerosol volume is
|
# diameter, chosen in such a way that the aerosol volume is
|
||||||
# the same as the total aerosol volume given by the full BLO model.
|
# the same as the total aerosol volume given by the full BLO model
|
||||||
|
# with (5, 5, 5) for the B/L/O weights.
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return mc.ExposureModel(
|
return mc.ExposureModel(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue