Skip to content

Commit

Permalink
disabled calculation of expected new cases (for now)
Browse files Browse the repository at this point in the history
  • Loading branch information
lrdossan committed Sep 9, 2024
1 parent 8e71828 commit bf2cbf7
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 74 deletions.
34 changes: 17 additions & 17 deletions caimira/apps/calculator/report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,13 @@ def calculate_report_data(form: VirusFormData, model: models.ExposureModel, exec
prob = np.array(model.infection_probability())
prob_dist_count, prob_dist_bins = np.histogram(prob/100, bins=100, density=True)

# Probabilistic exposure
if form.exposure_option == "p_probabilistic_exposure" and form.occupancy_format == "static":
prob_probabilistic_exposure = np.array(model.total_probability_rule()).mean()
else: prob_probabilistic_exposure = None
# Probabilistic exposure and expected new cases (only for static occupancy)
prob_probabilistic_exposure = None
expected_new_cases = None
if form.occupancy_format == "static":
if form.exposure_option == "p_probabilistic_exposure":
prob_probabilistic_exposure = np.array(model.total_probability_rule()).mean()
expected_new_cases = np.array(model.expected_new_cases()).mean()

exposed_presence_intervals = [list(interval) for interval in model.exposed.presence_interval().boundaries()]

Expand Down Expand Up @@ -200,7 +203,7 @@ def calculate_report_data(form: VirusFormData, model: models.ExposureModel, exec
"prob_hist_count": list(prob_dist_count),
"prob_hist_bins": list(prob_dist_bins),
"prob_probabilistic_exposure": prob_probabilistic_exposure,
"expected_new_cases": np.array(model.expected_new_cases()).mean(),
"expected_new_cases": expected_new_cases,
"uncertainties_plot_src": uncertainties_plot_src,
"CO2_concentrations": CO2_concentrations,
"conditional_probability_data": conditional_probability_data,
Expand Down Expand Up @@ -466,23 +469,19 @@ def manufacture_alternative_scenarios(form: VirusFormData) -> typing.Dict[str, m
def scenario_statistics(
mc_model: mc.ExposureModel,
sample_times: typing.List[float],
static_occupancy: bool,
compute_prob_exposure: bool,
):
model = mc_model.build_model(size=mc_model.data_registry.monte_carlo['sample_size'])
if (compute_prob_exposure):
# It means we have data to calculate the total_probability_rule
prob_probabilistic_exposure = model.total_probability_rule()
else:
prob_probabilistic_exposure = -1

return {
'probability_of_infection': np.mean(model.infection_probability()),
'expected_new_cases': np.mean(model.expected_new_cases()),
'expected_new_cases': np.mean(model.expected_new_cases()) if static_occupancy else None,
'concentrations': [
np.mean(model.concentration(time))
for time in sample_times
],
'prob_probabilistic_exposure': prob_probabilistic_exposure,
'prob_probabilistic_exposure': model.total_probability_rule() if compute_prob_exposure else None,
}


Expand All @@ -503,17 +502,18 @@ def comparison_report(
}
else:
statistics = {}

if (form.short_range_option == "short_range_yes" and form.exposure_option == "p_probabilistic_exposure" and form.occupancy_format == "static"):
compute_prob_exposure = True
else:
compute_prob_exposure = False
static_occupancy = False
compute_prob_exposure = False
if form.occupancy_format == "static": static_occupancy = True
if (form.short_range_option == "short_range_yes" and form.exposure_option == "p_probabilistic_exposure" and static_occupancy): compute_prob_exposure = True

with executor_factory() as executor:
results = executor.map(
scenario_statistics,
scenarios.values(),
[sample_times] * len(scenarios),
[static_occupancy] * len(scenarios),
[compute_prob_exposure] * len(scenarios),
timeout=60,
)
Expand Down
20 changes: 14 additions & 6 deletions caimira/apps/templates/base/calculator.report.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
</div>
{% endblock long_range_warning_animation %}
</div>
<h6><b>Expected new cases:</b> {{ long_range_expected_cases | float_format }}</h6>
{% if form.occupancy_format == "static" %}<h6><b>Expected new cases:</b> {{ long_range_expected_cases | float_format }}</h6>{% endif %}
</div>
<br>
{% if form.short_range_option == "short_range_yes" %}
Expand All @@ -126,19 +126,27 @@
</div>
{% endblock warning_animation %}
</div>
<h6><b>Expected new cases:</b> {{ expected_new_cases | float_format }}</h6>
{% if form.occupancy_format == "static" %}
<h6><b>Expected new cases:</b> {{ expected_new_cases | float_format }}</h6>
{% endif %}
</div>
{% endif %}
<div class="d-flex">
{% block report_summary %}
<div class="flex-row align-self-center">
<div class="align-self-center alert alert-dark mb-0" role="alert">
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{ long_range_prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>*.
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{ long_range_prob_inf | non_zero_percentage }}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>
{% endif %}*.
</div>
{% if form.short_range_option == "short_range_yes" %}
<br>
<div class="align-self-center alert alert-dark mb-0" role="alert">
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>.
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>
{% endif %}.
</div>
{% endif %}
{% block probabilistic_exposure_probability %}
Expand Down Expand Up @@ -302,15 +310,15 @@
<tr>
<th>Scenario</th>
<th>P(I)</th>
<th>Expected new cases</th>
{% if form.occupancy_format == "static" %}<th>Expected new cases</th>{% endif %}
</tr>
</thead>
<tbody>
{% for scenario_name, scenario_stats in alternative_scenarios.stats.items() %}
<tr>
<td> {{ scenario_name }}</td>
<td> {{ scenario_stats.probability_of_infection | non_zero_percentage }}</td>
<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>
{% if form.occupancy_format == "static" %}<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>{% endif %}
</tr>
{% endfor %}
</tbody>
Expand Down
20 changes: 13 additions & 7 deletions caimira/apps/templates/cern/calculator.report.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% set long_range_prob_inf = prob_inf %}
{% endif %}

{% if ((long_range_prob_inf > red_prob_lim) or (expected_new_cases >= 1)) %}
{% if ((long_range_prob_inf > red_prob_lim) or (form.occupancy_format == "static" and expected_new_cases >= 1)) %}
{% set long_range_scale_warning = 'red' %}
{% set long_range_warning_color= 'bg-danger' %}
{% elif (orange_prob_lim <= long_range_prob_inf <= red_prob_lim) %}
Expand All @@ -22,7 +22,7 @@
{% set long_range_warning_color = 'bg-success' %}
{% endif %}

{% if ((prob_inf > red_prob_lim) or (expected_new_cases >= 1)) %} {% set scale_warning = 'red' %}
{% if ((prob_inf > red_prob_lim) or (form.occupancy_format == "static" and expected_new_cases >= 1)) %} {% set scale_warning = 'red' %}
{% elif (orange_prob_lim <= prob_inf <= red_prob_lim) %} {% set scale_warning = 'orange' %}
{% else %} {% set scale_warning = 'green' %}
{% endif %}
Expand Down Expand Up @@ -70,7 +70,10 @@
<div class="alert alert-success mb-0" role="alert">
<strong>Acceptable:</strong>
{% endif %}
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{long_range_prob_inf | non_zero_percentage}}</b> and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>*.
Taking into account the uncertainties tied to the model variables, in this scenario and assuming all occupants are exposed equally (i.e. without short-range interactions), the <b>probability of one exposed occupant getting infected is {{long_range_prob_inf | non_zero_percentage}}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases is {{ long_range_expected_cases | float_format }}</b>
{% endif %}*.
</div>
{% if form.short_range_option == "short_range_yes" %}
<br>
Expand All @@ -84,7 +87,10 @@
<div class="alert alert-success mb-0" role="alert">
<strong>Acceptable:</strong>
{% endif %}
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b> and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>.
In this scenario, the <b>probability the occupant(s) exposed to short-range interactions get infected can go as high as {{ prob_inf | non_zero_percentage }}</b>
{% if form.occupancy_format == "static" %}
and the <b>expected number of new cases increases to {{ expected_new_cases | float_format }}</b>
{% endif %}.
</div>
{% endif %}

Expand Down Expand Up @@ -137,12 +143,12 @@
<tr>
<th>Scenario</th>
<th>P(i)</th>
<th>Expected new cases</th>
{% if form.occupancy_format == "static" %}<th>Expected new cases</th>{% endif %}
</tr>
</thead>
<tbody>
{% for scenario_name, scenario_stats in alternative_scenarios.stats.items() %}
{%if (( scenario_stats.probability_of_infection > red_prob_lim) or (scenario_stats.expected_new_cases >= 1)) %}
{%if (( scenario_stats.probability_of_infection > red_prob_lim) or (form.occupancy_format == "static" and scenario_stats.expected_new_cases >= 1)) %}
<tr class="alert-danger">
{% elif (orange_prob_lim <= scenario_stats.probability_of_infection <= red_prob_lim) %}
<tr class="alert-warning">
Expand All @@ -151,7 +157,7 @@
{% endif%}
<td> {{ scenario_name }}</td>
<td> {{ scenario_stats.probability_of_infection | non_zero_percentage }}</td>
<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>
{% if form.occupancy_format == "static" %}<td style="text-align:right">{{ scenario_stats.expected_new_cases | float_format }}</td>{% endif %}
</tr>
{% endfor %}
</tbody>
Expand Down
57 changes: 24 additions & 33 deletions caimira/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1873,50 +1873,41 @@ def expected_new_cases(self) -> _VectorisedFloat:
2) Short- and long-range exposure: take the infection_probability of long-range multiplied by the occupants exposed to long-range only,
plus the infection_probability of short- and long-range multiplied by the occupants exposed to short-range only.
In the case dynamic occupancy is defined, the maximum number of exposed occupants during the course of the simulation will be considered.
Currently disabled when dynamic occupancy is defined for the exposed population.
"""
exposed_occ: int = max(self.exposed.number.values) if isinstance(self.exposed.number, IntPiecewiseConstant) else self.exposed.number

if (isinstance(self.concentration_model.infected.number, IntPiecewiseConstant) or
isinstance(self.exposed.number, IntPiecewiseConstant)):
raise NotImplementedError("Cannot compute expected new cases "
"with dynamic occupancy")

if self.short_range != ():
new_cases_long_range = nested_replace(self, {'short_range': [],}).infection_probability() * (exposed_occ - self.exposed_to_short_range)
new_cases_long_range = nested_replace(self, {'short_range': [],}).infection_probability() * (self.exposed.number - self.exposed_to_short_range)
return (new_cases_long_range + (self.infection_probability() * self.exposed_to_short_range)) / 100

return self.infection_probability() * exposed_occ / 100
return self.infection_probability() * self.exposed.number / 100

def reproduction_number(self) -> _VectorisedFloat:
"""
The reproduction number can be thought of as the expected number of
cases directly generated by one infected case in a population.
It handles the cases when dynamic occupancy for the infected population is defined.
Currently disabled when dynamic occupancy is defined for both the infected and exposed population.
"""

infected_number = self.concentration_model.infected.number
if isinstance(infected_number, IntPiecewiseConstant):
# Handle case when infected number is dynamic
max_occ = max(infected_number.values)
if max_occ == 1:
return self.expected_new_cases()
else:
# Adjust to treat dynamic occupancy, limiting infected to 1 when present
inf_occ_values = [1 if occ > 0 else occ for occ in infected_number.values]
single_exposure_model = nested_replace(
self, {
'concentration_model.infected.number.values': inf_occ_values
}
)
return single_exposure_model.expected_new_cases()

elif isinstance(infected_number, int):
# Handle case when infected number is a single integer
if infected_number == 1:
return self.expected_new_cases()

# Create an equivalent exposure model but with precisely
# one infected case.
single_exposure_model = nested_replace(
self, {
'concentration_model.infected.number': 1}
)
if (isinstance(self.concentration_model.infected.number, IntPiecewiseConstant) or
isinstance(self.exposed.number, IntPiecewiseConstant)):
raise NotImplementedError("Cannot compute reproduction number "
"with dynamic occupancy")

if self.concentration_model.infected.number == 1:
return self.expected_new_cases()

# Create an equivalent exposure model but with precisely
# one infected case.
single_exposure_model = nested_replace(
self, {
'concentration_model.infected.number': 1}
)

return single_exposure_model.expected_new_cases()
return single_exposure_model.expected_new_cases()
2 changes: 1 addition & 1 deletion caimira/tests/apps/calculator/test_report_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,6 @@ def test_static_vs_dynamic_occupancy_from_form(baseline_form_data, data_registry
list(dynamic_occupancy_model.exposed.number.transition_times))

np.testing.assert_almost_equal(static_occupancy_report_data['prob_inf'], dynamic_occupancy_report_data['prob_inf'], 1)
np.testing.assert_almost_equal(static_occupancy_report_data['expected_new_cases'], dynamic_occupancy_report_data['expected_new_cases'], 1)
assert dynamic_occupancy_report_data['expected_new_cases'] == None
assert dynamic_occupancy_report_data['prob_probabilistic_exposure'] == None

28 changes: 18 additions & 10 deletions caimira/tests/models/test_dynamic_population.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,24 +232,32 @@ def test_dynamic_total_probability_rule(


def test_dynamic_expected_new_cases(
full_exposure_model: models.ExposureModel,
dynamic_infected_single_exposure_model: models.ExposureModel,
dynamic_exposed_single_exposure_model: models.ExposureModel,
dynamic_population_exposure_model: models.ExposureModel):

base_expected_new_cases = full_exposure_model.expected_new_cases()
npt.assert_almost_equal(base_expected_new_cases, dynamic_infected_single_exposure_model.expected_new_cases())
npt.assert_almost_equal(base_expected_new_cases, dynamic_exposed_single_exposure_model.expected_new_cases())
npt.assert_almost_equal(base_expected_new_cases, dynamic_population_exposure_model.expected_new_cases())
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_exposed_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()


def test_dynamic_reproduction_number(
full_exposure_model: models.ExposureModel,
dynamic_infected_single_exposure_model: models.ExposureModel,
dynamic_exposed_single_exposure_model: models.ExposureModel,
dynamic_population_exposure_model: models.ExposureModel):

base_reproduction_number = full_exposure_model.reproduction_number()
npt.assert_almost_equal(base_reproduction_number, dynamic_infected_single_exposure_model.reproduction_number())
npt.assert_almost_equal(base_reproduction_number, dynamic_exposed_single_exposure_model.reproduction_number())
npt.assert_almost_equal(base_reproduction_number, dynamic_population_exposure_model.reproduction_number())
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_exposed_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()

0 comments on commit bf2cbf7

Please sign in to comment.