| Documentation | Build Status | Code Quality | License & DOI | Downloads |
|---|---|---|---|---|
| ForwardDiff | ReverseDiff (tape) | Enzyme forward | Enzyme reverse | Mooncake reverse | Mooncake forward |
|---|---|---|---|---|---|
A verb grammar for n-ary composition over any Distributions.jl distribution.
- A natural history is usually a hand-rolled convolution or simulation loop per project; ComposedDistributions gives it one small vocabulary — chains, branches, and one_of outcomes — so the model is built by naming its structure, not by re-deriving the maths.
- The same composed object scores an observed record and simulates a new one, so a model built once serves calibration and forward simulation alike.
- Parameter uncertainty is an ordinary leaf, not a separate PPL-specific layer, so a delay's literature uncertainty is written once and reused whether it is sampled or just drawn from.
- Every leaf is an ordinary Distributions.jl distribution, so nothing needs reimplementing to compose it, and the composed result is itself a distribution that drops into code expecting one.
- A composed tree is inspectable, editable data — a parameter table, a nested prior, a rendered tree — rather than an opaque function, so its structure and priors can be read and changed without rereading the model code.
- Convolution, quadrature and automatic differentiation across four backends come with the package, so a composed delay is fit-ready with no extra plumbing.
See documentation for a full walkthrough.
A hospital pathway: an admission delay with literature uncertainty, then a death-versus-discharge split where the death probability is the case-fatality ratio, alongside a reporting delay truncated at a 21-day cutoff (reports arriving later are excluded) and a referral delay censored at 14 days (a referral still pending at day 14 is recorded as arriving then) — both plain Distributions.jl wrappers used here as ordinary leaves.
using ComposedDistributions, Distributions, Random
cfr = 0.12 # case-fatality ratio among admitted cases
admission = @uncertain compose((
path = sequential(
:onset_admit => LogNormal(Normal(0.0, 0.2), 0.4),
:admit_outcome => resolve(:death => (Gamma(1.5, 1.0), cfr),
:discharge => Gamma(2.0, 1.5))),
onset_report = truncated(Gamma(1.5, 1.0); upper = 21.0),
onset_referral = censored(Gamma(1.0, 2.0); upper = 14.0)))admission prints as the tree it is: three branches off the onset, the
admission branch itself a two-step chain ending in the death/discharge split,
and the reporting and referral branches keeping their truncated() and
censored() wrappers visible in the printed tree.
admissionThe same object simulates a structured record and scores one straight back.
record = rand(Xoshiro(1), admission)logpdf(admission, record)The getting started guide carries this same tree further: reading its parameter table, editing a node, collapsing a chain to its observed total, and fitting it.
ComposedDistributions builds on Distributions.jl rather than replacing it.
Every leaf is a Distributions.jl UnivariateDistribution, and a composed object is itself a Distribution, so logpdf, rand, mean, var and the rest of the interface work unchanged.
| Aspect | Distributions.jl | ComposedDistributions |
|---|---|---|
| Scope | one distribution | many delays wired into an event tree |
| Question | "what is this delay?" | "how do these events relate?" |
| Builds on | — | any Distributions.jl UnivariateDistribution as a leaf |
| Adds | — | compose, the five composers, a parameter table and structural edits |
Standard Distributions.jl wrappers slot in as leaves inside a tree: both truncated() and censored() work as ordinary leaves, as onset_report and onset_referral show above.
Wrapping a whole composed tree in truncated()/censored() is a different question: an event tree does not have a single scalar support to truncate or censor, so that path raises a clear error rather than silently doing the wrong thing.
- ModifiedDistributions.jl rescales, shifts, weights and reshapes the hazard of any distribution (
affine,weight,modify); its modifiers apply across composed chains as well as plain leaves. - LoweredDistributions.jl turns a distribution into a backend-agnostic dynamical-systems representation (
lower), the bridge to compartmental models; it lowers a composed tree as well as a single leaf. - CensoredDistributions.jl adds primary-event, interval and double-interval censoring wrappers for delay distributions, for observation processes with imperfect timing data.
- ConvolvedDistributions.jl builds convolutions, differences and products of independent delays; ComposedDistributions re-exports it directly, so a chain collapses to its convolved total with
observed_distribution. - DistributionsInference.jl is the fit-protocol and PPL-integration layer (Turing.jl, DynamicPPL, Bijectors); its extension here reads a composed tree's generated codec directly, so an uncertain leaf (including pooled and shared parameters) is fittable with no extra glue.
- Want to get started running code? See the getting started guide.
- Want the right verb by intent? See the Concepts page.
- Want to understand the API? See the API reference.
- Want to see the code? Check out our GitHub repository.
For usage questions, ask on the Julia Discourse (the SciML or usage categories) or the epinowcast community forum, our home for epidemiological modelling questions. Please use GitHub issues for bug reports and feature requests only.
We welcome contributions and new contributors! This package follows ColPrac and the SciML style.
If you would like to support ComposedDistributions, please star the repository — such metrics help secure future funding.
If you use ComposedDistributions in your work, please cite it:
@software{ComposedDistributions_jl,
author = {Sam Abbott and EpiAware contributors},
title = {ComposedDistributions.jl},
year = {2026},
doi = {10.5281/zenodo.XXXXXXX}, # replace once released
url = {https://github.com/EpiAware/ComposedDistributions.jl}
}Please note that the ComposedDistributions project is released with a Contributor Code of Conduct. By contributing, you agree to abide by its terms.