cara/caimira/tests/models/test_dynamic_population.py
lrdossan 20b0467f89 Backend separation
- extract, isolate and package it in a completely independent Python module, versioned and in a way that allows releases on PyPI.org
- fixed error in placeholder for secondary school (data registry defaults)
- added restriction in pytest version to install
- expected number of new cases fix
- data registry update (schema v2.1.1)
- github update
- deprecate ExpertApplication and CO2Application
- changes to reflect schema update 2.0.2
- version update
- Fixed error with f_inf (short-range)
- new folder layout
- Conditional probability data update
- General fixes
- Fitting results in L/S/person
- CO2 fitting algorithm refinement
2024-09-02 17:39:46 +02:00

261 lines
11 KiB
Python

import re
import numpy as np
import numpy.testing as npt
import pytest
from caimira.calculator.models import models
from caimira.calculator.models import dataclass_utils as dc_utils
@pytest.fixture
def full_exposure_model(data_registry):
return models.ExposureModel(
data_registry=data_registry,
concentration_model=models.ConcentrationModel(
data_registry=data_registry,
room=models.Room(volume=100),
ventilation=models.AirChange(
active=models.PeriodicInterval(120, 120), air_exch=0.25),
infected=models.InfectedPopulation(
data_registry=data_registry,
number=1,
presence=models.SpecificInterval(((8, 12), (13, 17), )),
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Seated'],
expiration=models.Expiration.types['Breathing'],
virus=models.Virus.types['SARS_CoV_2'],
host_immunity=0.
),
evaporation_factor=0.3,
),
short_range=(),
exposed=models.Population(
number=10,
presence=models.SpecificInterval(((8, 12), (13, 17), )),
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Seated'],
host_immunity=0.
),
geographical_data=(),
)
@pytest.fixture
def baseline_infected_population_number(data_registry):
return models.InfectedPopulation(
data_registry=data_registry,
number=models.IntPiecewiseConstant(
(8, 12, 13, 17), (1, 0, 1)),
presence=None,
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Seated'],
virus=models.Virus.types['SARS_CoV_2'],
expiration=models.Expiration.types['Breathing'],
host_immunity=0.,
)
@pytest.fixture
def baseline_exposed_population_number():
return models.Population(
number=models.IntPiecewiseConstant(
(8, 12, 13, 17), (10, 0, 10)),
presence=None,
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Seated'],
host_immunity=0.,
)
@pytest.fixture
def dynamic_infected_single_exposure_model(full_exposure_model, baseline_infected_population_number):
return dc_utils.nested_replace(full_exposure_model,
{'concentration_model.infected': baseline_infected_population_number, })
@pytest.fixture
def dynamic_exposed_single_exposure_model(full_exposure_model, baseline_exposed_population_number):
return dc_utils.nested_replace(full_exposure_model,
{'exposed': baseline_exposed_population_number, })
@pytest.fixture
def dynamic_population_exposure_model(full_exposure_model, baseline_infected_population_number ,baseline_exposed_population_number):
return dc_utils.nested_replace(full_exposure_model, {
'concentration_model.infected': baseline_infected_population_number,
'exposed': baseline_exposed_population_number,
})
@pytest.mark.parametrize(
"time",
[4., 8., 10., 12., 13., 14., 16., 20., 24.],
)
def test_population_number(full_exposure_model: models.ExposureModel,
baseline_infected_population_number: models.InfectedPopulation, time: float):
int_population_number: models.InfectedPopulation = full_exposure_model.concentration_model.infected
piecewise_population_number: models.InfectedPopulation = baseline_infected_population_number
with pytest.raises(
TypeError,
match=f'The presence argument must be an "Interval". Got {type(None)}'
):
dc_utils.nested_replace(
int_population_number, {'presence': None}
)
with pytest.raises(
TypeError,
match="The presence argument must be None for a IntPiecewiseConstant number"
):
dc_utils.nested_replace(
piecewise_population_number, {'presence': models.SpecificInterval(((8, 12), ))}
)
assert int_population_number.person_present(time) == piecewise_population_number.person_present(time)
assert int_population_number.people_present(time) == piecewise_population_number.people_present(time)
@pytest.mark.parametrize(
"time",
[4., 8., 10., 12., 13., 14., 16., 20., 24.],
)
def test_concentration_model_dynamic_population(full_exposure_model: models.ExposureModel,
dynamic_infected_single_exposure_model: models.ExposureModel,
time: float):
assert full_exposure_model.concentration(time) == dynamic_infected_single_exposure_model.concentration(time)
@pytest.mark.parametrize("number_of_infected",[1, 2, 3, 4, 5])
@pytest.mark.parametrize("time",[9., 12.5, 16.])
def test_linearity_with_number_of_infected(full_exposure_model: models.ExposureModel,
dynamic_infected_single_exposure_model: models.ExposureModel,
time: float,
number_of_infected: int):
static_multiple_exposure_model: models.ExposureModel = dc_utils.nested_replace(
full_exposure_model,
{
'concentration_model.infected.number': number_of_infected,
}
)
npt.assert_almost_equal(static_multiple_exposure_model.concentration(time), dynamic_infected_single_exposure_model.concentration(time) * number_of_infected)
npt.assert_almost_equal(static_multiple_exposure_model.deposited_exposure(), dynamic_infected_single_exposure_model.deposited_exposure() * number_of_infected)
@pytest.mark.parametrize(
"time", (8., 9., 10., 11., 12., 13., 14.),
)
def test_dynamic_dose(data_registry, full_exposure_model: models.ExposureModel, time: float):
dynamic_infected: models.ExposureModel = dc_utils.nested_replace(
full_exposure_model,
{
'concentration_model.infected': models.InfectedPopulation(
data_registry=data_registry,
number=models.IntPiecewiseConstant(
(8, 10, 12, 13, 17), (1, 2, 0, 3)),
presence=None,
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Seated'],
virus=models.Virus.types['SARS_CoV_2'],
expiration=models.Expiration.types['Breathing'],
host_immunity=0.,
),
}
)
single_infected: models.ExposureModel = dc_utils.nested_replace(
full_exposure_model,
{
'concentration_model.infected.number': 1,
'concentration_model.infected.presence': models.SpecificInterval(((8, 10), )),
}
)
two_infected: models.ExposureModel = dc_utils.nested_replace(
full_exposure_model,
{
'concentration_model.infected.number': 2,
'concentration_model.infected.presence': models.SpecificInterval(((10, 12), )),
}
)
three_infected: models.ExposureModel = dc_utils.nested_replace(
full_exposure_model,
{
'concentration_model.infected.number': 3,
'concentration_model.infected.presence': models.SpecificInterval(((13, 17), )),
}
)
dynamic_concentration = dynamic_infected.concentration(time)
dynamic_exposure = dynamic_infected.deposited_exposure()
static_concentration, static_exposure = zip(*[(model.concentration(time), model.deposited_exposure())
for model in (single_infected, two_infected, three_infected)])
npt.assert_almost_equal(dynamic_concentration, np.sum(static_concentration))
npt.assert_almost_equal(dynamic_exposure, np.sum(static_exposure))
def test_infection_probability(
full_exposure_model: models.ExposureModel,
dynamic_infected_single_exposure_model: models.ExposureModel,
dynamic_exposed_single_exposure_model: models.ExposureModel,
dynamic_population_exposure_model: models.ExposureModel):
base_infection_probability = full_exposure_model.infection_probability()
npt.assert_almost_equal(base_infection_probability, dynamic_infected_single_exposure_model.infection_probability())
npt.assert_almost_equal(base_infection_probability, dynamic_exposed_single_exposure_model.infection_probability())
npt.assert_almost_equal(base_infection_probability, dynamic_population_exposure_model.infection_probability())
def test_dynamic_total_probability_rule(
dynamic_infected_single_exposure_model: models.ExposureModel,
dynamic_exposed_single_exposure_model: models.ExposureModel,
dynamic_population_exposure_model: models.ExposureModel):
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute total probability "
"(including incidence rate) with dynamic occupancy")):
dynamic_infected_single_exposure_model.total_probability_rule()
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute total probability "
"(including incidence rate) with dynamic occupancy")):
dynamic_exposed_single_exposure_model.total_probability_rule()
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute total probability "
"(including incidence rate) with dynamic occupancy")):
dynamic_population_exposure_model.total_probability_rule()
def test_dynamic_expected_new_cases(
dynamic_infected_single_exposure_model: models.ExposureModel,
dynamic_exposed_single_exposure_model: models.ExposureModel,
dynamic_population_exposure_model: models.ExposureModel):
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute expected new cases "
"with dynamic occupancy")):
dynamic_infected_single_exposure_model.expected_new_cases()
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute expected new cases "
"with dynamic occupancy")):
dynamic_exposed_single_exposure_model.expected_new_cases()
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute expected new cases "
"with dynamic occupancy")):
dynamic_population_exposure_model.expected_new_cases()
def test_dynamic_reproduction_number(
dynamic_infected_single_exposure_model: models.ExposureModel,
dynamic_exposed_single_exposure_model: models.ExposureModel,
dynamic_population_exposure_model: models.ExposureModel):
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute reproduction number "
"with dynamic occupancy")):
dynamic_infected_single_exposure_model.reproduction_number()
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute reproduction number "
"with dynamic occupancy")):
dynamic_exposed_single_exposure_model.reproduction_number()
with pytest.raises(NotImplementedError, match=re.escape("Cannot compute reproduction number "
"with dynamic occupancy")):
dynamic_population_exposure_model.reproduction_number()