Public Documentation
Documentation for DistributionsInference's public interface.
DistributionsInference.DistributionsInference Module
DistributionsInferenceThe inference layer for the EpiAware composable-modelling stack: a PPL-neutral fit protocol (parameter rows, flat dimension, reconstruct hook), a LogDensityProblems-based log-density engine, and a FlexiChains readback, with no probabilistic programming language in the core package.
The protocol (parameter_rows, reconstruct), default-prior assembly over it (default_prior, with_priors), the engine (distribution_to_logdensity, logdensity, FitLogDensity) and the dotted-name FlexiChains readback inference_to_distribution, inference_to_distributions, their inference_to_dist/inference_to_dists aliases, and inference_to_parameters for the exact draws themselves) are implemented here; FlexiChains is a hard dependency. Everything else arrives through an extension: DynamicPPL (distribution_to_turing, both the model-building form and the FlexiChain-returning sampling form), DynamicPPL x FlexiChains (the VarName-keyed dispatch of the readback functions above), Bijectors (to_constrained, to_unconstrained, the logdensity_to_objective / objective_to_distribution pair built on them, and inference_to_parameter_distribution, the joint posterior fitted as an MvNormal on the unconstrained scale; distribution_to_objective is written in core over the objective pair, so it needs Bijectors loaded the same way), AdvancedMH x Bijectors (distribution_to_advancedmh), ComposedDistributions (the fit protocol over a composed tree's own codec), ModifiedDistributions (the fit protocol for a standalone modifier distribution, not only as a leaf inside a composed tree) and Optim (the minimise step of optimise_distribution).
using DistributionsInferenceContents
Index
DistributionsInference.DistributionsInferenceDistributionsInference.FitLogDensityDistributionsInference.default_priorDistributionsInference.distribution_to_advancedmhDistributionsInference.distribution_to_logdensityDistributionsInference.distribution_to_objectiveDistributionsInference.distribution_to_turingDistributionsInference.draws_to_chainDistributionsInference.estimated_rowsDistributionsInference.extra_logpriorDistributionsInference.extra_prior_stateDistributionsInference.flat_dimensionDistributionsInference.flat_priorsDistributionsInference.inference_to_distDistributionsInference.inference_to_distributionDistributionsInference.inference_to_distributionsDistributionsInference.inference_to_distsDistributionsInference.inference_to_parameter_distributionDistributionsInference.inference_to_parametersDistributionsInference.logdensityDistributionsInference.logdensity_to_objectiveDistributionsInference.minimiseDistributionsInference.objective_to_distributionDistributionsInference.observationsDistributionsInference.optimise_distributionDistributionsInference.parameter_rowsDistributionsInference.reconstructDistributionsInference.templateDistributionsInference.to_constrainedDistributionsInference.to_unconstrainedDistributionsInference.with_priors
Public API
DistributionsInference.FitLogDensity Type
struct FitLogDensity{D, T, L, SD, FP, ES, CF}A PPL-neutral log-density over a fit-protocol object's estimated parameters.
FitLogDensity carries everything needed to evaluate the (unnormalised) log-posterior of a fittable object over its estimated flat parameter vector, with no PPL dependency: the template obj, the observed data, a loglik reducer scoring data against the object reconstructed at a draw, and the estimated rows' priors flattened once at construction. Build it with distribution_to_logdensity; evaluate it on a flat vector with logdensity. It also implements the LogDensityProblems interface directly, so it is sampleable by any LogDensityProblems consumer.
Fields
obj: the template fittable object (the structurereconstructrebuilds).data: the observed records scored byloglik.loglik: a reducer(obj, data) -> Real(default sumslogpdf(obj, record)).scored_data: the batchloglikis actually called against, prepared once fromdataat construction (_prepare_scored_data, #115). Equal todataunlessloglikis the default reducer and an extension has registered a cheaper per-evaluation form forobj's anddata's shape (e.g.ComposedDistributionsconverting aNamedTuple-record batch to itsMissing-admitting vector form once, rather than on every evaluation).flat_priors: the estimated rows' priors, inparameter_rowsorder, collected once at construction. An entry isnothingfor an estimated row scored instead throughextra_logprior(an object-dependent prior; seeparameter_rows), which then contributes no per-row term. A tree mixing several prior families makes this vector abstractly typed, costing one dynamic dispatch per row inlogdensity.extra_state:extra_prior_state(obj), collected once at construction and threaded into everyextra_logpriorcall (#28).concrete_fields:_concrete_field_candidates(obj), the concrete-field- under-AD guard's structural state, collected once at construction and empty for a properly generic object.
See also
distribution_to_logdensity: the assembler.logdensity: evaluate on a flat vector.
Fields
obj::Anydata::Anyloglik::Anyscored_data::Anyflat_priors::Anyextra_state::Anyconcrete_fields::Any
DistributionsInference.default_prior Function
default_prior(
row
) -> Union{Distributions.Uniform{Float64}, Distributions.Normal, Distributions.Truncated{Distributions.Normal{T}, Distributions.Continuous, T1, _A, Nothing} where {T<:Real, T1<:Real, _A<:Union{Nothing, T1}}}Pick a default prior for one parameter_rows row, brms-style.
default_prior(row) is with_priors's per-row default for rows the caller does not override. row is a parameter_rows-shaped NamedTuple (; name, value, prior, support); the prior family follows the parameter's own name (the last dotted segment of name, e.g. :shape from Symbol("onset.shape")), not the row's support:
a
[0, 1]-support row (a simplex/probability parameter) ->Uniform(0, 1).a scale/shape/rate-type name (
:sigma,:scale,:shape,:rate, ...) ->truncated(Normal(value, scale); lower = 0), positive by construction.a location name (
:mu,:location, a bound) ->Normal(value, scale), unconstrained even under a positive-support row.otherwise, falls back to
support: non-negative ->truncated(Normal(value, scale); lower = 0), elseNormal(value, scale).
The spread scale is max(abs(value), 1), a weakly-informative width that scales with the parameter's magnitude.
Arguments
row: aparameter_rowsrow(; name, value, prior, support).
Examples
using DistributionsInference
row = (name = Symbol("onset.shape"), value = 2.0, prior = nothing,
support = (0.0, Inf))
DistributionsInference.default_prior(row)See also
with_priors: assembles a full row set from this default.
DistributionsInference.distribution_to_advancedmh Function
Sample a fittable object's posterior with AdvancedMH's random-walk Metropolis, and return a FlexiChain.
distribution_to_advancedmh(obj, data, sampler, nsamples; nchains = 1, burnin = 0, loglik, rng, kwargs...) assembles distribution_to_logdensity(obj, data; loglik), builds an AdvancedMH.DensityModel over its estimated flat parameters on the unconstrained scale (via to_constrained, so this needs Bijectors loaded as well as AdvancedMH), drives it with sampler for nsamples draws, and keys the draws back onto the constrained scale into a dotted-name FlexiChain with draws_to_chain — the same naming distribution_to_turing's sampling form produces, so the result reads back with inference_to_distribution/inference_to_distributions unchanged.
Sampling on the unconstrained scale means a random-walk proposal can never land outside a prior's support in the first place (a negative rate, say), which a DensityModel built directly over the constrained scale would need a hand-written guard for (any(<=(0), x) ? -Inf : ..., the pattern the getting-started tutorials used before this function existed). This needs every estimated row to carry its own prior, exactly to_constrained's own requirement — a row scored instead through extra_logprior (an object-dependent prior, e.g. a centred-pooled ComposedDistributions tree) has no bijector to build, the same row kind distribution_to_turing also refuses. Such an object still fits through distribution_to_logdensity driven by hand, on the constrained scale, where the hand-written guard is still needed.
sampler sizes its own proposal, so a caller who wants the common RWMH(MvNormal(zeros(dim), scale^2 * I)) shape reads dim off flat_dimension(obj) first (see the example below). burnin drops that many draws from the start of each chain before pooling. nchains > 1 runs that many independent sample calls and pools them into one multi-chain FlexiChain, chain-major, the same convention draws_to_chain uses.
This method is available only when both AdvancedMH and Bijectors are loaded.
Arguments
obj: the template fittable object, carrying itsparameter_rows.data: the observed records scored byloglik.sampler: anAdvancedMH.MHSampler, e.g.RWMH(...).nsamples: the number of samples per chain (including anyburnin).
Keyword Arguments
nchains: the number of independent chains to sample (default1).burnin: draws dropped from the start of each chain before pooling (default0).loglik: a reducer(obj, data) -> Realscoringdataagainst the reconstructed object (default: sum oflogpdf(obj, record)), the same defaultdistribution_to_logdensityuses.rng: the random-number generatorsampledraws with (defaultRandom.default_rng()).other keywords are forwarded to
AdvancedMH.sample.
Examples
using DistributionsInference, Distributions, AdvancedMH, Bijectors, Random
using LinearAlgebra: I
struct MHGammaLeaf{S <: Real}
shape::S
scale::Float64
end
Distributions.logpdf(d::MHGammaLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::MHGammaLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::MHGammaLeaf, x::AbstractVector)
return MHGammaLeaf(x[1], d.scale)
end
leaf = MHGammaLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2]
dim = DistributionsInference.flat_dimension(leaf)
Random.seed!(1)
chain = distribution_to_advancedmh(
leaf, data, RWMH(MvNormal(zeros(dim), 0.1^2 * I)), 2000; burnin = 1000)
fitted = inference_to_distribution(leaf, chain, mean)
fitted.scale # the fixed parameter, untouchedSee also
distribution_to_logdensity: the PPL-neutral log-density this wraps.distribution_to_turing: the sibling sampling verb.inference_to_distribution/inference_to_distributions: read the returned chain back ontoobj.draws_to_chain: the raw-draws-to-chain step this uses internally.
DistributionsInference.distribution_to_logdensity Function
distribution_to_logdensity(
obj,
data;
loglik
) -> Union{DistributionsInference.FitLogDensity{_A, _B, typeof(DistributionsInference._default_loglik), _C, _D, Nothing, Vector{Tuple{Int64, Symbol, DataType}}} where {_A, _B, _C, _D}, DistributionsInference.FitLogDensity{_A, _B, typeof(DistributionsInference._default_loglik), _C, _D, Vector{Tuple{Tuple, Symbol, ComposedDistributions.Pool}}, Vector{Tuple{Int64, Symbol, DataType}}} where {_A, _B, _C, _D}}Assemble a FitLogDensity from a fittable object and data.
distribution_to_logdensity(obj, data; loglik) packages the template obj and the observed data into the PPL-neutral log-density spec, reading the priors off obj's parameter_rows (the estimation boundary). The result evaluates the (unnormalised) log-posterior over the estimated flat parameter vector via logdensity, on the constrained scale: each prior is scored directly against its row's value with no Jacobian correction. An object with no estimated rows estimates nothing: the flat vector is empty and logdensity is just the data likelihood. Sampling on the unconstrained scale (the transform and its log-Jacobian) is a Bijectors extension concern.
A conditionally available exact likelihood is a loglik the caller writes and passes in, not a helper this package adds (#44). Choose between the exact and approximate branch with an explicit predicate or by dispatch, never by catching an exception from the exact path, which would hide a genuine bug in the exact branch. Where the exact form does not apply, refuse loudly with a named structural reason, the convention to_constrained and distribution_to_turing follow for a row kind they do not support.
Arguments
obj: the template fittable object, carrying itsparameter_rows.data: the observed records.
Keyword Arguments
loglik: a reducer(obj, data) -> Realscoringdataagainst the reconstructed object (default: sum oflogpdf(obj, record)).
Examples
using DistributionsInference, Distributions
struct ToyLeaf
shape::Float64
scale::Float64
end
Distributions.logpdf(d::ToyLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::ToyLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::ToyLeaf, x::AbstractVector)
return ToyLeaf(x[1], d.scale)
end
leaf = ToyLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2]
prob = distribution_to_logdensity(leaf, data)
DistributionsInference.flat_dimension(leaf)A conditionally exact likelihood, chosen by predicate and passed straight in as loglik:
using DistributionsInference, Distributions
struct ToyLeaf2
shape::Float64
scale::Float64
end
Distributions.logpdf(d::ToyLeaf2, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::ToyLeaf2)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::ToyLeaf2, x::AbstractVector)
return ToyLeaf2(x[1], d.scale)
end
has_exact_form(::ToyLeaf2) = false # a structural property of the object
function chosen_loglik(obj, data)
if has_exact_form(obj)
return sum(y -> logpdf(obj, y), data) # closed form
else
error("no exact likelihood for ToyLeaf2: <named structural reason>")
end
end
# Decide by an explicit predicate and refuse loudly when it does not apply,
# never by catching an exception from the exact path.
leaf2 = ToyLeaf2(2.0, 1.0)
data2 = [1.5, 2.0, 3.2]
prob2 = distribution_to_logdensity(leaf2, data2; loglik = chosen_loglik)
DistributionsInference.flat_dimension(leaf2)See also
logdensity: evaluate the assembled spec on a flat vector.parameter_rows,reconstruct: the fit protocol this reads.
DistributionsInference.distribution_to_objective Function
distribution_to_objective(
obj,
data;
loglik
) -> DistributionsInferenceBijectorsExt.var"#11#12"{DistributionsInference.FitLogDensity{D, T, L, SD, FP, ES, CF}} where {D, T, L, SD, FP, ES, CF}Build an optimiser objective straight from a distribution and data.
distribution_to_objective(obj, data; loglik) is logdensity_to_objective(distribution_to_logdensity(obj, data; loglik)): the composed convenience so a caller reaches the objective without assembling a FitLogDensity by hand first. It is written in core over those two functions rather than reimplemented, so it needs Bijectors loaded for the same reason logdensity_to_objective does, and gives the same ArgumentError naming Bijectors and DistributionsInferenceBijectorsExt when it is not.
Reach for this function when the objective is the whole of what is wanted; optimise_distribution runs the optimiser and rebuilds a fitted object too, and is a strict superset of what this composes.
Arguments
obj: the template fittable object, carrying itsparameter_rows.data: the observed records.
Keyword Arguments
loglik: a reducer(obj, data) -> Realscoringdataagainst the reconstructed object (default: sum oflogpdf(obj, record)).
Examples
using DistributionsInference, Distributions, Bijectors
struct ComposedLeaf
shape::Float64
scale::Float64
end
Distributions.logpdf(d::ComposedLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::ComposedLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::ComposedLeaf, x::AbstractVector)
return ComposedLeaf(x[1], d.scale)
end
leaf = ComposedLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2, 2.8, 1.9]
f = distribution_to_objective(leaf, data)
# `f` is the same callable the two-step route gives, ready for any external
# optimiser.
f([0.0])See also
logdensity_to_objective,distribution_to_logdensity: the two calls this composes.optimise_distribution: the whole fit, objective through to a rebuilt object.
DistributionsInference.distribution_to_turing Function
A DynamicPPL model over a fittable object's estimated parameters.
distribution_to_turing(obj, data) returns a DynamicPPL/Turing model whose free parameters are the estimated parameters of obj (one ~ site per estimated_rows(obj) row, the same flat parameters distribution_to_logdensity exposes), so a fitted posterior is sampleable with sample(distribution_to_turing(obj, data), NUTS(), ...). It is a light wrapper on the distribution_to_logdensity codec: each estimated row is a named ~ site sampled from its own prior, and the data likelihood plus extra_logprior are added with DynamicPPL.@addlogprob! from the codec's reconstruct(obj, θ) scored by loglik. The model's total log-density equals logdensity(distribution_to_logdensity(obj, data), x) at the corresponding constrained x by construction.
The ~ sites are named to match the inference_to_distributions contract exactly: an estimated row's dotted name (e.g. Symbol("onset.shape")) becomes the VarName <prefix>.onset.shape, so a chain from sample(distribution_to_turing(obj, data), ...; chain_type = FlexiChains.VNChain) reads back through inference_to_distribution/inference_to_distributions unchanged (that VarName-keyed dispatch lives in the DistributionsInferenceDynamicPPLFlexiChainsExt extension, so it needs FlexiChains loaded too; distribution_to_turing itself does not).
An estimated row with no fixed prior (prior === nothing, scored instead through extra_logprior — an object-dependent prior, e.g. a hierarchical population term; see parameter_rows) has no ~ site to sample it from and is rejected with an ArgumentError. Sample such an object with distribution_to_logdensity + LogDensityProblemsAD (the LogDensityProblems extension) instead.
A gradient-based sampler (e.g. NUTS) evaluates reconstruct at a ForwardDiff.Dual-valued flat vector, so each estimated field of obj's type must be generically typed. A field concretely typed Float64 errors under NUTS; a gradient-free sampler such as AdvancedMH has no such constraint.
This method is available only when DynamicPPL is loaded.
Arguments
obj: the template fittable object, carrying itsparameter_rows.data: the observed records scored byloglik.
Keyword Arguments
prefix: the outer submodel variable name the sites are namespaced under (default:d), matching the readback prefix.loglik: a reducer(obj, data) -> Realscoringdataagainst the reconstructed object (default: sum oflogpdf(obj, record)), the same defaultdistribution_to_logdensityuses.
Examples
using DistributionsInference, Distributions, DynamicPPL, Turing, Random
using FlexiChains: FlexiChains, VNChain
struct TuringGammaLeaf{S <: Real}
shape::S
scale::Float64
end
Distributions.logpdf(d::TuringGammaLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::TuringGammaLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::TuringGammaLeaf, x::AbstractVector)
return TuringGammaLeaf(x[1], d.scale)
end
leaf = TuringGammaLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2]
Random.seed!(1)
chain = sample(distribution_to_turing(leaf, data), NUTS(), 200;
chain_type = VNChain, progress = false)
fitted = inference_to_distribution(leaf, chain, mean)
fitted.scale # the fixed parameter, untouchedSee also
distribution_to_logdensity: the PPL-neutral log-density this wraps.inference_to_distribution/inference_to_distributions: read a fitted chain back ontoobj.parameter_rows/reconstruct: the fit protocol this reads.
DistributionsInference.draws_to_chain Function
draws_to_chain(
obj,
draws;
nchains
) -> FlexiChains.SymChain{FlexiChains.FlexiChainMetadata{DimensionalData.Dimensions.Lookups.Sampled{Int64, UnitRange{Int64}, DimensionalData.Dimensions.Lookups.ForwardOrdered, DimensionalData.Dimensions.Lookups.Regular{Int64}, DimensionalData.Dimensions.Lookups.Points, DimensionalData.Dimensions.Lookups.NoMetadata}, DimensionalData.Dimensions.Lookups.Sampled{Int64, UnitRange{Int64}, DimensionalData.Dimensions.Lookups.ForwardOrdered, DimensionalData.Dimensions.Lookups.Regular{Int64}, DimensionalData.Dimensions.Lookups.Points, DimensionalData.Dimensions.Lookups.NoMetadata}, Vector{Missing}, Vector{Missing}}}Build a dotted-name FlexiChain from raw sampler draws.
draws_to_chain(obj, draws; nchains = 1) keys draws by estimated_rows(obj)'s dotted names (in parameter_rows order), so the result reads back onto obj with inference_to_distribution/inference_to_distributions. draws is accepted in either raw shape a LogDensityProblems-compatible sampler hands back: a dim x niter matrix, or a niter-length vector of dim-length vectors, where dim is flat_dimension(obj). An object estimating nothing (dim == 0) still needs draws to carry the draw count — pass a (0, niter) matrix or a niter-length vector of empty vectors.
nchains splits the pooled niter draws into that many equal chains, chain-major: the first niter / nchains columns are chain 1, the next niter / nchains are chain 2, and so on. niter must be an exact multiple of nchains, checked here — this is the one place that can catch a chain count and a draw count silently disagreeing (#89). Default nchains = 1 treats every column as one chain.
public, not exported: an ordinary caller reaches inference_to_distribution/inference_to_distributions directly on raw draws instead, which build this internally. This function is for an engine author whose sampler hands back raw draws rather than a chain of its own (e.g. a LogDensityProblems-driven sampler) and needs to satisfy distribution_to_chain's FlexiChain-returning contract (#94).
Arguments
obj: the fittable object the draws were sampled for.draws: the raw draws,dim x niteror aniter-vector ofdim-vectors.
Keyword Arguments
nchains: the number of chains the pooled columns split into, chain-major (default1).
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChains
struct ChainLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::ChainLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
leaf = ChainLeaf(2.0, 1.0)
draws = reshape([2.1, 2.4, 2.0, 2.6], 1, 4)
chain = DistributionsInference.draws_to_chain(leaf, draws)
FlexiChains.has_parameter(chain, :shape)See also
inference_to_distribution: the Monte Carlo posterior predictive over a chain (or these raw draws directly).inference_to_distributions: the vectorised, every-draw form.distribution_to_chain: the engine contract this satisfies.
DistributionsInference.estimated_rows Function
estimated_rows(obj) -> VectorThe estimated rows of a fittable object: those with a non-nothing prior.
estimated_rows(obj) filters parameter_rows(obj) to the rows whose prior field is set, in the same order. These are the free parameters the engine estimates; a fixed row (prior === nothing) is excluded. An object with no estimated rows has flat_dimension zero.
Arguments
obj: the fittable object.
Examples
using DistributionsInference, Distributions
rows = [(name = :shape, value = 2.0, prior = LogNormal(0.0, 0.2),
support = (0.0, Inf)),
(name = :scale, value = 1.0, prior = nothing, support = (0.0, Inf))]
DistributionsInference.estimated_rows(rows)See also
parameter_rows: the full row inventory this filters.flat_dimension: the estimated count.
DistributionsInference.extra_logprior Function
extra_logprior(obj, reconstructed, x, state) -> AnyAdditional log-prior mass that depends on the reconstructed object.
extra_logprior(obj, reconstructed, x, state) is the neutral extension point for a prior term that cannot be scored per-row against x alone — a hierarchical population term is the motivating case, where a pooled member's log-density depends on the (reconstructed) population hyperparameters, not just on its own flat coordinate. The default returns 0.0: most fittable objects need no such term, since an ordinary per-parameter prior is already scored from parameter_rows(obj)'s prior column in the engine's logdensity. A type with an object-dependent prior overrides this with its own method and gives the corresponding row(s) prior = nothing (see parameter_rows).
Arguments
obj: the template fittable object.reconstructed:objrebuilt atx(i.e.reconstruct(obj, x)), the object the extra term is scored against.x: the estimated flat parameter vectorreconstructedwas built from.state:extra_prior_state(obj), computed once at construction.
Examples
using DistributionsInference, Distributions
struct PooledPair
a::Float64
b::Float64
mu::Float64
end
function DistributionsInference.parameter_rows(p::PooledPair)
return [(name = :mu, value = p.mu, prior = Normal(0.0, 1.0),
support = (-Inf, Inf)),
(name = :a, value = p.a, prior = nothing, support = (-Inf, Inf)),
(name = :b, value = p.b, prior = nothing, support = (-Inf, Inf))]
end
function DistributionsInference.reconstruct(p::PooledPair, x::AbstractVector)
return PooledPair(p.a, p.b, x[1])
end
# a and b share the population Normal(mu, 1): an object-dependent prior,
# scored here rather than per row.
function DistributionsInference.extra_logprior(p::PooledPair, r, x, ::Any)
return logpdf(Normal(r.mu, 1.0), r.a) + logpdf(Normal(r.mu, 1.0), r.b)
end
DistributionsInference.extra_logprior(
PooledPair(0.2, -0.1, 0.0), PooledPair(0.2, -0.1, 0.5), [0.5], nothing)See also
logdensity: adds this term after the per-row priors.parameter_rows: the row schema this keeps to four fields.extra_prior_state: precomputed once, threaded in asstate.
DistributionsInference.extra_prior_state Function
extra_prior_state(
obj
) -> Vector{Tuple{Tuple, Symbol, ComposedDistributions.Pool}}Structure-dependent state extra_logprior needs, computed once.
extra_prior_state(obj) runs once, when distribution_to_logdensity assembles a FitLogDensity, and the result is threaded into every extra_logprior call for that obj. The default returns nothing, which suits most fittable objects since extra_logprior's default is a constant 0.0.
Override this alongside extra_logprior when computing the extra term needs a walk of obj's own structure (e.g. finding which rows carry an object-dependent prior). That walk depends only on obj, never on the flat vector, so paying it here turns a per-evaluation cost into a one-off.
Arguments
obj: the template fittable object.
Examples
using DistributionsInference
struct ToyLeaf
shape::Float64
scale::Float64
end
DistributionsInference.extra_prior_state(::ToyLeaf) === nothingSee also
extra_logprior: consumes this state.distribution_to_logdensity: computes it once, at construction.
DistributionsInference.flat_dimension Function
flat_dimension(obj) -> AnyThe estimated parameter dimension of a fittable object.
flat_dimension(obj) is the number of scalar estimated parameters: the count of parameter_rows(obj) rows whose prior is not nothing. An object with no estimated rows has flat dimension 0. It is the length of the flat vector reconstruct consumes and the engine's logdensity evaluates on.
Arguments
obj: the fittable object.
Examples
using DistributionsInference, Distributions
rows = [(name = :shape, value = 2.0, prior = LogNormal(0.0, 0.2),
support = (0.0, Inf)),
(name = :scale, value = 1.0, prior = nothing, support = (0.0, Inf))]
DistributionsInference.flat_dimension(rows)See also
estimated_rows: the rows this counts.
DistributionsInference.flat_priors Function
flat_priors(
prob::DistributionsInference.FitLogDensity
) -> AnyThe estimated rows' priors a FitLogDensity was assembled with.
flat_priors(prob) is the accessor onto prob's flat_priors field: the estimated_rows(template(prob)) priors, collected once at construction, in parameter_rows order. An entry is nothing for a row scored instead through extra_logprior (an object-dependent prior). An engine author reaches it through this function rather than prob.flat_priors directly, for the same reason as template — this is also the vector to_constrained/to_unconstrained build their per-row transform from.
Arguments
prob: the assembledFitLogDensity.
Examples
using DistributionsInference, Distributions
struct AccessorLeaf3
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::AccessorLeaf3)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
leaf = AccessorLeaf3(2.0, 1.0)
prob = distribution_to_logdensity(leaf, [1.5, 2.0, 3.2])
DistributionsInference.flat_priors(prob)See also
template,observations: the other two accessors.to_constrained,to_unconstrained: built from this vector.
DistributionsInference.inference_to_dist Function
Short alias for inference_to_distribution.
inference_to_dist(obj, chain; draws) is identical to inference_to_distribution(obj, chain; draws) — the equal-weight MixtureModel over selected draws — and inference_to_dist(obj, chain, summary; draws) is identical to the marginal plug-in form (reduction positional). inference_to_distribution is the canonical, documented name; this is a shorter spelling of the same underlying function (a const alias, so every method — including one an extension adds later — is automatically shared between the two names).
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.summary: (3-argument form only) the reduction applied to each estimated row's selected draws before a singlereconstruct.
Keyword Arguments
draws: which pooled draws to use; seeinference_to_distributionsfor the accepted forms and the chain-major pooling caveat.nchains: raw-draws form only (default1).rng: used whendrawsis anInteger(defaultRandom.default_rng()).
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
struct GammaLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::GammaLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::GammaLeaf, x::AbstractVector)
return Gamma(x[1], d.scale)
end
leaf = GammaLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
mean(inference_to_dist(leaf, chain))See also
inference_to_distribution: the canonical name this aliases.
DistributionsInference.inference_to_distribution Function
inference_to_distribution(
obj,
chain::FlexiChains.FlexiChain;
draws,
rng
) -> Distributions.MixtureModel{VF, VS, C, Distributions.Categorical{Float64, Vector{Float64}}} where {VF<:Distributions.VariateForm, VS<:Distributions.ValueSupport, C<:Distributions.Distribution}The equal-weight MixtureModel over selected draws: the Monte Carlo posterior predictive.
inference_to_distribution(obj, chain; draws = nothing) reconstructs one Distribution per selected draw (as inference_to_distributions does) and returns the equal-weight MixtureModel over them — the Monte Carlo approximation to the posterior predictive distribution, carrying the full posterior uncertainty rather than collapsing it to a point.
Raises an ArgumentError naming the actual type reached when reconstruct(obj, x) does not return a Distribution (e.g. the toy fittable types in this package's own docstrings) — use inference_to_distributions for those.
chain also accepts raw draws with no chain of their own; see inference_to_distributions for the accepted forms and the nchains keyword.
Performance
The MixtureModel is built over every selected draw — this function does not cap or thin it, even at a large draw count, so a call over the pooled draws of a big multi-chain run mixes every one of them. Selecting fewer draws (via the draws keyword) is the caller's tool for the cost below, not a hidden default.
Once built, mean/rand/pdf/cdf on the mixture are cheap: mean/rand are O(1) in the component count K, and pdf/cdf are O(K) per call but stay well under a millisecond even at K = 8000 (~0.16 ms / ~0.9 ms respectively). quantile is the one call that is not cheap: it root-finds against the mixture's O(K) cdf on every iteration, so its cost grows with K and reaches roughly 13 ms at K = 8000. A single quantile call is fine; many quantile calls in a loop (e.g. a full posterior interval grid) are where this adds up — thin first with draws = 500 (or similar) to cut K, and quantile's cost, by the same factor.
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.
Keyword Arguments
draws: which pooled draws to keep. Seeinference_to_distributionsfor the four accepted forms and the chain-major pooling this documents (draws = 1:nis not a subsample of a multi-chain run — read that docstring before using a range here). Also the cheapest way to bound thequantilecost above, by cappingK.nchains: raw-draws form only (default1).rng: used whendrawsis anInteger(defaultRandom.default_rng()).
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
struct GammaLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::GammaLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::GammaLeaf, x::AbstractVector)
return Gamma(x[1], d.scale)
end
leaf = GammaLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
mean(inference_to_distribution(leaf, chain))See also
inference_to_dist: an alias for this function.inference_to_distributions: the vectorised form this is built on, and thedrawskeyword's full documentation.inference_to_distribution(obj, chain, summary): the cheaper plug-in point estimate this trades accuracy against.
inference_to_distribution(
obj,
chain::FlexiChains.FlexiChain,
summary;
draws,
rng
) -> AnyThe marginal plug-in Distribution: summarise, then reconstruct once.
inference_to_distribution(obj, chain, summary; draws = nothing) reduces each estimated row's selected draws with summary (e.g. mean) to a flat parameter vector, and calls reconstruct once on that vector — the plug-in estimate, positionally requiring the reduction so the loss of posterior uncertainty is visible at the call site. This replaces the old point_estimate, and like it, does not require reconstruct(obj, x) to return a Distribution: this form summarises and reconstructs once, exactly what inference_to_distributions does once per draw, so it is generic in the same way. Only the 2-argument MixtureModel form needs a Distribution out.
This is not the posterior predictive. Summarising before reconstructing is not the same distribution as reconstructing every draw and mixing: Gamma(mean(shape draws), scale) != mean(Gamma.(shape draws, scale)) — the left side is what this function returns, the right side (its Monte Carlo approximation) is inference_to_distribution(obj, chain). Reach for this only when a single point estimate genuinely suffices (e.g. handing one Distribution to code that takes one), not as a stand-in for the posterior predictive. That comparison needs reconstruct to return a Distribution, of course, even though this function itself does not require it.
chain also accepts raw draws with no chain of their own; see inference_to_distributions for the accepted forms and the nchains keyword.
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.summary: the reductionAbstractVector -> scalarapplied to each estimated row's selected draws (e.g.mean,median).
Keyword Arguments
draws: which pooled draws to summarise over. Seeinference_to_distributionsfor the four accepted forms and the chain-major pooling caveat.nchains: raw-draws form only (default1).rng: used whendrawsis anInteger(defaultRandom.default_rng()).
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
using Statistics: mean
struct GammaLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::GammaLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::GammaLeaf, x::AbstractVector)
return Gamma(x[1], d.scale)
end
leaf = GammaLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
inference_to_distribution(leaf, chain, mean)reconstruct(obj, x) need not return a Distribution here — unlike the 2-argument form, this one never mixes:
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
using Statistics: mean
struct NonDistLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::NonDistLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::NonDistLeaf, x::AbstractVector)
return NonDistLeaf(x[1], d.scale)
end
leaf = NonDistLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
inference_to_distribution(leaf, chain, mean).shapeSee also
inference_to_distribution: the Monte Carlo posterior predictive this trades accuracy for cheapness against.
DistributionsInference.inference_to_distributions Function
inference_to_distributions(
obj,
chain::FlexiChains.FlexiChain;
draws,
rng
) -> VectorEvery selected draw, reconstructed: the vectorised posterior readback.
inference_to_distributions(obj, chain; draws = nothing) reads chain (pooled across every chain it carries) and returns one reconstruct(obj, x) per selected draw. Unlike inference_to_distribution, the reconstructed element does not need to be a Distribution — this is the generic vectorised readback.
chain is a dotted-name FlexiChain, a VarName-keyed chain (once DynamicPPL is loaded alongside this package), or raw draws with no chain of their own — a dim x ndraws matrix or an ndraws-length vector of dim-length vectors, built into a chain via draws_to_chain — with an nchains keyword (default 1) for the raw-draws form.
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.
Keyword Arguments
draws: which pooled draws to keep — see "Thedrawskeyword" below.nchains: raw-draws form only; splits the pooled columns into this many equal chains, chain-major (default1).rng: the random-number generator used whendrawsis anInteger(defaultRandom.default_rng()).
The draws keyword
Every chain this reads pools its iterations across every chain it carries, chain-major: chain 1 occupies the first niters pooled entries, chain 2 occupies the next niters, and so on. draws = 1:200 on a chain with 4 chains of 2000 iterations each returns 200 draws from chain 1 only — it looks like a subsample of the whole run and is not (the user-facing form of the bug fixed in #89).
nothing(default): every pooled draw.an
AbstractRangeorAbstractVector{<:Integer}: exactly those pooled indices, in the chain-major order above. Every index must fall within1:n(nthe pooled draw count) — an out-of-range index (including0or negative: there is no wraparound-from-the-end indexing here) raises anArgumentErrornamingnand the offending index, rather than reaching a bareBoundsError.an
Integern:ndraws sampled at random from the whole pooled set, so this — not a range — is the way to get "some draws" that actually span every chain.0 <= n <= (the pooled draw count), checked the same way.
An empty selection (draws = 0, or an empty range/vector) is allowed here and returns an empty Vector — there is nothing wrong with "zero draws, reconstructed" as a vectorised result. inference_to_distribution (both forms) refuses an empty selection instead, since a MixtureModel over zero components and a summary over an empty collection have no sensible answer.
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
struct DrawsLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::DrawsLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::DrawsLeaf, x::AbstractVector)
return DrawsLeaf(x[1], d.scale)
end
leaf = DrawsLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
length(inference_to_distributions(leaf, chain))See also
inference_to_distribution: the equal-weightMixtureModelover this same selection — the Monte Carlo posterior predictive.inference_to_distribution(obj, chain, summary): the plug-in point estimate instead of the full draw set.
DistributionsInference.inference_to_dists Function
Short alias for inference_to_distributions.
inference_to_dists(obj, chain; draws) is identical to inference_to_distributions(obj, chain; draws) — every selected draw, reconstructed. inference_to_distributions is the canonical, documented name; this is a shorter spelling of the same underlying function (a const alias, so every method — including one an extension adds later — is automatically shared between the two names).
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.
Keyword Arguments
draws: which pooled draws to keep; seeinference_to_distributionsfor the accepted forms and the chain-major pooling caveat.nchains: raw-draws form only (default1).rng: used whendrawsis anInteger(defaultRandom.default_rng()).
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
struct DrawsAliasLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::DrawsAliasLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(
d::DrawsAliasLeaf, x::AbstractVector)
return DrawsAliasLeaf(x[1], d.scale)
end
leaf = DrawsAliasLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
length(inference_to_dists(leaf, chain))See also
inference_to_distributions: the canonical name this aliases.
DistributionsInference.inference_to_parameter_distribution Function
inference_to_parameter_distribution(
obj,
chain;
kwargs...
) -> AnyThe joint posterior approximated as a MvNormal, on the unconstrained scale.
inference_to_parameter_distribution(obj, chain; draws = nothing) fits a Distributions.MvNormal to chain's selected draws (pooled across every chain it carries, exactly as inference_to_distributions does) — the Gaussian approximation to the joint posterior, capturing the correlations between estimated_rows(obj)'s parameters that a set of marginal summaries (e.g. distribution_params's default mean, taken one row at a time) discards.
The returned MvNormal lives on the unconstrained scale, in estimated_rows(obj) row order: every draw is mapped through to_unconstrained before its mean and covariance are computed. A row with positive support (a shape, a scale, ...) has a posterior skewed toward a boundary the constrained scale cannot cross, and a Gaussian fitted there directly would put mass on impossible values (e.g. a negative shape); the unconstrained scale (a log link for a positive-support prior, a logit link for a [0, 1]-supported one, and so on, exactly as to_unconstrained/to_constrained build it from obj's own parameter_rows priors) is where the Gaussian approximation is honest. Map a draw from this MvNormal back to the constrained scale with to_constrained.
The intended use is Markov melding: a fitted posterior from an upstream model becomes a joint prior for a downstream one. Because this MvNormal carries correlation, a downstream model must score it as a joint term through extra_logprior — which receives the reconstructed object's full flat parameter vector (see parameter_rows) — not through per-row priors, which are independent by construction and would silently drop the correlation this function exists to keep.
This has no method until Bijectors is loaded (the unconstrained transform is a Bijectors extension concern, exactly as for to_unconstrained itself); the implementation lives in the DistributionsInferenceBijectorsExt extension.
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.
Keyword Arguments
draws: which pooled draws to fit over; seeinference_to_distributionsfor the four accepted forms and the chain-major pooling caveat. Needs more selected draws thanobjhas estimated parameters, so the fitted covariance is non-singular.nchains: raw-draws form only (default1).rng: used whendrawsis anInteger(defaultRandom.default_rng()).
Examples
using DistributionsInference, Distributions, Bijectors
using FlexiChains: FlexiChain, Parameter
struct MeldLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::MeldLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale,
prior = LogNormal(log(1.0), 0.2), support = (0.0, Inf))]
end
leaf = MeldLeaf(2.0, 1.0)
shape_draws = [2.1, 2.4, 2.0, 2.6]
scale_draws = [0.9, 1.1, 1.0, 1.2]
chain = FlexiChain{Symbol}(4, 1, Dict(
Parameter(:shape) => reshape(shape_draws, 4, 1),
Parameter(:scale) => reshape(scale_draws, 4, 1)))
mvn = inference_to_parameter_distribution(leaf, chain)
mvn isa MvNormalSee also
inference_to_parameters: the exact draws this fits over.to_unconstrained,to_constrained: the transform this scale lives on.extra_logprior: where a downstream model scores this as a joint prior.
DistributionsInference.inference_to_parameters Function
inference_to_parameters(
obj,
chain::FlexiChains.FlexiChain;
draws,
rng
) -> NamedTupleExact posterior draws, keyed by an object's dotted parameter names.
inference_to_parameters(obj, chain; draws = nothing) reads chain (pooled across every chain it carries, exactly as inference_to_distributions does) and returns a NamedTuple keyed by estimated_rows(obj)'s dotted names, one entry per row, each an exact vector of that row's selected draws — no summary, no reconstruct; the values are read straight off chain. This is the gap the reconstructed-object verbs (inference_to_distributions, inference_to_distribution) leave open: both need reconstruct(obj, x) to run before their result is any use, so neither hands back the parameter draws themselves.
A NamedTuple of equal-length vectors already satisfies the Tables.jl column-table interface structurally (every column an AbstractVector, every column the same length), so DataFrame(result) (or any other Tables.jl consumer) works with no Tables.jl dependency on this package's part — the interface is the promise, not a wrapper type this package defines.
chain also accepts raw draws with no chain of their own; see inference_to_distributions for the accepted forms and the nchains keyword.
Arguments
obj: the fittable object the draws were sampled for.chain: the chain (or raw draws) to read from.
Keyword Arguments
draws: which pooled draws to keep; seeinference_to_distributionsfor the four accepted forms and the chain-major pooling caveat.nchains: raw-draws form only (default1).rng: used whendrawsis anInteger(defaultRandom.default_rng()).
An object estimating nothing (estimated_rows(obj) empty) returns NamedTuple() — a legitimate zero-column table.
Two estimated rows sharing a dotted name is refused with an ArgumentError naming the duplicate, exactly as distribution_params does.
Examples
using DistributionsInference, Distributions
using FlexiChains: FlexiChain, Parameter
struct ParameterLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::ParameterLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
leaf = ParameterLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = FlexiChain{Symbol}(
4, 1, Dict(Parameter(:shape) => reshape(draws, 4, 1)))
inference_to_parameters(leaf, chain)See also
inference_to_parameter_distribution: the joint Gaussian approximation fitted to these same draws, on the unconstrained scale.inference_to_distributions: the reconstructed-object analogue of this function.distribution_params: the older, reduce-first primitive — it collapses each row to one value bysummary(defaultmean) before returning; this keeps every selected draw.
DistributionsInference.logdensity Function
logdensity(
prob::DistributionsInference.FitLogDensity,
x::AbstractVector
) -> AnyEvaluate a FitLogDensity on its estimated flat parameter vector.
logdensity(prob, x) is the (unnormalised) log-posterior at the estimated flat vector x (in parameter_rows(prob.obj) row order restricted to the estimated rows), on the constrained scale: each prior in x is scored directly, with no Jacobian correction (an unconstrained-scale transform is a Bijectors extension concern). The value is the sum of the priors' log-densities at x, plus extra_logprior (an object-dependent prior term; 0.0 unless prob.obj overrides it), plus the data log-likelihood of the object reconstructed there via reconstruct. x is flat_dimension(prob.obj) long — empty when prob.obj estimates nothing, where logdensity is just the data likelihood.
Arguments
prob: the assembledFitLogDensity.x: an estimated flat parameter vector of lengthflat_dimension(prob.obj).
Examples
using DistributionsInference, Distributions
struct FitLeaf
shape::Float64
scale::Float64
end
Distributions.logpdf(d::FitLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::FitLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::FitLeaf, x::AbstractVector)
return FitLeaf(x[1], d.scale)
end
leaf = FitLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2]
prob = distribution_to_logdensity(leaf, data)
DistributionsInference.logdensity(prob, [2.5])See also
distribution_to_logdensity: assembleprob.reconstruct: the flat vector -> concrete object hook this calls.
DistributionsInference.logdensity_to_objective Function
logdensity_to_objective(
prob
) -> DistributionsInferenceBijectorsExt.var"#11#12"{DistributionsInference.FitLogDensity{D, T, L, SD, FP, ES, CF}} where {D, T, L, SD, FP, ES, CF}The negative unconstrained log-posterior, as a plain callable for an external optimiser.
logdensity_to_objective(prob) returns f(z) -> Real: the negative of to_constrained's unconstrained-scale log-target logdensity(prob, x) + logjac at (x, logjac) = to_constrained(prob, z). Because distribution_to_logdensity's objective is already a plain (unnormalised) log-density, minimising f with any standard optimisation package finds a maximum a posteriori point directly. logdensity always scores an estimated row's own prior (that is what makes a row estimated; see parameter_rows), so a genuine maximum likelihood point needs a prior whose curvature is negligible next to the data likelihood rather than a loglik swap alone. The optimiser stays external (Optim.jl, Optimization.jl, or any package that accepts a plain callable and an initial vector); DistributionsInference ships no estimator method itself.
The optimiser's minimiser z_hat becomes a fitted object through objective_to_distribution(prob, z_hat). Reach for this function when the optimiser call is yours to write; optimise_distribution runs the whole round trip instead.
This has no method until Bijectors is loaded; the implementation lives in the DistributionsInferenceBijectorsExt extension.
Arguments
prob: the assembledFitLogDensity.
Examples
using DistributionsInference, Distributions, Bijectors
struct OptimLeaf
shape::Float64
scale::Float64
end
Distributions.logpdf(d::OptimLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::OptimLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::OptimLeaf, x::AbstractVector)
return OptimLeaf(x[1], d.scale)
end
leaf = OptimLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2, 2.8, 1.9]
prob = distribution_to_logdensity(leaf, data)
f = logdensity_to_objective(prob)
# `f` is a plain callable, ready for any external optimiser.
f([0.0])See also
to_constrained: the unconstrained transform this composes.distribution_to_logdensity,logdensity: the underlying objective.objective_to_distribution: the minimiser back to a fitted object.
DistributionsInference.minimise Function
minimise(objective, init, optimiser; kwargs...) -> AnyMinimise a plain callable from a starting point, with a given optimiser.
minimise(objective, init, optimiser) returns the minimising vector. It is the one optimiser-package-specific step of optimise_distribution, kept apart so that supporting another optimisation package is a method on this function and nothing else. Keyword arguments are forwarded to the optimiser package's own entry point.
The Optim.jl method (any Optim.AbstractOptimizer, e.g. LBFGS()) lives in the DistributionsInferenceOptimExt extension and has no method until Optim is loaded. Its options keyword takes an Optim.Options, and its autodiff keyword an ADTypes backend.
Set autodiff for any gradient-based optimiser. Optim differentiates by central finite differences unless told otherwise, which spends 2n objective evaluations on every gradient and loses accuracy; logdensity_to_objective's objective differentiates directly, so an AD backend gets the same point for a fraction of the work. The backend's package has to be loaded for ADTypes to dispatch on it.
using DistributionsInference, Bijectors, Optim, ADTypes, ForwardDiff
optimise_distribution(leaf, data, LBFGS(); autodiff = AutoForwardDiff())Arguments
objective: a callablez -> Realto minimise, e.g. the result oflogdensity_to_objective.init: the starting vector.optimiser: the optimiser, whose type selects the method.
Examples
using DistributionsInference, Optim
DistributionsInference.minimise(z -> sum(abs2, z .- 2.0), [0.0, 0.0], LBFGS())See also
optimise_distribution: the one-call fit this serves.logdensity_to_objective: the objective it usually minimises.
DistributionsInference.objective_to_distribution Function
objective_to_distribution(prob, z) -> AnyRebuild a fitted distribution from an optimiser's minimiser.
objective_to_distribution(prob, z) closes the round trip logdensity_to_objective opens: it maps the unconstrained point z an optimiser returned back to the constrained scale with to_constrained and rebuilds a concrete object there via reconstruct. What comes back is the same kind of object prob was assembled from, not a parameter vector.
This has no method until Bijectors is loaded; the implementation lives in the DistributionsInferenceBijectorsExt extension.
Arguments
prob: the assembledFitLogDensity.z: an unconstrained flat vector of lengthflat_dimension(prob.obj), e.g. an optimiser's minimiser.
Examples
using DistributionsInference, Distributions, Bijectors
struct MinimiserLeaf
shape::Float64
scale::Float64
end
function Distributions.logpdf(d::MinimiserLeaf, y::Real)
return logpdf(Gamma(d.shape, d.scale), y)
end
function DistributionsInference.parameter_rows(d::MinimiserLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::MinimiserLeaf, x::AbstractVector)
return MinimiserLeaf(x[1], d.scale)
end
leaf = MinimiserLeaf(2.0, 1.0)
prob = distribution_to_logdensity(leaf, [1.5, 2.0, 3.2])
objective_to_distribution(prob, [0.5]).shapeSee also
optimise_distribution: the one-call fit this is the last step of.logdensity_to_objective: the objective whose minimiser this reads.to_constrained,reconstruct: the two steps it composes.
DistributionsInference.observations Function
observations(
prob::DistributionsInference.FitLogDensity
) -> AnyThe observed data a FitLogDensity scores against.
observations(prob) is the accessor onto prob's data field: the records prob's loglik reducer is scored against at each logdensity call. An engine author reaches it through this function rather than prob.data directly, for the same reason as template.
Arguments
prob: the assembledFitLogDensity.
Examples
using DistributionsInference, Distributions
struct AccessorLeaf2
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::AccessorLeaf2)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
leaf = AccessorLeaf2(2.0, 1.0)
data = [1.5, 2.0, 3.2]
prob = distribution_to_logdensity(leaf, data)
DistributionsInference.observations(prob) === dataSee also
template,flat_priors: the other two accessors.distribution_to_logdensity: assemblesprob.
DistributionsInference.optimise_distribution Function
optimise_distribution(
obj,
data,
optimiser;
loglik,
init,
kwargs...
) -> AnyFit a distribution to data with an optimiser, in one call.
optimise_distribution(obj, data, optimiser) runs the whole point fit and returns a fitted distribution of the same kind as obj: it assembles distribution_to_logdensity(obj, data), starts the optimiser at obj's own parameter values mapped through to_unconstrained, minimises logdensity_to_objective with optimiser via minimise, and maps the minimiser back with objective_to_distribution. A FitLogDensity already to hand is fitted directly with optimise_distribution(prob, optimiser).
The point found is a maximum a posteriori one on the unconstrained scale, since logdensity always scores an estimated row's own prior (that is what makes a row estimated; see parameter_rows) and the objective carries the transform's log-Jacobian. A maximum likelihood point needs a prior whose curvature is negligible next to the data likelihood. An object estimating nothing has nothing to optimise, and comes back as it went in.
The optimiser package stays a caller's choice: optimiser is passed to minimise, whose Optim.jl method lives in the DistributionsInferenceOptimExt extension. The transform needs Bijectors loaded as well.
Arguments
obj: the template fittable object, carrying itsparameter_rows.data: the observed records.optimiser: the optimiser to minimise with, e.g.Optim.LBFGS().
Keyword Arguments
loglik: a reducer(obj, data) -> Realscoringdataagainst the reconstructed object (default: sum oflogpdf(obj, record)).init: the starting point on the CONSTRAINED scale — the unitsobj's ownparameter_rowsvalues are in, and a caller thinks in (default:obj's own estimated values). Mapped throughto_unconstrainedinternally before the optimiser sees it. A value on the boundary of its prior's support (a zero rate, a zero or one probability) has no unconstrained image, and is rejected with the offending row named rather than passed to the optimiser.other keywords are forwarded to
minimise, and from there to the optimiser package. WithOptim,optionstakes anOptim.OptionsandautodiffanADTypesbackend; seeminimisefor why a gradient-based fit wants the latter set.
Examples
using DistributionsInference, Distributions, Bijectors, Optim
struct FitLeaf{T <: Real}
shape::T
scale::Float64
end
Distributions.logpdf(d::FitLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::FitLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.5), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::FitLeaf, x::AbstractVector)
return FitLeaf(x[1], d.scale)
end
data = [1.5, 2.0, 3.2, 2.8, 1.9, 4.1, 2.5, 3.0]
fitted = optimise_distribution(FitLeaf(2.0, 1.0), data, LBFGS())
fitted.shapeSee also
minimise: the optimiser hook this calls.logdensity_to_objective,to_unconstrained,objective_to_distribution: the pieces this composes.distribution_to_logdensity: the sampling route to the same posterior.
DistributionsInference.parameter_rows Function
parameter_rows(obj) -> AnyThe scalar parameter rows of a fittable object.
parameter_rows(obj) returns an iterable of rows, one per scalar parameter of obj, each a NamedTuple with fields:
name: aSymbolidentifying the parameter (a dotted path for a nested parameter, e.g.Symbol("onset.shape")).value: the parameter's current value.prior: the attached prior (aUnivariateDistribution) if the parameter is estimated, ornothingif it is fixed atvalue.support: the(lower, upper)bounds of the parameter's admissible domain.
A parameter estimated under an object-dependent prior (e.g. a hierarchical population term whose log-density depends on other reconstructed parameters, not on value alone) also carries prior = nothing at the row level — the same as a fixed parameter — and is scored instead through extra_logprior. This keeps the row schema to exactly these four fields for every parameter kind. A type with such rows must give its own estimated_rows/flat_dimension methods (the generic defaults below treat prior === nothing as fixed and would otherwise drop it from the flat vector).
Every fittable object implements this with its own method; estimated_rows, flat_dimension and the engine's distribution_to_logdensity are built on it. A bare AbstractVector of already-built rows is its own parameter_rows, so a literal row list can stand in for a fittable object without a wrapping type.
Arguments
obj: the fittable object.
Examples
using DistributionsInference, Distributions
rows = [(name = :shape, value = 2.0, prior = LogNormal(0.0, 0.2),
support = (0.0, Inf)),
(name = :scale, value = 1.0, prior = nothing, support = (0.0, Inf))]
parameter_rows(rows) === rowsSee also
estimated_rows: the subset with an attached prior.reconstruct: the companion hook, flat vector -> concrete object.
DistributionsInference.reconstruct Function
reconstruct(obj, x::AbstractVector) -> Distributions.GammaReconstruct a concrete fittable object from an estimated flat parameter vector.
reconstruct(obj, x) returns a new object of the same kind as obj with each estimated parameter (an estimated_rows(obj) row, in parameter_rows order) taken from x, and every fixed parameter held at its value in obj. x is flat_dimension(obj) long — empty when obj estimates nothing, in which case reconstruct(obj, x) == obj.
This is the companion hook every fittable object implements with its own method, alongside parameter_rows. The engine's logdensity calls it once per evaluation to score the observations against the object collapsed at x.
An estimated field's type must stay generic (e.g. shape::S, not shape::Float64): a gradient-based sampler threads a tracer number (a ForwardDiff.Dual, a ReverseDiff.TrackedReal, ...) through x, and a concrete field rejects it with an opaque MethodError from inside obj's own constructor. Both call sites (logdensity and the DynamicPPL extension's turing model) guard this ahead of time with a named ArgumentError instead. A Union-typed field (e.g. Union{Float64, Missing}) is a false negative of that guard: isconcretetype is false for a Union, so it reads as already generic even though it cannot hold a tracer either.
Arguments
obj: the fittable object whose structure is rebuilt.x: an estimated flat vector of lengthflat_dimension(obj).
Examples
using DistributionsInference, Distributions
struct DemoLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::DemoLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::DemoLeaf, x::AbstractVector)
return DemoLeaf(x[1], d.scale)
end
DistributionsInference.reconstruct(DemoLeaf(2.0, 1.0), [3.5])See also
parameter_rows: the row inventory whose order fixesx's layout.distribution_to_logdensity: the engine assembler built onreconstruct.
DistributionsInference.template Function
template(prob::DistributionsInference.FitLogDensity) -> AnyThe template fittable object a FitLogDensity was assembled from.
template(prob) is the accessor onto prob's obj field: the structure reconstruct rebuilds. An engine author reaches it through this function rather than prob.obj directly, so a struct-field rename in a future release does not break an engine implemented outside this package.
Arguments
prob: the assembledFitLogDensity.
Examples
using DistributionsInference, Distributions
struct AccessorLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::AccessorLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
leaf = AccessorLeaf(2.0, 1.0)
prob = distribution_to_logdensity(leaf, [1.5, 2.0, 3.2])
DistributionsInference.template(prob) === leafSee also
observations,flat_priors: the other two accessors.distribution_to_logdensity: assemblesprob.
DistributionsInference.to_constrained Function
to_constrained(prob, z) -> Tuple{Any, Any}Map an unconstrained vector to the constrained scale and its log-Jacobian.
to_constrained(prob, z) returns (x, logjac): the constrained estimated flat parameters x corresponding to the unconstrained vector z, and the log-determinant Jacobian of that (inverse) transform. The transform is built per row from FitLogDensity's stored flat_priors (each estimated row's Bijectors.bijector(prior) — a positive-support prior pushes through an exp-type link, a simplex-valued prior through a logit/stick-breaking-type link, and so on). The unconstrained log-density a sampler works with is logdensity(prob, x) + logjac.
An estimated row with no per-row prior (prior === nothing, scored instead through extra_logprior — an object-dependent prior, e.g. a hierarchical population term; see parameter_rows) has no distribution to build a bijector from, so it is rejected with a clear ArgumentError, mirroring distribution_to_turing's rejection of the same row kind. A type needing an unconstrained transform for such a row supplies its own to_constrained method.
This has no method until Bijectors is loaded; the prior-driven transform lives in the DistributionsInferenceBijectorsExt extension.
Arguments
prob: the assembledFitLogDensity.z: an unconstrained flat vector of lengthflat_dimension(prob.obj).
Examples
using DistributionsInference, Distributions, Bijectors
struct ConstrainedLeaf
shape::Float64
scale::Float64
end
Distributions.logpdf(d::ConstrainedLeaf, y::Real) = logpdf(Gamma(d.shape, d.scale), y)
function DistributionsInference.parameter_rows(d::ConstrainedLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
function DistributionsInference.reconstruct(d::ConstrainedLeaf, x::AbstractVector)
return ConstrainedLeaf(x[1], d.scale)
end
leaf = ConstrainedLeaf(2.0, 1.0)
data = [1.5, 2.0, 3.2]
prob = distribution_to_logdensity(leaf, data)
# An unconstrained draw maps to the constrained (positive) shape plus a
# log-Jacobian.
x, logjac = DistributionsInference.to_constrained(prob, [0.0])
xSee also
distribution_to_logdensity: assembleprob.logdensity: the constrained-scale density this transform feeds.parameter_rows,reconstruct: the fit protocol this reads.
DistributionsInference.to_unconstrained Function
to_unconstrained(prob, x) -> AnyMap constrained estimated parameters to the unconstrained scale.
to_unconstrained(prob, x) is the forward direction of to_constrained: the unconstrained vector z whose constrained image is x, built per row from FitLogDensity's stored flat_priors (each estimated row's Bijectors.bijector(prior)). No log-Jacobian comes back, since the value of the transform is what a caller wants here — most often a starting point for an optimiser at the parameter values the template already carries, rather than an arbitrary zero.
An estimated row with no per-row prior (prior === nothing, scored instead through extra_logprior) has no distribution to build a bijector from and is rejected with an ArgumentError, exactly as in to_constrained.
This has no method until Bijectors is loaded; the prior-driven transform lives in the DistributionsInferenceBijectorsExt extension.
Arguments
prob: the assembledFitLogDensity.x: a constrained estimated flat vector of lengthflat_dimension(prob.obj).
Examples
using DistributionsInference, Distributions, Bijectors
struct UnconstrainedLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::UnconstrainedLeaf)
return [(name = :shape, value = d.shape,
prior = LogNormal(log(2.0), 0.2), support = (0.0, Inf)),
(name = :scale, value = d.scale, prior = nothing,
support = (0.0, Inf))]
end
leaf = UnconstrainedLeaf(2.0, 1.0)
prob = distribution_to_logdensity(leaf, [1.5, 2.0, 3.2])
# The positive shape maps onto the whole real line.
DistributionsInference.to_unconstrained(prob, [2.0])See also
to_constrained: the inverse, plus its log-Jacobian.optimise_distribution: the fit that starts from this point.
DistributionsInference.with_priors Function
with_priors(obj; priors, default) -> AnyAssemble a fully-specified row set from a fittable object, brms-style.
with_priors(obj; priors, default) reads parameter_rows(obj) and returns the same row shape with every row's prior field filled: a priors override for that row's dotted name, if given, else the row's own attached prior if it is already set, else default(row) (support-derived, default_prior unless a different default is given). The result is directly usable as obj's replacement row set — e.g. feeding reconstruct/estimated_rows or distribution_to_logdensity through the bare-row-vector identity (parameter_rows(rows) === rows) — so with_priors(obj) alone is the estimate-everything path for any fit-protocol object.
Arguments
obj: the fittable object (or a bare row vector).
Keyword Arguments
priors: per-parameter overrides, anAbstractDict{Symbol}keyed by a row's dottedname(default: empty).default: a functionrow -> priorfor rows not overridden and not already carrying aprior(default:default_prior).
Examples
using DistributionsInference, Distributions
rows = [(name = :shape, value = 2.0, prior = nothing, support = (0.0, Inf)),
(name = :scale, value = 1.0, prior = nothing, support = (0.0, Inf))]
priored = with_priors(rows)
priored[1].priorSee also
default_prior: the support-derived per-row default.parameter_rows: the row inventory read and replaced.