Merge branch 'feature/code_cleanout' into 'master'
Code cleanup Closes #250 and #249 See merge request cara/cara!364
This commit is contained in:
commit
9231890aa4
25 changed files with 107 additions and 121 deletions
|
|
@ -13,7 +13,6 @@ from keycloak.aio.realm import KeycloakRealm
|
|||
from tornado.web import Application, RequestHandler
|
||||
import tornado.log
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,10 @@ https://packaging.python.org/guides/distributing-packages-using-setuptools/
|
|||
from pathlib import Path
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
HERE = Path(__file__).parent.absolute()
|
||||
with (HERE / 'README.md').open('rt') as fh:
|
||||
LONG_DESCRIPTION = fh.read().strip()
|
||||
|
||||
|
||||
REQUIREMENTS: dict = {
|
||||
'core': [
|
||||
'aiohttp',
|
||||
|
|
@ -27,7 +25,6 @@ REQUIREMENTS: dict = {
|
|||
],
|
||||
}
|
||||
|
||||
|
||||
setup(
|
||||
name='auth-service',
|
||||
version="0.0.1",
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ from . import model_generator
|
|||
from .report_generator import ReportGenerator, calculate_report_data
|
||||
from .user import AuthenticatedUser, AnonymousUser
|
||||
|
||||
|
||||
# The calculator version is based on a combination of the model version and the
|
||||
# semantic version of the calculator itself. The version uses the terms
|
||||
# "{MAJOR}.{MINOR}.{PATCH}" to describe the 3 distinct numbers constituting a version.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import re
|
|||
import jinja2
|
||||
import mistune
|
||||
|
||||
|
||||
HEADER_PATTERN = re.compile(r'\n(#+)(.*)')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,10 @@ from .. import calculator
|
|||
from cara.monte_carlo.data import activity_distributions, virus_distributions, mask_distributions, short_range_distances
|
||||
from cara.monte_carlo.data import expiration_distribution, expiration_BLO_factors, expiration_distributions, short_range_expiration_distributions
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
minutes_since_midnight = typing.NewType('minutes_since_midnight', int)
|
||||
|
||||
|
||||
# Used to declare when an attribute of a class must have a value provided, and
|
||||
# there should be no default value used.
|
||||
_NO_DEFAULT = object()
|
||||
|
|
@ -365,7 +362,7 @@ class FormData:
|
|||
ventilation = models.HVACMechanical(
|
||||
active=always_on, q_air_mech=self.air_supply)
|
||||
|
||||
# this is a minimal, always present source of ventilation, due
|
||||
# This is a minimal, always present source of ventilation, due
|
||||
# to the air infiltration from the outside.
|
||||
# See CERN-OPEN-2021-004, p. 12.
|
||||
infiltration_ventilation = models.AirChange(active=always_on, air_exch=0.25)
|
||||
|
|
@ -583,8 +580,6 @@ class FormData:
|
|||
|
||||
present_intervals = []
|
||||
|
||||
# def add_interval(start, end):
|
||||
|
||||
current_time = start
|
||||
LOG.debug(f"starting time march at {_hours2timestring(current_time/60)} to {_hours2timestring(finish/60)}")
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ MONTH_NAMES = [
|
|||
def get_hourly_temperatures_celsius_per_hour(coordinates):
|
||||
wx_station_id = nearest_wx_station(
|
||||
longitude=coordinates[1], latitude=coordinates[0])[0]
|
||||
# average temperature of each month, hour per hour (from midnight to 11 pm)
|
||||
# Average temperature of each month, hour per hour (from midnight to 11 pm)
|
||||
return {month.replace(month, MONTH_NAMES[i][:3]):
|
||||
[t - 273.15 for t in temp] for i, (month, temp)
|
||||
in enumerate(wx_data()[wx_station_id].items())}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ oneoverln2 = 1 / np.log(2)
|
|||
_VectorisedFloat = typing.Union[float, np.ndarray]
|
||||
_VectorisedInt = typing.Union[int, np.ndarray]
|
||||
|
||||
|
||||
Time_t = typing.TypeVar('Time_t', float, int)
|
||||
BoundaryPair_t = typing.Tuple[Time_t, Time_t]
|
||||
BoundarySequence_t = typing.Union[typing.Tuple[BoundaryPair_t, ...], typing.Tuple]
|
||||
|
|
@ -130,7 +129,7 @@ class PeriodicInterval(Interval):
|
|||
@dataclass(frozen=True)
|
||||
class PiecewiseConstant:
|
||||
|
||||
# TODO: implement rather a periodic version (24-hour period), where
|
||||
# TODO: Implement rather a periodic version (24-hour period), where
|
||||
# transition_times and values have the same length.
|
||||
|
||||
#: transition times at which the function changes value (hours).
|
||||
|
|
@ -161,7 +160,7 @@ class PiecewiseConstant:
|
|||
return value
|
||||
|
||||
def interval(self) -> Interval:
|
||||
# build an Interval object
|
||||
# Build an Interval object
|
||||
present_times = []
|
||||
for t1, t2, value in zip(self.transition_times[:-1],
|
||||
self.transition_times[1:], self.values):
|
||||
|
|
@ -170,7 +169,7 @@ class PiecewiseConstant:
|
|||
return SpecificInterval(present_times=tuple(present_times))
|
||||
|
||||
def refine(self, refine_factor=10) -> "PiecewiseConstant":
|
||||
# build a new PiecewiseConstant object with a refined mesh,
|
||||
# Build a new PiecewiseConstant object with a refined mesh,
|
||||
# using a linear interpolation in-between the initial mesh points
|
||||
refined_times = np.linspace(self.transition_times[0], self.transition_times[-1],
|
||||
(len(self.transition_times)-1) * refine_factor+1)
|
||||
|
|
@ -482,7 +481,7 @@ Virus.types = {
|
|||
transmissibility_factor=1.0,
|
||||
),
|
||||
'SARS_CoV_2_ALPHA': SARSCoV2(
|
||||
# also called VOC-202012/01
|
||||
# Also called VOC-202012/01
|
||||
viral_load_in_sputum=1e9,
|
||||
infectious_dose=50.,
|
||||
viable_to_RNA_ratio = 0.5,
|
||||
|
|
@ -630,19 +629,19 @@ class _ExpirationBase:
|
|||
@property
|
||||
def particle(self) -> Particle:
|
||||
"""
|
||||
the Particle object representing the aerosol - here the default one
|
||||
The Particle object representing the aerosol - here the default one
|
||||
"""
|
||||
return Particle()
|
||||
|
||||
def aerosols(self, mask: Mask):
|
||||
"""
|
||||
total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
Total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
"""
|
||||
raise NotImplementedError("Subclass must implement")
|
||||
|
||||
def jet_origin_concentration(self):
|
||||
"""
|
||||
concentration of viruses at the jet origin (mL/m3).
|
||||
Concentration of viruses at the jet origin (mL/m3).
|
||||
"""
|
||||
raise NotImplementedError("Subclass must implement")
|
||||
|
||||
|
|
@ -657,7 +656,7 @@ class Expiration(_ExpirationBase):
|
|||
#: diameter of the aerosol in microns
|
||||
diameter: _VectorisedFloat
|
||||
|
||||
#: total concentration of aerosols per unit volume of expired air
|
||||
#: Total concentration of aerosols per unit volume of expired air
|
||||
# (in cm^-3), integrated over all aerosol diameters (corresponding
|
||||
# to c_n,i in Eq. (4) of https://doi.org/10.1101/2021.10.14.21264988)
|
||||
cn: float = 1.
|
||||
|
|
@ -665,7 +664,7 @@ class Expiration(_ExpirationBase):
|
|||
@property
|
||||
def particle(self) -> Particle:
|
||||
"""
|
||||
the Particle object representing the aerosol
|
||||
The Particle object representing the aerosol
|
||||
"""
|
||||
return Particle(diameter=self.diameter)
|
||||
|
||||
|
|
@ -675,7 +674,7 @@ class Expiration(_ExpirationBase):
|
|||
def volume(d):
|
||||
return (np.pi * d**3) / 6.
|
||||
|
||||
# final result converted from microns^3/cm3 to mL/cm^3
|
||||
# Final result converted from microns^3/cm3 to mL/cm^3
|
||||
return self.cn * (volume(self.diameter) *
|
||||
(1 - mask.exhale_efficiency(self.diameter))) * 1e-12
|
||||
|
||||
|
|
@ -684,7 +683,7 @@ class Expiration(_ExpirationBase):
|
|||
def volume(d):
|
||||
return (np.pi * d**3) / 6.
|
||||
|
||||
# final result converted from microns^3/cm3 to mL/m3
|
||||
# Final result converted from microns^3/cm3 to mL/m3
|
||||
return self.cn * volume(self.diameter) * 1e-6
|
||||
|
||||
|
||||
|
|
@ -792,7 +791,7 @@ class _PopulationWithVirus(Population):
|
|||
|
||||
def aerosols(self):
|
||||
"""
|
||||
total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
Total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
"""
|
||||
raise NotImplementedError("Subclass must implement")
|
||||
|
||||
|
|
@ -833,7 +832,7 @@ class _PopulationWithVirus(Population):
|
|||
@property
|
||||
def particle(self) -> Particle:
|
||||
"""
|
||||
the Particle object representing the aerosol expired by the
|
||||
The Particle object representing the aerosol expired by the
|
||||
population - here we take the default Particle object
|
||||
"""
|
||||
return Particle()
|
||||
|
|
@ -846,7 +845,7 @@ class EmittingPopulation(_PopulationWithVirus):
|
|||
|
||||
def aerosols(self):
|
||||
"""
|
||||
total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
Total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
Here arbitrarily set to 1 as the full emission rate is known.
|
||||
"""
|
||||
return 1.
|
||||
|
|
@ -875,7 +874,7 @@ class InfectedPopulation(_PopulationWithVirus):
|
|||
|
||||
def aerosols(self):
|
||||
"""
|
||||
total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
Total volume of aerosols expired per volume of air (mL/cm^3).
|
||||
"""
|
||||
return self.expiration.aerosols(self.mask)
|
||||
|
||||
|
|
@ -897,7 +896,7 @@ class InfectedPopulation(_PopulationWithVirus):
|
|||
@property
|
||||
def particle(self) -> Particle:
|
||||
"""
|
||||
the Particle object representing the aerosol - here the default one
|
||||
The Particle object representing the aerosol - here the default one
|
||||
"""
|
||||
return self.expiration.particle
|
||||
|
||||
|
|
@ -924,7 +923,6 @@ class ConcentrationModel:
|
|||
h = 1.5
|
||||
# Deposition rate (h^-1)
|
||||
k = (vg * 3600) / h
|
||||
#todo: Inside_temp needs to be exposed/added to the room;
|
||||
return (
|
||||
k + self.virus.decay_constant(self.room.humidity, self.room.inside_temp.value(time))
|
||||
+ self.ventilation.air_exchange(self.room, time)
|
||||
|
|
@ -1274,8 +1272,10 @@ class ExposureModel:
|
|||
self.concentration_model.evaporation_factor)
|
||||
|
||||
def _long_range_normed_exposure_between_bounds(self, time1: float, time2: float) -> _VectorisedFloat:
|
||||
"""The number of virions per meter^3 between any two times, normalized
|
||||
by the emission rate of the infected population"""
|
||||
"""
|
||||
The number of virions per meter^3 between any two times, normalized
|
||||
by the emission rate of the infected population
|
||||
"""
|
||||
exposure = 0.
|
||||
for start, stop in self.exposed.presence.boundaries():
|
||||
if stop < time1:
|
||||
|
|
@ -1315,7 +1315,7 @@ class ExposureModel:
|
|||
diameter = self.concentration_model.infected.particle.diameter
|
||||
|
||||
if not np.isscalar(diameter) and diameter is not None:
|
||||
# we compute first the mean of all diameter-dependent quantities
|
||||
# We compute first the mean of all diameter-dependent quantities
|
||||
# to perform properly the Monte-Carlo integration over
|
||||
# particle diameters (doing things in another order would
|
||||
# lead to wrong results for the probability of infection).
|
||||
|
|
@ -1323,11 +1323,11 @@ class ExposureModel:
|
|||
aerosols *
|
||||
fdep).mean()
|
||||
else:
|
||||
# in the case of a single diameter or no diameter defined,
|
||||
# In the case of a single diameter or no diameter defined,
|
||||
# one should not take any mean at this stage.
|
||||
dep_exposure_integrated = self._long_range_normed_exposure_between_bounds(time1, time2)*aerosols*fdep
|
||||
|
||||
# then we multiply by the diameter-independent quantity emission_rate_per_aerosol,
|
||||
# Then we multiply by the diameter-independent quantity emission_rate_per_aerosol,
|
||||
# and parameters of the vD equation (i.e. BR_k and n_in).
|
||||
deposited_exposure += (dep_exposure_integrated * emission_rate_per_aerosol *
|
||||
self.exposed.activity.inhalation_rate *
|
||||
|
|
@ -1361,7 +1361,7 @@ class ExposureModel:
|
|||
# Aerosols not considered given the formula for the initial
|
||||
# concentration at mouth/nose.
|
||||
if diameter is not None and not np.isscalar(diameter):
|
||||
# we compute first the mean of all diameter-dependent quantities
|
||||
# We compute first the mean of all diameter-dependent quantities
|
||||
# to perform properly the Monte-Carlo integration over
|
||||
# particle diameters (doing things in another order would
|
||||
# lead to wrong results for the probability of infection).
|
||||
|
|
@ -1370,24 +1370,24 @@ class ExposureModel:
|
|||
- np.array(short_range_lr_exposure * fdep).mean()
|
||||
* self.concentration_model.infected.activity.exhalation_rate)
|
||||
else:
|
||||
# in the case of a single diameter or no diameter defined,
|
||||
# In the case of a single diameter or no diameter defined,
|
||||
# one should not take any mean at this stage.
|
||||
this_deposited_exposure = (short_range_jet_exposure * fdep
|
||||
- short_range_lr_exposure * fdep
|
||||
* self.concentration_model.infected.activity.exhalation_rate)
|
||||
|
||||
# multiply by the (diameter-independent) inhalation rate
|
||||
# Multiply by the (diameter-independent) inhalation rate
|
||||
deposited_exposure += (this_deposited_exposure *
|
||||
interaction.activity.inhalation_rate
|
||||
/dilution)
|
||||
|
||||
# then we multiply by diameter-independent quantities: viral load
|
||||
# Then we multiply by diameter-independent quantities: viral load
|
||||
# and fraction of infected virions
|
||||
f_inf = self.concentration_model.infected.fraction_of_infectious_virus()
|
||||
deposited_exposure *= (f_inf
|
||||
* self.concentration_model.virus.viral_load_in_sputum
|
||||
* (1 - self.exposed.mask.inhale_efficiency()))
|
||||
# long-range concentration
|
||||
# Long-range concentration
|
||||
deposited_exposure += self.long_range_deposited_exposure_between_bounds(time1, time2)
|
||||
|
||||
return deposited_exposure
|
||||
|
|
@ -1404,7 +1404,7 @@ class ExposureModel:
|
|||
return deposited_exposure * self.repeats
|
||||
|
||||
def infection_probability(self) -> _VectorisedFloat:
|
||||
# viral dose (vD)
|
||||
# Viral dose (vD)
|
||||
vD = self.deposited_exposure()
|
||||
|
||||
# oneoverln2 multiplied by ID_50 corresponds to ID_63.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from typing import Any
|
||||
|
||||
|
||||
# For now we disable all type-checking in the monte-carlo submodule.
|
||||
def __getattr__(name) -> Any: ...
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from scipy.stats import weibull_min
|
|||
import cara.monte_carlo as mc
|
||||
from cara.monte_carlo.sampleable import LogCustom, LogNormal,LogCustomKernel,CustomKernel,Uniform, Custom
|
||||
|
||||
|
||||
sqrt2pi = np.sqrt(2.*np.pi)
|
||||
sqrt2 = np.sqrt(2.)
|
||||
|
||||
|
|
@ -26,7 +25,7 @@ class BLOmodel:
|
|||
vol. 42, no. 12, pp. 839 – 851, 2011,
|
||||
https://doi.org/10.1016/j.jaerosci.2011.07.009).
|
||||
"""
|
||||
#: factors assigned to resp. the B, L and O modes. They are
|
||||
#: 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). These are applied on top of the
|
||||
# cn concentrations (see below), and depend on the kind of activity
|
||||
|
|
@ -37,11 +36,11 @@ class BLOmodel:
|
|||
# total concentration of aerosols for each mode.
|
||||
cn: typing.Tuple[float, float, float] = (0.06, 0.2, 0.0010008)
|
||||
|
||||
# mean of the underlying normal distributions (represents the log of a
|
||||
# Mean of the underlying normal distributions (represents the log of a
|
||||
# diameter in microns), for resp. the B, L and O modes.
|
||||
mu: typing.Tuple[float, float, float] = (0.989541, 1.38629, 4.97673)
|
||||
|
||||
# std deviation of the underlying normal distribution, for resp.
|
||||
# Std deviation of the underlying normal distribution, for resp.
|
||||
# the B, L and O modes.
|
||||
sigma: typing.Tuple[float, float, float] = (0.262364, 0.506818, 0.585005)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import cara.models
|
|||
|
||||
from .sampleable import SampleableDistribution, _VectorisedFloatOrSampleable
|
||||
|
||||
|
||||
_ModelType = typing.TypeVar('_ModelType')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ from sklearn.neighbors import KernelDensity # type: ignore
|
|||
|
||||
import cara.models
|
||||
|
||||
|
||||
# Declare a float array type of a given size.
|
||||
# There is no better way to declare this currently, unfortunately.
|
||||
float_array_size_n = np.ndarray
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from contextlib import contextmanager
|
|||
import dataclasses
|
||||
import typing
|
||||
|
||||
|
||||
Datamodel_T = typing.TypeVar('Datamodel_T')
|
||||
dataclass_instance = typing.Any
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from cara.apps.calculator.report_generator import generate_permalink
|
|||
|
||||
_TIMEOUT = 20.
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app():
|
||||
return cara.apps.calculator.make_app()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def baseline_concentration_model():
|
|||
activity=models.Activity.types['Light activity'],
|
||||
known_individual_emission_rate=970 * 50,
|
||||
host_immunity=0.,
|
||||
# superspreading event, where ejection factor is fixed based
|
||||
# Superspreading event, where ejection factor is fixed based
|
||||
# on Miller et al. (2020) - 50 represents the infectious dose.
|
||||
),
|
||||
evaporation_factor=0.3,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class KnownNormedconcentration(models.ConcentrationModel):
|
|||
normed_concentration_function: typing.Callable = lambda x: 0
|
||||
|
||||
def infectious_virus_removal_rate(self, time: float) -> models._VectorisedFloat:
|
||||
# very large decay constant -> same as constant concentration
|
||||
# Very large decay constant -> same as constant concentration
|
||||
return 1.e50
|
||||
|
||||
def _normed_concentration_limit(self, time: float) -> models._VectorisedFloat:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
|
||||
from cara import models
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"η_inhale, expected_inhale_efficiency",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ from cara import data
|
|||
|
||||
|
||||
def test_piecewiseconstantfunction_wrongarguments():
|
||||
# number of values should be 1+number of transition times
|
||||
# Number of values should be 1+number of transition times
|
||||
pytest.raises(ValueError, models.PiecewiseConstant, (0, 1), (0, 0))
|
||||
pytest.raises(ValueError, models.PiecewiseConstant, (0,), (0, 0))
|
||||
# two transition times cannot be equal
|
||||
# Two transition times cannot be equal
|
||||
pytest.raises(ValueError, models.PiecewiseConstant, (0, 2, 2), (0, 0))
|
||||
# unsorted transition times are not allowed
|
||||
# Unsorted transition times are not allowed
|
||||
pytest.raises(ValueError, models.PiecewiseConstant, (2, 0), (0, 0))
|
||||
|
||||
# If vectors, must all be same length.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
|
||||
from cara import models
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"inside_temp, humidity, expected_halflife, expected_decay_constant",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ def test_multiple():
|
|||
|
||||
|
||||
@retry(tries=10)
|
||||
# expected values obtained from analytical formulas
|
||||
# Expected values obtained from analytical formulas
|
||||
@pytest.mark.parametrize(
|
||||
"BLO_weights, expected_aerosols",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ sqrt2pi = np.sqrt(2.*np.pi)
|
|||
sqrt2 = np.sqrt(2.)
|
||||
ln2 = np.log(2)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SimpleConcentrationModel:
|
||||
"""
|
||||
|
|
@ -34,54 +35,54 @@ class SimpleConcentrationModel:
|
|||
times.
|
||||
"""
|
||||
|
||||
#: infected people presence interval
|
||||
#: Infected people presence interval
|
||||
infected_presence: Interval
|
||||
|
||||
#: viral load (RNA copies / mL)
|
||||
#: Viral load (RNA copies / mL)
|
||||
viral_load: _VectorisedFloat
|
||||
|
||||
#: breathing rate (m^3/h)
|
||||
#: Breathing rate (m^3/h)
|
||||
breathing_rate: _VectorisedFloat
|
||||
|
||||
#: room volume (m^3)
|
||||
#: Room volume (m^3)
|
||||
room_volume: _VectorisedFloat
|
||||
|
||||
#: ventilation rate (air changes per hour) - including HEPA
|
||||
#: Ventilation rate (air changes per hour) - including HEPA
|
||||
lambda_ventilation: _VectorisedFloat
|
||||
|
||||
#: BLO factors
|
||||
BLO_factors: typing.Tuple[float, float, float]
|
||||
|
||||
#: number of infected people
|
||||
#: Number of infected people
|
||||
num_infected: int = 1
|
||||
|
||||
#: relative humidity RH
|
||||
#: Relative humidity RH
|
||||
humidity: float = 0.3
|
||||
|
||||
#: minimum particle diameter considered (microns)
|
||||
#: Minimum particle diameter considered (microns)
|
||||
diameter_min: float = 0.1
|
||||
|
||||
#: maximum particle diameter considered (microns)
|
||||
#: Maximum particle diameter considered (microns)
|
||||
diameter_max: float = 30.
|
||||
|
||||
#: evaporation factor
|
||||
#: Evaporation factor
|
||||
evaporation: float = 0.3
|
||||
|
||||
#: cn (cm^-3) for resp. the B, L and O modes. Corresponds to the
|
||||
# total concentration of aerosols for each mode.
|
||||
cn: typing.Tuple[float, float, float] = (0.06, 0.2, 0.0010008)
|
||||
|
||||
# mean of the underlying normal distributions (represents the log of a
|
||||
# Mean of the underlying normal distributions (represents the log of a
|
||||
# diameter in microns), for resp. the B, L and O modes.
|
||||
mu: typing.Tuple[float, float, float] = (0.989541, 1.38629, 4.97673)
|
||||
|
||||
# std deviation of the underlying normal distribution, for resp.
|
||||
# Std deviation of the underlying normal distribution, for resp.
|
||||
# the B, L and O modes.
|
||||
sigma: typing.Tuple[float, float, float] = (0.262364, 0.506818, 0.585005)
|
||||
|
||||
def removal_rate(self) -> _VectorisedFloat:
|
||||
"""
|
||||
removal rate lambda in h^-1, excluding the deposition rate.
|
||||
Removal rate lambda in h^-1, excluding the deposition rate.
|
||||
"""
|
||||
hl_calc = ((ln2/((0.16030 + 0.04018*(((293-273.15)-20.615)/10.585)
|
||||
+0.02176*(((self.humidity*100)-45.235)/28.665)
|
||||
|
|
@ -94,7 +95,7 @@ class SimpleConcentrationModel:
|
|||
@method_cache
|
||||
def deposition_removal_coefficient(self) -> float:
|
||||
"""
|
||||
coefficient in front of gravitational deposition rate, in h^-1.microns^-2
|
||||
Coefficient in front of gravitational deposition rate, in h^-1.microns^-2
|
||||
Note: 0.4512 = 1.88e-4 * 3600 / 1.5
|
||||
"""
|
||||
return 0.4512*(self.evaporation/2.5)**2
|
||||
|
|
@ -102,7 +103,7 @@ class SimpleConcentrationModel:
|
|||
@method_cache
|
||||
def aerosol_volume(self,diameter: float) -> float:
|
||||
"""
|
||||
particle volume in microns^3
|
||||
Particle volume in microns^3
|
||||
"""
|
||||
return 4*np.pi/3. * (diameter/2.)**3
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ class SimpleConcentrationModel:
|
|||
def Np(self,diameter: float,
|
||||
BLO_factors: typing.Tuple[float, float, float]) -> float:
|
||||
"""
|
||||
number of emitted particles per unit volume (BLO model)
|
||||
Number of emitted particles per unit volume (BLO model)
|
||||
in cm^-3.ln(micron)^-1
|
||||
"""
|
||||
result = 0.
|
||||
|
|
@ -122,7 +123,7 @@ class SimpleConcentrationModel:
|
|||
|
||||
def vR(self,diameter: float) -> float:
|
||||
"""
|
||||
emission rate per unit diameter, in RNA copies / h / micron
|
||||
Emission rate per unit diameter, in RNA copies / h / micron
|
||||
"""
|
||||
return (self.Np(diameter, self.BLO_factors)
|
||||
* self.aerosol_volume(diameter) * 1e-6)
|
||||
|
|
@ -145,7 +146,7 @@ class SimpleConcentrationModel:
|
|||
|
||||
def concentration(self,t: float) -> _VectorisedFloat:
|
||||
"""
|
||||
concentration at a given time t
|
||||
Concentration at a given time t
|
||||
"""
|
||||
trans_times = sorted(self.infected_presence.transition_times())
|
||||
if t==trans_times[0]:
|
||||
|
|
@ -187,28 +188,28 @@ class SimpleShortRangeModel:
|
|||
This assumes no mask wearing.
|
||||
"""
|
||||
|
||||
#: time intervals in which a short-range interaction occurs
|
||||
#: Time intervals in which a short-range interaction occurs
|
||||
interaction_interval: SpecificInterval
|
||||
|
||||
#: tuple with interpersonal distanced from infected person (m)
|
||||
#: Tuple with interpersonal distanced from infected person (m)
|
||||
distance : _VectorisedFloat = 0.854
|
||||
|
||||
#: breathing rate (m^3/h)
|
||||
#: Breathing rate (m^3/h)
|
||||
breathing_rate: _VectorisedFloat = 0.51
|
||||
|
||||
#: tuple with BLO factors
|
||||
#: Tuple with BLO factors
|
||||
BLO_factors: typing.Tuple[float, float, float] = (1,0,0)
|
||||
|
||||
#: minimum diameter for integration (short-range only) (microns)
|
||||
#: Minimum diameter for integration (short-range only) (microns)
|
||||
diameter_min: float = 0.1
|
||||
|
||||
#: maximum diameter for integration (short-range only) (microns)
|
||||
#: Maximum diameter for integration (short-range only) (microns)
|
||||
diameter_max: float = 100.
|
||||
|
||||
#: mouth opening diameter (m)
|
||||
#: Mouth opening diameter (m)
|
||||
D: float = 0.02
|
||||
|
||||
#: duration of the expiration (s)
|
||||
#: Duration of the expiration (s)
|
||||
tstar: float = 2.
|
||||
|
||||
#: Streamwise and radial penetration coefficients
|
||||
|
|
@ -220,27 +221,27 @@ class SimpleShortRangeModel:
|
|||
@method_cache
|
||||
def dilution_factor(self) -> _VectorisedFloat:
|
||||
"""
|
||||
computes dilution factor at a certain distance x
|
||||
Computes dilution factor at a certain distance x
|
||||
based on Wei JIA matlab script.
|
||||
"""
|
||||
x = np.array(self.distance)
|
||||
dilution = np.empty(x.shape, dtype=np.float64)
|
||||
# expired flow rate during the expiration period, m^3/s
|
||||
# Expired flow rate during the expiration period, m^3/s
|
||||
Q0 = np.array(self.breathing_rate/3600)
|
||||
# the expired flow velocity at the noozle (mouth opening), m/s
|
||||
# The expired flow velocity at the noozle (mouth opening), m/s
|
||||
u0 = np.array(Q0/(np.pi/4. * self.D**2))
|
||||
# parameters in the jet-like stage
|
||||
# Parameters in the jet-like stage
|
||||
# position of virtual origin
|
||||
x01 = self.D/2/self.Cr1
|
||||
# time of virtual origin
|
||||
# Time of virtual origin
|
||||
t01 = (x01/self.Cx1)**2 * (Q0*u0)**(-0.5)
|
||||
# transition point (in m)
|
||||
# Transition point (in m)
|
||||
xstar = np.array(self.Cx1*(Q0*u0)**0.25*(self.tstar + t01)**0.5
|
||||
- x01)
|
||||
# dilution factor at the transition point xstar
|
||||
# Dilution factor at the transition point xstar
|
||||
Sxstar = np.array(2.*self.Cr1*(xstar+x01)/self.D)
|
||||
|
||||
# calculate dilution factor at the short-range distance x
|
||||
# Calculate dilution factor at the short-range distance x
|
||||
dilution[x <= xstar] = 2.*self.Cr1*(x[x <= xstar] + x01)/self.D
|
||||
dilution[x > xstar] = Sxstar[x > xstar]*(1. + self.Cr2*(x[x > xstar]
|
||||
- xstar[x > xstar])
|
||||
|
|
@ -250,7 +251,7 @@ class SimpleShortRangeModel:
|
|||
|
||||
def jet_concentration(self,conc_model: SimpleConcentrationModel) -> _VectorisedFloat:
|
||||
"""
|
||||
virion concentration at the origin of the jet (close to
|
||||
Virion concentration at the origin of the jet (close to
|
||||
the mouth of the infected person), in m^-3
|
||||
we perform the integral of Np(d)*V(d) over diameter analytically
|
||||
"""
|
||||
|
|
@ -269,7 +270,7 @@ class SimpleShortRangeModel:
|
|||
|
||||
def concentration(self, conc_model: SimpleConcentrationModel, time: float) -> _VectorisedFloat:
|
||||
"""
|
||||
compute the short-range part of the concentration, and add it
|
||||
Compute the short-range part of the concentration, and add it
|
||||
to the long-range concentration
|
||||
"""
|
||||
if self.interaction_interval.triggered(time):
|
||||
|
|
@ -293,25 +294,25 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
interaction intervals are within presence intervals of the infected.
|
||||
"""
|
||||
|
||||
#: fraction of infected viruses
|
||||
#: Fraction of infected viruses
|
||||
finf: _VectorisedFloat = 0.5
|
||||
|
||||
#: host immunity factor (0. for not immune)
|
||||
#: Host immunity factor (0. for not immune)
|
||||
HI: _VectorisedFloat = 0.
|
||||
|
||||
#: infectious dose (ID50)
|
||||
#: Infectious dose (ID50)
|
||||
ID50: _VectorisedFloat = 50.
|
||||
|
||||
#: transmissibility factor w.r.t. original strain
|
||||
#: Transmissibility factor w.r.t. original strain
|
||||
# (<1 means more transmissible)
|
||||
transmissibility: _VectorisedFloat = 1.
|
||||
|
||||
#: list of short-range interaction models
|
||||
#: List of short-range interaction models
|
||||
sr_models: typing.Tuple[SimpleShortRangeModel, ...] = ()
|
||||
|
||||
def fdep(self, diameter: float, evaporation: float) -> float:
|
||||
"""
|
||||
fraction deposited
|
||||
Fraction deposited
|
||||
"""
|
||||
d = diameter * evaporation
|
||||
IFrac = 1 - 0.5 * (1 - (1 / (1 + (0.00076*(d**2.8)))))
|
||||
|
|
@ -327,7 +328,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
A general function to compute the main integral over diameters
|
||||
"""
|
||||
def integrand(diameter):
|
||||
# function to return the integrand
|
||||
# Function to return the integrand
|
||||
a = self.deposition_removal_coefficient()
|
||||
a_dsquare = a*diameter**2
|
||||
return -(self.vR(diameter)*self.fdep(diameter,evaporation)/(
|
||||
|
|
@ -345,7 +346,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
Same as f but with fdep included in the integral.
|
||||
"""
|
||||
def integrand(diameter):
|
||||
# function to return the integrand
|
||||
# Function to return the integrand
|
||||
a = self.deposition_removal_coefficient()
|
||||
a_dsquare = a*diameter**2
|
||||
return (self.vR(diameter)*self.fdep(diameter,evaporation)
|
||||
|
|
@ -357,7 +358,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
|
||||
def total_concentration(self, t: float):
|
||||
"""
|
||||
total concentration at time t
|
||||
Total concentration at time t
|
||||
"""
|
||||
res = self.concentration(t)
|
||||
for sr_mod in self.sr_models:
|
||||
|
|
@ -368,7 +369,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
def integrated_longrange_concentration(self,t1: float,t2: float,
|
||||
evaporation: float) -> _VectorisedFloat:
|
||||
"""
|
||||
background (long-range) concentration integrated from t1 to t2
|
||||
Background (long-range) concentration integrated from t1 to t2
|
||||
assuming both t1 and t2 are within a single presence interval.
|
||||
This includes the deposition fraction (fdep).
|
||||
"""
|
||||
|
|
@ -413,7 +414,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
@method_cache
|
||||
def integrated_shortrange_concentration(self) -> _VectorisedFloat:
|
||||
"""
|
||||
short-range concentration integrated over interaction times and
|
||||
Short-range concentration integrated over interaction times and
|
||||
diameters. This includes the deposition fraction (fdep).
|
||||
"""
|
||||
result = 0.
|
||||
|
|
@ -436,7 +437,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
|
||||
def dose(self) -> _VectorisedFloat:
|
||||
"""
|
||||
total deposited dose (integrated over time and over particle
|
||||
Total deposited dose (integrated over time and over particle
|
||||
diameters), including short and long-range.
|
||||
"""
|
||||
result = 0.
|
||||
|
|
@ -450,7 +451,7 @@ class SimpleExposureModel(SimpleConcentrationModel):
|
|||
|
||||
def probability_infection(self):
|
||||
"""
|
||||
total probability of infection
|
||||
Total probability of infection
|
||||
"""
|
||||
return (1. - np.exp(-self.dose() * ln2 * (1-self.HI)
|
||||
/(self.ID50 * self.transmissibility) )) * 100.
|
||||
|
|
@ -766,7 +767,7 @@ def test_longrange_exposure_with_distributions(c_model_distr):
|
|||
)
|
||||
|
||||
|
||||
# tests on the concentration with short-range should be skipped until
|
||||
# Tests on the concentration with short-range should be skipped until
|
||||
# one finds a way to avoid the large variability of the concentration
|
||||
# with short-range 'Speaking' or 'Shouting' interactions
|
||||
@pytest.mark.skip
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ def baseline_periodic_hepa():
|
|||
|
||||
|
||||
def test_concentrations(baseline_concentration_model):
|
||||
# expected concentrations were computed analytically
|
||||
# Expected concentrations were computed analytically
|
||||
ts = [0, 4, 5, 7, 10]
|
||||
concentrations = [baseline_concentration_model.concentration(float(t)) for t in ts]
|
||||
npt.assert_allclose(
|
||||
|
|
@ -73,7 +73,7 @@ def build_model(interval_duration):
|
|||
activity=models.Activity.types['Light activity'],
|
||||
known_individual_emission_rate=970 * 50,
|
||||
host_immunity=0.,
|
||||
# superspreading event, where ejection factor is fixed based
|
||||
# Superspreading event, where ejection factor is fixed based
|
||||
# on Miller et al. (2020) - 50 represents the infectious dose.
|
||||
),
|
||||
evaporation_factor=0.3,
|
||||
|
|
@ -91,7 +91,7 @@ def test_concentrations_startup():
|
|||
|
||||
|
||||
def test_r0(baseline_exposure_model):
|
||||
# expected r0 was computed with a trapezoidal integration, using
|
||||
# Expected r0 was computed with a trapezoidal integration, using
|
||||
# a mesh of 100'000 pts per exposed presence interval.
|
||||
r0 = baseline_exposure_model.reproduction_number()
|
||||
npt.assert_allclose(r0, 771.380385)
|
||||
|
|
@ -376,7 +376,7 @@ def build_exposure_model(concentration_model, short_range_model):
|
|||
)
|
||||
|
||||
|
||||
# expected deposited exposure were computed with a trapezoidal integration, using
|
||||
# Expected deposited exposure were computed with a trapezoidal integration, using
|
||||
# a mesh of 100'000 pts per exposed presence interval.
|
||||
@pytest.mark.parametrize(
|
||||
"month, expected_deposited_exposure",
|
||||
|
|
@ -396,7 +396,7 @@ def test_exposure_hourly_dep(month,expected_deposited_exposure, baseline_sr_mode
|
|||
deposited_exposure = m.deposited_exposure()
|
||||
npt.assert_allclose(deposited_exposure, expected_deposited_exposure)
|
||||
|
||||
# expected deposited exposure were computed with a trapezoidal integration, using
|
||||
# Expected deposited exposure were computed with a trapezoidal integration, using
|
||||
# a mesh of 100'000 pts per exposed presence interval and 25 pts per hour
|
||||
# for the temperature discretization.
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import cara.models
|
|||
import cara.monte_carlo.models as mc_models
|
||||
import cara.monte_carlo.sampleable
|
||||
|
||||
|
||||
MODEL_CLASSES = [
|
||||
cls for cls in vars(cara.models).values()
|
||||
if dataclasses.is_dataclass(cls)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ toronto_coordinates = (43.667, 79.400)
|
|||
toronto_hourly_temperatures_celsius_per_hour = data.get_hourly_temperatures_celsius_per_hour(
|
||||
toronto_coordinates)
|
||||
|
||||
|
||||
# Toronto hourly temperatures as piecewise constant function (in Kelvin).
|
||||
TorontoTemperatures_hourly = {
|
||||
month: models.PiecewiseConstant(
|
||||
|
|
@ -28,7 +27,6 @@ TorontoTemperatures_hourly = {
|
|||
for month, temperatures in toronto_hourly_temperatures_celsius_per_hour.items()
|
||||
}
|
||||
|
||||
|
||||
# Same Toronto temperatures on a finer temperature mesh (every 6 minutes).
|
||||
TorontoTemperatures = {
|
||||
month: TorontoTemperatures_hourly[month].refine(refine_factor=10)
|
||||
|
|
@ -36,7 +34,7 @@ TorontoTemperatures = {
|
|||
}
|
||||
|
||||
|
||||
# references values for infection_probability and expected new cases
|
||||
# References values for infection_probability and expected new cases
|
||||
# in the following tests, were obtained from the feature/mc branch
|
||||
@pytest.fixture
|
||||
def shared_office_mc():
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ import pytest
|
|||
from cara.monte_carlo.data import activity_distributions, virus_distributions
|
||||
|
||||
|
||||
|
||||
# mean & std deviations from https://doi.org/10.1101/2021.10.14.21264988 (Table 3)
|
||||
# Mean & std deviations from https://doi.org/10.1101/2021.10.14.21264988 (Table 3)
|
||||
# NOTE: a mistake was corrected for the std deviation of the
|
||||
# "Moderate exercise" case (0.37 in the report, but should be 0.34)
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -28,7 +27,7 @@ def test_activity_distributions(distribution, mean, std):
|
|||
npt.assert_allclose(activity.inhalation_rate.std(), std, atol=0.01)
|
||||
|
||||
|
||||
# mean & std deviations from https://doi.org/10.1101/2021.10.14.21264988 (Table 3)
|
||||
# Mean & std deviations from https://doi.org/10.1101/2021.10.14.21264988 (Table 3)
|
||||
# - with a refined precision on the values
|
||||
@pytest.mark.parametrize(
|
||||
"distribution, mean, std",[
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from cara.monte_carlo import sampleable
|
|||
]
|
||||
)
|
||||
def test_normal(mean, std):
|
||||
# test that the sample has approximately the right mean,
|
||||
# Test that the sample has approximately the right mean,
|
||||
# std deviation and distribution function.
|
||||
sample_size = 2000000
|
||||
samples = sampleable.Normal(mean, std).generate_samples(sample_size)
|
||||
|
|
@ -32,7 +32,7 @@ def test_normal(mean, std):
|
|||
]
|
||||
)
|
||||
def test_lognormal(mean_gaussian, std_gaussian):
|
||||
# test that the sample has approximately the right mean,
|
||||
# Test that the sample has approximately the right mean,
|
||||
# std deviation and distribution function.
|
||||
sample_size = 2000000
|
||||
samples = sampleable.LogNormal(mean_gaussian, std_gaussian
|
||||
|
|
@ -58,7 +58,7 @@ def test_lognormal(mean_gaussian, std_gaussian):
|
|||
[False, True],
|
||||
)
|
||||
def test_custom(use_kernel):
|
||||
# test that the sample has approximately the right distribution
|
||||
# Test that the sample has approximately the right distribution
|
||||
# function, with both Custom and CustomKernel method. The latter
|
||||
# is less accurate for smooth functions.
|
||||
# the distribution function is an inverted parabola, with maximum 0.15,
|
||||
|
|
@ -87,7 +87,7 @@ def test_custom(use_kernel):
|
|||
|
||||
|
||||
def test_logcustomkernel():
|
||||
# test that the sample has approximately the right distribution
|
||||
# Test that the sample has approximately the right distribution
|
||||
# function, for the LogCustomKernel.
|
||||
# the distribution function is an inverted parabola vs. the log of
|
||||
# the variable (normalized)
|
||||
|
|
|
|||
Loading…
Reference in a new issue