Compartmental Models

Dr. Arun Mitra Peddireddy
JHAPSMCON 2026
4th Annual Jharkhand State Conference of IAPSM

A Grain of Rice!

Total Grains: 18,446,744,073,709,551,615

Estimated Mass: ~461,168,602,000 metric tons.

The Magic of Exponential Growth!

Compartmental Models

Types of Models in IDM

Axis Choices
Mechanistic vs statistical Mechanism (SIR) vs pattern fitting (GLM, ARIMA)
Deterministic vs stochastic Same input → same output vs same input → distribution
Population vs individual Compartments (counts) vs agents (each person)
Homogeneous vs structured One pool vs age / space / risk groups
Time Continuous (ODE) vs discrete (difference eqs, branching)

Why Compartments?

  • Few parameters. Easy to interpret.
  • Closed-form intuition for R0, herd immunity, final size.
  • Same skeleton extends to vaccination, age structure, spatial spread.
  • The dashboard at 15:30 sits on top of compartmental thinking.

Same Shape, Different Disease

A 1918 flu epicurve and a COVID-19 wave look strikingly similar.

Compartmental dynamics are universal. Three equations recreate this shape.

SIR: Compartments

Mass-action: \(\lambda = \beta \times \frac{I}{N}\) assumes everyone mixes equally.

SIR: Equations

\[ \begin{aligned} \frac{dS}{dt} &= -\beta \times S \times \frac{I}{N} \\ \frac{dI}{dt} &= +\beta \times S \times \frac{I}{N} - (\gamma \times I) \\ \frac{dR}{dt} &= +\gamma \times I \end{aligned} \]

R0 = β / γ.

β = 0.3 / day,

D = 1/γ = 7 days

R0 = 2.1.

SIR — Code

library(deSolve)

sir_eq <- function(t, y, params) {
  with(as.list(c(y, params)), {
    dS <- -beta * S * I / N
    dI <-  beta * S * I / N - gamma * I
    dR <-  gamma * I
    list(c(dS, dI, dR))
  })
}

out <- ode(
  y     = c(S = 1e6 - 10, I = 10, R = 0),
  times = 0:200,
  func  = sir_eq,
  parms = c(beta = 0.3, gamma = 0.1, N = 1e6)
) |> 
  unclass() |> 
  as.data.frame() |> 
  as_tibble()
1
The model is a function — takes time, state, parameters; returns rates of change.
2
with(as.list(c(y, params))) lets us write S, I, beta, gamma by name instead of y["S"], params["beta"].
3
The three derivatives — read out loud as one English sentence each (“susceptibles fall at rate β·S·I/N…”).
4
deSolve expects a list whose first element is the vector of derivatives.
5
ode() integrates from t = 0 to t = 200 in steps of 1.

SIR — Watch It Run

SIR — Trajectory

  • S drains as people get infected.
  • I rises, peaks, then falls.
  • R fills in — those who’ve been through it.

Extending SIR to SEIR

E = exposed but not yet infectious. Adds a latent period before transmission begins.

From SIR…

\[ \begin{aligned} \frac{dS}{dt} &= -\beta \cdot S \cdot I / N \\ \frac{dI}{dt} &= +\beta \cdot S \cdot I / N - \gamma \cdot I \\ \frac{dR}{dt} &= +\gamma \cdot I \end{aligned} \]

…to SEIR

\[ \begin{aligned} \frac{dS}{dt} &= -\beta \cdot S \cdot I / N \\ \frac{dE}{dt} &= +\beta \cdot S \cdot I / N - \sigma \cdot E \\ \frac{dI}{dt} &= +\sigma \cdot E - \gamma \cdot I \\ \frac{dR}{dt} &= +\gamma \cdot I \end{aligned} \]

One new equation. σ = 1 / mean latent period. Latency delays the peak — final size barely changes.

SEIR — Code

seir_eq <- function(t, y, params) {
  with(as.list(c(y, params)), {
    dS <- -beta * S * I / N
    dE <-  beta * S * I / N - sigma * E
    dI <-  sigma * E - gamma * I
    dR <-  gamma * I
    list(c(dS, dE, dI, dR))
  })
}
1
The new line: people who got infected enter E first, not I.
2
dI changes — the source is now σ · E, not β · S · I / N directly.
3
The state vector grows by one — dE is added, in the same order as the initial conditions.

SIR vs SEIR — Same R0, Different Timing

Common Extensions

Variant New mechanism Use when…
SEIRS R → S (waning immunity) Endemic respiratory pathogens, repeated waves
SEIRD I → D (deaths) Mortality matters (Ebola, COVID severity)
SVEIR S → V (vaccinated) Vaccination programmes (Session 3)
MSEIR M = maternal immunity Childhood diseases (measles in infants)
Age-structured Compartments per age band Heterogeneous mixing (Session 3)
Metapopulation Compartments per location Spatial spread between districts/states

Same skeleton — add boxes, add arrows, add equations.

SEIRS — Waning Immunity

seirs_eq <- function(t, y, params) {
  with(as.list(c(y, params)), {
    dS <- -beta * S * I / N + omega * R
    dE <-  beta * S * I / N - sigma * E
    dI <-  sigma * E - gamma * I
    dR <-  gamma * I - omega * R
    list(c(dS, dE, dI, dR))
  })
}
# omega = 1 / mean duration of immunity
1
Recovered people return to S at rate ω — that’s the only new term in dS.
2
The mirror term in dR keeps the bookkeeping consistent (people leaving R are added to S).

With ω > 0 the system can settle into endemic oscillations — recurrent waves. The engine behind seasonal flu and COVID re-infection patterns.

SEIRD — Tracking Mortality

The flow out of I splits — fraction (1 − μ) recovers to R, fraction μ goes to D (cumulative deaths). μ = case fatality ratio.

seird_eq <- function(t, y, params) {
  with(as.list(c(y, params)), {
    dS <- -beta * S * I / N
    dE <-  beta * S * I / N - sigma * E
    dI <-  sigma * E - gamma * I
    dR <-  (1 - mu) * gamma * I
    dD <-  mu * gamma * I
    list(c(dS, dE, dI, dR, dD))
  })
}
# mu = case fatality ratio
1
The flow out of I splits — a fraction (1 − μ) recovers.
2
The remaining fraction μ goes to D (cumulative deaths).

\(D\) = cumulative deaths.

Stochastic Reality

Same R0 = 1.5. Same parameters. Different futures.

Real outbreaks are noisy, especially when case counts are small. With I0 = 5, about 11/100 chains fizzle out and never become an outbreak — even though the deterministic model would always predict a wave.

Group Work

Group Disease R0 D (days)
1 Measles-like 15 8
2 Flu-like 1.5 4
3 Ebola-like 2 10
4 COVID-19 like 3 7

For your group:

  1. Run the SIR script.
  2. Before plotting, try to guess the peak time and final size.
  3. Compare prediction to plot.


Bonus:

  • Extend to SEIR (σ = 1/5). What changed? What didn’t?

Group Work — Results

Final size depends on R0 alone — Kermack–McKendrick. Bigger R0 → more people infected. Peak timing depends on β and γ separately — Group 3 (longest D) peaks latest, even though its R0 is moderate.