def test_microdistancing__with_tanh_func_and_adjuster(): params = { "foo": { "function_type": "tanh", "parameters": { "shape": -0.05, "inflection_time": 275, "lower_asymptote": 0.6, "upper_asymptote": 1, }, "locations": LOCATIONS, }, "foo_adjuster": { "function_type": "empiric", "parameters": { "max_effect": 0.6, "times": [0, 365], "values": [1, 100], }, "locations": LOCATIONS, }, } expect_func = tanh_based_scaleup(**params["foo"]["parameters"]) expect_adj_func = scale_up_function([0, 365], [0.6, 60], method=4) params = {k: MicroDistancingFunc(**v) for k, v in params.items()} funcs = get_microdistancing_funcs(params=params, square_mobility_effect=False) assert funcs["work"](0) == 1 - expect_func(0) * expect_adj_func(0) assert funcs["home"](0) == 1 - expect_func(0) * expect_adj_func(0) assert funcs["work"](300) == 1 - expect_func(300) * expect_adj_func(300) assert funcs["home"](300) == 1 - expect_func(300) * expect_adj_func(300)
def test_tanh_function(): for grad in (0.1, 0.5, 1.0): for inflect_time in (0.0, 100.0, 500.0): for lower_asymptote in (0.0, 1.0): for upper_asymptote in (1.0, 100.0): # Get the function tanh_function = tanh_based_scaleup( shape=grad, inflection_time=inflect_time, lower_asymptote=lower_asymptote, upper_asymptote=upper_asymptote, ) # Get the results results = [ tanh_function(i_time) for i_time in np.linspace(0.0, 100.0, 10) ] inflection_result = tanh_function(inflect_time) # Make sure it makes sense assert all( [result >= lower_asymptote for result in results]) assert all( [result <= upper_asymptote for result in results]) assert inflection_result == lower_asymptote / 2.0 + upper_asymptote / 2
def get_posterior_percentiles_time_variant_profile(calibration_path, function='detection', burn_in=0): """ :param calibration_path: string :param function: only 'detection' for now :param burn_in: integer """ combined_burned_samples = combine_and_burn_samples(calibration_path, burn_in) calculated_times = range(200) store_matrix = np.zeros( (len(calculated_times), combined_burned_samples.shape[0])) if function == 'detection': i = 0 for index, row in combined_burned_samples.iterrows(): my_func = tanh_based_scaleup(row['tv_detection_b'], row['tv_detection_c'], 0.) detect_vals = [ row['prop_detected_among_symptomatic'] * my_func(t) for t in calculated_times ] store_matrix[:, i] = detect_vals i += 1 perc = np.percentile(store_matrix, [2.5, 25, 50, 75, 97.5], axis=1) calculated_times = np.array([calculated_times]) perc = np.concatenate((calculated_times, perc)) np.savetxt(function + ".csv", perc, delimiter=',')
def get_country_posterior_detection_percentiles(country_param_values): calculated_times = list(range(300))[30:] store_matrix = np.zeros( (len(calculated_times), len(country_param_values["time.start"]))) for i in range(len(country_param_values["time.start"])): if "case_detection.lower_asymptote" in country_param_values: lower_asymptote = country_param_values[ "case_detection.lower_asymptote"][i] else: lower_asymptote = 0.0 my_func = tanh_based_scaleup( country_param_values["case_detection.shape"][i], country_param_values["case_detection.inflection_time"][i], lower_asymptote, country_param_values["case_detection.upper_asymptote"][i], ) detect_vals = [my_func(float(t)) for t in calculated_times] store_matrix[:, i] = detect_vals perc = np.percentile(store_matrix, [2.5, 25, 50, 75, 97.5], axis=1) calculated_times = np.array([calculated_times]) perc = np.concatenate((calculated_times, perc)) return perc
def get_country_posterior_detection_percentiles(country_param_values): calculated_times = range(213) store_matrix = np.zeros( (len(calculated_times), len(country_param_values['start_time']))) for i in range(len(country_param_values['start_time'])): if 'tv_detection_sigma' in country_param_values: sigma = country_param_values['tv_detection_sigma'][i] else: sigma = 0. my_func = tanh_based_scaleup(country_param_values['tv_detection_b'][i], country_param_values['tv_detection_c'][i], sigma) detect_vals = [ country_param_values['prop_detected_among_symptomatic'][i] * my_func(float(t)) for t in calculated_times ] store_matrix[:, i] = detect_vals perc = np.percentile(store_matrix, [2.5, 25, 50, 75, 97.5], axis=1) calculated_times = np.array([calculated_times]) perc = np.concatenate((calculated_times, perc)) return perc
def calculate_screening_rate(time_idx, model, compartment_values, derived_outputs): screening_rate_func = tanh_based_scaleup( model.parameters["time_variant_tb_screening_rate"]["shape"], model.parameters["time_variant_tb_screening_rate"]["inflection_time"], model.parameters["time_variant_tb_screening_rate"]["lower_asymptote"], model.parameters["time_variant_tb_screening_rate"]["upper_asymptote"], ) return screening_rate_func(model.times[time_idx])
def __init__( self, country_iso3: str, region: str, mixing: dict, npi_effectiveness_params: dict, google_mobility_locations: dict, is_periodic_intervention: bool, periodic_int_params: dict, periodic_end_time: float, microdistancing_params: dict, ): """Build the time variant location adjustment functions""" # Load mobility data google_mobility_values, google_mobility_days = get_mobility_data( country_iso3, region, BASE_DATETIME, google_mobility_locations) # Build mixing data timeseries mixing = update_mixing_data( mixing, npi_effectiveness_params, google_mobility_values, google_mobility_days, is_periodic_intervention, periodic_int_params, periodic_end_time, ) # Build the time variant location adjustment functions from mixing timeseries mixing_locations = [loc for loc in LOCATIONS if loc in mixing] self.loc_adj_funcs = {} for loc_key in mixing_locations: loc_times = mixing[loc_key]["times"] loc_vals = mixing[loc_key]["values"] self.loc_adj_funcs[loc_key] = scale_up_function(loc_times, loc_vals, method=4) # Work out microdistancing function to be applied to all non-household locations if not microdistancing_params: self.microdistancing_function = None elif microdistancing_params["function_type"] == "tanh": self.microdistancing_function = tanh_based_scaleup( **microdistancing_params["parameters"]) elif microdistancing_params["function_type"] == "empiric": micro_times = microdistancing_params["parameters"]["times"] micro_vals = microdistancing_params["parameters"]["values"] self.microdistancing_function = scale_up_function(micro_times, micro_vals, method=4) # Load all location-specific mixing info. self.matrix_components = {} for sheet_type in ["all_locations"] + LOCATIONS: # Loads a 16x16 ndarray self.matrix_components[sheet_type] = get_country_mixing_matrix( sheet_type, country_iso3)
def get_microdist_func_component(func_params: MicroDistancingFunc): """ Get one function of time using the standard parameter request structure for any microdistancing function or adjustment to a microdistancing function. """ if func_params.function_type == "tanh": return tanh_based_scaleup(**func_params.parameters.dict()) elif func_params.function_type == "empiric": micro_times = func_params.parameters.times multiplier = func_params.parameters.max_effect micro_vals = [ multiplier * value for value in func_params.parameters.values ] return scale_up_function(micro_times, micro_vals, method=4) elif func_params.function_type == "constant": return lambda time: func_params.parameters.effect
def test_microdistancing__with_tanh_func_and_square_mobility_effect(): params = { "foo": { "function_type": "tanh", "parameters": { "shape": -0.05, "inflection_time": 275, "lower_asymptote": 0.6, "upper_asymptote": 1, }, "locations": LOCATIONS, } } expect_func = tanh_based_scaleup(**params["foo"]["parameters"]) params = {k: MicroDistancingFunc(**v) for k, v in params.items()} funcs = get_microdistancing_funcs(params=params, square_mobility_effect=True) assert funcs["work"](0) == (1 - expect_func(0)) ** 2 assert funcs["home"](0) == (1 - expect_func(0)) ** 2 assert funcs["work"](300) == (1 - expect_func(300)) ** 2 assert funcs["home"](300) == (1 - expect_func(300)) ** 2
def edit_adjustments_for_diabetes( model, adjustments, age_breakpoints, prop_diabetes, rr_progression_diabetes, future_diabetes_multiplier, ): diabetes_scale_up = tanh_based_scaleup(shape=0.05, inflection_time=1980, lower_asymptote=0.0, upper_asymptote=1.0) future_diabetes_trend = make_linear_curve(x_0=2020, x_1=2050, y_0=1, y_1=future_diabetes_multiplier) def combined_diabetes_scale_up(t): multiplier = 1.0 if t > 2020: multiplier = future_diabetes_trend(t) return multiplier * diabetes_scale_up(t) for i, age_breakpoint in enumerate(age_breakpoints): for stage in ["early", "late"]: param_name = stage + "_activation_rate" stratified_param_name = param_name + "Xage_" + str(age_breakpoint) function_name = stratified_param_name + "_func" unadjusted_progression_rate = adjustments[param_name][str( age_breakpoint)] adjustments[param_name][str(age_breakpoint)] = function_name model.time_variants[ function_name] = make_age_diabetes_scaleup_func( prop_diabetes[age_breakpoint], combined_diabetes_scale_up, rr_progression_diabetes, unadjusted_progression_rate, ) model.parameters[stratified_param_name] = stratified_param_name return adjustments
def build_detected_proportion_func( agegroup_strata: List[str], country: Country, pop: Population, testing: TestingToDetection, case_detection: CaseDetection, ): """ Returns a time varying function that gives us the proportion of cases detected. """ if testing is not None: # More empiric approach based on per capita testing rates assumed_tests_parameter = testing.assumed_tests_parameter assumed_cdr_parameter = testing.assumed_cdr_parameter smoothing_period = testing.smoothing_period testing_pop, testing_region = get_testing_pop(agegroup_strata, country, pop) detected_proportion = find_cdr_function_from_test_data( assumed_tests_parameter, assumed_cdr_parameter, smoothing_period, country.iso3, testing_pop, subregion=testing_region, ) else: # Approach based on a hyperbolic tan function detected_proportion = tanh_based_scaleup( case_detection.shape, case_detection.inflection_time, case_detection.lower_asymptote, case_detection.upper_asymptote, ) return detected_proportion
def process_unstratified_parameter_values(params, implement_acf, implement_ltbi_screening): """ This function calculates some unstratified parameter values for parameters that need pre-processing. This usually involves combining multiple input parameters to determine a model parameter :return: """ # Set unstratified detection flow parameter if "organ" in params["stratify_by"]: params["detection_rate"] = 1 detection_rate_func = None else: screening_rate_func = tanh_based_scaleup( params["time_variant_tb_screening_rate"]["shape"], params["time_variant_tb_screening_rate"]["inflection_time"], params["time_variant_tb_screening_rate"]["lower_asymptote"], params["time_variant_tb_screening_rate"]["upper_asymptote"], ) def detection_rate_func(t): return screening_rate_func(t) * params["passive_screening_sensitivity"]["unstratified"] params["detection_rate"] = "detection_rate" # Set unstratified treatment-outcome-related parameters if ( "age" in params["stratify_by"] ): # relapse and treatment death need to be adjusted by age later params["treatment_recovery_rate"] = 1.0 params["treatment_death_rate"] = 1.0 params["relapse_rate"] = 1.0 treatment_recovery_func = None treatment_death_func = None relapse_func = None else: time_variant_tsr = scale_up_function( list(params["time_variant_tsr"].keys()), list(params["time_variant_tsr"].values()), method=4, ) def treatment_recovery_func(t): return max( 1 / params["treatment_duration"], params["universal_death_rate"] / params["prop_death_among_negative_tx_outcome"] * (1.0 / (1.0 - time_variant_tsr(t)) - 1.0), ) def treatment_death_func(t): return ( params["prop_death_among_negative_tx_outcome"] * treatment_recovery_func(t) * (1.0 - time_variant_tsr(t)) / time_variant_tsr(t) - params["universal_death_rate"] ) def relapse_func(t): return (treatment_death_func(t) + params["universal_death_rate"]) * ( 1.0 / params["prop_death_among_negative_tx_outcome"] - 1.0 ) params["treatment_recovery_rate"] = "treatment_recovery_rate" params["treatment_death_rate"] = "treatment_death_rate" params["relapse_rate"] = "relapse_rate" # adjust late reactivation parameters using multiplier for latency_stage in ["early", "late"]: param_name = f"{latency_stage}_activation_rate" for key in params["age_specific_latency"][param_name]: params["age_specific_latency"][param_name][key] *= params["progression_multiplier"] # load unstratified latency parameters params = get_unstratified_parameter_values(params) # set reinfection contact rate parameters for state in ["latent", "recovered"]: params["contact_rate_from_" + state] = ( params["contact_rate"] * params["rr_infection_" + state] ) # assign unstratified parameter values to infection death and self-recovery processes for param_name in ["infect_death_rate", "self_recovery_rate"]: params[param_name] = params[param_name + "_dict"]["unstratified"] # if age-stratification is used, the baseline mortality rate is set to 1 so it can get multiplied by a time-variant if "age" in params["stratify_by"]: params["universal_death_rate"] = 1.0 # PT in household contacts contact_rate_functions = {} if params["hh_contacts_pt"]: scaleup_screening_prop = scale_up_function( x=[params["hh_contacts_pt"]["start_time"], params["hh_contacts_pt"]["start_time"] + 1], y=[0, params["hh_contacts_pt"]["prop_hh_contacts_screened"]], method=4, ) def make_contact_rate_func(raw_contact_rate_value): def contact_rate_func(t): rel_reduction = ( params["hh_contacts_pt"]["prop_smearpos_among_prev_tb"] * params["hh_contacts_pt"]["prop_hh_transmission"] * scaleup_screening_prop(t) * params["ltbi_screening_sensitivity"] * params["hh_contacts_pt"]["prop_pt_completion"] ) return raw_contact_rate_value * (1 - rel_reduction) return contact_rate_func for suffix in ["", "_from_latent", "_from_recovered"]: param_name = f"contact_rate{suffix}" raw_value = params[param_name] params[param_name] = param_name contact_rate_functions[param_name] = make_contact_rate_func(raw_value) # ACF flow parameter acf_detection_func = None if implement_acf: if ( len(params["time_variant_acf"]) == 1 and params["time_variant_acf"][0]["stratum_filter"] is None ): # universal ACF is applied acf_detection_func = scale_up_function( list(params["time_variant_acf"][0]["time_variant_screening_rate"].keys()), [ v * params["acf_screening_sensitivity"] for v in list( params["time_variant_acf"][0]["time_variant_screening_rate"].values() ) ], method=4, ) params["acf_detection_rate"] = "acf_detection_rate" else: params["acf_detection_rate"] = 1.0 # Preventive treatment flow parameters preventive_treatment_func = None if implement_ltbi_screening: if ( len(params["time_variant_ltbi_screening"]) == 1 and params["time_variant_ltbi_screening"][0]["stratum_filter"] is None ): # universal LTBI screening is applied preventive_treatment_func = scale_up_function( list( params["time_variant_ltbi_screening"][0]["time_variant_screening_rate"].keys() ), [ v * params["ltbi_screening_sensitivity" * params["pt_efficacy"]] for v in list( params["time_variant_ltbi_screening"][0][ "time_variant_screening_rate" ].values() ) ], method=4, ) params["preventive_treatment_rate"] = "preventive_treatment_rate" else: params["preventive_treatment_rate"] = 1.0 return ( params, treatment_recovery_func, treatment_death_func, relapse_func, detection_rate_func, acf_detection_func, preventive_treatment_func, contact_rate_functions, )
def stratify_by_clinical(model, model_parameters, compartments): """ Stratify the infectious compartments of the covid model (not including the pre-symptomatic compartments, which are actually infectious) - notifications are derived from progress from early to late for some strata - the proportion of people moving from presymt to early infectious, conditioned on age group - rate of which people flow through these compartments (reciprocal of time, using within_* which is a rate of ppl / day) - infectiousness levels adjusted by early/late and for clinical strata - we start with an age stratified infection fatality rate - 50% of deaths for each age bracket die in ICU - the other deaths go to hospital, assume no-one else can die from COVID - should we ditch this? """ # General stratification agegroup_strata = model_parameters["all_stratifications"]["agegroup"] all_stratifications = model_parameters["all_stratifications"] clinical_strata = model_parameters["clinical_strata"] # Infection rate multiplication # Importation implement_importation = model_parameters["implement_importation"] traveller_quarantine = model_parameters["traveller_quarantine"] # Time variant case detection prop_detected_among_symptomatic = model_parameters["prop_detected_among_symptomatic"] # FIXME: Make it clear that this for tahn tv_detection_b = model_parameters["tv_detection_b"] tv_detection_c = model_parameters["tv_detection_c"] tv_detection_sigma = model_parameters["tv_detection_sigma"] # ??? within_hospital_early = model_parameters["within_hospital_early"] within_icu_early = model_parameters["within_icu_early"] # Strata entry and infection death proportions icu_prop = model_parameters["icu_prop"] icu_mortality_prop = model_parameters["icu_mortality_prop"] infection_fatality_props_10_year = model_parameters["infection_fatality_props"] hospital_props_10_year = model_parameters["hospital_props"] hospital_props_multiplier = model_parameters["hospital_props_multiplier"] symptomatic_props_10_year = model_parameters["symptomatic_props"] use_raw_mortality_estimates = model_parameters["use_raw_mortality_estimates"] ifr_double_exp_model_params = model_parameters["ifr_double_exp_model_params"] # Define stratification - only stratify infected compartments strata_to_implement = clinical_strata model_parameters["all_stratifications"]["clinical"] = strata_to_implement compartments_to_split = [ comp for comp in compartments if comp.startswith(Compartment.EARLY_INFECTIOUS) or comp.startswith(Compartment.LATE_INFECTIOUS) ] # FIXME: Set params to make comparison happy model_parameters["infection_fatality_props"] = infection_fatality_props_10_year model_parameters["hospital_props"] = hospital_props_10_year # Calculate the proportion of 80+ years old among the 75+ population elderly_populations = get_population_by_agegroup([0, 75, 80], model_parameters["iso3"], model_parameters["region"]) prop_over_80 = elderly_populations[2] / sum(elderly_populations[1:]) # Age dependent proportions of infected people who become symptomatic. # This is defined 8x10 year bands, 0-70+, which we transform into 16x5 year bands 0-75+ symptomatic_props = repeat_list_elements(2, symptomatic_props_10_year) # Age dependent proportions of symptomatic people who become hospitalised. # This is defined 9x10 year bands, 0-80+, which we trransform into 16x5 year bands 0-75+ # Calculate 75+ age bracket as half 75-79 and half 80+ hospital_props = repeat_list_elements_average_last_two( [p * hospital_props_multiplier for p in hospital_props_10_year], prop_over_80) # Infection fatality rate by age group. if use_raw_mortality_estimates: # Data in props used 10 year bands 0-80+, but we want 5 year bands from 0-75+ # Calculate 75+ age bracket as weighted average between 75-79 and half 80+ infection_fatality_props = repeat_list_elements_average_last_two( infection_fatality_props_10_year, prop_over_80 ) else: infection_fatality_props = age_specific_ifrs_from_double_exp_model( ifr_double_exp_model_params['k'], ifr_double_exp_model_params['m'], ifr_double_exp_model_params['last_representative_age'] ) # Find the absolute progression proportions. symptomatic_props_arr = np.array(symptomatic_props) hospital_props_arr = np.array(hospital_props) # Determine the absolute proportion of presymptomatic who become sympt vs non-sympt. sympt, non_sympt = subdivide_props(1, symptomatic_props_arr) # Determine the absolute proportion of sympt who become hospitalized vs non-hospitalized. sympt_hospital, sympt_non_hospital = subdivide_props(sympt, hospital_props_arr) # Determine the absolute proportion of hospitalized who become icu vs non-icu. sympt_hospital_icu, sympt_hospital_non_icu = subdivide_props(sympt_hospital, icu_prop) abs_props = { "sympt": sympt.tolist(), "non_sympt": non_sympt.tolist(), "hospital": sympt_hospital.tolist(), "sympt_non_hospital": sympt_non_hospital.tolist(), # Overidden by a by time-varying proprotion later "icu": sympt_hospital_icu.tolist(), "hospital_non_icu": sympt_hospital_non_icu.tolist(), } # Calculate the absolute proportion of all patients who should eventually reach hospital death or ICU death. # Find IFR that needs to be contributed by ICU and non-ICU hospital deaths hospital_death, icu_death = [], [] for age_idx, agegroup in enumerate(agegroup_strata): # If IFR for age group is greater than absolute proportion hospitalised, increased hospitalised proportion if infection_fatality_props[age_idx] > abs_props["hospital"][age_idx]: abs_props["hospital"][age_idx] = infection_fatality_props[age_idx] # Find the target absolute ICU mortality and the amount left over from IFRs to go to hospital, if any target_icu_abs_mort = abs_props["icu"][age_idx] * icu_mortality_prop left_over_mort = infection_fatality_props[age_idx] - target_icu_abs_mort # If some IFR will be left over for the hospitalised if left_over_mort > 0.0: hospital_death_prop = left_over_mort icu_death_prop = target_icu_abs_mort # Otherwise if all IFR taken up by ICU else: hospital_death_prop = 0.0 icu_death_prop = infection_fatality_props[age_idx] hospital_death.append(hospital_death_prop) icu_death.append(icu_death_prop) abs_props.update({"hospital_death": hospital_death, "icu_death": icu_death}) # FIXME: These depend on static variables which have been made time-variant. # fatality rate for hospitalised patients rel_props = { "hospital_death": element_wise_list_division( abs_props["hospital_death"], abs_props["hospital_non_icu"] ), "icu_death": element_wise_list_division(abs_props["icu_death"], abs_props["icu"]), } # Progression rates into the infectious compartment(s) # Define progresion rates into non-symptomatic compartments using parameter adjustment. stratification_adjustments = {} for age_idx, age in enumerate(agegroup_strata): key = f"to_infectiousXagegroup_{age}" stratification_adjustments[key] = { "non_sympt": non_sympt[age_idx], "icu": sympt_hospital_icu[age_idx], "hospital_non_icu": sympt_hospital_non_icu[age_idx], } # Create a function for the proportion of symptomatic people who are detected at timestep `t`. scale_up_multiplier = \ tanh_based_scaleup(tv_detection_b, tv_detection_c, tv_detection_sigma) # Create function describing the proportion of cases detected over time def prop_detect_among_sympt_func(t): # Raw value without adjustment for any improved detection intervention without_intervention_value = prop_detected_among_symptomatic * scale_up_multiplier(t) # Return value modified for any future intervention that narrows the case detection gap int_detect_gap_reduction = model_parameters['int_detection_gap_reduction'] return without_intervention_value + (1. - without_intervention_value) * int_detect_gap_reduction # Set time-varying isolation proportions for age_idx, agegroup in enumerate(agegroup_strata): # Pass the functions to the model tv_props = TimeVaryingProprotions(age_idx, abs_props, prop_detect_among_sympt_func) time_variants = [ [f"prop_sympt_non_hospital_{agegroup}", tv_props.get_abs_prop_sympt_non_hospital,], [f"prop_sympt_isolate_{agegroup}", tv_props.get_abs_prop_isolated], ] for name, func in time_variants: model.time_variants[name] = func # Tell the model to use these time varying functions for the stratification adjustments. agegroup_adj = stratification_adjustments[f"to_infectiousXagegroup_{agegroup}"] agegroup_adj["sympt_non_hospital"] = f"prop_sympt_non_hospital_{agegroup}" agegroup_adj["sympt_isolate"] = f"prop_sympt_isolate_{agegroup}" # Calculate death rates and progression rates for hospitalised and ICU patients progression_death_rates = {} for stratum in ("hospital", "icu"): ( progression_death_rates[stratum + "_infect_death"], progression_death_rates[stratum + "_within_late"], ) = find_rates_and_complements_from_ifr( rel_props[stratum + "_death"], 1, [model_parameters["within_" + stratum + "_late"]] * 16, ) # Death and non-death progression between infectious compartments towards the recovered compartment for param in ("within_late", "infect_death"): stratification_adjustments.update( adjust_upstream_stratified_parameter( param, strata_to_implement[3:], "agegroup", model_parameters["all_stratifications"]["agegroup"], [ progression_death_rates["hospital_" + param], progression_death_rates["icu_" + param], ], overwrite=True, ) ) # Over-write rate of progression for early compartments for hospital and ICU # FIXME: Ask Romain if he knows why we bother doing this. stratification_adjustments.update( { "within_infectious": { "hospital_non_icuW": model_parameters["within_hospital_early"], "icuW": model_parameters["within_icu_early"], }, } ) # Sort out all infectiousness adjustments for entire model here # Sort out all infectiousness adjustments for all compartments of the model. # Now make adjustment for asymptomatic patients only strata_infectiousness = {} for stratum in strata_to_implement: if stratum + "_infect_multiplier" in model_parameters: strata_infectiousness[stratum] = model_parameters[stratum + "_infect_multiplier"] # Make adjustment for isolation/quarantine for stratum in strata_to_implement: if stratum in model_parameters["late_infect_multiplier"]: model.individual_infectiousness_adjustments.append( [ [Compartment.LATE_INFECTIOUS, "clinical_" + stratum], model_parameters["late_infect_multiplier"][stratum], ] ) # FIXME: Ask Romain about importation # work out time-variant clinical proportions for imported cases accounting for quarantine if model_parameters["implement_importation"]: rep_age_group = ( "35" # the clinical split will be defined according to this representative age-group ) tvs = model.time_variants # to reduce verbosity # create scale-up function for quarantine quarantine_scale_up = scale_up_function( model_parameters["traveller_quarantine"]["times"], model_parameters["traveller_quarantine"]["values"], method=4, ) # set fixed clinical proportions for imported cases (hospital_non_icu and icu) importation_props_by_clinical = { "hospital_non_icu": stratification_adjustments[ "to_infectiousXagegroup_" + rep_age_group ]["hospital_non_icu"], "icu": stratification_adjustments["to_infectiousXagegroup_" + rep_age_group]["icu"], } # create time-variant function for remaining imported clinical proportions tv_prop_imported_non_sympt = lambda t: stratification_adjustments[ "to_infectiousXagegroup_" + rep_age_group ]["non_sympt"] * (1.0 - quarantine_scale_up(t)) tv_prop_imported_sympt_non_hospital = lambda t: tvs[ stratification_adjustments["to_infectiousXagegroup_" + rep_age_group][ "sympt_non_hospital" ] ](t) * (1.0 - quarantine_scale_up(t)) tv_prop_imported_sympt_isolate = lambda t: tvs[ stratification_adjustments["to_infectiousXagegroup_" + rep_age_group]["sympt_isolate"] ](t) + quarantine_scale_up(t) * ( tvs[ stratification_adjustments["to_infectiousXagegroup_" + rep_age_group][ "sympt_non_hospital" ] ](t) + stratification_adjustments["to_infectiousXagegroup_" + rep_age_group]["non_sympt"] ) # Pass time-variant functions to the model object model.time_variants["tv_prop_imported_non_sympt"] = tv_prop_imported_non_sympt model.time_variants[ "tv_prop_imported_sympt_non_hospital" ] = tv_prop_imported_sympt_non_hospital model.time_variants["tv_prop_imported_sympt_isolate"] = tv_prop_imported_sympt_isolate for stratum in ["non_sympt", "sympt_isolate", "sympt_non_hospital"]: importation_props_by_clinical[stratum] = "tv_prop_imported_" + stratum # create absolute time-variant case detection proportion that will be returned to be used to set importation flow def modelled_abs_detection_proportion_imported(t): return ( stratification_adjustments["to_infectiousXagegroup_" + rep_age_group]["icu"] + stratification_adjustments["to_infectiousXagegroup_" + rep_age_group][ "hospital_non_icu" ] + tvs[ stratification_adjustments["to_infectiousXagegroup_" + rep_age_group][ "sympt_isolate" ] ](t) ) else: importation_props_by_clinical = {} modelled_abs_detection_proportion_imported = None # Stratify the model using the SUMMER stratification function model.stratify( "clinical", strata_to_implement, compartments_to_split, infectiousness_adjustments=strata_infectiousness, requested_proportions={ stratum: 1.0 / len(strata_to_implement) for stratum in strata_to_implement }, adjustment_requests=stratification_adjustments, entry_proportions=importation_props_by_clinical, verbose=False, ) return modelled_abs_detection_proportion_imported
def stratify_by_organ(model, params): compartments_to_stratify = [ Compartment.INFECTIOUS, Compartment.ON_TREATMENT, ] organ_strata = [ OrganStratum.SMEAR_POSITIVE, OrganStratum.SMEAR_NEGATIVE, OrganStratum.EXTRAPULMONARY, ] # Define infectiousness adjustment by organ status strata_infectiousness = {} for stratum in organ_strata: if stratum + "_infect_multiplier" in params: strata_infectiousness[stratum] = params[stratum + "_infect_multiplier"] # define differential natural history by organ status flow_adjustments = {} for param_name in ["infect_death_rate", "self_recovery_rate"]: stratified_param_names = get_stratified_param_names( param_name, model.stratifications) for stratified_param_name in stratified_param_names: flow_adjustments[stratified_param_name] = {} for organ_stratum in organ_strata: organ_stratum_ = (organ_stratum if organ_stratum != OrganStratum.EXTRAPULMONARY else OrganStratum.SMEAR_NEGATIVE) flow_adjustments[stratified_param_name][ organ_stratum + "W"] = params[param_name + "_dict"][organ_stratum_] # define differential detection rates by organ status screening_rate_func = tanh_based_scaleup( params["time_variant_tb_screening_rate"]["shape"], params["time_variant_tb_screening_rate"]["inflection_time"], params["time_variant_tb_screening_rate"]["lower_asymptote"], params["time_variant_tb_screening_rate"]["upper_asymptote"], ) if params["awareness_raising"]: awaireness_linear_scaleup = make_linear_curve( x_0=params["awareness_raising"]["scale_up_range"][0], x_1=params["awareness_raising"]["scale_up_range"][1], y_0=1, y_1=params["awareness_raising"]["relative_screening_rate"], ) def awaireness_multiplier(t): if t <= params["awareness_raising"]["scale_up_range"][0]: return 1.0 elif t >= params["awareness_raising"]["scale_up_range"][1]: return params["awareness_raising"]["relative_screening_rate"] else: return awaireness_linear_scaleup(t) else: awaireness_multiplier = lambda t: 1.0 combined_screening_rate_func = lambda t: screening_rate_func( t) * awaireness_multiplier(t) stratified_param_names = get_stratified_param_names( "detection_rate", model.stratifications) for stratified_param_name in stratified_param_names: flow_adjustments[stratified_param_name] = {} for organ_stratum in organ_strata: flow_adjustments[stratified_param_name][organ_stratum] = ( stratified_param_name + "_" + organ_stratum) model.time_variants[stratified_param_name + "_" + organ_stratum] = make_detection_func( organ_stratum, params, combined_screening_rate_func) model.parameters[stratified_param_name + "_" + organ_stratum] = (stratified_param_name + "_" + organ_stratum) # Adjust the progression rates by organ using the requested incidence proportions splitting_proportions = { "smear_positive": params["incidence_props_pulmonary"] * params["incidence_props_smear_positive_among_pulmonary"], "smear_negative": params["incidence_props_pulmonary"] * (1.0 - params["incidence_props_smear_positive_among_pulmonary"]), "extrapulmonary": 1.0 - params["incidence_props_pulmonary"], } for stage in ["early", "late"]: param_stem = stage + "_activation_rate" stratified_param_names = get_stratified_param_names( param_stem, model.stratifications) for stratified_param_name in stratified_param_names: flow_adjustments[stratified_param_name] = splitting_proportions # trigger model stratification model.stratify( "organ", organ_strata, compartments_to_stratify, infectiousness_adjustments=strata_infectiousness, flow_adjustments=flow_adjustments, )
def stratify_by_clinical(_covid_model, model_parameters, compartments): """ Stratify the infectious compartments of the covid model (not including the pre-symptomatic compartments, which are actually infectious) """ for i_age, h_prop in enumerate(model_parameters['hospital_props']): if h_prop > model_parameters['prop_detected_among_symptomatic']: print("Warning: Hospital proportions had to be reduced for age-group " + str(i_age)) model_parameters['hospital_props'][i_age] = model_parameters['prop_detected_among_symptomatic'] # Define stratification strata_to_implement = \ model_parameters["clinical_strata"] model_parameters["all_stratifications"]["clinical"] = \ strata_to_implement compartments_to_split = [ comp for comp in compartments if comp.startswith(Compartment.EARLY_INFECTIOUS) or comp.startswith(Compartment.LATE_INFECTIOUS) ] # Find unadjusted parameters model_parameters.update(get_raw_clinical_props(model_parameters)) # Find the absolute progression proportions from the requested splits abs_props = split_prop_into_two_subprops([1.0] * 16, "", model_parameters["raw_sympt"], "sympt") abs_props.update( split_prop_into_two_subprops(abs_props["sympt"], "sympt", model_parameters["raw_hospital"], "hospital") ) abs_props.update( split_prop_into_two_subprops(abs_props["hospital"], "hospital", [model_parameters["icu_prop"]] * 16, "icu") ) # Find the absolute proportion dying in hospital and in ICU abs_props.update(find_abs_death_props(model_parameters, abs_props)) # CFR for non-ICU hospitalised patients rel_props = { "hospital_death": element_wise_list_division(abs_props["hospital_death"], abs_props["hospital_non_icu"]), "icu_death": element_wise_list_division(abs_props["icu_death"], abs_props["icu"]) } # Progression rates into the infectious compartment(s) fixed_prop_strata = ["non_sympt"] stratification_adjustments = adjust_upstream_stratified_parameter( "to_infectious", fixed_prop_strata, "agegroup", model_parameters["all_stratifications"]["agegroup"], [abs_props[stratum] for stratum in fixed_prop_strata], ) # Set time-variant proportion of sympt_isolate among all symptomatics # create a scale-up function converging to 1 scale_up_multiplier = tanh_based_scaleup(model_parameters['tv_detection_b'], model_parameters['tv_detection_c'], model_parameters['tv_detection_sigma']) # use the input parameter 'prop_detected_among_symptomatic', specifying the maximum prop of isolates among all sympt _tv_prop_detect_among_sympt = lambda t: model_parameters['prop_detected_among_symptomatic'] * scale_up_multiplier(t) # Set isolation rates as absolute proportions _covid_model, stratification_adjustments = \ set_isolation_props(_covid_model, model_parameters, abs_props, stratification_adjustments, _tv_prop_detect_among_sympt) # Calculate death rates and progression rates for hospitalised and ICU patients progression_death_rates = {} for stratum in ("hospital", "icu"): ( progression_death_rates[stratum + "_infect_death"], progression_death_rates[stratum + "_within_late"], ) = find_rates_and_complements_from_ifr( rel_props[stratum + "_death"], model_parameters["n_compartment_repeats"][Compartment.LATE_INFECTIOUS], [model_parameters["within_" + stratum + "_late"]] * 16, ) # Death and non-death progression between infectious compartments towards the recovered compartment for param in ("within_late", "infect_death"): stratification_adjustments.update( adjust_upstream_stratified_parameter( param, strata_to_implement[3:], "agegroup", model_parameters["all_stratifications"]["agegroup"], [progression_death_rates["hospital_" + param], progression_death_rates["icu_" + param]], overwrite=True, ) ) # Over-write rate of progression for early compartments for hospital and ICU stratification_adjustments.update( {"within_infectious": {"hospital_non_icuW": model_parameters['within_hospital_early'], "icuW": model_parameters['within_icu_early']}, } ) # Sort out all infectiousness adjustments for entire model here _covid_model, stratification_adjustments, strata_infectiousness = \ adjust_infectiousness(_covid_model, model_parameters, strata_to_implement, stratification_adjustments) # work out time-variant clinical proportions for imported cases accounting for quarantine if model_parameters['implement_importation'] and model_parameters['imported_cases_explict']: rep_age_group = '35' # the clinical split will be defined according to this representative age-group tvs = _covid_model.time_variants # to reduce verbosity quarantine_scale_up = scale_up_function(model_parameters['traveller_quarantine']['times'], model_parameters['traveller_quarantine']['values'], method=4 ) tv_prop_imported_non_sympt =\ lambda t: stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['non_sympt'] *\ (1. - quarantine_scale_up(t)) tv_prop_imported_sympt_non_hospital = \ lambda t: tvs[stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['sympt_non_hospital']](t) *\ (1. - quarantine_scale_up(t)) tv_prop_imported_sympt_isolate =\ lambda t: tvs[stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['sympt_isolate']](t) +\ quarantine_scale_up(t) *\ (tvs[stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['sympt_non_hospital']](t) + stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['non_sympt']) tv_prop_imported_hospital_non_icu = lambda t: \ tvs[stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['hospital_non_icu']](t) tv_prop_imported_icu = lambda t: \ tvs[stratification_adjustments['to_infectiousXagegroup_' + rep_age_group]['icu']](t) _covid_model.time_variants['tv_prop_imported_non_sympt'] = tv_prop_imported_non_sympt _covid_model.time_variants['tv_prop_imported_sympt_non_hospital'] = tv_prop_imported_sympt_non_hospital _covid_model.time_variants['tv_prop_imported_sympt_isolate'] = tv_prop_imported_sympt_isolate _covid_model.time_variants['tv_prop_imported_hospital_non_icu'] = tv_prop_imported_hospital_non_icu _covid_model.time_variants['tv_prop_imported_icu'] = tv_prop_imported_icu importation_props_by_clinical = {} for stratum in strata_to_implement: importation_props_by_clinical[stratum] = "tv_prop_imported_" + stratum else: importation_props_by_clinical = {} # Stratify the model using the SUMMER stratification function _covid_model.stratify( "clinical", strata_to_implement, compartments_to_split, infectiousness_adjustments=strata_infectiousness, requested_proportions={ stratum: 1.0 / len(strata_to_implement) for stratum in strata_to_implement }, adjustment_requests=stratification_adjustments, entry_proportions=importation_props_by_clinical, verbose=False, ) return _covid_model, model_parameters