12
Simulating many independent trajectories
One stochastic trajectory shows one possible epidemic. Monte Carlo simulation repeats the model independently to reveal the range, frequency and empirical distribution of possible outcomes.
Why one trajectory is insufficient
Suppose two simulations use the same biological parameters and initial state. Different random numbers can produce different:
- early infection or recovery sequences;
- peak infectious counts and peak times;
- outbreak durations; and
- final epidemic sizes.
A Monte Carlo experiment approximates stochastic-model outcomes by running many independent simulated trajectories and summarising the resulting sample.
The model does not change between runs. Only the random realisation changes.
Two levels of repetition
Inner loop: one trajectory
Repeatedly apply infection, recovery or no change through time until extinction or the observation horizon.
Outer loop: many trajectories
Call the complete-trajectory function again for simulation 0, simulation 1, and so on.
The superscript \((m)\) identifies the simulation. It is not a mathematical power.
The Monte Carlo workflow
Independence and random seeds
Create the random generator once:
rng = np.random.default_rng(seed)
for simulation in range(number_of_simulations):
result = simulate_one_trajectory(..., rng)
The generator supplies a continuing sequence of pseudo-random numbers. Different runs receive different portions of that sequence and are treated as independent simulations.
Do not recreate np.random.default_rng(seed) with the same seed inside the outer loop. That would restart the same sequence for every simulation and could produce identical trajectories rather than independent replicates.
Using the seed once makes the entire Monte Carlo experiment reproducible. Changing the seed creates a different reproducible sample.
Two kinds of stored results
Path arrays
Store \(S\), \(I\) and \(R\) at every common time. These arrays support trajectory graphs, pointwise means and percentile bands.
One-row summaries
Store one peak, peak time, final size and completion indicator per simulation. These values support empirical outcome tables and histograms.
Keeping these two data structures separate prevents confusion between “one value per time” and “one value per simulation.”
Why trajectories must be aligned
Different outbreaks become extinct at different times. To calculate quantities across simulations at time \(t_n\), every path array must use the same time grid and length.
After extinction in a closed SIR model, the state is absorbing. Therefore it is biologically correct to extend the remaining array with the final state:
This is called absorbing-state padding. It aligns paths without inventing new epidemic events.
Empirical distributions
If the completed final sizes are \(Z_1,Z_2,\ldots,Z_M\), their empirical distribution is the observed pattern across the simulations.
For a possible value \(z\), its empirical relative frequency is:
A histogram groups nearby values into bins and displays how frequently different ranges occurred. It approximates the outcome distribution; it is not one epidemic trajectory.
Mean and percentile bands across paths
The region between the 10th and 90th percentiles describes the central 80% of the simulated counts at each time.
This percentile band describes variability among trajectories. It is not a confidence interval for an unknown parameter, and it does not represent one path that simultaneously follows the lower or upper boundary.
Scenario
Simulate \(M=400\) independent SIR trajectories in a population of 100:
The common observation horizon is 160 days. The program reports whether any trajectory is still infectious at that time, because its completed final size would not yet be known.
Interactive Python laboratory
Click Run code. The calculation may take longer than a single-path lesson because it simulates millions of individual DTMC steps.
Output
Run the code to see the result.
Understand the new Monte Carlo code
| Code | Meaning |
|---|---|
np.empty((M, K + 1), dtype=int) | Reserves a rectangular array with one trajectory per row and one time point per column. |
all_I_paths[simulation] | Selects the row assigned to one simulation. |
summary_rows.append({...}) | Adds one dictionary of outcome values for each completed call. |
extinction_step is not None | Checks whether an extinction step was recorded. |
S_path[step + 2:] = S | Fills every later position with the absorbing final susceptible count. |
all_I_paths.mean(axis=0) | Averages down the simulation rows separately for every time column. |
np.percentile(..., axis=0) | Calculates a pointwise percentile across simulations at each time. |
summaries[summaries["completed"]] | Filters the table so completed-final-size summaries exclude unfinished trajectories. |
Why use axis=0?
The aligned array has shape:
Rows distinguish simulations and columns distinguish time. Therefore axis=0 collapses the simulation direction and leaves one result for every time column.
Using axis=1 would instead average each trajectory across its time points, answering a different question.
Interpret the figures correctly
- The grey lines are 15 individual trajectories selected only to keep the figure readable.
- The blue line is the pointwise mean across all 400 trajectories.
- The blue band is the pointwise central 80% empirical range.
- The histograms use one summary outcome from each completed trajectory.
A histogram bar height is a count of simulations in an outcome interval. It is not the number of infectious people at a particular time.
Computational cost
If \(M\) simulations each allow \(K\) time steps, the maximum number of one-step calculations grows approximately like:
Doubling both the number of simulations and the number of time steps can make the work roughly four times larger. Early stopping reduces work when extinction occurs before the horizon.
Try these experiments
- Change
seed = 21. The empirical summaries change, but the biological model remains the same. - Reduce
number_of_simulationsto 25. Observe the less stable histograms and percentile band. - Increase it to 1,000 if your browser can run the larger experiment comfortably.
- Set
initial_I = 1. Look for stronger separation between small and large final sizes. - Shorten
max_days. Examine the incomplete-trajectory count and explain why those rows are excluded from completed final-size summaries. - Move the generator creation inside the outer loop with the same seed. Observe why repeated identical random sequences are a mistake, then restore the correct code.
Biological interpretation
The collection of paths shows that identical starting conditions and parameters do not imply identical epidemic histories. Some introductions disappear early, while others produce larger peaks and final sizes.
The empirical distributions quantify this outcome variation. They do not yet define which outcomes count as extinction or a major outbreak. Those event definitions and their estimated probabilities are introduced carefully in Lesson 13.
What this lesson has—and has not—done
You can now:
- distinguish the inner trajectory loop from the outer Monte Carlo loop;
- generate independent, reproducible trajectories correctly;
- align paths using absorbing-state padding;
- store path arrays and one-row outcome summaries;
- calculate pointwise means and empirical percentile bands; and
- construct empirical peak and final-size distributions.
Not yet: this page has not chosen a major-outbreak threshold or estimated event probabilities and Monte Carlo standard errors. Lesson 13 defines those outcomes before estimating them.