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 delays across events
A case moves through several delays — an incubation, then competing outcomes from onset. Wire them into one tree with Sequential and compose, and rescale a delay with affine from ModifiedDistributions:
using ComposedDistributions, ModifiedDistributions, Distributions
tree = Sequential([
Gamma(2.0, 1.5), # infection → onset
compose((admission = affine(LogNormal(1.0, 0.5); scale = 1.5),
death = Gamma(2.0, 3.0))), # onset → outcome
])The tree prints its own structure — branches, steps, and modifiers and all:
Sequential (2 steps)
├─ step_1: Gamma{Float64}(α=2.0, θ=1.5)
└─ step_2: Parallel (2 branches)
├─ admission: Affine(LogNormal{Float64}(μ=1.0, σ=0.5))
└─ death: Gamma{Float64}(α=2.0, θ=3.0)
Read its free parameters as a flat table, keyed by edge and parameter:
params_table(tree)params_table (6 rows)
edge param value support
──────────────── ───── ───── ──────────
step_1 shape 2.0 (0.0, Inf)
step_1 scale 1.5 (0.0, Inf)
step_2.admission mu 1.0 (0.0, Inf)
step_2.admission sigma 0.5 (0.0, Inf)
step_2.death shape 2.0 (0.0, Inf)
step_2.death scale 3.0 (0.0, Inf)
The whole tree is generative — simulate a case’s event times straight from it:
rand(tree)
# (step_1 = 2.37, step_2_admission = 2.72, step_2_death = 6.11)Build and simulate a model
Assemble a model from interchangeable parts — an AR(1) process on the log reproduction number, coupled to a renewal infection process through a generation interval, observed with negative-binomial error. IDModel also prints its structure:
using ComposableTuringIDModels, Distributions
latent = AR(
damp_priors = [truncated(Normal(0.8, 0.05), 0, 1)],
init_priors = [Normal(0.0, 0.25)],
ϵ_t = HierarchicalNormal(std_prior = HalfNormal(0.1)))
data = IDData(gen_distribution = Gamma(1.4, 1 / 0.38))
renewal = Renewal(data; rt = latent,
initialisation_prior = Normal(log(1.0), 1.0))
model = IDModel(renewal, NegativeBinomialError(
cluster_factor_prior = HalfNormal(0.1)))IDModel
├─ infection: Renewal
│ └─ rt: AR
│ └─ ϵ_t: HierarchicalNormal
└─ observation: NegativeBinomialError
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 infection trajectory:
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 = "infections", yscale = log10)
for s in sims
lines!(ax, 1:n, s.I_t; color = (:steelblue, 0.3))
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.