The leaf protocol
This page is the reference for the leaf protocol, the stable public contract a downstream leaf-wrapper package implements so its wrapped delays are transparent to composition. A leaf is a univariate distribution at the tip of a composed tree. Any Distributions.jl distribution is a valid leaf with no extra work, so a plain leaf needs none of these methods. A wrapper leaf, a type that carries fixed structure or extra parameters around an inner delay (censoring in CensoredDistributions, the modifiers in ModifiedDistributions), implements the methods below so the introspection and reconstruction layers see through the wrapper to the inner free delay.
The names are public but not exported, so a downstream package reaches them by the qualified name (ComposedDistributions.free_leaf and friends) and adds methods dispatching on its own wrapper type. Extending only free_leaf and rewrap_leaf is enough for a fixed-structure wrapper; a wrapper that attaches priors or owns extra parameters extends the rest so those reach params_table and build_priors. Every method here dispatches on an instance, and the flat-vector codec (flat_dimension, unflatten, flatten, reconstruct) reads the same instance-level hooks at call time, so there is no separate type-level table to keep in step and no further registration step.
The methods
The protocol splits into peel and rebuild, names, reconstruction, uncertainty, the shared tag, moments, and extra parameters.
| Method | Role |
|---|---|
free_leaf(leaf) | peel to the innermost free delay |
rewrap_leaf(leaf, inner) | rebuild the wrapper around a new inner delay |
component_names(node) | a node's child names |
param_names(leaf) | the inner delay's native parameter names |
leaf_param_names(leaf) | the estimable names, native then extra |
leaf_ctor(leaf) | the constructor that rebuilds the inner delay |
uncertain_specs(leaf) | attached priors, or nothing when fixed |
shared_tag(leaf) | the shared tie tag, or nothing |
leaf_mean(leaf), leaf_var(leaf) | per-leaf moments |
extra_leaf_params(leaf) | modifier-owned free parameters and supports |
set_extra_leaf_params(leaf, vals) | rebuild with new extra values |
leaf_detail_lines(leaf) | inspect rendering lines |
The peel and rebuild pair is the base of the protocol. free_leaf reaches the inner free delay whose parameters are the leaf's free parameters, and rewrap_leaf re-applies the fixed structure around a rebuilt delay. A plain leaf is its own free leaf and rewrap_leaf returns the new inner delay, so the identity holds without a method.
Names and reconstruction fix the coordinates the parameter table and the codec work in. param_names labels the native family parameters, leaf_param_names appends any extra names, and leaf_ctor rebuilds the inner delay from a positional tuple of native values.
Extra parameters
Most wrappers carry only fixed structure, so their extra-parameter map is empty and params_table shows just the inner delay's rows. A wrapper that owns a free parameter which is not one of the inner delay's native parameters reports it through extra_leaf_params, a NamedTuple mapping each extra name to a (value, support) pair. The thinning factor of thin(d, p) is the first instance, reported as a :thin entry with support (0.0, 1.0). The support drives the default prior, so a :thin factor picks up a Uniform(0, 1) default the same way a branch_probs row does. set_extra_leaf_params is the dual that rebuilds the leaf from new extra values by name.
A worked example with a plain leaf, where every peel is the identity and the extra map is empty.
using ComposedDistributions, Distributions
leaf = Gamma(2.0, 1.0)
(free = ComposedDistributions.free_leaf(leaf),
names = ComposedDistributions.leaf_param_names(leaf),
specs = ComposedDistributions.uncertain_specs(leaf),
tag = ComposedDistributions.shared_tag(leaf),
mean = ComposedDistributions.leaf_mean(leaf),
extras = ComposedDistributions.extra_leaf_params(leaf))(free = Distributions.Gamma{Float64}(α=2.0, θ=1.0), names = (:shape, :scale), specs = nothing, tag = nothing, mean = 2.0, extras = NamedTuple())A censored or truncated leaf peels its fixed structure to the inner delay, so the parameter table lists only the inner free parameters.
inner = ComposedDistributions.free_leaf(truncated(Gamma(2.0, 1.0); upper = 10.0))
rebuilt = ComposedDistributions.rewrap_leaf(
truncated(Gamma(2.0, 1.0); upper = 10.0), Gamma(3.0, 1.5))
(inner = inner, rebuilt = rebuilt)(inner = Distributions.Gamma{Float64}(α=2.0, θ=1.0), rebuilt = Truncated(Distributions.Gamma{Float64}(α=3.0, θ=1.5); upper=10.0))Adding a wrapper leaf
Implement
free_leafandrewrap_leafso the wrapper peels to and rebuilds around the inner free delay.Add
shared_taganduncertain_specsmethods that forward to the inner delay, so a tie or an attached prior under the wrapper still reaches the parameter table.Override
leaf_meanandleaf_varonly when the wrapper's transform changes the moment, as an affine scale and shift does.Implement
extra_leaf_paramsandset_extra_leaf_paramsonly when the wrapper owns a free parameter beyond the inner delay's native ones.Add a
leaf_detail_linesmethod when the wrapper's raw struct dump would clutter aninspecttree.
The docstrings for each method, with runnable examples, are in the public API reference. Steps 1-5 are all a leaf-wrapper package needs: params_table, build_priors and the generated flat-vector codec (flat_dimension/unflatten/flatten/reconstruct) all read the same instance-level hooks, so there is no separate codec-specific registration step.