Public Documentation
Documentation for DistributionsInference's public interface.
Contents
Index
DistributionsInference.FitLogDensityDistributionsInference.as_logdensityDistributionsInference.as_optimisation_objectiveDistributionsInference.as_turingDistributionsInference.default_priorDistributionsInference.distribution_paramsDistributionsInference.distribution_priorsDistributionsInference.estimated_rowsDistributionsInference.extra_logpriorDistributionsInference.extra_prior_stateDistributionsInference.flat_dimensionDistributionsInference.logdensityDistributionsInference.parameter_rowsDistributionsInference.readbackDistributionsInference.readback_drawsDistributionsInference.reconstructDistributionsInference.to_constrainedDistributionsInference.to_flexichain
Public API
DistributionsInference.FitLogDensity Type
struct FitLogDensity{D, T, L, 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 as_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)).flat_priors: the estimated rows' priors, inparameter_rowsorder, collected once at construction sologdensitydoes not re-derive them on every evaluation. 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 (aLogNormalhere, aBetathere) makes this vector abstractly typed, sologdensitypays one dynamic dispatch per row; a tuple-of-priors specialisation is the natural follow-up if profiling ever shows this matters.extra_state:extra_prior_state(obj), collected once at construction alongsideflat_priorsand threaded into everyextra_logpriorcall, so a package whose extra term needs a structural walk ofobj(DI#28's motivating case: which rows carry an object-dependent prior) pays it once here rather than on every evaluation.concrete_fields:_concrete_field_candidates(obj), collected once at construction alongsideflat_priors/extra_state— the concrete-field- under-AD guard's (DI#48) own structural state, empty for a properly generic object, sologdensity's per-evaluation guard is a singleisemptycheck rather than a freshestimated_rows(obj)walk on every call.
See also
as_logdensity: the assembler.logdensity: evaluate on a flat vector.
Fields
obj::Anydata::Anyloglik::Anyflat_priors::Anyextra_state::Anyconcrete_fields::Any
DistributionsInference.as_logdensity Function
as_logdensity(
obj,
data;
loglik
) -> Union{DistributionsInference.FitLogDensity{_A, _B, typeof(DistributionsInference._default_loglik), _C, Nothing, Vector{Tuple{Int64, Symbol, DataType}}} where {_A, _B, _C}, DistributionsInference.FitLogDensity{_A, _B, typeof(DistributionsInference._default_loglik), _C, Vector{Tuple{Tuple, Symbol, ComposedDistributions.Pool}}, Vector{Tuple{Int64, Symbol, DataType}}} where {_A, _B, _C}}Assemble a FitLogDensity from a fittable object and data.
as_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, not this core engine's, mirroring ComposedDistributions' to_constrained.
A CONDITIONALLY available exact likelihood — some objects can score their data through a closed form only under a structural condition, and otherwise only through an approximation — is a loglik a caller writes and passes in directly, not a helper this package adds (DistributionsInference#44: the hook plus this convention are enough on their own). Choose between the exact and approximate branch with an explicit predicate or by dispatch; NEVER by catching an exception thrown from the exact path, since that hides a genuine bug in the exact branch as if the exact form simply did not apply. Where the exact form does not apply, refuse loudly with a named structural reason (an error or ArgumentError naming what is missing), the same convention to_constrained and as_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 = DistributionsInference.as_logdensity(leaf, data)
DistributionsInference.flat_dimension(leaf)A conditionally exact likelihood, chosen by predicate and passed straight in as loglik (no dedicated helper needed):
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
# GOOD: decide by an explicit predicate, refuse loudly when it does not
# apply. NEVER decide by catching an exception from the exact path (a
# genuine bug in the exact branch would then be silently misread as "exact
# form unavailable").
leaf2 = ToyLeaf2(2.0, 1.0)
data2 = [1.5, 2.0, 3.2]
prob2 = DistributionsInference.as_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.as_optimisation_objective Function
The NEGATIVE unconstrained log-posterior, as a plain callable for an external optimiser.
as_optimisation_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 as_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 (a very diffuse prior on an otherwise ordinary row) rather than a loglik swap alone. DistributionsInference ships no estimator method itself: this is only the thin wiring of the transform, the objective and reconstruct together that the org's fitting layer needs to earn its place alongside a bespoke distribution_mle/distribution_map pair — the optimiser stays external (Optim.jl, Optimization.jl, or any package that accepts a plain callable and an initial vector).
The result reconstructs through the EXISTING readback path: run the optimiser's minimiser z_hat back through to_constrained(prob, z_hat) to recover the constrained point, then reconstruct(prob.obj, x_hat) for the fitted object, exactly as after any other unconstrained draw.
This has no method until Bijectors is loaded (built directly on to_constrained); the implementation lives in the DistributionsInferenceBijectorsExt extension, so the core package stays free of a Bijectors dependency, and no optimisation package is a dependency at all.
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 = DistributionsInference.as_logdensity(leaf, data)
f = DistributionsInference.as_optimisation_objective(prob)
# NOTHING below is a DistributionsInference method: `f` is just a plain
# callable, ready for any external optimiser.
f([0.0])See also
to_constrained: the unconstrained transform this composes.as_logdensity,logdensity: the underlying objective.reconstruct: rebuild the fitted object from the optimiser's minimiser, viato_constrainedback to the constrained scale.
DistributionsInference.as_turing Function
A DynamicPPL model over a fittable object's estimated parameters.
as_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 as_logdensity exposes), so a fitted posterior is sampleable with sample(as_turing(obj, data), NUTS(), ...). It is a light wrapper on the as_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(as_logdensity(obj, data), x) at the corresponding constrained x by construction.
The ~ sites are named to match the readback/readback_draws contract exactly: an estimated row's dotted name (e.g. Symbol("onset.shape")) becomes the VarName <prefix>.onset.shape, so a chain from sample(as_turing(obj, data), ...; chain_type = FlexiChains.VNChain) reads back through readback/readback_draws unchanged (the VarName-keyed dispatch also lives in this extension).
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 as_logdensity + LogDensityProblemsAD (the LogDensityProblems extension) instead.
A gradient-based sampler (e.g. NUTS) evaluates reconstruct at a ForwardDiff.Dual-valued flat vector, so obj's type must accept a non-Real concrete element for each ESTIMATED field: a Distributions.jl leaf already does (its type parameter is generic), and a hand-written struct needs the same — a field concretely typed Float64 errors under NUTS (a gradient-free sampler, e.g. AdvancedMH, has no such constraint).
This method is available only when DynamicPPL is loaded (the model lives in a package extension).
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 defaultas_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(as_turing(leaf, data), NUTS(), 200;
chain_type = VNChain, progress = false)
fitted = DistributionsInference.readback(leaf, chain)
fitted.scale # the fixed parameter, untouchedSee also
as_logdensity: the PPL-neutral log-density this wraps.readback/readback_draws: read a fitted chain back ontoobj.parameter_rows/reconstruct: the fit protocol this reads.
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 distribution_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.
Mirrors ComposedDistributions' default_prior (same heuristic, ported by hand — DI cannot depend on CD's copy; see this file's header note).
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
distribution_priors: assembles a full row set from this default.
DistributionsInference.distribution_params Function
distribution_params(
obj,
chain::FlexiChains.FlexiChain;
summary,
draw,
draws
) -> NamedTupleRead a dotted-name FlexiChain's parameter values, keyed by name.
distribution_params(obj, chain) is the params-first readback primitive (CD#195/DI#20): the estimated parameter values read from chain, keyed by each estimated_rows(obj) row's dotted name, before any object is rebuilt — a single draw's values, or each row's draws reduced by summary over the draws selection (default: the mean over every draw). readback is a thin layer on top: it collapses this result to a flat vector (estimated_rows order is fixed, so values(...) recovers it) and calls reconstruct.
The argument order is obj first, chain second — matching to_flexichain(obj, draws) and readback(obj, chain) in this same file, and ComposedDistributions' chain_to_params(template, chain) (the function this generalises, CD#195/DI#20): keeping one order across the module avoids a silent argument swap between sibling calls.
Arguments
obj: the fittable object the chain's parameters were sampled for.chain: theFlexiChainto read parameter values from (seeto_flexichain).
Keyword Arguments
summary: the reductionAbstractVector -> scalarapplied to each row's draws (defaultmean); ignored whendrawis given.draw: a single iteration index to read, overridingsummary/draws.draws: a subset of iterations to reduce over (a range / index vector, or a predicate over the iteration index);nothinguses every iteration.
Two estimated rows sharing a dotted name is refused with a clear ArgumentError naming the duplicate: a NamedTuple cannot key two entries by the same name, and a repeated name can only mean parameter_rows(obj) gave two distinct parameters the same identifier (a protocol bug in obj's own implementation), not a case with a sensible silent resolution.
Examples
using DistributionsInference, Distributions
struct ParamsLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::ParamsLeaf)
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 = ParamsLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = DistributionsInference.to_flexichain(leaf, reshape(draws, 1, :))
DistributionsInference.distribution_params(leaf, chain)See also
readback: rebuildsobjfrom this primitive's result.readback_draws: the vectorised, every-draw form (its own optimised implementation, not layered on this — see its docstring).
DistributionsInference.distribution_priors Function
distribution_priors(obj; priors, default) -> AnyAssemble a fully-specified row set from a fittable object, brms-style.
distribution_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/as_logdensity through the bare-row-vector fittable-object identity (parameter_rows(rows) === rows) — so distribution_priors(obj) alone is the estimate-everything path for any fit-protocol object, generalising ComposedDistributions' param_priors/uncertain(tree) (CD#195) beyond composed-distribution trees.
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 = DistributionsInference.distribution_priors(rows)
priored[1].priorSee also
default_prior: the support-derived per-row default.parameter_rows: the row inventory read and replaced.
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 — a type overriding both methods reads whatever it stashed there instead of recomputing it fromobjon every call.
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. `PooledPair` needs no precomputed state
# (the population members are fixed by the type, not found by a structural
# walk), so `extra_prior_state` is left at its default `nothing`.
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 a single time, when as_logdensity assembles a FitLogDensity, and the result is threaded into every extra_logprior call for that obj — mirroring how as_logdensity already collects flat_priors once rather than re-deriving them on every logdensity evaluation. The default returns nothing: most fittable objects need no such state, since extra_logprior's default is a constant 0.0.
Override this alongside extra_logprior when computing the extra term requires walking obj's own structure (e.g. finding which rows carry an object-dependent prior) — that walk depends only on obj (fixed for the life of a FitLogDensity), never on the flat vector x, so paying it once here rather than inside extra_logprior 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.as_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.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 = DistributionsInference.as_logdensity(leaf, data)
DistributionsInference.logdensity(prob, [2.5])See also
as_logdensity: assembleprob.reconstruct: the flat vector -> concrete object hook this calls.
DistributionsInference.parameter_rows Function
parameter_rows(
obj
) -> Vector{NamedTuple{(:name, :value, :prior, :support)}}The 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).
This is the one function every fittable object implements with its own method; the fallback below only raises a clear error naming the missing method. estimated_rows, flat_dimension and the engine's as_logdensity are all built on it. A bare AbstractVector of already-built rows is its own parameter_rows (the identity), 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))]
DistributionsInference.parameter_rows(rows) === rowsSee also
estimated_rows: the subset with an attached prior.reconstruct: the companion hook, flat vector -> concrete object.
DistributionsInference.readback Function
readback(
obj,
chain::FlexiChains.FlexiChain;
summary,
draw,
draws
) -> AnyRead a dotted-name FlexiChain back onto a fitted object.
readback(obj, chain) reduces chain (built by to_flexichain) to a flat estimated parameter vector and rebuilds a concrete object via reconstruct: a point summary by default (summary applied to each estimated row's draws, default mean), a single iteration (draw), or a summary restricted to a subset of iterations (draws).
Arguments
obj: the fittable object the chain's parameters were sampled for.chain: theFlexiChainto read parameter values from (seeto_flexichain).
Keyword Arguments
summary: the reductionAbstractVector -> scalarapplied to each row's draws (defaultmean); ignored whendrawis given.draw: a single iteration index to read, overridingsummary/draws.draws: a subset of iterations to reduce over (a range / index vector, or a predicate over the iteration index);nothinguses every iteration.
Examples
using DistributionsInference, Distributions
struct ReadbackLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::ReadbackLeaf)
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::ReadbackLeaf, x::AbstractVector)
return ReadbackLeaf(x[1], d.scale)
end
leaf = ReadbackLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6]
chain = DistributionsInference.to_flexichain(leaf, reshape(draws, 1, :))
DistributionsInference.readback(leaf, chain).shapeSee also
distribution_params: the params-first primitive this layers on.to_flexichain: build the chain this reads.readback_draws: the vectorised, every-draw form.
DistributionsInference.readback_draws Function
readback_draws(
obj,
chain::FlexiChains.FlexiChain;
draws
) -> VectorRead every draw of a dotted-name FlexiChain back onto a fitted object.
readback_draws(obj, chain) is the vectorised form of readback: where readback reduces the chain to one reconstructed object, readback_draws keeps every draw, returning a vector of reconstructed objects (one per selected iteration) — e.g. for a per-draw posterior-predictive summary.
Arguments
obj: the fittable object the chain's parameters were sampled for.chain: theFlexiChainto read every draw from (seeto_flexichain).
Keyword Arguments
draws: a subset of iterations to keep (a range / index vector, or a predicate over the iteration index);nothingkeeps every iteration.
Examples
using DistributionsInference, Distributions
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 = DistributionsInference.to_flexichain(leaf, reshape(draws, 1, :))
length(DistributionsInference.readback_draws(leaf, chain))Not layered on distribution_params
Unlike readback, this does not call distribution_params once per draw: distribution_params re-fetches and re-validates every estimated row's column on each call, which would be O(niter x nrows) column look-ups here instead of the O(nrows) this implementation does by materialising each column once up front. The two stay independent implementations of the same per-draw extraction for this reason.
See also
readback: the single-draw / reduced read this vectorises.distribution_params: the params-first primitivereadback(but not this function) layers on.to_flexichain: build the chain this reads.
DistributionsInference.reconstruct Function
reconstruct(obj, x::AbstractVector) -> AnyReconstruct 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; rebuilding a concrete object is necessarily type-specific, so the fallback below only raises a clear error naming the missing method. The engine's logdensity calls it once per evaluation to score prob.data 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 clear, named ArgumentError instead. That guard is not exhaustive: a field typed Union{Float64, Missing} (or any other Union) cannot hold a tracer either, but isconcretetype reads false for a Union, so the guard treats it as already generic and stays silent — a documented false negative for an unusual parameterisation, not a false positive.
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.as_logdensity: the engine assembler built onreconstruct.
DistributionsInference.to_constrained Function
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 as_turing's rejection of the same row kind. A type needing an unconstrained transform for such a row supplies its own to_constrained method (mirrors ComposedDistributions' centred-pool handling, which reads the transform off the pooled population's family instead of the row's own — absent — prior).
This has no method until Bijectors is loaded; the prior-driven transform lives in the DistributionsInferenceBijectorsExt extension, so the core package stays free of a Bijectors dependency.
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 = DistributionsInference.as_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
as_logdensity: assembleprob.logdensity: the constrained-scale density this transform feeds.parameter_rows,reconstruct: the fit protocol this reads.
DistributionsInference.to_flexichain Function
to_flexichain(
obj,
draws
) -> 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.
to_flexichain(obj, draws) keys draws by estimated_rows(obj)'s dotted names (in parameter_rows order), so the result reads back onto obj with readback/readback_draws. 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.
No DynamicPPL/Turing involvement: this works with the draws of ANY sampler that consumes as_logdensity(obj, data) through the LogDensityProblems interface.
Arguments
obj: the fittable object the draws were sampled for.draws: the raw draws,dim x niteror aniter-vector ofdim-vectors.
Examples
using DistributionsInference, Distributions
struct FlexiLeaf
shape::Float64
scale::Float64
end
function DistributionsInference.parameter_rows(d::FlexiLeaf)
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 = FlexiLeaf(2.0, 1.0)
draws = [2.1, 2.4, 2.0, 2.6] # 1 estimated parameter, 4 draws
chain = DistributionsInference.to_flexichain(leaf, reshape(draws, 1, :))
using FlexiChains: FlexiChains
FlexiChains.parameters(chain)See also
readback: reduce the chain back ontoobj(point summary/draw).readback_draws: the vectorised, every-draw form.