ProbabilityMeasures.jl provides normalized probability measures designed for use in probabilistic programs. Its implementations are type-generic, allocation-free in core density and sampling operations, and compatible with automatic differentiation, broadcasting on GPU arrays, and Reactant tracing.
The package is experimental. At present it implements the univariate normal, log-normal, exponential, uniform, Laplace, and categorical measures, and the multivariate normal.
Install it directly from GitHub:
using Pkg
Pkg.add(url="https://github.com/rsenne/ProbabilityMeasures.jl")
using ProbabilityMeasures
d = Normal(1.0, 2.0)
logdensityof(d, 0.5)
densityof(d, 0.5)
cdf(d, 0.5)
quantile(d, 0.95)
mean(d)
var(d)
rand(d)
Constructors preserve parameter types and do not validate:
julia> typeof(Normal(0.0f0, 1))
Normal{Float32, Int64}
julia> d = Normal(0.0, -1.0);
julia> checkparams(d)
false
julia> logdensityof(d, 0.0)
NaN
Use checkparams when accepting user-supplied parameters. Density evaluation itself
is total: invalid parameters and unsupported values return non-finite results rather
than throwing.
Normal(μ, σ), LogNormal(μ, σ), Exponential(θ), Uniform(a, b),
Laplace(μ, b), and Categorical(p) each support:
densityof and logdensityofcdf, ccdf, logcdf, and logccdfquantile, mean, median, var, std, and entropyrand and Random’s array-sampling methodsparams, support, insupport, and checkparamsCategorical(p) assigns the probabilities in p to categories 1:length(p). Draws
and quantiles use the promoted floating-point type of p:
julia> d = Categorical([0.2, 0.3, 0.5]);
julia> quantile(d, 0.5)
2.0
julia> rand(d) isa Float64
true
julia> logdensityof(d, 2.0), logdensityof(d, 2.5)
(-1.2039728043259361, -Inf)
MvNormal(μ, L) takes a lower-triangular covariance factor, so cov(d) == L * L'.
If you have a covariance matrix, factor it before constructing the measure.
using LinearAlgebra, ProbabilityMeasures
Σ = [4.0 1.0; 1.0 2.5]
d = MvNormal([1.0, -2.0], Matrix(cholesky(Σ).L))
logdensityof(d, [0.3, -1.0])
mean(d), cov(d), var(d), std(d), entropy(d)
rand(d)
It supports densityof, logdensityof, rand, mean, cov, var, std, entropy,
params, support, insupport, and checkparams. var and std return marginal
values. Multivariate cdf, quantile, and median are not provided.
Diagonal and isotropic factors are also supported:
MvNormal(μ, L) # general
MvNormal(μ, Diagonal(σ)) # independent coordinates, σ their standard deviations
MvNormal(μ, σ * I) # isotropic, σ the common standard deviation
The second argument is always a factor. In the diagonal and isotropic forms, σ
contains standard deviations, not variances.
The density result follows normal Julia promotion rules across the parameters and evaluation point:
julia> logdensityof(Normal(0, 1), 1.0f0) isa Float32
true
julia> logdensityof(Normal(0, 1), big"1.0") isa BigFloat
true
Measures broadcast as scalars. Use broadcasting for batched density evaluation:
d = Normal(0.0f0, 1.0f0)
xs = randn(Float32, 1024)
ys = logdensityof.(d, xs)
The same form works with GPU array types whose broadcast implementation supports the operations involved. Package extensions provide integration with ForwardDiff, ReverseDiff, EnzymeCore, and Reactant.
using ProbabilityMeasures, Reactant
d = Normal(0.0, 1.0)
xs = Reactant.to_rarray(randn(1000))
@jit logdensityof.(d, xs)
Scalar sampling is reparameterized: noise is drawn in the underlying floating-point type and the measure parameters enter through arithmetic. This allows derivatives with respect to the parameters without custom derivative rules. Categorical draws do not have a pathwise derivative, but their log-density is differentiable with respect to the probabilities.
Categorical accepts any AbstractVector. Use an isbits vector type, such as
StaticArrays.SVector, when the complete measure must be isbits.
A subtype of AbstractProbabilityMeasure must implement:
DensityInterface.logdensityof(d, x)Base.rand(rng::AbstractRNG, d)Base.eltype(::Type{typeof(d)})support(d)Implementations must also keep logdensityof total, avoid constructor validation,
and support numeric wrapper types used by AD and tracing systems. See the
developer documentation
for the complete contract.
The reusable conformance suite lives in libs/ProbabilityMeasuresTest:
using ProbabilityMeasuresTest
test_measure(Normal(0.0, 1.0))
It checks the interface, normalization, numerical behavior, type stability, allocations, automatic differentiation, GPU-style broadcasting, and Reactant tracing.
Instantiate and run the test environment from the repository root:
julia --project=test -e 'using Pkg; Pkg.instantiate()'
julia --project=test test/runtests.jl
See the contribution guide for contribution guidelines.
ProbabilityMeasures.jl currently contains Normal, LogNormal, Exponential, Uniform,
Laplace, Categorical, and MvNormal. Transformed or composite measures and
Distributions.jl interoperability are not implemented yet.
If you use ProbabilityMeasures.jl in published work, citation metadata is available in CITATION.cff.