Skip to content

Internal Documentation

Documentation for ComposedDistributions's internal interface.

Contents

Index

Internal API

ComposedDistributions.AbstractEventSpec Type
julia
abstract type AbstractEventSpec

The 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

source
ComposedDistributions.ParamsTable Type
julia
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
source
ComposedDistributions._composed_params Function
julia
_composed_params(
    d::Union{Parallel, Sequential}
) -> NamedTuple

Nested, 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

source
ComposedDistributions._composer_rand Function
julia
_composer_rand(
    rng::Random.AbstractRNG,
    d::Union{Parallel, Sequential}
) -> Any
julia
_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.

source
ComposedDistributions._edit_at Function
julia
_edit_at(node, path::Tuple, op) -> Any
julia
_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.

source
ComposedDistributions._flat_event_names Function
julia
_flat_event_names(
    d::Union{Parallel, Sequential}
) -> Tuple{Vararg{Symbol}}
julia
_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).

source
ComposedDistributions._hazard_panelled_integrate Function
julia
_hazard_panelled_integrate(f, lo, hi, c::Compete) -> Any

Integrate 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).

See also: probs, mean, var

source
Distributions.ccdf Method
julia
ccdf(c::Compete, t::Real) -> Any

Survival of the racing-hazard marginal any-event time at t: ∏_k S_k(t).

See also: Compete

source
Distributions.cdf Method
julia
cdf(c::Resolve, x::Real) -> Any

Cumulative 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

source
ConvolvedDistributions.convolve_series Function
julia
convolve_series(
    d::Sequential,
    series::AbstractVector{<:Real};
    events
) -> Any

Convolve 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: a Sequential chain, 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's event_names after the origin. nothing (the default) convolves to the endpoint (the whole-chain observed total).

Examples

julia
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

source
julia
convolve_series(
    delay::Distributions.Distribution{Distributions.Univariate, Distributions.Discrete},
    series::AbstractVector{<:Real};
    mask
) -> Any

Convolve 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,         , an off-by-one, so the discrete method reads 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: a DiscreteUnivariateDistribution (e.g. Poisson, DiscreteUniform, a shifted count delay).

  • series: the input timeseries (expected events at unit-spaced times from 0).

  • mask: optional. A Bool vector the same length as series; when given, only the output positions where mask is true are computed and the rest hold zero(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's pdf at the later lags. Omitted (the default), every position is computed.

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
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

source
julia
convolve_series(
    pmf::AbstractVector{<:Real},
    series::AbstractVector{<:Real};
    mask
) -> Any

Convolve 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 lags 0, 1, 2, ... (used as given).

  • series: the input timeseries (expected events at unit-spaced times from 0).

  • mask: optional output-position mask, as in convolve_series(delay, series).

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
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

source
julia
convolve_series(
    pmf::Distributions.DiscreteNonParametric,
    series::AbstractVector{<:Real};
    mask
) -> Any

Convolve 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: a DiscreteNonParametric whose support is the delay's lag grid (regularly spaced, starting at 0) and whose probabilities are the masses at those lags.

  • series: the input timeseries, sampled at the same grid steps as pmf's support, from time 0.

  • mask: optional output-position mask, as in convolve_series(delay, series).

Examples

julia
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

source
julia
convolve_series(
    delays::AbstractVector,
    series::AbstractVector{<:Real};
    indexed_by,
    mask,
    kwargs...
) -> Any

Convolve 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 time s spreads forward through delays[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 time i is read through delays[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, in series order.

  • series: the input timeseries (expected events at unit-spaced times from 0).

  • indexed_by: :primary (default) or :secondary.

  • mask: optional output-position mask, as in convolve_series(delay, series).

  • kwargs...: discretisation keywords, forwarded to delay_masses for each distinct delay.

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
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

source
julia
convolve_series(
    runs::AbstractVector{<:Pair},
    series::AbstractVector{<:Real};
    indexed_by,
    mask
) -> Any

Convolve 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 => length pairs, in series order. Each delay is 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 in convolve_series(delay, series).

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
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

source
julia
convolve_series(
    pmfs::AbstractMatrix{<:Real},
    series::AbstractVector{<:Real};
    indexed_by,
    mask
) -> Any

Convolve 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 in convolve_series(delay, series).

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
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

source
julia
convolve_series(
    pmfs::AbstractVector{<:AbstractVector{<:Real}},
    series::AbstractVector{<:Real};
    indexed_by,
    mask
) -> Any

Convolve 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 in convolve_series(delay, series).

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
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

source
ConvolvedDistributions.difference Method
julia
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

julia
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.

source
Distributions.logpdf Function
julia
logpdf(d::Sequential, x::AbstractVector) -> Missing

Log probability density of a chain's step-value vector.

See also: Sequential

source
julia
logpdf(
    d::Sequential,
    x::AbstractVector{>:Missing}
) -> Missing

Log 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

source
julia
logpdf(d::Parallel, x::AbstractVector) -> Missing

Log probability density of a branch-value vector, summed over branches.

See also: Parallel

source
julia
logpdf(d::Parallel, x::AbstractVector{>:Missing}) -> Missing

Log 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

source
julia
logpdf(c::Resolve, x::Real) -> Any

Log 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

source
julia
logpdf(c::Resolve, x::NamedTuple) -> Any

Score 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

source
julia
logpdf(c::Compete, t::Real) -> Any

Log 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

source
julia
logpdf(c::Compete, x::NamedTuple) -> Any

Score 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

source
julia
logpdf(d::Choose, x::NamedTuple; kind) -> Any

Score 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: the Choose node 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

julia
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)

See also: Choose, rand

source
julia
logpdf(
    d::Sequential,
    x::AbstractVector{<:NamedTuple}
) -> Any

Log 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

source
julia
logpdf(
    d::Choose,
    x::AbstractVector{<:NamedTuple};
    kind
) -> Any

Log 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: the Choose node to score under.

  • x: a vector of records, as a kind-less rand(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

julia
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))

See also: rand, Choose

source
julia
logpdf(d::ConvolvedDistributions.Convolved, x::Real) -> Any

Compute the log probability density function.

See also: pdf, logcdf

source
julia
logpdf(
    d::ConvolvedDistributions.Convolved,
    x::AbstractVector{<:Real}
) -> Any

Compute 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).

See also: logpdf, pdf

source
julia
logpdf(d::ConvolvedDistributions.Difference, z::Real) -> Any

Compute the log probability density function.

See also: pdf, logcdf

source
julia
logpdf(d::ConvolvedDistributions.Product, z::Real) -> Any

Compute the log probability density function.

See also: pdf, logcdf

source
julia
logpdf(d::ConvolvedDistributions.Ratio, z::Real) -> Any

Compute the log probability density function.

See also: pdf, logcdf

source
julia
logpdf(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.

source
julia
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 > N with size(x)[1:N] == size(d), or

  • an array of arrays xi of dimension N with size(xi) == size(d).

source
julia
logpdf(d::UnivariateDistribution, x::Real)

Evaluate the logarithm of probability density (mass) at x.

See also: pdf.

source
julia
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.

source
Statistics.mean Method
julia
mean(c::Resolve) -> Any

Mean 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

source
Statistics.mean Method
julia
mean(d::Sequential) -> Any

Overall 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

julia
using ComposedDistributions, Distributions

seq = Sequential(Gamma(2.0, 1.0), LogNormal(0.5, 0.4))
mean(seq)                 # overall mean delay (a scalar)

See also

source
StatsAPI.params Method
julia
params(d::Sequential) -> NamedTuple

Nested, 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

source
StatsAPI.params Method
julia
params(d::Parallel) -> NamedTuple

Nested, 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

source
Distributions.pdf Method
julia
pdf(d::Sequential, x::AbstractVector) -> Any

Probability density of a chain's step-value vector.

See also: logpdf

source
Distributions.pdf Method
julia
pdf(d::Parallel, x::AbstractVector) -> Any

Probability density of a branch-value vector.

See also: logpdf

source
Distributions.pdf Method
julia
pdf(c::Resolve, x::Real) -> Any

Probability 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

source
Distributions.pdf Method
julia
pdf(d::Choose, x::Real; kind)

Probability density of the selected alternative at x.

See also: logpdf

source
Distributions.probs Method
julia
probs(c::Resolve) -> NamedTuple

The 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: the Resolve node whose declared branch probabilities to read.

Examples

julia
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

source
Distributions.probs Method
julia
probs(c::Compete) -> NamedTuple

The 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: the Compete node whose derived per-cause winning split to read.

Examples

julia
using ComposedDistributions, Distributions

node = compete(:death => Gamma(2.0, 3.0), :recover => Gamma(3.0, 2.0))
probs(node)

See also: Compete, occurrence_probability

source
Base.rand Function
julia
rand(rng::Random.AbstractRNG, d::Sequential) -> NamedTuple

Sample 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

source
julia
rand(rng::Random.AbstractRNG, d::Parallel) -> NamedTuple

Sample 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

source
julia
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

julia
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) pair

See also: event_names, as_mixture

source
julia
rand(rng::Random.AbstractRNG, d::Choose; kind) -> NamedTuple

Sample 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).

See also: Choose, logpdf

source
julia
rand(
    rng::Random.AbstractRNG,
    d::Choose,
    n::Int64;
    kind
) -> Vector

Draw 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: the Choose node 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

julia
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

source
julia
rand(rng::Random.AbstractRNG, d::Uncertain) -> Any

Draw 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.

See also: Uncertain, update

source
julia
rand(rng::Random.AbstractRNG, p::Pool) -> Any

Draw 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)).

See also: Pool, pool

source
julia
rand(rng::Random.AbstractRNG, p::Pool, n::Int64) -> Any

Draw 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.

See also: Pool, pool

source
julia
rand([rng::AbstractRNG,] s::Sampleable)

Generate one sample for s.

julia
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 s is univariate, it returns a vector of length n.

  • When s is multivariate, it returns a matrix with n columns.

  • When s is 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.

source
julia
rand(rng::AbstractRNG, d::UnivariateDistribution)

Generate a scalar sample from d. The general fallback is quantile(d, rand()).

source
julia
rand(::AbstractRNG, ::Distributions.AbstractMvNormal)

Sample a random vector from the provided multi-variate normal distribution.

source
julia
rand(::AbstractRNG, ::Sampleable)

Samples from the sampler and returns the result.

source
julia
rand(d::Union{UnivariateMixture, MultivariateMixture})

Draw a sample from the mixture model d.

julia
rand(d::Union{UnivariateMixture, MultivariateMixture}, n)

Draw n samples from d.

source
Base.show Function
julia
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

source
julia
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

source
julia
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

source
julia
show(io::IO, _::MIME{Symbol("text/plain")}, c::Compete)

Print a Compete node as a recursive indented tree.

See also: Compete

source
julia
show(io::IO, _::MIME{Symbol("text/plain")}, d::Choose)

Print a Choose node as its selector and named alternatives.

See also: Choose

source
julia
show(io::IO, d::Shared{tag})

Print a Shared tagged leaf as its tag and wrapped distribution.

See also: shared

source
julia
show(io::IO, d::Uncertain)

Print an Uncertain leaf as its constructor form: the template and the name = spec pairs.

See also: uncertain

source
julia
show(io::IO, p::Pool)

Print a Pool spec as its constructor form.

See also: pool

source
julia
show(io::IO, d::Varying)

Print a Varying leaf as its constructor form: the covariate it maps and its reference distribution.

See also: varying

source
julia
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
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 day

Container 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.

source
julia
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
julia> show("Hello World!")
"Hello World!"
julia> print("Hello World!")
Hello World!
source
Statistics.std Method
julia
std(d::Sequential) -> Any

Overall 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

source
ComposedDistributions.threshold Function
julia
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: the Context field name to read.

  • cutoff: the switching value; above applies at or past it.

Keyword Arguments

  • below: the subtree used for a covariate strictly less than cutoff.

  • above: the subtree used for a covariate at or past cutoff.

  • reference: the distribution used without a context (default below, the pre-threshold regime).

Examples

julia
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` subtree

See also

  • varying: the general covariate-indexed map this specialises.
source
Statistics.var Method
julia
var(d::Sequential) -> Any

Overall 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