Overview
ComposableTuringIDModels builds an epidemiological model by assembling small parts rather than writing one bespoke model. Each part plays one of three roles — a latent process, an infection process, or an observation process — and each part becomes a Turing / DynamicPPL model through a single generic constructor, as_turing_model. Because every part speaks that one interface, parts nest inside one another and a whole model is composed from the pieces.
Three roles, one interface
A model is put together from parts filling three roles.
A latent process describes an unobserved series
over time, e.g. a log reproduction number or a growth rate. An infection process turns that latent series into unobserved infections
(directly, through exponential growth, or through the renewal equation). It takes the latent process as a slot and draws it while building the infections. An observation process turns infections into the observed data
, adding reporting delays, ascertainment, day-of-week effects, right-truncation, and count noise.
Each part is a plain struct with a single method of as_turing_model, which returns a DynamicPPL.Model. There is no deep type hierarchy: a part is identified by the method it implements, not by its place in a tree. A part that contains another part builds the inner model and samples it as a submodel, so the parts nest through the same interface they expose.
The three roles feed one another and plug into that single interface:
as_turing_model interface, and any part can be swapped for a compatible one.Swap a part to change an assumption
Because the parts share one interface, you compare modelling assumptions by swapping one struct for another and leaving the rest untouched.
using ComposableTuringIDModels, Distributions
# One latent process: an ARIMA-style differenced AR.
latent = DiffLatentModel(; model = AR(), init_priors = [Normal(), Normal()])
# Fold it into a direct-infections process, then swap only the observation
# model. Everything else stays the same.
poisson_model = IDModel(
DirectInfections(; Z = latent, initialisation_prior = Normal()),
PoissonError())
negbin_model = IDModel(
DirectInfections(; Z = latent, initialisation_prior = Normal()),
NegativeBinomialError())
# Each assembly is turned into one Turing model. `missing` data simulates from
# the prior; the composed model exposes its generated quantities.
turing_model = as_turing_model(poisson_model, missing, 20)
(; generated_y_t, I_t, Z_t) = turing_model()
length(generated_y_t), length(I_t), length(Z_t)(20, 20, 20)Where to go next
Composable design explains the
as_turing_modelprotocol and how parts nest as submodels in more detail.The case studies build complete models and fit them to real surveillance data, from a renewal model to a compartmental SIR.
The Public API lists every component you can compose.