Repository navigation
add damping tutorial & exercise #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
charlie0614
wants to merge
1
commit into
main
Choose a base branch
from
add_damping_levels_tutorial
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,226 @@ | ||
| import marimo | ||
|
|
||
| __generated_with = "0.24.0" | ||
| app = marimo.App(width="medium") | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(): | ||
| import marimo as mo | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
|
|
||
| return mo, np, plt | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| # Combining Multiple Interventions | ||
| ## Introduction | ||
|
|
||
| In the previous tutorial, we applied non-pharmaceutical interventions by reducing the contacts in a population via a multiplicative factor. For more advanced simulations and studies of interventions, we can model several interventions in place at the same time. In this tutorial we want to show how we combine multiple interventions. Every `Damping` has a **Level** and a **Type**: | ||
|
|
||
| - Dampings with the same **Level** but different **Type** are **summed**. This fits measures that each affect a different, non-overlapping part of the population, e.g., a school closure and an office closure, since neither affects the contacts the other one does. | ||
| - Dampings on different **Levels** are combined multiplicatively, i.e., two dampings `a` and `b` on different levels combine to `1 - (1-a)(1-b)`. This fits measures of a fundamentally different kind acting on the *same* population, e.g., mask-wearing (which reduces the risk of whatever contacts happen) and homeschooling (which reduces how many contacts happen in the first place). Since mask-wearing's effect only applies to the contacts homeschooling leaves behind, the two effects compound rather than simply add. | ||
| - A new damping with the same **Level and Type** as an existing one **replaces** it from its start time onward instead of adding to it, e.g., strengthening or relaxing a single measure over time. | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| ## Model Setup | ||
|
|
||
| We use the same SECIR-type model with one age group as for the previous tutorial. For a detailed description on that, see Tutorial 1. | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(): | ||
| import memilio.simulation.osecir as osecir | ||
| from memilio.simulation import AgeGroup, Damping, LogLevel, set_log_level | ||
| set_log_level(LogLevel.Off) | ||
| return AgeGroup, Damping, osecir | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(AgeGroup, np, osecir): | ||
| # Initialize total population, simulation start time, simulation time frame and initial step size | ||
| total_population = 100000 | ||
| t0 = 0 | ||
| tmax = 100 | ||
| dt = 0.1 | ||
| contact_frequency = 10 | ||
|
|
||
| # Create model with one age group | ||
| model = osecir.Model(1) | ||
|
|
||
| # Set infection state stay times (in days) | ||
| group = AgeGroup(0) | ||
| model.parameters.TimeExposed[group] = 3.2 | ||
| model.parameters.TimeInfectedNoSymptoms[group] = 2. | ||
| model.parameters.TimeInfectedSymptoms[group] = 6. | ||
| model.parameters.TimeInfectedSevere[group] = 12. | ||
| model.parameters.TimeInfectedCritical[group] = 8. | ||
|
|
||
| # Set infection state transition probabilities | ||
| model.parameters.RelativeTransmissionNoSymptoms[group] = 0.67 | ||
| model.parameters.TransmissionProbabilityOnContact[group] = 0.1 | ||
| model.parameters.RecoveredPerInfectedNoSymptoms[group] = 0.2 | ||
| model.parameters.RiskOfInfectionFromSymptomatic[group] = 0.25 | ||
| model.parameters.SeverePerInfectedSymptoms[group] = 0.2 | ||
| model.parameters.CriticalPerSevere[group] = 0.25 | ||
| model.parameters.DeathsPerCritical[group] = 0.3 | ||
|
|
||
| # Set contact frequency | ||
| model.parameters.ContactPatterns.cont_freq_mat[0].baseline = np.ones((1, 1)) * contact_frequency | ||
|
|
||
| # 1% of the population is initially infected, 0.5% Exposed and 0.5% in the pre- or asymptomatic state | ||
| model.populations[group, osecir.InfectionState.Exposed] = 0.005 * total_population | ||
| model.populations[group, osecir.InfectionState.InfectedNoSymptoms] = 0.005 * total_population | ||
| # The rest of the population is Susceptible | ||
| model.populations.set_difference_from_total( | ||
| (group, osecir.InfectionState.Susceptible), total_population) | ||
| return dt, model, t0, tmax | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| ## Same Level, Different Types: Additive | ||
|
|
||
| Suppose two measures start on day 10, each affecting a different, non-overlapping part of the population: a partial school closure reduces contacts by 20%, and an office closure reduces contacts by a further 40%. We give them the same `level=0` but different `type`, and MEmilio sums their values: `0.2 + 0.4 = 0.6`. | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(Damping, model, np): | ||
| model.parameters.ContactPatterns.cont_freq_mat.add_damping( | ||
| Damping(coeffs=np.ones((1, 1)) * 0.2, t=10.0, level=0, type=0)) # partial school closure | ||
| model.parameters.ContactPatterns.cont_freq_mat.add_damping( | ||
| Damping(coeffs=np.ones((1, 1)) * 0.4, t=10.0, level=0, type=1)) # office closure | ||
| return | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| ## A Different Level: Multiplicative | ||
|
|
||
| On day 25, a mask mandate takes effect, reducing the risk of whatever contacts still happen by 30%, regardless of how many contacts the school/office closures already removed. Since it's a fundamentally different kind of measure acting on the same population, we place it on a new `level=1`. It combines multiplicatively with the level-0 total: `1 - (1 - 0.6)(1 - 0.3) = 0.72` | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(Damping, model, np): | ||
| model.parameters.ContactPatterns.cont_freq_mat.add_damping( | ||
| Damping(coeffs=np.ones((1, 1)) * 0.3, t=25.0, level=1, type=0)) # mask mandate | ||
| return | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| ## Same Level and Type: Replaces | ||
|
|
||
| At day 40, the partial school closure is escalated to full homeschooling, now reducing contacts by 40% instead of 20%. Because this damping shares its `level=0, type=0` with the day-10 school closure, it **replaces** it rather than adding to it. From day 40 onward, level 0 is the sum of this new value and the still-active office-closure damping: `0.4 + 0.4 = 0.8`, which then combines multiplicatively with the mask mandate on level 1: `1 - (1 - 0.8)(1 - 0.3) = 0.86`. | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(Damping, model, np): | ||
| # TODO: Add a damping representing full homeschooling: a 40% reduction, starting at t=40.0, | ||
| # using the SAME level and type as the day-10 school-closure damping above, so that it | ||
| # replaces that damping instead of adding a third contribution. | ||
| return | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| ## Verifying the Combined Effect | ||
|
|
||
| We sample the effective contact rate at a few time points and compare against the values | ||
| computed by hand above. | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(model): | ||
| baseline = model.parameters.ContactPatterns.cont_freq_mat[0].baseline[0, 0] | ||
| sample_times = [0, 15, 20, 30, 39, 45] | ||
| for t_sample in sample_times: | ||
| effective = model.parameters.ContactPatterns.cont_freq_mat.get_matrix_at(t_sample)[0, 0] | ||
| reduction = 1 - effective / baseline | ||
| print(f"t = {t_sample:>3}: effective contacts = {effective:.2f}, " | ||
| f"combined reduction = {reduction:.2f}") | ||
| return | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| Expected values: | ||
|
|
||
| - `t = 0`: no damping active, reduction = 0. | ||
| - `t = 15`: only the day-10 school/office closures are active, reduction = 0.6 (`0.2 + 0.4`). | ||
| - `t = 20`: unchanged, still 0.6. | ||
| - `t = 30`: the mask mandate is active too, reduction = 0.72 (`1 - (1-0.6)(1-0.3)`). | ||
| - `t = 39`: unchanged, still 0.72. | ||
| - `t = 45`: full homeschooling has replaced the partial school closure, reduction = 0.86 | ||
| (`1 - (1 - (0.4+0.4))(1-0.3)`). | ||
|
|
||
| ## Model Simulation | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(dt, model, osecir, t0, tmax): | ||
| result = osecir.simulate(t0, tmax, dt, model) | ||
| print(result.print_table(return_string=True)) | ||
| return (result,) | ||
|
|
||
|
|
||
| @app.cell(hide_code=True) | ||
| def _(mo): | ||
| mo.md(r""" | ||
| ## Visualization of Model Output | ||
| """) | ||
| return | ||
|
|
||
|
|
||
| @app.cell | ||
| def _(osecir, plt, result): | ||
| result_array = result.as_ndarray() | ||
|
|
||
| fig, ax = plt.subplots() | ||
| time = result_array[0, :] | ||
| ax.plot(time, result_array[1 + int(osecir.InfectionState.Exposed), :], label='Exposed') | ||
| ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedNoSymptoms), :], label='Infected No Symptoms') | ||
| ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedSymptoms), :], label='Infected Symptoms') | ||
| ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedSevere), :], label='Infected Severe') | ||
| ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedCritical), :], label='Infected Critical') | ||
|
|
||
| for t_damping, label in [(10, 'school + office closure'), (25, 'mask mandate'), | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it renders for me ( nice graphic btw) but the annotations are slightly hard to read because they are on the lines of the states... I dont know how to fix it and maybe not necessary but maybe think abouztit |
||
| (40, 'full homeschooling')]: | ||
| ax.axvline(t_damping, color='gray', linestyle='--', linewidth=0.8) | ||
| ax.text(t_damping, ax.get_ylim()[1] * 0.95, label, rotation=90, | ||
| verticalalignment='top', fontsize=8, color='gray') | ||
|
|
||
| ax.set_xlabel('Time [days]') | ||
| ax.set_ylabel('Individuals [#]') | ||
| ax.legend() | ||
| plt.show() | ||
| return | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| app.run() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
short comment of that the exercisee should rerun?