Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/distributions/binomial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function BayesBase.prod(

return ExponentialFamilyDistribution(
Univariate,
Discrete,
naturalparameters,
nothing,
attributes
Expand Down
1 change: 1 addition & 0 deletions src/distributions/chi_squared.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function BayesBase.prod(

return ExponentialFamilyDistribution(
Univariate,
Continuous,
naturalparameters,
nothing,
attributes
Expand Down
1 change: 1 addition & 0 deletions src/distributions/laplace.jl
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ function BayesBase.prod(
attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp)
return ExponentialFamilyDistribution(
Univariate,
Continuous,
naturalparameters,
nothing,
attributes
Expand Down
1 change: 1 addition & 0 deletions src/distributions/negative_binomial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function BayesBase.prod(
attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp)
return ExponentialFamilyDistribution(
Univariate,
Discrete,
naturalparameters,
nothing,
attributes
Expand Down
1 change: 1 addition & 0 deletions src/distributions/pareto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ function BayesBase.prod(
attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, support)
return ExponentialFamilyDistribution(
Univariate,
Continuous,
naturalparameters,
nothing,
attributes
Expand Down
1 change: 1 addition & 0 deletions src/distributions/poisson.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function BayesBase.prod(

return ExponentialFamilyDistribution(
Univariate,
Discrete,
naturalparameters,
nothing,
attributes
Expand Down
1 change: 1 addition & 0 deletions src/distributions/rayleigh.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ function BayesBase.prod(

return ExponentialFamilyDistribution(
Univariate,
Continuous,
naturalparameters,
nothing,
attributes
Expand Down
5 changes: 3 additions & 2 deletions src/distributions/von_mises.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ function BayesBase.prod(
conditionerright = getconditioner(right)
ηleft = getnaturalparameters(left)
ηright = getnaturalparameters(right)

return ExponentialFamilyDistribution(VonMises, ηright + ηleft, conditionerleft + conditionerright, nothing)

return ExponentialFamilyDistribution(VonMises, ηright + ηleft , conditionerleft + conditionerright)

end

BayesBase.insupport(ef::ExponentialFamilyDistribution{T}, value) where {T <: VonMises} = insupport(convert(Distribution, ef), value)
Expand Down
2 changes: 2 additions & 0 deletions src/distributions/weibull.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function BayesBase.prod(
attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp)
return ExponentialFamilyDistribution(
Univariate,
Continuous,
naturalparameters,
nothing,
attributes
Expand All @@ -51,6 +52,7 @@ function BayesBase.prod(
attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp)
return ExponentialFamilyDistribution(
Univariate,
Continuous,
naturalparameters,
nothing,
attributes
Expand Down
63 changes: 34 additions & 29 deletions src/exponential_family.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,11 @@ BayesBase.insupport(attributes::ExponentialFamilyDistributionAttributes, value)
BayesBase.value_support(::Type{ExponentialFamilyDistributionAttributes{B, S, L, P}}) where {B, S, L, P} = value_support(P)

"""
ExponentialFamilyDistribution(::Type{T}, naturalparameters, conditioner, attributes)
ExponentialFamilyDistribution(::Type{Tvar}, ::Type{Tval}, naturalparameters, conditioner, attributes)

`ExponentialFamilyDistribution` structure represents a generic exponential family distribution in natural parameterization.
Type `T` can be either a distribution type (e.g. from the `Distributions.jl` package) or a variate type (e.g. `Univariate`).
Type `Tvar` can be either a distribution type (e.g. from the `Distributions.jl` package) or a variate type (e.g. `Univariate`).
Type `Tval` is can be `Continuous` or `Discrete` of type `Distributions.ValueSupport`.
In the context of the package, exponential family distributions are represented in the form:

```math
Expand All @@ -194,34 +195,35 @@ For a given member of exponential family:

```jldoctest
julia> ef = convert(ExponentialFamilyDistribution, Bernoulli(0.5))
ExponentialFamily(Bernoulli)
ExponentialFamily(Bernoulli, Discrete)

julia> getsufficientstatistics(ef)
(identity,)
```

```jldoctest
julia> ef = convert(ExponentialFamilyDistribution, Laplace(1.0, 0.5))
ExponentialFamily(Laplace, conditioned on 1.0)
ExponentialFamily(Laplace, Continuous, conditioned on 1.0)

julia> logpdf(ef, 4.0)
-6.0
```

See also: [`getbasemeasure`](@ref), [`getsufficientstatistics`](@ref), [`getnaturalparameters`](@ref), [`getlogpartition`](@ref), [`getsupport`](@ref)
"""
struct ExponentialFamilyDistribution{T, P, C, A}
struct ExponentialFamilyDistribution{Tvar, Tval, P, C, A}
naturalparameters::P
conditioner::C
attributes::A

ExponentialFamilyDistribution(
::Type{T},
::Type{Tvar},
::Type{Tval},
naturalparameters::P,
conditioner::C = nothing,
attributes::A = nothing
) where {T, P, C, A} = begin
new{T, P, C, A}(
) where {Tvar, Tval, P, C, A} = begin
new{Tvar, Tval, P, C, A}(
naturalparameters,
conditioner,
attributes
Expand All @@ -230,18 +232,18 @@ struct ExponentialFamilyDistribution{T, P, C, A}
end

function ExponentialFamilyDistribution(
::Type{T},
::Type{Tvar},
naturalparameters::P,
conditioner = nothing
) where {T <: Distribution, P}
if !isproper(NaturalParametersSpace(), T, naturalparameters, conditioner)
) where {Tvar <: Distribution, P}
if !isproper(NaturalParametersSpace(), Tvar, naturalparameters, conditioner)
error(lazy"Parameter vector $(naturalparameters) is not a valid natural parameter for distribution $(T).")
end
return ExponentialFamilyDistribution(T, naturalparameters, conditioner, nothing)
return ExponentialFamilyDistribution(Tvar, value_support(Tvar), naturalparameters, conditioner, nothing)
end

function Base.show(io::IO, ef::ExponentialFamilyDistribution{T}) where {T}
print(io, "ExponentialFamily(", T)
function Base.show(io::IO, ef::ExponentialFamilyDistribution{Tvar, Tval}) where {Tvar, Tval}
print(io, "ExponentialFamily(", Tvar,", ", Tval)
conditioner = getconditioner(ef)
if !isnothing(conditioner)
print(io, ", conditioned on ", conditioner)
Expand Down Expand Up @@ -860,6 +862,8 @@ function BayesBase.params(::NaturalParametersSpace, distribution::Distribution)
return map(MeanParametersSpace() => NaturalParametersSpace(), exponential_family_typetag(distribution), θ)
end

BayesBase.value_support(::ExponentialFamilyDistribution{Tvar, Tval}) where {Tvar, Tval} = Tval

Base.convert(::Type{Distribution}, ef::ExponentialFamilyDistribution{T}) where {T} =
error("Cannot convert an arbitrary `ExponentialFamily{$T}` object to a `Distribution`. An explicit approximation method is required.")

Expand Down Expand Up @@ -895,9 +899,10 @@ function BayesBase.paramfloattype(ef::ExponentialFamilyDistribution)
return deep_eltype(getnaturalparameters(ef))
end

function BayesBase.convert_paramfloattype(::Type{F}, ef::ExponentialFamilyDistribution{T}) where {F, T}
function BayesBase.convert_paramfloattype(::Type{F}, ef::ExponentialFamilyDistribution{Tvar, Tval}) where {F, Tvar, Tval}
return ExponentialFamilyDistribution(
T,
Tvar,
Tval,
convert_paramfloattype(F, getnaturalparameters(ef)),
getconditioner(ef),
getattributes(ef)
Expand Down Expand Up @@ -927,18 +932,18 @@ Base.isapprox(left::ExponentialFamilyDistribution, right::ExponentialFamilyDistr
Base.:(==)(left::ExponentialFamilyDistribution, right::ExponentialFamilyDistribution) = false

function Base.isapprox(
left::ExponentialFamilyDistribution{T},
right::ExponentialFamilyDistribution{T};
left::ExponentialFamilyDistribution{Tvar, Tval},
right::ExponentialFamilyDistribution{Tvar, Tval};
kwargs...
) where {T}
) where {Tvar, Tval}
return getbasemeasure(left) == getbasemeasure(right) &&
getsufficientstatistics(left) == getsufficientstatistics(right) &&
getlogpartition(left) == getlogpartition(right) && getsupport(left) == getsupport(right) &&
getconditioner(left) == getconditioner(right) &&
isapprox(getnaturalparameters(left), getnaturalparameters(right); kwargs...)
end

function Base.:(==)(left::ExponentialFamilyDistribution{T}, right::ExponentialFamilyDistribution{T}) where {T}
function Base.:(==)(left::ExponentialFamilyDistribution{Tvar, Tval}, right::ExponentialFamilyDistribution{Tvar, Tval}) where {Tvar, Tval}
return getbasemeasure(left) == getbasemeasure(right) &&
getsufficientstatistics(left) == getsufficientstatistics(right) &&
getlogpartition(left) == getlogpartition(right) && getsupport(left) == getsupport(right) &&
Expand Down Expand Up @@ -966,8 +971,8 @@ end

Base.similar(ef::ExponentialFamilyDistribution) = similar(ef, eltype(getnaturalparameters(ef)))

function Base.similar(ef::ExponentialFamilyDistribution{T}, ::Type{F}) where {T, F}
return ExponentialFamilyDistribution(T, similar(getnaturalparameters(ef), F), getconditioner(ef), getattributes(ef))
function Base.similar(ef::ExponentialFamilyDistribution{Tvar, Tval}, ::Type{F}) where {Tvar, Tval, F}
return ExponentialFamilyDistribution(Tvar, Tval, similar(getnaturalparameters(ef), F), getconditioner(ef), getattributes(ef))
end

BayesBase.vague(::Type{ExponentialFamilyDistribution{T}}, args...) where {T <: Distribution} =
Expand All @@ -989,9 +994,9 @@ end

function BayesBase.prod(
::PreserveTypeProd{ExponentialFamilyDistribution},
left::ExponentialFamilyDistribution{T},
right::ExponentialFamilyDistribution{T}
) where {T}
left::ExponentialFamilyDistribution{Tvar, Tval},
right::ExponentialFamilyDistribution{Tvar, Tval}
) where {Tvar, Tval}
# Se here we assume that if both left has the exact same base measure and this base measure is `ConstantBaseMeasure`
# We assume that this code-path is static and should be const-folded in run-time (there are tests that check that this function does not allocated more than `similar(left)`)
if isbasemeasureconstant(left) === ConstantBaseMeasure() &&
Expand All @@ -1009,10 +1014,10 @@ function BayesBase.prod(
end

function BayesBase.prod!(
container::ExponentialFamilyDistribution{T},
left::ExponentialFamilyDistribution{T},
right::ExponentialFamilyDistribution{T}
) where {T}
container::ExponentialFamilyDistribution{Tvar, Tval},
left::ExponentialFamilyDistribution{Tvar, Tval},
right::ExponentialFamilyDistribution{Tvar, Tval}
) where {Tvar, Tval}
# First check if we can actually simply sum-up the natural parameters
# We assume that this code-path is static and should be const-folded in run-time (there are tests that check that this function does not allocate in this simple case)
if isbasemeasureconstant(left) === ConstantBaseMeasure() &&
Expand Down
2 changes: 1 addition & 1 deletion test/distributions/binomial_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ end
support = 0:1:max(nleft, nright)

@test sum(hist_sum, support) ≈ 1.0 atol = 1e-9

@test value_support(prod_dist) == Discrete
for x in support
@test basemeasure(prod_dist, x) ≈ (binomial(nleft, x) * binomial(nright, x))
@test all(sufficientstatistics(prod_dist, x) .≈ (x,))
Expand Down
7 changes: 4 additions & 3 deletions test/distributions/categorical_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ end

@test !@inferred(insupport(ef, s + 1))
@test @inferred(insupport(ef, s))
@test @inferred(value_support(ef)) == Discrete

# # Not in the support
@test_throws Exception logpdf(ef, ones(s))
Expand All @@ -91,9 +92,9 @@ end
using RecursiveArrayTools
include("distributions_setuptests.jl")
for s in (2, 3, 4, 5)
@testset let params = rand(s - 1)
ef = ExponentialFamilyDistribution(Categorical, [params..., 0], s, nothing)
part_ef = ExponentialFamilyDistribution(Categorical, ArrayPartition(params, [0]), s, nothing)
@testset let params = rand(s-1)
ef = ExponentialFamilyDistribution(Categorical, [params..., 0], s)
part_ef = ExponentialFamilyDistribution(Categorical, ArrayPartition(params, [0]), s)
@test convert(Distribution, ef) ≈ convert(Distribution, part_ef)
@test mean(ef) ≈ mean(part_ef)
@test var(ef) ≈ var(part_ef)
Expand Down
1 change: 1 addition & 0 deletions test/distributions/pareto_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ end
@test first(
hquadrature(x -> pdf(prod_dist, tan(x * pi / 2)) * (pi / 2) * (1 / cos(x * pi / 2)^2), (2 / pi) * atan(getsupport(prod_dist).lb), 1.0)
) ≈ 1.0
@test value_support(prod_dist) == Continuous
end
end
end
1 change: 1 addition & 0 deletions test/distributions/poisson_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ end
@test getsupport(prod_dist) == NaturalNumbers()

@test sum(pdf(prod_dist, x) for x in 0:15) ≈ 1.0
@test value_support(prod_dist) == Discrete
end
end
1 change: 1 addition & 0 deletions test/exponential_family_setuptests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ BayesBase.vague(::Type{ArbitraryConditionedDistributionFromExponentialFamily}) =
ArbitraryConditionedDistributionFromExponentialFamily(1.0, -2)

BayesBase.params(dist::ArbitraryConditionedDistributionFromExponentialFamily) = (dist.con, dist.p1)
BayesBase.value_support(::ArbitraryConditionedDistributionFromExponentialFamily) = Continuous

ExponentialFamily.separate_conditioner(::Type{ArbitraryConditionedDistributionFromExponentialFamily}, params) = ((params[2],), params[1])
ExponentialFamily.join_conditioner(::Type{ArbitraryConditionedDistributionFromExponentialFamily}, cparams, conditioner) = (conditioner, cparams...)
Expand Down
7 changes: 6 additions & 1 deletion test/exponential_family_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ end
end

@testset let member =
ExponentialFamilyDistribution(Univariate, [2.0, 2.0], nothing, ArbitraryExponentialFamilyAttributes)
ExponentialFamilyDistribution(Univariate, Continuous, [2.0, 2.0], nothing, ArbitraryExponentialFamilyAttributes)
η = @inferred(getnaturalparameters(member))

@test ExponentialFamily.exponential_family_typetag(member) === Univariate
Expand All @@ -91,6 +91,8 @@ end
@test @inferred(insupport(member, 1.0))
@test !@inferred(insupport(member, -1.0))

@test @inferred(value_support(member)) == Continuous

_similar = @inferred(similar(member))

# The standard `@allocated` is not really reliable in this test
Expand All @@ -106,6 +108,7 @@ end
@test all(@inferred(sufficientstatistics(_similar, 2.0)) .≈ (2.0, log(2.0)))
@test @inferred(logpartition(_similar, η)) ≈ 0.25
@test @inferred(getsupport(_similar)) == RealInterval(0, Inf)
@test @inferred(value_support(_similar)) == Continuous
end
end

Expand Down Expand Up @@ -144,6 +147,8 @@ end
@test insupport(member, 1.0)
@test !insupport(member, -1.0)

@test @inferred(value_support(member)) == Continuous

# Computed by hand
@test @inferred(logpdf(member, 2.0)) ≈ (3.75 + 2log(2))
@test @inferred(logpdf(member, 4.0)) ≈ (7.75 + 4log(2))
Expand Down