Skip to content

Public Documentation

Documentation for ConvolvedDistributions's public interface.

Contents

Index

Public API

ConvolvedDistributions.AbstractConvolvedDistribution Type
julia
abstract type AbstractConvolvedDistribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport} <: Distributions.Distribution{F<:Distributions.VariateForm, S<:Distributions.ValueSupport}

Supertype of the distributions of X op Y for independent components — the generalised convolutions. Convolved is the classical sum, Difference the reflected form (Z = X - Y), and Product the Mellin form (Z = X * Y); further operations (order statistics) fit the same family.

Parametric on variate form and value support (Distribution{F, S}), so the univariate members keep their UnivariateDistribution{Continuous} supertype and existing dispatch is unchanged.

Required of a concrete subtype:

  • params(d);

  • logpdf(d, x) finite on its support;

  • Base.show(io, d).

Verify a subtype with ConvolvedDistributions.TestUtils.test_convolved_interface, and family membership with ConvolvedDistributions.TestUtils.test_abstract_membership.

See also


Fields

source
ConvolvedDistributions.AbstractSolverMethod Type
julia
abstract type AbstractSolverMethod

Abstract type for solver methods used in CDF/PDF computation.

Subtypes determine whether analytical solutions are preferred or numerical integration is forced.

See also


Fields

source
ConvolvedDistributions.AnalyticalSolver Type
julia
struct AnalyticalSolver{S} <: ConvolvedDistributions.AbstractSolverMethod

Solver that attempts analytical solutions when available, falling back to numerical integration.

Stores a numerical integration solver for use when no analytical solution exists for a given distribution pair.

See also


Fields

  • solver::Any: Fallback solver for when no analytical solution exists.
source
ConvolvedDistributions.Convolved Type
julia
struct Convolved{C<:Tuple, M<:ConvolvedDistributions.AbstractSolverMethod} <: ConvolvedDistributions.AbstractConvolvedDistribution{Distributions.Univariate, Distributions.Continuous}

Distribution of a sum of independent random variables (a convolution).

Convolved represents      where the are independent univariate distributions. It is the keystone primitive for multi-delay epidemiological models, where an observed delay is the sum of several independent stages (e.g. onset-to-death = onset-to-admission admission-to-death).

Components may have negative support (for example a Normal capturing pre-symptomatic transmission timing); minimum and maximum are the sums of the component supports, taking the value where a component is unbounded.

CDF computation

The CDF is computed by integrating one component out against the CDF of the others:

where is the last component (the integration variable) and is the convolution of the remaining components. For more than two components the remaining convolution is folded recursively.

Where an analytical convolution is available (Distributions.convolve applies, e.g. Normal+Normal, equal-scale Gamma, equal-rate Exponential) the two-component result is taken directly from the convolved distribution unless a NumericSolver method is set. All other cases use AD-safe fixed-node Gauss-Legendre quadrature: the integral is mapped from the fixed reference domain   onto the real bounds inside the integrand and reduced as a bare weighted dot product (gl_integrate), which lets every AD backend specialise on the integrand's own type so component Duals and tangents propagate.

The method field selects the CDF/PDF backend: an AnalyticalSolver (the default) uses the analytic convolution when one exists and falls back to quadrature otherwise, while a NumericSolver forces the numeric quadrature path even when an analytic convolution exists; the latter is useful for validation and debugging.

See also


Fields

  • components::Tuple: Tuple of independent component distributions to be summed.

  • method::ConvolvedDistributions.AbstractSolverMethod: Solver method choosing the analytic vs numeric quadrature backend.

source
ConvolvedDistributions.DelayPMF Type
julia
struct DelayPMF{V<:(AbstractVector), I<:Real}

A precomputed discretised delay PMF, built once and reused across many vector evaluations.

DelayPMF holds the raw CDF-difference interval masses of a delay distribution on the grid [0, 1), [1, 2), ..., [m, m + 1) scaled by interval (where m is the maxlag it was built with), clamped at zero against numeric-CDF noise but never renormalised, so a single discretisation is shared across many series or lag lookups instead of being rebuilt per call.

Build it with discretise_pmf; apply it with convolve_series (the causal series convolution, unit grid only) or read masses at integer lags with pdf(pmf, lag). The object is immutable and carries no mutable cache: the masses keep the delay's parameter type, so the build and every reuse differentiate cleanly, and a parameter change is handled by building a fresh object.

See also


Fields

  • masses::AbstractVector: The discretised interval masses over the grid 0..maxlag.

  • interval::Real: The grid width the masses were discretised on.

source
ConvolvedDistributions.Difference Type
julia
struct Difference{X<:(Distributions.UnivariateDistribution), Y<:(Distributions.UnivariateDistribution), M<:ConvolvedDistributions.AbstractSolverMethod} <: ConvolvedDistributions.AbstractConvolvedDistribution{Distributions.Univariate, Distributions.Continuous}

Distribution of a difference of two independent random variables.

Difference represents    where X and Y are independent univariate distributions. It is the dual of Convolved (the sum  ): a convolution of X with the reflection of Y. Where a convolution gathers two delays into one longer gap, a difference is the signed gap between two events, so it arises as a derived observation (for example the offset between two independently timed measurements) rather than as a delay leaf.

Because the subtraction reflects Y, the support of Z is in general two-sided and can be negative: it runs from   to  , taking the value where a component is unbounded. Z is therefore not a non-negative delay distribution; treat a Difference as an observation or derived quantity, not as a delay leaf.

Independence

The construction assumes X and Y are independent. The density, CDF, mean and variance below all rely on this; they are not correct for dependent components.

Density and CDF computation

The density is the cross-correlation of the two component densities:

and the CDF integrates X's CDF against Y's density:

For a Normal-Normal pair the closed form   is used directly unless a NumericSolver method is set. All other cases use AD-safe fixed-node Gauss-Legendre quadrature (gl_integrate), the same construction Convolved uses: the integral is mapped from the fixed reference domain   onto the integration bounds inside the integrand and reduced as a bare weighted dot product, so every AD backend specialises on the integrand's own type and component Duals and tangents propagate.

The method field selects the backend: an AnalyticalSolver (the default) uses the analytic difference where one exists and falls back to quadrature otherwise, while a NumericSolver forces the numeric path even for a Normal-Normal pair (useful for validation).

See also


Fields

  • x::Distributions.UnivariateDistribution: The minuend component (the X in Z = X - Y).

  • y::Distributions.UnivariateDistribution: The subtrahend component (the Y in Z = X - Y).

  • method::ConvolvedDistributions.AbstractSolverMethod: Solver method choosing the analytic vs numeric quadrature backend.

source
ConvolvedDistributions.GaussLegendre Type
julia
struct GaussLegendre{R}

Fixed-node Gauss-Legendre quadrature solver (the package default).

Integrates with a Gauss-Legendre rule of n nodes, evaluated as a bare weighted dot product (see gl_integrate). The constant control flow and the accumulator type being seeded from the integrand make this the AD-safe default: every supported AD backend can differentiate through it, unlike adaptive schemes whose node count depends on integrand values.

The reference nodes and weights are built once at construction and held on the solver, so the differentiated hot path is a pure weighted sum with no shared mutable state. This matters for trace-based reverse-mode backends (Enzyme, ReverseDiff): resolving the rule through a mutated global cache inside the integrand crashes Enzyme reverse, so the rule travels with the solver instead.

n = 64 is accurate to about 1e-13 on the smooth, density-weighted integrands used in this package. This is the core default and needs no heavy dependency. Load Integrals.jl and pass an Integrals.jl algorithm (e.g. QuadGKJL()) when adaptive accuracy is wanted and AD is not.

Examples

julia
using ConvolvedDistributions

# A 64-node rule; integrate x^2 over [0, 1] (exact value 1/3)
solver = ConvolvedDistributions.GaussLegendre(; n = 64)
approx = ConvolvedDistributions.integrate(solver, x -> x^2, 0.0, 1.0)

See also

  • gl_integrate: The underlying quadrature reduction.

  • integrate: The pluggable entry point dispatching on a solver.


Fields

  • n::Int64: Number of Gauss-Legendre nodes.

  • rule::Any: The reference Gauss-Legendre rule (nodes/weights on [-1, 1]).

source
ConvolvedDistributions.NumericSolver Type
julia
struct NumericSolver{S} <: ConvolvedDistributions.AbstractSolverMethod

Solver that always uses numerical integration.

Forces numerical computation even when analytical solutions are available, useful for testing and validation.

The solver field contains the numerical integration solver to use.

See also


Fields

  • solver::Any: Numerical integration solver to use.
source
ConvolvedDistributions.Product Type
julia
struct Product{X<:(Distributions.UnivariateDistribution), Y<:(Distributions.UnivariateDistribution), M<:ConvolvedDistributions.AbstractSolverMethod} <: ConvolvedDistributions.AbstractConvolvedDistribution{Distributions.Univariate, Distributions.Continuous}

Distribution of a product of two independent random variables.

Product represents   where X and Y are independent univariate distributions with non-negative support. It is the multiplicative member of the family: where Convolved gathers two delays into one longer gap and Difference takes the signed gap between two events, a product scales one variable by another, as when a delay is stretched by an independent multiplicative factor.

Both components must satisfy minimum(d) >= 0; the constructor throws an ArgumentError otherwise. Sign-crossing supports are future work (they split the Mellin quadrature into positive and negative branches). The support of Z runs from to , taking the value where a component is unbounded.

Independence

The construction assumes X and Y are independent. The density, CDF, mean and variance below all rely on this; they are not correct for dependent components.

Density and CDF computation

The density is the Mellin convolution of the two component densities:

and the CDF integrates X's CDF against Y's density:

evaluated numerically in the equivalent survival form    so the integrand stays bounded when Y's density diverges at zero (Gamma or Weibull shape below one).

For a LogNormal-LogNormal pair the closed form   is used directly unless a NumericSolver method is set. All other cases use AD-safe fixed-node Gauss-Legendre quadrature (gl_integrate), the same construction Convolved and Difference use: the integral is mapped from the fixed reference domain   onto the integration bounds inside the integrand and reduced as a bare weighted dot product, so every AD backend specialises on the integrand's own type and component Duals and tangents propagate.

The method field selects the backend: an AnalyticalSolver (the default) uses the analytic product where one exists and falls back to quadrature otherwise, while a NumericSolver forces the numeric path even for a LogNormal-LogNormal pair (useful for validation).

See also


Fields

  • x::Distributions.UnivariateDistribution: The multiplicand component (the X in Z = X * Y).

  • y::Distributions.UnivariateDistribution: The multiplier component (the Y in Z = X * Y).

  • method::ConvolvedDistributions.AbstractSolverMethod: Solver method choosing the analytic vs numeric quadrature backend.

source
ConvolvedDistributions.convolve_series Function
julia
convolve_series(
    delay::Distributions.Distribution{Distributions.Univariate, Distributions.Discrete},
    series::AbstractVector{<:Real}
) -> Any

Convolve a timeseries with the PMF of a discrete delay distribution.

convolve_series(delay, series) for a DiscreteUnivariateDistribution delay reads the delay PMF directly off the integer lag grid — the lag-k mass IS pdf(delay, k) — and returns the causal discrete convolution of series with that PMF, truncated to the series window. With series the expected events at times 0, 1, ..., t (e.g. infections), the result is the expected downstream event counts at the same times (the EpiNow2-style latent / renewal observation layer).

The masses are [pdf(delay, k) for k in 0:(length(series) - 1)], used as given: no renormalisation, so any delay mass beyond the series window is truncated. pdf(delay, k) is differentiable in the delay parameters for the standard discrete families, so gradients flow under the supported AD backends.

Direct PMF evaluation, NOT a CDF difference: for an integer-support delay,         , an off-by-one, so the discrete method reads pdf(delay, k) and never routes through discretise_pmf's CDF-difference masses.

Only the integer lags 0, 1, 2, ... are read. A delay with atoms off the integer grid is out of scope (a lag grid means masses at the integers), and mass at negative lags cannot enter a causal convolution, so lags below 0 are not read (consistent with the causal kernel).

For a CONTINUOUS delay there is no mass on the integer grid until it is discretised, and discretisation is an explicit modelling choice; that method throws — see below.

Unlike convolved, which combines distributions into a single Convolved distribution, this returns a numeric series; the separate verb keeps convolved strictly for distribution construction.

Arguments

  • delay: a DiscreteUnivariateDistribution (e.g. Poisson, DiscreteUniform, a shifted count delay).

  • series: the input timeseries (expected events at unit-spaced times from 0).

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
using ConvolvedDistributions, Distributions

delay = Poisson(2.0)
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series(delay, infections)

See also

source
julia
convolve_series(
    delay::Distributions.Distribution{Distributions.Univariate, Distributions.Continuous},
    series::AbstractVector{<:Real}
)

Reject a continuous delay: discretising it needs an explicit censoring scheme.

convolve_series(delay, series) for a ContinuousUnivariateDistribution (including a Convolved total delay) throws an ArgumentError. A continuous delay carries no mass on the integer lag grid until it is discretised, and there is more than one right way to do that: the naive CDF-difference scheme is interval-censored on the secondary event with an EXACT primary, whereas the usual epidemiological case (a primary event known only to the day) needs double interval censoring. Rather than pick one silently, the caller discretises first and passes the resulting PMF to convolve_series(pmf, series):

  • discretise_pmf(delay, maxlag) for interval-censored- secondary masses (exact primary), or

  • CensoredDistributions.jl's double-interval-censored extension for day-binned primaries.

See also

source
julia
convolve_series(
    pmf::AbstractVector{<:Real},
    series::AbstractVector{<:Real}
) -> Any

Convolve a timeseries with a caller-supplied discretised delay PMF.

convolve_series(pmf, series) returns the causal discrete convolution of series with the probability masses pmf, truncated to the series window: out[i] = sum(pmf[k + 1] * series[i - k] for k in 0:(min(length(pmf), i) - 1)). pmf[k + 1] is read as the delay mass at integer lag k on the same unit grid as series.

The masses are used exactly as given: no renormalisation, no validation that they sum to one, and no tail correction — mass at lags beyond the series window (including any pmf entries past length(series)) is simply never used, so sub-normalised or window-truncated PMFs stay truncated. This is the decoupled form of convolve_series(delay, series): the caller owns the discretisation (e.g. double-interval-censored masses from CensoredDistributions.jl), and this method only convolves. The convolution is linear, so gradients flow through both pmf and series under the supported AD backends.

Arguments

  • pmf: the discretised delay probability masses at integer lags 0, 1, 2, ... (used as given).

  • series: the input timeseries (expected events at unit-spaced times from 0).

Returns

  • A numeric vector of expected downstream counts, the same length as series.

Examples

julia
using ConvolvedDistributions

pmf = [0.5, 0.3, 0.2]
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series(pmf, infections)

See also

source
julia
convolve_series(
    pmf::ConvolvedDistributions.DelayPMF,
    series::AbstractVector{<:Real}
) -> Any

Convolve a timeseries with a precomputed DelayPMF, reusing the build-once masses.

convolve_series(pmf, series) is the causal, window-truncated convolution of series with a PMF discretised once via discretise_pmf instead of rebuilt per call, so the result is numerically identical to discretising and convolving in one step. The series is interpreted on the PMF's own grid: entry i of series is the value at time (i - 1) * pmf.interval, and lag k shifts by k grid steps of that width. The grid width therefore comes from the discretisation the caller chose — a weekly-binned PMF convolves a weekly series — and no separate step argument exists to conflict with it.

Arguments

  • pmf: a precomputed DelayPMF; its interval is the grid the series is read on.

  • series: the input timeseries, sampled at steps of pmf.interval from time 0.

Examples

julia
using ConvolvedDistributions, Distributions

pmf = discretise_pmf(Gamma(2.0, 1.0), 6)
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
expected_counts = convolve_series(pmf, infections)

See also

source
ConvolvedDistributions.convolved Function
julia
convolved(
    components::AbstractVector{<:Distributions.UnivariateDistribution};
    method
) -> ConvolvedDistributions.Convolved{C, AnalyticalSolver{ConvolvedDistributions.GaussLegendre{ConvolvedDistributions._GL{Vector{Float64}, Vector{Float64}}}}} where C<:Tuple

Create the distribution of a sum of independent delays (a convolution).

Accepts either two or more positional component distributions, or a single vector/tuple of components. Returns a Convolved distribution.

Arguments

  • components: Two or more UnivariateDistributions, or a vector/tuple of them.

Keyword Arguments

  • method: The solver method, an AnalyticalSolver (the default) or NumericSolver. NumericSolver forces numeric quadrature even when an analytic convolution is available.

Returns

  • A Convolved distribution of the sum of the components.

Examples

julia
using ConvolvedDistributions, Distributions

# Sum of two delays
d = convolved(Gamma(2.0, 1.0), LogNormal(1.5, 0.5))
cdf_at_5 = cdf(d, 5.0)

# Sum of three delays from a vector
d3 = convolved([Gamma(2.0, 1.0), Gamma(1.0, 1.0), Normal(0.0, 1.0)])
mean_sample = rand(d3)

# Force numeric quadrature even for an analytic pair
dn = convolved(Normal(0.0, 1.0), Normal(1.0, 2.0);
    method = NumericSolver())
cdf_numeric = cdf(dn, 2.0)

See also

source
ConvolvedDistributions.difference Function
julia
difference(
    x::Distributions.UnivariateDistribution,
    y::Distributions.UnivariateDistribution;
    method
) -> Difference{X, Y, AnalyticalSolver{ConvolvedDistributions.GaussLegendre{ConvolvedDistributions._GL{Vector{Float64}, Vector{Float64}}}}} where {X<:(Distributions.UnivariateDistribution), Y<:(Distributions.UnivariateDistribution)}

Create the distribution of a difference of two independent variables.

Returns a Difference representing   . This is the dual of convolved: a convolution forms the sum of two delays, a difference the signed gap between two events. The support of Z can be negative, so Z is an observation or derived quantity rather than a non-negative delay leaf (see Difference).

X and Y are assumed independent.

Arguments

  • x: The minuend distribution (the X in Z = X - Y), a UnivariateDistribution.

  • y: The subtrahend distribution (the Y in Z = X - Y), a UnivariateDistribution.

Keyword Arguments

  • method: The solver method, an AnalyticalSolver (the default) or NumericSolver. NumericSolver forces numeric quadrature even for a Normal-Normal pair, mirroring convolved.

Returns

  • A Difference distribution of the signed gap Z = X - Y.

Examples

julia
using ConvolvedDistributions, Distributions

# Difference of two delays; mean is the difference of the means (≈ 3)
d = difference(Normal(5.0, 1.0), Normal(2.0, 1.0))
mean(d)

See also

source
ConvolvedDistributions.discretise_pmf Function
julia
discretise_pmf(
    delay::Distributions.UnivariateDistribution,
    maxlag::Integer;
    interval
) -> ConvolvedDistributions.DelayPMF{V, Float64} where V<:(AbstractVector)

Discretise a delay distribution to a DelayPMF once, for reuse across many series or lag lookups.

discretise_pmf(delay, maxlag; interval = 1.0) computes the raw CDF-difference interval masses of delay on the grid [0, 1), ..., [maxlag, maxlag + 1) (scaled by interval) and returns a precomputed DelayPMF to pass into convolve_series or pdf(pmf, lag). Building once and reusing avoids rediscretising the delay per series or per record.

The masses F(k + 1) - F(k) are the interval-censored-secondary scheme with an EXACT primary event: the probability the delay lands in day k given the primary happened at a precise instant. This is an explicit modelling choice, not a neutral default. When the PRIMARY is also known only to the day — the usual epidemiological case — the day-binned masses need double interval censoring, which is CensoredDistributions.jl's job (build those masses there and pass them to convolve_series(pmf, series)). Reach for discretise_pmf only when the exact-primary assumption is the one you want.

The masses are clamped at zero against numeric-CDF noise but never renormalised, so delay mass beyond maxlag stays truncated. They keep the delay's parameter type, so the discretisation differentiates w.r.t. the delay parameters; a parameter change is handled by calling discretise_pmf again (there is no stale cache).

Arguments

  • delay: the delay distribution to discretise (any UnivariateDistribution, including a Convolved total delay).

  • maxlag: the largest integer lag to carry a mass for; the PMF has maxlag + 1 entries.

Keyword Arguments

  • interval: the discretisation grid width (default 1.0). The width travels with the DelayPMF: convolve_series reads the series on the PMF's own grid, and pdf(pmf, k) is the mass on [k * interval, (k + 1) * interval).

Returns

  • A DelayPMF holding the maxlag + 1 interval masses and the grid width.

Examples

julia
using ConvolvedDistributions, Distributions

delay = convolved(Gamma(2.0, 1.0), LogNormal(0.5, 0.4))
# Build the delay PMF once for a 30-step window.
pmf = discretise_pmf(delay, 30)

# Reuse it across many series without rediscretising.
infections = [0.0, 1.0, 3.0, 6.0, 8.0, 5.0, 2.0]
counts = convolve_series(pmf, infections)

See also

source
ConvolvedDistributions.gl_integrate Function
julia
gl_integrate(f, lo, hi) -> Any
gl_integrate(
    f,
    lo,
    hi,
    rule::ConvolvedDistributions._GL
) -> Any

Integrate a scalar function f over [lo, hi] by fixed-node Gauss-Legendre quadrature.

The reference domain [-1, 1] is mapped onto [lo, hi] inside the integrand and the result reduced as a weighted dot product, so the accumulator's element type is taken from the integrand and AD Duals and tangents propagate. Returns a typed zero when hi <= lo.

Arguments

  • f: The scalar integrand.

  • lo: Lower integration bound.

  • hi: Upper integration bound.

  • rule: Gauss-Legendre rule to use (default: the 192-node convolution rule).

Returns

  • The quadrature approximation of the integral, typed by the integrand (a typed zero when hi <= lo).

Examples

julia
using ConvolvedDistributions

# Integrate x^2 over [0, 1] (exact value 1/3)
approx = ConvolvedDistributions.gl_integrate(x -> x^2, 0.0, 1.0)

See also

source
ConvolvedDistributions.integrate Function
julia
integrate(
    solver::ConvolvedDistributions.GaussLegendre,
    f,
    lower,
    upper
) -> Any

Integrate a scalar function f over [lower, upper] using solver.

This is the pluggable integration entry point: every integral in the package calls integrate, dispatching on the solver type. The default GaussLegendre solver routes to gl_integrate. Loading the optional Integrals.jl extension adds a method for Integrals.jl algorithms (e.g. QuadGKJL()), which builds an IntegralProblem and calls solve.

Arguments

  • solver: The integration backend. GaussLegendre by default; any Integrals.jl algorithm when that extension is loaded.

  • f: The scalar integrand.

  • lower: Lower integration bound.

  • upper: Upper integration bound.

Returns

  • The integral approximation computed by the chosen backend, typed by the integrand.

Examples

julia
using ConvolvedDistributions

solver = ConvolvedDistributions.GaussLegendre(; n = 64)
approx = ConvolvedDistributions.integrate(solver, x -> x^2, 0.0, 1.0)

See also

source
ConvolvedDistributions.product Function
julia
product(
    x::Distributions.UnivariateDistribution,
    y::Distributions.UnivariateDistribution;
    method
) -> ConvolvedDistributions.Product{X, Y, AnalyticalSolver{ConvolvedDistributions.GaussLegendre{ConvolvedDistributions._GL{Vector{Float64}, Vector{Float64}}}}} where {X<:(Distributions.UnivariateDistribution), Y<:(Distributions.UnivariateDistribution)}

Create the distribution of a product of two independent variables.

Returns a Product representing  , the multiplicative member of the family (the Mellin convolution): where convolved sums two delays and difference takes their signed gap, a product scales one variable by an independent multiplicative factor. Both components must have non-negative support (minimum(d) >= 0); sign-crossing supports throw an ArgumentError and are future work.

X and Y are assumed independent.

Arguments

  • x: The multiplicand distribution (the X in Z = X * Y), a UnivariateDistribution with non-negative support.

  • y: The multiplier distribution (the Y in Z = X * Y), a UnivariateDistribution with non-negative support.

Keyword Arguments

  • method: The solver method, an AnalyticalSolver (the default) or NumericSolver. NumericSolver forces numeric quadrature even for a LogNormal-LogNormal pair, mirroring convolved.

Returns

  • A Product distribution of the product Z = X * Y.

Examples

julia
using ConvolvedDistributions, Distributions

# A delay stretched by an independent multiplicative factor;
# the mean is the product of the means (≈ 3.3).
d = product(Gamma(3.0, 1.0), LogNormal(0.0, 0.3))
mean(d)

See also

source