diff --git a/src/distributions/binomial.jl b/src/distributions/binomial.jl index 67e37915..e6bba461 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 06099653..7d860469 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 d9163c2e..c0cc2b99 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 ebd06d36..ddf2d72a 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 a86bdc66..7d50bed9 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 e01ae1c2..9fd5be8d 100644 --- a/src/distributions/von_mises.jl +++ b/src/distributions/von_mises.jl @@ -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) diff --git a/src/distributions/weibull.jl b/src/distributions/weibull.jl index 33fe3f8d..bfe825dc 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 1d60dfa7..20d140ad 100644 --- a/src/exponential_family.jl +++ b/src/exponential_family.jl @@ -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 @@ -194,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,) @@ -202,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 @@ -210,18 +211,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 @@ -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) @@ -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.") @@ -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) @@ -927,10 +932,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) && @@ -938,7 +943,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) && @@ -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} = @@ -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() && @@ -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() && diff --git a/test/distributions/binomial_tests.jl b/test/distributions/binomial_tests.jl index ee2efa21..ec9d6575 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 e9d547ae..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)) @@ -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) 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_setuptests.jl b/test/exponential_family_setuptests.jl index 6226405f..80590a03 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 ba15320c..db349b41 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 @@ -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 @@ -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 @@ -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))