New method inhale_efficiency in _MaskBase; using _MaskBase everywhere needed (also for types); new MeasuredMask with different exhale_efficiency functions; new mask types

This commit is contained in:
Nicolas Mounet 2021-05-26 23:39:33 +02:00 committed by Phil Elson
parent 78cdb22798
commit adc11cb527
3 changed files with 58 additions and 18 deletions

View file

@ -269,10 +269,10 @@ class FormData:
else:
return ventilation
def mask(self) -> models.Mask:
def mask(self) -> models._MaskBase:
# Initializes the mask type if mask wearing is "continuous", otherwise instantiates the mask attribute as
# the "No mask"-mask
mask = models.Mask.types[self.mask_type if self.mask_wearing_option == "mask_on" else 'No mask']
mask = models._MaskBase.types[self.mask_type if self.mask_wearing_option == "mask_on" else 'No mask']
return mask
def infected_population(self) -> models.InfectedPopulation:

View file

@ -390,10 +390,10 @@ class ModelWidgets(View):
def _build_mask(self, node):
mask = node.dcs_instance()
for name, mask_ in models.Mask.types.items():
for name, mask_ in models._MaskBase.types.items():
if mask == mask_:
break
mask_choice = widgets.Select(options=list(models.Mask.types.keys()), value=name)
mask_choice = widgets.Select(options=list(models._MaskBase.types.keys()), value=name)
def on_mask_change(change):
node.dcs_select(change['new'])
@ -496,7 +496,7 @@ baseline_model = models.ExposureModel(
number=1,
virus=models.Virus.types['SARS_CoV_2'],
presence=models.SpecificInterval(((8, 12), (13, 17))),
mask=models.Mask.types['No mask'],
mask=models._MaskBase.types['No mask'],
activity=models.Activity.types['Seated'],
expiration=models.Expiration.types['Talking'],
),
@ -505,7 +505,7 @@ baseline_model = models.ExposureModel(
number=10,
presence=models.SpecificInterval(((8, 12), (13, 17))),
activity=models.Activity.types['Seated'],
mask=models.Mask.types['No mask'],
mask=models._MaskBase.types['No mask'],
),
)
@ -515,10 +515,10 @@ class CARAStateBuilder(state.StateBuilder):
# For example, build_type__VentilationBase is called when dealing with ConcentrationModel
# types as it has a ventilation: _VentilationBase field.
def build_type_Mask(self, _: dataclasses.Field):
def build_type__MaskBase(self, _: dataclasses.Field):
return state.DataclassStatePredefined(
models.Mask,
choices=models.Mask.types,
models._MaskBase,
choices=models._MaskBase.types,
)
def build_type_Virus(self, _: dataclasses.Field):

View file

@ -474,13 +474,20 @@ Virus.types = {
@dataclass(frozen=True)
class _MaskBase:
"""
Represents the filtration of aerosols by a mask, both inward and
Represents the filtration of aerosols by a mask, both inward and
outward.
The nature of the various air exchange schemes means that it is expected
for subclasses of _MaskBase to exist.
"""
#: Pre-populated examples of Masks.
types: typing.ClassVar[typing.Dict[str, "_MaskBase"]]
def exhale_efficiency(self, diameter: float) -> _VectorisedFloat:
# Overall efficiency, including the effect of the leaks.
# Overall exhale efficiency, including the effect of the leaks.
raise NotImplementedError("Subclass must implement")
def inhale_efficiency(self) -> _VectorisedFloat:
# Overall inhale efficiency, including the effect of the leaks.
raise NotImplementedError("Subclass must implement")
@ -495,9 +502,6 @@ class Mask(_MaskBase):
#: Filtration efficiency of masks when inhaling.
η_inhale: _VectorisedFloat
#: Pre-populated examples of Masks.
types: typing.ClassVar[typing.Dict[str, "Mask"]]
def exhale_efficiency(self, diameter: float) -> _VectorisedFloat:
# Overall efficiency with the effect of the leaks for aerosol emission
# Gammaitoni et al (1997). Diameter is in cm.
@ -507,8 +511,38 @@ class Mask(_MaskBase):
eta_out = self.η_exhale * (1 - self.η_leaks)
return eta_out
def inhale_efficiency(self) -> _VectorisedFloat:
# Overall inhale efficiency, including the effect of the leaks.
return self.η_inhale
Mask.types = {
@dataclass(frozen=True)
class MeasuredMask(_MaskBase):
#: Filtration efficiency of masks when inhaling.
η_inhale: _VectorisedFloat
def exhale_efficiency(self, diameter: float) -> _VectorisedFloat:
# See CERN-OPEN-2021-004 (doi: 10.17181/CERN.1GDQ.5Y75), and Ref.
# therein (Asadi 2020).
# Obtained from measurements of filtration efficiency and of
# the leakage through the sides.
# Diameter is in cm.
if diameter < 0.5e-4:
eta_out = 0.
elif diameter < 0.94614e-4:
eta_out = 0.5893 * diameter * 1e4 + 0.1546
elif diameter < 3e-4:
eta_out = 0.0509 * diameter * 1e4 + 0.664
else:
eta_out = 0.8167
return eta_out
def inhale_efficiency(self) -> _VectorisedFloat:
# Overall inhale efficiency, including the effect of the leaks.
return self.η_inhale
_MaskBase.types = {
'No mask': Mask(0, 0, 0),
'Type I': Mask(
η_exhale=0.95,
@ -520,6 +554,12 @@ Mask.types = {
η_leaks=0.15, # (same outward effect as type 1 - Asadi 2020)
η_inhale=0.865, # (94% penetration efficiency + 8% max inward leakage -> EN 149)
),
'Type I measured': MeasuredMask(
η_inhale=0.3, # (Browen 2010)
),
'FFP2 measured': MeasuredMask(
η_inhale=0.865, # (94% penetration efficiency + 8% max inward leakage -> EN 149)
),
}
@ -549,7 +589,7 @@ class Expiration(_ExpirationBase):
ejection_factor: typing.Tuple[float, ...]
particle_sizes: typing.Tuple[float, ...] = (0.8e-4, 1.8e-4, 3.5e-4, 5.5e-4) # In cm.
def aerosols(self, mask: Mask):
def aerosols(self, mask: _MaskBase):
def volume(diameter):
return (4 * np.pi * (diameter/2)**3) / 3
total = 0
@ -625,7 +665,7 @@ class Population:
presence: Interval
#: The kind of mask being worn by the people.
mask: Mask
mask: _MaskBase
#: The physical activity being carried out by the people.
activity: Activity
@ -848,7 +888,7 @@ class ExposureModel:
inf_aero = (
self.exposed.activity.inhalation_rate *
(1 - self.exposed.mask.η_inhale) *
(1 - self.exposed.mask.inhale_efficiency()) *
exposure
)