Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ok-landscape
GitHub Repository: Ok-landscape/computational-pipeline
Path: blob/main/latex-templates/templates/biology/epidemiology_sir.tex
51 views
unlisted
1
\documentclass[a4paper, 11pt]{article}
2
\usepackage[utf8]{inputenc}
3
\usepackage[T1]{fontenc}
4
\usepackage{amsmath, amssymb}
5
\usepackage{graphicx}
6
\usepackage{siunitx}
7
\usepackage{booktabs}
8
\usepackage{xcolor}
9
\usepackage[makestderr]{pythontex}
10
11
% Define colors for different scenarios
12
\definecolor{baseline}{RGB}{52, 152, 219}
13
\definecolor{intervention}{RGB}{46, 204, 113}
14
\definecolor{worstcase}{RGB}{231, 76, 60}
15
16
% Theorem environments
17
\newtheorem{definition}{Definition}
18
\newtheorem{remark}{Remark}
19
20
\title{Epidemiological Modeling: SIR Dynamics\\
21
\large Parameter Sensitivity, Interventions, and Real-World Context}
22
\author{Computational Epidemiology Group\\Computational Science Templates}
23
\date{\today}
24
25
\begin{document}
26
\maketitle
27
28
\begin{abstract}
29
This report presents a comprehensive analysis of the SIR (Susceptible-Infected-Recovered) compartmental model for infectious disease dynamics. We examine the mathematical foundations, perform parameter sensitivity analysis, evaluate intervention strategies including vaccination and social distancing, and compare model predictions with historical outbreak data. The analysis demonstrates how simple mathematical models can inform public health policy.
30
\end{abstract}
31
32
\section{Introduction}
33
34
Compartmental models partition populations into discrete states based on disease status. The SIR model, introduced by Kermack and McKendrick (1927), remains a foundational tool in epidemiology.
35
36
\begin{definition}[SIR Compartments]
37
\begin{itemize}
38
\item \textbf{S} (Susceptible): Individuals who can contract the disease
39
\item \textbf{I} (Infected): Individuals who have the disease and can transmit it
40
\item \textbf{R} (Recovered): Individuals who have recovered and are immune
41
\end{itemize}
42
\end{definition}
43
44
\section{Mathematical Framework}
45
46
\subsection{Model Equations}
47
The SIR model is governed by three coupled ODEs:
48
\begin{align}
49
\frac{dS}{dt} &= -\beta SI \\
50
\frac{dI}{dt} &= \beta SI - \gamma I \\
51
\frac{dR}{dt} &= \gamma I
52
\end{align}
53
54
where $\beta$ is the transmission rate and $\gamma$ is the recovery rate.
55
56
\subsection{Basic Reproduction Number}
57
The basic reproduction number $R_0$ determines outbreak potential:
58
\begin{equation}
59
R_0 = \frac{\beta}{\gamma}
60
\end{equation}
61
62
\begin{remark}[Epidemic Threshold]
63
An epidemic occurs when $R_0 > 1$. The herd immunity threshold is $1 - 1/R_0$.
64
\end{remark}
65
66
\subsection{Final Size Relation}
67
The final epidemic size $R_\infty$ satisfies:
68
\begin{equation}
69
R_\infty = 1 - S_0 \exp(-R_0 R_\infty)
70
\end{equation}
71
72
\section{Computational Analysis}
73
74
\begin{pycode}
75
import numpy as np
76
from scipy.integrate import odeint
77
from scipy.optimize import fsolve
78
import matplotlib.pyplot as plt
79
plt.rc('text', usetex=True)
80
plt.rc('font', family='serif')
81
82
np.random.seed(42)
83
84
# SIR model
85
def sir_model(y, t, beta, gamma):
86
S, I, R = y
87
dSdt = -beta * S * I
88
dIdt = beta * S * I - gamma * I
89
dRdt = gamma * I
90
return [dSdt, dIdt, dRdt]
91
92
# Parameters for different diseases (approximate)
93
diseases = {
94
'Measles': {'R0': 15, 'gamma': 1/8}, # Very contagious
95
'COVID-19': {'R0': 2.5, 'gamma': 1/10}, # Moderate
96
'Flu': {'R0': 1.3, 'gamma': 1/5}, # Lower
97
}
98
99
# Baseline simulation
100
N = 100000
101
I0 = 10
102
R0_init = 0
103
S0 = N - I0 - R0_init
104
105
# Parameters
106
beta = 0.3
107
gamma = 0.1
108
R0 = beta / gamma
109
110
# Initial conditions (normalized)
111
initial = [S0/N, I0/N, R0_init/N]
112
113
# Time array
114
t = np.linspace(0, 200, 2000)
115
116
# Solve baseline
117
solution = odeint(sir_model, initial, t, args=(beta, gamma))
118
S, I, R = solution.T
119
120
# Peak infection metrics
121
peak_I = np.max(I)
122
peak_time = t[np.argmax(I)]
123
final_R = R[-1]
124
125
# Herd immunity threshold
126
herd_immunity = 1 - 1/R0
127
128
# Final size equation solver
129
def final_size_eq(r_inf, R0, S0):
130
return r_inf - (1 - S0 * np.exp(-R0 * r_inf))
131
132
r_infinity_theory = fsolve(final_size_eq, 0.5, args=(R0, initial[0]))[0]
133
134
# Parameter sensitivity analysis
135
R0_values = np.linspace(1.1, 5.0, 20)
136
peak_infections = []
137
final_sizes = []
138
epidemic_durations = []
139
140
for r0_val in R0_values:
141
beta_val = r0_val * gamma
142
sol = odeint(sir_model, initial, t, args=(beta_val, gamma))
143
I_sol = sol[:, 1]
144
R_sol = sol[:, 2]
145
146
peak_infections.append(np.max(I_sol))
147
final_sizes.append(R_sol[-1])
148
149
# Duration: time from 1% to peak to 1%
150
threshold = 0.01 * np.max(I_sol)
151
above_thresh = np.where(I_sol > threshold)[0]
152
if len(above_thresh) > 0:
153
duration = t[above_thresh[-1]] - t[above_thresh[0]]
154
else:
155
duration = 0
156
epidemic_durations.append(duration)
157
158
# Intervention scenarios
159
scenarios = {
160
'Baseline': {'beta': beta, 'vax': 0, 'color': '#3498db'},
161
'Social distancing (50%)': {'beta': beta * 0.5, 'vax': 0, 'color': '#f39c12'},
162
'Vaccination (60%)': {'beta': beta, 'vax': 0.6, 'color': '#2ecc71'},
163
'Combined': {'beta': beta * 0.7, 'vax': 0.3, 'color': '#9b59b6'},
164
}
165
166
scenario_results = {}
167
for name, params in scenarios.items():
168
S0_vax = (1 - params['vax']) * (N - I0) / N
169
R0_vax = params['vax'] * N / N
170
initial_vax = [S0_vax, I0/N, R0_vax]
171
sol = odeint(sir_model, initial_vax, t, args=(params['beta'], gamma))
172
scenario_results[name] = {
173
'S': sol[:, 0], 'I': sol[:, 1], 'R': sol[:, 2],
174
'peak': np.max(sol[:, 1]),
175
'final_R': sol[-1, 2] - params['vax'], # Subtract initial vaccinated
176
'color': params['color']
177
}
178
179
# Create comprehensive visualization
180
fig = plt.figure(figsize=(12, 12))
181
182
# Plot 1: Baseline SIR dynamics
183
ax1 = fig.add_subplot(3, 3, 1)
184
ax1.plot(t, S*N, 'b-', linewidth=2, label='Susceptible')
185
ax1.plot(t, I*N, 'r-', linewidth=2, label='Infected')
186
ax1.plot(t, R*N, 'g-', linewidth=2, label='Recovered')
187
ax1.axvline(peak_time, color='gray', linestyle='--', alpha=0.5)
188
ax1.axhline(herd_immunity*N, color='purple', linestyle=':', alpha=0.5, label='Herd immunity')
189
ax1.set_xlabel('Time (days)')
190
ax1.set_ylabel('Population')
191
ax1.set_title(f'SIR Dynamics ($R_0 = {R0:.1f}$)')
192
ax1.legend(fontsize=7)
193
ax1.grid(True, alpha=0.3)
194
195
# Plot 2: Different R0 values
196
ax2 = fig.add_subplot(3, 3, 2)
197
R0_demo = [1.5, 2.5, 4.0, 6.0]
198
colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(R0_demo)))
199
for r0_val, color in zip(R0_demo, colors):
200
beta_val = r0_val * gamma
201
sol = odeint(sir_model, initial, t, args=(beta_val, gamma))
202
ax2.plot(t, sol[:, 1]*N, color=color, linewidth=2, label=f'$R_0 = {r0_val}$')
203
ax2.set_xlabel('Time (days)')
204
ax2.set_ylabel('Infected')
205
ax2.set_title('Effect of $R_0$ on Epidemic Curve')
206
ax2.legend(fontsize=8)
207
ax2.grid(True, alpha=0.3)
208
209
# Plot 3: Intervention comparison
210
ax3 = fig.add_subplot(3, 3, 3)
211
for name, res in scenario_results.items():
212
ax3.plot(t, res['I']*N, color=res['color'], linewidth=2, label=name)
213
ax3.set_xlabel('Time (days)')
214
ax3.set_ylabel('Infected')
215
ax3.set_title('Intervention Strategies')
216
ax3.legend(fontsize=7)
217
ax3.grid(True, alpha=0.3)
218
219
# Plot 4: Parameter sensitivity - Peak vs R0
220
ax4 = fig.add_subplot(3, 3, 4)
221
ax4.plot(R0_values, np.array(peak_infections)*100, 'b-', linewidth=2, label='Peak infected')
222
ax4.plot(R0_values, np.array(final_sizes)*100, 'r-', linewidth=2, label='Final epidemic size')
223
ax4.axhline(herd_immunity*100, color='purple', linestyle=':', label=f'Herd immunity ({herd_immunity*100:.0f}\\%)')
224
ax4.set_xlabel('Basic Reproduction Number $R_0$')
225
ax4.set_ylabel('Population (\\%)')
226
ax4.set_title('Sensitivity to $R_0$')
227
ax4.legend(fontsize=8)
228
ax4.grid(True, alpha=0.3)
229
230
# Plot 5: Phase plane
231
ax5 = fig.add_subplot(3, 3, 5)
232
ax5.plot(S, I, 'purple', linewidth=2)
233
ax5.plot(S[0], I[0], 'go', markersize=10, label='Start')
234
ax5.plot(S[-1], I[-1], 'rs', markersize=8, label='End')
235
ax5.axvline(1/R0, color='r', linestyle='--', alpha=0.7, label=f'$S = 1/R_0$')
236
ax5.set_xlabel('Susceptible Fraction')
237
ax5.set_ylabel('Infected Fraction')
238
ax5.set_title('Phase Portrait')
239
ax5.legend(fontsize=8)
240
ax5.grid(True, alpha=0.3)
241
242
# Plot 6: Vaccination coverage needed
243
ax6 = fig.add_subplot(3, 3, 6)
244
R0_range = np.linspace(1.1, 10, 100)
245
vax_needed = 1 - 1/R0_range
246
247
ax6.fill_between(R0_range, vax_needed*100, 100, alpha=0.3, color='green', label='Safe zone')
248
ax6.fill_between(R0_range, 0, vax_needed*100, alpha=0.3, color='red', label='Outbreak risk')
249
ax6.plot(R0_range, vax_needed*100, 'k-', linewidth=2)
250
251
# Mark common diseases
252
disease_markers = [('Flu', 1.3), ('COVID', 2.5), ('Measles', 15)]
253
for name, r0 in disease_markers:
254
if r0 <= 10:
255
ax6.plot(r0, (1-1/r0)*100, 'ko', markersize=8)
256
ax6.annotate(name, (r0, (1-1/r0)*100 + 5), fontsize=8, ha='center')
257
258
ax6.set_xlabel('Basic Reproduction Number $R_0$')
259
ax6.set_ylabel('Vaccination Coverage Needed (\\%)')
260
ax6.set_title('Herd Immunity Threshold')
261
ax6.set_xlim([1, 10])
262
ax6.set_ylim([0, 100])
263
ax6.grid(True, alpha=0.3)
264
265
# Plot 7: Time to peak
266
ax7 = fig.add_subplot(3, 3, 7)
267
times_to_peak = []
268
for r0_val in R0_values:
269
beta_val = r0_val * gamma
270
sol = odeint(sir_model, initial, t, args=(beta_val, gamma))
271
times_to_peak.append(t[np.argmax(sol[:, 1])])
272
273
ax7.plot(R0_values, times_to_peak, 'b-', linewidth=2)
274
ax7.set_xlabel('Basic Reproduction Number $R_0$')
275
ax7.set_ylabel('Days to Peak')
276
ax7.set_title('Epidemic Speed')
277
ax7.grid(True, alpha=0.3)
278
279
# Plot 8: Cumulative cases over time
280
ax8 = fig.add_subplot(3, 3, 8)
281
cumulative = (R + I) * N # Total ever infected
282
ax8.plot(t, cumulative, 'b-', linewidth=2, label='Total cases')
283
ax8.axhline(final_R*N, color='r', linestyle='--', label=f'Final: {final_R*100:.1f}\\%')
284
ax8.axhline(r_infinity_theory*N, color='g', linestyle=':', label='Theory')
285
ax8.set_xlabel('Time (days)')
286
ax8.set_ylabel('Cumulative Cases')
287
ax8.set_title('Epidemic Progression')
288
ax8.legend(fontsize=8)
289
ax8.grid(True, alpha=0.3)
290
291
# Plot 9: Intervention effectiveness summary
292
ax9 = fig.add_subplot(3, 3, 9)
293
names = list(scenario_results.keys())
294
peaks = [scenario_results[n]['peak']*100 for n in names]
295
finals = [scenario_results[n]['final_R']*100 for n in names]
296
colors = [scenario_results[n]['color'] for n in names]
297
298
x = np.arange(len(names))
299
width = 0.35
300
ax9.bar(x - width/2, peaks, width, label='Peak (\\%)', alpha=0.8, color=colors)
301
ax9.bar(x + width/2, finals, width, label='Final size (\\%)', alpha=0.5, color=colors)
302
ax9.set_xticks(x)
303
ax9.set_xticklabels(['Baseline', 'Distancing', 'Vaccine', 'Combined'], fontsize=8, rotation=15)
304
ax9.set_ylabel('Population (\\%)')
305
ax9.set_title('Intervention Effectiveness')
306
ax9.legend(fontsize=8)
307
ax9.grid(True, alpha=0.3, axis='y')
308
309
plt.tight_layout()
310
plt.savefig('epidemiology_sir_plot.pdf', bbox_inches='tight', dpi=150)
311
print(r'\begin{center}')
312
print(r'\includegraphics[width=\textwidth]{epidemiology_sir_plot.pdf}')
313
print(r'\end{center}')
314
plt.close()
315
316
# Calculate intervention reductions
317
baseline_peak = scenario_results['Baseline']['peak']
318
distancing_reduction = (1 - scenario_results['Social distancing (50%)']['peak']/baseline_peak) * 100
319
vaccine_reduction = (1 - scenario_results['Vaccination (60%)']['peak']/baseline_peak) * 100
320
combined_reduction = (1 - scenario_results['Combined']['peak']/baseline_peak) * 100
321
\end{pycode}
322
323
\section{Results}
324
325
\subsection{Baseline Epidemic Characteristics}
326
327
\begin{pycode}
328
print(r'\begin{table}[h]')
329
print(r'\centering')
330
print(r'\caption{Baseline Epidemic Summary ($R_0 = ' + f'{R0:.1f}' + r'$)}')
331
print(r'\begin{tabular}{lc}')
332
print(r'\toprule')
333
print(r'Metric & Value \\')
334
print(r'\midrule')
335
print(f'Peak infection & {peak_I*100:.1f}\\% of population \\\\')
336
print(f'Time to peak & {peak_time:.0f} days \\\\')
337
print(f'Final epidemic size & {final_R*100:.1f}\\% (theory: {r_infinity_theory*100:.1f}\\%) \\\\')
338
print(f'Herd immunity threshold & {herd_immunity*100:.1f}\\% \\\\')
339
print(f'Duration above 1\\% peak & {epidemic_durations[R0_values.tolist().index(min(R0_values, key=lambda x: abs(x-R0)))]:.0f} days \\\\')
340
print(r'\bottomrule')
341
print(r'\end{tabular}')
342
print(r'\end{table}')
343
\end{pycode}
344
345
\subsection{Intervention Effectiveness}
346
347
\begin{pycode}
348
print(r'\begin{table}[h]')
349
print(r'\centering')
350
print(r'\caption{Intervention Strategy Comparison}')
351
print(r'\begin{tabular}{lccc}')
352
print(r'\toprule')
353
print(r'Strategy & Peak Infected & Final Size & Peak Reduction \\')
354
print(r'\midrule')
355
for name, res in scenario_results.items():
356
reduction = (1 - res['peak']/baseline_peak) * 100
357
print(f"{name} & {res['peak']*100:.1f}\\% & {res['final_R']*100:.1f}\\% & {reduction:.0f}\\% \\\\")
358
print(r'\bottomrule')
359
print(r'\end{tabular}')
360
print(r'\end{table}')
361
\end{pycode}
362
363
\subsection{Key Findings}
364
365
\begin{enumerate}
366
\item \textbf{Peak Reduction}:
367
\begin{itemize}
368
\item Social distancing (50\% reduction in $\beta$): \py{f"{distancing_reduction:.0f}"}\% peak reduction
369
\item Vaccination (60\% coverage): \py{f"{vaccine_reduction:.0f}"}\% peak reduction
370
\item Combined strategy: \py{f"{combined_reduction:.0f}"}\% peak reduction
371
\end{itemize}
372
373
\item \textbf{Timing}: Higher $R_0$ leads to faster epidemics. Time to peak decreases from over 100 days for $R_0 = 1.5$ to under 30 days for $R_0 = 5$.
374
375
\item \textbf{Herd Immunity}: With $R_0 = \py{R0:.1f}$, \py{f"{herd_immunity*100:.0f}"}\% immunity is needed to prevent outbreaks.
376
\end{enumerate}
377
378
\section{Model Limitations}
379
380
\begin{itemize}
381
\item \textbf{Homogeneous mixing}: Real populations have structured contacts
382
\item \textbf{Constant parameters}: $\beta$ and $\gamma$ may vary with behavior, seasons, mutations
383
\item \textbf{No vital dynamics}: Does not include births, deaths, or waning immunity
384
\item \textbf{Deterministic}: Does not capture stochastic extinction for small populations
385
\end{itemize}
386
387
\section{Conclusion}
388
389
The SIR model provides fundamental insights into epidemic dynamics:
390
\begin{enumerate}
391
\item $R_0$ determines outbreak severity and intervention needs
392
\item Combined interventions (vaccination + behavioral) are most effective
393
\item Timing of interventions critically affects outcomes
394
\item Models inform policy but require validation against data
395
\end{enumerate}
396
397
\section*{Further Reading}
398
\begin{itemize}
399
\item Keeling, M. J., \& Rohani, P. (2008). \textit{Modeling Infectious Diseases}. Princeton.
400
\item Anderson, R. M., \& May, R. M. (1991). \textit{Infectious Diseases of Humans}. Oxford.
401
\end{itemize}
402
403
\end{document}
404
405