示例#1
0
def test_stochastic_exit_flows(pop, rtol, deathrate):
    """
    Check that death flows produce outputs that tend towards mean as pop increases.
    """
    model = CompartmentalModel(
        times=[0, 10], compartments=["S", "I", "R"], infectious_compartments=["I"]
    )
    s_pop = 0.80 * pop
    i_pop = 0.20 * pop
    model.set_initial_population(distribution={"S": s_pop, "I": i_pop})
    model.add_universal_death_flows("deaths", deathrate)
    model.run_stochastic(RANDOM_SEED)

    # No change to recovered compartments
    assert_array_equal(model.outputs[:, 2], 0)

    # Calculate births using mean birth rate
    mean_s = np.zeros_like(model.times)
    mean_i = np.zeros_like(model.times)
    mean_s[0] = s_pop
    mean_i[0] = i_pop
    for i in range(1, len(model.times)):
        mean_s[i] = mean_s[i - 1] - deathrate * mean_s[i - 1]
        mean_i[i] = mean_i[i - 1] - deathrate * mean_i[i - 1]

    # All S and I compartment sizes are are within the error range
    # of the mean of the multinomial dist that determines exit.
    assert_allclose(model.outputs[:, 0], mean_s, rtol=rtol)
    assert_allclose(model.outputs[:, 1], mean_i, rtol=rtol)
示例#2
0
def test_model__with_birth_and_death_rate_replace_deaths__expect_pop_static_overall():
    model = CompartmentalModel(
        times=[0, 5], compartments=["S", "I", "R"], infectious_compartments=["I"]
    )
    model.set_initial_population(distribution={"S": 100, "I": 100})
    model.add_replacement_birth_flow("births", "S")
    # Add some dying at ~2 people / 100 / year.
    model.add_universal_death_flows("deaths", 0.02)
    model.run()
    expected_outputs = np.array(
        [
            [100.0, 100.0, 0],  # Initial conditions
            [102.0, 98.0, 0],
            [104.0, 96.0, 0],
            [105.8, 94.2, 0],  # Tweaked.
            [107.7, 92.3, 0],  # Tweaked.
            [109.5, 90.5, 0],  # Tweaked.
        ]
    )
    assert_allclose(model.outputs, expected_outputs, atol=0.1, verbose=True)
示例#3
0
def test_model__with_higher_birth_than_and_death_rate__expect_pop_increase():
    model = CompartmentalModel(
        times=[0, 5], compartments=["S", "I", "R"], infectious_compartments=["I"]
    )
    model.set_initial_population(distribution={"S": 100, "I": 100})
    # Add some babies at ~10 babies / 100 / year.
    model.add_crude_birth_flow("births", 0.1, "S")
    # Add some dying at ~2 people / 100 / year.
    model.add_universal_death_flows("deaths", 0.02)
    model.run()
    expected_outputs = np.array(
        [
            [100.0, 100.0, 0],  # Initial conditions
            [118.6, 98.0, 0],  # Tweaked ~0.1
            [138.6, 96.1, 0],  # Tweaked ~0.4
            [160.1, 94.2, 0],  # Tweaked ~0.9
            [183.1, 92.3, 0],  # Tweaked ~1.7
            [207.9, 90.5, 0],  # Tweaked ~2.7
        ]
    )
    assert_allclose(model.outputs, expected_outputs, atol=0.1, verbose=True)
示例#4
0
def test_model__with_death_rate__expect_pop_decrease():
    """
    Ensure that a model with two compartments and only death rate dynamics results in fewer people.
    """
    model = CompartmentalModel(
        times=[0, 5], compartments=["S", "I", "R"], infectious_compartments=["I"]
    )
    model.set_initial_population(distribution={"S": 100, "I": 100})
    # Add some dying at ~2 people / 100 / year.
    model.add_universal_death_flows("deaths", 0.02)
    model.run()
    # Expect that we have fewer people in the population per year
    expected_outputs = np.array(
        [
            [100.0, 100.0, 0],  # Initial conditions
            [98.0, 98.0, 0],
            [96.1, 96.1, 0],
            [94.2, 94.2, 0],
            [92.3, 92.3, 0],
            [90.5, 90.5, 0],
        ]
    )
    assert_allclose(model.outputs, expected_outputs, atol=0.1, verbose=True)
示例#5
0
def test_model__with_complex_dynamics__expect_correct_outputs():
    """
    Ensure that a model with the "full suite" of TB dynamics produces correct results:
        - 5 compartments
        - birth rate +  universal death rate
        - standard inter-compartment flows
    """
    model = CompartmentalModel(
        times=[0, 5], compartments=["S", "EL", "LL", "I", "R"], infectious_compartments=["I"]
    )
    model.set_initial_population(distribution={"S": 900, "I": 100})
    model.add_crude_birth_flow("births", 0.02, "S")
    model.add_universal_death_flows("universal_deaths", 0.02)
    model.add_infection_frequency_flow("infection", 14, "S", "EL")
    model.add_infection_frequency_flow("reinfection", 14, "R", "EL")
    model.add_infection_frequency_flow("regression", 3, "LL", "EL")
    model.add_transition_flow("early_progression", 2, "EL", "I")
    model.add_transition_flow("stabilisation", 3, "EL", "LL")
    model.add_transition_flow("early_progression", 1, "LL", "I")
    model.add_death_flow("infect_death", 0.4, "I")
    model.add_transition_flow("recovery", 0.2, "I", "R")
    model.add_transition_flow("case_detection", 1, "I", "R")
    model.run()
    # Expect that the results are consistent, nothing crazy happens.
    # These results were not independently calculated, so this is more of an "acceptance test".
    expected_outputs = np.array(
        [
            [900.0, 0.0, 0.0, 100.0, 0.0],
            [66.1, 203.8, 274.2, 307.2, 75.3],
            [2.9, 150.9, 220.5, 345.3, 69.4],
            [2.2, 127.3, 175.6, 297.0, 58.1],
            [1.8, 106.4, 145.6, 248.8, 48.5],
            [1.5, 88.8, 121.5, 207.8, 40.5],
        ]
    )
    assert_allclose(model.outputs, expected_outputs, atol=0.2, verbose=True)
示例#6
0
def build_model(params: dict) -> CompartmentalModel:
    time = params["time"]
    model = CompartmentalModel(
        times=[time["start"], time["end"]],
        compartments=COMPARTMENTS,
        infectious_compartments=INFECTIOUS_COMPS,
        timestep=time["step"],
    )

    # Add initial population
    init_pop = {
        Compartment.EARLY_LATENT: params["initial_early_latent_population"],
        Compartment.LATE_LATENT: params["initial_late_latent_population"],
        Compartment.INFECTIOUS: params["initial_infectious_population"],
        Compartment.DETECTED: params["initial_detected_population"],
        Compartment.ON_TREATMENT: params["initial_on_treatment_population"],
        Compartment.RECOVERED: 0,
    }
    sum_init_pop = sum(init_pop.values())
    init_pop[Compartment.
             SUSCEPTIBLE] = params["start_population_size"] - sum_init_pop
    model.set_initial_population(init_pop)

    # Add inter-compartmental flows
    params = _get_derived_params(params)
    # Entry flows
    model.add_crude_birth_flow(
        "birth",
        params["crude_birth_rate"],
        Compartment.SUSCEPTIBLE,
    )
    # Infection flows.
    model.add_infection_frequency_flow(
        "infection",
        params["contact_rate"],
        Compartment.SUSCEPTIBLE,
        Compartment.EARLY_LATENT,
    )
    model.add_infection_frequency_flow(
        "infection_from_latent",
        params["contact_rate_from_latent"],
        Compartment.LATE_LATENT,
        Compartment.EARLY_LATENT,
    )
    model.add_infection_frequency_flow(
        "infection_from_recovered",
        params["contact_rate_from_recovered"],
        Compartment.RECOVERED,
        Compartment.EARLY_LATENT,
    )

    # Transition flows.
    model.add_fractional_flow(
        "treatment_early",
        params["preventive_treatment_rate"],
        Compartment.EARLY_LATENT,
        Compartment.RECOVERED,
    )
    model.add_fractional_flow(
        "treatment_late",
        params["preventive_treatment_rate"],
        Compartment.LATE_LATENT,
        Compartment.RECOVERED,
    )
    model.add_fractional_flow(
        "stabilisation",
        params["stabilisation_rate"],
        Compartment.EARLY_LATENT,
        Compartment.LATE_LATENT,
    )
    model.add_fractional_flow(
        "early_activation",
        params["early_activation_rate"],
        Compartment.EARLY_LATENT,
        Compartment.INFECTIOUS,
    )
    model.add_fractional_flow(
        "late_activation",
        params["late_activation_rate"],
        Compartment.LATE_LATENT,
        Compartment.INFECTIOUS,
    )

    # Post-active-disease flows
    model.add_fractional_flow(
        "detection",
        params["detection_rate"],
        Compartment.INFECTIOUS,
        Compartment.DETECTED,
    )
    model.add_fractional_flow(
        "treatment_commencement",
        params["treatment_commencement_rate"],
        Compartment.DETECTED,
        Compartment.ON_TREATMENT,
    )
    model.add_fractional_flow(
        "missed_to_active",
        params["missed_to_active_rate"],
        Compartment.DETECTED,
        Compartment.INFECTIOUS,
    )
    model.add_fractional_flow(
        "self_recovery_infectious",
        params["self_recovery_rate"],
        Compartment.INFECTIOUS,
        Compartment.LATE_LATENT,
    )
    model.add_fractional_flow(
        "self_recovery_detected",
        params["self_recovery_rate"],
        Compartment.DETECTED,
        Compartment.LATE_LATENT,
    )
    model.add_fractional_flow(
        "treatment_recovery",
        params["treatment_recovery_rate"],
        Compartment.ON_TREATMENT,
        Compartment.RECOVERED,
    )
    model.add_fractional_flow(
        "treatment_default",
        params["treatment_default_rate"],
        Compartment.ON_TREATMENT,
        Compartment.INFECTIOUS,
    )
    model.add_fractional_flow(
        "failure_retreatment",
        params["failure_retreatment_rate"],
        Compartment.ON_TREATMENT,
        Compartment.DETECTED,
    )
    model.add_fractional_flow(
        "spontaneous_recovery",
        params["spontaneous_recovery_rate"],
        Compartment.ON_TREATMENT,
        Compartment.LATE_LATENT,
    )

    # Death flows
    # Universal death rate to be overriden by a multiply in age stratification.
    uni_death_flow_names = model.add_universal_death_flows("universal_death",
                                                           death_rate=1)
    model.add_death_flow(
        "infectious_death",
        params["infect_death_rate"],
        Compartment.INFECTIOUS,
    )
    model.add_death_flow(
        "detected_death",
        params["infect_death_rate"],
        Compartment.DETECTED,
    )
    model.add_death_flow(
        "treatment_death",
        params["treatment_death_rate"],
        Compartment.ON_TREATMENT,
    )

    # Apply age-stratification
    age_strat = _build_age_strat(params, uni_death_flow_names)
    model.stratify_with(age_strat)

    # Add vaccination stratification.
    vac_strat = _build_vac_strat(params)
    model.stratify_with(vac_strat)

    # Apply organ stratification
    organ_strat = _build_organ_strat(params)
    model.stratify_with(organ_strat)

    # Apply strain stratification
    strain_strat = _build_strain_strat(params)
    model.stratify_with(strain_strat)

    # Add amplification flow
    model.add_fractional_flow(
        name="amplification",
        fractional_rate=params["amplification_rate"],
        source=Compartment.ON_TREATMENT,
        dest=Compartment.ON_TREATMENT,
        source_strata={"strain": "ds"},
        dest_strata={"strain": "mdr"},
        expected_flow_count=9,
    )

    # Add cross-strain reinfection flows
    model.add_infection_frequency_flow(
        name="reinfection_ds_to_mdr",
        contact_rate=params["reinfection_rate"],
        source=Compartment.EARLY_LATENT,
        dest=Compartment.EARLY_LATENT,
        source_strata={"strain": "ds"},
        dest_strata={"strain": "mdr"},
        expected_flow_count=3,
    )
    model.add_infection_frequency_flow(
        name="reinfection_mdr_to_ds",
        contact_rate=params["reinfection_rate"],
        source=Compartment.EARLY_LATENT,
        dest=Compartment.EARLY_LATENT,
        source_strata={"strain": "mdr"},
        dest_strata={"strain": "ds"},
        expected_flow_count=3,
    )

    model.add_infection_frequency_flow(
        name="reinfection_late_ds_to_mdr",
        contact_rate=params["reinfection_rate"],
        source=Compartment.LATE_LATENT,
        dest=Compartment.EARLY_LATENT,
        source_strata={"strain": "ds"},
        dest_strata={"strain": "mdr"},
        expected_flow_count=3,
    )
    model.add_infection_frequency_flow(
        name="reinfection_late_mdr_to_ds",
        contact_rate=params["reinfection_rate"],
        source=Compartment.LATE_LATENT,
        dest=Compartment.EARLY_LATENT,
        source_strata={"strain": "mdr"},
        dest_strata={"strain": "ds"},
        expected_flow_count=3,
    )

    # Apply classification stratification
    class_strat = _build_class_strat(params)
    model.stratify_with(class_strat)

    # Apply retention stratification
    retention_strat = _build_retention_strat(params)
    model.stratify_with(retention_strat)

    # Register derived output functions, which are calculations based on the model's compartment values or flows.
    # These are calculated after the model is run.
    model.request_output_for_flow("notifications", flow_name="detection")
    model.request_output_for_flow("early_activation",
                                  flow_name="early_activation")
    model.request_output_for_flow("late_activation",
                                  flow_name="late_activation")
    model.request_output_for_flow("infectious_deaths",
                                  flow_name="infectious_death")
    model.request_output_for_flow("detected_deaths",
                                  flow_name="detected_death")
    model.request_output_for_flow("treatment_deaths",
                                  flow_name="treatment_death")
    model.request_output_for_flow("progression_early",
                                  flow_name="early_activation")
    model.request_output_for_flow("progression_late",
                                  flow_name="late_activation")
    model.request_aggregate_output("progression",
                                   ["progression_early", "progression_late"])
    model.request_output_for_compartments("population_size", COMPARTMENTS)
    model.request_aggregate_output(
        "_incidence",
        sources=["early_activation", "late_activation"],
        save_results=False)
    model.request_function_output("incidence",
                                  sources=["_incidence", "population_size"],
                                  func=lambda i, p: 1e5 * i / p)
    model.request_aggregate_output(
        "disease_deaths",
        sources=["infectious_deaths", "detected_deaths", "treatment_deaths"])
    cum_start_time = params["cumulative_output_start_time"]
    model.request_cumulative_output("cumulative_diseased",
                                    source="_incidence",
                                    start_time=cum_start_time)
    model.request_cumulative_output("cumulative_deaths",
                                    source="disease_deaths",
                                    start_time=cum_start_time)
    model.request_output_for_compartments("_count_infectious",
                                          INFECTIOUS_COMPS,
                                          save_results=False)
    model.request_function_output(
        "prevalence_infectious",
        sources=["_count_infectious", "population_size"],
        func=lambda c, p: 1e5 * c / p,
    )
    model.request_output_for_compartments(
        "_count_latent", [Compartment.EARLY_LATENT, Compartment.LATE_LATENT],
        save_results=False)
    model.request_function_output(
        "percentage_latent",
        sources=["_count_latent", "population_size"],
        func=lambda c, p: 100 * c / p,
    )
    return model