Epimodels
Sponsored by
AI-powered epidemiological intelligence
Epimodels is a Python library for simulating and fitting mathematical epidemic models. It provides deterministic models in both continuous (ODE-based) and discrete (difference equation) time, along with a comprehensive parameter inference framework, symbolic analysis tools, and multiple ODE solver backends.
Features
- 27 model classes across continuous, discrete and stochastic (CTMC) families (SIR, SIS, SIRS, SEIR, SEQIAHR, multi-strain, vector-borne, and more)
- Model registry --
get_model("SIR", family="continuous"), string-based lookup across all families - Model fitting -- parameter estimation from observed data with 7 loss functions and 4 optimizers, plus a one-call
model.fit(...)sugar API - Bayesian inference -- DE-MCMC posterior sampling with normal/Poisson/negative-binomial observation models
- Uncertainty ensembles -- run many simulations with sampled parameters and get quantile bands
- Intervention scenarios -- time-bounded parameter changes (lockdowns, vaccination) with scenario comparison
- Rt estimation -- Cori/EpiEstim-style time-varying reproduction number from incidence data
- SDE models -- stochastic differential equation versions of any continuous model (JAX backend)
- Network models -- event-driven SIR/SIS on networkx graphs, adjacency dicts or matrices
- Model serialization -- save/load model specs (and traces) to JSON/YAML
- Symbolic analysis -- R0 computation, equilibrium finding, stability analysis, sensitivity analysis
- Multiple solvers -- scipy (CPU) and diffrax/JAX (GPU) backends with a unified interface
- Phase space tools -- time delay embedding, mutual information, phase portraits
- Mermaid diagrams -- auto-generated compartment flow diagrams for every model
Installation
pip install epimodels
Optional extras:
pip install epimodels[plot] # matplotlib plotting
pip install epimodels[dataframe] # pandas DataFrame support
pip install epimodels[jax] # diffrax/JAX GPU solvers
Getting Started
Simulation
from epimodels.continuous.models import SIR
model = SIR()
model([1000, 1, 0], [0, 50], 1001, {'beta': 2, 'gamma': .1})
model.plot_traces()
print(f"R0 = {model.R0}")
print(model.summary())
Parameter Fitting
from epimodels.continuous.models import SIR
from epimodels.fitting import fit_model, Dataset
model = SIR()
dataset = Dataset()
dataset.add_series("I", times=[0, 1, 2, 3, 5, 7, 10], values=[1, 3, 8, 20, 50, 80, 60])
result = fit_model(
model,
dataset,
params={"beta": (0.1, 5.0), "gamma": (0.01, 1.0)},
initial_conditions=[1000, 1, 0],
time_range=[0, 10],
)
print(result.best_params)
print(result.fitted_model.summary())
Symbolic Analysis
from epimodels.validation import SymbolicModel
sym = SymbolicModel()
sym.add_parameter("beta", positive=True, real=True)
sym.add_parameter("gamma", positive=True, real=True)
sym.add_variable("S", positive=True)
sym.add_variable("I", positive=True)
sym.add_variable("R", positive=True)
sym.set_total_population("N")
sym.define_ode("S", "-beta*S*I/N")
sym.define_ode("I", "beta*S*I/N - gamma*I")
sym.define_ode("R", "gamma*I")
R0 = sym.compute_R0_next_generation()
print(f"R0 = {R0}")
Available Models
Continuous (ODE)
| Model | Compartments | Key Features |
|---|---|---|
SIR |
S, I, R | Classic susceptible-infectious-removed |
SIS |
S, I | No immunity, reinfection |
SIRS |
S, I, R | Waning immunity |
SEIR |
S, E, I, R | Latent period |
SEQIAHR |
S, E, I, A, H, R, C, D | COVID-like with quarantine, hospitalization |
Dengue4Strain |
49 compartments | 4-strain dengue with cross-immunity |
SIRSEI |
7 compartments | Malaria vector-host with climate forcing |
SIRSEIData |
7 compartments | Malaria with real climate data |
SEIRS_SEI |
7 compartments | Vector-borne with deforestation/fire effects |
SIR2Strain |
10 compartments | Two-strain SIR with cross-immunity |
SIR1D |
S, I | 1D reduced SIR (beta/gamma tracking) |
SISLogistic |
S, I | SIS with logistic population growth |
SIRSNonAutonomous |
S, I, R | Time-dependent parameters (callables) |
NeipelHeterogeneousSIR |
I, tau | Heterogeneous susceptibility |
Discrete (Difference Equations)
| Model | Compartments | Key Features |
|---|---|---|
SIR |
S, I, R | Classic discrete SIR |
SIS |
S, I | No immunity |
SIRS |
S, I, R | Waning immunity |
SEIR |
S, E, I, R | Latent period |
SEIS |
S, E, I | Exposed, no immunity |
SIpRpS |
S, I, R | Partial immunity |
SIpR |
S, I, R | Secondary infections from recovered |
SEIpRpS |
S, E, I, R | Exposed + partial immunity |
SEIpR |
S, E, I, R | Exposed + secondary infections from R |
Influenza |
20 compartments | Age-structured (4 groups) |
SEQIAHR |
S, E, I, A, H, R, C, D | COVID-like discrete version |
Solvers
Epimodels supports multiple ODE solvers through a unified interface. You can choose between scipy (CPU-only) and diffrax (JAX-accelerated with GPU support) backends.
Available Solvers
| Backend | Class | Methods | Best For |
|---|---|---|---|
| scipy | ScipySolver |
RK45, RK23, DOP853, Radau, BDF, LSODA | General use, CPU-bound |
| diffrax | DiffraxSolver |
Tsit5, Dopri5, Dopri8, Euler, Heun, Midpoint, Ralston | GPU acceleration, batch simulations |
Usage Examples
from epimodels.continuous import SIR
from epimodels.solvers import ScipySolver, DiffraxSolver
# Default scipy solver (RK45)
model = SIR()
model([999, 1, 0], [0, 100], 1000, {'beta': 0.3, 'gamma': 0.1})
# Explicit scipy solver with specific method
solver = ScipySolver(method='LSODA')
model = SIR()
model([999, 1, 0], [0, 100], 1000, {'beta': 0.3, 'gamma': 0.1}, solver=solver)
# JAX-accelerated solver (requires: pip install diffrax jax)
solver = DiffraxSolver(solver='Tsit5', rtol=1e-6, atol=1e-9)
model = SIR()
model([999, 1, 0], [0, 100], 1000, {'beta': 0.3, 'gamma': 0.1}, solver=solver)
When to Use Each Solver
| Scenario | Recommended Solver | Reason |
|---|---|---|
| General use | ScipySolver('LSODA') |
Fast, handles stiffness automatically |
| High accuracy needed | ScipySolver('DOP853') |
8th order method |
| Stiff systems | ScipySolver('BDF') or ScipySolver('Radau') |
Implicit methods |
| Batch simulations | DiffraxSolver('Tsit5') |
GPU parallelization |
| Parameter sweeps | DiffraxSolver |
JAX JIT compilation |
| Quick prototyping | Default (RK45) | Robust and reliable |
Installing Diffrax
For GPU acceleration, install the JAX backend:
# CPU only
pip install diffrax jax
# GPU (CUDA 12)
pip install diffrax "jax[cuda12]"
Model Fitting
The epimodels.fitting module provides parameter estimation from observed epidemiological data.
Loss Functions
| Loss Function | Best For |
|---|---|
SumOfSquaredErrors |
General purpose |
WeightedSSE |
Variable importance weighting |
PoissonLikelihood |
Count data |
NegativeBinomialLikelihood |
Overdispersed count data |
NormalLikelihood |
Continuous data with noise |
HuberLoss |
Robust to outliers |
CustomLoss |
User-defined objectives |
Optimizers
| Optimizer | Methods | Notes |
|---|---|---|
ScipyOptimizer |
L-BFGS-B, BFGS, Nelder-Mead, Powell, CG, differential_evolution | CPU, most methods |
JAXOptimizer |
Adam, SGD, RMSprop | GPU-accelerated |
NevergradOptimizer |
Derivative-free | No gradients needed |
MultiStartOptimizer |
Multi-start wrapper | Avoids local minima |
Related Libraries
For stochastic epidemic models check EpiStochModels.
Model Registry
Look up models by name across the continuous, discrete and stochastic families:
from epimodels import get_model, list_models
SIR = get_model("SIR", family="continuous")
model = SIR()
print(list_models())
Custom models can be registered with the @register_model decorator from epimodels.registry.
Intervention Scenarios
from epimodels.continuous import SIR
from epimodels.interventions import Intervention, Scenario, ScenarioComparison
model = SIR()
base = 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=[Intervention("beta", start=10, end=40, factor=0.4)])
cmp = ScenarioComparison(base, [lockdown]).run()
print(cmp.peak("I"))
cmp.plot("I")
Uncertainty Ensembles
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,
)
ensemble.quantiles([0.025, 0.5, 0.975])
ensemble.plot_band("I")
Bayesian Inference
# ... or simply, using the sugar API:
result = model.fit(
{"I": observed_I}, times=times,
params_to_fit={"beta": (0.1, 5.0), "gamma": (0.01, 1.0)},
total_population=10000,
method="bayes", likelihood="poisson",
)
print(result.summary())
Rt Estimation
Model-free, real-time reproduction number estimation from incidence data (Cori et al. 2013, the EpiEstim method):
from epimodels.rt import estimate_rt
result = estimate_rt(incidence, window=7, si_mean=4.0, si_sd=2.0)
print(result.rt_mean) # posterior mean Rt per window
result.plot() # Rt with 95% credible band
Stochastic Differential Equations
Add demographic (square-root) noise to any continuous model via diffrax/JAX
(pip install epimodels[jax]):
from epimodels.sde import SDEModel
sde = SDEModel(SIR())
sde([999, 1, 0], [0, 100], 1000, {"beta": 2.0, "gamma": 0.5},
n_sims=50, seed=42)
sde.get_quantiles(0.95)
sde.plot_traces("I")
Network Models
Event-driven SIR/SIS on contact networks (pip install epimodels[network]).
Accepts networkx graphs, adjacency dicts or adjacency matrices:
import networkx as nx
from epimodels.network import NetworkSIR
G = nx.barabasi_albert_graph(1000, 3, seed=0)
model = NetworkSIR(G)
model(5, [0, 50], {"beta": 0.3, "gamma": 0.1}, n_sims=20, seed=0)
print(model.final_size().mean()) # mean attack rate
model.plot_traces("I")
Saving and Loading Models
from epimodels.io import save_model, load_model
model.simulate([1000, 1, 0], [0, 50], 1001, {"beta": 2, "gamma": 0.1})
save_model(model, "sir_run.json", include_traces=True)
clone = load_model("sir_run.json") # registry-based reconstruction
Documentation
Full documentation is available at epimodels.readthedocs.io.
License
MIT License - see LICENSE.txt for details.
Release files for epimodels 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| epimodels-1.4.0.tar.gz | 149.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| epimodels-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 277.2 kB
Release files / epimodels-1.4.0.tar.gz
| Download URL | epimodels-1.4.0.tar.gz |
|---|---|
| Size | 149.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7ba3428d5254aaa5d0fc57dbe60781c1efdc5fd3f39c048a143e18754318bf8b
|
|
BLAKE2b-256 checksum How to use checksums |
638b95c52257a6be28300e23290e33d671b9936fed6f2b853017f8e02aca1fa6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency logRelease files / epimodels-1.4.0-py3-none-any.whl
| Download URL | epimodels-1.4.0-py3-none-any.whl |
|---|---|
| Size | 127.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7e1f21436535ba9f44529de3ef7b75a85e44e422906f7c07bddfeaa18d85cdf
|
|
BLAKE2b-256 checksum How to use checksums |
48ce4d83864e0beb80a97ceacb2d27ece406ea82f8bb692db41564443560f9ac
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 17, 2026.
Transparency log