Quick start
EpiAware is a set of small Julia packages that compose into infectious disease models. This shows the two ways you work with them: compose the distributions your data need, and assemble a whole model you can fit. The figures below are produced by running the code in CI, so they track the current packages.
New to Julia? Start with the Using Julia guide.
Install
Most packages are early and installed from GitHub:
using Pkg
Pkg.add(url = "https://github.com/EpiAware/ComposedDistributions.jl")
Pkg.add(url = "https://github.com/EpiAware/ComposableTuringIDModels.jl")Compose a delay chain
A case moves through events — infection, symptom onset, hospital admission, death — each a delay on the one before. Chain them with Sequential, and the tree prints its own structure:
using ComposedDistributions, Distributions
chain = Sequential([
Gamma(2.0, 1.5), # infection → onset
LogNormal(1.0, 0.5), # onset → admission
Gamma(2.0, 3.0), # admission → death
])Sequential (3 steps)
├─ step_1: Gamma{Float64}(α=2.0, θ=1.5)
├─ step_2: LogNormal{Float64}(μ=1.0, σ=0.5)
└─ step_3: Gamma{Float64}(α=2.0, θ=3.0)
The delay from infection to each event is the convolution of the steps so far — convolved composes them, and the result is an ordinary distribution you can pdf, rand, and plot:
using CairoMakie
to_onset = Gamma(2.0, 1.5)
to_admission = convolved(to_onset, LogNormal(1.0, 0.5))
to_death = convolved(to_admission, Gamma(2.0, 3.0))
ts = 0:0.1:32
fig = Figure()
ax = Axis(fig[1, 1]; xlabel = "days since infection", ylabel = "density")
for (d, label) in [(to_onset, "onset"), (to_admission, "admission"),
(to_death, "death")]
lines!(ax, ts, pdf.(d, ts); label, linewidth = 3)
end
axislegend(ax)
fig
Build and simulate a model
Assemble a model from interchangeable parts — a latent random walk driving direct infections, observed with Poisson error — with IDModel, which also prints its structure:
using ComposableTuringIDModels, Distributions
model = IDModel(
DirectInfections(; Z = RandomWalk(), initialisation_prior = Normal()),
PoissonError())IDModel
├─ infection: DirectInfections
│ └─ Z: RandomWalk
│ └─ ϵ_t: HierarchicalNormal
└─ observation: PoissonError
as_turing_model turns the assembly into a Turing model. Pass missing for the data and it simulates from the prior — here 30 draws of a 40-day outbreak:
n = 40
simulator = as_turing_model(model, missing, n)
sims = [simulator() for _ in 1:30] # each has generated_y_t, I_t, Z_t
fig = Figure()
ax = Axis(fig[1, 1]; xlabel = "day", ylabel = "cases")
for s in sims
lines!(ax, 1:n, s.generated_y_t; color = (:steelblue, 0.28))
end
fig
To fit real data, pass the case series instead of missing and sample:
turing_model = as_turing_model(model, cases, length(cases))
# 1000 draws across 2 chains, one per core
chain = sample(turing_model, NUTS(), MCMCThreads(), 1_000, 2)Next
- Browse the packages and open each one’s documentation.
- Each package’s docs include worked examples that go further.
- Ready to contribute or bring a use case? Get involved.