Internal Documentation
Documentation for ComposedDistributions's internal interface.
Contents
Index
ComposedDistributions.AbstractEventSpecComposedDistributions.ParamsTableBase.randBase.showComposedDistributions._composed_paramsComposedDistributions._composer_randComposedDistributions._edit_atComposedDistributions._flat_event_namesComposedDistributions._hazard_panelled_integrateComposedDistributions.thresholdConvolvedDistributions.convolve_seriesConvolvedDistributions.differenceDistributions.ccdfDistributions.cdfDistributions.logpdfDistributions.pdfDistributions.pdfDistributions.pdfDistributions.pdfDistributions.probsDistributions.probsStatistics.meanStatistics.meanStatistics.stdStatistics.varStatsAPI.paramsStatsAPI.params
Internal API
ComposedDistributions.AbstractEventSpec Type
abstract type AbstractEventSpecThe supertype of the event-skeleton spec nodes.
An EventSkeleton is a tree of these structural nodes: a named Hole leaf, a →-chain, a |-one_of group, or a &-parallel group. The nodes carry names and composition structure only, no distributions.
See also
EventSkeleton: the skeleton wrapper the nodes sit under.@events: the macro that lowers an operator diagram to them.
Fields
sourceComposedDistributions.ParamsTable Type
struct ParamsTable{C<:NamedTuple}A Tables.jl column table of a composed distribution's free parameters.
The value params_table returns: a Tables.jl source (a column table) that prints as a padded edge | param | value | support | prior table. It is a thin wrapper over a NamedTuple of equal-length column vectors, forwarding the whole Tables.jl column interface and column access (tbl.edge, tbl.param, ...), so Tables.istable, Tables.columns, Tables.getcolumn, DataFrame(tbl) and build_priors all consume it unchanged; only its display is customised.
See also: params_table, build_priors.
Fields
columns::NamedTuple
ComposedDistributions._composed_params Function
_composed_params(
d::Union{Parallel, Sequential}
) -> NamedTupleNested, name-keyed parameters of a composed distribution.
Returns a NamedTuple keyed by the node names, each value the params of that child (recursing into nested composers; a leaf delegates to its standard/ extended Distributions.params). A Resolve node contributes a name-keyed NamedTuple of its outcomes plus a branch_probs entry. This nested form is for prior introspection via params_table; a composed distribution reconstructs through compose, not through Distribution(params...).
See also: params_table, event_names, event
ComposedDistributions._composer_rand Function
_composer_rand(
rng::Random.AbstractRNG,
d::Union{Parallel, Sequential}
) -> Any_composer_rand(rng, d)The vector-valued realisation of a composer: the generic per-leaf-value draw (one value per leaf, a nested composer contributing its own sub-vector). A Resolve child collapses to its marginal time-to-resolution (its univariate rand), a Choose child to its first alternative. Composed distributions score this flat, vector-valued representation (consumed by logpdf/AD); the labelled outputs below (_named_composer_rand, mean/var/std of a Parallel) wrap it by name for users.
ComposedDistributions._edit_at Function
_edit_at(node, path::Tuple, op) -> Any_edit_at(node, path, op)The path-walk core shared by update, prune and splice: walks path from node, applying op(target) at the addressed node and rebuilding the spine on the way back up. path is a tuple of edge names, in the same forms event accepts. An empty path applies op to node itself; otherwise _edit_step dispatches on the composer type to find the named child, recurse, and rebuild with the edited child swapped in.
ComposedDistributions._flat_event_names Function
_flat_event_names(
d::Union{Parallel, Sequential}
) -> Tuple{Vararg{Symbol}}_flat_event_names(d)The internal worker behind the public event_names (flat) accessor: the tuple of event names matching the scored event vector [E_0, E_1, ..., E_k], the root origin event followed by one target event per edge in depth-first order. Built by appending into a Symbol[] and freezing to a tuple, mirroring the params_table pre-order walk; edge names are read from the parent composer's names field (a leaf edge does not store its own name), so each child is visited paired with its edge name.
Event names are derived from the composer's edge names (an edge :onset_admit gives origin :onset and target :admit); an edge with a positional default name (:step_i / :branch_i) contributes the positional event name :event_i instead. These event names key a data row (a linelist column is an event time), distinct from the edge names (component_names / the parameter inventory).
ComposedDistributions._hazard_panelled_integrate Function
_hazard_panelled_integrate(f, lo, hi, c::Compete) -> AnyIntegrate f over [lo, hi], splitting at each of c's causes' own quantile (or moment) markers so a wide shared window doesn't starve the region where the mass actually sits. Falls back to the single-window _PRIMARY rule when there are no interior breaks (a window that's already narrow, or every cause lacking a usable quantile/moment marker).
Distributions.ccdf Method
ccdf(c::Compete, t::Real) -> AnySurvival of the racing-hazard marginal any-event time at t: ∏_k S_k(t).
See also: Compete
Distributions.cdf Method
cdf(c::Resolve, x::Real) -> AnyCumulative distribution function of the one_of-outcome marginal at x.
The branch-prob-weighted mixture cdf Σ_i p_i F_i(x), summed directly so the probabilities keep their (possibly AD Dual) element type rather than being stripped by as_mixture's float.(branch_probs). This keeps the cdf AD-safe on a differentiated path (e.g. a censored-survival term), matching logpdf.
For a defective node (a no-event branch present) a no-event branch never contributes to "occurred by x" at any x, so it is skipped rather than scored; the sum rises only to occurrence_probability(c) as x → ∞, a proper sub-stochastic law rather than one coerced back to mass one. ccdf is 1 - cdf, the generic Distributions fallback, so it comes along for free: the defective survival that flattens at the no-event probability instead of decaying to zero.
See also: as_mixture, occurrence_probability
ConvolvedDistributions.convolve_series Function
convolve_series(
d::Sequential,
series::AbstractVector{<:Real};
events
) -> AnyConvolve a timeseries through a composed chain's observed delay.
convolve_series(chain, series), where series is a numeric timeseries vector, collapses the Sequential chain to its observed total delay (observed_distribution, the convolution of the chain steps) and hands it straight to ConvolvedDistributions.convolve_series. With series the expected events at unit-spaced times 0, 1, ..., t (e.g. infections), a discrete observed delay gives the expected downstream event counts at the same times — the EpiNow2-style latent / renewal observation layer, driven by a composed delay rather than a bare distribution.
A chain's observed total is usually continuous (e.g. a Convolved sum of Gamma/LogNormal steps), and ConvolvedDistributions is discrete-convolution -only: it throws, naming CensoredDistributions.jl (which owns primary and interval censoring, including double-interval-censored masses for a day-binned primary) as the way to build a PMF first, then convolve_series(pmf, series). This method does not choose a scheme on the caller's behalf — it collapses the tree and delegates, nothing more.
Pass events to convolve the series to a chosen interim event of the chain rather than its endpoint. A single event name returns the count series at that event; a tuple or vector of names returns a NamedTuple of series keyed by the names. The cumulative delay to an interim event is the observed collapse of the chain prefix up to that event (the convolution of the steps leading to it), so selecting the terminal event reproduces the plain whole-chain result. Only a plain continuous chain (every step a delay leaf, no branching) has such per-event cumulative delays; a chain with a branching step is rejected.
Arguments
chain: aSequentialchain, collapsed to its observed total delay.series: the input timeseries (expected events at unit-spaced times from 0).
Keyword Arguments
events: a chain event name, or a tuple/vector of names, to convolve the series to (the cumulative delay of the chain prefix up to that event). The valid names are the chain'sevent_namesafter the origin.nothing(the default) convolves to the endpoint (the whole-chain observed total).
Examples
using ComposedDistributions, ConvolvedDistributions, Distributions
chain = Sequential(Gamma(2.0, 1.0), LogNormal(0.5, 0.4))
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
maxlag = length(infections) - 1
# Standing in for what CensoredDistributions.jl would build for the chain's
# own (continuous) observed total: a caller-owned PMF, here from a discrete
# distribution's own masses.
masses = pdf.(NegativeBinomial(5, 0.5), 0:maxlag)
expected_counts = convolve_series(masses, infections)
# The count series at named interim events (here the prefix to each event).
onset_to = sequential(:onset_admit => Gamma(2.0, 1.0),
:admit_death => LogNormal(0.5, 0.4))
by_event = convolve_series(onset_to, infections; events = (:admit, :death))See also
observed_distribution: the chain-to-total-delay collapse.event_names: the chain's event names, the valideventsselectors.difference: the difference of two observed totals.
convolve_series(
delay::Distributions.Distribution{Distributions.Univariate, Distributions.Discrete},
series::AbstractVector{<:Real};
mask
) -> AnyConvolve a timeseries with the PMF of a discrete delay distribution.
convolve_series(delay, series) for a DiscreteUnivariateDistribution delay reads the delay PMF directly off the integer lag grid — the lag-k mass IS pdf(delay, k) — and returns the causal discrete convolution of series with that PMF, truncated to the series window. With series the expected events at times 0, 1, ..., t (e.g. infections), the result is the expected downstream event counts at the same times (the EpiNow2-style latent / renewal observation layer).
The masses are [pdf(delay, k) for k in 0:(length(series) - 1)], used as given: no renormalisation, so any delay mass beyond the series window is truncated. pdf(delay, k) is differentiable in the delay parameters for the standard discrete families, so gradients flow under the supported AD backends.
Direct PMF evaluation, NOT a CDF difference: for an integer-support delay, pdf(delay, k) rather than a CDF-difference mass.
Only the integer lags 0, 1, 2, ... are read. A delay with atoms off the integer grid is out of scope (a lag grid means masses at the integers), and mass at negative lags cannot enter a causal convolution, so lags below 0 are not read (consistent with the causal kernel).
A Convolved/Difference/Product of integer-lattice discrete components is itself a DiscreteUnivariateDistribution (#85) and flows straight through this method, reading its exact masses. A CONTINUOUS delay has no mass on the integer grid until it is discretised, and discretisation is an explicit modelling choice this package does not make; it matches no method here, so convolve_series(a_continuous_delay, series) is a MethodError naming what is actually missing, rather than a pre-emptive gate (#95) — see convolve_series(pmf, series) below for the caller-owned discretisation path.
Unlike convolved, which combines distributions into a single Convolved distribution, this returns a numeric series; the separate verb keeps convolved strictly for distribution construction.
Arguments
delay: aDiscreteUnivariateDistribution(e.g.Poisson,DiscreteUniform, a shifted count delay).series: the input timeseries (expected events at unit-spaced times from 0).mask: optional. ABoolvector the same length asseries; when given, only the output positions wheremaskistrueare computed and the rest holdzero(eltype(result)). Masked-out positions are genuinely skipped, not computed and discarded, so a mask selecting a few positions out of a long series is cheap —pdf(delay, k)is only evaluated for the lags a requested position can actually read, so a mask restricted to an early window also skips evaluating the delay'spdfat the later lags. Omitted (the default), every position is computed.
Returns
- A numeric vector of expected downstream counts, the same length as
series.
Examples
using ConvolvedDistributions, Distributions
delay = Poisson(2.0)
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series(delay, infections)See also
convolved: the distribution-level convolutionconvolve_series(pmf, series): the PMF-vector form for caller-owned discretisation
convolve_series(
pmf::AbstractVector{<:Real},
series::AbstractVector{<:Real};
mask
) -> AnyConvolve a timeseries with a caller-supplied discretised delay PMF.
convolve_series(pmf, series) returns the causal discrete convolution of series with the probability masses pmf, truncated to the series window: out[i] = sum(pmf[k + 1] * series[i - k] for k in 0:(min(length(pmf), i) - 1)). pmf[k + 1] is read as the delay mass at integer lag k on the same unit grid as series.
The masses are used exactly as given: no renormalisation, no validation that they sum to one, and no tail correction — mass at lags beyond the series window (including any pmf entries past length(series)) is simply never used, so sub-normalised or window-truncated PMFs stay truncated. This is the decoupled form of convolve_series(delay, series): the caller owns the discretisation (e.g. double-interval-censored masses from CensoredDistributions.jl), and this method only convolves. The convolution is linear, so gradients flow through both pmf and series under the supported AD backends.
Arguments
pmf: the discretised delay probability masses at integer lags0, 1, 2, ...(used as given).series: the input timeseries (expected events at unit-spaced times from 0).mask: optional output-position mask, as inconvolve_series(delay, series).
Returns
- A numeric vector of expected downstream counts, the same length as
series.
Examples
using ConvolvedDistributions
pmf = [0.5, 0.3, 0.2]
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series(pmf, infections)See also
convolve_series(pmf::DiscreteNonParametric, series): the non-unit-grid form
convolve_series(
pmf::Distributions.DiscreteNonParametric,
series::AbstractVector{<:Real};
mask
) -> AnyConvolve a timeseries with a delay's DiscreteNonParametric PMF.
convolve_series(pmf, series) for a DiscreteNonParametric pmf reads its support as the delay's lag grid and its probabilities as the masses at those lags, then convolves as convolve_series(probs(pmf), series). The support must start at 0 and be regularly spaced (a constant gap between consecutive support points); an irregular or offset grid throws an ArgumentError, since convolve_series has no separate argument to carry a grid width or starting lag.
This is the non-unit-grid caller-supplied form: a DiscreteNonParametric built on a coarser grid (e.g. DiscreteNonParametric(0:7:28, weekly_masses) for weekly bins) convolves correctly, whereas a plain AbstractVector PMF only ever reads as the unit grid.
Unlike the AbstractVector form, which uses masses exactly as given (no renormalisation, so a window-truncated tail stays sub-normalised), DiscreteNonParametric enforces a genuine probability vector at construction (sum(probs(pmf)) ≈ 1, or Distributions.jl throws a DomainError). A window-truncated or otherwise sub-normalised PMF therefore needs the plain vector form instead.
Arguments
pmf: aDiscreteNonParametricwhose support is the delay's lag grid (regularly spaced, starting at0) and whose probabilities are the masses at those lags.series: the input timeseries, sampled at the same grid steps aspmf's support, from time 0.mask: optional output-position mask, as inconvolve_series(delay, series).
Examples
using ConvolvedDistributions, Distributions
pmf = DiscreteNonParametric([0.0, 7.0, 14.0], [0.6, 0.3, 0.1])
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series(pmf, infections)See also
convolve_series(pmf::AbstractVector, series): the unit-grid vector form
convolve_series(
delays::AbstractVector,
series::AbstractVector{<:Real};
indexed_by,
mask,
kwargs...
) -> AnyConvolve a timeseries with a time-varying delay: one delay per time point.
convolve_series(delays, series) takes one delay per entry of series and returns the convolution, truncated to the series window. Each delay's lag masses come from its own single-delay convolve_series(delay, series) method, so the elements may be of any, and of mixed, types. A type whose masses are not what that method gives specialises delay_masses instead.
Identical delays share one set of masses, however often they recur, so a delay is only ever built once. With a mask, a delay is also built no larger than the requested output positions can actually read — a delay at a time point a mask excludes entirely is never built beyond a single placeholder lag.
indexed_by names which time the delay belongs to:
:primary(the default): the delay belongs to the events, so the cohort at timesspreads forward throughdelays[s]—out[i] = Σ_s series[s] * pmf_s[i - s + 1]. Conserves mass up to the truncated tail.:secondary: the delay belongs to the observation time, so everything landing at timeiis read throughdelays[i]—out[i] = Σ_k pmf_i[k + 1] * series[i - k]. Not mass-conserving.
Any other keyword is forwarded to each distinct delay's delay_masses call, so a vector of continuous delays needing a non-default discretisation (e.g. interval) agrees with the same keywords passed to the single-delay convolve_series(delay, series) form.
Arguments
delays: one delay per time point, inseriesorder.series: the input timeseries (expected events at unit-spaced times from 0).indexed_by::primary(default) or:secondary.mask: optional output-position mask, as inconvolve_series(delay, series).kwargs...: discretisation keywords, forwarded todelay_massesfor each distinct delay.
Returns
- A numeric vector of expected downstream counts, the same length as
series.
Examples
using ConvolvedDistributions, Distributions
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
delays = [Poisson(λ) for λ in range(3.0, 1.0; length = length(infections))]
expected_counts = convolve_series(delays, infections)See also
convolve_series(delay, series): the static single-delay formconvolve_series(pmfs::AbstractMatrix, series): the caller-supplied-PMF formdelay_masses: how one delay's masses are read, and where to specialise it
convolve_series(
runs::AbstractVector{<:Pair},
series::AbstractVector{<:Real};
indexed_by,
mask
) -> AnyConvolve a timeseries with a delay that changes less often than the series.
convolve_series(runs, series) takes delay => length pairs, each holding for that many consecutive time points, and expands them before convolving — so the delays (or mass vectors) are given once per regime rather than once per time point. The lengths must sum to length(series).
indexed_by is as in the one-delay-per-time-point form.
Arguments
runs:delay => lengthpairs, inseriesorder. Eachdelayis anything the one-per-time-point form accepts, including a mass vector.series: the input timeseries (expected events at unit-spaced times from 0).indexed_by::primary(default) or:secondary.mask: optional output-position mask, as inconvolve_series(delay, series).
Returns
- A numeric vector of expected downstream counts, the same length as
series.
Examples
using ConvolvedDistributions, Distributions
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series([Poisson(3.0) => 3, Poisson(1.0) => 4],
infections)See also
convolve_series(delays::AbstractVector, series): one delay per time point
convolve_series(
pmfs::AbstractMatrix{<:Real},
series::AbstractVector{<:Real};
indexed_by,
mask
) -> AnyConvolve a timeseries with time-varying caller-supplied delay PMFs held in a matrix.
convolve_series(pmfs, series) reads an AbstractMatrix as one delay PMF per time point, lags down columns: pmfs[k + 1, j] is the mass at lag k for time point j, so size(pmfs, 2) must equal length(series). The lag count size(pmfs, 1) is free. A time-by-lag matrix M is passed as transpose(M).
indexed_by is as in the vector-of-delays form. Masses are used exactly as given: no renormalisation, no sum-to-one check and no tail correction.
Arguments
pmfs: delay masses, lags down columns, one column per time point.series: the input timeseries (expected events at unit-spaced times from 0).indexed_by::primary(default) or:secondary.mask: optional output-position mask, as inconvolve_series(delay, series).
Returns
- A numeric vector of expected downstream counts, the same length as
series.
Examples
using ConvolvedDistributions
infections = [0.0, 1.0, 3.0, 6.0]
pmfs = [0.5 0.5 0.6 0.7
0.3 0.3 0.3 0.2
0.2 0.2 0.1 0.1]
expected_counts = convolve_series(pmfs, infections)See also
convolve_series(pmf::AbstractVector, series): the static unit-grid vector formconvolve_series(delays::AbstractVector, series): the vector-of-distributions form
convolve_series(
pmfs::AbstractVector{<:AbstractVector{<:Real}},
series::AbstractVector{<:Real};
indexed_by,
mask
) -> AnyConvolve a timeseries with time-varying caller-supplied delay PMFs held in a vector of vectors.
The ragged counterpart of the matrix form: pmfs[j] is the delay PMF for time point j on the unit lag grid, so length(pmfs) must equal length(series) while each PMF may carry its own number of lags. indexed_by and the masses-as-given contract are as in the matrix form.
Arguments
pmfs: one vector of delay masses per time point, each from lag 0.series: the input timeseries (expected events at unit-spaced times from 0).indexed_by::primary(default) or:secondary.mask: optional output-position mask, as inconvolve_series(delay, series).
Returns
- A numeric vector of expected downstream counts, the same length as
series.
Examples
using ConvolvedDistributions
infections = [0.0, 1.0, 3.0, 6.0]
pmfs = [[0.5, 0.3, 0.2], [0.5, 0.5], [1.0], [0.4, 0.4, 0.1, 0.1]]
expected_counts = convolve_series(pmfs, infections)See also
convolve_series(pmfs::AbstractMatrix, series): the rectangular matrix form
ConvolvedDistributions.difference Method
difference(
a::Sequential,
b;
kwargs...
) -> Union{ConvolvedDistributions.Difference{X, Y, ConvolvedDistributions.AnalyticalSolver{ConvolvedDistributions.GaussLegendre{ConvolvedDistributions._GL{Vector{Float64}, Vector{Float64}}}}, Distributions.Continuous} where {X<:(Distributions.UnivariateDistribution), Y<:(Distributions.UnivariateDistribution)}, ConvolvedDistributions.Difference{X, Y, ConvolvedDistributions.AnalyticalSolver{ConvolvedDistributions.GaussLegendre{ConvolvedDistributions._GL{Vector{Float64}, Vector{Float64}}}}, Distributions.Discrete} where {X<:(Distributions.UnivariateDistribution), Y<:(Distributions.UnivariateDistribution)}}Difference of two observed total delays, Z = X - Y.
difference(a, b) accepts a Sequential chain for either operand and collapses it to its observed total delay (observed_distribution) before forming the Difference. With both operands chains, Z is the difference of the two convolved totals; a bare distribution operand is used as-is. This extends the univariate ConvolvedDistributions difference to composed stacks.
Examples
using ComposedDistributions, Distributions
onset = Sequential(Gamma(2.0, 1.0), LogNormal(0.5, 0.4))
report = Sequential(Gamma(1.5, 1.0), Gamma(1.0, 2.0))
gap = difference(onset, report)See also
observed_distribution: the chain-to-total-delay collapse.Difference: the univariate difference distribution.
Distributions.logpdf Function
logpdf(d::Sequential, x::AbstractVector) -> MissingLog probability density of a chain's step-value vector.
See also: Sequential
logpdf(
d::Sequential,
x::AbstractVector{>:Missing}
) -> MissingLog probability density of a chain's step-value vector admitting missing steps: missing in a slot means that step was not observed (the ecosystem- wide convention), scoring the observed steps and integrating out the rest (each unobserved step's own marginal contributes zero log density).
See also: Sequential
logpdf(d::Parallel, x::AbstractVector) -> MissingLog probability density of a branch-value vector, summed over branches.
See also: Parallel
logpdf(d::Parallel, x::AbstractVector{>:Missing}) -> MissingLog probability density of a branch-value vector admitting missing branches: missing in a slot means that branch was not observed (the ecosystem-wide convention), scoring the observed branches and integrating out the rest (each unobserved branch's own marginal contributes zero log density).
See also: Parallel
logpdf(c::Resolve, x::Real) -> AnyLog probability density of the one_of-outcome marginal at x.
Routed through the AD-safe _one_of_logmix reduction rather than logpdf(as_mixture(c), x): as_mixture does float.(branch_probs), which strips an AD Dual/tracked type from the branch probabilities, breaking the gradient w.r.t. a covariate case-fatality term (logistic(Xβ)) when a Resolve is scored as a leaf of a plain (non-censored) compose(...) tree. The explicit log-sum-exp keeps the probabilities' element type, so a Dual propagates exactly as on the censored-tree scorer.
See also: as_mixture
logpdf(c::Resolve, x::NamedTuple) -> AnyScore a standalone Resolve outcome record (the shape a bare rand(c) returns): log p_i + logpdf(delay_i, t) for the fired outcome i at time t, so logpdf(c, rand(c)) round-trips. A column table (a NamedTuple of vectors) is a multi-record source, summed per row.
See also: rand, event_names
logpdf(c::Compete, t::Real) -> AnyLog density of the racing-hazard marginal any-event time T = min_k D_k.
The marginal density is ∑_j f_j(t) ∏_{k≠j} S_k(t); this is its log via the log-sum-exp of the cause-resolved sub-densities, AD-safe (the leaf params propagate, no float stripping).
See also: Compete, Distributions.probs
logpdf(c::Compete, x::NamedTuple) -> AnyScore a standalone Compete cause record (the shape a bare rand(c) returns): the cause-resolved sub-density f_j(t) ∏_{k≠j} S_k(t) of the winning cause j at time t (the winning probability is derived from the hazards, so there is no branch-probability term), so logpdf(c, rand(c)) round-trips. A column table (a NamedTuple of vectors) is a multi-record source, summed per row.
See also: rand, event_names
logpdf(d::Choose, x::NamedTuple; kind) -> AnyScore a self-describing Choose record (the shape a bare rand(d) returns).
A bare rand(d) draw is a NamedTuple whose selector field names the drawn alternative and whose remaining fields are that alternative's labelled draw, so logpdf(d, rand(d)) round-trips with no kind argument: the selector field is read to pick the alternative, then the rest of the record is scored under that alternative's own logpdf. A leaf alternative's value rides in the :value field; a composer alternative is scored on its own labelled record fields. A column table of such records is summed per row.
Passing kind names the alternative instead of reading a selector field, which is how a committed-selection draw from a composer alternative scores: both the single labelled record and the column table rand(d, n; kind) returns are handed to that alternative's own logpdf, which already sums a table per row.
Arguments
d: theChoosenode to score under.x: a self-describing record, or a column table of them.kind: name of the active alternative, for records drawn with an explicit selection (so carrying no selector field). Defaults to reading the selector.
Examples
using ComposedDistributions, Distributions, Random
d = choose(:leaf => Gamma(2.0, 1.0),
:path => sequential(:a => Gamma(2.0, 1.0), :b => Gamma(3.0, 1.0)))
logpdf(d, rand(Xoshiro(1), d, 4; kind = :path); kind = :path)logpdf(
d::Sequential,
x::AbstractVector{<:NamedTuple}
) -> AnyLog density of a batch of labelled records, summed over the batch.
A Vector of NamedTuple records (e.g. [rand(d) for _ in 1:n]) scores each record through the single-record logpdf(d, ::NamedTuple) value-name path and sums. The AbstractVector{<:NamedTuple} element type is disjoint from the flat single-record logpdf(d, ::AbstractVector{<:Real}) method, so a scalar-valued record vector is never mistaken for a batch (and vice versa). The concrete Sequential/Parallel methods (rather than a Union) keep this strictly more specific than the flat per-type methods, so dispatch is unambiguous. A column table (as rand(d, n) returns) is scored by the NamedTuple method's table branch.
See also: rand, event_names
logpdf(
d::Choose,
x::AbstractVector{<:NamedTuple};
kind
) -> AnyLog density of a batch of Choose records, summed over the batch.
A kind-less rand(d, n) returns a Vector of self-describing records rather than a column table (the drawn alternative varies row to row, so there is no one column layout), and each record scores through the selector-reading logpdf(d, ::NamedTuple) path. Passing kind instead hands the whole vector to the named alternative's own batch logpdf.
Arguments
d: theChoosenode to score under.x: a vector of records, as a kind-lessrand(d, n)returns.kind: name of the active alternative, when the records were drawn with an explicit selection. Defaults to reading each record's selector field.
Examples
using ComposedDistributions, Distributions, Random
d = choose(:short => Gamma(2.0, 1.0), :long => Gamma(5.0, 1.0))
logpdf(d, rand(Xoshiro(1), d, 4))logpdf(d::ConvolvedDistributions.Convolved, x::Real) -> AnyCompute the log probability density function.
sourcelogpdf(
d::ConvolvedDistributions.Convolved,
x::AbstractVector{<:Real}
) -> AnyCompute log densities for a vector of points, analytically where convolved_logpdf has an exact route, otherwise as the log of the batched PDF solve.
Each numeric point is integrated over the same window the scalar path picks (shared composite panels plus per-point end corrections), so batched and scalar numeric log densities agree to well within ~1e-8 even for wide batches (typically near machine precision; extreme 100x-plus point spans stay within ~1e-6).
sourcelogpdf(d::ConvolvedDistributions.Difference, z::Real) -> AnyCompute the log probability density function.
sourcelogpdf(d::ConvolvedDistributions.Product, z::Real) -> AnyCompute the log probability density function.
sourcelogpdf(d::ConvolvedDistributions.Ratio, z::Real) -> AnyCompute the log probability density function.
sourcelogpdf(d::Distribution{ArrayLikeVariate{N}}, x::AbstractArray{<:Real,N}) where {N}Evaluate the logarithm of the probability density function of d at x.
This function checks if the size of x is compatible with distribution d. This check can be disabled by using @inbounds.
Implementation
Instead of logpdf one should implement _logpdf(d, x) which does not have to check the size of x.
See also: pdf, gradlogpdf.
logpdf(d::Distribution{ArrayLikeVariate{N}}, x) where {N}Evaluate the logarithm of the probability density function of d at every element in a collection x.
This function checks for every element of x if its size is compatible with distribution d. This check can be disabled by using @inbounds.
Here, x can be
an array of dimension
> Nwithsize(x)[1:N] == size(d), oran array of arrays
xiof dimensionNwithsize(xi) == size(d).
logpdf(d::UnivariateDistribution, x::Real)Evaluate the logarithm of probability density (mass) at x.
See also: pdf.
logpdf(d::Union{UnivariateMixture, MultivariateMixture}, x)Evaluate the logarithm of the (mixed) probability density function over x. Here, x can be a single sample or an array of multiple samples.
Statistics.mean Method
mean(c::Resolve) -> AnyMean of the one_of-outcome marginal.
For a proper node this is the ordinary mixture mean. For a defective node (a no-event branch present) there is no unconditional mean — the marginal has an atom at "never", no finite time — so this reports the conditional-on- occurrence mean instead: the branch-prob-weighted average of the observed branches' means, renormalised by occurrence_probability.
See also: as_mixture, occurrence_probability
Statistics.mean Method
mean(d::Sequential) -> AnyOverall mean of a composed distribution (the simple "mean delay").
mean(d) behaves like a normal delay distribution's mean. For a univariate-collapsible composer (a Sequential chain, a Convolved (ConvolvedDistributions.jl), a Resolve) it returns the scalar mean of the overall observed delay — the mean of observed_distribution(d) (the convolved total for a chain, the marginal time-to-resolution for a Resolve). For a genuinely multivariate Parallel (several independent observed endpoints) it returns the per-endpoint Vector, one overall mean per branch endpoint, not the origin / intermediate events. Censoring is seen through to the free delay. An uncertain leaf contributes its template moment (parameter uncertainty is not propagated); guard with has_uncertain(d) if that matters, draw the marginal with rand, or collapse the leaf to its concrete template with update(tree, params) to work with fixed parameters.
For a single event's own moment, fetch its distribution with event and take its mean directly, e.g. mean(event(d, :onset_admit)).
Examples
using ComposedDistributions, Distributions
seq = Sequential(Gamma(2.0, 1.0), LogNormal(0.5, 0.4))
mean(seq) # overall mean delay (a scalar)See also
event: fetch an edge/event's own distributionevent_names: the flat per-event labelsobserved_distribution: collapse a chain to its terminal scalar
StatsAPI.params Method
params(d::Sequential) -> NamedTupleNested, name-keyed parameters of the chain.
Returns a NamedTuple keyed by the step names, each value the params of that step (recursing into nested composers; a leaf delegates to its standard/extended Distributions.params). This nested form is for prior introspection via params_table; a composed distribution reconstructs through compose, not through Distribution(params...).
See also: params_table, event_names, event
StatsAPI.params Method
params(d::Parallel) -> NamedTupleNested, name-keyed parameters of the branches.
Returns a NamedTuple keyed by the branch names, each value the params of that branch (recursing into nested composers; a leaf delegates to its standard/ extended Distributions.params). This nested form is for prior introspection via params_table; a composed distribution reconstructs through compose, not through Distribution(params...).
See also: params_table, event_names, event
Distributions.pdf Method
pdf(d::Sequential, x::AbstractVector) -> AnyProbability density of a chain's step-value vector.
See also: logpdf
Distributions.pdf Method
pdf(d::Parallel, x::AbstractVector) -> AnyProbability density of a branch-value vector.
See also: logpdf
Distributions.pdf Method
pdf(c::Resolve, x::Real) -> AnyProbability density of the one_of-outcome marginal at x.
exp of the AD-safe logpdf, so branch-prob gradients survive (see the logpdf note on why as_mixture is avoided on a differentiated path).
See also: logpdf
Distributions.pdf Method
pdf(d::Choose, x::Real; kind)Probability density of the selected alternative at x.
See also: logpdf
Distributions.probs Method
probs(c::Resolve) -> NamedTupleThe per-outcome probabilities of a fixed-probability Resolve node: its declared branch probabilities (the no-event branch's mass is the non-occurrence probability), returned as a NamedTuple keyed by the outcome names.
This is the Resolve method of Distributions.probs, the standard mixture-weight reader: a Resolve lowers to a MixtureModel (see as_mixture), so its weights are the declared branch probabilities. The racing-hazard Compete sibling derives the same split from the hazards instead.
Arguments
c: theResolvenode whose declared branch probabilities to read.
Examples
using ComposedDistributions, Distributions
node = resolve(:death => (Gamma(1.5, 1.0), 0.3),
:disch => (Gamma(2.0, 1.5), 0.7))
probs(node)See also: occurrence_probability
Distributions.probs Method
probs(c::Compete) -> NamedTupleThe derived per-cause winning probabilities of a racing-hazard Compete node: P(cause = j) = ∫ f_j(t) ∏_{k≠j} S_k(t) dt, returned as a NamedTuple keyed by the outcome names.
This is the Compete method of Distributions.probs, the standard mixture-weight reader: it gives the same per-outcome split Resolve returns from its declared branch probabilities, but derived here from the hazards rather than declared.
Computed by AD-safe fixed-node Gauss-Legendre quadrature of the cause-resolved sub-density over the marginal support, panelled at each cause's own quantile (or moment) markers so a wide window doesn't starve the region where the mass actually sits (_hazard_panelled_integrate). The probabilities are sub-stochastic-free (they sum to one for proper, eventually-certain causes); a node whose causes can leave residual survival at +∞ (a defective cause) sums to less than one, the deficit being the never-resolved mass.
Arguments
c: theCompetenode whose derived per-cause winning split to read.
Examples
using ComposedDistributions, Distributions
node = compete(:death => Gamma(2.0, 3.0), :recover => Gamma(3.0, 2.0))
probs(node)See also: Compete, occurrence_probability
Base.rand Function
rand(rng::Random.AbstractRNG, d::Sequential) -> NamedTupleSample a chain realisation as a NamedTuple keyed by the per-step value names: one entry per leaf step, a nested Sequential/Parallel step contributing its own sub-values under dotted-joined names, and a Resolve step contributing its own collapsed scalar.
See also: Sequential
rand(rng::Random.AbstractRNG, d::Parallel) -> NamedTupleSample a branch realisation as a NamedTuple keyed by the per-branch value names: one entry per leaf branch, a nested Sequential/Parallel branch contributing its own sub-values under dotted-joined names, and a Resolve branch contributing its own collapsed scalar.
See also: Parallel
rand(
rng::Random.AbstractRNG,
c::ComposedDistributions.AbstractOneOf;
outcome
) -> Union{Tuple{Any, Any}, NamedTuple}Sample a one_of node (Resolve / Compete).
By default (outcome = false) the draw returns the full named event record of the outcome that fired: a NamedTuple keyed by event_names, a positional origin slot then one slot per outcome, with the fired outcome's time present and the others missing. This is the same self-describing record the in-tree path produces (the node nested in a compose(...) tree), so a standalone draw identifies which outcome won and feeds straight back into logpdf.
With outcome = true the draw instead returns the compact (name, time) pair of the outcome that fired, so a standalone draw tells you which outcome/cause won without reading the sparse record. For a Resolve the outcome is drawn from the branch probabilities and the time from that outcome's own delay; for a Compete a latent time is drawn per cause and the argmin cause with its min time is returned (the marginal any-event time min_k D_k alone is the pair's second element). A no-event win yields a missing time.
To recover the marginal time-to-resolution alone (the mixture over outcomes, discarding which fired) sample as_mixture(c) instead.
Examples
using ComposedDistributions, Distributions, Random
node = resolve(:death => (Gamma(1.5, 1.0), 0.3),
:disch => (Gamma(2.0, 1.5), 0.7))
rand(MersenneTwister(1), node) # the named event record
rand(MersenneTwister(1), node; outcome = true) # the (name, time) pairSee also: event_names, as_mixture
rand(rng::Random.AbstractRNG, d::Choose; kind) -> NamedTupleSample a Choose, returning a self-describing record tagging which alternative was drawn.
Without a kind (the forward-simulation path, where no data names the branch) an alternative is sampled uniformly and the result is a NamedTuple carrying the selector field set to the drawn alternative's name plus that alternative's own draw, so the record identifies which alternative fired and feeds straight back into logpdf with no extra arguments. A leaf alternative's value is labelled :value; a composer alternative contributes its own flat event-record fields.
With a kind (explicit selection) the draw is that alternative's own rand returned directly (a scalar for a leaf, a labelled NamedTuple for a composer), not wrapped in a selector tag: the caller already named the alternative, so this is the in-tree / committed-selection path (logpdf(d, draw; kind) scores it).
rand(
rng::Random.AbstractRNG,
d::Choose,
n::Int64;
kind
) -> VectorDraw n independent Choose realisations.
Mirrors the single-draw rand(d; kind) convention. With a kind, the batch draws directly from that alternative (rand(rng, dist, n), the alternative's own multi-draw form) — the committed-selection path, no selector tag. Without a kind, each of the n draws is its own self-describing tagged record (a Vector of NamedTuples, one per draw), the forward-simulation path, so each round-trips through logpdf with no extra argument.
Either shape scores as a batch in one call, logpdf(d, rand(d, n; kind)), passing back the same kind the draw used. The result is the summed log density, matching the single-record scorer applied row by row.
Arguments
rng: random number generator. Defaults to the global one.d: theChoosenode to draw from.n: number of independent realisations.kind: name of the alternative to draw from. Defaults to sampling an alternative uniformly per draw and tagging each record with it.
Examples
using ComposedDistributions, Distributions, Random
d = choose(:short => Gamma(2.0, 1.0), :long => Gamma(5.0, 1.0))
xs = rand(Xoshiro(1), d, 5; kind = :short)
logpdf(d, xs; kind = :short)See also: rand(::Choose), logpdf
rand(rng::Random.AbstractRNG, d::Uncertain) -> AnyDraw the marginal of an uncertain distribution: draw every uncertain parameter from its spec (recursively, so a nested Uncertain spec draws via its own rand), rebuild the concrete leaf (fixed wrapper structure re-applied), then draw the value. Each call draws a fresh parameter set, so repeated draws are iid from the marginal.
rand(rng::Random.AbstractRNG, p::Pool) -> AnyDraw the marginal of one pooled parameter: draw from the population (its own hyperparameters, then the parameter).
This is the marginal of a single pooled parameter in isolation; the joint prior-predictive of a whole pooled tree, where the population is shared across members, comes from sampling the flat priors and rebuilding with update(tree,unflatten(tree, x)).
rand(rng::Random.AbstractRNG, p::Pool, n::Int64) -> AnyDraw n independent marginal draws of one pooled parameter.
Mirrors the single-draw rand(::Pool): delegates to the population's own multi-draw rand(rng, population, n). As with the single-draw form, this is the marginal of one pooled parameter in isolation, not the joint prior-predictive of a whole pooled tree.
rand([rng::AbstractRNG,] s::Sampleable)Generate one sample for s.
rand([rng::AbstractRNG,] s::Sampleable, n::Int)Generate n samples from s. The form of the returned object depends on the variate form of s:
When
sis univariate, it returns a vector of lengthn.When
sis multivariate, it returns a matrix withncolumns.When
sis matrix-variate, it returns an array, where each element is a sample matrix. rand([rng::AbstractRNG,] s::Sampleable, dim1::Int, dim2::Int...) rand([rng::AbstractRNG,] s::Sampleable, dims::Dims)
Generate an array of samples from s whose shape is determined by the given dimensions.
rand(rng::AbstractRNG, d::UnivariateDistribution)Generate a scalar sample from d. The general fallback is quantile(d, rand()).
rand(::AbstractRNG, ::Distributions.AbstractMvNormal)Sample a random vector from the provided multi-variate normal distribution.
sourcerand(::AbstractRNG, ::Sampleable)Samples from the sampler and returns the result.
sourcerand(d::Union{UnivariateMixture, MultivariateMixture})Draw a sample from the mixture model d.
rand(d::Union{UnivariateMixture, MultivariateMixture}, n)Draw n samples from d.
Base.show Function
show(io::IO, _::MIME{Symbol("text/plain")}, d::Sequential)Print a Sequential chain as a recursive indented tree, descending into any nested composer children so the whole structure is shown at once.
See also: Sequential
show(io::IO, _::MIME{Symbol("text/plain")}, d::Parallel)Print a Parallel composer as a recursive indented tree, descending into any nested composer children so the whole structure is shown at once.
See also: Parallel
show(io::IO, _::MIME{Symbol("text/plain")}, c::Resolve)Print a Resolve node as a recursive indented tree, labelling each outcome with its name and branch probability and descending into any nested composer outcome so the whole structure is shown at once.
See also: Resolve
show(io::IO, _::MIME{Symbol("text/plain")}, c::Compete)Print a Compete node as a recursive indented tree.
See also: Compete
show(io::IO, _::MIME{Symbol("text/plain")}, d::Choose)Print a Choose node as its selector and named alternatives.
See also: Choose
show(io::IO, d::Shared{tag})Print a Shared tagged leaf as its tag and wrapped distribution.
See also: shared
show(io::IO, d::Uncertain)Print an Uncertain leaf as its constructor form: the template and the name = spec pairs.
See also: uncertain
show(io::IO, p::Pool)Print a Pool spec as its constructor form.
See also: pool
show(io::IO, d::Varying)Print a Varying leaf as its constructor form: the covariate it maps and its reference distribution.
See also: varying
show(io::IO, mime, x)The display functions ultimately call show in order to write an object x as a given mime type to a given I/O stream io (usually a memory buffer), if possible. In order to provide a rich multimedia representation of a user-defined type T, it is only necessary to define a new show method for T, via: show(io, ::MIME"mime", x::T) = ..., where mime is a MIME-type string and the function body calls write (or similar) to write that representation of x to io. (Note that the MIME"" notation only supports literal strings; to construct MIME types in a more flexible manner use MIME{Symbol("")}.)
For example, if you define a MyImage type and know how to write it to a PNG file, you could define a function show(io, ::MIME"image/png", x::MyImage) = ... to allow your images to be displayed on any PNG-capable AbstractDisplay (such as IJulia). As usual, be sure to import Base.show in order to add new methods to the built-in Julia function show.
Technically, the MIME"mime" macro defines a singleton type for the given mime string, which allows us to exploit Julia's dispatch mechanisms in determining how to display objects of any given type.
The default MIME type is MIME"text/plain". There is a fallback definition for text/plain output that calls show with 2 arguments, so it is not always necessary to add a method for that case. If a type benefits from custom human-readable output though, show(::IO, ::MIME"text/plain", ::T) should be defined. For example, the Day type uses 1 day as the output for the text/plain MIME type, and Day(1) as the output of 2-argument show.
Examples
julia> struct Day
n::Int
end
julia> Base.show(io::IO, ::MIME"text/plain", d::Day) = print(io, d.n, " day")
julia> Day(1)
1 dayContainer types generally implement 3-argument show by calling show(io, MIME"text/plain"(), x) for elements x, with :compact => true set in an IOContext passed as the first argument.
show([io::IO = stdout], x)Write a text representation of a value x to the output stream io. New types T should overload show(io::IO, x::T). The representation used by show generally includes Julia-specific formatting and type information, and should be parseable Julia code when possible.
repr returns the output of show as a string.
For a more verbose human-readable text output for objects of type T, define show(io::IO, ::MIME"text/plain", ::T) in addition. Checking the :compact IOContext key (often checked as get(io, :compact, false)::Bool) of io in such methods is recommended, since some containers show their elements by calling this method with :compact => true.
See also print, which writes un-decorated representations.
Examples
julia> show("Hello World!")
"Hello World!"
julia> print("Hello World!")
Hello World!Statistics.std Method
std(d::Sequential) -> AnyOverall standard deviation of a composed distribution.
std(d) is sqrt(var(d)) (or its elementwise form for a Parallel). For a single event's own std, use std(event(d, name)).
See also
sourceComposedDistributions.threshold Function
threshold(
covariate::Symbol,
cutoff::Real;
below,
above,
reference
)Build a covariate-threshold Varying node: one subtree below a covariate value, another at or above it.
threshold(covariate, cutoff; below, above) is the common activation shape (a regime that switches at a given index value) as one call rather than a hand-written varying map. Right-continuous at cutoff, matching the rest of the ecosystem's step-function convention: below applies for a covariate strictly less than cutoff, above for a covariate at or past it. below and above may themselves be composite nodes (a Resolve/ Compete), since threshold builds on varying.
Arguments
covariate: theContextfield name to read.cutoff: the switching value;aboveapplies at or past it.
Keyword Arguments
below: the subtree used for a covariate strictly less thancutoff.above: the subtree used for a covariate at or pastcutoff.reference: the distribution used without a context (defaultbelow, the pre-threshold regime).
Examples
using ComposedDistributions, Distributions
sw = threshold(:x, 10.0; below = Gamma(2.0, 1.0), above = Gamma(2.0, 3.0))
instantiate(sw, Context(x = 5.0)) # the `below` subtree
instantiate(sw, Context(x = 15.0)) # the `above` subtreeSee also
varying: the general covariate-indexed map this specialises.
Statistics.var Method
var(d::Sequential) -> AnyOverall variance of a composed distribution.
var(d) mirrors mean: the scalar variance of the overall observed delay for a univariate-collapsible composer (the variance of observed_distribution(d)), or the per-endpoint Vector for a Parallel. For a single event's own variance, use var(event(d, name)).
See also
source