Skip to content

Commit

Permalink
added exposure_model, conditional_prob_inf_given_viral_load, monte_ca…
Browse files Browse the repository at this point in the history
…rlo_sample_size, infected_population_scenario_activity
  • Loading branch information
lrdossan committed Oct 20, 2023
1 parent 8a63ff0 commit 00681f8
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 75 deletions.
50 changes: 8 additions & 42 deletions caimira/apps/calculator/defaults.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import typing

import caimira.apps.calculator.global_store.constants as constants

# ------------------ Default form values ----------------------

# 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()
DEFAULT_MC_SAMPLE_SIZE = 250_000
DEFAULT_MC_SAMPLE_SIZE = constants.monte_carlo_sample_size

#: The default values for undefined fields. Note that the defaults here
#: and the defaults in the html form must not be contradictory.
Expand Down Expand Up @@ -79,50 +81,14 @@

# ------------------ Activities ----------------------

ACTIVITIES: typing.List[typing.Dict[str, typing.Any]] = [
# Mostly silent in the office, but 1/3rd of time speaking.
{'name': 'office', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': 2}},
{'name': 'smallmeeting', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': None}},
# Each infected person spends 1/3 of time speaking.
{'name': 'largemeeting', 'activity': 'Standing',
'expiration': {'Speaking': 1, 'Breathing': 2}},
{'name': 'callcentre', 'activity': 'Seated', 'expiration': 'Speaking'},
# Daytime control room shift, 50% speaking.
{'name': 'controlroom-day', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': 1}},
# Nightshift control room, 10% speaking.
{'name': 'controlroom-night', 'activity': 'Seated',
'expiration': {'Speaking': 1, 'Breathing': 9}},
{'name': 'library', 'activity': 'Seated', 'expiration': 'Breathing'},
# Model 1/2 of time spent speaking in a lab.
{'name': 'lab', 'activity': 'Light activity',
'expiration': {'Speaking': 1, 'Breathing': 1}},
# Model 1/2 of time spent speaking in a workshop.
{'name': 'workshop', 'activity': 'Moderate activity',
'expiration': {'Speaking': 1, 'Breathing': 1}},
{'name': 'training', 'activity': 'Standing', 'expiration': 'Speaking'},
{'name': 'training_attendee', 'activity': 'Seated', 'expiration': 'Breathing'},
{'name': 'gym', 'activity': 'Heavy exercise', 'expiration': 'Breathing'},
{'name': 'household-day', 'activity': 'Light activity',
'expiration': {'Breathing': 5, 'Speaking': 5}},
{'name': 'household-night', 'activity': 'Seated',
'expiration': {'Breathing': 7, 'Speaking': 3}},
{'name': 'primary-school', 'activity': 'Light activity',
'expiration': {'Breathing': 5, 'Speaking': 5}},
{'name': 'secondary-school', 'activity': 'Light activity',
'expiration': {'Breathing': 7, 'Speaking': 3}},
{'name': 'university', 'activity': 'Seated',
'expiration': {'Breathing': 9, 'Speaking': 1}},
{'name': 'restaurant', 'activity': 'Seated',
'expiration': {'Breathing': 1, 'Speaking': 9}},
{'name': 'precise', 'activity': None, 'expiration': None},
]
ACTIVITIES: typing.Dict[str, typing.Dict] = constants.infected_population_scenario_activity
# ACTIVITIES: typing.List[typing.Dict[str, typing.Any]] = [
# {'name': 'precise', 'activity': None, 'expiration': None},
# ]

# ------------------ Validation ----------------------

ACTIVITY_TYPES = [activity['name'] for activity in ACTIVITIES]
ACTIVITY_TYPES = list(ACTIVITIES.keys())
COFFEE_OPTIONS_INT = {'coffee_break_0': 0, 'coffee_break_1': 1,
'coffee_break_2': 2, 'coffee_break_4': 4}
CONFIDENCE_LEVEL_OPTIONS = {'confidence_low': 10,
Expand Down
47 changes: 45 additions & 2 deletions caimira/apps/calculator/global_store/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,39 @@
'maximum_distance': 2.,
}

infected_population_scenario_activity = {
'office': {'activity': 'Seated', 'expiration': {'Speaking': 1, 'Breathing': 2}},
'smallmeeting': {'activity': 'Seated', 'expiration': {'Speaking': 1}},
'largemeeting': {'activity': 'Standing', 'expiration': {'Speaking': 1, 'Breathing': 2}},
'callcenter': {'activity': 'Seated', 'expiration': {'Speaking': 1}},
'controlroom-day': {'activity': 'Seated', 'expiration': {'Speaking': 1, 'Breathing': 1}},
'controlroom-night': {'activity': 'Seated', 'expiration': {'Speaking': 1, 'Breathing': 9}},
'library': {'activity': 'Seated', 'expiration': {'Breathing': 1}},
'lab': {'activity': 'Light activity', 'expiration': {'Speaking': 1, 'Breathing': 1}},
'workshop': {'activity': 'Moderate activity', 'expiration': {'Speaking': 1, 'Breathing': 1}},
'training': {'activity': 'Standing', 'expiration': {'Speaking': 1}},
'training_attendee': {'activity': 'Seated', 'expiration': {'Breathing': 1}},
'gym': {'activity': 'Heavy exercise', 'expiration': {'Breathing': 1}},
'household-day': {'activity': 'Light activity', 'expiration': {'Breathing': 5, 'Speaking': 5}},
'household-night': {'activity': 'Seated', 'expiration': {'Breathing': 7, 'Speaking': 3}},
'primary-school': {'activity': 'Light activity', 'expiration': {'Breathing': 5, 'Speaking': 5}},
'secondary-school': {'activity': 'Light activity', 'expiration': {'Breathing': 7, 'Speaking': 3}},
'university': {'activity': 'Seated', 'expiration': {'Breathing': 9, 'Speaking': 1}},
'restaurant': {'activity': 'Seated', 'expiration': {'Breathing': 1, 'Speaking': 9}},
}

monte_carlo_sample_size = 250000

conditional_prob_inf_given_viral_load = {
'lower_percentile': 0.05,
'upper_percentile': 0.95,
'min_vl': 2,
'max_vl': 10,
}

exposure_model = {
'repeats': 1
}

def update_local_reference(local_data, api_data):
'''
Expand All @@ -195,7 +228,9 @@ def update_local_reference(local_data, api_data):


async def populate_data():
global data_fetched, covid_overal_vl_data, infectious_dose_distribution, viable_to_RNA_ratio_distribution, virus_distributions, activity_distributions, mask_distributions, expiration_BLO_factors, long_range_expiration_distributions, short_range_expiration_distributions, short_range_distances
global data_fetched, covid_overal_vl_data, infectious_dose_distribution, viable_to_RNA_ratio_distribution, virus_distributions, activity_distributions, mask_distributions
global expiration_BLO_factors, long_range_expiration_distributions, short_range_expiration_distributions, short_range_distances, infected_population_scenario_activity
global monte_carlo_sample_size, conditional_prob_inf_given_viral_load, exposure_model

if not data_fetched and os.environ.get('DATA_SERVICE_ENABLED', 'False').lower() == 'true':
# Fetch data if it hasn't been fetched yet
Expand Down Expand Up @@ -224,7 +259,15 @@ async def populate_data():
short_range_expiration_distributions, data['short_range_expiration_distributions'])
short_range_distances = update_local_reference(
short_range_distances, data['short_range_distances'])

infected_population_scenario_activity = update_local_reference(
infected_population_scenario_activity, data['infected_population_scenario_activity'])
monte_carlo_sample_size = update_local_reference(
monte_carlo_sample_size, data['monte_carlo_sample_size'])
conditional_prob_inf_given_viral_load = update_local_reference(
conditional_prob_inf_given_viral_load, data['conditional_prob_inf_given_viral_load'])
exposure_model = update_local_reference(
exposure_model, data['exposure_model'])

data_fetched = True

# Executed when this module is imported
Expand Down
35 changes: 9 additions & 26 deletions caimira/apps/calculator/model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .defaults import (NO_DEFAULT, DEFAULT_MC_SAMPLE_SIZE, DEFAULTS, ACTIVITIES, ACTIVITY_TYPES, COFFEE_OPTIONS_INT, CONFIDENCE_LEVEL_OPTIONS,
MECHANICAL_VENTILATION_TYPES, MASK_TYPES, MASK_WEARING_OPTIONS, MONTH_NAMES, VACCINE_BOOSTER_TYPE, VACCINE_TYPE,
VENTILATION_TYPES, VIRUS_TYPES, VOLUME_TYPES, WINDOWS_OPENING_REGIMES, WINDOWS_TYPES)
import caimira.apps.calculator.global_store.constants as constants

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -373,7 +374,7 @@ def build_CO2_model(self, sample_size=DEFAULT_MC_SAMPLE_SIZE) -> models.CO2Conce
population = mc.SimplePopulation(
number=models.IntPiecewiseConstant(transition_times=tuple(transition_times), values=tuple(total_people)),
presence=None,
activity=activity_distributions[ACTIVITIES[ACTIVITY_TYPES.index(self.activity_type)]['activity']],
activity=activity_distributions[ACTIVITIES[self.activity_type]['activity']],
)

# Builds a CO2 concentration model based on model inputs
Expand Down Expand Up @@ -506,9 +507,9 @@ def infected_population(self) -> mc.InfectedPopulation:
# Initializes the virus
virus = virus_distributions[self.virus_type]

activity_index = ACTIVITY_TYPES.index(self.activity_type)
activity_defn = ACTIVITIES[activity_index]['activity']
expiration_defn = ACTIVITIES[activity_index]['expiration']
# activity_index = ACTIVITY_TYPES.index(self.activity_type)
activity_defn = ACTIVITIES[self.activity_type]['activity']
expiration_defn = ACTIVITIES[self.activity_type]['expiration']

if (self.activity_type == 'smallmeeting'):
# Conversation of N people is approximately 1/N% of the time speaking.
Expand All @@ -533,29 +534,11 @@ def infected_population(self) -> mc.InfectedPopulation:
return infected

def exposed_population(self) -> mc.Population:
scenario_activity = {
'office': 'Seated',
'controlroom-day': 'Seated',
'controlroom-night': 'Seated',
'smallmeeting': 'Seated',
'largemeeting': 'Standing',
'callcentre': 'Seated',
'library': 'Seated',
'training': 'Standing',
'training_attendee': 'Seated',
'lab':'Light activity',
'workshop': 'Moderate activity',
'gym':'Heavy exercise',
'household-day': 'Light activity',
'household-night': 'Seated',
'primary-school': 'Light activity',
'secondary-school': 'Light activity',
'university': 'Seated',
'restaurant': 'Seated',
'precise': self.precise_activity['physical_activity'] if self.activity_type == 'precise' else None,
}
# scenario_activity = {
# 'precise': self.precise_activity['physical_activity'] if self.activity_type == 'precise' else None,
# }

activity_defn = scenario_activity[self.activity_type]
activity_defn = str(constants.infected_population_scenario_activity[self.activity_type]['activity'])
activity = activity_distributions[activity_defn]

infected_occupants = self.infected_people
Expand Down
9 changes: 6 additions & 3 deletions caimira/apps/calculator/report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from ... import monte_carlo as mc
from .model_generator import FormData, DEFAULT_MC_SAMPLE_SIZE
from ... import dataclass_utils
import caimira.apps.calculator.global_store.constants as constants


def model_start_end(model: models.ExposureModel):
Expand Down Expand Up @@ -201,16 +202,18 @@ def conditional_prob_inf_given_vl_dist(infection_probability: models._Vectorised
for vl_log in viral_loads:
specific_prob = infection_probability[np.where((vl_log-step/2-specific_vl)*(vl_log+step/2-specific_vl)<0)[0]] #type: ignore
pi_means.append(specific_prob.mean())
lower_percentiles.append(np.quantile(specific_prob, 0.05))
upper_percentiles.append(np.quantile(specific_prob, 0.95))
lower_percentiles.append(np.quantile(specific_prob, constants.conditional_prob_inf_given_viral_load['lower_percentile']))
upper_percentiles.append(np.quantile(specific_prob, constants.conditional_prob_inf_given_viral_load['upper_percentile']))

return pi_means, lower_percentiles, upper_percentiles


def manufacture_conditional_probability_data(exposure_model: models.ExposureModel,
infection_probability: models._VectorisedFloat):

min_vl, max_vl, step = 2, 10, 8/100
min_vl = constants.conditional_prob_inf_given_viral_load['min_vl']
max_vl = constants.conditional_prob_inf_given_viral_load['max_vl']
step = (max_vl - min_vl)/100
viral_loads = np.arange(min_vl, max_vl, step)
specific_vl = np.log10(exposure_model.concentration_model.virus.viral_load_in_sputum)
pi_means, lower_percentiles, upper_percentiles = conditional_prob_inf_given_vl_dist(infection_probability, viral_loads,
Expand Down
3 changes: 2 additions & 1 deletion caimira/tests/apps/calculator/test_model_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from caimira.apps.calculator.model_generator import minutes_since_midnight
from caimira import models
from caimira.monte_carlo.data import expiration_distributions
import caimira.apps.calculator.global_store.constants as constants


def test_model_from_dict(baseline_form_data):
Expand All @@ -34,7 +35,7 @@ def test_model_from_dict_invalid(baseline_form_data):
]
)
def test_blend_expiration(mask_type):
SAMPLE_SIZE = 250000
SAMPLE_SIZE = constants.monte_carlo_sample_size
TOLERANCE = 0.02
blend = {'Breathing': 2, 'Speaking': 1}
r = model_generator.build_expiration(blend).build_model(SAMPLE_SIZE)
Expand Down
3 changes: 2 additions & 1 deletion caimira/tests/models/test_short_range_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from caimira.apps.calculator.model_generator import build_expiration
from caimira.monte_carlo.data import short_range_expiration_distributions,\
expiration_distributions, short_range_distances, activity_distributions
import caimira.apps.calculator.global_store.constants as constants

SAMPLE_SIZE = 250_000
SAMPLE_SIZE = constants.monte_carlo_sample_size


@pytest.fixture
Expand Down

0 comments on commit 00681f8

Please sign in to comment.