Quick start

Install EpiAware and build your first models — composing distributions and assembling a joint model you can fit.

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

Thirty prior predictive infection trajectories over 40 days on a log scale

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.