Merge branch 'feature/virology_parameters' into 'master'

ER and P(I) formulas update according to qID parameter

Closes #184

See merge request cara/cara!236
This commit is contained in:
Philip James Elson 2021-08-06 08:24:28 +00:00
commit e1d8b3a2c8
15 changed files with 153 additions and 113 deletions

View file

@ -2,7 +2,7 @@
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
CARA models the concentration profile of potential virions in enclosed spaces with clear and intuitive graphs.
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.

View file

@ -116,7 +116,7 @@ def plot(times, concentrations, model: models.ExposureModel):
ax.set_xlabel('Time of day')
ax.set_ylabel('Mean concentration ($q/m^3$)')
ax.set_title('Mean concentration of infectious quanta')
ax.set_title('Mean concentration of virions')
ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter("%H:%M"))
# Plot presence of exposed person
@ -237,7 +237,7 @@ def comparison_plot(scenarios: typing.Dict[str, dict], sample_times: np.ndarray)
ax.set_xlabel('Time of day')
ax.set_ylabel('Mean concentration ($q/m^3$)')
ax.set_title('Mean concentration of infectious quanta')
ax.set_title('Mean concentration of virions')
return fig

View file

@ -257,7 +257,7 @@
<p class="data_text"> <strong> Notes for alternative scenarios: </strong><br>
<ol>
<li>This graph shows the concentration of infectious quanta in the air. The filtration of Type I and FFP2 masks, if worn, applies not only to the emission rate but also to the individual exposure (i.e. inhalation).
<li>This graph shows the concentration of virions in the air. The filtration of Type I and FFP2 masks, if worn, applies not only to the emission rate but also to the individual exposure (i.e. inhalation).
For this reason, scenarios with different types of mask will show the same concentration on the graph but have different absorbed doses and infection probabilities.</li>
<li>If you have selected more sophisticated options, such as HEPA filtration or FFP2 masks, alternatives will be indicated in the plot as the "base scenario with/without...", representing a variation on the inputs inserted in the form.<br>
The other alternative scenarios shown for comparison will not include either HEPA filtration or FFP2 masks.</li>
@ -288,7 +288,7 @@
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
</p>
<p>
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
CARA models the concentration profile of potential virions in enclosed spaces with clear and intuitive graphs.
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
</p>

View file

@ -380,7 +380,7 @@ v{{ calculator_version }} <span style="float:right; font-weight:bold">Please sen
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
</p>
<p>
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
CARA models the concentration profile of virions in enclosed spaces with clear and intuitive graphs.
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
</p>

View file

@ -15,7 +15,7 @@ If you are using the expert version of the tool, you should look at the expert
CARA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
</p>
<p>
CARA models the concentration profile of potential infectious viruses in enclosed spaces with clear and intuitive graphs.
CARA models the concentration profile of potential virions in enclosed spaces with clear and intuitive graphs.
The user can set a number of parameters, including room volume, exposure time, activity type, mask-wearing and ventilation.
The report generated indicates how to avoid exceeding critical concentrations and chains of airborne transmission in spaces such as individual offices, meeting rooms and labs.
</p>
@ -183,15 +183,15 @@ It is estimated based on the emission rate of virus into the simulated volume, a
This probability is valid for the simulation duration - i.e. the start and end time.
If you are using the natural ventilation option, the simulation is only valid for the selected month, because the following or preceding month will have a different average temperature profile.
The <code>expected number of new cases</code> for the simulation is calculated based on the probability of infection, multiplied by the number of exposed occupants.</p>
<p>The graph shows the variation in the concentration of infectious viruses within the simulated volume.
<p>The graph shows the variation in the concentration of virions within the simulated volume.
It is determined by:</p>
<ul>
<li>The presence of the infected person, who emits airborne viruses in the volume.</li>
<li>The emission rate is related to the type of activity of the infected person (sitting, light exercise), their level of vocalisation (breathing, talking or shouting).</li>
<li>The accumulation of infectious quanta in the volume, which is driven, among other factors, by ventilation (if applicable).<ul>
<li>The accumulation of virions in the volume, which is driven, among other factors, by ventilation (if applicable).<ul>
<li>In a mechanical ventilation scenario, the removal rate is constant, based on fresh airflow supply in and out of the simulated space.</li>
<li>Under natural ventilation conditions, the effectiveness of ventilation relies upon the hourly temperature difference between the inside and outside air temperature.</li>
<li>A HEPA filter removes infectious virus from the air at a constant rate and is modelled in the same way as mechanical ventilation, however air passed through a HEPA filter is recycled (i.e. it is not fresh air).</li>
<li>A HEPA filter removes virions from the air at a constant rate and is modelled in the same way as mechanical ventilation, however air passed through a HEPA filter is recycled (i.e. it is not fresh air).</li>
</ul>
</li>
</ul>

View file

@ -141,7 +141,7 @@ class ExposureModelResult(View):
ax.set_xlabel('Time (hours)')
ax.set_ylabel('Concentration ($q/m^3$)')
ax.set_title('Concentration of infectious quanta')
ax.set_title('Concentration of virions')
else:
self.ax.ignore_existing_data_limits = True
self.line.set_data(ts, concentration)
@ -154,7 +154,7 @@ class ExposureModelResult(View):
def update_textual_result(self, model: models.ExposureModel):
lines = []
P = model.infection_probability()
lines.append(f'Emission rate (quanta/hr): {np.round(model.concentration_model.infected.emission_rate_when_present(),0)}')
lines.append(f'Emission rate (virus/hr): {np.round(model.concentration_model.infected.emission_rate_when_present(),0)}')
lines.append(f'Probability of infection: {np.round(P, 0)}%')
lines.append(f'Number of exposed: {model.exposed.number}')
@ -186,7 +186,7 @@ class ExposureComparissonResult(View):
ax.spines['top'].set_visible(False)
ax.set_xlabel('Time (hours)')
ax.set_ylabel('Concentration ($q/m^3$)')
ax.set_title('Concentration of infectious quanta')
ax.set_title('Concentration of virions')
return ax
def scenarios_updated(self, scenarios: typing.Sequence[ScenarioType], _):

View file

@ -24,7 +24,7 @@ The model used is based on scientific publications relating to airborne transmis
The tool helps assess the potential dose of infectious airborne viruses in indoor gatherings, with people seated, standing, moving around, while breathing, speaking or shouting/singing. The model is based on the Wells-Riley model of aerosol disease transmission, which assumes a fixed value for the average infectious dose. The dose-response models for respiratory diseases is more accurate, although since this parameter for SARS-CoV-2 is not known so far, the Wells-Riley method is recommended in the health science community (see <a href="#references_block">References</a>).
The methodology of the model is divided into three parts:
<ol>
<li>Estimating the emission rate of infectious viruses.</li>
<li>Estimating the emission rate of virions.</li>
<li>Modeling the concentration evolution of viruses within a given volume and consequent inhalation dose during the exposure time.</li>
<li>Estimating the probability of a COVID-19 infection, the expected number of new cases arising from the transmission event and the basic reproduction rate (R0).</li>
</ol>

View file

@ -420,8 +420,8 @@ class Virus:
#: RNA copies / mL
viral_load_in_sputum: _VectorisedFloat
#: RNA-copies per quantum
quantum_infectious_dose: _VectorisedFloat
#: Dose to initiate infection, in RNA copies
infectious_dose: _VectorisedFloat
#: Pre-populated examples of Viruses.
types: typing.ClassVar[typing.Dict[str, "Virus"]]
@ -458,20 +458,20 @@ Virus.types = {
# It is somewhere between 1000 or 10 SARS-CoV viruses,
# as per https://www.dhs.gov/publication/st-master-question-list-covid-19
# 50 comes from Buonanno et al.
quantum_infectious_dose=50.,
infectious_dose=50.,
),
'SARS_CoV_2_B117': SARSCoV2(
# also called VOC-202012/01
viral_load_in_sputum=1e9,
quantum_infectious_dose=30.,
infectious_dose=30.,
),
'SARS_CoV_2_P1': SARSCoV2(
viral_load_in_sputum=1e9,
quantum_infectious_dose=1/0.045,
infectious_dose=1/0.045,
),
'SARS_CoV_2_B16172': SARSCoV2(
viral_load_in_sputum=1e9,
quantum_infectious_dose=30/1.6,
infectious_dose=30/1.6,
),
}
@ -677,7 +677,7 @@ class InfectedPopulation(Population):
Note that the rate is not currently time-dependent.
"""
# Emission Rate (infectious quantum / h)
# Emission Rate (virions / h)
# Note on units: exhalation rate is in m^3/h, aerosols in mL/cm^3
# and viral load in virus/mL -> 1e6 conversion factor
aerosols = self.expiration.aerosols(self.mask)
@ -685,15 +685,14 @@ class InfectedPopulation(Population):
ER = (self.virus.viral_load_in_sputum *
self.activity.exhalation_rate *
10 ** 6 *
aerosols /
self.virus.quantum_infectious_dose)
aerosols)
# For superspreading event, where ejection_factor is infinite we fix the ER
# based on Miller et al. (2020).
if isinstance(aerosols, np.ndarray):
ER[np.isinf(aerosols)] = 970
ER[np.isinf(aerosols)] = 970 * self.virus.infectious_dose
elif np.isinf(aerosols):
ER = 970
ER = 970 * self.virus.infectious_dose
return ER
@ -804,7 +803,7 @@ class ConcentrationModel:
def concentration(self, time: float) -> _VectorisedFloat:
"""
Virus quanta concentration, as a function of time.
Virus exposure concentration, as a function of time.
The formulas used here assume that all parameters (ventilation,
emission rate) are constant between two state changes - only
the value of these parameters at the next state change, are used.
@ -867,8 +866,8 @@ class ExposureModel:
#: The fraction of viruses actually deposited in the respiratory tract
fraction_deposited: _VectorisedFloat = 0.6
def quanta_exposure(self) -> _VectorisedFloat:
"""The number of virus quanta per meter^3."""
def exposure(self) -> _VectorisedFloat:
"""The number of virus per meter^3."""
exposure = 0.0
for start, stop in self.exposed.presence.boundaries():
@ -877,7 +876,7 @@ class ExposureModel:
return exposure * self.repeats
def infection_probability(self) -> _VectorisedFloat:
exposure = self.quanta_exposure()
exposure = self.exposure()
inf_aero = (
self.exposed.activity.inhalation_rate *
@ -886,7 +885,7 @@ class ExposureModel:
)
# Probability of infection.
return (1 - np.exp(-inf_aero)) * 100
return (1 - np.exp(-(inf_aero/self.concentration_model.virus.infectious_dose))) * 100
def expected_new_cases(self) -> _VectorisedFloat:
prob = self.infection_probability()

View file

@ -43,18 +43,18 @@ symptomatic_vl_frequencies = LogCustomKernel(
virus_distributions = {
'SARS_CoV_2': mc.SARSCoV2(
viral_load_in_sputum=symptomatic_vl_frequencies,
quantum_infectious_dose=100,
infectious_dose=100,
),
'SARS_CoV_2_B117': mc.SARSCoV2(
viral_load_in_sputum=symptomatic_vl_frequencies,
quantum_infectious_dose=60,
infectious_dose=60,
),
'SARS_CoV_2_P1': mc.SARSCoV2(
viral_load_in_sputum=symptomatic_vl_frequencies,
quantum_infectious_dose=100/2.25,
infectious_dose=100/2.25,
),
'SARS_CoV_2_B16172': mc.SARSCoV2(
viral_load_in_sputum=symptomatic_vl_frequencies,
quantum_infectious_dose=60/1.6,
infectious_dose=60/1.6,
),
}

View file

@ -13,7 +13,6 @@ from cara import models
{'humidity': np.array([0.5, 0.4])},
{'air_change': np.array([100, 120])},
{'viral_load_in_sputum': np.array([5e8, 1e9])},
{'quantum_infectious_dose': np.array([50, 20])},
]
)
def test_concentration_model_vectorisation(override_params):
@ -21,8 +20,7 @@ def test_concentration_model_vectorisation(override_params):
'volume': 75,
'humidity': 0.5,
'air_change': 100,
'viral_load_in_sputum': 1e9,
'quantum_infectious_dose': 50,
'viral_load_in_sputum': 1e9
}
defaults.update(override_params)
@ -43,7 +41,7 @@ def test_concentration_model_vectorisation(override_params):
),
virus=models.SARSCoV2(
viral_load_in_sputum=defaults['viral_load_in_sputum'],
quantum_infectious_dose=defaults['quantum_infectious_dose'],
infectious_dose=50.,
),
expiration=models.Expiration((1., 0., 0.)),
)

View file

@ -3,26 +3,27 @@ import typing
import numpy as np
import numpy.testing
import pytest
from dataclasses import dataclass
from cara import models
from cara.models import ExposureModel
@dataclass(frozen=True)
class KnownConcentrations(models.ConcentrationModel):
"""
A ConcentrationModel which is based on pre-known quanta concentrations and
A ConcentrationModel which is based on pre-known exposure concentrations and
which therefore doesn't need other components. Useful for testing.
"""
def __init__(self, concentration_function: typing.Callable) -> None:
self._func = concentration_function
concentration_function: typing.Callable
def infectious_virus_removal_rate(self, time: float) -> models._VectorisedFloat:
# very large decay constant -> same as constant concentration
return 1.e50
def _concentration_limit(self, time: float) -> models._VectorisedFloat:
return self._func(time)
return self.concentration_function(time)
def state_change_times(self):
return [0, 24]
@ -31,7 +32,7 @@ class KnownConcentrations(models.ConcentrationModel):
return 24
def concentration(self, time: float) -> models._VectorisedFloat: # noqa
return self._func(time)
return self.concentration_function(time)
halftime = models.PeriodicInterval(120, 60)
@ -49,33 +50,47 @@ populations = [
# 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),
models.Activity(np.array([0.51, 0.57]), 0.57),
),
]
dummy_room = models.Room(50, 0.5)
dummy_ventilation = models._VentilationBase()
dummy_infected_population = models.InfectedPopulation(
number=1,
presence=halftime,
mask=models.Mask.types['Type I'],
activity=models.Activity.types['Standing'],
virus=models.Virus.types['SARS_CoV_2_B117'],
expiration=models.Expiration.types['Talking']
)
def known_concentrations(func):
return KnownConcentrations(dummy_room, dummy_ventilation, dummy_infected_population, func)
@pytest.mark.parametrize(
"population, cm, f_dep, expected_exposure, expected_probability",[
[populations[1], KnownConcentrations(lambda t: 1.2), 1.,
np.array([14.4, 14.4]), np.array([99.6803184113, 99.5181053773])],
"population, cm, f_dep, expected_exposure, expected_probability", [
[populations[1], known_concentrations(lambda t: 36), 1.,
np.array([432, 432]), np.array([99.6803184113, 99.5181053773])],
[populations[2], KnownConcentrations(lambda t: 1.2), 1.,
np.array([14.4, 14.4]), np.array([97.4574432074, 98.3493482895])],
[populations[2], known_concentrations(lambda t: 36), 1.,
np.array([432, 432]), np.array([97.4574432074, 98.3493482895])],
[populations[0], KnownConcentrations(lambda t: np.array([1.2, 2.4])), 1.,
np.array([14.4, 28.8]), np.array([98.3493482895, 99.9727534893])],
[populations[0], known_concentrations(lambda t: np.array([36, 72])), 1.,
np.array([432, 864]), np.array([98.3493482895, 99.9727534893])],
[populations[1], KnownConcentrations(lambda t: np.array([1.2, 2.4])), 1.,
np.array([14.4, 28.8]), np.array([99.6803184113, 99.9976777757])],
[populations[1], known_concentrations(lambda t: np.array([36, 72])), 1.,
np.array([432, 864]), np.array([99.6803184113, 99.9976777757])],
[populations[0], KnownConcentrations(lambda t: 2.4), np.array([0.5, 1.]),
28.8, np.array([98.3493482895, 99.9727534893])],
[populations[0], known_concentrations(lambda t: 72), np.array([0.5, 1.]),
864, np.array([98.3493482895, 99.9727534893])],
])
def test_exposure_model_ndarray(population, cm, f_dep,
expected_exposure, expected_probability):
model = ExposureModel(cm, population, fraction_deposited = f_dep)
model = ExposureModel(cm, population, fraction_deposited=f_dep)
np.testing.assert_almost_equal(
model.quanta_exposure(), expected_exposure
model.exposure(), expected_exposure
)
np.testing.assert_almost_equal(
model.infection_probability(), expected_probability, decimal=10
@ -89,12 +104,13 @@ def test_exposure_model_ndarray(population, cm, f_dep,
@pytest.mark.parametrize("population", populations)
def test_exposure_model_ndarray_and_float_mix(population):
cm = KnownConcentrations(lambda t: 0 if np.floor(t) % 2 else np.array([1.2, 1.2]))
cm = known_concentrations(lambda t: 0 if np.floor(t) %
2 else np.array([1.2, 1.2]))
model = ExposureModel(cm, population)
expected_exposure = np.array([14.4, 14.4])
np.testing.assert_almost_equal(
model.quanta_exposure(), expected_exposure
model.exposure(), expected_exposure
)
assert isinstance(model.infection_probability(), np.ndarray)
@ -103,22 +119,23 @@ def test_exposure_model_ndarray_and_float_mix(population):
@pytest.mark.parametrize("population", populations)
def test_exposure_model_compare_scalar_vector(population):
cm_scalar = KnownConcentrations(lambda t: 1.2)
cm_array = KnownConcentrations(lambda t: np.array([1.2, 1.2]))
cm_scalar = known_concentrations(lambda t: 1.2)
cm_array = known_concentrations(lambda t: np.array([1.2, 1.2]))
model_scalar = ExposureModel(cm_scalar, population)
model_array = ExposureModel(cm_array, population)
expected_exposure = 14.4
np.testing.assert_almost_equal(
model_scalar.quanta_exposure(), expected_exposure
model_scalar.exposure(), expected_exposure
)
np.testing.assert_almost_equal(
model_array.quanta_exposure(), np.array([expected_exposure]*2)
model_array.exposure(), np.array([expected_exposure]*2)
)
@pytest.fixture
def conc_model():
interesting_times = models.SpecificInterval(([0, 1], [1.01, 1.02], [12, 24]))
interesting_times = models.SpecificInterval(
([0, 1], [1.01, 1.02], [12, 24]))
always = models.SpecificInterval(((0, 24),))
return models.ConcentrationModel(
models.Room(25),
@ -133,23 +150,51 @@ def conc_model():
)
)
# expected quanta were computed with a trapezoidal integration, using
# expected exposure were computed with a trapezoidal integration, using
# a mesh of 10'000 pts per exposed presence interval.
@pytest.mark.parametrize("exposed_time_interval, expected_quanta", [
[(0, 1), 5.3334352],
[(1, 1.01), 0.061759078],
[(1.01, 1.02), 0.060016487],
[(12, 12.01), 0.0019012647],
[(12, 24), 75.513005],
[(0, 24), 81.956988],
]
@pytest.mark.parametrize("exposed_time_interval, expected_exposure", [
[(0, 1), 266.67176],
[(1, 1.01), 3.0879539],
[(1.01, 1.02), 3.00082435],
[(12, 12.01), 0.095063235],
[(12, 24), 3775.65025],
[(0, 24), 4097.8494],
]
)
def test_exposure_model_integral_accuracy(exposed_time_interval,
expected_quanta, conc_model):
expected_exposure, conc_model):
presence_interval = models.SpecificInterval((exposed_time_interval,))
population = models.Population(
10, presence_interval, models.Mask.types['Type I'],
models.Activity.types['Standing'],
)
model = ExposureModel(conc_model, population, fraction_deposited=1.)
np.testing.assert_allclose(model.quanta_exposure(), expected_quanta)
np.testing.assert_allclose(model.exposure(), expected_exposure)
def test_infectious_dose_vectorisation():
infected_population = models.InfectedPopulation(
number=1,
presence=halftime,
mask=models.Mask.types['Type I'],
activity=models.Activity.types['Standing'],
virus=models.SARSCoV2(
viral_load_in_sputum=1e9,
infectious_dose=np.array([50, 20, 30]),
),
expiration=models.Expiration.types['Talking']
)
cm = KnownConcentrations(
dummy_room, dummy_ventilation, infected_population, lambda t: 1.2)
presence_interval = models.SpecificInterval(((0, 1),))
population = models.Population(
10, presence_interval, models.Mask.types['Type I'],
models.Activity.types['Standing'],
)
model = ExposureModel(cm, population, fraction_deposited=1.0)
inf_probability = model.infection_probability()
assert isinstance(inf_probability, np.ndarray)
assert inf_probability.shape == (3, )

View file

@ -7,14 +7,12 @@ import cara.models
@pytest.mark.parametrize(
"override_params", [
{'viral_load_in_sputum': np.array([5e8, 1e9])},
{'quantum_infectious_dose': np.array([50, 20])},
{'exhalation_rate': np.array([0.75, 0.81])},
]
)
def test_infected_population_vectorisation(override_params):
defaults = {
'viral_load_in_sputum': 1e9,
'quantum_infectious_dose': 50,
'exhalation_rate': 0.75,
}
defaults.update(override_params)
@ -33,7 +31,7 @@ def test_infected_population_vectorisation(override_params):
),
virus=cara.models.Virus(
viral_load_in_sputum=defaults['viral_load_in_sputum'],
quantum_infectious_dose=defaults['quantum_infectious_dose'],
infectious_dose=50.,
),
expiration=cara.models.Expiration((1., 0., 0.)),
)

View file

@ -7,7 +7,7 @@ import cara.data as data
def test_no_mask_superspeading_emission_rate(baseline_model):
expected_rate = 970.
expected_rate = 48500.
npt.assert_allclose(
[baseline_model.infected.emission_rate(t) for t in [0, 1, 4, 4.5, 5, 8, 9]],
[0, expected_rate, expected_rate, 0, 0, expected_rate, 0],
@ -44,7 +44,7 @@ def test_concentrations(baseline_model):
concentrations = [baseline_model.concentration(t) for t in ts]
npt.assert_allclose(
concentrations,
[0.000000e+00, 0.41611256, 1.3205628e-14, 0.41611256, 4.1909001e-28],
[0.000000e+00, 20.805628, 6.602814e-13, 20.805628, 2.09545e-26],
rtol=1e-6
)
@ -354,16 +354,16 @@ def build_exposure_model(concentration_model):
)
# expected quanta were computed with a trapezoidal integration, using
# expected exposure were computed with a trapezoidal integration, using
# a mesh of 100'000 pts per exposed presence interval.
@pytest.mark.parametrize(
"month, expected_quanta",
"month, expected_exposure",
[
['Jan', 9.930854],
['Jun', 37.962708],
['Jan', 496.5427],
['Jun', 1898.1354],
],
)
def test_quanta_hourly_dep(month,expected_quanta):
def test_exposure_hourly_dep(month,expected_exposure):
m = build_exposure_model(
build_hourly_dependent_model(
month,
@ -371,20 +371,20 @@ def test_quanta_hourly_dep(month,expected_quanta):
intervals_presence_infected=((8, 12), (13, 17))
)
)
quanta = m.quanta_exposure()
npt.assert_allclose(quanta, expected_quanta)
exposure = m.exposure()
npt.assert_allclose(exposure, expected_exposure)
# expected quanta were computed with a trapezoidal integration, using
# expected 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(
"month, expected_quanta",
"month, expected_exposure",
[
['Jan', 9.993842],
['Jun', 40.151985],
['Jan', 499.6921],
['Jun', 2007.59925],
],
)
def test_quanta_hourly_dep_refined(month,expected_quanta):
def test_exposure_hourly_dep_refined(month,expected_exposure):
m = build_exposure_model(
build_hourly_dependent_model(
month,
@ -393,5 +393,5 @@ def test_quanta_hourly_dep_refined(month,expected_quanta):
temperatures=data.GenevaTemperatures,
)
)
quanta = m.quanta_exposure()
npt.assert_allclose(quanta, expected_quanta, rtol=0.02)
exposure = m.exposure()
npt.assert_allclose(exposure, expected_exposure, rtol=0.02)

View file

@ -84,6 +84,6 @@ def test_build_concentration_model(baseline_mc_model: cara.monte_carlo.Concentra
def test_build_exposure_model(baseline_mc_exposure_model: cara.monte_carlo.ExposureModel):
model = baseline_mc_exposure_model.build_model(7)
assert isinstance(model, cara.models.ExposureModel)
prob = model.quanta_exposure()
prob = model.exposure()
assert isinstance(prob, np.ndarray)
assert prob.shape == (7, )

View file

@ -233,42 +233,42 @@ def skagit_chorale_mc():
@pytest.mark.parametrize(
"mc_model, expected_pi, expected_new_cases, expected_dose, expected_qR",
"mc_model, expected_pi, expected_new_cases, expected_dose, expected_ER",
[
["shared_office_mc", 10.7, 0.32, 0.954, 10.9],
["classroom_mc", 36.1, 6.85, 13.0, 474.4],
["ski_cabin_mc", 16.3, 0.49, 0.599, 123.4],
["gym_mc", 2.25, 0.63, 0.01307, 16.4],
["waiting_room_mc", 9.72, 1.36, 0.571, 58.9],
["skagit_chorale_mc",29.9, 17.9, 1.90, 1414],
["shared_office_mc", 10.7, 0.32, 57.24, 654],
["classroom_mc", 36.1, 6.85, 780.0, 28464],
["ski_cabin_mc", 16.3, 0.49, 35.94, 7404],
["gym_mc", 2.25, 0.63, 0.7842, 984],
["waiting_room_mc", 9.72, 1.36, 34.26, 3534],
["skagit_chorale_mc",29.9, 17.9, 190.0, 141400],
]
)
def test_report_models(mc_model, expected_pi, expected_new_cases,
expected_dose, expected_qR, request):
expected_dose, expected_ER, request):
mc_model = request.getfixturevalue(mc_model)
exposure_model = mc_model.build_model(size=SAMPLE_SIZE)
npt.assert_allclose(exposure_model.infection_probability().mean(),
expected_pi, rtol=TOLERANCE)
npt.assert_allclose(exposure_model.expected_new_cases().mean(),
expected_new_cases, rtol=TOLERANCE)
npt.assert_allclose(exposure_model.quanta_exposure().mean(),
npt.assert_allclose(exposure_model.exposure().mean(),
expected_dose, rtol=TOLERANCE)
npt.assert_allclose(
exposure_model.concentration_model.infected.emission_rate_when_present().mean(),
expected_qR, rtol=TOLERANCE)
expected_ER, rtol=TOLERANCE)
@pytest.mark.parametrize(
"mask_type, month, expected_pi, expected_dose, expected_qR",
"mask_type, month, expected_pi, expected_dose, expected_ER",
[
["No mask", "Jul", 30.0, 6.764, 64.9],
["Type I", "Jul", 10.2, 1.223, 11.7],
["FFP2", "Jul", 4.0, 1.223, 11.7],
["Type I", "Feb", 4.25, 0.357, 11.7],
["No mask", "Jul", 30.0, 405.84, 3894],
["Type I", "Jul", 10.2, 73.38, 702],
["FFP2", "Jul", 4.0, 73.38, 702],
["Type I", "Feb", 4.25, 21.42, 702],
],
)
def test_small_shared_office_Geneva(mask_type, month, expected_pi,
expected_dose, expected_qR):
expected_dose, expected_ER):
concentration_mc = mc.ConcentrationModel(
room=models.Room(volume=33, humidity=0.5),
ventilation=models.MultipleVentilation(
@ -309,8 +309,8 @@ def test_small_shared_office_Geneva(mask_type, month, expected_pi,
exposure_model = exposure_mc.build_model(size=SAMPLE_SIZE)
npt.assert_allclose(exposure_model.infection_probability().mean(),
expected_pi, rtol=TOLERANCE)
npt.assert_allclose(exposure_model.quanta_exposure().mean(),
npt.assert_allclose(exposure_model.exposure().mean(),
expected_dose, rtol=TOLERANCE)
npt.assert_allclose(
exposure_model.concentration_model.infected.emission_rate_when_present().mean(),
expected_qR, rtol=TOLERANCE)
expected_ER, rtol=TOLERANCE)