Public Documentation
Documentation for ComposedDistributions's public interface.
ComposedDistributions.ComposedDistributions Module
ComposedDistributionsThe verb grammar for n-ary composition over any Distributions.jl UnivariateDistribution. Compose delays into chains (Sequential), independent branches (Parallel), fixed-probability or racing one_of outcomes (Resolve / Compete) and data-selected disjunctions (Choose); the compose front-end lowers a NamedTuple, a Tables.jl table, or a nested matrix to the same stack. Read the structure with params_table / event_names / event, build priors with build_priors, and edit the tree with update / prune / splice. Attach parameter uncertainty with uncertain (parameters that are themselves distributions, nestable):randdraws the marginal, and update collapses an uncertain leaf to its concrete template.
Hard-deps ConvolvedDistributions (a chain collapses to a convolved total via observed_distribution) and extends its convolve_series/difference generics for composed tree types; its own convolution/quadrature surface (convolved, integrate/gl_integrate, the solver-method types) is reached with a separate using ConvolvedDistributions, not re-exported here. No censoring: this is the generic composition layer.
Examples
using ComposedDistributions, Distributions
# A two-step delay chain, then its parameter table.
tree = compose((onset_admit = [Gamma(2.0, 1.0), LogNormal(0.5, 0.4)],))
params_table(tree)Contents
Index
ComposedDistributions.ComposedDistributionsComposedDistributions.AbstractComposedDistributionComposedDistributions.AbstractContextComposedDistributions.AbstractMultiChildComposedDistributions.AbstractOneOfComposedDistributions.CentredPoolPriorComposedDistributions.ChooseComposedDistributions.CompeteComposedDistributions.ContextComposedDistributions.EventSkeletonComposedDistributions.NoEventComposedDistributions.ParallelComposedDistributions.PoolComposedDistributions.ResolveComposedDistributions.SequentialComposedDistributions.SharedComposedDistributions.UncertainComposedDistributions.VaryingComposedDistributions._centred_pool_rowsComposedDistributions._pool_centred_logpriorComposedDistributions._validate_pool_groupsComposedDistributions._validate_tree_namesComposedDistributions.as_mixtureComposedDistributions.build_priorsComposedDistributions.centred_pool_rowsComposedDistributions.child_logpdfComposedDistributions.child_nleavesComposedDistributions.child_rand!ComposedDistributions.chooseComposedDistributions.competeComposedDistributions.component_namesComposedDistributions.composeComposedDistributions.default_priorComposedDistributions.elapsed_betweenComposedDistributions.eventComposedDistributions.event_incrementsComposedDistributions.event_namesComposedDistributions.event_timesComposedDistributions.event_treeComposedDistributions.extra_leaf_paramsComposedDistributions.flat_dimensionComposedDistributions.flattenComposedDistributions.free_leafComposedDistributions.has_uncertainComposedDistributions.has_varyingComposedDistributions.inspectComposedDistributions.instantiateComposedDistributions.leaf_ctorComposedDistributions.leaf_detail_linesComposedDistributions.leaf_meanComposedDistributions.leaf_param_namesComposedDistributions.leaf_varComposedDistributions.missing_covariatesComposedDistributions.observed_distributionComposedDistributions.occurrence_probabilityComposedDistributions.parallelComposedDistributions.param_namesComposedDistributions.param_priorsComposedDistributions.params_tableComposedDistributions.poolComposedDistributions.pool_centred_logpriorComposedDistributions.pool_groupComposedDistributions.pool_noncentredComposedDistributions.pruneComposedDistributions.reconstructComposedDistributions.required_covariatesComposedDistributions.required_parametersComposedDistributions.reserved_record_fieldsComposedDistributions.resolveComposedDistributions.rewrap_leafComposedDistributions.sequentialComposedDistributions.set_extra_leaf_paramsComposedDistributions.sharedComposedDistributions.shared_tagComposedDistributions.spliceComposedDistributions.tieComposedDistributions.uncertainComposedDistributions.uncertain_specsComposedDistributions.unflattenComposedDistributions.updateComposedDistributions.validate_pool_groupsComposedDistributions.validate_tree_namesComposedDistributions.varyingComposedDistributions.with_covariatesComposedDistributions.@eventsComposedDistributions.@uncertain
Public API
ComposedDistributions.@events Macro
Declare an event-tree topology as a readable operator diagram.
@events lowers an operator diagram of event names to an EventSkeleton carrying structure only, no distributions. Fill the holes later with update(skeleton; name = dist, ...) to build the concrete composed tree, so one delay topology is reused across pathogens or settings.
The operators (parentheses group for precedence):
→(\to, typed\to<tab>) chains events into aSequential; a nested→chain flattens into one sequential of all events.|branches into a one_of outcome. Whether the node becomes a fixed-probabilityResolveor a racing-hazardCompeteis decided at fill time by the fill value type (seeupdate), so|stays one syntax.&runs branches inParallel.
A bare identifier is an event name and becomes a named hole, the key the fill substitutes. A nested one_of or parallel group inside a → chain is named deterministically from its branches (_or_ for one_of, _and_ for parallel, e.g. death | discharge names its enclosing step death_or_discharge); a fill names only the branch holes, never the group.
Arguments
body: the event diagram, either a bare expression or abegin ... endblock holding exactly one diagram expression.
Examples
using ComposedDistributions, Distributions
skeleton = @events begin
onset → admission → (death | discharge)
end
tree = update(skeleton;
onset = Gamma(2.0, 1.0),
admission = LogNormal(0.5, 0.4),
death = (Gamma(1.5, 1.0), 0.3),
discharge = Gamma(2.0, 1.5))
event_names(tree)See also
EventSkeleton: the topology type this builds.update: fill the holes to build the concrete tree.sequential,parallel,resolve,compete: the verbs the fill lowers to.
ComposedDistributions.@uncertain Macro
Read distribution-valued constructor arguments as parameter priors.
@uncertain expr rewrites expr so that a distribution literal passed as a positional argument to a distribution constructor becomes that parameter's prior, the natural spelling of the positional uncertain family form. It walks the whole expression, so it composes with compose, the composer verbs and the ModifiedDistributions wrappers.
Each call D(pos_args...) whose head D is a distribution type (a bare name beginning with an uppercase letter) and at least one of whose positional arguments is itself such a distribution literal is rewritten to uncertain(D, pos_args...). The runtime then sorts each positional argument: a UnivariateDistribution marks that parameter uncertain (a prior), a Real fixes it. So @uncertain LogNormal(Normal(0.0, 1.0), 0.5) is uncertain(LogNormal, Normal(0.0, 1.0), 0.5) (mu uncertain, sigma fixed at 0.5). A constructor with only literal arguments (LogNormal(0.5, 0.4)) has no distribution-valued argument and is left unchanged, and lowercase-headed calls (compose, affine, ...) are not themselves rewritten, though their arguments still are, so a modifier wraps the rewritten uncertain leaf.
A keyword-carrying constructor call and a qualified head (Base.Gamma) are left unrewritten; reach those through the explicit uncertain constructor.
Arguments
expr: an expression building a (possibly composed) distribution, with a distribution literal in a parameter slot standing for that parameter's prior.
Examples
using ComposedDistributions, Distributions
# `shape` uncertain, `scale` fixed at 1.0.
@uncertain Gamma(Normal(0.7, 0.2), 1.0)
# A whole tree: only the Gamma leaf's shape is made uncertain.
@uncertain compose((
onset = Gamma(LogNormal(log(2.0), 0.2), 1.0),
admit = LogNormal(0.5, 0.4)))See also
sourceComposedDistributions.AbstractComposedDistribution Type
abstract type AbstractComposedDistribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport} <: Distributions.Distribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport}AbstractComposedDistribution{F<:VariateForm, S<:ValueSupport}Supertype of the composer nodes that combine named child distributions into an event tree: the multivariate Sequential / Parallel / Choose and the univariate one_of family (AbstractOneOf: Resolve / Compete). Parametric on variate form so the one supertype spans both.
Required methods a concrete subtype implements (the node interface):
child_nleaves(c),child_logpdf(c, x, offset, n),child_rand!(out, offset, rng, c)— walk the flat event vector;component_names(c)— the child names;params(c)andparams_table(c);event_names(c)(flat) andevent_tree(c)(nested);Base.show(io, c).
Verify a subtype with ComposedDistributions.TestUtils.test_composed_interface.
Fields
sourceComposedDistributions.AbstractContext Type
abstract type AbstractContextSupertype of the covariate contexts a composed tree is resolved against.
A subtype carries the covariates a Varying leaf reads. Context is the concrete open-NamedTuple implementation; the abstract type is what the uncertain distributions work can extend with its own sampled-parameter context. instantiate dispatches on AbstractContext.
See also
Context: the concrete covariate bag.instantiate: resolves a tree against a context.
Fields
sourceComposedDistributions.AbstractMultiChild Type
abstract type AbstractMultiChild{S<:Distributions.ValueSupport} <: ComposedDistributions.AbstractComposedDistribution{Distributions.Multivariate, S<:Distributions.ValueSupport}AbstractMultiChild{S<:ValueSupport}Supertype of the positional multi-child composers Sequential and Parallel (subtype of AbstractComposedDistribution{Multivariate, S}). These two store .components and carry their child names in a names type parameter (read with component_names), and are walked positionally by the tree machinery, so they share dispatch on ::AbstractMultiChild (the supertype the tree walkers key off). Choose (disjoint alternatives) is a sibling, not a multi-child node.
Fields
sourceComposedDistributions.AbstractOneOf Type
abstract type AbstractOneOf <: ComposedDistributions.AbstractComposedDistribution{Distributions.Univariate, Distributions.Continuous}Shared supertype of the one_of-outcome composers.
The two one_of-outcome nodes — the fixed-probability mixture Resolve (cause and timing independent) and the racing-hazard Compete (the winning probability derived from the hazards, timing coupled) — subtype AbstractOneOf. The tree walkers dispatch on it wherever the behaviour is shared (one event slot per outcome, the shared origin, the per-outcome rand) and on the concrete type only where the scoring arithmetic differs.
AbstractOneOf is the univariate arm of the composer hierarchy: it subtypes AbstractComposedDistribution{Univariate, Continuous}, so it stays a UnivariateDistribution while sharing the composed supertype the multivariate Sequential / Parallel / Choose also sit under.
Examples
using ComposedDistributions, Distributions
r = resolve(:death => (Gamma(1.5, 1.0), 0.3), :disch => Gamma(2.0, 1.5))
r isa ComposedDistributions.AbstractOneOfSee also
Fields
sourceComposedDistributions.CentredPoolPrior Type
struct CentredPoolPrior{P<:Pool}The centred latent's prior marker, carried on the prior column of a centred pooled parameter's row.
It is not a fixed distribution (the population depends on the estimated hyperparameters), so DistributionsInference.jl's distribution_to_logdensity/logdensity scores it separately (_pool_centred_logprior) and skips it in the fixed per-row prior sum. A non-nothing entry, so the row still counts as estimated.
Reached by qualified name from outside this package — DistributionsInference.jl's fit-protocol extension pattern-matches on this marker to translate a centred-pooled row's prior to nothing.
See also: _centred_pool_rows, _pool_centred_logprior, pool
Fields
pool::Pool
ComposedDistributions.Choose Type
struct Choose{names, A<:Tuple} <: ComposedDistributions.AbstractComposedDistribution{Distributions.Multivariate, Distributions.Continuous}A data-selected disjunction over independent named alternatives.
Choose holds selector naming the data field that picks which alternative applies to a record. Exactly one alternative is active per record, chosen by the selector value, not by a branch probability and not off a shared origin. This is the disjunctive split that neither Parallel (shared origin, product over branches) nor Resolve (shared origin, probabilistic mixture) expresses: the alternatives are genuinely independent sub-models with different origins, and the data says which one generated the record.
Scoring and model dispatch route to the selected alternative: logpdf(d, x; kind) takes the chosen name as the kind keyword (no default — a Choose has no single distribution to score without a selection). Sampling has two forms: rand(d; kind) draws the named alternative directly, while a bare rand(d) (no kind, the forward-simulation path) samples an alternative uniformly and returns a self-describing record tagging which was drawn (the selector field set to its name plus the alternative's own draw), so logpdf(d, rand(d)) round-trips with no kind argument. The selection walk is type-stable: the selected alternative is found by a hand-rolled recursion over the name tuple that barriers into the chosen alternative's concrete type, so inference of the hot-path logpdf is preserved.
An alternative may itself be any distribution or a nested composer (Sequential, Parallel, Resolve, or another Choose), so a composed tree nests inside a data-selected split. A Choose may also nest the other way, as a child of a Sequential / Parallel / compose composer: the flat, data-free value path (logpdf/rand without a kind) commits to its first alternative, so the node's flat width is that alternative's leaf count; every alternative must share that leaf count for the nested Choose to occupy one fixed flat slot, or the parent's width query errors.
For prior introspection (params_table, build_priors, update) the alternatives' parameters are namespaced per alternative: independent per-branch params live under their alternative name (index.… / sourced.…), so each branch's parameters are inventoried and sampled separately. A parameter tied across alternatives via shared(:tag, ...) is keyed once by its tag and is inventoried once and sampled once, so the tied value is shared by every alternative that uses it.
Fields
the alternative names (
Symbols) live in thenamestype parameter (read withcomponent_names).alternatives: tuple of the alternative distributions, one per name.selector: the row field name (Symbol) whose value selects an alternative.
See also
choose: friendly constructor overname => distpairsResolve: exactly one of several shared-origin outcomes (mixture)Parallel: independent shared-origin branches (product)
Fields
alternatives::Tuple: Tuple of the alternative distributions, one per name.selector::Symbol: The row field name (Symbol) whose value selects an alternative.
ComposedDistributions.Compete Type
struct Compete{names, D<:Tuple} <: ComposedDistributions.AbstractOneOfResolve risks by racing hazards: the dual of convolved under minimum instead of sum.
Given cause-specific delay distributions D_1, ..., D_n, Compete represents the first-event time T = min_k D_k together with which cause won. The marginal any-event survival is ∏_k S_k(t) and density ∑_j f_j(t) ∏_{k≠j} S_k(t), so it nests as a univariate leaf. Observing a resolved (cause j, time t) scores f_j(t) ∏_{k≠j} S_k(t). The winning probability of each cause is derived from the hazards (P(cause = j) = ∫ f_j ∏_{k≠j} S_k), not a free parameter — this is the key difference from the fixed-probability mixture Resolve.
Build it with the compete constructor by giving bare delays (no branch probabilities): compete(:death => D1, :recover => D2).
Three views must agree: rand draws a latent time per cause and returns the winning cause's named event record; logpdf is the one_of-risks likelihood (marginal or cause-resolved); and the forward convolve_series stream is the per-outcome sub-density, sub-stochastic (not renormalised).Competeships against plainDistributions.ccdf/logccdf, so any stock univariate leaf races without a package-specific interface.
Fields
the one_of outcome names (
Symbols) live in thenamestype parameter (read withcomponent_names).delays: tuple of the cause-specific delay distributions.
See also
compete: the constructor (bare delays; no branch probabilities).Resolve: the fixed-probability mixture sibling.Distributions.probs: the derived per-cause winning probabilities.convolved: the sum dual (events in series).
Fields
delays::Tuple: Tuple of the cause-specific delay distributions.
ComposedDistributions.Context Type
struct Context{NT<:NamedTuple} <: AbstractContextThe covariate context a Varying leaf is resolved against.
A Context is an open bag of covariates (a NamedTuple) — calendar time, a region/stratum, or (for the uncertain-distributions work) sampled parameter values. instantiate reads the covariate a leaf names from it. Build one with keyword covariates. It is open (rather than a fixed time field) so it can also carry the uncertain-distributions work's sampled parameters.
Examples
using ComposedDistributions
ctx = Context(time = 4.0)
ctx.covariates.timeSee also
instantiate: resolve a tree/leaf against a context.Varying: the context-indexed leaf.
Fields
covariates::NamedTuple: The covariates keyed by name (time,region, sampled params, ...).
ComposedDistributions.EventSkeleton Type
struct EventSkeleton{S<:ComposedDistributions.AbstractEventSpec}An event-tree topology: named events and their composition structure, with no distributions attached yet.
An EventSkeleton is built by @events from a readable operator diagram: → (\to) chains events into a Sequential, | branches into a one_of outcome, & runs branches in Parallel, and parentheses group for precedence. A bare identifier is an event name and becomes a named hole. Fill the holes with update(skeleton; name = dist, ...) to build the concrete composed tree; whether a | node becomes a fixed-probability Resolve or a racing-hazard Compete is decided there by the fill value type.
A skeleton carries names and structure only, so one delay topology is reused across pathogens or settings by filling it with different distributions. It is independent of Distributions.jl families and of ModifiedDistributions: the fill value at each hole is any valid leaf.
Arguments
spec: the root structural spec node (aHole, a→-chain, a|-one_of group, or a&-parallel group).
Examples
using ComposedDistributions, Distributions
skeleton = @events begin
onset → admission → (death | discharge)
end
tree = update(skeleton;
onset = Gamma(2.0, 1.0),
admission = LogNormal(0.5, 0.4),
death = (Gamma(1.5, 1.0), 0.3),
discharge = Gamma(2.0, 1.5))
event_names(tree)See also
@events: the macro that builds a skeleton from an operator diagram.update: fill the holes to build the concrete composed tree.sequential,parallel,resolve,compete: the verbs the fill lowers to.
Fields
spec::ComposedDistributions.AbstractEventSpec
ComposedDistributions.NoEvent Type
struct NoEvent <: Distributions.Distribution{Distributions.Univariate, Distributions.Continuous}Marker distribution for a no-event (absorbing) outcome of a resolve node: the outcome where nothing happens and no event time is written.
A none => (NoEvent(), q) branch carries no delay; its mass q is the probability that no event occurs. On rand a no-event win yields missing (no time recorded). On logpdf an observed non-occurrence (an explicit no event by the horizon record) scores the survival term log q (mixture) or the racing-hazard survival ∏ S_k; a latent non-occurrence (a record whose no-event slot is simply missing) contributes no one_of term.
NoEvent is a degenerate placeholder, not a sampling distribution: it has no support and errors if asked for a density or a draw. It exists only to mark the absorbing branch so the one_of node carries its mass q.
See also
Fields
sourceComposedDistributions.Parallel Type
struct Parallel{names, C<:Tuple} <: ComposedDistributions.AbstractMultiChild{Distributions.Continuous}Independent branches composed from any univariate distributions.
Parallel places Sequential, Parallel, Resolve, Compete or Choose composer, so trees nest recursively and the nesting is the tree.
logpdf is the sum of the per-branch log-densities,
The branches are independent here: this is the plain generic composition. The shared-origin coupling (where every branch shares one latent primary event) is a censored specialisation layered on top elsewhere, not part of this type.
Fields
components: tuple of the branch distributions (each univariate or a nested composer).the branch names (
Symbols), one per component, live in thenamestype parameter (read withcomponent_names); thecomposefront-ends thread the user's names through, positional construction assigns:branch_1, :branch_2, ....
See also
Sequential: a chain of additive stepsResolve: exactly one of several outcomes
Fields
components::Tuple: Tuple of the branch distributions (each univariate or a nested composer).
ComposedDistributions.Pool Type
struct Pool{group, noncentred, P<:(Distributions.UnivariateDistribution)}A partial-pooling spec: a parameter drawn, across a group of leaves, from one shared population distribution.
Pool marks a parameter (an entry of an uncertain leaf's specs) as partially pooled across the leaves that name the same group: every member's parameter is drawn from one common population distribution whose own free parameters are the estimated hyperparameters. It is the middle of the pooling spectrum — shared/tie is complete pooling (one value everywhere) and independent uncertain specs are no pooling (K unlinked values).
The population is an ordinary distribution — usually an uncertain one, so its free parameters carry their priors through the same machinery as any uncertain leaf. A location-scale population (Normal/LogNormal) is reparameterised non-centred (one Normal(0, 1) latent per member); a general population is scored centred (each member's parameter directly against the population).
Fields
the pooling-group name (
Symbol) lives in thegrouptype parameter (read withpool_group); leaves naming the same group are one population.population: the population distribution (its free parameters are the hyperparameters).whether the non-centred (location-scale) parameterisation is used (only for a
Normal/LogNormalpopulation) lives in thenoncentredtype parameter (read withpool_noncentred).
See also
Fields
population::Distributions.UnivariateDistribution: The population distribution; its free parameters are the hyperparameters.
ComposedDistributions.Resolve Type
struct Resolve{names, D<:Tuple, P<:Tuple, S} <: ComposedDistributions.AbstractOneOfResolve outcomes composed from any univariate distributions: exactly one of several outcomes occurs, governed by branch probabilities summing to one.
Resolve names each one_of outcome, its delay distribution, and the branch probability of that outcome. It lowers to a Distributions.MixtureModel (see as_mixture) over the outcome delays weighted by the branch probabilities, so the realisation is a single time and the type is univariate. A death-versus-recovery competition makes the death branch probability the case-fatality ratio.
Being univariate, a Resolve nests as a child of Sequential or Parallel. This is the plain generic composition; per-record outcome selection and censoring are not part of this type.
The branch probabilities are ordinarily fixed structure. To estimate them, attach a simplex-valued Distributions.Dirichlet prior with update(node, (branch_probs = Dirichlet(α),)): the Dirichlet is what you write, but the codec estimates the node through the Dirichlet's K-1 stick-breaking coordinates (:stick_1 … :stick_{K-1}, each a Beta, so every draw lands on the simplex and the gradient is well-defined), and the probabilities are recovered from any draw (via update / Distributions.probs). See update for the full story.
Fields
the one_of outcome names (
Symbols) live in thenamestype parameter (read withcomponent_names).delays: tuple of the one_of outcome delay distributions.branch_probs: tuple of the branch probabilities, summing to one.branch_prob_prior: the attachedDirichletprior when the branch probabilities are uncertain, elsenothing(fixed structure).
See also
as_mixture: theMixtureModelloweringupdate: attach aDirichletto estimate the branch probabilitiesSequential: a chain of additive stepsParallel: independent branches
Fields
delays::Tuple: Tuple of the one_of outcome delay distributions.branch_probs::Tuple: Tuple of the branch probabilities, summing to one.branch_prob_prior::Any: The attached simplex-valued prior over the branch probabilities (aDistributions.Dirichlet), ornothingwhen the probabilities are fixed structure. When present the branch probabilities are estimated through the stick-breaking codec: the user writes theDirichlet, K-1 stick coordinates are what the sampler estimates, and the probabilities are recovered from any draw (seeupdate).
ComposedDistributions.Sequential Type
struct Sequential{names, C<:Tuple} <: ComposedDistributions.AbstractMultiChild{Distributions.Continuous}A chain of independent steps composed from any univariate distributions.
Sequential links events Sequential, Parallel, Resolve, Compete or Choose composer, in which case it contributes its own flat sub-vector, so chains nest recursively and the nesting is the tree.
logpdf sums the per-step log-densities over the matching slices of the value vector:
This is the plain generic composition; censoring and per-record marginalisation are not part of this type. Cumulative event times, if wanted, are the running sum of the step values.
Fields
components: tuple of the step distributions (each univariate or a nested composer).the step names (
Symbols), one per component, live in thenamestype parameter (read withcomponent_names); thecomposefront-ends thread the user's names through, positional construction assigns:step_1, :step_2, ....
See also
Fields
components::Tuple: Tuple of the step distributions(each univariate or a nested composer).
ComposedDistributions.Shared Type
struct Shared{tag, D<:(Distributions.UnivariateDistribution)} <: Distributions.UnivariateDistribution{Distributions.ValueSupport}A name-tagged leaf tied across the branches of a composed distribution.
Shared wraps a leaf distribution with a tag (a Symbol) marking it as a shared parameter group. Two Shared leaves carrying the same tag are treated as the same free parameter by the prior/params interface: params_table lists the group's parameters once (deduped by tag), a downstream composed_parameters_model samples the group once and places the sampled values in every occurrence, and update updates all occurrences from one entry. The wrapper is transparent to scoring and sampling (every distribution method delegates to the wrapped leaf), so it only changes how parameters are inventoried, sampled and reconstructed.
Fields
the shared-parameter group name (
Symbol) lives in thetagtype parameter (read withshared_tag).dist: the wrapped leaf distribution.
See also
shared: constructor over a name and a distribution.params_table,update: dedup occurrences by tag.
Fields
dist::Distributions.UnivariateDistribution: The wrapped leaf distribution.
ComposedDistributions.Uncertain Type
struct Uncertain{VS<:Distributions.ValueSupport, L<:Distributions.Distribution{Distributions.Univariate, VS<:Distributions.ValueSupport}, S<:NamedTuple} <: Distributions.Distribution{Distributions.Univariate, VS<:Distributions.ValueSupport}A leaf distribution whose parameters are themselves distributions.
Uncertain pairs a concrete template leaf with specs, a NamedTuple mapping parameter names (as in params_table's param column) to distributions. A spec entry may itself be an Uncertain, so parameter uncertainty nests. Parameters without a spec stay fixed at the template's values, and the template's fixed wrapper structure (truncation, censoring) is carried through every draw via free_leaf/rewrap_leaf.
The generative model is hierarchical:
with fixed parameters taken from the template. rand draws the marginal (parameters drawn internally). The rest of the univariate surface (scalar logpdf/pdf/cdf/quantile, the moments) delegates to the template, so it reports the leaf at the template's central parameter values, not the marginal. Collapse an uncertain leaf to a concrete distribution by pinning its parameters with update(tree, params).
Only rand is marginal
Every other method — logpdf/cdf/quantile/... and the moments mean/var/std — silently reports the template's central values, not the marginal. Scoring or summarising a raw Uncertain leaf therefore answers "as if" its parameters were fixed at the template. Guard a scoring/fitting loop with has_uncertain, and collapse to concrete values first with update(tree, params).
Fields
template: the concrete (possibly wrapped) leaf supplying the family, the fixed parameter values, and the fixed wrapper structure.specs:NamedTupleof the uncertain parameters, each value a distribution (possibly itself anUncertain).
See also
Fields
template::Distributions.UnivariateDistribution: The concrete (possibly wrapped) template leaf: family, fixed parameter values, and fixed wrapper structure (truncation / censoring).specs::NamedTuple:NamedTupleof the uncertain parameters: each key a parameter name of the template's free delay, each value a distribution (possiblyUncertain).
ComposedDistributions.Varying Type
struct Varying{F, D<:(Distributions.UnivariateDistribution)} <: Distributions.Distribution{Distributions.Univariate, Distributions.Continuous}A context-indexed leaf: a delay whose distribution varies with a covariate.
Varying holds a map f from a covariate value to a UnivariateDistribution (e.g. t -> Gamma(shape(t), scale)), the covariate name it reads from a Context (default :time), and a reference distribution used whenever the leaf is queried without a context. Because Varying <: UnivariateDistribution it drops into Sequential / Parallel / compose as an ordinary leaf, and every Distributions method (logpdf, cdf, mean, rand, params, ...) delegates to the reference, so a tree with varying leaves still scores and samples at its reference by default.
instantiate(d, ctx) is the step that swaps the reference for f evaluated at the context's covariate: instantiate(leaf, Context(time = 4.0)) returns f(4.0). Resolve a whole tree at a context and then score / sample / convolve the concrete result.
Warning
Because the leaf delegates to reference, scoring or sampling a tree that still holds a Varying leaf does not error — it silently uses the reference (a wrong answer against real per-record covariates). Always instantiate first, and guard a fitting loop with has_varying.
The varying map f is fixed structure (like a truncation bound or a censoring window), so the introspection interface (params_table, update) treats the reference's parameters as the free parameters and peels/rewraps through the wrapper; the coefficients of f are not (yet) inventoried (see the design note's open questions).
Fields
f: map from a covariate value to aUnivariateDistribution.covariate: theContextfield name to read (Symbol, default:time).reference: the distribution used when no context is supplied.
See also
varying: friendly constructor.instantiate: resolves the leaf against a context.Context: the covariate bag.
Fields
f::Any: Map from a covariate value to aUnivariateDistribution.covariate::Symbol: TheContextfield name this leaf reads (default:time).reference::Distributions.UnivariateDistribution: The distribution used when no context is supplied.
ComposedDistributions._centred_pool_rows Function
Deprecated alias for centred_pool_rows; kept transitionally so a caller already qualifying it (ComposedDistributions._centred_pool_rows, or an explicit using ComposedDistributions: _centred_pool_rows) keeps working across the rename to a leading-underscore-free public name (the org's naming convention reserves a leading underscore for internal-only names). New code should call centred_pool_rows; this alias is removed in a future cleanup once DistributionsInference.jl's fit-protocol extension has moved off it.
Arguments
- the composed tree whose centred-pooled rows are collected; see
centred_pool_rows.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
ComposedDistributions._centred_pool_rows(tree)See also
sourceComposedDistributions._pool_centred_logprior Function
Deprecated alias for pool_centred_logprior; kept transitionally so a caller already qualifying it (ComposedDistributions._pool_centred_logprior, or an explicit using ComposedDistributions: _pool_centred_logprior) keeps working across the rename to a leading-underscore-free public name (the org's naming convention reserves a leading underscore for internal-only names). New code should call pool_centred_logprior; this alias is removed in a future cleanup once DistributionsInference.jl's fit-protocol extension has moved off it.
Arguments
rows: the(path, param, pool)triples fromcentred_pool_rows.nt: the nestedNamedTuplefromunflattenat the same draw.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
rows = ComposedDistributions.centred_pool_rows(tree)
x = fill(0.5, ComposedDistributions.flat_dimension(tree))
nt = ComposedDistributions.unflatten(tree, x)
ComposedDistributions._pool_centred_logprior(rows, nt)See also
sourceComposedDistributions._validate_pool_groups Function
Deprecated alias for validate_pool_groups; kept transitionally so a caller already qualifying it (ComposedDistributions._validate_pool_groups, or an explicit using ComposedDistributions: _validate_pool_groups) keeps working across the rename (this was declared public with the leading underscore, which the org's naming convention reserves for internal-only names). New code should call validate_pool_groups; this alias is removed in a future cleanup once DistributionsInference.jl's fit-protocol extension has moved off it.
Arguments
d: the composed tree whose pool groups are checked; seevalidate_pool_groups.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
ComposedDistributions._validate_pool_groups(tree)See also
sourceComposedDistributions._validate_tree_names Function
Deprecated alias for validate_tree_names; kept transitionally so a caller already qualifying it (ComposedDistributions._validate_tree_names, or an explicit using ComposedDistributions: _validate_tree_names) keeps working across the rename (this was declared public with the leading underscore, which the org's naming convention reserves for internal-only names). New code should call validate_tree_names; this alias is removed in a future cleanup once DistributionsInference.jl's fit-protocol extension has moved off it.
Arguments
d: the composed tree whose pool/shared/root names are checked; seevalidate_tree_names.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
ComposedDistributions._validate_tree_names(tree)See also
sourceComposedDistributions.as_mixture Function
as_mixture(c::Resolve) -> Distributions.MixtureModelLower a Resolve node to a Distributions.MixtureModel.
Returns the MixtureModel over the outcome delays weighted by the branch probabilities, the marginal time-to-resolution regardless of which outcome occurs.
Examples
using ComposedDistributions, Distributions
node = Resolve(:death => (Gamma(1.5, 1.0), 0.3),
:disch => (Gamma(2.0, 1.5), 0.7))
as_mixture(node)See also
Resolve: the composer type
ComposedDistributions.build_priors Function
build_priors(table; priors, default) -> NamedTupleAssemble the nested prior NamedTuple from a params_table inventory.
build_priors(table; priors, default) turns the flat parameter table into the nested NamedTuple that a downstream composed_parameters_model (and update) expect, so users define priors against the flat table rows rather than by hand-matching the tree.
For each row the prior is chosen in order: 2. a user priors override for that (edge, param), if present, else
the row's attached
prior(anuncertainparameter's spec rides the table'spriorcolumn), if present, elsedefault(row), the per-row default (support-deriveddefault_priorunless a differentdefaultfunction is given).
By default every row gets a sensible support-derived prior, so build_priors(params_table(tree)) alone yields a complete prior NamedTuple. A user overrides only the parameters they care about (brms-style partial override) through priors.
row is a NamedTuple (; edge, param, value, support) (the table's columns for that row), so a custom default can pick a prior from the parameter's support.
Arguments
table: aparams_tableinventory (any Tables.jl column table withedge,param,value,supportcolumns).
Keyword Arguments
priors: per-parameter overrides, either a(edge, param) => priormapping (e.g. aDict) or a nestedNamedTuplekeyed like the tree ((onset_admit = (shape = prior,),)); only the listed parameters are overridden (default: empty).default: a functionrow -> priorfor rows not overridden (default:default_prior, deriving the prior family from the parameter's support).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
tbl = params_table(tree)
# Support-derived defaults everywhere, overriding only one parameter.
nested = build_priors(tbl;
priors = (onset_admit = (shape = truncated(Normal(2, 0.5); lower = 0),),))
nested.onset_admit.shapeSee also
params_table: the flat inventory keyed against.default_prior: the support-derived per-row default.composed_parameters_model(downstream),update: consume the result.
ComposedDistributions.centred_pool_rows Function
centred_pool_rows(
dist
) -> Vector{Tuple{Tuple, Symbol, Pool}}The centred pooled parameters' (path, param, pool) triples, in table order.
Collected once per params_table walk (typically at distribution_to_logdensity construction time), so a tree with only non-centred (or no) pooling adds no per-evaluation cost. Reached by qualified name from outside this package — DistributionsInference.jl's fit-protocol extension calls this directly to find the rows pool_centred_logprior needs to score.
Arguments
- the composed tree whose centred-pooled rows are collected.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
ComposedDistributions.centred_pool_rows(tree)See also
sourceComposedDistributions.child_logpdf Function
child_logpdf(node, x, offset, n)A composer node's contribution to the joint log density, scoring its n-wide slice x[offset + 1 : offset + n] of the flat event vector. Part of the public composer-node extension contract, alongside child_nleaves and child_rand!; see Writing a new composer node. A univariate leaf scores the one scalar at its slot; a nested node recurses into its children, passing each its own offset.
Arguments
node: the composer node or leaf distribution to score.x: the flat event vector being scored.offset: the zero-based start index of this node's slice inx.n: the slice width,child_nleaves(node).
Examples
using ComposedDistributions, Distributions
node = compose((onset = Gamma(2.0, 1.0), report = Gamma(1.5, 1.0)))
n = ComposedDistributions.child_nleaves(node)
x = collect(values(rand(node)))
ComposedDistributions.child_logpdf(node, x, 0, n)See also
child_nleaves: the slice widthnto pass.child_rand!: draw a node into its slice of the flat vector.
ComposedDistributions.child_nleaves Function
child_nleaves(node)Number of flat event-vector slots a composer node occupies (one per leaf below it). Part of the public composer-node extension contract, alongside child_logpdf and child_rand!; see Writing a new composer node. A univariate leaf occupies one slot; a nested node occupies the sum of its children's widths.
Arguments
node: the composer node or leaf distribution whose flat slot width is read.
Examples
using ComposedDistributions, Distributions
node = compose((onset = Gamma(2.0, 1.0), report = Gamma(1.5, 1.0)))
ComposedDistributions.child_nleaves(node)See also
child_logpdf: score a node's slice of the flat vector.child_rand!: draw a node into its slice of the flat vector.
ComposedDistributions.child_rand! Function
child_rand!(out, offset, rng, node)Draw a composer node in place into its slice out[offset + 1 : offset + n] of the flat output vector, where n is child_nleaves(node). Returns nothing. Part of the public composer-node extension contract, alongside child_nleaves and child_logpdf; see Writing a new composer node. A univariate leaf writes its one slot; a nested node fills its slice by recursing into its children.
Arguments
out: the flat output vector to write into.offset: the zero-based start index of this node's slice inout.rng: the random number generator to draw from.node: the composer node or leaf distribution to draw.
Examples
using ComposedDistributions, Distributions, Random
node = compose((onset = Gamma(2.0, 1.0), report = Gamma(1.5, 1.0)))
out = zeros(ComposedDistributions.child_nleaves(node))
ComposedDistributions.child_rand!(out, 0, Random.default_rng(), node)
outSee also
child_nleaves: the slice width written.child_logpdf: score a node's slice of the flat vector.
ComposedDistributions.choose Function
choose(alternatives::Pair...; selector) -> ChooseBuild a Choose data-selected disjunction from name => dist alternatives.
Each alternative is name => dist: the alternative name (a Symbol) and its independent sub-distribution. The selector keyword names the data field a record carries to pick an alternative (default :kind). At least two alternatives are required and their names must be unique.
Arguments
alternatives: thename => distpairs, each an independent sub-distribution (aUnivariateDistributionor a nested composer). A single named tuple(name = dist, …)is the equivalent positional spelling for hand-written alternatives, kept separate from theselectorkeyword; use Pairs for data-driven or computed names.
Keyword Arguments
selector: the row field name (Symbol) whose value picks an alternative (default:kind).
Examples
using ComposedDistributions, Distributions
# An index case (a short delay) vs a sourced case (a longer coupled delay),
# selected by the row's `:kind` field.
d = choose(:index => Gamma(2.0, 1.0),
:sourced => Gamma(4.0, 1.5))
# Score the alternative the data names.
logpdf(d, 3.0; kind = :index)using ComposedDistributions, Distributions
# The equivalent named tuple spelling; `selector` stays a keyword.
d = choose((index = Gamma(2.0, 1.0), sourced = Gamma(4.0, 1.5)); selector = :kind)
logpdf(d, 3.0; kind = :index)See also
Choose: the disjunction type
ComposedDistributions.compete Function
compete(outcomes::Pair...) -> CompeteBuild a racing-hazard Compete node from bare name => delay outcomes: the cause-specific delays race, the first wins, and the winning probability of each cause is derived from the hazards (cause coupled to timing).
Each outcome is name => delay (a bare delay, no branch probability). At least two outcomes are required. To give an explicit fixed probability per outcome (a mixture where cause is independent of timing) use resolve instead.
Arguments
outcomes: two or more barename => delaypairs, each giving the outcome name (aSymbol) and its cause-specific delay distribution (no branch probability). A single named tuple(name = delay, …)is the equivalent positional spelling for hand-written outcomes; use Pairs for data-driven or computed names.
Examples
using ComposedDistributions, Distributions
node = compete(:death => Gamma(2.0, 3.0), :recover => Gamma(3.0, 2.0))
probs(node)using ComposedDistributions, Distributions
# The equivalent named tuple spelling for hand-written outcomes.
node = compete((death = Gamma(2.0, 3.0), recover = Gamma(3.0, 2.0)))
probs(node)See also
Compete: the composer typeresolve: the fixed-probability sibling constructor ((delay, prob))Distributions.probs: the derived per-cause winning probabilitiescompose: the front-end that nests the node as a branch
ComposedDistributions.component_names Function
component_names(_::Sequential{names}) -> AnyThe child names of a composed distribution.
Returns the tuple of names for a composer's direct children: the step names of a Sequential chain, the branch names of a Parallel set, or the outcome names of a Resolve node. These edge names key the parameter inventory, distinct from the flat event names of _flat_event_names.
Examples
using ComposedDistributions, Distributions
oa = LogNormal(1.5, 0.4)
ad = Gamma(2.0, 1.0)
tree = compose((onset_admit = [oa, ad],))
ComposedDistributions.component_names(tree)See also
event_names: the public edge-name accessor_flat_event_names: the flat event names
ComposedDistributions.compose Function
Build a nested composer stack from a friendly front-end input.
compose lowers a NamedTuple, a Tables.jl table, or a nested matrix to the same Sequential / Parallel stack. It is a constructor over the composers, not a new tree type.
Arguments
input: the front-end to lower, one of the three forms below.
Inputs
NamedTuple(named, recursive): aParallelover the named children. A child that is itself aNamedTuplenests as aParallel, a child that is aVectororTupleof distributions nests as aSequential, and a bareUnivariateDistributionis a leaf branch.Tables.jl table with
nameanddistcolumns: aParallelover the rows, the column-table equivalent of a flatNamedTuple. An optionalchaincolumn folds rows sharing a non-zero group id into aSequentialbranch, and an optionalcompete/probcolumn pair folds rows sharing a non-zerocompeteid into aResolvenode whoseprobentries are the branch probabilities (each inand summing to one per group). nested
Matrixof distributions: rows areParallelbranches and the columns within a row areSequentialsteps. This orientation is canonical, so a one-column matrix is parallel leaf branches (one row each) and a one-row matrix is aParallel-of-one wrapping aSequentialof the row's columns.
A varargs-pairs spelling, compose(:a => d1, :b => d2, ...), is also available as a convenience over the NamedTuple form (see the compose(pairs::Pair{Symbol}...) method below); the NamedTuple form above stays primary.
Contract
compose always returns a composer, never a bare univariate leaf. A single branch stays a Parallel-of-one and a single step a one-element Sequential; the wrapper is never collapsed away. A bare leaf is used directly at the scoring layer, where downstream helpers such as record_distributions and composed_distribution_model accept a bare UnivariateDistribution, so callers do not need compose to pass one through.
Examples
using ComposedDistributions, Distributions
# A regular 2x2 grid built three ways, all equal.
nt = (r1 = [Gamma(2.0, 1.0), LogNormal(0.5, 0.4)],
r2 = [Gamma(1.0, 1.0), Gamma(3.0, 1.0)])
table = (name = [:a, :b, :c, :d],
dist = [Gamma(2.0, 1.0), LogNormal(0.5, 0.4),
Gamma(1.0, 1.0), Gamma(3.0, 1.0)],
chain = [1, 1, 2, 2])
mat = [Gamma(2.0, 1.0) LogNormal(0.5, 0.4); Gamma(1.0, 1.0) Gamma(3.0, 1.0)]
compose(nt) == compose(table) == compose(mat)The three front-ends are chosen to build identical stacks for the same structure, as the example above shows.
See also
Sequential,Parallel,Resolve: the composers
ComposedDistributions.default_prior Function
default_prior(
row
) -> Union{Distributions.Uniform{Float64}, Distributions.Normal, Distributions.Truncated{Distributions.Normal{T}, Distributions.Continuous, T1, _A, Nothing} where {T<:Real, T1<:Real, _A<:Union{Nothing, T1}}}Pick a default prior for a parameter row, brms-style.
default_prior(row) is the per-row default build_priors uses for rows the user does not override. row is a (; edge, param, value, support) NamedTuple (a params_table row); the prior family follows the parameter's own natural domain (classified by name), not the leaf's variate support:
a probability parameter, support
[0, 1](abranch_probsrow) ->Uniform(0, 1).a scale/shape/rate-type parameter (
:sigma,:scale,:shape,:rate, ...) ->truncated(Normal(value, scale); lower = 0), positive by construction even for a location-family delay (aNormal/Affine(Normal)sigma).a location parameter (
:mu,:location, aUniformbound) ->Normal(value, scale), unconstrained since the location lives on the whole line even for a positive-support delay.otherwise, an unmapped name falls back to the variate support: a non-negative support ->
truncated(Normal(value, scale); lower = 0), elseNormal(value, scale).
The spread scale defaults to max(abs(value), 1), a weakly-informative width that scales with the parameter's magnitude.
Arguments
row: aparams_tablerow(; edge, param, value, support).
Examples
using ComposedDistributions, Distributions
# A positive scale parameter -> a positive-truncated default.
default_prior((; edge = :onset_admit, param = :scale,
value = 1.0, support = (0.0, Inf)))DistributionsInference's with_priors
DistributionsInference.with_priors applies the same support-derived heuristic generically, over any fit-protocol object's parameter_rows (a flat, dotted-name row schema), not just a ComposedDistributions tree. It is a separate implementation, not a thin wrapper over this one: DistributionsInference depends on ComposedDistributions, not the reverse, so this package's own default_prior/build_priors cannot delegate to it without inverting that dependency. The two stay independent, parallel implementations of the same heuristic for their respective row shapes.
See also
build_priors: assembles the nested prior NamedTuple, using this as the per-row default and accepting overrides.
ComposedDistributions.elapsed_between Function
Distribution of the elapsed distance between two named events of a chain.
elapsed_between(chain, from, to) returns the LAW of the elapsed distance from event from to event to on a Sequential chain, as an ordinary univariate distribution: the convolution of the steps strictly between the two events. elapsed_between(chain, to) is the common origin-to-to form (the convolution of the chain prefix up to to).
This is the distribution-level counterpart to the sample-level difference of two event positions, and is deliberately NOT difference: two events on one chain descend from a shared origin, so their absolute positions are not independent and difference(chain, chain) would double-count the shared leading steps. elapsed_between convolves only the intervening steps, so it is the correct same-chain law. The result discretises and composes like any other univariate component, and recomputes when an upstream step's parameters change.
Only a plain continuous chain (every step a delay leaf or a nested plain chain, no branching) has a single elapsed-distance law between two of its events; a chain with a branching step, or a pair whose ordering has no single scalar law, is rejected with a clear error. An uncertain-leaf chain must be pinned with update first, as the template density is not the marginal.
Arguments
chain: aSequentialchain.from: the earlier event name (origin-to-toform omits it).to: the later event name.
Examples
using ComposedDistributions, Distributions
chain = sequential(:origin_onset => Gamma(2.0, 1.0),
:onset_admit => LogNormal(0.5, 0.4),
:admit_exit => Gamma(1.5, 1.0))
# Between two named events: the single intervening step.
law = elapsed_between(chain, :onset, :admit)
rand(law)
# Origin to a named intermediate event: the convolution of the prefix.
law0 = elapsed_between(chain, :admit)
mean(law0)See also
convolved: the chain-step convolution this returns.event_names: the chain's event names, the valid selectors.difference: the difference of two INDEPENDENT observed totals.
ComposedDistributions.event Function
event(d)Fetch a composed distribution's child (event/edge), or descend a name path.
event(d, path...) returns the sub-distribution of d at the named location: a single Symbol fetches a direct child (a branch of a Parallel, a step of a Sequential, an outcome delay of a Resolve, or an alternative of a Choose); multiple Symbols, or a single dotted-path Symbol (:admit_path.admit_death, as in params_table's edge column), descend the tree one name per step. Throws an ArgumentError naming the valid children if a name along the path is not a child at that level (mirroring update/prune/splice).
Arguments
d: the composed distribution to look up a child of (or descend into).path: one or more edge/event names (Symbols) fromddown to the target, or a single dotted-pathSymbol.
Examples
using ComposedDistributions, Distributions
tree = compose((admit_path = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4))),
onset_recover = Gamma(3.0, 1.0)))
event(tree, :onset_recover)
event(tree, :admit_path, :admit_death)See also
event_names: list a node's flat event namesevent_tree: the nested tree of event names
ComposedDistributions.event_increments Function
event_increments(
d::Union{Parallel, Sequential},
rec::NamedTuple
) -> NamedTupleInvert event_times: convert a record of absolute positions back into the per-step increments the scorer (logpdf) consumes.
event_increments(d, event_times(d, record)) == record. Pass a Vector of records for a batch.
Arguments
d: the composed tree (aSequentialorParallel) the record was drawn from; a bare leaf or one_of node errors, having no per-step chain.record: a single record (NamedTuple) of absolute positions, or aVectorof such records for a batch.
Examples
using ComposedDistributions, Distributions
tree = sequential(:a => LogNormal(0.5, 0.4), :b => Gamma(2.0, 1.0))
event_increments(tree, (a = 1.0, b = 3.0))See also
event_times: the forward transform.
ComposedDistributions.event_names Function
event_names(
d::Union{ComposedDistributions.AbstractOneOf, Parallel, Sequential}
) -> Union{Tuple{Vararg{Symbol}}, Tuple{Symbol, Vararg{Any}}}The flat event names of a composed distribution.
event_names(d) returns the tuple of event names in flat depth-first order: the root origin event followed by one target event per leaf edge. An inner composer's events are exposed, so compose((path = [a, b],)) lists the inner (:onset, ...) events rather than just the (:path,) edge. Event names are derived from the edge names (an edge :onset_admit gives origin :onset and target :admit); a positional default edge contributes :event_i. These event names key a data row, distinct from the nested edge/child structure of event_tree (whose first level is the top-level child names).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = LogNormal(1.5, 0.4),
admit_death = Gamma(2.0, 1.0)))
event_names(tree)See also
event_tree: the nested tree of event namesevent: fetch a child or subtree by name pathparams_table: the parameter table
ComposedDistributions.event_times Function
event_times(
d::Union{Parallel, Sequential},
rec::NamedTuple
) -> NamedTupleConvert a drawn record of per-step increments into absolute positions measured from the composed tree's origin.
A draw from d (rand(d)) records each event as an increment from its predecessor. event_times accumulates these into absolute positions: chain steps sum along the chain, parallel branches each measure from the shared origin, and a resolved/racing node reports the position of the outcome that fired. The result is keyed exactly like the input record and stays in unitless distance from the origin. event_increments is the inverse.
Pass a Vector of records for a batch; each row is transformed independently.
Arguments
d: the composed tree (aSequentialorParallel) the record was drawn from; a bare leaf or one_of node errors, having no per-step chain.record: a single drawn record (NamedTuple) of per-step increments, or aVectorof such records for a batch.
Examples
using ComposedDistributions, Distributions
tree = compose((
path = sequential(:step_a => LogNormal(0.5, 0.4),
:step_b => Gamma(2.0, 1.0)),
side = Gamma(1.5, 1.0)))
event_times(tree, (path_step_a = 1.0, path_step_b = 2.0, side = 3.0))See also
event_increments: the inverse transform.event_names: the flat event layout this reuses.
ComposedDistributions.event_tree Function
event_tree(d::Union{Parallel, Sequential}) -> NamedTupleThe nested tree of event names of a composed distribution.
event_tree(d) returns the event-name structure as data: a nested NamedTuple keyed by child name down to the leaves, mirroring the tree. Its first level is the top-level child names (the old top-level event_names result); a Sequential/Parallel/Choose child recurses to its own nested NamedTuple, a Resolve child to its outcome names, and a leaf to its own name. Pair with event_names for the flat per-event layout that matches rand/mean/var.
Examples
using ComposedDistributions, Distributions
tree = compose((admit_path = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4))),
onset_recover = Gamma(3.0, 1.0)))
event_tree(tree)See also
event_names: the flat per-event namesevent: fetch a child or subtree by name path
ComposedDistributions.extra_leaf_params Function
extra_leaf_params(leaf) -> AnyThe extra, modifier-owned parameters of a leaf, keyed by name.
A NamedTuple mapping each extra-parameter name to a (value, support) NamedTuple: value is the parameter's current value and support the (lower, upper) bounds a default prior is derived from. The default (a plain leaf, no extras) is the empty NamedTuple (;), and a Truncated peels to its untruncated inner delay. A modifier layer that owns a free parameter which is not one of the inner delay's native parameters plugs in by defining this on its own wrapper type. The thinning factor of thin(d, p) (ModifiedDistributions' ThinOp) is the first instance: it reports (thin = (value = p, support = (0.0, 1.0)),), at which point params_table surfaces a :thin row and update round-trips it.
Arguments
leaf: the (possibly wrapped) leaf distribution to inspect.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.extra_leaf_params(Gamma(2.0, 1.0))See also
set_extra_leaf_params: the setter dual that rebuilds the leaf.leaf_param_names: appends the extra names after the native ones.
ComposedDistributions.flat_dimension Function
flat_dimension(
d::ComposedDistributions.AbstractComposedDistribution
) -> AnyThe estimated parameter dimension of a composed distribution.
flat_dimension(d) is the number of scalar estimated parameters: the count of uncertain specs across the tree, i.e. the params_table rows whose prior column carries a spec. A fixed (non-uncertain) leaf contributes nothing, so a tree with no uncertain leaves has flat dimension 0. It is the length of the flat vector flatten produces and unflatten consumes. Read straight off the same compile-time layout walk unflatten uses (a literal count baked in at generation time), so it cannot drift from the codec.
Arguments
d: a composed distribution.
Examples
using ComposedDistributions, Distributions
tree = compose((
onset_admit = uncertain(Gamma(2.0, 1.0); shape = LogNormal(log(2.0), 0.2)),
admit_death = LogNormal(0.5, 0.4)))
# Public but not exported; reach it by the qualified name. Only onset_admit's
# shape is uncertain, so the dimension is 1.
ComposedDistributions.flat_dimension(tree)See also
sourceComposedDistributions.flatten Function
flatten(
d::ComposedDistributions.AbstractComposedDistribution,
nt::NamedTuple
) -> AnyFlatten a nested parameter NamedTuple to the estimated flat vector.
flatten(d, nt) reads nt (keyed like params(d), the shape update consumes) at each estimated params_table row (an uncertain spec's parameter) and returns those values as a Vector, in table order restricted to the spec'd rows. A fixed parameter is not read. It is the inverse of unflatten: flatten(d, unflatten(d, x)) == x.
Shares the same compile-time layout walk unflatten uses (a thin generated view over it), so the two cannot drift apart.
Arguments
d: the composed distribution whose table fixes the order.nt: a nested parameterNamedTuplekeyed likeparams(d).
Examples
using ComposedDistributions, Distributions
tree = compose((
onset_admit = uncertain(Gamma(2.0, 1.0); shape = LogNormal(log(2.0), 0.2)),
admit_death = LogNormal(0.5, 0.4)))
# The estimated vector is 1-long (onset_admit.shape); round-trip it.
# Public but not exported; reach the codec by the qualified name.
nt = ComposedDistributions.unflatten(tree, [2.0])
ComposedDistributions.flatten(tree, nt)See also
unflatten: the inverse, flat vector -> nested NamedTuple.flat_dimension: the estimated length.
ComposedDistributions.free_leaf Function
free_leaf(leaf) -> AnyInnermost free delay of a (possibly wrapped) leaf.
The base identity contract: a plain leaf is its own free leaf, and a Truncated peels to its untruncated inner delay (the truncation bounds are fixed structure, not free parameters). A wrapper type (censoring in CensoredDistributions, the modifiers in ModifiedDistributions) adds its own method dispatching on its own type, so a composed leaf is transparent to the prior/params interface. Pair with rewrap_leaf, which rebuilds the same wrapper around a new inner delay.
Arguments
leaf: the (possibly wrapped) leaf distribution to peel.
Examples
using ComposedDistributions, Distributions
free_leaf(truncated(Gamma(2.0, 1.0); upper = 10.0))See also
rewrap_leaf: the inverse rebuild.
ComposedDistributions.has_uncertain Function
has_uncertain(
d::Union{ComposedDistributions.AbstractOneOf, Choose, Parallel, Sequential}
) -> Union{Missing, Bool}Whether a composed distribution still contains an Uncertain leaf.
An Uncertain leaf delegates every Distributions method except rand to its template until it is collapsed with update(tree, params), so scoring or summarising a raw tree that still holds an Uncertain leaf silently uses the template's central values instead of the marginal — a silent wrong answer, not an error. Guard a scoring/fitting loop with this predicate:
collapsed = update(tree, fitted_params)
@assert !has_uncertain(collapsed) # catch a forgotten update before scoring
logpdf(collapsed, x)has_uncertain walks the tree (through Sequential/Parallel/Choose/the one_of composers, and through wrapper leaves via the _uncertain_specsrouting hook so ashared/modifier-wrapped uncertain leaf is still seen) and returnstrueas soon as any leaf carries a spec; a fully collapsed tree returnsfalse.
Arguments
d: the composed distribution, node, or leaf to check.
Examples
using ComposedDistributions, Distributions
u = uncertain(Gamma(2.0, 1.0); shape = LogNormal(log(2.0), 0.2))
tree = compose((onset_admit = u, admit_death = LogNormal(0.5, 0.4)))
has_uncertain(tree) # an uncertain leaf remains
collapsed = update(tree, (onset_admit = (shape = 3.0, scale = 1.5),
admit_death = (mu = 0.7, sigma = 0.5)))
has_uncertain(collapsed) # resolved: falseSee also
update: collapse an uncertain leaf to a concrete distribution.has_varying: the same guard for the observed (varying) case.
ComposedDistributions.has_varying Function
has_varying(d::Varying) -> BoolWhether a composed distribution still contains an un-resolved Varying leaf.
A Varying leaf delegates every Distributions method to its reference until the tree is resolved with instantiate(tree, ctx), so scoring or sampling a raw tree that still holds a Varying leaf silently uses the reference (e.g. the t = 0 delay) instead of the per-record value — a silent wrong answer, not an error. Guard a scoring/sampling call in a fitting loop with this predicate:
resolved = instantiate(tree, Context(time = t))
@assert !has_varying(resolved) # catch a forgotten instantiate before scoring
logpdf(resolved, x)has_varying walks the tree and returns true as soon as any leaf is a Varying; a fully stationary or fully-instantiated tree returns false.
Arguments
d: the composed distribution, node, or leaf to check.
Examples
using ComposedDistributions, Distributions
tree = compose((onset = varying(t -> Gamma(2.0, 1.0 + 0.1t)),
admit = LogNormal(0.5, 0.4)))
has_varying(tree) # a varying leaf remains
has_varying(instantiate(tree, Context(time = 5.0))) # resolved: falseSee also
instantiate: resolve every varying leaf against a context.Varying: the context-indexed leaf.has_uncertain: the same guard for the latent (uncertain) case.
ComposedDistributions.inspect Function
inspect(io::IO, d)Print a composed distribution's full nested detail.
inspect(io, d) walks the same tree as show but prints each leaf's full text/plain representation (every field), so it is the opt-in companion to the compact structural show. A composer node prints its header and recurses; a leaf prints its detailed representation indented under its name. Writes to io (default stdout) and returns nothing.
Arguments
io: the IO stream to print to (defaultstdout).d: the composed distribution (or bare leaf) to inspect.
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
inspect(tree)See also
event_tree: the nested tree of event namesparams_table: the flat parameter inventory
ComposedDistributions.instantiate Function
instantiate(d, _::Nothing) -> AnyResolve a composed tree (or a leaf) against a Context.
instantiate(d, ctx) walks a composed distribution and returns the same tree with every Varying leaf replaced by its distribution at the context's covariate; a fixed leaf is returned unchanged (the identity default), so a stationary tree is untouched and passing nothing is always a no-op. The result is a fully concrete composer that scores, samples and convolves exactly as a hand-built stationary tree would — non-stationarity is resolved here, before those steps, so the convolution / renewal layer receives a concrete kernel per context.
Arguments
d: a composer, a leaf, or aVaryingleaf.ctx: aContextof covariates, ornothing(a no-op).
Examples
using ComposedDistributions, Distributions
chain = sequential(:onset_admit => varying(t -> Gamma(2.0, 1.0 + 0.1t)),
:admit_death => LogNormal(0.5, 0.4))
at_day5 = instantiate(chain, Context(time = 5.0)) # concrete, stationary chain
observed_distribution(at_day5) # the convolution kernel at t = 5See also
observed_distribution: collapse the resolved chain to its kernel.
ComposedDistributions.leaf_ctor Function
leaf_ctor(leaf) -> TypeThe constructor that rebuilds a leaf's free delay from a positional tuple of parameter values, in leaf_param_names order (excluding any trailing extra_leaf_params, which _update_leaf splits off and re-attaches around the rebuild).
Reconstruction is how an updated parameter vector becomes a distribution again: update, the unflatten then update posterior read-back, uncertain's pinning path, a tied leaf's signature, and a pooled population's template all rebuild a leaf this way. The base identity returns the inner delay's type constructor, which is right for a Distributions.jl family whose params are its constructor arguments.
A leaf type whose free parameters are not its native constructor arguments overrides this. A moment-parameterised wrapper is the motivating case: it reports moments (a mean and a standard deviation) as its parameters, and it carries its family in a type parameter, so the bare UnionAll cannot be called positionally. Such a type returns a callable that supplies whatever the value tuple alone does not carry.
An override must return an egal-stable callable: two structurally identical leaves must return === constructors, since _tie_signature groups tied leaves by this value. A callable closing over a type parameter is egal-stable; one closing over a runtime value is not, and would make tie wrongly reject two compatible leaves. Prefer a callable struct over an anonymous closure.
Arguments
leaf: the leaf whose free delay is rebuilt.
Examples
using ComposedDistributions, Distributions
ctor = ComposedDistributions.leaf_ctor(Gamma(2.0, 1.0))
ctor(3.0, 1.5)See also
free_leaf: peel to the inner free delay.rewrap_leaf: re-apply the fixed structure around a rebuilt delay.
ComposedDistributions.leaf_detail_lines Function
leaf_detail_lines(leaf) -> Vector{SubString{String}}Per-leaf inspect detail lines for a (possibly wrapped) leaf.
The leaf-detail extension point inspect reads through _inspect_leaf: the generic method returns the leaf's full text/plain show, split into lines so a multi-line struct dump stays aligned under its tree prefix. A leaf-wrapper type (censoring in CensoredDistributions, the modifiers in ModifiedDistributions) adds its own method dispatching on its own type, to surface the inner free delay's detail instead of the wrapper's raw struct dump. Pair with uncertain_specs, the sibling extension hook for the prior column.
Arguments
leaf: the (possibly wrapped) leaf distribution to render.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.leaf_detail_lines(Gamma(2.0, 1.0))See also
free_leaf,rewrap_leaf: the sibling leaf-wrapper hooks.inspect: the tree-printing entry point this feeds.
ComposedDistributions.leaf_mean Function
leaf_mean(leaf) -> AnyMean of a (possibly wrapped) leaf, seen through its fixed wrapper structure.
The default is that of the inner free delay (mean(free_leaf(leaf))): a plain leaf is its own free leaf, a Convolved free-leafs to itself and reuses its additive mean, and an Uncertain leaf free-leafs to its template (so a tree containing one reports the template moment; guard with has_uncertain first if that matters). A modifier whose transform changes the moment (an Affine scale/shift) overrides this to report its own analytic moment.
Arguments
leaf: the (possibly wrapped) leaf distribution whose mean is read.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.leaf_mean(Gamma(2.0, 1.0))See also
leaf_var: the matching per-leaf variance.
ComposedDistributions.leaf_param_names Function
leaf_param_names(leaf) -> TupleThe estimable parameter names of a (possibly wrapped) leaf.
The inner free delay's param_names, padding with positional fallbacks (:param_1, ...) so every value has a label even when the family is unmapped, then the names of any extra_leaf_params appended in order. A censored or modified leaf delegates to its free delay (free_leaf), so the fixed wrapper structure never appears, while a thinning modifier's :thin factor rides the trailing extra-parameter slot. These names are the coordinates params_table, uncertain and build_priors key on.
Arguments
leaf: the (possibly wrapped) leaf distribution whose parameter names are read.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.leaf_param_names(Gamma(2.0, 1.0))See also
extra_leaf_params: the extra names appended after the native ones.
ComposedDistributions.leaf_var Function
leaf_var(leaf) -> AnyVariance of a (possibly wrapped) leaf, seen through its fixed wrapper structure.
The variance dual of leaf_mean: the inner free delay's variance by default (var(free_leaf(leaf))), overridden by a modifier whose transform changes the moment (an Affine).
Arguments
leaf: the (possibly wrapped) leaf distribution whose variance is read.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.leaf_var(Gamma(2.0, 1.0))See also
leaf_mean: the matching per-leaf mean.
ComposedDistributions.missing_covariates Function
missing_covariates(d, ctx::AbstractContext) -> VectorThe covariate names required_covariates(d) lists that a Context does not carry.
missing_covariates(d, ctx) returns the (possibly empty) vector of covariate names required by d but absent from ctx, so a caller can validate a data source up front — confirming every needed covariate column is present before the first instantiate — rather than discovering a gap reactively, one covariate at a time, mid-resolution.
Arguments
d: the composed distribution, node, or leaf to inspect.ctx: theContextto check.
Examples
using ComposedDistributions, Distributions
tree = compose((onset = varying(t -> Gamma(2.0, 1.0 + 0.1t)),
admit = LogNormal(0.5, 0.4)))
missing_covariates(tree, Context(region = "a"))
missing_covariates(tree, Context(time = 4.0))See also
required_covariates: the full set this checks against.instantiate: resolve a tree once its covariates are all present.
ComposedDistributions.observed_distribution Function
observed_distribution(
d::Distributions.UnivariateDistribution
) -> Distributions.UnivariateDistributionThe univariate scalar a downstream observation observes for a composer.
An observation model observes one quantity, so lowering a composer first reduces it to that quantity:
a
ConvolvedorResolveis already univariate (the observed sum, resp. the marginal time-to-resolution) and is returned unchanged;a
Sequentialchain's observed quantity is the total elapsed time from origin to the terminal event, the convolution of its steps, returned as aConvolved.
A Parallel has several independent endpoints and so no single observed scalar; it is not lowered here.
Examples
using ComposedDistributions, Distributions
seq = Sequential(Gamma(2.0, 1.0), LogNormal(0.5, 0.4))
observed_distribution(seq)See also
convolved: the chain-step convolution
ComposedDistributions.occurrence_probability Function
occurrence_probability(c::Resolve) -> AnyThe probability that any (non-no-event) outcome occurs for a fixed-probability Resolve node: one minus the no-event branch mass.
See also: Distributions.probs
occurrence_probability(c::Compete) -> AnyThe probability that any (non-no-event) outcome occurs for a one_of node.
For a racing-hazard Compete node occurrence_probability is the sum of the derived per-cause split Distributions.probs returns (one for proper, eventually-certain causes; the resolved mass for a defective node). For a fixed-probability Resolve node it is one minus the no-event branch mass.
Arguments
c: theCompetenode whose any-event probability to read.
Examples
using ComposedDistributions, Distributions
node = compete(:death => Gamma(2.0, 3.0), :recover => Gamma(3.0, 2.0))
occurrence_probability(node)See also
Distributions.probs: the per-outcome winning split this sums.
ComposedDistributions.parallel Function
parallel(branches::Pair...) -> ParallelCompose univariate distributions into Parallel branches.
Lowercase verb mirroring sequential / resolve: the public constructor for a Parallel branch set. Pass branch distributions positionally (default names :branch_1, :branch_2, ...) or name => dist pairs to name the branches; a branch may itself be a Sequential, Resolve, Compete, Choose or nested set. Prefer this verb over the bare struct constructor.
Arguments
branches: the branch distributions, either as positional distributions or asname => distpairs naming each branch. A single named tuple(name = dist, …)is the equivalent positional spelling for hand-written branches; use Pairs for data-driven or computed names.
Examples
using ComposedDistributions, Distributions
d = parallel(:admit => Gamma(2.0, 1.0), :notif => LogNormal(1.0, 0.5))
event_names(d)using ComposedDistributions, Distributions
# The equivalent named tuple spelling.
d = parallel((admit = Gamma(2.0, 1.0), notif = LogNormal(1.0, 0.5)))
event_names(d)See also
Parallel: the composer typesequential,resolve,compete: the sibling constructorscompose: the NamedTuple/table/matrix front-end
ComposedDistributions.param_names Function
param_names(
_::Distributions.Normal
) -> Tuple{Symbol, Symbol}The scalar parameter names of a leaf distribution, matched positionally to params(leaf).
Distributions.jl exposes parameter values through params but not their names, so the common families are mapped explicitly here; anything unmapped falls back to :param_1, :param_2, ....
A leaf type whose free parameters are not the native family's overrides this, in step with leaf_ctor: the two together fix the coordinates that params_table, uncertain, build_priors and the flat codec work in. A moment-parameterised wrapper naming a mean and a standard deviation, rather than a shape and a scale, is the motivating case.
Arguments
- the leaf distribution whose parameter names are read.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.param_names(Gamma(2.0, 1.0))See also
leaf_ctor: the matching rebuild.
ComposedDistributions.param_priors Function
param_priors(tree; kwargs...) -> NamedTupleBuild the nested prior NamedTuple straight from a composed distribution.
param_priors(tree; priors, default) is a thin convenience over build_priors(params_table(tree)): it reads the parameter inventory of the composed distribution tree and assembles the nested prior NamedTuple in one call, forwarding the same keyword surface. It adds no prior logic of its own.
The result is spec-shaped (a nested NamedTuple of distributions keyed like the tree), so it feeds update directly: update(tree, param_priors(tree)) promotes every free parameter to uncertain with its default prior — the explicit estimate-everything path under uncertain-first (a bare tree estimates nothing). Pass priors to swap in your own spec for named parameters.
Arguments
tree: a composed distribution fromcompose.
Keyword Arguments
priors: per-parameter overrides, either a(edge, param) => priormapping or a nestedNamedTuplekeyed like the tree; only the listed parameters are overridden (default: empty).default: a functionrow -> priorfor rows not overridden (default:default_prior).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
priors = param_priors(tree)
priors.onset_admit.shapeSee also
build_priors: the underlying table-based assembly.params_table: the parameter inventory read internally.
ComposedDistributions.params_table Function
params_table(
d::Union{ComposedDistributions.AbstractOneOf, Choose, Parallel, Sequential}
) -> ComposedDistributions.ParamsTable{@NamedTuple{edge::Vector{Symbol}, param::Vector{Symbol}, value::Vector{Any}, support::Vector{Any}, prior::Vector{Any}}}Flatten a composed distribution's parameters into a prior-definition table.
params_table(d) returns a Tables.jl column table (a ParamsTable wrapping a NamedTuple of equal-length column vectors, so Tables.istable(params_table(d)) is true and it prints as a padded table); wrap it in DataFrame for a DataFrame. It has one row per scalar free parameter of the composed distribution d, with columns:
edge: the dotted path of names to the parameter's edge/leaf (e.g.:onset_admit, or:resolution.branch_probsinside aResolve).param: the parameter name (e.g.:mu,:sigma; positional:param_iwhere the family is unmapped).value: the current parameter value.support: the(minimum, maximum)variate support of that edge's distribution, the domain a prior over the edge must respect (fromminimum/maximum/support).prior: the attached prior of anuncertainparameter (its spec distribution), ornothingfor a fixed parameter.build_priorsuses a non-nothingentry ahead of its per-row default.
Define priors against the rows of this table instead of hand-matching parameter names. Built from params (nested, name-keyed values) plus the edge distributions' support.
For a Choose node the alternatives' independent per-branch params are namespaced per alternative (index.… / sourced.…), one row-group per alternative. A parameter tied across alternatives via shared(:tag, ...) is inventoried once under its tag edge and sampled once, so a value tied across the index and sourced branches appears as a single row-group.
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = LogNormal(1.5, 0.4),
admit_death = Gamma(2.0, 1.0)))
tbl = params_table(tree)
tbl.edge # a column; wrap the table in `DataFrame(tbl)` for a DataFrameSee also
params: the nested name-keyed valuesevent_names,event: name introspection
ComposedDistributions.pool Function
pool(
group::Symbol;
...
) -> Pool{_A, true, P} where {_A, P<:(Uncertain{Distributions.Continuous, Distributions.LogNormal{Float64}})}
pool(
group::Symbol,
population::Distributions.UnivariateDistribution;
noncentred
) -> PoolDeclare a parameter partially pooled across the leaves of a group, drawn from a shared population distribution.
pool(group, population) returns a Pool spec to place inside an uncertain leaf where a prior would go, e.g.
uncertain(Gamma(2.0, 1.0);
shape = pool(:district,
uncertain(LogNormal(0.0, 1.0); mu = Normal(0.0, 1.0),
sigma = truncated(Normal(0.0, 1.0); lower = 0.0))))reading as: shape is partially pooled across the :district leaves — each district's shape is drawn from one shared LogNormal population whose (mu, sigma) are estimated. The population is an ordinary distribution; build it with uncertain so its free parameters carry their priors through the same machinery as any uncertain leaf (those become the hyperparameter rows <group>.mu, <group>.sigma, ...). The leaves that name the same group are one population, grouped by tag the way shared groups tied leaves. pool(group) uses a default estimated-LogNormal population.
A location-scale population (Normal/LogNormal) is reparameterised non-centred — one Normal(0, 1) latent per member, reconstructed loc + scale*z (Normal) or exp(loc + scale*z) (LogNormal) — keeping the CensoredDistributions-compatible [hyper..., z...] flat vector. A general population takes the centred path (each member's parameter scored directly against the population). Pass noncentred = false to force the centred form on a location-scale population; noncentred = true is rejected for a general one.
rand on a pooled leaf draws that one parameter's marginal from the population; the joint prior-predictive of a whole pooled tree (population shared across members) comes from sampling the flat priors and rebuilding with update(tree,unflatten(tree, x)).
Arguments
group: the pooling-group name (Symbol).population: the shared population distribution (default: an estimatedLogNormal). Its free parameters are the hyperparameters.
Keyword Arguments
noncentred: force the parameterisation. Defaults totruefor a location-scale (Normal/LogNormal) population,falseotherwise.
Examples
using ComposedDistributions, Distributions
# Three districts' onset->death delays with a partially pooled shape, drawn
# from a shared estimated-LogNormal population.
model = compose((
north = uncertain(Gamma(2.0, 1.0); shape = pool(:district)),
east = uncertain(Gamma(2.0, 1.0); shape = pool(:district)),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:district))))
# 2 hyperparameters + 3 latents = 5 estimated parameters.
ComposedDistributions.flat_dimension(model)See also
sourceComposedDistributions.pool_centred_logprior Function
pool_centred_logprior(rows, nt) -> AnySum each centred member's log-density against its population reconstructed at the current hyperparameters (read from the flattened draw nt).
Reached by qualified name from outside this package — DistributionsInference.jl's fit-protocol extension calls this to score a composed tree's centred-pooled population term.
Arguments
rows: the(path, param, pool)triples fromcentred_pool_rows.nt: the nestedNamedTuplefromunflattenat the same draw.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
rows = ComposedDistributions.centred_pool_rows(tree)
x = fill(0.5, ComposedDistributions.flat_dimension(tree))
nt = ComposedDistributions.unflatten(tree, x)
ComposedDistributions.pool_centred_logprior(rows, nt)See also
sourceComposedDistributions.pool_group Function
pool_group(_::Pool{group}) -> AnyThe pooling-group name (Symbol) of a Pool spec.
Examples
using ComposedDistributions
spec = pool(:district)
ComposedDistributions.pool_group(spec)See also: pool_noncentred, pool
ComposedDistributions.pool_noncentred Function
pool_noncentred(_::Pool{group, noncentred}) -> AnyWhether a Pool spec uses the non-centred (location-scale) parameterisation.
Examples
using ComposedDistributions
spec = pool(:district)
ComposedDistributions.pool_noncentred(spec)See also: pool_group, pool
ComposedDistributions.prune Function
prune(
d::Union{Choose, Parallel, Resolve, Sequential},
path::Symbol
) -> Union{Choose, Parallel, Resolve, Sequential}Drop a branch from a composed distribution (a topology edit).
prune(d, path) removes the node addressed by path from its parent, changing the tree shape. A Resolve arm is removed and the remaining branch probabilities are renormalised to sum to one; a Choose alternative or a Sequential/Parallel step is removed. The parent must keep at least the minimum number of children (two for Resolve/Choose, one for Sequential/Parallel). The result is a valid composed distribution that scores and rands. path accepts the same forms as event: varargs Symbols, a dotted Symbol, or a tuple of edge names.
prune and splice are the two topology edits (they change the tree shape); update keeps the same shape and replaces contents.
Arguments
d: the composed distribution to edit.path: the branch to drop, as varargsSymbols, a dottedSymbol, or a tuple of edge names.
Examples
using ComposedDistributions, Distributions
node = resolve(:death => (Gamma(1.5, 1.0), 0.3),
:disch => (Gamma(2.0, 1.5), 0.5),
:transfer => (Gamma(1.0, 1.0), 0.2))
tree = compose((resolution = node, onset = Gamma(1.0, 1.0)))
tree2 = prune(tree, :resolution, :transfer)
event_names(event(tree2, :resolution))See also
splice: insert a before/after step at a node (the other topology edit)update: replace a node or its values (keeps the shape)
ComposedDistributions.reconstruct Function
reconstruct(
d::ComposedDistributions.AbstractComposedDistribution,
x::AbstractVector
) -> AnyRebuild a composed distribution straight from its estimated flat vector.
reconstruct(d, x) collapses d at the estimated parameters in x, holding each fixed parameter at its template value. It is update(d,unflatten(d, x)) named as one verb, and is the flat-vector primary a per-gradient hot path routes through (DistributionsInference.jl's distribution_to_logdensity and distribution_to_turing).
Being that composition rather than a generated function of its own, reconstruct is not independently shown @inferred-concrete; the guarantee is unflatten's, and update's inferrability is inherited from it.
Arguments
d: the composed distribution to rebuild.x: a flat vector of lengthflat_dimension(d).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = uncertain(Gamma(2.0, 1.0);
shape = LogNormal(log(2.0), 0.2)),
admit_death = LogNormal(0.5, 0.4)))
ComposedDistributions.reconstruct(tree, [3.0])See also
sourceComposedDistributions.required_covariates Function
required_covariates(d) -> Dict{Symbol, Vector{Symbol}}The covariate names a composed distribution's Varying leaves and data-selected Choose disjunctions will read, keyed to the node paths that read them.
Returns a Dict{Symbol, Vector{Symbol}}: each key is a covariate name a Varying leaf's covariate field or a Choose's selector names, and each value is the dotted edge path (the same edge namespace params_table uses) of every node that reads it. A stationary tree (no Varying leaf, no data-selected Choose) returns an empty Dict.
Pair with missing_covariates to check a Context up front, reporting every covariate a fitting loop still needs instead of discovering each one reactively, one at a time, mid-instantiate.
Arguments
d: the composed distribution, node, or leaf to inspect.
Examples
using ComposedDistributions, Distributions
tree = compose((onset = varying(t -> Gamma(2.0, 1.0 + 0.1t)),
admit = LogNormal(0.5, 0.4)))
required_covariates(tree)See also
required_parameters: the symmetric sibling over unpinned parameters.missing_covariates: check aContextagainst this set.has_varying: the boolean guard this generalises.
ComposedDistributions.required_parameters Function
required_parameters(
d
) -> Vector{@NamedTuple{edge::Symbol, param::Symbol}}The unpinned (estimated) parameters a composed distribution's params_table still needs, as (edge, param) pairs.
The symmetric sibling of required_covariates: where that lists the covariate columns a tree's Varying/Choose leaves still need from a Context, this lists the parameters an uncertain leaf still needs a value for (every params_table row whose prior is not nothing). A fully concrete (pinned) tree returns an empty vector.
Arguments
d: the composed distribution, node, or leaf to inspect.
Examples
using ComposedDistributions, Distributions
tree = compose((onset = uncertain(Gamma(2.0, 1.0); shape = LogNormal(0.0, 0.3)),
admit = LogNormal(0.5, 0.4)))
required_parameters(tree)See also
required_covariates: the symmetric sibling over covariate columns.params_table: the full parameter inventory this reads.
ComposedDistributions.reserved_record_fields Function
reserved_record_fields() -> NTuple{6, Symbol}The reserved per-record field names in a scoring row.
A row passed to logpdf(tree, row) (or a table of such rows) is matched to the tree's events by name; these reserved names are read for their own meaning instead of being treated as events, and every other field must be an event of the tree. Returned as a Tuple of Symbols. Each reserved field, with its owner, type, default and when it is read:
weight/count(the scorer;Real; default1, unweighted): read when a row carries a multiplicity.obs_time(the censored scorer;Real; default none, uncensored): read when a per-record right-truncation horizonDis given.obs_window(the censored scorer;Real; default none): read when a δ-bounded window[obs_time - δ, obs_time]is given.branch_probs/branch_prob(a nestedResolve; probabilities; default the node's own): read when a row overrides a Resolve's branch split.
The covariate context read by a Varying leaf is a separate namespace: a single flat bag keyed by bare covariate name (Context(time = 4.0)), shared across every leaf and merged last-writer-wins by with_covariates, so an observed covariate and a sampler-threaded latent parameter share one channel. Covariate names are open (any Symbol), so they are not validated against a fixed registry the way these reserved row fields are.
Examples
using ComposedDistributions
reserved_record_fields()See also
event_names: the event namespace a row is otherwise matched against.params_table: the parameter namespace (dotted node paths).
ComposedDistributions.resolve Function
resolve(
outcomes::Pair...
) -> Resolve{_A, D, P, Nothing} where {_A, D<:Tuple, P<:Tuple}Build a fixed-probability Resolve node from name => (delay, branch_prob) outcomes: exactly one outcome resolves, with cause independent of timing.
Each outcome is name => (delay, branch_prob); the branch probabilities must each lie in
The last outcome's probability may be omitted (a bare name => delay): it then takes the residual 1 - sum(of the others), so a probability that is fully determined by the rest need not be written out (and cannot disagree with them). The leading probabilities must sum to at most one. Omitting any outcome but the last, or more than one, is rejected. To omit every probability (a racing-hazard node where the winning probability is derived from the hazards) use compete instead.
Arguments
outcomes: two or morename => (delay, branch_prob)pairs, each giving the outcome name (aSymbol), its delay distribution, and the probability that the outcome occurs. The last pair's probability may be omitted (a barename => delay), taking the residual1 - sum(of the others). A single named tuple(name = (delay, branch_prob), …)is the equivalent positional spelling for hand-written outcomes; use Pairs for data-driven or computed names.
Examples
using ComposedDistributions, Distributions
cfr = 0.3
node = resolve(:death => (Gamma(1.5, 1.0), cfr),
:disch => (Gamma(2.0, 1.5), 1 - cfr))
mean(node)using ComposedDistributions, Distributions
# The equivalent named tuple spelling for hand-written outcomes.
cfr = 0.3
node = resolve((death = (Gamma(1.5, 1.0), cfr),
disch = (Gamma(2.0, 1.5), 1 - cfr)))
mean(node)using ComposedDistributions, Distributions
# The discharge probability is the residual `1 - cfr`, so it is omitted.
cfr = 0.3
node = resolve(:death => (Gamma(1.5, 1.0), cfr),
:disch => Gamma(2.0, 1.5))
mean(node)See also
Resolve: the composer typecompete: the racing-hazard sibling constructor (bare delays)as_mixture: theMixtureModelloweringcompose: the front-end that nests aResolveas a branchSequential,Parallel: the sibling composers
ComposedDistributions.rewrap_leaf Function
rewrap_leaf(leaf, inner) -> AnyRebuild the same wrapper around a new inner delay inner.
The inverse of free_leaf: the base identity returns inner, and a Truncated re-applies its bounds around the rebuilt inner delay. A wrapper type adds its own method on its own type, so rewrap_leaf(leaf, free_leaf_of_new) carries the fixed structure across a parameter update.
Arguments
leaf: the wrapped leaf whose fixed structure is re-applied.inner: the new inner delay to wrap.
Examples
using ComposedDistributions, Distributions
rewrap_leaf(truncated(Gamma(2.0, 1.0); upper = 10.0), Gamma(3.0, 1.5))See also
free_leaf: peel to the inner free delay.
ComposedDistributions.sequential Function
sequential(steps::Pair...) -> SequentialCompose univariate distributions into a Sequential chain.
Lowercase verb mirroring parallel / resolve: the public constructor for a Sequential chain. Pass step distributions positionally (default names :step_1, :step_2, ...) or name => dist pairs to name the steps; a step may itself be a Parallel, Resolve, Compete, Choose or nested chain. Prefer this verb over the bare struct constructor.
Arguments
steps: the step distributions, either as positional distributions or asname => distpairs naming each step. A single named tuple(name = dist, …)is the equivalent positional spelling for hand-written steps; use Pairs for data-driven or computed names.
Examples
using ComposedDistributions, Distributions
d = sequential(:onset_admit => Gamma(2.0, 1.0),
:admit_death => LogNormal(0.5, 0.4))
event_names(d)using ComposedDistributions, Distributions
# The equivalent named tuple spelling.
d = sequential((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
event_names(d)See also
Sequential: the composer typecompose: the NamedTuple/table/matrix front-end
ComposedDistributions.set_extra_leaf_params Function
set_extra_leaf_params(
leaf,
_::NamedTuple{()}
) -> Distributions.TruncatedSet a leaf's extra, modifier-owned parameters by name and rebuild the leaf.
The setter dual of extra_leaf_params: vals is a NamedTuple mapping each extra name to a new value (the support is fixed structure, not passed), and the leaf is rebuilt carrying the updated values. The default no-extras method is the identity on the empty NamedTuple, and a Truncated re-applies its bounds around the rebuilt inner delay. A modifier layer that owns an extra parameter defines this on its own wrapper type, rebuilding around the new value.
Arguments
leaf: the leaf whose extra parameters are set.vals: aNamedTupleof extra name to new value.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.set_extra_leaf_params(Gamma(2.0, 1.0), (;))See also
extra_leaf_params: reads the extra parameters and their supports.
ComposedDistributions.shared Function
shared(
name::Symbol,
dist::Distributions.UnivariateDistribution
) -> SharedTag a leaf distribution as a shared parameter group named name.
shared(name, dist) marks dist as a tied parameter so multiple occurrences of the same name in a composed distribution are handled once by the prior/params interface (inventoried, sampled and updated as a single free parameter), with the shared value placed in every occurrence. The result is transparent to scoring and sampling.
shared(name, dist) is the leaf-local spelling of the tie, applied where the leaf is built. tie(d, paths...; name) is the tree-level spelling of the same tie: it walks a composed d to the named leaves and wraps each in the exact shared(name, leaf) artefact this produces. Use whichever is convenient; the tagged occurrences are one free parameter either way.
Arguments
name: the shared-parameter group name (Symbol).dist: the leaf distribution to tag.
Examples
using ComposedDistributions, Distributions
# The same incubation `inc` tied across two branches of a `choose`.
inc = shared(:inc, Gamma(2.0, 1.0))
d = choose(:index => inc,
:sourced => compose((src = LogNormal(0.5, 0.4), inc = inc)))
event_names(d)See also
sourceComposedDistributions.shared_tag Function
shared_tag(leaf) -> AnyThe shared tag of a (possibly wrapped) leaf, or nothing when untagged.
A shared(:tag, dist) leaf carries a tag that ties every occurrence to one set of parameters. The tag survives wrapper leaves (a Truncated, and the censoring / modifier wrappers whose own methods live in their owning package or extension), so a shared(:inc, ...) leaf and a bare shared(:inc, Gamma(...)) both report :inc. An untagged leaf reports nothing. params_table uses the tag as a leaf's edge and inventories a tied parameter once.
Arguments
leaf: the (possibly wrapped) leaf distribution whose tag is read.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.shared_tag(shared(:inc, Gamma(2.0, 1.0)))See also
sourceComposedDistributions.splice Function
splice(
d::Union{Choose, Parallel, Resolve, Sequential},
path::Symbol;
before,
after
)Splice before/after steps around a node in a composed distribution (a topology edit).
splice(d, path; before, after) replaces the node at path with a Sequential chain of before, the original node, then after (any of which may be omitted). This inserts a change-point step around the addressed node without rebuilding the rest of the tree, e.g. an extra delay before a branch or a follow-up step after it, changing the tree shape. The result is a valid composed distribution that scores and rands. path accepts the same forms as event: varargs Symbols, a dotted Symbol, or a tuple of edge names.
splice and prune are the two topology edits (they change the tree shape); update keeps the same shape and replaces contents.
Arguments
d: the composed distribution to edit.path: the node to wrap, as varargsSymbols, a dottedSymbol, or a tuple of edge names.
Keyword Arguments
before: aname => diststep inserted before the node (default: none).after: aname => diststep inserted after the node (default: none).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
tree2 = splice(tree, :admit_death; after = :death_report => Gamma(1.0, 2.0))
event_names(event(tree2, :admit_death))See also
prune: drop a branch (the other topology edit)update: replace a node or its values (keeps the shape)
ComposedDistributions.tie Function
tie(
d::Union{ComposedDistributions.AbstractOneOf, Choose, Parallel, Sequential},
paths...;
name
)Tie leaves at named paths of a composed distribution into one shared group.
tie(d, paths...; name) walks the composed distribution d to each leaf named by paths and wraps it in a Shared group tagged name, returning the rebuilt composed distribution. This is the tree-level, path-based spelling of shared: tie(d, p1, p2; name = :inc) produces the exact same artefact as building d with shared(:inc, leaf) at each of those leaves, so every tag consumer (params_table, build_priors, update, a downstream composed_parameters_model) inventories, samples and updates the tied leaves as a single free parameter.
Each path takes the same forms event and update accept: a bare Symbol direct child, a dotted-path Symbol (:"sourced.inc", as in params_table's edge column), or a tuple of edge names from the root. Every path must resolve to a leaf (not a composer subtree), and the tied leaves must be parameter-compatible (same inner family and parameter structure), since they become one group.
Arguments
d: the composed distribution to tie leaves in.paths: one or more leaf paths to tie together.
Keyword Arguments
name: the shared-parameter group name (Symbol, required).
Examples
using ComposedDistributions, Distributions
d = choose(:index => compose((inc = Gamma(2.0, 1.0),)),
:sourced => compose((src = LogNormal(0.5, 0.4), inc = Gamma(2.0, 1.0))))
tied = tie(d, (:index, :inc), (:sourced, :inc); name = :inc)
params_table(tied)See also
sourceComposedDistributions.uncertain Function
uncertain(
template::Distributions.UnivariateDistribution;
kwargs...
) -> AnyAttach parameter uncertainty to a distribution: parameters that are themselves distributions, nestable to any depth.
uncertain has three forms:
uncertain(template; kwargs...)wraps a concretetemplateleaf so the named parameters are drawn from the given distributions rather than fixed. Each keyword is a parameter name of the template's free delay (as inparams_table'sparamcolumn); a distribution value makes that parameter uncertain, aRealvalue re-pins it to a new fixed value.uncertain(Family, args...)(a type, e.g.Gamma) takes one positional argument per parameter, in the family's constructor order: aUnivariateDistributionmakes that parameter uncertain, aRealfixes it. The template is the family's default instance with theRealslots pinned; the uncertain slots are driven by their specs.uncertain(Family; kwargs...)is the keyword form on the family's default-constructed template; every parameter must then be given explicitly.
A spec may itself be an uncertain distribution, so hyper-uncertainty nests. The template may be a wrapped leaf (truncated(...), a censoring wrapper): the wrapper is fixed structure re-applied to every draw. Apply such wrappers inside the template. truncated is the exception: applied outside it pushes itself into the template automatically.
The result is a Distributions.UnivariateDistribution and composes as a leaf everywhere (sequential, parallel, resolve, compete, choose, shared): rand draws the marginal, and update(tree, params) collapses an uncertain leaf to its concrete template. In params_table an uncertain parameter's spec rides the row's prior column, so build_priors picks it up without an explicit override.
Only rand is marginal
Every other query on the result — logpdf/cdf/quantile/... and the moments mean/var/std — silently reports the template's central values, not the marginal. Guard a scoring/fitting loop with has_uncertain before assuming a tree is fully concrete.
A template whose parameters are themselves composite (e.g. a Convolved/ Difference node from the ConvolvedDistributions interop, whose parameters are its components' own parameter tuples rather than scalars) is refused with an informative ArgumentError: attach uncertainty to an individual component instead, either by building the composite from uncertain components or by targeting one through update at its component_i path (that interop sees through a composite leaf to its component parameters).
Arguments
template: the concrete (possibly wrapped) leaf distribution, or a distribution type (e.g.Gamma).args...: for the positional family form, one value per parameter (a distribution for an uncertain parameter, aRealfor a fixed one).
Keyword Arguments
kwargs...: parameter name=spec pairs. A distribution spec makes the parameter uncertain; aRealre-pins the template's fixed value.
Examples
using ComposedDistributions, Distributions
# A literature-reported Gamma delay with an uncertain shape.
u = uncertain(Gamma(2.0, 1.0); shape = LogNormal(log(2.0), 0.2))
rand(u)
# The positional family form: shape uncertain, scale fixed at 1.0.
uncertain(Gamma, LogNormal(log(2.0), 0.2), 1.0)
# Nested: the shape's prior location is itself uncertain.
uncertain(Gamma(2.0, 1.0);
shape = uncertain(LogNormal(log(2.0), 0.2); mu = Normal(log(2.0), 0.1)))See also
sourceuncertain(
d::ComposedDistributions.AbstractComposedDistribution,
params::NamedTuple
) -> AnyPromote one or more of an existing tree's free parameters to uncertain.
uncertain(tree, params) / uncertain(tree; kwargs...) apply params (or the keywords, packed the same way) through the same dotted/nested targeting update uses — a Shared tag routes through its tag, a nested edge targets a descendant node — but restricted to calls that introduce or extend at least one prior. A distribution value promotes that parameter (replacing any spec already there — promoting an already-uncertain parameter replaces its spec; it does not nest a hyperprior); a Real value alongside it re-pins a sibling parameter in the same call without itself becoming uncertain. A call with no distribution anywhere is refused: use update for a purely concrete edit.
uncertain(tree) (no params) promotes every free parameter with its default, support-derived prior — one call in place of update(tree, param_priors(tree)).
This is the preferred way to write a promotion: update(tree, nt) still accepts the identical distribution-valued nt directly (unchanged), for a call site that assembles nt programmatically without knowing in advance whether it promotes anything.
Arguments
tree: the composed distribution to promote parameters of.params: a nestedNamedTuple, dotted/nested likeupdate'sparams, with at least one distribution-valued entry.
Keyword Arguments
kwargs...: the same targeting, as keywords (onset_admit = (shape = ...,)).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
# Targeted promotion: only onset_admit.shape becomes uncertain.
u = uncertain(tree; onset_admit = (shape = LogNormal(log(2.0), 0.2),))
has_uncertain(u)
# Promote every free parameter with a default prior.
everything = uncertain(tree)
has_uncertain(everything)See also
update: the underlying mechanism (merge mode) this promotes through; also the concrete-set / node-replacement / flat-vector / table / chain verb.Uncertain: the leaf type a promoted parameter's spec builds.param_priors: the default priorsuncertain(tree)(bare) applies.has_uncertain: check whether any promotion remains unresolved.
ComposedDistributions.uncertain_specs Function
uncertain_specs(leaf) -> AnyLeaf-level distribution-valued parameter specs, or nothing for a fixed leaf.
The uncertain-spec protocol: a NamedTuple of a leaf's distribution-valued parameters (its attached priors), keyed by parameter name, or nothing when the leaf carries no attached prior. The base identity returns nothing, and a Truncated peels to its untruncated inner delay's specs (the truncation bounds are fixed structure, not free parameters). Uncertain reports its own specs (see Uncertain.jl), and a leaf-wrapper type (censoring in CensoredDistributions, the modifiers in ModifiedDistributions) adds its own method dispatching on its own type and forwarding to its inner delay's specs, so an uncertain prior attached under a wrapper still reaches params_table's prior column and build_priors. Without a forwarding method the attached prior is silently dropped and the parameter is treated as fixed.
Arguments
leaf: the (possibly wrapped) leaf distribution to inspect.
Examples
using ComposedDistributions, Distributions
ComposedDistributions.uncertain_specs(Gamma(2.0, 1.0)) === nothingSee also
free_leaf,rewrap_leaf: the sibling leaf-wrapper hooks.leaf_detail_lines: the sibling extension hook forinspectrendering.has_uncertain: the boolean check built on this protocol.
ComposedDistributions.unflatten Function
unflatten(
d::ComposedDistributions.AbstractComposedDistribution,
x::AbstractVector
) -> AnyRebuild the full nested parameter NamedTuple from an estimated flat vector.
unflatten(d, x) maps the estimated flat vector x (the spec'd parameters, e.g. a draw from a sampler) back to the full nested NamedTuple update consumes: each estimated parameter takes its value from x, each fixed parameter its template value. It is the inverse of flatten, so update(d, unflatten(d, x)) collapses every uncertain leaf at the draw while holding the fixed parameters at the template.
Generated once per distinct tree type from a compile-time layout walk (no Dict, no intermediate Any-typed accumulation), so the result is @inferred-concrete and the reverse-mode AD backends (including Enzyme) differentiate through it.
Arguments
d: the composed distribution whose table fixes the layout.x: an estimated flat vector of lengthflat_dimension(d).
Examples
using ComposedDistributions, Distributions
tree = compose((
onset_admit = uncertain(Gamma(2.0, 1.0); shape = LogNormal(log(2.0), 0.2)),
admit_death = LogNormal(0.5, 0.4)))
# One estimated parameter (onset_admit.shape); the rest stay at the template.
# Public but not exported; reach it by the qualified name.
update(tree, ComposedDistributions.unflatten(tree, [3.0]))See also
flatten: the inverse, nested NamedTuple -> flat vector.reconstruct: flat vector straight to a rebuilt distribution.update: rebuild the distribution from the result.
ComposedDistributions.update Function
Update a composed distribution's parameters or replace named nodes — the single verb for every shape-preserving edit, dispatching on the second argument.
a nested
NamedTupleof parameter values/specs (below): fixes or re-specs free parameters, the fine-grained value edit;one or more
path => new_nodepairs (update(d, edits::Pair...)instructural_edits.jl): replaces whole nodes, coarser than a value edit but still same-shape.
For topology edits that change the tree shape, use prune or splice instead.
update(d, params::NamedTuple) — set free parameters
update(d, params) returns a new distribution of the same structure as d with its parameters set from params, a nested NamedTuple mirroring the tree: a Sequential/Parallel is keyed by its edge names, a leaf by its parameter names (as in params_table's param column), and a Resolve by its outcome names plus an optional branch_probs entry. A censored leaf is transparent: supply only the inner delay's parameters and the censoring is carried through.
The value type at a leaf parameter decides what happens (the object-level spelling of "distribution in the slot = estimate, value = fix"):
a
Realpins the parameter to that fixed value, collapsing anyuncertainspec on it. A NamedTuple of all-Realvalues replaces every free parameter (each key required), the plain concrete update;a distribution makes the parameter
uncertainwith that spec. Passing distributions switches to a partial update: only the named parameters change (an absent parameter keeps its current spec or fixed value), soupdate(tree, (onset = (shape = LogNormal(log(2), 0.2),),))makes justonset'sshapeuncertain. Promote a whole tree to uncertainty over its free parameters with default priors viaupdate(tree,param_priors(tree))— the explicit estimate-everything path;a
poolspec makes the parameter partially pooled across the leaves that name the same group (also a partial update), e.g.update(tree, (onset = (shape = pool(:district),),)).
A Resolve node's branch_probs are a node-level parameter, not a leaf: attach a simplex-valued Distributions.Dirichlet at the branch_probs slot to make them uncertain, update(node, (branch_probs = Dirichlet(ones(K)),)). The Dirichlet is the prior you write; the codec estimates the node through the Dirichlet's K-1 stick-breaking coordinates (labelled :stick_1 … :stick_{K-1} in params_table and a fitted chain), each a Beta in (0, 1), so every draw lands on the probability simplex and the gradient is well-defined. The probabilities are recovered from any draw: a strict update from the stick coordinates (as read back from a chain) collapses the node to concrete probabilities summing to one (read them with Distributions.probs). Promote attaches a flat Dirichlet(ones(K)) per Resolve.
Read a fitted chain back onto a template with DistributionsInference.point_estimate (or readback_draws for every draw) — this package stays fit-protocol-agnostic, so chain readback lives in DistributionsInference.jl rather than here; the NamedTuple it returns pairs directly with update.
Arguments
d: the composed distribution (or bare leaf) to update.params: a nested NamedTuple keyed liked, each leaf value aReal(fix) or aUnivariateDistribution(make uncertain).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
# Concrete values pin the parameters.
tree2 = update(tree, (onset_admit = (shape = 3.0, scale = 1.5),
admit_death = (mu = 0.7, sigma = 0.5)))
event(tree2, :onset_admit)
# A distribution makes just that parameter uncertain (a partial update).
est = update(tree, (onset_admit = (shape = LogNormal(log(2.0), 0.2),),))
has_uncertain(est)update(d, path => new_node, ...) — replace nodes
update(d, path => new_node, ...) returns a new composed distribution of the same outer structure as d with the node addressed by each path replaced by new_node. A path is a Symbol (a top-level child), a dotted Symbol (:admit_path.admit_resolution.death, as in event / params_table), or a tuple of edge names from the root (e.g. (:admit_path, :admit_resolution, :death)); the same address event reads is the one this writes. new_node may be a leaf distribution or a nested composer. This shares the recursive reconstruction with the value-update form above, so the result scores and rands. It preserves the tree shape; for shape changes use prune or splice.
Arguments
d: the composed distribution to edit.edits: one or morepath => new_nodepairs.
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
tree2 = update(tree, :admit_death => Gamma(3.0, 1.5))
event(tree2, :admit_death)update(d, x::AbstractVector) — set from flat vector
update(d, x) is a shorthand for update(d, unflatten(d, x)): rebuild the distribution with parameters read from the flat estimated vector x. Each estimated parameter (an uncertain spec in params_table) takes its value from the vector, each fixed parameter its template value. This collapses the tree at the draw and is commonly used to rebuild a distribution from a sampler output after reading it into the flat coordinate system.
Arguments
d: the composed distribution to update.x: a flat vector of estimated parameters, of lengthflat_dimension(d).
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = uncertain(Gamma(2.0, 1.0);
shape = LogNormal(log(2.0), 0.2)),
admit_death = LogNormal(0.5, 0.4)))
# The one estimated parameter is onset_admit.shape; the vector is length 1.
# This is equivalent to
# update(tree, ComposedDistributions.unflatten(tree, [3.0])).
result = update(tree, [3.0])
event(result, :onset_admit)update(d, table) — bulk-set from a Tables.jl table
update(d, table) reads a params_table-shaped Tables.jl table (any Tables.istable source with edge/param columns) and folds every row into the tree in one call: a row's prior (when present and not nothing) promotes that parameter to uncertain, otherwise its value sets it — the spreadsheet-style bulk-edit route, an input format for update rather than a separate verb.
Arguments
d: the composed distribution to update.table: a Tables.jl table withedge/paramcolumns and avalueand/orpriorcolumn.
Examples
using ComposedDistributions, Distributions
tree = compose((onset_admit = Gamma(2.0, 1.0),
admit_death = LogNormal(0.5, 0.4)))
update(tree, params_table(tree)) # a no-op round-trip hereSee also
uncertain(tree, ...): the promotion-only entry point built on the same merge-mode pipeline as this docstring's distribution-valued formsparams_table: the flat inventory whoseparamnames key the leavesparam_priors: default priors for the promote path
ComposedDistributions.validate_pool_groups Function
validate_pool_groups(d) -> AnyCheck that every leaf of each pool group declares the same population distribution and parameterisation, throwing an ArgumentError on a mismatch.
Called once at params_table construction time (typically at distribution_to_logdensity construction), not per gradient evaluation. Reached by qualified name from outside this package — DistributionsInference.jl's fit-protocol extension calls this directly to gate a tree before fitting (#212).
Arguments
d: the composed tree whose pool groups are checked.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
ComposedDistributions.validate_pool_groups(tree)See also
sourceComposedDistributions.validate_tree_names Function
validate_tree_names(d)Check that no pool group, shared tag, or top-level edge name in the tree collides with one from another of those three roles, throwing an ArgumentError on a collision.
The root-lifted codec merge (unflatten's _root_merge_expr) puts pool groups, shared tags, and root edge names into the same flat namespace; a name crossing roles would silently clobber another entry there. Reusing the same tag for a deliberate tie (shared/tie, or a pool group with several members) is the intended feature and is not flagged; only a name crossing roles is an error. Called once at params_table construction time (typically at distribution_to_logdensity construction), not per gradient evaluation. Reached by qualified name from outside this package — DistributionsInference.jl's fit-protocol extension calls this directly to gate a tree before fitting (#212).
Arguments
d: the composed tree whose pool/shared/root names are checked.
Examples
using ComposedDistributions, Distributions
tree = compose((north = uncertain(Gamma(2.0, 1.0);
shape = pool(:region, Beta(2.0, 3.0))),
south = uncertain(Gamma(2.0, 1.0); shape = pool(:region, Beta(2.0, 3.0)))))
ComposedDistributions.validate_tree_names(tree)See also
sourceComposedDistributions.varying Function
varying(f; covariate, reference) -> VaryingBuild a Varying context-indexed leaf.
varying(f; covariate = :time, reference = f(0.0)) wraps a map f (a covariate value to a UnivariateDistribution) as a leaf that varies with the named covariate. The reference distribution is used when the leaf is queried without a context; it defaults to f(0.0) (the map at the origin), which suits a :time covariate — pass an explicit reference for a categorical covariate where f(0.0) is not meaningful.
Arguments
f: map from a covariate value to aUnivariateDistribution.
Keyword Arguments
covariate: theContextfield name to read (default:time).reference: the distribution used without a context (defaultf(0.0)).
Examples
using ComposedDistributions, Distributions
# An onset->admit delay whose mean shortens over calendar time.
d = varying(t -> Gamma(2.0, 1.0 + 0.1 * t); covariate = :time)
mean(d) # the reference (t = 0)
mean(instantiate(d, Context(time = 5.0))) # the delay at t = 5See also
Varying: the leaf type.instantiate: resolve against a context.
ComposedDistributions.with_covariates Function
with_covariates(ctx::Context; covariates...) -> ContextAdd or override covariates on a Context, returning a new context.
with_covariates(ctx; kwargs...) is how the sampling layer of the uncertain distributions work threads its latent index into the same covariate channel an observed covariate uses: starting from a per-record observed context (calendar time, region), it adds the parameter values it has sampled (with_covariates(ctx; inc_shape = θ)), and a Varying leaf keyed on that name resolves against them exactly as a time-varying leaf resolves against time. Observed and latent indices are the same covariate channel; the only difference is who fills the slot — the data, or the sampler. Later keys win over earlier ones.
Arguments
ctx: theContextto extend.
Keyword Arguments
covariates...: covariatename = valuepairs to add or override.
Examples
using ComposedDistributions
base = Context(time = 4.0) # observed covariates
drawn = with_covariates(base; inc_shape = 2.3) # sampler adds a latent param
(drawn.covariates.time, drawn.covariates.inc_shape)See also
source