Skip to content

EpiAware/ComposedDistributions.jl

Repository files navigation

ComposedDistributions ComposedDistributions logo

Documentation Build Status Code Quality License & DOI Downloads
Stable Dev Test codecov AD SciML Code Style Aqua QA JET License: MIT Downloads Downloads
ForwardDiff ReverseDiff (tape) Enzyme forward Enzyme reverse Mooncake reverse Mooncake forward
cov ForwardDiff cov ReverseDiff cov Enzyme forward cov Enzyme reverse cov Mooncake reverse cov Mooncake forward

A verb grammar for n-ary composition over any Distributions.jl distribution.

Why ComposedDistributions?

  • 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.

Getting started

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.

admission

The 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.

Relationship to Distributions.jl

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.

Related packages

  • 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.

Where to learn more

Getting help

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.

Contributing

We welcome contributions and new contributors! This package follows ColPrac and the SciML style.

Supporting and citing

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}
}

Code of conduct

Please note that the ComposedDistributions project is released with a Contributor Code of Conduct. By contributing, you agree to abide by its terms.

About

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages