Skip to content

ConvolvedDistributions

Raw-distribution convolution and shared numeric quadrature for any Distributions.jl distribution.

Why ConvolvedDistributions?

  • Convolution of any pair: convolved builds the distribution of a sum of independent delays (a convolution) from any two or more Distributions.jl distributions, not just pairs with a closed form.

  • Analytic fast path: closed-form convolutions (Normal + Normal, equal-scale Gamma, equal-rate Exponential) are used where they exist, with an AD-safe Gauss-Legendre quadrature fallback for every other pair.

  • Differences as well as sums: difference builds the X - Y dual, 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_integrate layer with a lightweight fixed-node GaussLegendre default 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:

julia
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:

julia
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:

julia
d_trunc = truncated(d; upper = 10.0)

cdf(d_trunc, 5.0)
0.5725523804006694

Loading the Optimization extension adds quantile support by numerically inverting the CDF:

julia
using Optimization, OptimizationOptimJL

quantile(d, 0.5)
4.713624818230322

The components and their sum can be compared visually, here with AlgebraOfGraphics.jl:

julia
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:

AspectDistributions.jl convolveConvolvedDistributions.jl convolved
CoverageClosed-form, same-family pairs only (e.g. Normal + Normal, equal-scale Gamma); errors otherwiseAny pair of univariate distributions
MethodReturns the closed-form distributionAnalytic fast path where a closed form exists, AD-safe Gauss-Legendre quadrature fallback otherwise
FormsTwo positional argumentsNested, vector, tuple, and varargs forms for sums of many delays
DifferencesNot supporteddifference builds the X - Y dual
EvaluationWhatever the returned distribution supportsBatched cdf / pdf / logpdf over vectors of points
GradientsDepend on the returned distributionFlow 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 / logpdf methods 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

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:

bibtex
@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.