Skip to content

Getting started

Welcome to the ComposedDistributions documentation. This page is the quickstart. The home page is generated from the README, so it stays short; put the walkthrough a new user needs here and grow it into tutorials as the package develops.

These docs are generated

This site's layout, navigation, and infrastructure are produced by EpiAwarePackageTools. Editing the generated pages by hand is not needed; write your content in the package-owned source pages and let the scaffold render the rest. See Infrastructure and template sync for how the kit keeps this repository in sync.

Installation

julia
using Pkg
Pkg.add("ComposedDistributions")

Load the package:

julia
using ComposedDistributions

What ComposedDistributions does

ComposedDistributions composes per-event delay distributions into one object that describes a whole record. A composed object is a multi-state event process: named events linked by delays, which the composers wire into a tree. The same object scores observed records with logpdf and simulates new ones with rand, so a model is built once and used in both directions. It composes any Distributions.jl UnivariateDistribution, with no censoring, so it is the generic composition layer.

The building blocks are five composers. Sequential chains steps in series, Parallel fans branches off one shared origin, Resolve and Compete express one_of outcomes (a fixed-probability mixture and racing hazards), and Choose selects a branch from a data field. The compose front-end lowers a NamedTuple, a Tables.jl table, or a nested matrix to the same stack.

The package has four layers, each building on the one before.

The Concepts page maps each modelling concept to the verb that builds it.

A first example

Compose two delays off a shared onset, then simulate and score a record.

julia
using ComposedDistributions, Distributions, Random

tree = compose((onset_admit = Gamma(2.0, 1.0),
    admit_death = LogNormal(0.5, 0.4)))

record = rand(Xoshiro(1), tree)
(onset_admit = 1.5771947977125587, admit_death = 1.1939403667137392)

The composed object scores that record straight back.

julia
logpdf(tree, record)
-1.6269591820480291

Read its free parameters as a flat table, keyed by edge and parameter name.

julia
params_table(tree)
params_table (4 rows)
  edge         param  value  support     prior
  ───────────  ─────  ─────  ──────────  ─────
  onset_admit  shape  2.0    (0.0, Inf)       
  onset_admit  scale  1.0    (0.0, Inf)       
  admit_death  mu     0.5    (0.0, Inf)       
  admit_death  sigma  0.4    (0.0, Inf)

Uncertain distributions

A literature-reported delay rarely comes with exact parameters. Wrap the uncertainty inline with uncertain: parameters that are themselves distributions, nestable to any depth. The result is still a univariate distribution, so it composes as a leaf everywhere, and rand draws the marginal (a fresh parameter draw each call); the rest of the surface reports the template's central values until you pin concrete parameters with update (guard against a forgotten collapse with has_uncertain).

julia
u = uncertain(Gamma(2.0, 1.0); shape = LogNormal(log(2.0), 0.2))
utree = compose((onset_admit = u, admit_death = LogNormal(0.5, 0.4)))
has_uncertain(utree)
true

An uncertain leaf is one of two deferred leaves: a leaf that is not yet a concrete distribution but a map to one, resolved before scoring. It maps a latent parameter (a value a sampler draws, with the spec as its prior); its sibling Varying maps an observed covariate (time, stratum), resolved by instantiate against a Context. Both delegate silently to a fallback until resolved, and each has a guard — has_uncertain / has_varying — for a fitting loop to check. See the varying-distributions reference for the observed case.

Key features

  • Distributions.jl integration. A composed object is a Distribution, so logpdf, rand, mean, var and the rest of the interface work unchanged, and any Distributions.jl leaf composes with no package-specific hooks.

  • One structure, many front-ends. compose lowers a NamedTuple, a Tables.jl table, or a nested matrix to the same composer stack.

  • A readable, editable tree. params_table inventories the free parameters, build_priors derives priors from their support, and update / prune / splice reshape the tree.

  • Convolution built in. The package re-exports ConvolvedDistributions, so convolve_distributions, difference and the quadrature surface are reachable through ComposedDistributions alone.

  • Automatic differentiation. Scoring is differentiable through ForwardDiff, ReverseDiff, Mooncake and Enzyme, so a composed distribution drops into a probabilistic-programming fit.

Learning more