← Past problems · 2014 set

2014 · Problem B — The Next Plague?

Compartmental ODE Epidemiology Resource allocation Risk communication

Read the official problem page →

The prompt, restated

A remote village of about 300 people has roughly half its inhabitants reporting similar symptoms — fever, fatigue, and a rash, plus a handful of severe cases. The team is asked to play the role of a public-health analyst dispatched to the scene: (1) Propose a disease model that captures the observed dynamics and classify the outbreak's severity (mild, moderate, severe, pandemic-prone). (2) Decide whether the outbreak is contained inside the village or is likely to spread to neighbouring villages. (3) With a fixed budget of medical staff, vaccines, anti-virals, and isolation beds, allocate resources to minimize total morbidity and mortality. (4) Respond to staged information drops — a second information packet reveals more cases in a nearby town, a third reveals genomic data suggesting a respiratory pathogen — and update both the model and the recommendation. (5) Write a one-page non-technical news brief the local health authority could release without causing panic.

The judges explicitly reward teams who keep their model in step with the information release: a paper that classifies the disease as "mild flu" on day 1 and never revisits the call after the day-3 genomic data shows up will be marked down heavily.

Key modeling idea

Treat the village as a closed SIR (or SEIR) compartmental system with a small population $N \approx 300$, then ask whether the basic reproduction number $R_0$ implied by the observed attack rate is above or below 1 under realistic intervention scenarios. Severity is a function of $R_0$, the case fatality rate (CFR), and the doubling time; containment is a network question (does the village mix with neighbours?). Resource allocation becomes an optimization over interventions — isolate, treat, vaccinate — each with its own cost, efficacy, and supply cap.

Suggested approach

  • Step 1 — Fit a baseline SEIR. Use a four-compartment model $S \to E \to I \to R$ with $\beta$ (contact rate), $\sigma$ (1/incubation period), and $\gamma$ (1/infectious period). Calibrate $\beta$ from the observed attack rate (~150/300 in a few days suggests $R_0 \approx 3\text{–}5$ [illustrative]). See technique 6 for the ODE setup.
  • Step 2 — Classify severity. Use a small decision table on $(R_0,\,\text{CFR},\,\text{doubling time})$: mild (<1.5, <0.1%, >14 d), moderate (1.5–2.5, 0.1–1%, 7–14 d), severe (2.5–5, 1–5%, 3–7 d), pandemic-prone (>5, >5%, <3 d). Anchor the bands to WHO pandemic-phase guidance.
  • Step 3 — Test containment. Add a coupled second compartmental model for a neighbouring village, with a small inter-village mixing rate $\epsilon$ representing travel. Sweep $\epsilon$; a containment failure is when the second village's $I_2(t)$ peak exceeds a threshold within 30 days.
  • Step 4 — Allocate resources by linear program. Variables: vaccine doses to villages, antivirals to symptomatic cases, isolation beds for severe cases. Objective: minimize total deaths $= \sum_i \text{CFR}_i \cdot \text{Infected}_i$. Constraints: supply caps, staff-hours, isolation-bed count. Solve with `scipy.optimize.linprog` (technique 11).
  • Step 5 — Roll forward with each info drop. Maintain the model as a living artifact: on each new information release, refit $\beta$ and CFR with the latest data, recompute the resource allocation, and report what changed. Judges reward this discipline more than they reward a complex model.

Data sources to consider

SourceWhat you get
WHO Disease Outbreak News archivesReal attack-rate / CFR templates for SARS, MERS, Ebola, COVID-19
CDC EPI-X / MMWR (Morbidity & Mortality Weekly)Calibrated incubation and infectious-period distributions
Anderson & May, Infectious Diseases of Humans (1991)Canonical SEIR derivations and $R_0$ estimation methods
Ferguson et al., Imperial COVID-19 Report 9Worked example of intervention modelling and resource scaling
WHO & Gavi vaccine supply dataRealistic dose / cold-chain / staffing constraints for the LP

Common pitfalls

  • Picking SIR without justifying. A measles-like rash with the observed attack rate is closer to SEIR with a 7–14 day incubation. Defend the compartment choice against at least one alternative (SIRS, SEIRD).
  • Treating $R_0$ as the disease. $R_0$ depends on the social network as much as on the pathogen. Distinguish the basic reproduction number from the effective $R_t$ under interventions.
  • Allocating before optimizing. "Vaccinate everyone" is not a model. Write the LP, even if you solve it by hand for $n=3$ villages.
  • Updating the model but not the recommendation. When the day-3 genomic drop indicates respiratory transmission, the allocation should shift toward masks and isolation, not just toward antivirals. Show the recommendation change.
  • Panicking the news brief. The non-technical summary should communicate uncertainty without alarmism — a single chart of $I(t)$ with a 90% band, plus three plain bullets, is usually enough.

Python sketch

SEIR baseline with a sweep over a single intervention parameter (contact reduction from isolation), plus a tiny LP for vaccine allocation across two villages.

import numpy as np
from scipy.integrate import odeint
from scipy.optimize import linprog

# --- SEIR model -------------------------------------------------
def seir(y, t, beta, sigma, gamma, N):
    S, E, I, R = y
    dS = -beta * S * I / N
    dE =  beta * S * I / N - sigma * E
    dI =  sigma * E - gamma * I
    dR =  gamma * I
    return [dS, dE, dI, dR]

N, I0, E0 = 300, 5, 0
beta0, sigma, gamma = 0.9, 1/5.2, 1/7.0          # ~ R0 = 6.3
t = np.linspace(0, 60, 600)

# baseline
sol = odeint(seir, [N - I0 - E0, E0, I0, 0], t,
             args=(beta0, sigma, gamma, N))
peak_baseline = sol[:, 2].max()

# isolation: cut contacts by fraction phi
phi = 0.5
sol_iso = odeint(seir, [N - I0 - E0, E0, I0, 0], t,
                 args=(beta0 * (1 - phi), sigma, gamma, N))
peak_iso = sol_iso[:, 2].max()
print(f"peak I: baseline={peak_baseline:.0f}, isolation={peak_iso:.0f}")  # [illustrative]

# --- Vaccine allocation LP --------------------------------------
# minimize -(efficacy_A * x_A + efficacy_B * x_B)   (max deaths averted)
# s.t. x_A + x_B <= 200 doses, x_A <= 300, x_B <= 500
c = [-0.85, -0.70]                                # negate for max
A_ub = [[1, 1]]; b_ub = [200]
bounds = [(0, 300), (0, 500)]
res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs")
print(f"vaccines: village A = {res.x[0]:.0f}, village B = {res.x[1]:.0f}")

Sensitivity & validation checklist

  • Vary $R_0$ from 1.5 to 6 — the severity-classification bucket should flip exactly where your decision table says it should.
  • Sweep inter-village mixing $\epsilon$ from 0 to 0.05; identify the threshold above which containment fails.
  • Vaccine efficacy: at 50% efficacy the LP should prefer the larger village; at 90% efficacy it should prefer the higher-incidence village.
  • Compare your peak-infection curve to the analytic SIR peak $1 - (1 + \ln R_0)/R_0$ — should agree to within a few percent for the SIR limit $\sigma \to \infty$.
  • Stress the staged-information rollout: feed the day-3 update and confirm the model refits and the recommendation changes accordingly.

Related pages