Public Documentation
Documentation for ComposableTuringIDModels's public interface.
ComposableTuringIDModels.ComposableTuringIDModels Module
Composable probabilistic infectious disease modelling in Julia.
ComposableTuringIDModels builds epidemiological models from small, reusable components — infection processes (each owning its own latent parameter process) and observation models — each turned into a Turing/DynamicPPL model by the single generic constructor as_turing_model. Components compose by sampling one another as submodels, so a full model is assembled rather than hand-written.
This package is ported and adapted from the open-source, Apache-2.0 licensed EpiAware package; see the NOTICE file for attribution.
Examples
using ComposableTuringIDModels, Distributions
model = IDModel(
DirectInfections(; Z = RandomWalk(), initialisation = Normal()),
PoissonError())
rand(as_turing_model(model, missing, 20))Contents
Index
ComposableTuringIDModels.ComposableTuringIDModelsComposableTuringIDModels.ARComposableTuringIDModels.AbstractAccumulationStepComposableTuringIDModels.AbstractComposableModelComposableTuringIDModels.AbstractConstantRenewalStepComposableTuringIDModels.AbstractInfectionModelComposableTuringIDModels.AbstractLatentModelComposableTuringIDModels.AbstractMixingModelComposableTuringIDModels.AbstractObservationErrorModelComposableTuringIDModels.AbstractObservationModelComposableTuringIDModels.AbstractPriorModelComposableTuringIDModels.AbstractRenewalModifierComposableTuringIDModels.AggregateComposableTuringIDModels.AscertainmentComposableTuringIDModels.BinomialErrorComposableTuringIDModels.BroadcastLatentModelComposableTuringIDModels.CatalystODEParamsComposableTuringIDModels.CombineInfectionsComposableTuringIDModels.CombineLatentModelsComposableTuringIDModels.ConcatLatentModelsComposableTuringIDModels.ConstantRenewalStepComposableTuringIDModels.DiffLatentModelComposableTuringIDModels.DirectInfectionsComposableTuringIDModels.DirectSampleComposableTuringIDModels.ExactGPComposableTuringIDModels.ExpGrowthRateComposableTuringIDModels.FixedInterceptComposableTuringIDModels.GravityComposableTuringIDModels.HalfNormalComposableTuringIDModels.HierarchicalNormalComposableTuringIDModels.HierarchyComposableTuringIDModels.HilbertSpaceGPComposableTuringIDModels.IDModelComposableTuringIDModels.IDObservablesComposableTuringIDModels.IDProblemComposableTuringIDModels.IIDComposableTuringIDModels.ImportedCasesComposableTuringIDModels.InterceptComposableTuringIDModels.LatentDelayComposableTuringIDModels.MAComposableTuringIDModels.MissingObservationsComposableTuringIDModels.MixingStepComposableTuringIDModels.ModelShapeComposableTuringIDModels.NUTSamplerComposableTuringIDModels.NegativeBinomialErrorComposableTuringIDModels.NormalErrorComposableTuringIDModels.NullComposableTuringIDModels.ODEProcessComposableTuringIDModels.PoissonErrorComposableTuringIDModels.PrefixLatentModelComposableTuringIDModels.PrefixObservationModelComposableTuringIDModels.RandomWalkComposableTuringIDModels.RecordExpectedLatentComposableTuringIDModels.RecordExpectedObsComposableTuringIDModels.RenewalComposableTuringIDModels.RenewalStepComposableTuringIDModels.RepeatBlockComposableTuringIDModels.RepeatEachComposableTuringIDModels.ReplicateComposableTuringIDModels.ReportTriangleComposableTuringIDModels.ReportingCDFComposableTuringIDModels.ReportingPMFComposableTuringIDModels.ReportingTriangleComposableTuringIDModels.RightTruncateComposableTuringIDModels.SEIRParamsComposableTuringIDModels.SIRParamsComposableTuringIDModels.SafeNegativeBinomialComposableTuringIDModels.SafePoissonComposableTuringIDModels.SplitComposableTuringIDModels.StrataMapComposableTuringIDModels.StratifyComposableTuringIDModels.SusceptibleDepletionComposableTuringIDModels.TransformLatentModelComposableTuringIDModels.TransformObservationModelComposableTuringIDModels.UncertainDelayKernelFunctions.Matern32KernelKernelFunctions.Matern52KernelKernelFunctions.SqExponentialKernelComposableTuringIDModels.NegativeBinomialMeanClustComposableTuringIDModels.R_to_rComposableTuringIDModels.accumulate_scanComposableTuringIDModels.across_shapeComposableTuringIDModels.apply_methodComposableTuringIDModels.arimaComposableTuringIDModels.armaComposableTuringIDModels.as_turing_modelComposableTuringIDModels.as_turing_submodelComposableTuringIDModels.ascertainment_dayofweekComposableTuringIDModels.assert_prior_lengthComposableTuringIDModels.atComposableTuringIDModels.broadcast_dayofweekComposableTuringIDModels.broadcast_nComposableTuringIDModels.broadcast_ruleComposableTuringIDModels.broadcast_weeklyComposableTuringIDModels.condition_modelComposableTuringIDModels.define_y_tComposableTuringIDModels.equal_dimensionsComposableTuringIDModels.expected_RtComposableTuringIDModels.forecastComposableTuringIDModels.generate_observation_error_priorsComposableTuringIDModels.generated_observablesComposableTuringIDModels.get_param_arrayComposableTuringIDModels.get_stateComposableTuringIDModels.gravityComposableTuringIDModels.implements_infection_interfaceComposableTuringIDModels.implements_observation_interfaceComposableTuringIDModels.implements_prior_interfaceComposableTuringIDModels.infection_strataComposableTuringIDModels.observation_errorComposableTuringIDModels.pairwise_gen_intComposableTuringIDModels.path_priorComposableTuringIDModels.prior_orderComposableTuringIDModels.r_to_RComposableTuringIDModels.renewal_foiComposableTuringIDModels.renewal_init_stateComposableTuringIDModels.renewal_init_windowComposableTuringIDModels.renewal_pressureComposableTuringIDModels.spectral_densityComposableTuringIDModels.spread_drawsComposableTuringIDModels.standardised_index
Public API
ComposableTuringIDModels.AR Type
An autoregressive AR(p) latent process.
with damping coefficients damp, initial conditions from the prior in init, and innovations from the error model ϵ_t. The order p is fixed by the damp prior (a length-k vector ⇒ order k, a single distribution or a process ⇒ order 1); the init prior is sized to match.
Each prior slot takes a raw prior: pass a bare Distribution (order 1), a vector of them (order = its length), or a process (a latent model). The ϵ_t innovations are a length-n PATH slot, so a bare Distribution there is auto-wrapped in an Intercept — a constant innovation path (one shared draw broadcast to every step); use IID for n independent innovations. At order 1 the damp slot decides whether the coefficient is constant or time-varying, through the same time-varying-parameter mechanism any component can use:
AR(damp = Normal(...))— aDistributiongives a constant coefficient, drawn as a single scalar RV (efficient, no length-nallocation);AR(damp = RandomWalk())— a process gives a time-varying coefficient path, drawn at length n-1and threaded per step.
The coefficient is mapped through transform (default tanh for a process, so an unbounded path stays in the stationary band; identity for a bounded Distribution) and tracked as the generated quantity ρ, recoverable from the chain (group(chain, :ρ)). Higher-order (p > 1) coefficients are constant; time-varying higher-order AR is not yet supported.
Examples
using ComposableTuringIDModels, Distributions
ar = AR()
mdl = as_turing_model(ar, 10)
rand(mdl)ComposableTuringIDModels.AbstractAccumulationStep Type
Abstract supertype for accumulation step structs used with accumulate_scan.
A concrete AbstractAccumulationStep is a callable (step)(state, ϵ) returning the next state. It is backend-agnostic: it contains no Turing/DynamicPPL machinery and is reused unchanged across model components (RandomWalk, AR, MA, LatentDelay).
ComposableTuringIDModels.AbstractComposableModel Type
The single light supertype for every model component in ComposableTuringIDModels.
Unlike the deep abstract hierarchy used by the original EpiAware package, this package keeps a shallow tree: one root supertype, and directly beneath it a small set of role supertypes — AbstractLatentModel, AbstractInfectionModel, AbstractObservationModel (and AbstractObservationErrorModel under the last) — that encode the role a component plays. There is no deeper AbstractTuring* tree and there are no per-concept generate_* functions; dispatch happens on the concrete struct inside the single generic as_turing_model.
Encoding the role in the type lets the composer and manipulators constrain their component slots, so passing a wrong-role component (e.g. an observation model where a latent model is expected) fails at construction rather than at sampling. See AbstractLatentModel and its siblings for the interface each role's as_turing_model must satisfy.
ComposableTuringIDModels.AbstractConstantRenewalStep Type
Abstract supertype for renewal accumulation steps (constant generation interval, with or without susceptible depletion).
sourceComposableTuringIDModels.AbstractInfectionModel Type
Supertype for infection process models.
An infection model maps a series length n to a path of unobserved infections I_t. It owns its own latent (parameter) process internally — generating, e.g., a (log) reproduction number or growth-rate path — and maps that to infections, so no external latent path is threaded in. Its role interface is
as_turing_model(model::AbstractInfectionModel, n) # ⇒ (; I_t, Z_t)where the returned named tuple carries the infection path I_t and the model's internal latent draw Z_t (the (log) nothing for models with no exposable latent such as ODEProcess). Exposing Z_t keeps the latent recoverable as a generated quantity downstream.
Members include DirectInfections, ExpGrowthRate, Renewal and ODEProcess. Only Renewal carries a generation interval; the others take a transformation directly.
n is the size of the value the model returns: a ModelShape. A bare n::Int is a length-n path, giving a plain vector I_t. An n::Dims{2} is (n_strata, n_time), which is size(I_t) — one renewal recursion per stratum, each with its own seed. CombineInfections keeps the plain Int (its stratum count is fixed by how many models it holds) and returns an n_strata x n_time matrix. Any of these compose with Split / StrataMap on the observation side for the full range of infection↔observation mapping cardinalities.
ComposableTuringIDModels.AbstractLatentModel Type
Alias for AbstractPriorModel.
A latent process and a parameter prior share one role: both map a length n to a length-n vector via as_turing_model(m, n). So AbstractLatentModel is a const alias for AbstractPriorModel (AbstractLatentModel === AbstractPriorModel). Either name refers to the same type. Use whichever reads better at the call site.
ComposableTuringIDModels.AbstractMixingModel Type
Abstract supertype for drawn coupling operators.
A fixed coupling operator is a plain matrix and needs no type. A mixing model is for the case where the operator is inferred, so it has to be rebuilt from sampled parameters on every draw. Its interface is one method,
as_turing_model(m::MyMixing, n) # ⇒ a DynamicPPL.Model returning the operatorreturning whatever renewal_pressure accepts, which is usually a strata × strata matrix. Gravity is the worked example.
Handing one to Renewal's mixing keyword builds a MixingStep instead of a plain core, and the renewal resolves it before the scan through the same seam that resolves a prior-carrying modifier.
ComposableTuringIDModels.AbstractObservationErrorModel Type
Internal supertype shared by simple observation-error models (Poisson, negative binomial).
It exists only so that the generic observation-error as_turing_model loop — which is identical across error families — can be written once and dispatch the family-specific pieces (observation_error and generate_observation_error_priors) on the concrete type. It is the error sub-role of AbstractObservationModel; the package keeps no deeper hierarchy than this.
ComposableTuringIDModels.AbstractObservationModel Type
Supertype for observation models.
An observation model maps a path of expected observations Y_t to observed counts y_t. Its role interface is
as_turing_model(model::AbstractObservationModel, y_t, Y_t) # ⇒ observed counts y_ty_t === missing triggers prior/predictive simulation; a concrete y_t conditions the model on data. Observation modifiers (e.g. LatentDelay, Ascertainment, Aggregate) are themselves AbstractObservationModels: wrapping an observation model yields another observation model. Their inner-model slots are typed AbstractObservationModel, so only observation components can be wrapped.
AbstractObservationErrorModel is the sub-role for the simple error families (Poisson, negative binomial).
ComposableTuringIDModels.AbstractPriorModel Type
Supertype for prior models — a parameter prior expressed as a length-n submodel rather than a bare Distribution.
A prior model maps a length n to a length-n vector of parameter values via the same as_turing_model protocol every other component speaks:
as_turing_model(prior::AbstractPriorModel, n) # ⇒ a length-`n` vectorA raw Distribution (or vector of them) is not a prior model but flows through the same as_turing_submodel seam: as_turing_model has Distribution and Vector{<:Distribution} methods, so a bare distribution composes as a length-n prior submodel exactly like a model does. This is the single role for every parameter process: a latent process (a RandomWalk for a time-varying parameter, an AR process, …) satisfies the same as_turing_model(m, n) ⇒ length-n contract, so it drops into any prior slot directly. A genuinely scalar parameter is drawn with a native tilde (σ ~ model.std), keeping the chain as small as a bare ~ dist.
ComposableTuringIDModels.AbstractRenewalModifier Type
Abstract supertype for renewal modifiers composed onto a RenewalStep.
The type covers two shapes. A scan modifier is called by the scan itself: it transforms the proposed new incidence and carries its own substate. It implements
modifier_init_state(mod, window)— the modifier's initial substate, given the step's initial incidence window. The window is passed because a substate that tracks the incidence has to match its shape: a scalar for one series, one value per stratum for a stratified renewal.apply_modifier(mod, incidence, substate)— return(new_incidence, new_substate).
A prior-carrying modifier instead needs parameters sampled before the scan — a per-time importation rate, say — which the scan cannot draw, because a scan step is a plain deterministic function. It implements the other part of the interface:
as_turing_model(mod, n)— aDynamicPPL.Modelreturning the modifier used in the scan. The default method samples nothing and returnsmodunchanged, so a purely deterministic modifier (e.g.SusceptibleDepletion(1000.0)) is a scan modifier and implements nothing extra. A prior-carrying modifier implements this method, draws its slots throughas_turing_submodel, and returns a resolved scan modifier holding the drawn values — e.g.ImportedCasesresolves to anImportedRateand implements no scan interface of its own.
RenewalStep resolves its whole modifier tuple through this one seam (see its as_turing_model method), so a sampling modifier needs no special handling anywhere in the renewal model. A prior-carrying modifier reaching the scan means the step was built by hand and never resolved, so the scan interface errors with that message rather than a bare MethodError.
Each modifier's sampled variables are prefixed by its position in the modifier tuple, modifier_<i>, so two modifiers of the same kind cannot collide on a variable name — in Renewal(gen_int, SusceptibleDepletion(N), ImportedCases(Normal())) the importation rate is modifier_2.import_rates. Inserting or reordering a modifier therefore renames the variables of every modifier after it.
ComposableTuringIDModels.Aggregate Type
Aggregate the expected observations of an underlying model over reporting windows.
Each entry of aggregation gives the window length to sum over at the corresponding (broadcast) time point, and present (derived as aggregation .!= 0) marks the time points that are reported. The aggregation and presence vectors are broadcast to the observation length with RepeatEach, the expected observations are summed over each window, the inner model is applied to the present windows, and the predictions are scattered back into a full-length vector (zeros elsewhere).
Because the outermost modifier is applied first, the nesting of a LatentDelay decides the units the delay is measured in. Aggregate(LatentDelay(model, pmf), aggregation) sums into windows and then convolves, so the delay is in windows and the leading windows it consumes go unpredicted, while LatentDelay(Aggregate(model, aggregation), pmf) delays the daily series before summing, so the delay is in time points.
Either way the delay leaves the head of the series uncovered. A window with no expected values left to sum is dropped rather than scored against a zero it never measured, so the counts reported for it stay out of the likelihood. A window the delay only partially uncovers still has values to sum and is kept.
Arguments
ag: theAggregatemodel.y_t: the observed series (ormissingwhen simulating predictively).Y_t: the expected-observation series.
Examples
using ComposableTuringIDModels
obs = Aggregate(PoissonError(), [0, 0, 0, 0, 0, 0, 7])
mdl = as_turing_model(obs, missing, fill(10.0, 14))
rand(mdl)Fields
model: the underlying observation model applied to the aggregated windows.aggregation: the per-period window lengths (0marks an unreported point).present: the boolean presence mask (aggregation .!= 0).
ComposableTuringIDModels.Ascertainment Type
Scale the expected observations of an underlying observation model by an ascertainment prior process.
The latent_model slot takes a latent model for a time-varying ascertainment effect, or a bare Distribution for a single constant ascertainment factor shared across the series (wrapped in an Intercept, so one value is drawn and broadcast). Whatever is passed generates a length-length(Y_t) series which is combined with the expected observations Y_t through transform before being passed to the inner observation model. The default transform applies a multiplicative effect on the exponential scale ((Y_t, x) -> xexpy.(Y_t, x)), so a value x multiplies the expected observation by exp(x). The prior is prefixed with latent_prefix (a latent model via PrefixLatentModel, a distribution via its sampled-variable name) unless the prefix is the empty string.
Arguments
obs_model: theAscertainmentmodel.y_t: the observed series (ormissingwhen simulating predictively).Y_t: the expected-observation series.
Examples
using ComposableTuringIDModels, Distributions
# A latent model gives a time-varying ascertainment effect. The default
# transform reads the effect on the log scale, so `log(0.1)` is a 10%
# ascertainment rate.
obs = Ascertainment(PoissonError(), FixedIntercept(log(0.1)))
rand(as_turing_model(obs, missing, fill(10.0, 5)))
# A bare Distribution / prior gives a single constant ascertainment factor.
obs_const = Ascertainment(PoissonError(), Normal(0.0, 0.1))
rand(as_turing_model(obs_const, missing, fill(10.0, 5)))Fields
model: the underlying observation model the ascertained expected observations are passed to.latent_model: the prior model generating the ascertainment effect (a latent model for a time-varying effect, a distribution/prior for a constant factor), prefixed unlesslatent_prefixis empty.transform: the function(Y_t, x)combining expected observations with the ascertainment effect.latent_prefix: the prefix applied to the ascertainment prior's variables.
ComposableTuringIDModels.BinomialError Type
A binomial observation-error model: the observed successes are binomially distributed about a per-time-point number of trials N and a success probability supplied by the expected series.
Unlike the count error families (PoissonError, NegativeBinomialError) — whose expected series Y_t is an expected count — the expected series passed to BinomialError is the success probability
The number-of-trials N comes from the data
A binomial likelihood needs a number of trials per time point, N_t. N is known data (it is not inferred), so — like the observed successes — it is supplied through the observation data y_t, not stored on the model. The BinomialError struct carries no data.
The observation data is a NamedTuple with a y field (the observed successes) and an N field (the number of trials):
y_t = (y = observed_successes, N = trials)where N is a scalar Integer (the same trials at every time point) or an AbstractVector{<:Integer} (per-time-point trials). To simulate, pass y = missing while still supplying N, e.g. y_t = (y = missing, N = fill(20, n)).
This follows the same NamedTuple-data pattern as a Split stream: the shared define_y_t hook unpacks the y field that every error model scores, and BinomialError additionally reads the N field it needs.
Examples
using ComposableTuringIDModels
be = BinomialError()
# 20 trials per time point; the expected series is a success probability.
mdl = as_turing_model(be, (y = missing, N = fill(20, 10)), fill(0.3, 10))
rand(mdl)ComposableTuringIDModels.BroadcastLatentModel Type
Broadcast a shorter latent process to length n under a broadcast rule.
The inner model is generated over the length the rule requires (broadcast_n), then expanded to length n (broadcast_rule).
Arguments
model: theBroadcastLatentModel.n: the length of the broadcasted series to generate.
Examples
using ComposableTuringIDModels
each = BroadcastLatentModel(RandomWalk(), 7, RepeatEach())
rand(as_turing_model(each, 10))The model slot is a length-n PATH slot: a bare Distribution there is auto-wrapped in an Intercept, giving a constant inner path; a process, an IID, or a vector passes through. Use IID for n independent draws. It is composed through as_turing_submodel.
Fields
model: the underlying latent model.period: the broadcast period.broadcast_rule: theAbstractBroadcastRuleapplied.
ComposableTuringIDModels.CatalystODEParams Type
Declarative, model-agnostic ODE parameter component built from any Catalyst ReactionSystem, usable as the parameter component of an ODEProcess in place of the hand-coded SIRParams / SEIRParams.
You declare a reaction network and give priors for its initial conditions and rate parameters; Catalyst + ModelingToolkit generate the ODE system and a symbolic Jacobian (jac = true), so there is no hand-written vector field or Jacobian to keep in sync, and nothing here is specialised to a particular compartmental model. Construct it for an SIR network, an SEIR network, or any other network the same way — only the reactions change.
Sampling and problem rebuilding are symbolic: as_turing_model samples each supplied prior into a flat Turing variable named after its species / parameter symbol (e.g. β, S) and returns symbolic symbol => value maps, which remake places into the problem by name. There is no positional-index bookkeeping, so species / parameter ordering inside the compiled problem is never assumed. Index the resulting solution symbolically too, with the network's own handles: sol2infs = sol -> sol[rn.I, :].
Optional extension
The constructor and sampling logic load only when Catalyst and ModelingToolkit are present (using ComposableTuringIDModels, Catalyst, ModelingToolkit). The heavy symbolic stack stays out of the default install; the hand-coded models remain the zero-latency default. Constructing a CatalystODEParams before loading Catalyst raises an informative error.
Arguments
rn: theCatalystReactionSystem(e.g. from@reaction_network).
Keyword Arguments
tspan: the ODE solution time span.u0_priors: the initial conditions, as symbolic-handle ⇒ spec pairs ([rn.S => Beta(...), rn.R => 0.0, ...]). Each spec is either aDistribution(sampled, as a flat variable named after the species) or a plainReal(a fixed initial value, not sampled). Every species ofrnmust appear.p_priors: the rate parameters, as symbolic-handle ⇒ spec pairs ([rn.β => LogNormal(...), ...]), each aDistribution(sampled) or a fixedReal. Every parameter ofrnmust appear.
Fields
prob: theODEProblembuilt fromrn(auto symbolic Jacobian).u0_specs: per-species specs (symbolic handle, flat name, prior-or-fixed).p_specs: per-parameter specs (symbolic handle, flat name, prior-or-fixed).
Examples
using ComposableTuringIDModels, Catalyst, ModelingToolkit, OrdinaryDiffEq, Distributions
sir = @reaction_network begin
β, S + I --> 2I
γ, I --> R
end
params = CatalystODEParams(sir;
tspan = (0.0, 30.0),
u0_priors = [sir.S => Beta(99, 1), sir.I => Beta(1, 99), sir.R => 0.0],
p_priors = [sir.β => LogNormal(log(0.3), 0.1), sir.γ => LogNormal(log(0.1), 0.1)])
process = ODEProcess(params = params, sol2infs = sol -> sol[sir.I, :])ComposableTuringIDModels.CombineInfections Type
Combine several infection processes into one n_strata x n_time I_t matrix.
Each model in models is drawn independently over the same series length n — a genuinely different infection process per stratum (a different region, a different pathogen variant), each with its own internal latent. Stratum k is row k of the returned I_t, the same inf_strata x time orientation StrataMap uses, so CombineInfections composes directly with Split on the observation side: IDModel(CombineInfections(models), Split(template, W)) maps several distinct infection processes onto observation streams through a weight matrix W, covering many-to-one (a single aggregation row) and many-to-many (a general W).
Each sub-model is deliberately prefixed by its names entry before being sampled as a submodel (like Split): two independent infection processes would otherwise collide on variable names (e.g. two RandomWalks both naming their innovation ϵ_t), so this is one of the components that prefixes on purpose rather than following the package's flat, prefix-off default.
n stays a plain Int here. The stratum count is fixed by how many models models holds, not by a shape argument, and each model runs its own independent scan, so there is no shared incidence window for a mixing operator to couple. See Renewal's mixing slot and Coupled patch models for the couplable case.
Fields
models: the vector of infection models, one per stratum.names: the stratum names, used both as the submodel prefix and as theZ_tNamedTuple keys.
Examples
using ComposableTuringIDModels, Distributions
north = DirectInfections(; Z = RandomWalk(), initialisation = Normal(log(50.0), 0.2))
south = DirectInfections(; Z = RandomWalk(), initialisation = Normal(log(20.0), 0.2))
model = CombineInfections([north, south], ["north", "south"])
sim = as_turing_model(model, 12)()
size(sim.I_t) # 2 strata x 12 time pointsComposableTuringIDModels.CombineLatentModels Type
Combine several latent models of the same length by summing their outputs.
Each component is generated over the full length n and the results are added. When a non-empty prefix is supplied for a component it is wrapped in a PrefixLatentModel so its variables stay distinct.
Arguments
latent_models: theCombineLatentModelscollection.n: the shape to generate — a length or an(n_strata, n_time)shape, whatever the component models accept.
Examples
using ComposableTuringIDModels, Distributions
combined = CombineLatentModels([Intercept(Normal(2, 0.2)), AR()])
rand(as_turing_model(combined, 10))Fields
models: the vector of latent models (prefix-wrapped where a prefix is set).prefixes: the vector of prefixes, one per model.
ComposableTuringIDModels.ConcatLatentModels Type
Concatenate several latent models along time into one length-n series.
The length n is partitioned across the component models by dimension_adaptor (default equal_dimensions); each component generates its own segment and the segments are concatenated.
Arguments
latent_models: theConcatLatentModelscollection.n: the total length of the latent series to generate.
Examples
using ComposableTuringIDModels, Distributions
combined = ConcatLatentModels([Intercept(Normal(2, 0.2)), AR()])
rand(as_turing_model(combined, 10))Fields
models: the vector of latent models (prefix-wrapped where a prefix is set).no_models: the number of models in the collection.dimension_adaptor: maps(n, no_models)to a vector of segment lengths.prefixes: the vector of prefixes, one per model.
ComposableTuringIDModels.ConstantRenewalStep Type
Renewal step with a constant generation interval (stored reversed) and a coupling operator.
rev_gen_int is a vector for one shared generation interval and a strata × lags matrix for one interval per stratum. mixing defaults to I, which leaves the strata uncoupled; see renewal_pressure for what else it accepts.
Fields
rev_gen_int: the reversed generation interval.mixing: the coupling operator applied to the convolved window.
ComposableTuringIDModels.DiffLatentModel Type
Model a latent process as a d-fold differenced version of an inner process.
If model, then
and cumsum d times. The d initial terms are inferred from the prior in init; d equals the length of that prior.
The init slot takes a raw prior: pass a vector of Distributions (its length sets d), or a richer prior model. It is sampled through as_turing_submodel.
The model slot is a length-n PATH slot: a bare Distribution there is auto-wrapped in an Intercept, giving a constant inner path; use IID for n independent draws.
Composing DiffLatentModel over an AR gives an ARIMA-style latent process.
Examples
using ComposableTuringIDModels, Distributions
diff = DiffLatentModel(; model = RandomWalk(), init = [Normal(), Normal()])
mdl = as_turing_model(diff, 10)
rand(mdl)ComposableTuringIDModels.DirectInfections Type
Model unobserved infections as a direct transformation of an internally generated latent path.
where the latent model Z supplies transformation, and the unconstrained initial infections initialisation. The latent process is generated inside the model rather than threaded in from outside, so as_turing_model takes a ModelShape n and returns the named tuple (; I_t, Z_t).
This model carries no generation interval — it never uses one — so it takes a transformation directly (Renewal is the only infection model that carries a generation interval).
Handing Z a Stratify (or Replicate) and calling as_turing_model(model, (n_strata, n_time)) gives one direct-infections series per stratum, each with its own seed (from a vector-valued initialisation, or the same scalar seed broadcast to every stratum). This model has no coupling slot. A plain transformation of the latent path has no incidence window for a mixing operator to act on. See Renewal for coupled strata.
Fields
Z: the latent process model (anAbstractLatentModel) generating. A length- nPATH slot: a bareDistributionhere is auto-wrapped in anIntercept, giving a constant path (one shared draw broadcast to lengthn); useIIDfornindependent draws, orStratify/Replicatefor a strata axis.transformation: the link mapping the unconstrained sum to non-negative infections (defaultexp).initialisation: the prior for the unconstrained initial infections (aDistributionor prior model, sampled throughas_turing_submodel).
Examples
using ComposableTuringIDModels, Distributions
inf = DirectInfections(; Z = RandomWalk(), initialisation = Normal())
mdl = as_turing_model(inf, 10)
rand(mdl)Stratified, sharing a random walk with partially pooled per-stratum deviations:
strat = DirectInfections(; Z = Stratify(RandomWalk(), Hierarchy()),
initialisation = Normal())
size(as_turing_model(strat, (3, 10))().I_t)ComposableTuringIDModels.DirectSample Type
Direct sampling from a model's prior (no MCMC).
apply_method(model, ::DirectSample) samples the prior: with an integer n_samples it draws that many times with Turing.Prior() (returning a chain), and with nothing it draws once with rand (returning a NamedTuple).
Fields
n_samples: number of prior draws, ornothingfor a singleranddraw.
ComposableTuringIDModels.ExactGP Type
An exact Gaussian-process latent process.
The exact counterpart of HilbertSpaceGP. Where the Hilbert-space model approximates the GP by a short weighted sum of fixed basis functions, this model forms the full kernel and draws the path from it directly, so it is the exact GP the Hilbert-space model approximates:
The nugget
The path is drawn non-centred: standard-normal weights HilbertSpaceGP this keeps only n weights
Kernels are KernelFunctions.jl types (SqExponentialKernel, Matern32Kernel, Matern52Kernel, ...) — the same kernels HilbertSpaceGP uses, and the ones AbstractGPs.jl builds exact GPs from. The kernel sees the standardised index standardised_index, exactly as in HilbertSpaceGP, so the length scale n, so — again as in HilbertSpaceGP — as_turing_model builds it once and captures it rather than rebuilding it inside the @model body; only the covariance and its factorisation are per-evaluation work.
Fields
length_scale: prior for the length scale; it must put no mass below zero, since the covariance is not positive definite at . Checked at construction.marginal_std: prior for the marginal standard deviation; it must put no mass below zero. Checked at construction. kernel: the covariance kernel, a KernelFunctions.jlKernel(defaultSqExponentialKernel()).jitter: relative diagonal nuggetfor a stable Cholesky factor (default 1e-6); the amount added is.
Sampled variables
n weights as z, so a chain reads as chain[:σ] and a value is pinned with fix(model, (ℓ = 0.5, σ = 0.5)).
Those names reach the top level of a composed model unprefixed, where σ collides with the σ of an error model such as NormalError and check_model fails. Wrap the process in PrefixLatentModel and its hyperparameters become gp.ℓ and gp.σ. Composable design covers why names are generic and prefixes local.
Examples
using ComposableTuringIDModels, Distributions
gp = ExactGP()
mdl = as_turing_model(gp, 30)
rand(mdl)A rougher prior with a Matérn-3/2 kernel:
gp_matern = ExactGP(kernel = Matern32Kernel())
length(as_turing_model(gp_matern, 30)())Composed with an error model that owns a σ of its own, prefixed so the two scales stay apart:
using DynamicPPL: VarInfo
composed = IDModel(
Renewal(;
generation_time = [0.3, 0.4, 0.3],
rt = PrefixLatentModel(; model = gp, prefix = "gp"),
initialisation = Normal()
),
NormalError()
)
keys(VarInfo(as_turing_model(composed, fill(10.0, 20), 20)))ComposableTuringIDModels.ExpGrowthRate Type
Model unobserved infections via an internally generated time-varying exponential growth rate.
where the latent model rt supplies the (log) growth rates transformation, and the unconstrained initial infections initialisation. The growth-rate process is generated inside the model, so as_turing_model takes a ModelShape n and returns the named tuple (; I_t, Z_t) with Z_t the growth-rate path.
This model carries no generation interval — it never uses one — so it takes a transformation directly (Renewal is the only infection model that carries a generation interval).
Handing rt a Stratify (or Replicate) and calling as_turing_model(model, (n_strata, n_time)) gives one growth-rate series per stratum, each cumulated (and seeded) independently. This model has no coupling slot. The cumulative sum has no incidence window for a mixing operator to act on. See Renewal for coupled strata.
Fields
rt: the latent process model (anAbstractLatentModel) generating the growth-rate path. A length-nPATH slot: a bareDistributionhere is auto-wrapped in anIntercept, giving a constant path (one shared draw broadcast to lengthn); useIIDfornindependent draws, orStratify/Replicatefor a strata axis.transformation: the link mapping the unconstrained cumulative sum to non-negative infections (default: numerically equivalent toexp, implemented viaLogExpFunctions.xexpyfor numerical stability).initialisation: prior for the unconstrained initial infections (aDistributionor prior model, sampled throughas_turing_submodel).
Examples
using ComposableTuringIDModels, Distributions
egr = ExpGrowthRate(; rt = RandomWalk(), initialisation = Normal())
rand(as_turing_model(egr, 10))Stratified, each stratum an independent growth-rate path:
strat = ExpGrowthRate(; rt = Replicate(RandomWalk()), initialisation = Normal())
size(as_turing_model(strat, (3, 10))().I_t)ComposableTuringIDModels.FixedIntercept Type
A fixed (non-sampled) intercept broadcast to a length-n latent process.
ComposableTuringIDModels.Gravity Type
A gravity coupling operator with inferred exponents.
The exponents of gravity are prior slots, so the operator is rebuilt from sampled values on every draw. The coupling between strata is therefore estimated rather than assumed. Handing one to Renewal's mixing keyword is the only difference from a fixed matrix. The renewal draws it through the step seam before the scan.
Each exponent slot takes a bare Distribution (one scalar draw), a FixedIntercept to hold it fixed, or any other prior model, so a partly-fixed operator needs no separate type.
Both the fixed and the inferred path call the same gravity function, so they cannot drift.
Fields
pop: the population of each stratum.dist: thestrata × stratadistance matrix.α: prior for the exponent on the destination population.β: prior for the exponent on the origin population.γ: prior for the exponent on distance.within: the own-stratum weight, relative to a typical pairwise term.
Examples
using ComposableTuringIDModels, Distributions
pop = [1.0e6, 2.0e5]
dist = [0.0 50.0; 50.0 0.0]
g = Gravity(pop, dist; α = Normal(0, 0.5), β = Normal(0, 0.5),
γ = truncated(Normal(2, 0.5), 0, Inf))
size(as_turing_model(g, (2, 20))())ComposableTuringIDModels.HalfNormal Type
A half-normal prior distribution parameterised by its mean μ.
so that
Examples
using ComposableTuringIDModels, Distributions
hn = HalfNormal(1.0)
nothing
# outputComposableTuringIDModels.HierarchicalNormal Type
A non-centred hierarchical normal latent process.
Fields
mean: the mean of the normal process.std: the prior for the standard deviation— a Distribution(a constant, one scalar RV) or a process (a length- n, e.g. time-varying, scale). Drawn through the singleas_turing_submodelseam and broadcast over the innovations, so a process makes the scale time-varying (stochastic volatility) with no other change.add_mean: flag controlling whethermeanis added (false whenmean == 0).
Examples
using ComposableTuringIDModels, Distributions
hn = HierarchicalNormal()
mdl = as_turing_model(hn, 10)
rand(mdl)ComposableTuringIDModels.Hierarchy Type
Partially pool a per-group level across groups, where the cross-group relationship is itself a prior model.
Hierarchy is a non-centred partial-pooling latent process over a grouping dimension. Built with as_turing_model(h, n_groups) it draws a single shared level mean and n_groups group deviations from across, and returns the numeric length-n_groups vector
with Z — with no group-axis contract change.
The number of groups is not a field of the struct: it is supplied at build time (read from the grouping dimension of the data), exactly the way a series length n is passed to as_turing_model(latent, n).
Both slots take a raw prior (a bare Distribution, or a latent/prior model), sampled through as_turing_submodel, so the pooling behaviour is parameterised rather than hard-coded through across:
an i.i.d.-Normal (
IID(Normal()), the default) gives classic exchangeable partial pooling — each group's deviation is an independent draw shrunk toward the shared level;a
RandomWalkrelates neighbouring groups (adjacent age-bands / ordered strata), so the group effects are correlated along the grouping dimension;any other
AbstractLatentModelworks — the hierarchy is a prior process over the grouping dimension.
This is the numeric, contract-compliant partial-pooling construct: it returns length-n_groups values rather than per-group model variants, and takes its cross-group relationship through the prior interface.
Fields
mean: prior for the shared level(a Distributionor prior model).across: the cross-group relationship generating the group deviations (defaultIID(Normal())). A length-n_groupsPATH slot: a bareDistributionhere is auto-wrapped in anIntercept— a constant deviation shared across groups — so useIIDfor exchangeable (independent) group deviations.
Examples
using ComposableTuringIDModels, Distributions
# Partially pool a per-group level across 3 groups with classic (exchangeable)
# pooling; n_groups is supplied at build time.
h = Hierarchy(; across = IID(Normal(0.0, 1.0)))
length(as_turing_model(h, 3)())ComposableTuringIDModels.HilbertSpaceGP Type
A Hilbert-space approximate Gaussian process (HSGP) latent process.
A Gaussian process places a prior over functions and is a natural latent process for a smoothly varying quantity such as
], which writes the GP as a short weighted sum of fixed basis functions
where the eigenfunctions
and kernel. Kernels are KernelFunctions.jl types, so the model reuses the ecosystem-standard kernels rather than defining its own: SqExponentialKernel (the default) gives very smooth paths, while Matern32Kernel / Matern52Kernel give progressively rougher ones. Only the spectral_density changes between kernels; the basis is shared. The Gaussian-process case study checks the basis against the Gram matrix AbstractGPs.jl builds from the same kernel, and compares this model against ExactGP.
Only n, m and the boundary factor c, not on any sampled parameter. as_turing_model therefore builds them outside the @model body and captures them, so nothing in the basis is differentiated and each log-density evaluation only reweights and combines a fixed matrix. (Composed inside another model — a Renewal whose rt slot is this GP — the enclosing @model reconstructs its submodels on every evaluation, so the basis is rebuilt inside the traced call. It still depends on no sampled parameter, but it is executed and taped like the rest of the body, so it is not free; the Gaussian-process case study measures what that costs.) The latent path is a cheap matrix–vector product of a fixed basis against a small set of standard-normal weights — a non-centred parameterisation that is fast and samples well under NUTS, including with Mooncake reverse-mode AD.
The accuracy/speed trade-off is controlled by two numbers [ DocumenterCitations.CitationSiteNode("riutortmayol2023practical-cite-4")
]: the number of basis functions m (more basis functions resolve shorter length scales, at linear cost) and the boundary factor c (the domain is extended to standardised_index), m stays adequate as the series length changes.
The two act at opposite ends of the length-scale range, and each sets the floor on accuracy where the other cannot help. A short m: the basis has to resolve wiggles finer than c, because the approximation is periodic on c = 1.2 that boundary error cannot be cleared by any m, so the constructor rejects it.
The defaults are tuned for the squared-exponential kernel
With m = 20 and c = 1.5 the squared-exponential covariance is reconstructed to a few parts in ten thousand for m is what fixes it: m = 60 brings c sets the floor instead). The Matérn kernels are markedly worse at the same m — their spectral density has an algebraic rather than Gaussian tail, so the truncated basis discards more of it. At Matern32Kernel error is about 2.5%, roughly 110 times the squared-exponential figure, and at Matern52Kernel sits between the two.
A truncated basis loses variance rather than adding it, so where m is too small the process is systematically under-dispersed: at m = 20 the implied marginal standard deviation against a nominal m for a fit that settles on a short length scale, and raise it further for a Matérn kernel. The prior's floor of 0.05 is a numerical guard on the spectral density, not a claim that a 20-function basis resolves that scale.
Fields
length_scale: prior for the length scale; it must put no mass below zero, since has no spectral density. Checked at construction.marginal_std: prior for the marginal standard deviation; it must put no mass below zero. Checked at construction. m: number of basis functions.c: boundary factor; the GP is approximated on with .kernel: the covariance kernel, a KernelFunctions.jlKernel(defaultSqExponentialKernel()).
Sampled variables
m basis weights as β, so a chain reads as chain[:σ] and a value is pinned with fix(model, (ℓ = 0.5, σ = 0.5)).
Those names reach the top level of a composed model unprefixed, where σ collides with the σ of an error model such as NormalError and check_model fails. Wrap the process in PrefixLatentModel and its hyperparameters become gp.ℓ and gp.σ. Composable design covers why names are generic and prefixes local.
Examples
using ComposableTuringIDModels, Distributions
gp = HilbertSpaceGP()
mdl = as_turing_model(gp, 30)
rand(mdl)A rougher prior with a Matérn-3/2 kernel:
gp_matern = HilbertSpaceGP(kernel = Matern32Kernel())
length(as_turing_model(gp_matern, 30)())Composed with an error model that owns a σ of its own, prefixed so the two scales stay apart:
using DynamicPPL: VarInfo
composed = IDModel(
Renewal(;
generation_time = [0.3, 0.4, 0.3],
rt = PrefixLatentModel(; model = gp, prefix = "gp"),
initialisation = Normal()
),
NormalError()
)
keys(VarInfo(as_turing_model(composed, fill(10.0, 20), 20)))ComposableTuringIDModels.IDModel Type
A composed epidemiological model linking an infection process and an observation model.
The infection process owns its own latent (parameter) process internally, so a composed model is just two parts: infections, then observations.
Sampling as_turing_model(model, y_t, n) runs the two stages as submodels:
The returned generated quantities are (; generated_y_t, expected_y_t, I_t, Z_t). generated_y_t is the observation model's sampled y_t (the observed-or-simulated series, or a NamedTuple of streams for a Split); expected_y_t is its pre-error expected series (the uniform observation return contract). Z_t is the infection model's internal latent draw (e.g. the (log) nothing for infection models with no exposable latent (e.g. ODEProcess). Pass y_t = missing to simulate from the prior, or a data vector to condition.
Fields
infection_model: the infection process model generating (and its internal latent ).observation_model: the observation model mapping to .
Examples
using ComposableTuringIDModels, Distributions
model = IDModel(
DirectInfections(; Z = RandomWalk(), initialisation = Normal()),
PoissonError())
mdl = as_turing_model(model, missing, 20)
rand(mdl)ComposableTuringIDModels.IDObservables Type
Container for the outputs of an inference run: the model, the data, the posterior samples, and any generated quantities.
Fields
model: the model that was sampled.data: the data the model was conditioned on.samples: the posterior samples (or optimiser result).generated: generated quantities, ormissingif not computed.
ComposableTuringIDModels.IDProblem Type
A full epidemiological inference problem: an infection process, an observation model, and a time span. The latent (parameter) process is owned by the infection model, so it is not a separate slot here.
as_turing_model(problem, data) assembles the corresponding IDModel over tspan and conditions it on data.y_t. The infection process's shape is read from the observation model and data.y_t at build time (see infection_strata), not stored on the problem: a plain vector (or missing) gives a single-series infection process, exactly as today, while a data matrix or a NamedTuple of streams gives a stratified infection process with one row per stratum.
Arguments
idproblem: theIDProblem.data: a value with ay_tfield holding the observations (ormissing).
Examples
using ComposableTuringIDModels, Distributions
problem = IDProblem(
infection = DirectInfections(; Z = RandomWalk(), initialisation = Normal()),
observation_model = PoissonError(),
tspan = (1, 20))
rand(as_turing_model(problem, (; y_t = missing)))Fields
infection: the infection process model.observation_model: the observation model.tspan: the(first, last)time span of the series.
ComposableTuringIDModels.IID Type
Model a latent process ϵ_t.
Examples
using ComposableTuringIDModels, Distributions
model = IID(Normal(0, 1))
mdl = as_turing_model(model, 10)
rand(mdl)ComposableTuringIDModels.ImportedCases Type
Imported-cases modifier for RenewalStep.
Adds an externally seeded importation rate to the renewal incidence,
so infections can arrive from outside the modelled population — the mechanism behind a renewal process that would otherwise die out from a zero initial incidence, and behind reintroduction after local elimination.
For infection arriving from another modelled stratum, rather than from outside the system altogether, use the renewal mixing slot instead (see renewal_pressure); handing this modifier a Stratify rate gives a strata × time importation rate, one exogenous stream per stratum, read per step with at exactly as a shared rate is.
importation_rate is a per-step parameter slot holding the unconstrained rate at: a bare Distribution is one unknown constant shared across time, a Vector{<:Distribution} or a latent process (e.g. a RandomWalk) is a length-n path. The modifier maps whatever it gets onto the positive rate transformation exp), so importation is positive by construction and any latent process can drive it — a time-varying
The rate is drawn before the scan through the modifier seam (see AbstractRenewalModifier), giving an ImportedRate that adds SusceptibleDepletion the imports are added to the depleted incidence, so they are not scaled by the susceptible fraction and do not themselves deplete the pool; placed before it they are treated as part of the incidence the pool depletes.
The drawn rate is named import_rates, prefixed by the modifier's position in the renewal step's modifier tuple, so its posterior is read from a chain as
model = Renewal(gen_int, SusceptibleDepletion(N), ImportedCases(Normal());
rt = RandomWalk(), initialisation = Normal())
# `modifier_2` because importation is the second modifier; `exp` puts the
# draws back on the scale of imports per unit time.
exp.(vec(chain[@varname(modifier_2.import_rates)]))Inserting or reordering modifiers renames it.
Nothing clamps the incidence afterwards: the renewal recursion stays positive because transformation's job. Passing transformation = identity hands that job to you, and a rate that goes negative then subtracts from the incidence.
Arguments
importation_rate: the unconstrained importation-rate prior — aDistribution, aVector{<:Distribution}, or any prior/latent process.
Keyword Arguments
transformation: the map from the unconstrained rate onto the positive importation rate (defaultexp).
Examples
Drawing the modifier through its pre-scan seam shows the rate the scan will add. A bare Distribution gives one constant, here with median
using ComposableTuringIDModels, Distributions, Random
Random.seed!(189)
as_turing_model(ImportedCases(Normal(-1.0, 0.5)), 5)().rateA latent process gives a path, positive at every time despite the walk itself being unconstrained:
Random.seed!(189)
as_turing_model(ImportedCases(RandomWalk()), 5)().rateEither goes onto a renewal process as a positional modifier, where the rate joins the model's parameters under the modifier's position:
r = Renewal([0.2, 0.3, 0.5], ImportedCases(Normal(-1.0, 0.5));
rt = FixedIntercept(0.0), initialisation = Normal())
keys(rand(as_turing_model(r, 5)))Fields
importation_rate: the unconstrained importation-rate prior.transformation: the map onto the positive importation rate.
ComposableTuringIDModels.Intercept Type
Broadcast a single sampled intercept value to a length-n latent process.
The field intercept sets the prior the intercept is drawn from — a Distribution, drawn with a native tilde (a single scalar draw broadcast to length n).
Examples
using ComposableTuringIDModels, Distributions
int = Intercept(Normal(0, 1))
mdl = as_turing_model(int, 10)
rand(mdl)ComposableTuringIDModels.LatentDelay Type
Apply a reporting delay to an underlying observation model.
The expected observations are convolved with the (reversed) delay PMF before being passed to the wrapped model. LatentDelay shortens the expected observation vector by the length of the delay PMF to avoid fitting to partially observed data.
The delay composes through the same constant-vs-process seam as every other parameter, so it can be fixed (a known PMF, or a continuous distribution discretised once at construction), uncertain (an UncertainDelay whose distribution's parameters are prior slots, sampled and rediscretised per draw so the delay is inferred), or time-varying (a delay that changes with time). A time-varying delay is either a deterministic per-time sequence of PMFs or an UncertainDelay with a process-valued parameter; it is applied with a time-indexed convolution (one reversed kernel per step). The uncertain and process-valued cases thread their parameters through the same as_turing_submodel seam every other component uses, so a prior on the delay composes exactly like a prior anywhere else in the model.
Constructors
LatentDelay(model, pmf)— from a fixed delay PMF (non-negative, sums to 1).LatentDelay(model, distribution; D, Δd)— discretise a fixed continuous delay distribution once via double-interval censoring (CensoredDistributions.jl).Dis the truncation horizon; if omitted anddistributionhas finite support (e.g. it was built withtruncated), the support's upper bound is used asDdirectly, so a caller who has already truncated the distribution does not need to repeat the horizon as a separate keyword —truncated(dist, lower, upper)is enough.LatentDelay(model, pmfs::AbstractVector{<:AbstractVector})— a deterministic time-varying delay from a per-time sequence of PMFs (one per time point, all the same length, each non-negative and summing to 1).LatentDelay(model, delay::UncertainDelay)— an inferred delay whose distribution parameters carry priors; time-invariant when the parameters areDistributions and time-varying when any is a process (seeUncertainDelay).
Fields
model: the wrapped observation model the delayed expected observations are passed to.delay: the delay specification — the reversed fixed delay PMF (a vector), a per-time sequence of PMFs, or anUncertainDelaycomponent that samples the delay parameters and builds the PMF(s) per draw.
Examples
A fixed delay:
using ComposableTuringIDModels, Distributions
obs = LatentDelay(NegativeBinomialError(), truncated(Normal(5.0, 2.0), 0.0, Inf))
mdl = as_turing_model(obs, missing, fill(10, 30))
mdl()A fixed delay with an explicit horizon expressed on the distribution itself (truncating above at 15, rather than passing D = 15.0 separately):
bounded = LatentDelay(
NegativeBinomialError(), truncated(Normal(5.0, 2.0), 0.0, 15.0)
)
length(bounded.delay)A deterministic time-varying delay — a PMF per time point (here sharpening over time), all the same length:
n = 30
pmfs = [(w = [0.6 - 0.01t, 0.3, 0.1 + 0.01t]; w ./ sum(w)) for t in 1:n]
tv = LatentDelay(PoissonError(), pmfs)
as_turing_model(tv, missing, fill(100.0, n))().y_tComposableTuringIDModels.MA Type
A moving-average MA(q) latent process.
with coefficients θ and innovations from the error model ϵ_t. The order q is the length of the coefficient prior.
ϵ_t is a length-n path slot: a process gives time-varying innovations, while a bare Distribution is auto-wrapped in an Intercept, giving a constant innovation path (one shared draw broadcast to every step). Use IID for n independent innovations.
At order 1 the θ slot decides whether the coefficient is constant or time-varying, through the same single-seam mechanism as AR's damping: MA(θ = Normal(...)) is a constant coefficient (one scalar RV) while MA(θ = RandomWalk()) threads a per-step coefficient path. Higher-order (q > 1) coefficients are constant.
Examples
using ComposableTuringIDModels, Distributions
ma = MA()
mdl = as_turing_model(ma, 10)
rand(mdl)KernelFunctions.Matern32Kernel Type
Matern32Kernel(; metric=Euclidean())Matérn kernel of order metric.
Definition
For inputs
By default,
See also: MaternKernel
KernelFunctions.Matern52Kernel Type
Matern52Kernel(; metric=Euclidean())Matérn kernel of order metric.
Definition
For inputs
By default,
See also: MaternKernel
ComposableTuringIDModels.MissingObservations Type
A partially-missing observation vector split into a concrete value vector and a presence mask, so it carries no Missing in its type.
value[i] is the observed entry at i when present[i] is true, and an unused placeholder otherwise. Defined here (rather than in compose.jl, where concrete_observations builds one) so that it loads before the observation-error models that score one directly, further down the include order.
Examples
using ComposableTuringIDModels: MissingObservations
carrier = MissingObservations([1.0, 0.0, 3.0], [true, false, true])
carrier.value[carrier.present]ComposableTuringIDModels.MixingStep Type
A renewal core whose coupling operator is drawn before the scan.
This is to a coupling operator what ImportedCases is to an importation rate. A scan step is a deterministic function, so an operator built from sampled parameters cannot be assembled inside the recursion. MixingStep holds the generation interval and an AbstractMixingModel. Its as_turing_model seam draws the model's parameters and hands back a ConstantRenewalStep carrying the realised operator.
It is generic over every mixing model, so a new movement model needs only its own as_turing_model returning a matrix.
Fields
rev_gen_int: the reversed generation interval.mixing: the mixing model drawn before the scan.
ComposableTuringIDModels.ModelShape Type
The shape a shape-aware component's n argument can take.
n::Int asks for a length-n path; n::Dims{2} asks for an (n_strata, n_time) matrix, which is size(I_t) for a stratified infection process. A bare Distribution is the one exception to the contract: it ignores whichever shape it is asked for and draws a single scalar.
Examples
using ComposableTuringIDModels
ModelShapeComposableTuringIDModels.NUTSampler Type
NUTS sampling method for a DynamicPPL.Model.
Fields
target_acceptance: target acceptance rate.adtype: automatic-differentiation backend.mcmc_parallel: MCMC parallelisation strategy.nchains: number of chains.max_depth: NUTS tree-depth limit.Δ_max: divergence threshold.init_ϵ: initial step size (0.0lets NUTS find one).ndraws: total draws.metricT: HMC metric type.nadapts: adaptation steps (-1uses the Turing default).
ComposableTuringIDModels.NegativeBinomialError Type
A negative-binomial observation-error model with an inferred cluster factor.
The field cluster_factor sets the prior for the cluster factor — a Distribution (a constant, one scalar RV) or a process (a length-n, e.g. time-varying, overdispersion). It is drawn through the single as_turing_submodel seam and read per time point via at, so a process makes the overdispersion time-varying with no other change.
Examples
using ComposableTuringIDModels, Distributions
nb = NegativeBinomialError()
mdl = as_turing_model(nb, missing, fill(10, 10))
rand(mdl)ComposableTuringIDModels.NegativeBinomialMeanClust Function
Construct a SafeNegativeBinomial from a mean μ and cluster factor α using the variance relationship
Arguments
μ: the mean of the distribution.α: the cluster factor relating mean and variance.
Examples
using ComposableTuringIDModels
NegativeBinomialMeanClust(10.0, 0.1)ComposableTuringIDModels.NormalError Type
A normal (Gaussian) observation-error model with an inferred standard deviation.
Unlike PoissonError and NegativeBinomialError, which model count observations, NormalError models continuous observations: each observed value is normally distributed about its expected value,
with the standard deviation std. It is the minimal non-count observation error, useful for already-aggregated or transformed quantities (e.g. log-incidence, prevalence proportions, wastewater concentrations) where a Gaussian likelihood is appropriate.
The field std sets the prior for Distribution (a constant, one scalar RV) or a process (a length-n, e.g. time-varying, standard deviation). It is drawn through the single as_turing_submodel seam and read per time point via at, so a process makes the observation noise time-varying with no other change.
Examples
using ComposableTuringIDModels, Distributions
ne = NormalError()
mdl = as_turing_model(ne, missing, fill(10.0, 10))
rand(mdl)ComposableTuringIDModels.Null Type
A null latent model that generates nothing (no latent variables).
Examples
using ComposableTuringIDModels
null = Null()
mdl = as_turing_model(null, 10)
isnothing(mdl())
# output
trueComposableTuringIDModels.ODEProcess Type
An infection process defined by solving an ODE.
ODEProcess combines a parameter struct (params, e.g. SIRParams or SEIRParams, whose as_turing_model samples (u0, p)) with a solver, extra solver_options, and a sol2infs link mapping the ODE solution to a latent-infection series. The compartmental dynamics are fully determined by the sampled ODE parameters, so the model carries no separate latent as_turing_model samples the parameters, solves the ODE, and returns (; I_t, Z_t) with Z_t = nothing (no exposable latent path).
Arguments
infection: theODEProcess.n: the requested series length; passed through to the parameter model (the ODE dimension is fixed, sonis otherwise unused —nothingis also accepted).
Examples
using ComposableTuringIDModels, OrdinaryDiffEq, Distributions, LogExpFunctions
sirparams = SIRParams(
tspan = (0.0, 100.0),
infectiousness = LogNormal(log(0.3), 0.05),
recovery_rate = LogNormal(log(0.1), 0.05),
initial_prop_infected = Beta(1, 99))
N = 1000.0
sir_process = ODEProcess(
params = sirparams,
sol2infs = sol -> softplus.(N .* sol[2, :]),
solver_options = Dict(:saveat => 1.0))
as_turing_model(sir_process, nothing)()Fields
params: the ODE parameter model (anAbstractLatentModel, e.g.SIRParams/SEIRParams, whoseas_turing_modelsamples(u0, p)).solver: the ODE solver (defaultAutoVern7(Rodas5P())).sol2infs: link mapping the ODE solution to an infection series.solver_options: extra options passed tosolve(aDictorNamedTuple).
ComposableTuringIDModels.PoissonError Type
A Poisson observation-error model.
Examples
using ComposableTuringIDModels
poi = PoissonError()
mdl = as_turing_model(poi, missing, fill(10, 10))
rand(mdl)
nothing
# outputComposableTuringIDModels.PrefixLatentModel Type
Wrap an inner latent model so its sampled variables are prefixed with prefix.
The inner model is prefixed with DynamicPPL.prefix before being sampled as a submodel, so its variables appear as prefix.varname.
Arguments
model: the inner latent model.n: the shape to generate — a length or an(n_strata, n_time)shape, whatever the inner model accepts.
Examples
using ComposableTuringIDModels
pm = PrefixLatentModel(; model = HierarchicalNormal(), prefix = "Test")
rand(as_turing_model(pm, 10))The model slot takes a raw component: a latent model, or a Distribution (or a vector of them).
Fields
model: the latent model to prefix.prefix: the string prefix applied to the inner model's variables.
ComposableTuringIDModels.PrefixObservationModel Type
Wrap an inner observation model so its sampled variables are prefixed with prefix.
The inner model is prefixed with DynamicPPL.prefix before being sampled as a submodel, so its variables appear as prefix.varname.
Arguments
observation_model: thePrefixObservationModel.y_t: the observed series (ormissingwhen simulating predictively).Y_t: the expected-observation series.
Examples
using ComposableTuringIDModels
pm = PrefixObservationModel(; model = PoissonError(), prefix = "Test")
mdl = as_turing_model(pm, missing, fill(10.0, 5))
rand(mdl)Fields
model: the inner observation model to prefix.prefix: the string prefix applied to the inner model's variables.
ComposableTuringIDModels.R_to_r Function
Approximate the exponential growth rate r implied by a reproduction number R₀ and discrete generation interval w.
Solves r initial guess refined with newton_steps Newton iterations.
Arguments
R₀: the reproduction number.w: the discrete generation interval weights (or aRenewalmodel, whose generation interval is used).
Keyword Arguments
newton_steps: number of Newton refinement steps (default2).Δd: generation-interval discretisation width (default1.0).
Examples
using ComposableTuringIDModels
R_to_r(1.5, [0.2, 0.3, 0.5])ComposableTuringIDModels.RandomWalk Type
Model the latent process
where init and the increments ϵ_t (a HierarchicalNormal by default, giving an inferred step standard deviation).
The init slot takes a raw prior: pass a bare Distribution, or a richer prior model. It is sampled through as_turing_submodel.
ϵ_t is a length-n PATH slot: a process gives time-varying increments, while a bare Distribution is auto-wrapped in an Intercept, giving a constant increment path (one shared draw broadcast to every step). Use IID for n independent increments.
Examples
using ComposableTuringIDModels, Distributions
rw = RandomWalk()
mdl = as_turing_model(rw, 10)
rand(mdl)ComposableTuringIDModels.RecordExpectedLatent Type
Record the inner latent vector as a tracked generated quantity (exp_latent).
Arguments
model: the inner latent model whose output is recorded.n: the shape to generate — a length or an(n_strata, n_time)shape, whatever the inner model accepts.
Examples
using ComposableTuringIDModels
rm = RecordExpectedLatent(FixedIntercept(0.1))
rand(as_turing_model(rm, 1))The model slot is a length-n PATH slot: a bare Distribution there is auto-wrapped in an Intercept, giving a constant inner path; a process, an IID, or a vector passes through. Use IID for n independent draws. It is composed through as_turing_submodel.
Fields
model: the latent model whose expected latent vector is recorded.
ComposableTuringIDModels.RecordExpectedObs Type
Record the expected observations as a tracked generated quantity (exp_y_t).
The expected observations Y_t are tracked via the := syntax before the inner model is applied unchanged, so the expected observations are available in the returned chain alongside the inner model's variables.
Arguments
model: theRecordExpectedObsmodel.y_t: the observed series (ormissingwhen simulating predictively).Y_t: the expected-observation series.
Examples
using ComposableTuringIDModels
obs = RecordExpectedObs(PoissonError())
mdl = as_turing_model(obs, missing, fill(10.0, 5))
rand(mdl)Fields
model: the inner observation model whose expected observations are recorded.
ComposableTuringIDModels.Renewal Type
Model unobserved infections via a time-varying renewal process driven by an internally generated (log) reproduction number.
where the latent model rt supplies the (log) reproduction number transformation, as_turing_model takes a ModelShape n and returns the named tuple (; I_t, Z_t) with Z_t the (log)
Renewal is the one infection model that needs a generation interval, so it takes one directly through the generation_time keyword, which dispatches on the value: a discrete probability vector is used as-is, a continuous Distribution is discretised internally (see the constructor), and a pmf-producing prior model (e.g. an UncertainDelay) lets the generation interval itself be inferred — its distribution's parameters carry priors and the interval is rediscretised per draw through the as_turing_submodel seam.
Renewal is a step-composing helper: positional AbstractRenewalModifier arguments are composed onto the renewal RenewalStep. Passing a SusceptibleDepletion(N) gives a renewal process with a fixed population
Modifiers apply in the order given, and a modifier carrying priors (e.g. an ImportedCases importation rate) draws them once before the scan, so composing one takes no extra wiring: Renewal(gen_int, SusceptibleDepletion(N), ImportedCases(Normal(-1, 0.5))).
Strata and coupling
Handing rt a Stratify (or Replicate) and calling as_turing_model(renewal, (n_strata, n_time)) runs one renewal recursion per stratum. R_t is a n_strata × n_time matrix, each stratum has its own seed (from a vector-valued initialisation, or the same scalar seed broadcast to every stratum), and each stratum's incidence window advances on its own. The mixing keyword couples the strata by transforming the incidence window each stratum's force of infection is convolved against — see renewal_pressure for the extension point. It defaults to I (LinearAlgebra.I), leaving strata uncoupled: a Dims{2} shape with no mixing is n_strata independent single-series renewal processes sharing one model.
The generation interval, when inferred, is drawn at the shape the renewal needs: one pmf for n::Int, one pmf per stratum for n::Dims{2}. Because each parameter of an UncertainDelay is itself a prior slot, giving one a Hierarchy partially pools that parameter across strata. A partially pooled generation interval therefore needs no new component, e.g. UncertainDelay(LogNormal, [Hierarchy(), σ_prior]; D = 14.0) drawn at n_strata.
Fields
gen_int: the discrete generation interval vector (non-negative, sums to 1), or, for an inferred generation interval, the pmf-producing prior model.transformation: the transformation between the unconstrained and constrained domains (defaultexp).rt: the latent process model (anAbstractLatentModel) generating the (log) reproduction number. A length-nPATH slot: a bareDistributionhere is auto-wrapped in anIntercept, giving a constant path (one shared draw broadcast to lengthn); useIIDfornindependent draws, orStratify/Replicatefor a strata axis.initialisation: prior for the unconstrained initial infections (aDistributionor prior model, sampled throughas_turing_submodel).recurrent_step: the renewal accumulation step (anAbstractConstantRenewalStep), ornothingwhen the generation interval is inferred and the step is built per draw.mixing: the coupling operator applied to the incidence window beforeR_t(defaultI, uncoupled); seerenewal_pressure.
Constructor
Renewal(; generation_time, rt, initialisation, transformation = exp, mixing = I, D_gen = nothing, Δd = 1.0)— one keyword constructor that dispatches ongeneration_time:a discrete probability vector (non-negative, sums to 1) is used directly as the generation interval;
a continuous
Distributionis discretised via double-interval censoring (CensoredDistributions.jl), usingD_gen/Δd, with the delay-0 bin dropped and the remainder renormalised; anda pmf-producing prior model (an
AbstractPriorModelsuch as anUncertainDelay) is held as-is and sampled per draw, giving an inferred generation interval (an uncertain discretised distribution used as the generation interval). Its fixed horizon keeps the interval length constant across draws; the lag-0 bin is dropped and the remainder renormalised per draw, exactly as for the fixed distribution.
Examples
A fixed generation interval:
using ComposableTuringIDModels, Distributions
renewal = Renewal(; generation_time = [0.2, 0.3, 0.5], rt = RandomWalk(),
initialisation = Normal())
rand(as_turing_model(renewal, 20))An inferred generation interval — an uncertain discretised distribution used as the generation interval, whose LogNormal parameters carry priors:
gen = UncertainDelay(
LogNormal, [Normal(1.9, 0.2), truncated(Normal(0.5, 0.2), 0, Inf)]; D = 14.0)
renewal = Renewal(; generation_time = gen, rt = RandomWalk(),
initialisation = Normal())
rand(as_turing_model(renewal, 20))
# With a fixed population and susceptible depletion.
depleting = Renewal([0.2, 0.3, 0.5], SusceptibleDepletion(1000.0);
rt = RandomWalk(), initialisation = Normal())
rand(as_turing_model(depleting, 20))A stratified renewal: one recursion per stratum, sharing a random walk in R_t-space with partially pooled per-stratum deviations, left uncoupled (mixing defaults to I):
strat = Renewal(; generation_time = [0.2, 0.3, 0.5],
rt = Stratify(RandomWalk(), Hierarchy()), initialisation = Normal())
size(as_turing_model(strat, (3, 20))().I_t)ComposableTuringIDModels.RenewalStep Type
The renewal accumulation step: a force-of-infection core (a constant generation interval by default) with a tuple of modifiers (AbstractRenewalModifiers) composing on top, sharing one incidence window.
With no modifiers it is a plain renewal recurrence. With modifiers its state is (; val, window, substates) — the newest incidence, the shared window, and one substate per modifier; each step computes the core force of infection, threads it through the modifiers (each transforming the incidence and updating its own substate), then advances the shared window once with the final incidence.
RenewalStep(core, (SusceptibleDepletion(N),)) is a renewal process with a fixed population N and susceptible depletion; a Renewal built with a SusceptibleDepletion(N) modifier uses exactly this step.
ComposableTuringIDModels.RepeatBlock Type
Broadcast rule that repeats the latent process in blocks of length period (e.g. a piecewise-constant weekly process).
Examples
using ComposableTuringIDModels
broadcast_rule(RepeatBlock(), [1, 2, 3, 4, 5], 10, 2)ComposableTuringIDModels.RepeatEach Type
Broadcast rule that repeats the latent process at each position within a period (e.g. a fixed day-of-week effect).
Examples
using ComposableTuringIDModels
broadcast_rule(RepeatEach(), [1, 2], 10, 2)ComposableTuringIDModels.Replicate Type
Draw n_strata independent copies of a path model, one per stratum.
Built with as_turing_model(m, (n_strata, n_time)), Replicate draws model n_strata times over the time axis, each draw prefixed by its stratum index so the variables of stratum g never collide with stratum h, and stacks the results into a n_strata × n_time matrix. There is no cross-stratum relationship: every stratum's path is independent of every other's.
As with Stratify, the stratum count is not a field of the struct: it arrives through the shape argument, so one Replicate serves any panel width.
Two uses:
on its own, in a strata slot, e.g.
rt = Replicate(RandomWalk())givesn_stratafully independentR_tpaths with no shared structure at all;in
Stratify'sacrossslot, e.g.Stratify(RandomWalk(), Replicate(RandomWalk()))gives per-stratum deviations that vary in time rather than a single constant per-stratum offset — seeacross_shape, whichReplicateoverrides to draw the full(n_strata, n_time)shape.
The model slot takes a raw prior (a bare Distribution, or a latent/prior model), sampled through as_turing_submodel; a bare Distribution is auto-wrapped in an Intercept (a constant path), the same PATH convention used elsewhere (e.g. RandomWalk's ϵ_t).
Fields
model: the path model drawn independently for each stratum.
Examples
using ComposableTuringIDModels, Distributions
# 3 fully independent random-walk paths over a 20-step time axis; the strata
# count arrives through the shape, not a field.
rep = Replicate(RandomWalk())
size(as_turing_model(rep, (3, 20))())ComposableTuringIDModels.ReportTriangle Type
A 2D reporting-triangle observation model (epinowcast-style nowcasting), the joint counterpart to the RightTruncate marginal.
ReportTriangle consumes the expected eventual totals Y_t = μ_t (per reference day, the same quantity the infection pipeline produces) together with a reporting-delay PMF p drawn from a submodel, expands them to per-cell expected means μ_{t,d} = μ_t · p[d + 1], and scores only the observed cells of a ReportingTriangle (t + d ≤ now) under a per-cell count error model. The not-yet-reported cells are never sampled. Because the model's Y_t stays the eventual total, the nowcast of the eventual total is just Y_t read out as a generated quantity (and the completed triangle is μ_{t,d} over all d).
The delay PMF is supplied as a composable submodel delay_model — mirroring how RightTruncate takes a ReportingCDF — sampled with to_submodel(..., false) inside the model. The default ReportingPMF wraps a fixed PMF (the fixed-delay, independent-cell variant: each observed cell is an independent Poisson / negative-binomial draw about its mean, via the per-cell AbstractObservationErrorModel supplied as error_model). Because the delay is a submodel, an estimated / time-varying delay — and the multinomial-split parameterisation — are the seams this grows from (a later phase of the nowcasting design).
The default PMF is built with the same released-CD double_interval_censored + pdf discretisation path that LatentDelay uses, so the triangle's per-cell means and the right-truncation nowcast share one delay kernel.
Constructors
ReportTriangle(error_model, delay_model)— from a delay submodel producing the PMF (e.g. aReportingPMF);Dmaxis read from it.ReportTriangle(error_model, pmf::AbstractVector)— wrap a precomputed delay PMF (non-negative, sums to 1) in the defaultReportingPMF.ReportTriangle(error_model, distribution; D, Δd)— discretise a continuous reporting-delay distribution via double-interval censoring (CensoredDistributions.jl) into aReportingPMF, exactly asLatentDelay.
The y_t data contract
The observation data is a ReportingTriangle, built through the shared define_y_t hook from either a matrix or a long-form table:
y_t = define_y_t(obs, N, Y_t) # from a count matrix
y_t = define_y_t(obs, reports, Y_t; now = now) # from (reference, delay, count) rowsPass y_t = missing to simulate: ReportTriangle builds a fully observed triangle (now = n + Dmax) of missing cells and fills them predictively.
Fields
error_model: the per-cell count-error model (e.g.PoissonError,NegativeBinomialError).delay_model: the delay submodel producing the reporting-delay PMFp(delays0 … Dmax); cell(t, d)has expected meanY_t[t] · p[d + 1]. The defaultReportingPMFholds a fixed PMF.
Examples
using ComposableTuringIDModels, Distributions
obs = ReportTriangle(PoissonError(), truncated(Normal(2.0, 1.0), 0.0, Inf))
# Simulate a triangle for 15 reference days of expected total 50.
sim = as_turing_model(obs, missing, fill(50.0, 15))()
sim.observedComposableTuringIDModels.ReportingCDF Type
A composable reporting-completeness component: a Distribution (or a precomputed vector) turned into the cumulative reporting proportion by age, for use as the correction submodel of RightTruncate.
Given a series length n, sampling as_turing_model(c::ReportingCDF, n) returns a length-n vector F where F[a + 1] is the fraction of a reference day's eventual total reported within a days (a = 0, 1, …, n - 1), in [0, 1]. Reference days older than the reporting delay's support are fully reported (F = 1), so the vector is padded with ones to length n. The CDF built from a delay distribution is non-decreasing, but ReportingCDF does not require monotonicity — a precomputed curve may be non-monotonic, so an over-/under-reporting correction that recovers can be expressed.
It is the fixed-delay default used by RightTruncate: the completeness is precomputed once and held constant. Because the correction is supplied to RightTruncate as a submodel, a user can instead pass any latent component that produces a length-n completeness curve — a flexible non-parametric CDF, or even a non-monotonic correction — without changing RightTruncate.
Constructors
ReportingCDF(distribution; D, Δd)— discretise a continuous reporting-delay distribution via double-interval censoring (CensoredDistributions.jl) and take the cumulative sum of the resulting PMF, exactly the released-CD pathLatentDelayuses.ReportingCDF(cdf)— from a precomputed completeness vector by age (in[0, 1]; need not be monotonic).
Examples
using ComposableTuringIDModels, Distributions
c = ReportingCDF(truncated(Normal(5.0, 2.0), 0.0, Inf))
as_turing_model(c, 10)()Fields
cdf: the reporting-completeness curve by age (cdf[a + 1]), in[0, 1](need not be monotonic). Padded with ones up to the requested length when shorter.
ComposableTuringIDModels.ReportingPMF Type
A composable reporting-delay PMF component: a Distribution (or a precomputed vector) turned into the reporting-delay PMF p (delays 0 … Dmax), for use as the delay submodel of ReportTriangle.
Sampling as_turing_model(c::ReportingPMF, n) returns the length-(Dmax + 1) PMF (the argument n is the role-interface series length and is ignored — the PMF is indexed by delay, not reference day). The PMF is non-negative and sums to one; Dmax = length(pmf) - 1.
It is the fixed-delay default used by ReportTriangle: the PMF is precomputed once and held constant. Because the delay is supplied to ReportTriangle as a submodel — mirroring how RightTruncate takes a ReportingCDF — a user can instead pass any latent component producing the delay PMF, the seam an estimated / time-varying delay grows from.
Constructors
ReportingPMF(distribution; D, Δd)— discretise a continuous reporting-delay distribution via double-interval censoring (CensoredDistributions.jl), exactly the released-CD pathLatentDelayuses.ReportingPMF(pmf)— from a precomputed delay PMF (non-negative, sums to 1).
Examples
using ComposableTuringIDModels, Distributions
c = ReportingPMF(truncated(Normal(2.0, 1.0), 0.0, Inf))
as_turing_model(c, 10)()Fields
pmf: the reporting-delay PMFp(delays0 … Dmax, non-negative, sums to 1);Dmax = length(pmf) - 1.
ComposableTuringIDModels.ReportingTriangle Type
A reporting triangle: the reference-date × reporting-delay count matrix with the not-yet-reported cells masked off.
counts[t, d + 1] is the number of events with reference day t first reported at delay d = 0, 1, …, Dmax. A cell is observed at the present time now iff t + d ≤ now; the remaining lower-right cells have not yet been reported and are masked out by observed. This is the native object of epinowcast-style nowcasting: it keeps the full joint reference-day × delay structure rather than collapsing it to a per-reference-day observed-so-far total (the marginal that RightTruncate conditions on — see the consistency note below).
Build one with define_y_t from either a dense matrix or a long-form table of (reference, delay, count) rows; pass it as the y_t data to a ReportTriangle observation model.
Consistency with right-truncation (CDF-scaling)
Summing the observed cells of reference day t (delays d = 0 … now − t) gives Σ_d counts[t, d+1], the observed-so-far total — which in expectation is μ_t · F[(now − t) + 1], exactly the CDF-scaled expected observed-so-far that RightTruncate conditions on. So the observed row-sums of the triangle are the marginal of the joint model; the triangle additionally models how that total is split across delays.
Fields
counts: theN[t, d+1]count matrix (reference day × delay). Unobserved cells are ignored byReportTriangle; they may holdmissing,0, or any placeholder.observed: the boolean mask of reported cells (t + d ≤ now).Dmax: the maximum reporting delay (the number of delay columns isDmax + 1, delays0 … Dmax).
Examples
using ComposableTuringIDModels
# A 4 × 3 matrix (reference days 1..4, delays 0..2); now = 4.
N = [10 5 2; 12 6 3; 14 7 4; 16 8 5]
rt = define_y_t(ReportTriangle(PoissonError(), [0.5, 0.3, 0.2]), N, fill(20.0, 4))
rt.observedComposableTuringIDModels.RightTruncate Type
Correct an underlying observation model for right-truncation (not-yet-reported counts), the EpiNow2-style CDF-scaling nowcast.
The infection → expected-observation pipeline produces t. At the present time now (taken to be the last reference day, now = n) a reference day of age Y_t remains the eventual total (so the nowcast of the eventual total is just Y_t read out as a generated quantity).
The modifier mirrors Ascertainment: it wraps an inner observation model, draws a length-n correction series from a submodel, transforms the expected-observation vector, and delegates via to_submodel(..., false). The correction cdf_model is any component producing a length-n completeness curve F (by age). The default is a fixed ReportingCDF built from a reporting delay distribution (the released-CD case), but because the correction is a submodel a user can supply a flexible non-parametric CDF — or even a non-monotonic correction — without changing RightTruncate.
The completeness is indexed by age: the most recent reference day (t = n, age 0) is scaled by F[1] and the oldest (t = 1, age n - 1) by F[n], so the age-indexed series is reversed onto the reference-day axis. A fully-reported correction (all ones) leaves the inner model unchanged.
This is the fixed-delay variant. An estimated / time-varying delay (a latent correction with sampled parameters) is a planned follow-up, and is exactly the submodel slot generalising to it.
Constructors
RightTruncate(model, cdf_model)— from an inner observation model and a correction component (a latent-role submodel producing the length-ncompleteness curve, e.g. aReportingCDF).RightTruncate(model, distribution; D, Δd)— wrap a continuous reporting-delay distribution in the defaultReportingCDF(the released-CD path).RightTruncate(model, cdf::AbstractVector)— wrap a precomputed completeness vector in a fixedReportingCDF.
Arguments
obs_model: theRightTruncatemodel.y_t: the observed-so-far series (ormissingwhen simulating predictively).Y_t: the expected eventual-total series.
Examples
using ComposableTuringIDModels, Distributions
obs = RightTruncate(NegativeBinomialError(), truncated(Normal(5.0, 2.0), 0.0, Inf))
mdl = as_turing_model(obs, missing, fill(100.0, 30))
rand(mdl)Fields
model: the inner observation-error model the corrected expected observations are passed to.cdf_model: the correction submodel producing the length-nreporting completeness curve (by age).
ComposableTuringIDModels.SEIRParams Type
SEIR compartmental model parameters and priors, usable as the latent component of an ODEProcess.
The sampled initial infected proportion is split between the exposed and infectious compartments using the constant-incidence equilibrium proportions
Declarative alternative
This model's vector field and Jacobian are hand-coded. To build a new or custom compartmental network without hand-deriving a Jacobian, use CatalystODEParams: it reads any Catalyst reaction network and generates the ODE system and Jacobian symbolically (an opt-in extension).
Arguments
params: theSEIRParamsstruct.n: unusedIntsize argument; accepted for the commonas_turing_modelsignature.
Keyword Arguments
tspan: the ODE solution time span.infectiousness: prior for .incubation_rate: prior for .recovery_rate: prior for .initial_prop_infected: prior for the initial infected proportion.
Examples
using ComposableTuringIDModels, OrdinaryDiffEq, Distributions
seirparams = SEIRParams(
tspan = (0.0, 30.0),
infectiousness = LogNormal(log(0.3), 0.05),
incubation_rate = LogNormal(log(0.1), 0.05),
recovery_rate = LogNormal(log(0.1), 0.05),
initial_prop_infected = Beta(1, 99))
rand(as_turing_model(seirparams, 0))Fields
prob: theODEProbleminstance for the SEIR model.infectiousness: prior for .incubation_rate: prior for .recovery_rate: prior for .initial_prop_infected: prior for the initial infected proportion.
ComposableTuringIDModels.SIRParams Type
SIR compartmental model parameters and priors, usable as the latent component of an ODEProcess.
Declarative alternative
This model's vector field and Jacobian are hand-coded. To build a new or custom compartmental network without hand-deriving a Jacobian, use CatalystODEParams: it reads any Catalyst reaction network and generates the ODE system and Jacobian symbolically (an opt-in extension).
Arguments
params: theSIRParamsstruct.n: unusedIntsize argument (the ODE dimension is fixed); accepted for the commonas_turing_modelsignature.
Keyword Arguments
tspan: the ODE solution time span.infectiousness: prior for .recovery_rate: prior for .initial_prop_infected: prior for the initial infected proportion.
Examples
using ComposableTuringIDModels, OrdinaryDiffEq, Distributions
sirparams = SIRParams(
tspan = (0.0, 30.0),
infectiousness = LogNormal(log(0.3), 0.05),
recovery_rate = LogNormal(log(0.1), 0.05),
initial_prop_infected = Beta(1, 99))
rand(as_turing_model(sirparams, 0))Fields
prob: theODEProbleminstance for the SIR model.infectiousness: prior for .recovery_rate: prior for .initial_prop_infected: prior for the initial infected proportion.
ComposableTuringIDModels.SafeNegativeBinomial Type
A negative binomial distribution parameterised by (r, p) that avoids InexactError at very large means.
The package uses a mean/cluster-factor parameterisation when constructing this distribution from an expected count (see NegativeBinomialMeanClust).
Construction does not validate r/p, since these may transiently be out-of-domain values on the automatic-differentiation path (e.g. built from a sampled cluster factor while logpdf is evaluated). rand validates instead, raising a DomainError naming the invalid parameter rather than the opaque sqrt/Gamma error a bad value would otherwise surface as.
Examples
using ComposableTuringIDModels, Distributions
bigμ = exp(48.0)
σ² = bigμ + 0.05 * bigμ^2
p = bigμ / σ²
r = bigμ * p / (1 - p)
d = SafeNegativeBinomial(r, p)
logpdf(d, 100)
nothing
# outputComposableTuringIDModels.SafePoisson Type
A Poisson distribution parameterised by its mean λ that avoids InexactError for very large means.
Construction does not validate λ, since it may transiently be an out-of-domain value on the automatic-differentiation path. rand validates instead, raising a DomainError naming λ.
Examples
using ComposableTuringIDModels, Distributions
d = SafePoisson(exp(48.0))
logpdf(d, 100)
nothing
# outputComposableTuringIDModels.Split Type
Split one expected series into several named observation streams — the single observation-composition construct for parallel, cascade, and data-driven strata composition.
Each stream is a full AbstractObservationModel (a bare error family or a delay / ascertainment / truncation pipeline). Split feeds every stream the expected series arriving at the point where it sits in the pipeline and automatically prefixes each stream's sampled variables with its name (via DynamicPPL.prefix), so no manual prefix layer is needed. The uniform (; y_t, expected) return contract exposes each stream's pre-error expected series so streams can thread on one another.
Composition by placement
Split is itself an observation model consuming an expected series, so where it sits chooses the composition:
Parallel — placed high, on infections: every stream observes the same
(cases and deaths each a delayed, ascertained fraction of the same infections).IDModel(inf, Split((cases = …, deaths = …))).Cascade — placed low, inside a stream's pipeline: the shared upstream layers run first and
Splitbranches on their expected output, so a later stream is observed downstream of an earlier one. For deaths as a delayed fraction of the expected reported cases, share the case delay then split:LatentDelay(Split((cases = leaf, deaths = pipeline)), case_delay).Data-driven strata — built from a single template model (or a set of named streams) plus a weight
map: the infection→observation cardinality is entirely down tomap, covering the same range of cardinalities as above. SeeStrataMapfor the underlying projection.
The threaded quantity is always a stream's expected (pre-error) series, never its realised noisy draw; observing a downstream stream off another's sampled counts is out of scope.
Data contract
y_t is a NamedTuple of observed series keyed by stream name (or missing to simulate). The return value is (; y_t, expected), each a NamedTuple of per-stream series. When Split is nested inside another modifier the incoming missing reaches it as a shared placeholder; the explicit stream names let it still fan out.
In the data-driven strata mode (m.names === nothing), y_t may instead be an AbstractMatrix (one stream per row, "group1", "group2", … as the generated names) — the data contract a multi-stratum IDModel using CombineInfections on the infection side uses.
The incoming expected series may be a single vector (broadcast to every stream), a per-stream NamedTuple, an inf_strata × time matrix (one stream per row), or a StrataMap.
Constructors
Split(streams::NamedTuple)— explicit named streams, one-to-one with the incoming expected series.Split(streams::NamedTuple, map::AbstractMatrix)— explicit named streams fed by anobs_strata × inf_strataweightmap, so named streams can still express a many-to-one, many-to-many, or finer/coarser mapping.Split(template::AbstractObservationModel)— a data-driven strata split replicating the template once pery_tentry.Split(template::AbstractObservationModel, map::AbstractMatrix)— a strata split that projects the incoming expected series (aninf_strata × timematrix, or a single vector treated as one infection stratum) onto the observation streams through theobs_strata × inf_strataweightmap. This is the composed-model form of aStrataMap: the strata come from the infection process at run time, soIDModel(infection, Split(template, map))maps infections onto streams end-to-end.
Examples
using ComposableTuringIDModels, Distributions
# Parallel: cases and deaths, each a delayed fraction of the SAME infections.
parallel = Split((
cases = LatentDelay(NegativeBinomialError(), [0.4, 0.3, 0.2, 0.1]),
deaths = LatentDelay(NegativeBinomialError(), [0.1, 0.2, 0.3, 0.4])))
rand(as_turing_model(parallel, (cases = missing, deaths = missing), fill(100.0, 12)))
# Cascade: share the case delay, then split so deaths sit downstream of cases.
cascade = LatentDelay(
Split((
cases = PoissonError(),
deaths = LatentDelay(
Ascertainment(PoissonError(), FixedIntercept(log(0.1))),
[0.2, 0.3, 0.5]))),
[0.5, 0.3, 0.2])
rand(as_turing_model(cascade, (cases = missing, deaths = missing), fill(100.0, 12)))
# Many-to-one: three age strata of infections observed as one hospitalisation
# stream, via a single aggregation row.
hospitalisations = Split((hosp = NegativeBinomialError(),), [1.0 1.0 1.0])
I_t = [10.0 10.0 10.0; 20.0 20.0 20.0; 5.0 5.0 5.0] # 3 strata, 3 time steps
rand(as_turing_model(hospitalisations, (hosp = missing,), I_t))Fields
streams: a NamedTuple of per-stream models, or a single strata template.names: the stream names, ornothingin strata mode (names come from data).map: anobs_strata × inf_strataweight matrix projecting infection strata onto streams, ornothingwhen the incoming expected series is used directly.
KernelFunctions.SqExponentialKernel Type
SqExponentialKernel(; metric=Euclidean())Squared exponential kernel with respect to the metric.
Definition
For inputs
By default,
See also: GammaExponentialKernel
ComposableTuringIDModels.StrataMap Type
A strata mapping supplied as the expected series to a Split: project a multi-stratum expected series onto observation streams through a (possibly weighted) linear map.
strata is an inf_strata × time matrix of per-infection-stratum expected series; map is an obs_strata × inf_strata weight matrix. Stream k receives map is the one mechanism for every infection→observation cardinality:
one-to-one —
map = I(n), or no map at all.many-to-one — one aggregation row, e.g.
[1.0 1.0 1.0]summing three infection strata into a single observation stream.many-to-many — a general weight matrix.
finer or coarser than the infection process — a non-square
map, e.g. splitting one infection stratum across two reporting streams with weights that sum to 1.
The stream count is size(map, 1), read from the data at model-build time.
Fields
strata: theinf_strata × timeexpected series (per infection stratum).map: theobs_strata × inf_strataweight matrix.
Examples
using ComposableTuringIDModels
M = [10.0 10.0 10.0 10.0 10.0; 4.0 4.0 4.0 4.0 4.0]
W = [1.0 0.0; 0.0 1.0; 1.0 1.0] # stratum 1, stratum 2, and their sum
sm = StrataMap(M, W)
size(sm.map, 1) # 3 observation streamsComposableTuringIDModels.Stratify Type
Add a strata axis to a shared latent path.
Stratify(shared, across) draws shared once over the time axis and across over the strata axis, then broadcasts the two together with combine into a n_strata × n_time matrix,
with shared draw and across draw. Built with as_turing_model(m, (n_strata, n_time)), so the stratum count is not a field of the struct: it arrives through the shape argument, exactly the way a series length n is passed to a plain path model. One Stratify therefore serves any panel width.
across is drawn at across_shape(m.across, n), which is n_strata by default: across is a length-n_strata vector, and combine broadcasts it against a 1 × n_time row, (g,) .+ (1, t) ⇒ (g, t) — a constant offset per stratum. An across model that itself spans both axes (e.g. Replicate) overrides across_shape to draw the full (n_strata, n_time) matrix instead, and the same broadcast, (g, t) .+ (1, t) ⇒ (g, t), now gives a time-varying deviation per stratum. One combine line therefore serves both a constant per-stratum offset and a time-varying one, with no branch on which across was supplied.
The across slot is where the pooling choice lives:
Hierarchypartially pools the per-stratum deviations towards a shared level;IIDdraws each deviation independently, giving no pooling;FixedIntercept(0.0)fixes every deviation to zero, giving full pooling — every stratum then sharessharedexactly;a
RandomWalkcorrelates neighbouring strata, useful when the strata are ordered (e.g. adjacent age bands).
Both slots take a raw prior (a bare Distribution, or a latent/prior model), sampled through as_turing_submodel; a bare Distribution is auto-wrapped in an Intercept (a constant path), the same PATH convention used elsewhere (e.g. RandomWalk's ϵ_t).
Fields
shared: the path drawn once over the time axis.across: the cross-stratum relationship generating the per-stratum offsets (or deviations, for anacrossthat spans both axes).combine: howacrossandsharedare broadcast together (default+, a multiplicative effect on a log scale).
Examples
using ComposableTuringIDModels, Distributions
# A shared random walk in R_t-space with partially pooled per-stratum
# deviations; the strata count (3) arrives through the shape, not a field.
strat = Stratify(RandomWalk(), Hierarchy())
size(as_turing_model(strat, (3, 20))())ComposableTuringIDModels.SusceptibleDepletion Type
Susceptible-depletion modifier for RenewalStep.
Scales the proposed incidence by the available susceptible fraction and depletes the susceptible pool,
with population size pop_size. Its substate is the current susceptible count Renewal(gen_int, SusceptibleDepletion(N)).
pop_size is a scalar for one series, and a per-stratum vector for a stratified renewal. Each stratum then depletes its own pool. A scalar given to a stratified renewal is shared by every stratum, so each depletes a separate pool of the same size.
It samples nothing, so it is a plain scan modifier: the pre-scan seam (see AbstractRenewalModifier) returns it unchanged.
Fields
pop_size: the population size, one value or one per stratum.
ComposableTuringIDModels.TransformLatentModel Type
Apply a transformation function to the output of an inner latent model.
Arguments
model: the inner latent model whose output is transformed.n: the shape to generate — a length or an(n_strata, n_time)shape, whatever the inner model accepts.
Examples
using ComposableTuringIDModels, Distributions
trans = TransformLatentModel(Intercept(Normal(2, 0.2)), x -> exp.(x))
rand(as_turing_model(trans, 5))The model slot is a length-n PATH slot: a bare Distribution there is auto-wrapped in an Intercept, giving a constant inner path; a process, an IID, or a vector passes through. Use IID for n independent draws. It is composed through as_turing_submodel.
Fields
model: the latent model to transform.transform: the transformation function applied to the latent vector.
ComposableTuringIDModels.TransformObservationModel Type
Apply a transformation function to the expected observations before passing them to an inner observation model.
The expected observations Y_t are mapped through transform and the result is passed to the inner model. The default transform applies a softplus (x -> log1pexp.(x)), keeping the transformed expected observations positive.
Arguments
obs: theTransformObservationModel.y_t: the observed series (ormissingwhen simulating predictively).Y_t: the expected-observation series.
Examples
using ComposableTuringIDModels
obs = TransformObservationModel(PoissonError(), x -> x .* 2)
mdl = as_turing_model(obs, missing, fill(10.0, 5))
rand(mdl)Fields
model: the inner observation model the transformed expected observations are passed to.transform: the transformation applied to the expected observations.
ComposableTuringIDModels.UncertainDelay Type
A delay whose distribution's parameters are prior slots, so the reporting delay is inferred rather than fixed.
UncertainDelay(family, params; D, Δd) describes a continuous delay distribution family(θ...) whose positional parameters θ are drawn from the priors in params (one prior per parameter). Each prior may be a bare Distribution (the parameter is constant — uncertain but time-invariant) or a process (an AbstractPriorModel, e.g. a RandomWalk, so the parameter is time-varying), through the same constant-vs-process seam every other component uses. Each draw builds the right-truncated, double-interval-censored delay PMF (the same _discretised_pmf path the fixed delay uses):
if every parameter is a
Distribution, one time-invariant PMF is built andas_turing_model(u::UncertainDelay)returns it (drawing the parameters as one slot through theas_turing_submodelseam); andif any parameter is a process,
as_turing_model(u::UncertainDelay, n)builds one PMF per time point — each parameter is read at timetviaat(a constant stays constant, a process path is indexed), so the delay, and its discretised PMF, varies with time. A time-varying delay needs a series length, so the no-nmethod raises an error.
The truncation horizon D is required and fixed: it holds the PMF length constant across draws and across time points (only the parameters vary), which the convolution relies on. Δd is the discretisation bin width.
Constructor
UncertainDelay(family, params; D, Δd = 1.0)—familyis a distribution constructor (e.g.LogNormal,Gamma) called asfamily(θ...), andparamsis a vector of priors (aDistributionor a process), one per positional parameter offamily.
Fields
params: the priors for the delay distribution's positional parameters.family: the distribution constructor built from the sampled parameters.D: the fixed right-truncation horizon (keeps the PMF length constant).Δd: the discretisation bin width.
Examples
An uncertain (time-invariant) delay whose LogNormal parameters carry priors:
using ComposableTuringIDModels, Distributions
delay = UncertainDelay(
LogNormal, [Normal(1.5, 0.4), truncated(Normal(0.4, 0.2), 0, Inf)]; D = 20.0)
obs = LatentDelay(NegativeBinomialError(), delay)
mdl = as_turing_model(obs, missing, fill(100.0, 40))
rand(mdl)A time-varying delay: the LogNormal meanlog is a RandomWalk (it drifts with time) while the sdlog carries a constant prior:
tv = UncertainDelay(
LogNormal, [RandomWalk(), truncated(Normal(0.4, 0.2), 0, Inf)]; D = 20.0)
tv_obs = LatentDelay(NegativeBinomialError(), tv)
as_turing_model(tv_obs, missing, fill(100.0, 40))().y_tComposableTuringIDModels.accumulate_scan Function
Apply an AbstractAccumulationStep across an input sequence in a single pass.
This is an optimised accumulate-based replacement for an explicit for loop. acc_step is a callable step (state, ϵ) -> new_state, initial_state seeds the scan, and ϵ_t is the driving sequence. The returned value is assembled by get_state from the accumulated states.
Arguments
acc_step: anAbstractAccumulationStep, a callable(state, ϵ) -> new_stateapplied at each element of the sequence.initial_state: the seed state passed toaccumulateasinit.ϵ_t: the driving sequence accumulated over.
Examples
using ComposableTuringIDModels
accumulate_scan(ComposableTuringIDModels.RWStep(), 0.0, [1.0, 2.0, 3.0])ComposableTuringIDModels.across_shape Function
The shape Stratify draws its across slot at.
The default method returns n[1], the stratum count alone, so across draws one value per stratum: a per-stratum offset, constant over time. An across model that spans both axes (e.g. Replicate) overrides this method to return the full shape n, so it draws a (n_strata, n_time) matrix instead — a per-stratum deviation that varies over time. This is the extension point: a new across model that needs a shape other than a bare stratum count adds a method here.
Arguments
across: the model in theacrossslot.n: the(n_strata, n_time)shape theStratifyis being built at.
Examples
using ComposableTuringIDModels
ComposableTuringIDModels.across_shape(Hierarchy(), (3, 20))ComposableTuringIDModels.apply_method Function
Condition a model by fixing some parameters and conditioning on others, then run an inference method.
Arguments
idproblem: theIDProblem(or aDynamicPPL.Model).method: the inference method (a sampler, e.g.NUTSampler).data: the data to condition on (with ay_tfield).
Keyword Arguments
fix_parameters: aNamedTupleof parameters to fix.condition_parameters: aNamedTupleof parameters to condition on.kwargs...: forwarded to the inference method.
Examples
using ComposableTuringIDModels, Distributions
problem = IDProblem(
infection = DirectInfections(; Z = RandomWalk(), initialisation = Normal()),
observation_model = PoissonError(),
tspan = (1, 20))
y = rand(as_turing_model(problem, (; y_t = missing)))
nothingComposableTuringIDModels.arima Function
Build an ARIMA(p, d, q) latent process: an arma wrapped in a d-fold DiffLatentModel.
Arguments
ar_init: prior(s) for the AR initial conditions.diff_init: prior(s) for the differencing initial conditions (setsd).damp: prior(s) for the AR damping coefficients.θ: prior(s) for the MA coefficients.ϵ_t: the innovation model (defaultHierarchicalNormal).
Examples
using ComposableTuringIDModels, Distributions
model = arima()
rand(as_turing_model(model, 10))ComposableTuringIDModels.arma Function
Build an ARMA(p, q) latent process: an AR whose innovation model is an MA.
Arguments
init: prior(s) for the AR initial conditions.damp: prior(s) for the AR damping coefficients.θ: prior(s) for the MA coefficients.ϵ_t: the innovation model (defaultHierarchicalNormal).
Examples
using ComposableTuringIDModels, Distributions
model = arma(; θ = [truncated(Normal(0.0, 0.02), -1, 1)],
damp = [truncated(Normal(0.0, 0.02), 0, 1)])
rand(as_turing_model(model, 10))ComposableTuringIDModels.as_turing_model Function
Construct a DynamicPPL.Model from an ComposableTuringIDModels model component.
as_turing_model is the single generic entry point of the package. Every concrete model struct implements exactly one
@model function as_turing_model(m::MyModel, args...; kwargs...)
...
endmethod, and components are composed by sampling submodels of one another through the as_turing_submodel seam:
z ~ as_turing_submodel(inner_model, n)as_turing_submodel disables automatic variable prefixing by default so that parameter names stay flat unless prefixing is explicitly requested (prefix = true).
The fallback method below errors with a clear message when a struct does not yet implement as_turing_model, which keeps the public surface honest.
Arguments
model: anComposableTuringIDModelsmodel component (a subtype ofAbstractComposableModel).args...: positional arguments forwarded to the component's method, such as the series lengthn(latent models) or the expected/observed series (infection and observation models).kwargs...: keyword arguments forwarded to the component's method.
Examples
using ComposableTuringIDModels, Distributions
turing_model = as_turing_model(RandomWalk(), 10)
rand(turing_model)A path model has no strata axis of its own; ask for one explicitly rather than guessing whether a shared path was meant to be pooled across strata.
Every AbstractPriorModel speaks the length-n path contract (as_turing_model(m, n::Int)). This guard rejects a Dims{2} shape for any model that has not opted into a strata axis, so a stratum-shaped call to a plain path model fails at the point it is made rather than returning something silently wrong. Wrap the model in a Stratify (or a Replicate) to give it one.
Examples
using ComposableTuringIDModels, Distributions
try
as_turing_model(RandomWalk(), (3, 10))
catch e
e
endSample a raw prior Distribution as a single scalar RV.
Giving as_turing_model a Distribution method lets a bare distribution flow through as_turing_submodel exactly like a full model, so a component's parameter slot samples θ ~ as_turing_submodel(model.slot, n) uniformly whether the slot holds a bare distribution or a process. A bare distribution draws ONE scalar value (a constant, no length-n allocation) whatever shape it is asked for — n is ignored, whether it is a length or an (n_strata, n_time) shape. A component then reads a possibly-time-varying parameter per step with at, so the scalar stays constant while a process-valued slot varies — this is the single seam behind AR's optionally-time-varying damping and the other per-step parameters.
For n independent draws (a white-noise process) use the explicit IID component; for a single shared value broadcast to length n use Intercept; for per-element priors use a Vector{<:Distribution}.
Arguments
prior: the prior distribution.n: accepted for a uniform seam signature; ignored (the draw is scalar whatever shape is asked for).
Examples
using ComposableTuringIDModels, Distributions
as_turing_model(Normal(), 3)() # a single scalar drawSample a vector of prior Distributions as a length-n prior submodel, one independent draw per element.
The length is fixed by the vector, so n must match it. This is the explicit way a prior slot asks for n independent draws with per-element priors (e.g. an AR's per-lag damping coefficients).
Arguments
prior: the vector of prior distributions.n: the required length (must equallength(prior)).
Examples
using ComposableTuringIDModels, Distributions
as_turing_model([Normal(0, 1), Normal(5, 0.1)], 2)()Resolve an accumulation step ahead of the scan, sampling any parameters its parts carry.
The default method samples nothing and returns the step unchanged. A RenewalStep resolves both its core and its AbstractRenewalModifiers through their own as_turing_model methods and rebuilds itself from the resolved parts. Renewal draws its step through this one seam, so neither a modifier with priors nor a drawn coupling operator (a MixingStep) needs special handling in the infection model.
Each modifier is prefixed by its position in the tuple, so the modifier_<i> (see AbstractRenewalModifier).
Arguments
step: the accumulation step to resolve.n: the series length the scan will run over.
Draw the coupling operator ahead of the scan.
Samples the AbstractMixingModel's parameters through as_turing_submodel and returns the ConstantRenewalStep the scan uses, carrying the realised operator. The operator's variables are namespaced under core.mixing, so a fixed and an inferred coupling differ in the chain and nowhere else.
Arguments
step: theMixingStepto resolve.n: the shape the scan will run over.
Sample the importation rate ahead of the scan.
Draws the unconstrained rate slot through as_turing_submodel — a bare Distribution giving one constant rate, a process giving a length-n path — maps it onto the positive scale with the modifier's transformation, and returns the ImportedRate the scan uses. The map is broadcast, so a constant stays a scalar (no length-n allocation) and the scan reads either shape with at.
Arguments
mod: theImportedCasesmodifier.n: the length of the renewal series.
Generate observations from an observation-error model.
Supports missing observations (y_t === missing, simulating predictively) and expected-observation vectors Y_t shorter than y_t (the expected values are aligned to the last length(Y_t) entries). Expected values are nudged by a tiny constant to avoid degenerate error distributions.
The error family supplies generate_observation_error_priors (sampled as a submodel) and observation_error (the per-time-point distribution).
Returns the uniform (; y_t, expected) tuple: y_t is the observed (or simulated) counts and expected is the pre-error series. Exposing expected lets a Split thread one stream's expectation into another.
Sample a constant-parameter UncertainDelay over an axis, returning the same pmf at every point.
Every parameter is a Distribution (uncertain but constant), so one pmf is drawn — through the no-n method above — and copied across the axis. This is what lets a stratified renewal's generation-interval slot take a fully constant UncertainDelay: drawn at n_strata, every stratum gets the same (uncertain) interval, with no new component. Give one of the parameters a Hierarchy instead for a partially pooled per-stratum interval — that makes the delay time-varying (see the method below) rather than needing this one.
Sample a time-varying UncertainDelay as a length-n sequence of delay pmfs.
Each parameter is drawn through the as_turing_submodel seam: a Distribution parameter draws a scalar (constant across time), while a process parameter (an AbstractPriorModel) draws a length-n path. The pmf at time t is built from each parameter read at t via at, so the delay distribution — and its discretised pmf — varies with time. The fixed horizon D keeps every pmf the same length.
Convenience 2-argument form: read the infection process's shape from the data.
The observation model and the data together fix the shape of the infection process, so nothing about it needs to be supplied explicitly or stored on the model. as_turing_model(model, Y) is as_turing_model(model, Y, shape) with shape resolved via infection_strata: the number of infection strata the observation model consumes given the data's row count, paired with the data's time length.
Three age strata observed as one hospitalisation stream is Split(NegativeBinomialError(), [1.0 1.0 1.0]); a 1 x T data matrix then builds a 3-stratum infection process.
Examples
using ComposableTuringIDModels, Distributions
model = IDModel(
DirectInfections(;
Z = Stratify(RandomWalk(), Hierarchy(; across = IID(Normal(0, 0.5)))),
initialisation = Normal(log(50), 0.2)),
Split(PoissonError(), [1.0 1.0 1.0]))
# One observation stream, 12 time steps: the three infection strata come
# from the weight matrix, not from the data.
Ymiss = Matrix{Union{Missing, Float64}}(missing, 1, 12)
sim = as_turing_model(model, Ymiss)()
size(sim.I_t)ComposableTuringIDModels.as_turing_submodel Function
Compose a component as a Turing submodel: to_submodel(as_turing_model(m, args...), prefix).
This is the single public composition seam of the package. Every composition point — a manipulator wrapping an inner model, an infection model owning its latent process, a component sampling a vector/process-valued prior slot — threads its sub-component through here, and third-party component authors use it as the way to compose an as_turing_model inside their own @model body:
latent ~ as_turing_submodel(inner_model, n)Because as_turing_model also has Distribution and Vector{<:Distribution} methods, the same call composes a raw prior:
damp ~ as_turing_submodel(model.damp, p; prefix = true) # a Distribution or a processprefix defaults to false — the package standard, keeping the submodel's variable names flat. Two kinds of call site pass prefix = true:
a prior slot (a component's
damp/init/θetc.), so the slot's left-hand name namespaces the whole prior submodel and a process-valued prior can never collide with the host's own variables;the deliberately-prefixing components (
PrefixLatentModel,Split), which stream their children under an explicit name.
Arguments
m: the component (or raw prior) to compose.args...: positional arguments forwarded toas_turing_model(e.g. the series lengthn, or the observed/expected series for an observation model).
Keyword Arguments
prefix: whether to prefix the submodel's variables with the tilde left-hand name (defaultfalse).
Examples
Inside a component's @model body it is used on the right of a ~:
latent ~ as_turing_submodel(inner_model, n)
damp ~ as_turing_submodel(model.damp, p; prefix = true)It returns a Turing submodel; the underlying prior submodel returns a length-n value:
using ComposableTuringIDModels, Distributions
length(as_turing_model(Normal(), 4)())ComposableTuringIDModels.ascertainment_dayofweek Function
Build an Ascertainment model for a day-of-week reporting effect.
The latent model is wrapped with broadcast_dayofweek so a 7-day effect is broadcast across the expected-observation series, and combined multiplicatively with the expected observations by default.
Arguments
model: the underlying observation model.
Keyword Arguments
latent_model: the latent model broadcast over the week (defaultHierarchicalNormal()).transform: the function(x, y)combining expected observations with the broadcast effect (default(x, y) -> x .* y).latent_prefix: the prefix applied to the latent model's variables (default"DayofWeek").
Examples
using ComposableTuringIDModels
obs = ascertainment_dayofweek(PoissonError())
mdl = as_turing_model(obs, missing, fill(10.0, 14))
rand(mdl)ComposableTuringIDModels.assert_prior_length Function
Assert that a vector-of-Distributions prior has exactly k elements.
Pairs with prior_order: once a slot has fixed the order k (e.g. from damp), a second per-lag/per-element slot given as a vector (e.g. init) must match it. A single Distribution or a richer prior model broadcasts to k and imposes no constraint, so it always passes.
Arguments
p: the prior for the slot being checked. A vector ofDistributions is length-checked; anything else broadcasts and always passes.k: the required number of elements, fixed earlier by another slot.what: a short description of the slot, used in the assertion message.
Examples
using ComposableTuringIDModels, Distributions
ComposableTuringIDModels.assert_prior_length([Normal(), Normal()], 2, :damp)ComposableTuringIDModels.at Function
Read a possibly-time-varying parameter at step t.
A scalar (a constant parameter, drawn from a Distribution prior through the single as_turing_submodel seam) is returned unchanged at every step; a vector (a per-step path, drawn from a process prior) is indexed at t; a matrix (a strata × time parameter) is indexed at column t. A component's recursion writes at(ρ, t) * … so the same code serves a constant and a time-varying (or strata-varying) parameter — the scalar branch is zero-cost (no per-step allocation).
This is the read side of the widening seam a custom component uses to make any per-step parameter optionally time-varying: draw the slot through as_turing_submodel, then read it per step with at. See Time-varying damping in an AR process for the worked example.
Examples
using ComposableTuringIDModels
(ComposableTuringIDModels.at(0.5, 3), ComposableTuringIDModels.at([0.1, 0.2, 0.3], 2))ComposableTuringIDModels.broadcast_dayofweek Function
Build a BroadcastLatentModel for a day-of-week effect: a transformed inner model repeated across a 7-day period.
Arguments
model: the inner latent model.link: link applied before broadcasting (defaultx -> 7 * softmax(x), constraining the week effects to sum to 7).
Examples
using ComposableTuringIDModels
broadcast_dayofweek(RandomWalk())ComposableTuringIDModels.broadcast_n Function
Length of the inner series an AbstractBroadcastRule needs to produce a length-n broadcasted series. Each rule implements its own method.
Arguments
rule: theAbstractBroadcastRule.n: the length of the broadcasted output series.period: the broadcast period.
Examples
using ComposableTuringIDModels
broadcast_n(RepeatEach(), 10, 7), broadcast_n(RepeatBlock(), 10, 7)ComposableTuringIDModels.broadcast_rule Function
Expand an inner latent series to length n under an AbstractBroadcastRule. Each rule implements its own method.
Arguments
rule: theAbstractBroadcastRule.latent: the inner latent series to expand.n: the length of the broadcasted output series.period: the broadcast period.
Examples
using ComposableTuringIDModels
broadcast_rule(RepeatEach(), [1, 2], 5, 2)ComposableTuringIDModels.broadcast_weekly Function
Build a BroadcastLatentModel for a piecewise-constant weekly process.
Arguments
model: the inner latent model.
Examples
using ComposableTuringIDModels
broadcast_weekly(RandomWalk())ComposableTuringIDModels.condition_model Function
Condition a DynamicPPL.Model by fixing some parameters and conditioning on others.
condition_model(model, fix_parameters, condition_parameters)equals condition(fix(model, fix_parameters), condition_parameters). Either named tuple may be empty.
Arguments
model: theDynamicPPL.Modelto fix and condition.fix_parameters: a named tuple of parameters to fix to constant values.condition_parameters: a named tuple of parameters to condition on data.
Examples
using ComposableTuringIDModels, Distributions
m = as_turing_model(RandomWalk(), 10)
condition_model(m, (rw_init = 0.0,), NamedTuple())ComposableTuringIDModels.define_y_t Function
Unpack the observed count series an observation-error model scores from the data y_t, dispatching on the model type.
The default method covers every count family (Poisson, negative binomial) and the Gaussian family: it accepts a plain observation vector, a missing (replaced by a length-Y_t vector of missing for predictive simulation), a MissingObservations carrier (rebuilt into a ragged Vector{Union{Missing,T}}), or a NamedTuple carrying the counts in a y field alongside any extra per-time-point data (a model that needs more than the counts — e.g. BinomialError, which also needs the number of trials — reads those extra fields itself). This keeps the simple case ergonomic (a plain vector just works) while letting a model opt into a richer NamedTuple data contract.
Arguments
obs_model: the observation-error model.y_t: the observed data — a vector,missing, aMissingObservationscarrier, or aNamedTuple.Y_t: the expected-observation series (used to size amissingseries).
Examples
using ComposableTuringIDModels
# A plain vector passes through; a NamedTuple's `y` field is unpacked.
define_y_t(PoissonError(), [1, 2, 3], fill(10.0, 3)),
define_y_t(PoissonError(), (y = [1, 2, 3],), fill(10.0, 3))Build the ReportingTriangle data a ReportTriangle scores.
Dispatched on ReportTriangle as the triangle method of the shared define_y_t data-unpacking hook (the vector / NamedTuple methods serve the per-time-point error families). It accepts:
a
ReportingTriangle— returned unchanged (already built);a dense matrix
N[t, d+1]— the present time defaults tonow = size(N, 1)(the last reference day), masking cell(t, d)observed ifft + d ≤ now;a long-form table of
(reference, delay, count)rows (anyTables.jltable, e.g. aDataFrame) — accumulated into the matrix, withnowsupplied as a keyword;missing— a fully observed triangle ofmissingcells for predictive simulation (now = n + Dmax, so every delay of every reference day is reported).
Arguments
obs_model: theReportTrianglemodel (fixesDmax).y_t: the raw data — aReportingTriangle, a matrix, a long-form table, ormissing.Y_t: the expected eventual-total series (its lengthnsets the number of reference days when sizing amissingtriangle).
Keyword Arguments
now: the present time used for thet + d ≤ nowmask. Defaults to the number of reference days for a matrix, and is required for a long-form table.reference,delay,count: the column names in a long-form table (defaults:reference,:delay,:count).
Examples
using ComposableTuringIDModels
obs = ReportTriangle(PoissonError(), [0.5, 0.3, 0.2]) # Dmax = 2
N = [10 5 2; 12 6 3; 14 7 4] # 3 reference days
define_y_t(obs, N, fill(20.0, 3)).observedComposableTuringIDModels.equal_dimensions Function
Partition n elements into m segments of as-equal-as-possible length.
Each segment gets floor(n / m), and the n mod m leftover elements are handed out one apiece to the leading segments. The segment lengths therefore always sum to exactly n (differing by at most one), which is required by ConcatLatentModels's @assert sum(dims) == n check. This is the default dimension_adaptor for ConcatLatentModels.
Arguments
n: the total number of elements.m: the number of segments.
Examples
using ComposableTuringIDModels
ComposableTuringIDModels.equal_dimensions(10, 3)ComposableTuringIDModels.expected_Rt Function
Expected reproduction number
Arguments
gen_int: the discrete generation interval weights (or aRenewalmodel, whose generation interval is used).infections: the infection series (longer than the generation interval).
Examples
using ComposableTuringIDModels
expected_Rt([0.2, 0.3, 0.5], [100.0, 200, 300, 400, 500])ComposableTuringIDModels.forecast Function
Forecast observations over a future horizon from a fitted model.
Given a model fit to an observed series y of length chain), forecast predicts the observations for the next horizon time points
y extends along the TIME axis: a plain vector of length strata x T matrix (extended by horizon further columns), or a NamedTuple of per-stream series (each stream extended in turn). The strata count, when there is one, is read from the observation model and y exactly as IDProblem reads it at build time.
This works because the package's latent processes are non-centred: a RandomWalk, AR or MA accumulates an i.i.d. sequence of standard innovations, so the future innovations are independent prior draws that continue — rather than overwrite — the fitted trajectory. forecast extends each draw's innovation stream to the horizon length with fresh prior draws and then calls predict on the model rebuilt at length
The result is a chain of the same shape as the input containing the predicted observations y_t[T+1] … y_t[T+h] (the in-sample points stay conditioned on the data and so are not resampled). Pass it to generated_observables or returned to recover the extended latent trajectories per draw.
The extension is exact for the package's non-centred processes because their future innovations are independent of the fitted history. A latent whose stored stream is itself jointly correlated across time (e.g. an exact-GP MvNormal) would instead need its tail drawn conditional on the history; forecast detects that generically and errors rather than returning a mis-calibrated forecast.
Arguments
y: the observed series the model was fit to (length ).chain: the posterior samples from fittingmodeltoy.horizon: the number of future time points to forecast.
Keyword arguments
rng: random number generator for the future innovations and the predictive draws.
Examples
using ComposableTuringIDModels, Distributions, Turing, Random
Random.seed!(1)
model = IDModel(
DirectInfections(; Z = RandomWalk(), initialisation = Normal(1.0, 0.5)),
PoissonError())
y = fill(5, 15)
chain = sample(as_turing_model(model, y, length(y)), Prior(), 40;
progress = false)
fc = forecast(model, y, chain, 7)
size(fc)Forecast from a fitted IDProblem; see forecast for the model method. The observation model and infection process are taken from the problem, and the horizon extends the problem's tspan.
ComposableTuringIDModels.generate_observation_error_priors Function
Generate the priors required by an observation-error model. Returns a named tuple consumed by observation_error. The default is an empty tuple.
Arguments
obs_model: the observation-error model whose priors are generated.y_t: the observed series (ormissingwhen simulating predictively).Y_t: the expected-observation series.
Examples
using ComposableTuringIDModels
m = generate_observation_error_priors(NegativeBinomialError(), missing, fill(10.0, 5))
rand(m)ComposableTuringIDModels.generated_observables Function
Wrap a model, data, and inference solution into an IDObservables.
When solution is an MCMC Chains, the model is re-run over the draws with DynamicPPL.returned to recover the model's returned generated quantities (e.g. (; generated_y_t, expected_y_t, I_t, Z_t)) per sample, stored in the generated field. For any other solution (an optimiser result, a prior draw, …) there are no per-draw generated quantities, so generated is missing.
Arguments
model: the model that was sampled (a conditionedDynamicPPL.Model).data: the data the model was conditioned on.solution: the inference solution (samples or optimiser result).
Examples
using ComposableTuringIDModels, Distributions
m = as_turing_model(
IDModel(
DirectInfections(; Z = RandomWalk(), initialisation = Normal()),
PoissonError()), missing, 10)
generated_observables(m, (; y_t = missing), rand(m))ComposableTuringIDModels.get_param_array Function
Reshape an MCMCChains.Chains object into a (draws × chains) array of per-sample NamedTuples.
Arguments
chn: theChainsobject.
Examples
using ComposableTuringIDModels
nothingComposableTuringIDModels.get_state Function
Assemble the final sequence from the raw output of accumulate_scan.
The default method prepends initial_state to the last element of each accumulated state. Step structs whose state is a named tuple (e.g. the MA and LatentDelay steps) override this method to extract the relevant field.
Arguments
acc_step: theAbstractAccumulationStepused in the scan; the method dispatches on its concrete type.initial_state: the seed state used byaccumulate_scan.state: the raw accumulated output produced byaccumulate.
Examples
using ComposableTuringIDModels
accumulate_scan(ComposableTuringIDModels.RWStep(), 0.0, [1.0, 2.0, 3.0])ComposableTuringIDModels.gravity Function
Build a gravity coupling operator from populations and distances.
Off-diagonal entry g's force of infection that comes from stratum h; the diagonal is the share from itself. within sets the self-weight relative to a typical pairwise term, so the default of 1.0 makes a stratum's own history count about as much as a neighbour's.
Populations are taken in units of their mean and each row is normalised to sum to one. Both are required rather than optional: renewal_pressure computes K is indistinguishable from scaling K carrying only where a stratum's force comes from and pop can therefore be raw counts.
within to be non-zero. At within = 0 a common row factor divides out and
Arguments
pop: the population of each stratum, in any units.dist: thestrata × stratadistance matrix. Only its off-diagonal entries are read.
Keyword Arguments
α: the exponent on the destination population (default1.0).β: the exponent on the origin population (default1.0).γ: the exponent on distance (default2.0).within: the own-stratum weight, relative to a typical pairwise term (default1.0).
Examples
using ComposableTuringIDModels
pop = [1.0e6, 2.0e5]
dist = [0.0 50.0; 50.0 0.0]
gravity(pop, dist; α = 0.0, β = 1.0, γ = 2.0)ComposableTuringIDModels.implements_infection_interface Function
Check that model satisfies the AbstractInfectionModel interface: it is an infection model and as_turing_model(model, n) returns a DynamicPPL.Model.
The infection model owns its latent process internally, so the construction check passes only a series length n (no external latent path).
Arguments
model: the component to check.
Keyword Arguments
n: the shape argument used for the construction check (default10). AnIntdraws a length-npath. ADims{2}(n_strata, n_time) draws astrata x timematrix. Pass the matching value for a component that needs a strata axis.
Examples
using ComposableTuringIDModels, Distributions
implements_infection_interface(
DirectInfections(; Z = RandomWalk(), initialisation = Normal()))ComposableTuringIDModels.implements_observation_interface Function
Check that model satisfies the AbstractObservationModel interface: it is an observation model and as_turing_model(model, y_t, Y_t) returns a DynamicPPL.Model.
Arguments
model: the component to check.
Keyword Arguments
y_t: the observed series for the construction check (defaultmissing, i.e. predictive simulation).Y_t: the expected-observation series (defaultfill(10.0, 10)).
Examples
using ComposableTuringIDModels
implements_observation_interface(PoissonError())ComposableTuringIDModels.implements_prior_interface Function
Check that model satisfies the AbstractPriorModel interface: it is a prior model and as_turing_model(model, n) returns a DynamicPPL.Model.
Every AbstractLatentModel is also an AbstractPriorModel, so this holds for latent models (a latent process used directly as a prior) as well as for any bespoke prior submodel. A bare Distribution is not a prior model (it composes through as_turing_submodel instead), so this returns false for one.
Arguments
model: the component to check.
Keyword Arguments
n: the prior length used for the construction check (default10).
Examples
using ComposableTuringIDModels
implements_prior_interface(RandomWalk())ComposableTuringIDModels.infection_strata Function
The number of infection strata an observation model consumes.
The seam an observation model uses to say how many infection strata it consumes, given the number of observation streams in the data. The default passes the observation stream count straight through (a one-to-one mapping). Split with a weight map overrides it with the map's column count, so a many-to-one or many-to-many mapping can build the right-shaped infection process from the data alone.
Arguments
obs: the observation model.n_obs_strata: the number of observation streams in the data.
Examples
using ComposableTuringIDModels
ComposableTuringIDModels.infection_strata(
Split(PoissonError(), [1.0 1.0 1.0]), 1)ComposableTuringIDModels.observation_error Function
The per-time-point observation-error distribution given an expected value and the sampled priors. Each error family implements its own method.
Arguments
obs_model: the observation-error model.Y_t: the expected observation at a single time point.additional positional arguments: any sampled priors produced by
generate_observation_error_priorsfor the family (e.g. the squared cluster factor forNegativeBinomialError).
Examples
using ComposableTuringIDModels
observation_error(PoissonError(), 10.0)ComposableTuringIDModels.pairwise_gen_int Function
Fold per-pair generation intervals into a coupling operator.
Returns the strata × strata × lags array renewal_pressure takes, with entry [g, h, i] equal to K[g, h] * G[g, h, i]: the weight stratum h puts on stratum g at lag i. A between-stratum transmission can therefore carry a longer effective interval than a within-stratum one, which a single shared interval cannot express.
Lags are indexed forwards, so [:, :, 1] is lag 1.
Arguments
K: thestrata × stratacoupling weights.G: the per-pair generation intervals, either astrata × strata × lagsarray whose[g, h, :]is the interval fromhtog, or a single vector used for every pair.
Examples
using ComposableTuringIDModels
K = [0.9 0.1; 0.05 0.95]
size(pairwise_gen_int(K, [0.2, 0.3, 0.5]))ComposableTuringIDModels.path_prior Function
Widen a raw prior into a well-defined length-n PATH prior.
A bare Distribution given to a length-n PATH slot (an innovation, or a latent process such as Hierarchy's across) is wrapped in an Intercept so it is a constant path (one shared draw broadcast to length n), not a scalar. A process, an explicit IID/Intercept, or a vector passes through unchanged.
A component author writing a new PATH-slot constructor calls this on the raw argument, exactly as AR, MA and Hierarchy do for their innovation/across slots. Per-step PARAMETER slots (damp, θ, std, …) keep the bare Distribution — a scalar constant — and must not use this; use IID for n independent draws instead.
Examples
using ComposableTuringIDModels, Distributions
ComposableTuringIDModels.path_prior(Normal()) isa InterceptComposableTuringIDModels.prior_order Function
The order (p/q/d) implied by a prior slot.
A vector of Distributions fixes the order to the vector length (one independent per-lag/per-element prior); a single Distribution or a richer prior model (an AbstractPriorModel) defaults to order 1. This is how AR and MA infer their order from damp/θ without a separate order argument; a component author defining a similar per-lag slot uses it the same way.
Examples
using ComposableTuringIDModels, Distributions
(ComposableTuringIDModels.prior_order(Normal()),
ComposableTuringIDModels.prior_order([Normal(), Normal()]))ComposableTuringIDModels.r_to_R Function
Reproduction number implied by an exponential growth rate r and discrete generation interval w:
Arguments
r: the exponential growth rate.w: the discrete generation interval weights.
Examples
using ComposableTuringIDModels
r_to_R(0.1, [0.2, 0.3, 0.5])ComposableTuringIDModels.renewal_foi Function
Force of infection for a constant-generation-interval renewal step: the reproduction number renewal_pressure on each stratum,
This is the raw new-incidence term before any modifier (e.g. susceptible depletion) is applied. renewal_foi is shared by the internal ConstantRenewalStep core and the composable RenewalStep so the two cannot drift. It reads the coupling operator off the step and defers to renewal_pressure, so a new coupling needs no change here.
Arguments
step: the renewal step supplying the generation interval and the mixing.window: the recent incidence, oldest to newest.Rt: the reproduction number, a scalar or one value per stratum.
Examples
using ComposableTuringIDModels
step = ComposableTuringIDModels.ConstantRenewalStep(reverse([0.2, 0.3, 0.5]))
ComposableTuringIDModels.renewal_foi(step, [10.0, 20.0, 30.0], 1.5)ComposableTuringIDModels.renewal_init_state Function
The state a renewal scan carries: the newest incidence val alongside the window it came from, and substates when the step has modifiers.
Pairing the value with the window lets get_state read a field rather than index the accumulated array, which keeps the scan differentiable under every AD backend.
A new renewal step implements this, usually by wrapping renewal_init_window.
Arguments
step: the renewal step being seeded.I₀: the initial incidence, one value or one per stratum.r: the growth rate implied by .len_gen_int: the number of lags the generation interval covers.
Examples
using ComposableTuringIDModels
step = ComposableTuringIDModels.ConstantRenewalStep(reverse([0.2, 0.3, 0.5]))
ComposableTuringIDModels.renewal_init_state(step, 10.0, 0.1, 3)ComposableTuringIDModels.renewal_init_window Function
The incidence window a renewal scan starts from: len_gen_int values decaying at the rate r implied by I₀. A vector for one series, a strata × lags matrix for a stratified renewal, since I₀ and r broadcast.
A new renewal step implements this if it seeds its window differently, and renewal_init_state to wrap it in the state the scan carries.
Arguments
step: the renewal step being seeded.I₀: the initial incidence, one value or one per stratum.r: the growth rate implied by .len_gen_int: the number of lags the generation interval covers.
Examples
using ComposableTuringIDModels
step = ComposableTuringIDModels.ConstantRenewalStep(reverse([0.2, 0.3, 0.5]))
ComposableTuringIDModels.renewal_init_window(step, 10.0, 0.1, 3)ComposableTuringIDModels.renewal_pressure Function
Generation-time-weighted incidence pressure on each stratum, before
This is the one dispatch point for coupling between strata. It sits between the generation-time convolution and
The mixing argument chooses the method.
I(LinearAlgebra.I), the default, leaves the strata uncoupled. For a single series the window is a vector and this is a dot product, so a one-stratum model runs exactly the arithmetic it would with no strata axis at all.A
strata × stratamatrix mixes the convolved histories, so off-diagonal mass is infection arriving from another stratum. Rowgsays where stratumg's force of infection comes from.A
strata × lagsgeneration interval gives each stratum its own interval, with or without a mixing matrix on top.A
strata × strata × lagsarray gives a per-pair generation interval, so a between-stratum transmission can carry a longer effective interval. Build one withpairwise_gen_int. Its lags are indexed forwards, soK[:, :, 1]is the lag-1 weight.
Nothing about a mixing matrix is checked or normalised. A zero column K[:, h] means stratum h infects nobody, though its own incidence still evolves through K[h, h]. A zero row K[g, :] means stratum g receives no force at all, so its incidence falls to zero and stays there.
Extend it by adding a method: any operator on the convolved window is a valid coupling.
Arguments
mixing: the coupling operator.g: the reversed generation interval, a vector or astrata × lagsmatrix.window: the recent incidence, oldest to newest.
Examples
using ComposableTuringIDModels, LinearAlgebra
window = [10.0 20.0; 5.0 8.0] # 2 strata, 2 lags
g = [0.4, 0.6]
renewal_pressure(I, g, window) # uncoupledComposableTuringIDModels.spectral_density Function
Spectral density HilbertSpaceGP covariance kernel at frequency ω, for marginal standard deviation σ and length scale ℓ.
The kernels are KernelFunctions.jl types. A kernel enters the Hilbert-space approximation only through this spectral density — the Fourier transform of the stationary covariance — so switching kernel just reweights the shared basis by spectral_density method; nothing else changes.
ω may be a scalar or a vector (the call broadcasts). The one-dimensional squared-exponential, Matérn-3/2 and Matérn-5/2 spectral densities are [ DocumenterCitations.CitationSiteNode("riutortmayol2023practical-cite-5")
]
with SqExponentialKernel) gives infinitely differentiable, very smooth paths; Matern32Kernel (once-differentiable) and Matern52Kernel (twice-differentiable) give progressively rougher ones — the three choices most often used for a smooth epidemiological latent process. HilbertSpaceGP weights each basis function by
Requires NaN, which would silently propagate into the basis weights. A new method should assert the same.
Examples
using ComposableTuringIDModels
spectral_density(SqExponentialKernel(), [0.0, 1.0, 2.0], 1.0, 0.8)ComposableTuringIDModels.spread_draws Function
Convert an MCMCChains.Chains object to a tidy DataFrame (one row per draw, with draw, chain, and iteration columns).
Arguments
chn: theChainsobject to convert.
Examples
using ComposableTuringIDModels
nothingComposableTuringIDModels.standardised_index Function
Standardise the integer index
This is the input grid both HilbertSpaceGP and ExactGP hand to their covariance kernel, so a given length scale
It is public so that a comparison against another Gaussian-process implementation — an AbstractGPs.jl GP, say — can be built on the same coordinates rather than on a reimplementation of this formula. Requires n > 1, since the standard deviation of a single point is zero.
Arguments
n: the series length.
Examples
using ComposableTuringIDModels
standardised_index(5)