cara/caimira/tests/models/test_dynamic_population.py
lrdossan cd3df90c4c Added dynamic groups in backend and propagated required changes to requests
- Correction of typos

- Changes do model to accommodate group of exposure models

- Handled virus validator to accommodate group of exposure models

- Modifications to accomodate generation of results for exposure model group

- Added data registry attribute to exposuremodelgroups class

- Fixed test for dynamic models

- Defined ExposureModelGroup with required methods

- Adapted virus report data to generate results for groups of exposed population

- Build same distributions for different models

- Fixed bug with defaults

- Handled definition of a single ExposureModel root obj when only one group is defined

- Added short-range expirations per group

- Added full validation on short_range interactions with dynamic exposure model

- Added full set of tests

- Updated docstrings

- Added support for number of people in exposed population, which should be identical within each group

- Added type checks

- Added UI adjustments for expected new cases

- Adapted exposed population data format to accommodate constant total_people
2025-03-19 15:45:05 +01:00

233 lines
9.5 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(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 dynamic_infected_single_exposure_model(full_exposure_model, baseline_infected_population):
return dc_utils.nested_replace(full_exposure_model,
{'concentration_model.infected': baseline_infected_population, })
@pytest.fixture
def dynamic_population_exposure_model(full_exposure_model, baseline_infected_population):
return dc_utils.nested_replace(full_exposure_model, {
'concentration_model.infected': baseline_infected_population,
})
@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: models.InfectedPopulation, time: float):
int_population_number: models.InfectedPopulation = full_exposure_model.concentration_model.infected
piecewise_population_number: models.InfectedPopulation = baseline_infected_population
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_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_population_exposure_model.infection_probability())
@pytest.mark.skip
def test_dynamic_total_probability_rule(
dynamic_infected_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_population_exposure_model.total_probability_rule()
@pytest.mark.skip
def test_dynamic_expected_new_cases(
dynamic_infected_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_population_exposure_model.expected_new_cases()
@pytest.mark.skip
def test_dynamic_reproduction_number(
dynamic_infected_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_population_exposure_model.reproduction_number()