ConvolvedDistributions
Raw-distribution convolution and shared numeric quadrature for any Distributions.jl distribution.
Why ConvolvedDistributions?
Convolution of any pair:
convolvedbuilds the distribution of a sum of independent delays (a convolution) from any two or moreDistributions.jldistributions, not just pairs with a closed form.Analytic fast path: closed-form convolutions (
Normal+Normal, equal-scaleGamma, equal-rateExponential) are used where they exist, with an AD-safe Gauss-Legendre quadrature fallback for every other pair.Differences as well as sums:
differencebuilds theX - Ydual, the signed gap between two independent events.Timeseries convolution:
convolve_series(delay, series)convolves a numeric series (e.g. expected infections over time) with the discretised delay PMF to give expected downstream counts, the renewal-style observation layer. Gradients flow through the delay parameters.Pluggable integration: a shared
integrate/gl_integratelayer with a lightweight fixed-nodeGaussLegendredefault and an optional Integrals.jl backend.Gradients everywhere: gradients flow through the component parameters on every supported AD backend (ForwardDiff, ReverseDiff, Enzyme, Mooncake), so convolved distributions can be fitted with gradient-based samplers and optimisers.
Getting started
See the Getting started documentation for a full walkthrough.
The following example convolves two delays, an incubation period and a reporting delay, and evaluates the resulting distribution:
using ConvolvedDistributions, Distributions
# Sum of two independent delays
incubation = Gamma(2.0, 1.0)
reporting = LogNormal(1.0, 0.5)
d = convolved(incubation, reporting)
(cdf(d, 5.0), pdf(d, 5.0))(0.5555707883725701, 0.1887715075992186)difference gives the signed gap between two independent events, for example the delay between two reporting streams:
z = difference(Normal(5.0, 1.0), Normal(2.0, 1.0))
(mean(z), cdf(z, 0.0))(3.0, 0.016947426762344637)A Convolved distribution is a UnivariateDistribution, so it composes with Distributions.truncated. Right truncation is useful when scoring against data observed only up to a cutoff:
d_trunc = truncated(d; upper = 10.0)
cdf(d_trunc, 5.0)0.5725523804006694Loading the Optimization extension adds quantile support by numerically inverting the CDF:
using Optimization, OptimizationOptimJL
quantile(d, 0.5)4.713624818230322The components and their sum can be compared visually, here with AlgebraOfGraphics.jl:
using CairoMakie, AlgebraOfGraphics, DataFramesMeta
CairoMakie.activate!(type = "png", px_per_unit = 2)
x = 0.0:0.1:15.0
df = vcat(
DataFrame(x = x, density = pdf.(incubation, x),
Distribution = "Incubation (Gamma)"),
DataFrame(x = x, density = pdf.(reporting, x),
Distribution = "Reporting (LogNormal)"),
DataFrame(x = x, density = pdf(d, collect(x)),
Distribution = "Convolved sum")
)
draw(
data(df) *
mapping(:x, :density, color = :Distribution) *
visual(Lines, linewidth = 2)
)
Relationship to Distributions.jl
Distributions.jl ships a convolve function, but it only covers pairs with a closed-form result:
| Aspect | Distributions.jl convolve | ConvolvedDistributions.jl convolved |
|---|---|---|
| Coverage | Closed-form, same-family pairs only (e.g. Normal + Normal, equal-scale Gamma); errors otherwise | Any pair of univariate distributions |
| Method | Returns the closed-form distribution | Analytic fast path where a closed form exists, AD-safe Gauss-Legendre quadrature fallback otherwise |
| Forms | Two positional arguments | Nested, vector, tuple, and varargs forms for sums of many delays |
| Differences | Not supported | difference builds the X - Y dual |
| Evaluation | Whatever the returned distribution supports | Batched cdf / pdf / logpdf over vectors of points |
| Gradients | Depend on the returned distribution | Flow through the component parameters on all supported AD backends |
For example, Distributions.convolve(Gamma(2, 1), LogNormal(0, 1)) throws a MethodError and Distributions.convolve(Gamma(2, 1), Gamma(3, 2)) throws an ArgumentError because the scales differ, whereas convolved handles both via quadrature. When a closed form does exist, convolved uses it, so there is no cost to reaching for the more general function.
What packages work well with ConvolvedDistributions.jl?
Distributions.jl provides the base functionality for working with distributions; every component and every result is a
UnivariateDistribution.CensoredDistributions.jl adds censoring and truncation layers for epidemiological observation processes; this package was split out of it and the two compose.
Turing.jl for Bayesian inference; the
cdf/pdf/logpdfmethods are AD-safe, so convolved distributions can be fitted with its samplers.Integrals.jl as an optional quadrature backend via the package extension.
Where to learn more
Want to get started running code? Check out the Getting started documentation.
Want to understand the API? Check out our API reference.
Want to chat with someone about
ConvolvedDistributions? Post on our GitHub Discussions.Want to contribute to
ConvolvedDistributions? Check the open issues and the Contributing section below.Want to see our code? Check out our GitHub Repository.
Contributing
We welcome contributions and new contributors! This package follows ColPrac and the SciML style.
Supporting and citing
If you would like to support ConvolvedDistributions, please star the repository — such metrics help secure future funding.
If you use ConvolvedDistributions in your work, please cite it:
@software{ConvolvedDistributions_jl,
author = {Sam Abbott and EpiAware contributors},
title = {ConvolvedDistributions.jl},
year = {2026},
url = {https://github.com/EpiAware/ConvolvedDistributions.jl}
}A citable DOI will be added with the first tagged release.
Code of conduct
Please note that the ConvolvedDistributions project is released with a Contributor Code of Conduct. By contributing, you agree to abide by its terms.