From f7dc7e71b8320efac43a0489ab2b0bc3fc271f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C5=9Een=C3=B6z?= Date: Thu, 15 Aug 2024 14:36:59 +0200 Subject: [PATCH 1/4] add value support as a type to ExponentialFamilyDistribution --- src/distributions/binomial.jl | 1 + src/distributions/chi_squared.jl | 1 + src/distributions/laplace.jl | 1 + src/distributions/negative_binomial.jl | 1 + src/distributions/pareto.jl | 1 + src/distributions/poisson.jl | 1 + src/distributions/rayleigh.jl | 1 + src/distributions/von_mises.jl | 2 +- src/distributions/weibull.jl | 2 + src/exponential_family.jl | 59 ++++++++++++++----------- test/distributions/binomial_tests.jl | 2 +- test/distributions/categorical_tests.jl | 5 ++- test/distributions/pareto_tests.jl | 1 + test/distributions/poisson_tests.jl | 1 + test/exponential_family_tests.jl | 3 +- 15 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/distributions/binomial.jl b/src/distributions/binomial.jl index 7f7c4f6e..be2ac307 100644 --- a/src/distributions/binomial.jl +++ b/src/distributions/binomial.jl @@ -43,6 +43,7 @@ function BayesBase.prod( return ExponentialFamilyDistribution( Univariate, + Discrete, naturalparameters, nothing, attributes diff --git a/src/distributions/chi_squared.jl b/src/distributions/chi_squared.jl index d3e487c0..23c493db 100644 --- a/src/distributions/chi_squared.jl +++ b/src/distributions/chi_squared.jl @@ -24,6 +24,7 @@ function BayesBase.prod( return ExponentialFamilyDistribution( Univariate, + Continuous, naturalparameters, nothing, attributes diff --git a/src/distributions/laplace.jl b/src/distributions/laplace.jl index 5661107c..0ebe1889 100644 --- a/src/distributions/laplace.jl +++ b/src/distributions/laplace.jl @@ -102,6 +102,7 @@ function BayesBase.prod( attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp) return ExponentialFamilyDistribution( Univariate, + Continuous, naturalparameters, nothing, attributes diff --git a/src/distributions/negative_binomial.jl b/src/distributions/negative_binomial.jl index 9a82f582..55f9a67f 100644 --- a/src/distributions/negative_binomial.jl +++ b/src/distributions/negative_binomial.jl @@ -42,6 +42,7 @@ function BayesBase.prod( attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp) return ExponentialFamilyDistribution( Univariate, + Discrete, naturalparameters, nothing, attributes diff --git a/src/distributions/pareto.jl b/src/distributions/pareto.jl index b09d48d9..5dc7e901 100644 --- a/src/distributions/pareto.jl +++ b/src/distributions/pareto.jl @@ -85,6 +85,7 @@ function BayesBase.prod( attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, support) return ExponentialFamilyDistribution( Univariate, + Continuous, naturalparameters, nothing, attributes diff --git a/src/distributions/poisson.jl b/src/distributions/poisson.jl index af2cd398..d4ccb756 100644 --- a/src/distributions/poisson.jl +++ b/src/distributions/poisson.jl @@ -25,6 +25,7 @@ function BayesBase.prod( return ExponentialFamilyDistribution( Univariate, + Discrete, naturalparameters, nothing, attributes diff --git a/src/distributions/rayleigh.jl b/src/distributions/rayleigh.jl index 7f1cf973..d0b49d0c 100644 --- a/src/distributions/rayleigh.jl +++ b/src/distributions/rayleigh.jl @@ -23,6 +23,7 @@ function BayesBase.prod( return ExponentialFamilyDistribution( Univariate, + Continuous, naturalparameters, nothing, attributes diff --git a/src/distributions/von_mises.jl b/src/distributions/von_mises.jl index 22d3b289..e4041f7d 100644 --- a/src/distributions/von_mises.jl +++ b/src/distributions/von_mises.jl @@ -31,7 +31,7 @@ function BayesBase.prod( ηleft = getnaturalparameters(left) ηright = getnaturalparameters(right) - return ExponentialFamilyDistribution(VonMises,ηright + ηleft , conditionerleft + conditionerright, nothing) + return ExponentialFamilyDistribution(VonMises, ηright + ηleft , conditionerleft + conditionerright) end diff --git a/src/distributions/weibull.jl b/src/distributions/weibull.jl index d2595f1e..705fa30e 100644 --- a/src/distributions/weibull.jl +++ b/src/distributions/weibull.jl @@ -28,6 +28,7 @@ function BayesBase.prod( attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp) return ExponentialFamilyDistribution( Univariate, + Continuous, naturalparameters, nothing, attributes @@ -51,6 +52,7 @@ function BayesBase.prod( attributes = ExponentialFamilyDistributionAttributes(basemeasure, sufficientstatistics, logpartition, supp) return ExponentialFamilyDistribution( Univariate, + Continuous, naturalparameters, nothing, attributes diff --git a/src/exponential_family.jl b/src/exponential_family.jl index f635b826..dd880899 100644 --- a/src/exponential_family.jl +++ b/src/exponential_family.jl @@ -163,10 +163,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 @@ -209,18 +210,19 @@ julia> logpdf(ef, 4.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 @@ -229,18 +231,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) @@ -862,6 +864,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.") @@ -897,9 +901,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) @@ -929,10 +934,10 @@ 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) && @@ -940,7 +945,7 @@ function Base.isapprox( 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) && @@ -968,8 +973,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} = @@ -991,9 +996,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() && @@ -1011,10 +1016,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() && diff --git a/test/distributions/binomial_tests.jl b/test/distributions/binomial_tests.jl index 670f9e96..f00d3841 100644 --- a/test/distributions/binomial_tests.jl +++ b/test/distributions/binomial_tests.jl @@ -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,)) diff --git a/test/distributions/categorical_tests.jl b/test/distributions/categorical_tests.jl index 7a6757d7..203be90d 100644 --- a/test/distributions/categorical_tests.jl +++ b/test/distributions/categorical_tests.jl @@ -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)) @@ -92,8 +93,8 @@ end 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) + 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) diff --git a/test/distributions/pareto_tests.jl b/test/distributions/pareto_tests.jl index 21ddc7a2..4bf69191 100644 --- a/test/distributions/pareto_tests.jl +++ b/test/distributions/pareto_tests.jl @@ -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 diff --git a/test/distributions/poisson_tests.jl b/test/distributions/poisson_tests.jl index 29b8fb6f..d220492a 100644 --- a/test/distributions/poisson_tests.jl +++ b/test/distributions/poisson_tests.jl @@ -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 diff --git a/test/exponential_family_tests.jl b/test/exponential_family_tests.jl index b70f77c3..fefe1ad3 100644 --- a/test/exponential_family_tests.jl +++ b/test/exponential_family_tests.jl @@ -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 @@ -106,6 +106,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 From 75186922c2119f4bc9b6c892f3168e5d40058f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C5=9Een=C3=B6z?= Date: Thu, 15 Aug 2024 14:42:36 +0200 Subject: [PATCH 2/4] add few tests with value_support --- test/exponential_family_setuptests.jl | 1 + test/exponential_family_tests.jl | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/test/exponential_family_setuptests.jl b/test/exponential_family_setuptests.jl index ebf32e08..b7bd3083 100644 --- a/test/exponential_family_setuptests.jl +++ b/test/exponential_family_setuptests.jl @@ -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...) diff --git a/test/exponential_family_tests.jl b/test/exponential_family_tests.jl index fefe1ad3..43be1d66 100644 --- a/test/exponential_family_tests.jl +++ b/test/exponential_family_tests.jl @@ -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 @@ -145,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)) From 6ee404b793e5d589673620c6319ead65e2be83bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C5=9Een=C3=B6z?= Date: Thu, 15 Aug 2024 15:15:58 +0200 Subject: [PATCH 3/4] change the doc tests --- src/exponential_family.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exponential_family.jl b/src/exponential_family.jl index 7a536f77..e75727a6 100644 --- a/src/exponential_family.jl +++ b/src/exponential_family.jl @@ -195,7 +195,7 @@ For a given member of exponential family: ```jldoctest julia> ef = convert(ExponentialFamilyDistribution, Bernoulli(0.5)) -ExponentialFamily(Bernoulli) +ExponentialFamily(Bernoulli, Discrete) julia> getsufficientstatistics(ef) (identity,) @@ -203,7 +203,7 @@ julia> getsufficientstatistics(ef) ```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 From e3c2ada566b62eafbb1af6e3b0d34a91fbc3192e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C5=9Een=C3=B6z?= Date: Thu, 15 Aug 2024 15:41:13 +0200 Subject: [PATCH 4/4] show function --- src/exponential_family.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exponential_family.jl b/src/exponential_family.jl index e75727a6..20d140ad 100644 --- a/src/exponential_family.jl +++ b/src/exponential_family.jl @@ -243,7 +243,7 @@ function ExponentialFamilyDistribution( end function Base.show(io::IO, ef::ExponentialFamilyDistribution{Tvar, Tval}) where {Tvar, Tval} - print(io, "ExponentialFamily(", Tvar, Tval) + print(io, "ExponentialFamily(", Tvar,", ", Tval) conditioner = getconditioner(ef) if !isnothing(conditioner) print(io, ", conditioned on ", conditioner)