Solving the SIR model from scratch
Three coupled equations explain why an outbreak peaks and then burns out. Below they are derived, integrated and put on sliders.
The model
Split a population into the susceptible , the infectious and the recovered . Infections happen when susceptible and infectious people meet, at a rate per day, and the infectious recover at a rate . Working in shares of the population (so ) gives
The second equation already holds the main result. grows only while , that is while with the basic reproduction number . Every infection uses up susceptibles, so falls until the condition fails and the curve turns over.
Integrating it
There is no closed-form solution for , so we integrate numerically. SciPy’s solve_ivp does it in a few
lines. The figure above uses a hand-written fourth-order Runge–Kutta step instead, which is the same idea in
about fifteen lines of JavaScript.
import numpy as npfrom scipy.integrate import solve_ivp
def sir(t, y, beta, gamma): s, i, r = y # shares of the population return [-beta * s * i, beta * s * i - gamma * i, gamma * i]
beta, gamma = 0.32, 0.10 # per dayy0 = [0.999, 0.001, 0.0]t = np.linspace(0, 160, 641)
sol = solve_ivp(sir, (0, 160), y0, args=(beta, gamma), t_eval=t)s, i, r = sol.yprint(f"R0 = {beta / gamma:.2f}, peak on day {t[i.argmax()]:.0f}")How big does it get?
Divide the first equation by the third and integrate. With almost everyone susceptible at the start, the share that is still susceptible at the end, , solves
For that leaves about 5% untouched, so roughly 95% are infected at some point. Drag up until drops below 1 and the outbreak never takes off.