Skip to content

Commit 16b53dd

Browse files
committed
Exercise 3
1 parent 020de12 commit 16b53dd

1 file changed

Lines changed: 224 additions & 0 deletions

File tree

exercises/exercise3.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import marimo
2+
3+
__generated_with = "0.20.4"
4+
app = marimo.App(width="medium")
5+
6+
7+
@app.cell
8+
def _():
9+
import marimo as mo
10+
import matplotlib.pyplot as plt
11+
import pandas as pd
12+
import numpy as np
13+
14+
return mo, np, plt
15+
16+
17+
@app.cell(hide_code=True)
18+
def _(mo):
19+
mo.md(r"""
20+
# Simulating an ODE-based model with a Damping from Python
21+
## Introduction
22+
23+
In the previous tutorial, we created, initialized and simulated MEmilio's ODE-based SECIR-type model with one (age) group. In this tutorial, we will show how to incorporate non-pharmaceutical interventions (NPIs) through the use of `Dampings` in the ODE-based SECIR-type model.
24+
25+
The example requires to have the memilio-simulation package installed which can be accessed under https://github.com/SciCompMod/memilio/tree/main/pycode/memilio-simulation.
26+
""")
27+
return
28+
29+
30+
@app.cell(hide_code=True)
31+
def _(mo):
32+
mo.md(r"""
33+
## Model Setup
34+
35+
We first import the needed functions from the memilio-simulation package:
36+
""")
37+
return
38+
39+
40+
@app.cell
41+
def _():
42+
import memilio.simulation.osecir as osecir
43+
from memilio.simulation import AgeGroup, Damping, LogLevel, set_log_level
44+
set_log_level(LogLevel.Off)
45+
return AgeGroup, Damping, osecir
46+
47+
48+
@app.cell
49+
def _(mo):
50+
mo.md(r"""
51+
Next, we create and initialize a SECIR-type model with one age group. For a detailed description on that, see Tutorial 1.
52+
""")
53+
return
54+
55+
56+
@app.cell
57+
def _(AgeGroup, np, osecir):
58+
# Initialize total population, simulation start time, simulation time frame and initial step size
59+
total_population = 100000
60+
t0 = 0
61+
tmax = 100
62+
dt = 0.1
63+
contact_frequency = 10
64+
65+
# Create model with one age group
66+
model = osecir.Model(1)
67+
68+
# Set infection state stay times (in days)
69+
group = AgeGroup(0)
70+
model.parameters.TimeExposed[group] = 3.2
71+
model.parameters.TimeInfectedNoSymptoms[group] = 2.
72+
model.parameters.TimeInfectedSymptoms[group] = 6.
73+
model.parameters.TimeInfectedSevere[group] = 12.
74+
model.parameters.TimeInfectedCritical[group] = 8.
75+
76+
# Set infection state transition probabilities
77+
model.parameters.RelativeTransmissionNoSymptoms[group] = 0.67
78+
model.parameters.TransmissionProbabilityOnContact[group] = 0.1
79+
model.parameters.RecoveredPerInfectedNoSymptoms[group] = 0.2
80+
model.parameters.RiskOfInfectionFromSymptomatic[group] = 0.25
81+
model.parameters.SeverePerInfectedSymptoms[group] = 0.2
82+
model.parameters.CriticalPerSevere[group] = 0.25
83+
model.parameters.DeathsPerCritical[group] = 0.3
84+
85+
# Set contact frequency
86+
model.parameters.ContactPatterns.cont_freq_mat[0].baseline = np.ones((1, 1)) * 5
87+
return dt, group, model, t0, tmax, total_population
88+
89+
90+
@app.cell
91+
def _(mo):
92+
mo.md(r"""
93+
### Exercise
94+
Please set the contact frequency to 10.
95+
""")
96+
return
97+
98+
99+
@app.cell
100+
def _():
101+
# Insert code here
102+
return
103+
104+
105+
@app.cell
106+
def _(mo):
107+
mo.md(r"""
108+
After the model initialization, we add a contact reduction (`Damping`) that represents an NPI like e.g. mask wearing or social distancing. Dampings are a factor applied to the contact frequency and can be added to the model at fixed simulation time points before simulating. They have a *Level* and a *Type*. A damping with a given level and type replaces the previously active one with the same level and type, while all currently active dampings of one level and different types are summed up. If two dampings have different levels (independent of the type) they are combined multiplicatively. In the following we apply a `Damping` of 0.9 after 10 days and another damping of 0.6 after 20 days which means that the contacts are reduced by 10% and 40%, respectively. In general, it is also possible to increase the contact rate by using negative Damping values. To always retain a minimum level of contacts, a minimum contact frequency can be set that is never deceeded. In our example we set this minimum contact rate to 0.
109+
""")
110+
return
111+
112+
113+
@app.cell
114+
def _(Damping, model, np):
115+
# Set minimum contact frequency
116+
model.parameters.ContactPatterns.cont_freq_mat[0].minimum = np.zeros((1, 1))
117+
118+
# Add contact reduction by 10% after 10 days
119+
model.parameters.ContactPatterns.cont_freq_mat.add_damping(Damping(coeffs=np.ones((1, 1)) * 0.9, t=10.0, level=0, type=0))
120+
# Add contact reduction by 40% after 20 days
121+
model.parameters.ContactPatterns.cont_freq_mat.add_damping(Damping(coeffs=np.ones((1, 1)) * 0.6, t=20.0, level=0, type=0))
122+
return
123+
124+
125+
@app.cell
126+
def _(mo):
127+
mo.md(r"""
128+
### Exercise
129+
Please create a damping that replaces the 10% reduction after 10 days by a 20% reduction. Additionally, add a damping after 40 days that increases the contact rate by 50%.
130+
""")
131+
return
132+
133+
134+
@app.cell
135+
def _():
136+
# Insert code here
137+
return
138+
139+
140+
@app.cell
141+
def _(mo):
142+
mo.md(r"""
143+
Again, we start with 0.5% of the population initially in `Exposed` and 0.5% initially in `InfectedNoSymptoms` while the remaining 99% is `Susceptible`.
144+
""")
145+
return
146+
147+
148+
@app.cell
149+
def _(group, model, osecir, total_population):
150+
# 1% of the population is initially infected, 0.5% Exposed and 0.5% in the pre- or asymptomatic state
151+
model.populations[group, osecir.InfectionState.Exposed] = 0.005 * total_population
152+
model.populations[group, osecir.InfectionState.InfectedNoSymptoms] = 0.005 * total_population
153+
# The rest of the population is Susceptible
154+
model.populations.set_difference_from_total(
155+
(group, osecir.InfectionState.Susceptible), total_population)
156+
return
157+
158+
159+
@app.cell
160+
def _(mo):
161+
mo.md(r"""
162+
## Model simulation
163+
164+
We simulate the model from `t0` to `tmax` with initial step size `dt` and subsequently print the time series result:
165+
""")
166+
return
167+
168+
169+
@app.cell
170+
def _(dt, model, osecir, t0, tmax):
171+
# Simulate model from t0 to tmax with initial step size dt
172+
result = osecir.simulate(t0, tmax, dt, model)
173+
result.print_table()
174+
return (result,)
175+
176+
177+
@app.cell
178+
def _(mo):
179+
mo.md(r"""
180+
Subsequently, we interpolate the simulated time series to full days using the linear interpolation function:
181+
""")
182+
return
183+
184+
185+
@app.cell
186+
def _(osecir, result):
187+
# Interpolate result to full days
188+
interpolated_result = osecir.interpolate_simulation_result(result)
189+
interpolated_result.print_table()
190+
return
191+
192+
193+
@app.cell
194+
def _(mo):
195+
mo.md(r"""
196+
## Visualization of model output
197+
198+
We plot the trajectories of all infected compartments i.e. `Exposed`, `InfectedNoSymptoms`, `InfectedSymptoms`, `InfectedSevere` and `InfectedCritical`.
199+
""")
200+
return
201+
202+
203+
@app.cell
204+
def _(osecir, plt, result):
205+
# Convert time series to array
206+
result_array = result.as_ndarray()
207+
208+
# Plot the number of infected with symptoms over time
209+
fig, ax = plt.subplots()
210+
time = result_array[0, :]
211+
ax.plot(time, result_array[1 + int(osecir.InfectionState.Exposed), :], label='Exposed')
212+
ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedNoSymptoms), :], label='Infected No Symptoms')
213+
ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedSymptoms), :], label='Infected Symptoms')
214+
ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedSevere), :], label='Infected Severe')
215+
ax.plot(time, result_array[1 + int(osecir.InfectionState.InfectedCritical), :], label='Infected Critical')
216+
ax.set_xlabel('Time [days]')
217+
ax.set_ylabel('Individuals [#]')
218+
ax.legend()
219+
plt.show()
220+
return
221+
222+
223+
if __name__ == "__main__":
224+
app.run()

0 commit comments

Comments
 (0)