SIR Model with Vaccination

An SIR vaccination simulation modeling epidemics with optional vaccination.

Level:Intermediate

populationreinforcing-loopbalancing-loopcontrol

  • Stocks:susceptible, infected, recovered
  • Flows:infections, recoveries, vaccinations
  • Feedback Loops:disease spread (reinforcing), herd immunity (balancing)
  • Probes:susceptible, infected, recovered

Leverage Points

Discover how small, well-focused actions can produce significant, lasting improvements in complex systems.

Explore Leverage Points
simulation.py

SIR outbreak with optional vaccination

This minimal model tracks susceptible, infected and recovered populations. Flip vaccination on or off to see how immunisation rates change the course of an outbreak.


from tys import probe, progress

Simulate an SIR outbreak with optional vaccination.

def simulate(cfg: dict):
    import simpy
    env = simpy.Environment()

Parameters

    population = cfg["population"]
    infected = cfg["initial_infected"]
    beta = cfg["beta"]
    gamma = cfg["gamma"]
    vacc_rate = cfg.get("vaccination_rate", 0.0)
    vaccinate = cfg.get("vaccinate", False)
    days = cfg["sim_days"]

    susceptible = population - infected
    recovered = 0.0
    vaccinated_total = 0.0

    done = env.event()

Record the S, I and R populations over time.

    def recorder():
        while True:
            probe("susceptible", env.now, susceptible)
            probe("infected", env.now, infected)
            probe("recovered", env.now, recovered)
            yield env.timeout(1)
    env.process(recorder())

Apply infection, recovery and vaccination each day.

    def dynamics():
        nonlocal susceptible, infected, recovered, vaccinated_total
        for d in range(days):
            new_infections = beta * susceptible * infected / population
            new_recoveries = gamma * infected
            if vaccinate:
                new_vaccinated = vacc_rate * susceptible
            else:
                new_vaccinated = 0.0

            susceptible = max(susceptible - new_infections - new_vaccinated, 0.0)
            infected = max(infected + new_infections - new_recoveries, 0.0)
            recovered = min(recovered + new_recoveries + new_vaccinated, population)
            vaccinated_total += new_vaccinated

            if infected < 1:
                progress(int(100 * d / days), "🎉 Epidemic nearly over")

            yield env.timeout(1)

        progress(100)
        done.succeed({
            "final_infected": infected,
            "vaccinated_total": vaccinated_total
        })

    env.process(dynamics())
    env.run(until=done)
    return done.value


def requirements():
    return {
        "builtin": ["micropip", "pyyaml"],
        "external": ["simpy==4.1.1"],
    }
Default.yaml
population: 1000
initial_infected: 10
beta: 0.3
gamma: 0.1
vaccination_rate: 0.01
vaccinate: true
sim_days: 160
Charts (Default)

susceptible

susceptible chartCSV
Samples161 @ 0.00–160.00
Valuesmin 23.79, mean 181.00, median 54.32, max 990.00, σ 265.49

infected

infected chartCSV
Samples161 @ 0.00–160.00
Valuesmin 0.00, mean 42.55, median 6.12, max 205.05, σ 63.35

recovered

recovered chartCSV
Samples161 @ 0.00–160.00
Valuesmin 0.00, mean 776.45, median 939.56, max 976.21, σ 295.03
Final Results (Default)
MetricValue
final_infected0.00
vaccinated_total291.17
FAQ
How is vaccination modeled?
If vaccinate is true, each day susceptible individuals are reduced by vacc_rate * susceptible and moved to the recovered count.
How does infection spread each day?
New infections equal beta * susceptible * infected divided by the total population.
When is progress text shown?
If infected falls below one the simulation marks near completion with a progress message.