Public Documentation
Documentation for ReparameterisedDistributions's public interface.
ReparameterisedDistributions.ReparameterisedDistributions Module
ReparameterisedDistributionsParameter-convention switches for Distributions.jl: wrap a distribution so that it is parameterised by the quantities a modeller reasons about — its moments — rather than by its native parameters.
Distributions.jl parameterises each family by its native parameters: a Gamma by shape and scale, a LogNormal by the mean and standard deviation of its logarithm. A delay distribution, though, is elicited as a mean and a standard deviation, and a prior belongs on the mean. Such a prior cannot be expressed through a native leaf, because independent priors on shape and scale do not compose into a prior on the mean.
reparameterise returns a Distribution whose parameters are the moments. It evaluates and samples exactly as the native distribution does, so it can be used directly on the left of a ~; it converts to the native family through an exact closed form; and it stays differentiable, so the moments can be sampled. native reaches the native distribution — and, through it, the native parameters — when the moments alone are not enough. rescale scales a registered moment while holding the others fixed, routing through the same closed form.
Examples
using ReparameterisedDistributions, Distributions
d = reparameterise(LogNormal; mean = 8.0, sd = 2.0)
(params(d), mean(d), std(d))Contents
Index
ReparameterisedDistributions.ReparameterisedDistributionsReparameterisedDistributions.AbstractReparameterisedDistributionReparameterisedDistributions.ReparameterisedReparameterisedDistributions.nativeReparameterisedDistributions.reparameteriseReparameterisedDistributions.rescaleReparameterisedDistributions.solve_momentReparameterisedDistributions.test_reparameterisationReparameterisedDistributions.to_nativeReparameterisedDistributions.valid_moments
Public API
ReparameterisedDistributions.AbstractReparameterisedDistribution Type
abstract type AbstractReparameterisedDistribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport} <: Distributions.Distribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport}Supertype for a distribution that stands in for a native family under a different parameterisation.
A subtype stores the alternative parameters and converts to the native family on demand, so it evaluates exactly as the native distribution does while reporting the alternative parameters as its own. The variate form and value support are carried as type parameters and taken from the family being wrapped, so a wrapper around a discrete family stays discrete.
See also
reparameterise: the public constructor.Reparameterised: the concrete wrapper.
Fields
sourceReparameterisedDistributions.Reparameterised Type
struct Reparameterised{D, names, N, T<:Real, F<:Distributions.VariateForm, S<:Distributions.ValueSupport} <: ReparameterisedDistributions.AbstractReparameterisedDistribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport}A native distribution family stood up under an alternative parameterisation.
Stores the alternative parameter values in the order of the registered names, and converts to the native family through to_native whenever a density, moment or sample is asked for. params reports the alternative values, not the native ones, so it is those that the ecosystem's parameter introspection sees and that a prior can be placed on.
The family D, the parameter names and the variate form and value support are type parameters, so a wrapper around a discrete family stays discrete and the conversion is resolved at compile time.
Fields
vals: the alternative parameter values, in registerednamesorder.
See also
reparameterise: the public constructor.
Fields
vals::NTuple{N, T} where {N, T<:Real}: The alternative parameter values, in registerednamesorder.
ReparameterisedDistributions.native Function
native(
d::ReparameterisedDistributions.Reparameterised{D, names}
) -> AnyThe native distribution a wrapper's alternative parameters convert to.
Every density, moment and sampling method on a Reparameterised goes through this, so it is also the way to reach the native parameters — the ones the wrapped family was actually built from — when the moments alone are not enough: params(native(d)) rather than params(d). Throws a DomainError if d's parameters are invalid — see valid_moments.
Examples
using ReparameterisedDistributions, Distributions
d = reparameterise(LogNormal; mean = 8.0, sd = 2.0)
native(d), params(native(d))See also
to_native: the per-family closed form this dispatches to.valid_moments: the per-family guard checked first.
ReparameterisedDistributions.reparameterise Function
reparameterise(
d::Type{D<:Distributions.Distribution};
kwargs...
)Wrap dist_or_type so that the keyword parameters given here are its parameters.
The keywords name an alternative parameterisation of the family, and the wrapper converts to the native family internally through an exact closed form. The result is a Distribution, so it evaluates and samples exactly as the native distribution does and can be used directly on the left of a ~ in a probabilistic model.
The point of the wrapper is that the alternative parameters remain the parameters: params reports them, and the ecosystem's parameter introspection places a prior on them, rather than on the native parameters that merely imply them. A prior on a delay's mean cannot be expressed through a native Gamma(shape, scale) leaf, because independent priors on shape and scale do not compose into a prior on the mean.
Pass either the family (LogNormal) or an instance of it, whose parameter values are ignored — only its family is taken. The keywords are order-insensitive, as keywords are everywhere else.
Arguments
dist_or_type: the native family to wrap, as a type or an instance.check_args: whether to reject invalid parameters at construction. Left on by default. A sampler exploring an unconstrained parameter turns it off: an invalid proposal then giveslogpdf == -Inf(andpdf == 0) rather than an exception raised in the middle of a gradient. Every other method still converts, so an invalid distribution has no mean, no quantile and no draw, and asking for one raises.alt_params: the alternative parameters, as keywords.
Note
params reports the moments, so the usual typeof(d)(params(d)...) idiom does not rebuild one of these — the family and the parameter names live in type parameters. Rebuild through reparameterise instead. Generic code relying on that idiom will raise rather than silently misbehave. For the native parameters — the ones the wrapped family was actually built from — use params(native(d)).
Examples
using ReparameterisedDistributions, Distributions
d = reparameterise(LogNormal; mean = 8.0, sd = 2.0)
params(d)using ReparameterisedDistributions, Distributions
mean(reparameterise(LogNormal; mean = 8.0, sd = 2.0))using ReparameterisedDistributions, Distributions
d = reparameterise(LogNormal; mean = 8.0, sd = 2.0)
params(native(d))See also
native: the native distribution a wrapper converts to.
ReparameterisedDistributions.rescale Function
rescale(
d::ReparameterisedDistributions.Reparameterised{D, names},
factor::Real;
parameter,
check_args
) -> ReparameterisedDistributions.ReparameterisedScale d's named parameter by factor, holding the others fixed.
Routes through whichever moment parameterisation d was itself built under — the registered names its type already carries — so scaling a mean (or any other registered parameter) has one home rather than a caller hand-rebuilding the wrapper from params(d). An affine transform is not a substitute: for a discrete family such as NegativeBinomial, scaling the native support does not scale the mean cleanly, so the scaling has to happen in moment coordinates and convert back through the family's own closed form.
parameter must be one of d's registered names, or this throws a DomainError rather than silently applying the factor under different semantics. d must itself be a Reparameterised distribution — a native, unwrapped Distribution has no registered parameterisation to route through and is rejected with an ArgumentError naming the family to wrap first.
Arguments
d: the distribution to rescale.factor: the multiplicative factor applied toparameter.parameter: the registered name to scale. Defaults to:mean.check_args: forwarded to the rebuilt wrapper; seereparameterise.
Examples
using ReparameterisedDistributions, Distributions
d = reparameterise(Gamma; mean = 8.0, shape = 2.0)
mean(rescale(d, 2.0))See also
reparameterise: the constructorrescalerebuilds through.
ReparameterisedDistributions.solve_moment Function
solve_moment(
_::Type{D},
_::Val{names},
residual,
deriv,
bracket,
vals
) -> AnySolve a family's own moment equation for a scalar s, exactly and differentiably, regardless of which solver backend runs the root-find.
A numeric family calls this inside its own to_native method, passing its own residual, deriv and bracket as ordinary functions — this registers no method on anything of ours, so a downstream package supplies its own three functions exactly as this package's own Weibull registration does (see src/families.jl).
residual(s, vals)is the moment equation, zero at the solution.deriv(s, vals)is its derivative with respect tos.bracket(pvals) -> (lo, hi)is a sign-changing interval forresidual, evaluated onvalsstripped to its primal type.
The root-find itself runs on the primal-stripped vals, then two steps of an implicit-function-theorem correction recover the exact derivative in the caller's own type afterwards: s is a root of residual, so subtracting residual / deriv leaves the VALUE unchanged to machine precision while making the DERIVATIVE exactly -residual_vals / deriv, whatever derivative the solver's own s arrived with (a garbage one, a zero one, or none at all). Two steps, not one: measured against the Weibull (mean, sd) equation, one correction step gives a correct gradient but a Hessian wrong by 2% on the diagonal and 21% off-diagonal.
Arguments
the native family being converted to.
Val(names): the alternative parameter names.residual,deriv,bracket: the family's own moment equation.vals: the alternative parameter values, innamesorder.
Examples
The to_native method that calls this must be registered under the parameter names sorted alphabetically, as reparameterise canonicalises its keywords that way before dispatching.
solve_moment is public but not exported, so import it by name.
using ReparameterisedDistributions, Distributions
using ReparameterisedDistributions: solve_moment
# A toy equation, exp(s) = 2, solved for illustration; a real family's
# residual is its own moment equation.
solve_moment(Gamma, Val((:shape,)), (s, vals) -> exp(s) - vals[1],
(s, vals) -> exp(s), pvals -> (-10.0, 10.0), (2.0,))See also
to_native: the conversion a numeric family calls this from.
ReparameterisedDistributions.test_reparameterisation Function
test_reparameterisation(::Type{D}, names, vals; kwargs...)Check a registered (family, parameter-name) pair against the whole registration contract.
Runs one @testset covering everything a registration has to get right: that it is registered at all and under the canonical (alphabetically sorted) parameter names; that valid_moments answers Bool and answers true at the point given; that to_native is inferred to a concrete distribution of the family and never admits nothing; that the wrapper builds, reports the given parameters as its own, keeps the family's variate form and value support, and evaluates and samples as the native distribution does; that any parameter named mean, sd or var comes back out of the built distribution; and that each invalid point given is rejected at construction and yields -Inf without it.
Written for a family registered from another package as much as for this one's own: it imports nothing private, so a downstream registration can be held to the same contract by the same code.
Test supplies this through a package extension, so a test suite reaches it with using Test. Calling it without Test loaded raises.
Arguments
the native family the registration converts to.
names: the alternative parameter names, as a tuple ofSymbols, in the order they are registered under.vals: valid alternative parameter values, innamesorder.invalid: alternative parameter tuples that must be rejected. Empty by default, but passing at least one is what checks that the family's guard actually guards.rtol: the relative tolerance themean/sd/varround-trip is held to. Loose enough by default for a numeric conversion.
Examples
using ReparameterisedDistributions, Distributions, Test
using ReparameterisedDistributions: test_reparameterisation
test_reparameterisation(Gamma, (:mean, :sd), (8.0, 3.0);
invalid = ((8.0, -1.0), (-8.0, 1.0)))See also
to_native: one of the two hooks this checks.valid_moments: the other.
ReparameterisedDistributions.to_native Function
to_native(
_::Type{D},
_::Val{names},
vals
) -> Distributions.GammaThe closed-form conversion from a family's alternative parameters to the native distribution.
Each supported (family, parameter-name) pair adds a method. A method should be exact algebra where a closed form exists, and otherwise call solve_moment. It must build the native distribution with check_args = false, so the conversion stays differentiable and a sampler probing an invalid point yields -Inf rather than throwing mid-gradient.
A method does NOT need to guard its own input: valid_moments is checked at every call site first, so this always runs on parameters already known valid and always returns a concrete D, never a Union of one and nothing. Keep it that way — do not add an early return nothing here. That split, not a stylistic preference, is why this stays two methods rather than one: an AD-hot call site that bound a Union{Nothing, D}-typed conversion result has produced a silently wrong reverse-mode gradient in this package before, and a call site here can only avoid that failure mode if every registered to_native method is unconditionally concrete.
Calling this directly, rather than through native or a wrapper's own methods, without first checking valid_moments is undefined: it may return a meaningless distribution or throw, never nothing.
Arguments
the native family being converted to.
Val(names): the alternative parameter names, as a value type so the conversion is resolved at compile time.vals: the alternative parameter values, innamesorder.
Examples
using ReparameterisedDistributions, Distributions
to_native(LogNormal, Val((:mean, :sd)), (8.0, 2.0))See also
reparameterise: the public constructor that dispatches to this.native: the wrapper-level accessor most callers want instead.valid_moments: the guard checked before this runs.
ReparameterisedDistributions.valid_moments Function
valid_moments(_::Type{D}, _::Val{names}, vals) -> AnyWhether a family's alternative parameters describe a member of the family, answered without converting or throwing.
Checked BEFORE to_native at every call site, hot path and construction alike — a family's own to_native method assumes its input has already passed this check, and is free to build whatever it builds without guarding, so it always returns a concrete distribution rather than a Union of one and nothing. Keeping that return type concrete is not cosmetic: an AD-hot call site that instead bound a Union{Nothing, <native type>}-typed conversion result has produced a silently wrong reverse-mode gradient in this package before, with no error and no warning; a family's own math never has to defend against that on its own so long as it registers a truthful predicate here.
nothing cannot be recovered from the native distribution's own type: some conversions are even in the invalid direction (the LogNormal and Gamma conversions square the standard deviation, so a negative one builds exactly the same, perfectly valid native distribution as its positive counterpart), so the check has to happen in the alternative parameters' own coordinates, in a method registered here, before to_native runs.
Each supported (family, parameter-name) pair adds a method alongside its to_native registration. The 3-arg fallback accepts anything, so a family that registers to_native without a matching valid_moments method is silently treated as always valid: on the check_args = false hot path nothing surfaces the omission, and logpdf/pdf/loglikelihood return a finite, wrong density instead of -Inf at an invalid point. Register both methods together.
Arguments
the native family being checked for.
Val(names): the alternative parameter names, as a value type so the check is resolved at compile time.vals: the alternative parameter values, innamesorder.
Returns
Bool. Must not throw, and must also exclude points to_native cannot represent even though the moment itself looks fine, such as a numeric family's solvable window.
Examples
using ReparameterisedDistributions, Distributions
valid_moments(LogNormal, Val((:mean, :sd)), (8.0, 2.0))using ReparameterisedDistributions, Distributions
valid_moments(LogNormal, Val((:mean, :sd)), (8.0, -1.0))See also
to_native: the conversion this guards, registered alongside it.