07
Programming the SIR model with Euler’s method
We now combine the SIR equations, initial conditions, time arrays and forward Euler method to simulate a complete epidemic. The program repeatedly recalculates all three rates because the biological flows change with the epidemic state.
Biological scenario
A closed population contains 1,000 people. Initially, 990 are susceptible, 10 are infectious and none have recovered. We use \(\beta=0.3\) per day and \(\gamma=0.1\) per day.
We want to estimate the epidemic trajectory for 160 days and determine the infectious peak, its timing and the final compartment sizes.
Ideas brought together
The SIR equations
\[\frac{dS}{dt}=-\beta\frac{SI}{N},\]
\[\frac{dI}{dt}=\beta\frac{SI}{N}-\gamma I,\]
\[\frac{dR}{dt}=\gamma I.\]
At each time point, the current values of \(S\), \(I\) and \(R\) determine the infection and recovery flows. Therefore, the rates must be recalculated inside the loop.
The three Euler updates
Essential rule: use one common current state
Calculate all three derivatives from \(S_n,I_n,R_n\) before storing any next value. Do not calculate \(I_{n+1}\) using the newly updated \(S_{n+1}\). That would no longer be the standard forward Euler method.
Step 1: Write a reusable rate function
def sir_rates(S, I, R, beta, gamma):
N = S + I + R
infection_flow = beta * S * I / N
recovery_flow = gamma * I
dS_dt = -infection_flow
dI_dt = infection_flow - recovery_flow
dR_dt = recovery_flow
return dS_dt, dI_dt, dR_dt
The function receives one epidemic state and returns its three instantaneous rates. It does not update the compartments.
Step 2: Prepare time and storage
number_of_steps = int(round(end_time / dt))
time = np.linspace(0, end_time, number_of_steps + 1)
S = np.zeros(number_of_steps + 1)
I = np.zeros(number_of_steps + 1)
R = np.zeros(number_of_steps + 1)
S[0], I[0], R[0] = S0, I0, R0
np.zeros() creates fixed-size numerical arrays. The initial conditions are placed at index 0. The later zeros are storage positions and will be replaced by the loop.
Step 3: Apply Euler repeatedly
for n in range(number_of_steps):
dS_dt, dI_dt, dR_dt = sir_rates(
S[n], I[n], R[n], beta, gamma
)
S[n + 1] = S[n] + dS_dt * dt
I[n + 1] = I[n] + dI_dt * dt
R[n + 1] = R[n] + dR_dt * dt
The function call uses only values at index n. After all derivatives are returned, the three next values are stored at index n + 1.
Step 4: Extract epidemic outcomes
peak_index = np.argmax(I)
peak_infectious = I[peak_index]
peak_time = time[peak_index]
- np.argmax(I) returns the index at which \(I\) is largest.
- The same index retrieves the peak value from I.
- It also retrieves the corresponding peak time from time.
Run the complete SIR simulation
Run the program to see the numerical summary, selected-day table and compartment graph. You can change \(\beta\), \(\gamma\) or dt.
Output
Run the code to see the result.
How the outcome is calculated
| Code | Technical purpose | Biological purpose |
|---|---|---|
R[-1] | Uses index −1 to select the final array entry. | Gives the recovered population at the end of the simulation. |
np.argmax(I) | Returns the position of the maximum value. | Locates the epidemic peak. |
np.abs(population - N) | Calculates the size of each deviation from \(N\). | Measures population-conservation error. |
np.max(...) | Selects the largest deviation. | Reports the worst conservation error across all times. |
plt.plot(time, I) | Plots paired horizontal and vertical array values. | Shows how the infectious population changes through time. |
plt.legend() | Displays the labels supplied to plotted lines. | Identifies the three compartments and peak marker. |
Expected epidemic pattern
Initially, infection occurs faster than recovery, so the infectious population grows. As susceptible people are depleted, the infection flow weakens. The infectious peak occurs when the infection and recovery flows are approximately equal. After that point, recovery exceeds new infection and \(I\) declines.
The deterministic result is one smooth population-level trajectory. It represents average model behaviour and does not describe random individual infection and recovery events.
Important numerical and model conditions
- The initial compartments and parameters must be non-negative.
- The total population must be positive.
- \(\Delta t>0\) and should be sufficiently small.
- All rates in one Euler step must be calculated from the same current state.
- A negative compartment signals an invalid numerical result, often caused by a time step that is too large.
- Population conservation should be monitored across the entire trajectory.
- The final simulation time must be long enough to capture the outcome of interest.
Try changing transmission
Run the model with beta = 0.15 and then with beta = 0.4, keeping all other values unchanged.
- Predict which model will have the larger infectious peak.
- Compare the peak times.
- Compare the final recovered populations.
- Explain the differences using the infection flow \(\beta SI/N\).