From 00681f8b10b2076a81c4064a7ab84f166041fde2 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Fri, 20 Oct 2023 10:22:49 +0200 Subject: [PATCH] added exposure_model, conditional_prob_inf_given_viral_load, monte_carlo_sample_size, infected_population_scenario_activity --- caimira/apps/calculator/defaults.py | 50 +++---------------- .../apps/calculator/global_store/constants.py | 47 ++++++++++++++++- caimira/apps/calculator/model_generator.py | 35 ++++--------- caimira/apps/calculator/report_generator.py | 9 ++-- .../apps/calculator/test_model_generator.py | 3 +- .../tests/models/test_short_range_model.py | 3 +- 6 files changed, 72 insertions(+), 75 deletions(-) diff --git a/caimira/apps/calculator/defaults.py b/caimira/apps/calculator/defaults.py index 22b157aa..de6ff6cd 100644 --- a/caimira/apps/calculator/defaults.py +++ b/caimira/apps/calculator/defaults.py @@ -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. @@ -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, diff --git a/caimira/apps/calculator/global_store/constants.py b/caimira/apps/calculator/global_store/constants.py index 5d6ac3ec..612ec9c8 100644 --- a/caimira/apps/calculator/global_store/constants.py +++ b/caimira/apps/calculator/global_store/constants.py @@ -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): ''' @@ -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 @@ -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 diff --git a/caimira/apps/calculator/model_generator.py b/caimira/apps/calculator/model_generator.py index 664eef2b..8741b9b5 100644 --- a/caimira/apps/calculator/model_generator.py +++ b/caimira/apps/calculator/model_generator.py @@ -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__) @@ -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 @@ -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. @@ -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 diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py index 6efb9177..01378d46 100644 --- a/caimira/apps/calculator/report_generator.py +++ b/caimira/apps/calculator/report_generator.py @@ -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): @@ -201,8 +202,8 @@ 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 @@ -210,7 +211,9 @@ def conditional_prob_inf_given_vl_dist(infection_probability: models._Vectorised 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, diff --git a/caimira/tests/apps/calculator/test_model_generator.py b/caimira/tests/apps/calculator/test_model_generator.py index 099a00d2..57b48a95 100644 --- a/caimira/tests/apps/calculator/test_model_generator.py +++ b/caimira/tests/apps/calculator/test_model_generator.py @@ -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): @@ -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) diff --git a/caimira/tests/models/test_short_range_model.py b/caimira/tests/models/test_short_range_model.py index 84ced3c2..4a8627de 100644 --- a/caimira/tests/models/test_short_range_model.py +++ b/caimira/tests/models/test_short_range_model.py @@ -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