Scenario Analysis: Interventions and Ensembles

Two related tools help answer “what if” questions:

  • epimodels.interventions — time-bounded parameter changes (lockdowns, vaccination rollouts, behaviour change) with side-by-side scenario comparison;

  • epimodels.ensembles — many simulations under parameter uncertainty, summarized with quantile bands.

Intervention scenarios

An Intervention modifies one parameter over a time window, either multiplicatively (factor) or absolutely (new_value). A Scenario bundles a model with parameters, initial conditions and interventions:

from epimodels.continuous import SIR
from epimodels.interventions import Intervention, Scenario, ScenarioComparison

model = SIR()

baseline = Scenario(
    "baseline", model,
    params={"beta": 2.0, "gamma": 0.5},
    initial_conditions=[999, 1, 0],
    trange=[0, 100], totpop=1000,
)

lockdown = Scenario(
    "lockdown", model,
    params={"beta": 2.0, "gamma": 0.5},
    initial_conditions=[999, 1, 0],
    trange=[0, 100], totpop=1000,
    interventions=[
        # 60% contact reduction between days 10 and 40
        Intervention("beta", start=10, end=40, factor=0.4),
    ],
)

cmp = ScenarioComparison(baseline, [lockdown]).run()

cmp.peak("I")          # {'baseline': ..., 'lockdown': ...}
cmp.final_size("R")    # attack rate per scenario
cmp.plot("I")          # trajectories of all scenarios on one axes

Interventions are evaluated inside the ODE right-hand side, so the parameter changes smoothly follow the schedule at each integration step. Multiple interventions can be combined per scenario, and new_value overrides a parameter absolutely instead of scaling it:

# Vaccination: reduce beta permanently from day 30 on, and
# boost gamma to a fixed value during the same period
vaccination = Scenario(
    "vaccination", model,
    params={"beta": 2.0, "gamma": 0.5},
    initial_conditions=[999, 1, 0],
    trange=[0, 100], totpop=1000,
    interventions=[
        Intervention("beta", start=30, factor=0.5),   # until the end
        Intervention("gamma", start=30, end=60, new_value=0.8),
    ],
)

Uncertainty ensembles

simulate_ensemble() runs n_sims realizations, each drawing parameters from a sampler you provide, and collects the trajectories on a common time grid:

import numpy as np
from epimodels.continuous import SIR
from epimodels.ensembles import simulate_ensemble

model = SIR()
rng = np.random.default_rng(0)

ensemble = simulate_ensemble(
    model,
    n_sims=200,
    param_sampler=lambda: {
        "beta": rng.uniform(1.5, 2.5),
        "gamma": 0.5,
    },
    initial_conditions=[999, 1, 0],
    trange=[0, 100],
    totpop=1000,
)

len(ensemble)                 # 200 realizations
ensemble.traces["I"].shape    # (200, 201) — sims x time points

q = ensemble.quantiles([0.025, 0.5, 0.975])
q["I"][0.5]                   # median trajectory

ensemble.summary()            # final size / peak statistics
ensemble.plot_band("I")       # median with 95% band
ensemble.plot_band("I", show_reps=True)  # overlay individual runs

Initial conditions can also be sampled by passing a callable, and n_jobs > 1 parallelizes the realizations across processes.