From e71eb2ded6f3320d66a6d7efc615e3e60dddb58f Mon Sep 17 00:00:00 2001 From: Oliver Schulz Date: Tue, 16 Nov 2021 17:44:55 +0100 Subject: [PATCH] Replace hasdensity by DensityKind (#9) Also changes behavior of `logfuncdensity` to always return a density and adds `funcdensity`, Co-authored-by: Philipp Gabler Co-authored-by: David Widmann --- Project.toml | 2 +- README.md | 2 +- docs/make.jl | 5 +- docs/src/api.md | 9 +- docs/src/index.md | 31 +++-- src/DensityInterface.jl | 3 +- src/interface.jl | 301 +++++++++++++++++++++++++++++++--------- src/interface_test.jl | 53 ++++--- test/runtests.jl | 5 +- test/test_interface.jl | 31 +++-- 10 files changed, 318 insertions(+), 124 deletions(-) diff --git a/Project.toml b/Project.toml index 6726a66..a875e93 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "DensityInterface" uuid = "b429d917-457f-4dbc-8f4c-0cc954292b1d" -version = "0.3.3" +version = "0.4.0" [deps] InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" diff --git a/README.md b/README.md index 271911b..74db9d8 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Build Status](https://github.com/JuliaMath/DensityInterface.jl/workflows/CI/badge.svg?branch=master)](https://github.com/JuliaMath/DensityInterface.jl/actions?query=workflow%3ACI) [![Codecov](https://codecov.io/gh/JuliaMath/DensityInterface.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaMath/DensityInterface.jl) -This package defines an interface for mathematical/statistical densities in Julia. See the documentation for details. +This package defines an interface for mathematical/statistical densities and objects associated with a density in Julia. See the documentation for details. ## Documentation diff --git a/docs/make.jl b/docs/make.jl index af63a6e..c2b3435 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,8 +13,9 @@ DocMeta.setdocmeta!( :DocTestSetup, quote using DensityInterface - d = logfuncdensity(x -> x^2) - log_f = logdensityof(d) + object = logfuncdensity(x -> -x^2) + log_f = logdensityof(object) + f = densityof(object) x = 4 end; recursive=true, diff --git a/docs/src/api.md b/docs/src/api.md index a87b6e4..74eba64 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -3,19 +3,24 @@ ## Interface ```@docs -hasdensity logdensityof logdensityof(::Any) logfuncdensity +funcdensity densityof densityof(::Any) ``` - ## Types ```@docs +IsDensity +HasDensity +IsOrHasDensity +NoDensity +DensityKind DensityInterface.LogFuncDensity +DensityInterface.FuncDensity ``` ## Test utility diff --git a/docs/src/index.md b/docs/src/index.md index f8a13cc..763207b 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -12,33 +12,36 @@ end DensityInterface ``` -This package defines an interface for mathematical/statistical densities and objects associated with a density in Julia. The interface comprises the functions [`hasdensity`](@ref), [`logdensityof`](@ref)/[`densityof`](@ref)[^1] and [`logfuncdensity`](@ref). +This package defines an interface for mathematical/statistical densities and objects associated with a density in Julia. The interface comprises the type [`DensityKind`](@ref) and the functions [`logdensityof`](@ref)/[`densityof`](@ref)[^1] and [`logfuncdensity`](@ref)/[`funcdensity`](@ref). The following methods must be provided to make a type (e.g. `SomeDensity`) compatible with the interface: ```jldoctest a import DensityInterface -DensityInterface.hasdensity(::SomeDensity) = true -DensityInterface.logdensityof(d::SomeDensity, x) = log_of_d_at(x) +@inline DensityInterface.DensityKind(::SomeDensity) = IsDensity() +DensityInterface.logdensityof(object::SomeDensity, x) = log_of_d_at(x) -DensityInterface.logdensityof(SomeDensity(), x) isa Real +object = SomeDensity() +DensityInterface.logdensityof(object, x) isa Real # output true ``` -The object `d` may be a density itself or something that can be said to have a density. If `d` is a distribution, the density is its probability density function. In the measure theoretical sense, the density function is the Radon–Nikodym derivative of `d` with respect to an implicit base measure. In statistical inference applications, for example, `d` might be a likelihood, prior or posterior[^2]. +`object` may be/represent a density itself (`DensityKind(object) === IsDensity()`) or it may be something that can be said to have a density (`DensityKind(object) === HasDensity()`)[^2]. -DensityInterface automatically provides `logdensityof(d)`, equivalent to `x -> logdensityof(d, x)`. This constitutes a convenient way of passing a (log-)density function to algorithms like optimizers, samplers, etc.: +In statistical inference applications, for example, `object` might be a likelihood, prior or posterior. + +DensityInterface automatically provides `logdensityof(object)`, equivalent to `x -> logdensityof(object, x)`. This constitutes a convenient way of passing a (log-)density function to algorithms like optimizers, samplers, etc.: ```jldoctest a using DensityInterface -d = SomeDensity() -log_f = logdensityof(d) -log_f(x) == logdensityof(d, x) +object = SomeDensity() +log_f = logdensityof(object) +log_f(x) == logdensityof(object, x) # output @@ -46,15 +49,14 @@ true ``` ```julia -SomeOptimizerPackage.maximize(logdensityof(d), x_init) +SomeOptimizerPackage.maximize(logdensityof(object), x_init) ``` Reversely, a given log-density function `log_f` can be converted to a DensityInterface-compatible density object using [`logfuncdensity`](@ref): ```julia -d = logfuncdensity(log_f) -hasdensity(d) == true -logdensityof(d, x) == log_f(x) +object = logfuncdensity(log_f) +DensityKind(object) === IsDensity() && logdensityof(object, x) == log_f(x) # output @@ -64,5 +66,4 @@ true [^1]: The function names `logdensityof` and `densityof` were chosen to convey that the target object may either *be* a density or something that can be said to *have* a density. They also have less naming conflict potential than `logdensity` and esp. `density` (the latter already being exported by Plots.jl). -[^2]: The package [`MeasureTheory`](https://github.com/cscherrer/MeasureTheory.jl) provides tools to work with densities and measures that go beyond the density in -respect to an implied base measure. +[^2]: The package [`Distributions`](https://github.com/JuliaStats/Distributions.jl) supports `DensityInterface` for `Distributions.Distribution`. diff --git a/src/DensityInterface.jl b/src/DensityInterface.jl index a0b1846..b7798e2 100644 --- a/src/DensityInterface.jl +++ b/src/DensityInterface.jl @@ -3,7 +3,8 @@ """ DensityInterface -Trait-based interface for mathematical/statistical densities +Trait-based interface for mathematical/statistical densities and objects +associated with a density. """ module DensityInterface diff --git a/src/interface.jl b/src/interface.jl index 29d7b25..665feb6 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -2,73 +2,193 @@ """ - hasdensity(d)::Bool + abstract type DensityKind end -Return `true` if `d` is compatible with the `DensityInterface` interface. + DensityKind(object) -`hasdensity(d) == true` implies that `d` is either a density itself or has an -associated density, e.g. a probability density function or a Radon–Nikodym -derivative with respect to an implicit base measure. It also implies that the -value of that density at given points can be calculated via +Subtypes of `DensityKind` indicate if an `object` *is* a density or if it *has* +a density, in the sense of the `DensityInterface` API, or if is *not* +associated with a density (not compatible with `DensityInterface`). + +`DensityKind(object)` returns either `IsDensity()`, `HasDensity()` or +`NoDensity()`. + +In addition to the subtypes [`IsDensity`](@ref), [`HasDensity`](@ref) or +[`NoDensity`](@ref), a union `IsOrHasDensity = Union{IsDensity, HasDensity}` +is defined for convenience. + +`DensityKind(object) isa IsOrHasDensity` implies that `object` is either a +density itself or can be said to have an associated density. It also implies +that the value of that density at given points can be calculated via [`logdensityof`](@ref) and [`densityof`](@ref). + +`DensityKind(object)` defaults to `NoDensity()` (object is not and does not +have a density). For a type that *is* (directly represents) a density, like a +probability density, define + +```julia +@inline DensityKind(::MyDensityType) = IsDensity() ``` + +For a type that *has* (is associated with) a density in some way, like +a probability distribution has a probability density, define + +```julia +@inline DensityKind(::MyDensityType) = HasDensity() +``` +""" +abstract type DensityKind end +export DensityKind + +@inline DensityKind(object) = NoDensity() + + """ -function hasdensity end -export hasdensity + struct IsDensity <: DensityKind end -@inline hasdensity(::Any) = false +As a return value of [`DensityKind(object)`](@ref), indicates that +`object` *is* (represents) a density, like a probability density +object. + +See [`DensityKind`](@ref) for details. +""" +struct IsDensity <: DensityKind end +export IsDensity + + +""" + struct HasDensity <: DensityKind end + +As a return value of [`DensityKind(object)`](@ref), indicates that +`object` *has* a density, like a probability distribution has +a probability density. + +See [`DensityKind`](@ref) for details. +""" +struct HasDensity <: DensityKind end +export HasDensity + + +""" + struct NoDensity <: DensityKind end -function check_hasdensity(d) - hasdensity(d) || throw(ArgumentError("Object of type $(typeof(d)) is not compatible with DensityInterface")) +As a return value of [`DensityKind(object)`](@ref), indicates that +`object` *is not* and *does not have* a density, as understood by +`DensityInterface`. + +See [`DensityKind`](@ref) for details. +""" +struct NoDensity <: DensityKind end +export NoDensity + + +""" + IsOrHasDensity = Union{IsDensity, HasDensity} + +As a return value of [`DensityKind(object)`](@ref), indicates that `object` +either *is* or *has* a density, as understood by `DensityInterface`. + +See [`DensityKind`](@ref) for details. +""" +const IsOrHasDensity = Union{IsDensity, HasDensity} +export IsOrHasDensity + + +function _check_is_or_has_density(object) + DensityKind(object) isa IsOrHasDensity || throw(ArgumentError("Object of type $(typeof(object)) neither is nor has a density")) end """ - logdensityof(d, x)::Real + logdensityof(object, x)::Real -Compute the logarithmic value of density `d` or it's associated density +Compute the logarithmic value of the density `object` (resp. its associated density) at a given point `x`. ```jldoctest a -julia> hasdensity(d) -true +julia> DensityKind(object) +IsDensity() -julia> logy = logdensityof(d, x); logy isa Real +julia> logy = logdensityof(object, x); logy isa Real true ``` -See also [`hasdensity`](@ref) and [`densityof`](@ref). +See also [`DensityKind`](@ref) and [`densityof`](@ref). """ function logdensityof end export logdensityof """ - logdensityof(d) + logdensityof(object) -Return a function that computes the logarithmic value of density `d` -or its associated density at a given point. +Return a function that computes the logarithmic value of the density `object` +(resp. its associated density) at a given point. ```jldoctest a -julia> log_f = logdensityof(d); log_f isa Function +julia> log_f = logdensityof(object); log_f isa Function true -julia> log_f(x) == logdensityof(d, x) +julia> log_f(x) == logdensityof(object, x) true ``` -`logdensityof(d)` defaults to `Base.Fix1(logdensityof, d)`, but may be +`logdensityof(object)` defaults to `Base.Fix1(logdensityof, object)`, but may be specialized. If so, [`logfuncdensity`](@ref) will typically have to be specialized for the return type of `logdensityof` as well. [`logfuncdensity`](@ref) is the inverse of `logdensityof`, so -`logfuncdensity(log_f)` must be equivalent to `d`. +`logfuncdensity(log_f)` must be equivalent to `object`. """ -function logdensityof(d) - check_hasdensity(d) - Base.Fix1(logdensityof, d) +function logdensityof(object) + _check_is_or_has_density(object) + Base.Fix1(logdensityof, object) end +""" + densityof(object, x)::Real + +Compute the value of the density `object` (resp. its associated density) +at a given point `x`. + +```jldoctest a +julia> DensityKind(object) +IsDensity() + +julia> densityof(object, x) == exp(logdensityof(object, x)) +true +``` + +`densityof(object, x)` defaults to `exp(logdensityof(object, x))`, but +may be specialized. + +See also [`DensityKind`](@ref) and [`densityof`](@ref). +""" +densityof(object, x) = exp(logdensityof(object, x)) +export densityof + +""" + densityof(object) + +Return a function that computes the value of the density `object` +(resp. its associated density) at a given point. + +```jldoctest a +julia> f = densityof(object); + +julia> f(x) == densityof(object, x) +true +``` + +`densityof(object)` defaults to `Base.Fix1(densityof, object)`, but may be specialized. +""" +function densityof(object) + _check_is_or_has_density(object) + Base.Fix1(densityof, object) +end + + + """ logfuncdensity(log_f) @@ -76,12 +196,12 @@ Return a `DensityInterface`-compatible density that is defined by a given log-density function `log_f`: ```jldoctest -julia> d = logfuncdensity(log_f); +julia> object = logfuncdensity(log_f); -julia> hasdensity(d) == true -true +julia> DensityKind(object) +IsDensity() -julia> logdensityof(d, x) == log_f(x) +julia> logdensityof(object, x) == log_f(x) true ``` @@ -93,17 +213,27 @@ specialized for the return type of `logfuncdensity` as well. `logfuncdensity` is the inverse of `logdensityof`, so the following must hold true: -* `logfuncdensity(logdensityof(d))` is equivalent to `d` -* `logdensityof(logfuncdensity(log_f))` is equivalent to `log_f`. +* `d = logfuncdensity(logdensityof(object))` is equivalent to `object` in + respect to `logdensityof` and `densityof`. However, `d` may not be equal to + `object`, especially if `DensityKind(object) == HasDensity()`: `logfuncdensity` always + creates something that *is* density, never something that just *has* + a density in some way (like a distribution or a measure in general). +* `logdensityof(logfuncdensity(log_f))` is equivalent (typically equal or even + identical to) to `log_f`. -See also [`hasdensity`](@ref). +See also [`DensityKind`](@ref). """ function logfuncdensity end export logfuncdensity -logfuncdensity(log_f) = LogFuncDensity(log_f) +@inline logfuncdensity(log_f) = LogFuncDensity(log_f) -logfuncdensity(log_f::Base.Fix1{typeof(logdensityof)}) = log_f.x +# For functions stemming from objects that *have* a density, create a new density: +@inline _logfuncdensity_impl(::HasDensity, log_f::Base.Fix1{typeof(logdensityof)}) = LogFuncDensity(log_f) +# For functions stemming from objects that *are* a density, recover original object: +@inline _logfuncdensity_impl(::IsDensity, log_f::Base.Fix1{typeof(logdensityof)}) = log_f.x + +@inline logfuncdensity(log_f::Base.Fix1{typeof(logdensityof)}) = _logfuncdensity_impl(DensityKind(log_f.x), log_f) InverseFunctions.inverse(::typeof(logfuncdensity)) = logdensityof InverseFunctions.inverse(::typeof(logdensityof)) = logfuncdensity @@ -121,57 +251,94 @@ struct LogFuncDensity{F} end LogFuncDensity -@inline hasdensity(::LogFuncDensity) = true +@inline DensityKind(::LogFuncDensity) = IsDensity() + +@inline logdensityof(object::LogFuncDensity, x) = object._log_f(x) +@inline logdensityof(object::LogFuncDensity) = object._log_f -@inline logdensityof(d::LogFuncDensity, x) = d._log_f(x) -@inline logdensityof(d::LogFuncDensity) = d._log_f +@inline densityof(object::LogFuncDensity, x) = exp(object._log_f(x)) +@inline densityof(object::LogFuncDensity) = exp ∘ object._log_f -function Base.show(io::IO, d::LogFuncDensity) - print(io, nameof(typeof(d)), "(") - show(io, d._log_f) +function Base.show(io::IO, object::LogFuncDensity) + print(io, nameof(typeof(object)), "(") + show(io, object._log_f) print(io, ")") end """ - densityof(d, x)::Real + funcdensity(f) -Compute the value of density `d` or its associated density at a given point -`x`. +Return a `DensityInterface`-compatible density that is defined by a given +non-log density function `f`: -```jldoctest a -julia> hasdensity(d) -true +```jldoctest +julia> object = funcdensity(f); + +julia> DensityKind(object) +IsDensity() -julia> densityof(d, x) == exp(logdensityof(d, x)) +julia> densityof(object, x) == f(x) true ``` -`densityof(d, x)` defaults to `exp(logdensityof(d, x))`, but -may be specialized. +`funcdensity(f)` returns an instance of [`DensityInterface.FuncDensity`](@ref) +by default, but may be specialized to return something else depending on the +type of `f`). If so, [`densityof`](@ref) will typically have to be +specialized for the return type of `funcdensity` as well. -See also [`hasdensity`](@ref) and [`logdensityof`](@ref). -""" -densityof(d, x) = exp(logdensityof(d, x)) -export densityof +`funcdensity` is the inverse of `densityof`, so the following must +hold true: + +* `d = funcdensity(densityof(object))` is equivalent to `object` in + respect to `logdensityof` and `densityof`. However, `d` may not be equal to + `object`, especially if `DensityKind(object) == HasDensity()`: `funcdensity` always + creates something that *is* density, never something that just *has* + a density in some way (like a distribution or a measure in general). +* `densityof(funcdensity(f))` is equivalent (typically equal or even + identical to) to `f`. +See also [`DensityKind`](@ref). """ - densityof(d) +function funcdensity end +export funcdensity -Return a function that computes the value of density `d` or its associated -density at a given point. +@inline funcdensity(f) = FuncDensity(f) -```jldoctest a -julia> f = densityof(d); +# For functions stemming from objects that *have* a density, create a new density: +@inline _funcdensity_impl(::HasDensity, f::Base.Fix1{typeof(densityof)}) = FuncDensity(f) +# For functions stemming from objects that *are* a density, recover original object: +@inline _funcdensity_impl(::IsDensity, f::Base.Fix1{typeof(densityof)}) = f.x + +@inline funcdensity(f::Base.Fix1{typeof(densityof)}) = _funcdensity_impl(DensityKind(f.x), f) + +InverseFunctions.inverse(::typeof(funcdensity)) = densityof +InverseFunctions.inverse(::typeof(densityof)) = funcdensity -julia> f(x) == densityof(d, x) -true -``` -`densityof(d)` defaults to `Base.Fix1(densityof, d)`, but may be specialized. """ -function densityof(d) - check_hasdensity(d) - Base.Fix1(densityof, d) + struct DensityInterface.FuncDensity{F} + +Wraps a non-log density function `f` to make it compatible with +`DensityInterface` interface. Typically, `FuncDensity(f)` should not be +called directly, [`funcdensity`](@ref) should be used instead. +""" +struct FuncDensity{F} + _f::F +end +FuncDensity + +@inline DensityKind(::FuncDensity) = IsDensity() + +@inline logdensityof(object::FuncDensity, x) = log(object._f(x)) +@inline logdensityof(object::FuncDensity) = log ∘ object._f + +@inline densityof(object::FuncDensity, x) = object._f(x) +@inline densityof(object::FuncDensity) = object._f + +function Base.show(io::IO, object::FuncDensity) + print(io, nameof(typeof(object)), "(") + show(io, object._f) + print(io, ")") end diff --git a/src/interface_test.jl b/src/interface_test.jl index a1de403..24259f2 100644 --- a/src/interface_test.jl +++ b/src/interface_test.jl @@ -1,38 +1,45 @@ # This file is a part of DensityInterface.jl, licensed under the MIT License (MIT). """ - DensityInterface.test_density_interface(d, x, ref_logd_at_x; kwargs...) + DensityInterface.test_density_interface(object, x, ref_logd_at_x; kwargs...) -Test if `d` is compatible with `DensityInterface`. +Test that `object` is compatible with `DensityInterface`. -Tests that [`logdensityof(d, x)`](@ref) equals `ref_logd_at_x` and -that the behavior of [`logdensityof(d)`](@ref), -[`densityof(d, x)`](@ref) and [`densityof(d)`](@ref) is consistent. +Tests that either `DensityKind(object) isa IsOrHasDensity`. -Also tests if `logfuncdensity(logdensityof(d))` returns -a density equivalent to `d` in respect to the functions above. +Also tests that [`logdensityof(object, x)`](@ref) equals `ref_logd_at_x` and +that the behavior of [`logdensityof(object)`](@ref), +[`densityof(object, x)`](@ref) and [`densityof(object)`](@ref) is consistent. -The results of `logdensityof(d, x)` and `densityof(d, x)` are compared to +The results of `logdensityof(object, x)` and `densityof(object, x)` are compared to `ref_logd_at_x` and `exp(ref_logd_at_x)` using `isapprox`. `kwargs...` are forwarded to `isapprox`. + +Also tests that `d = logfuncdensity(logdensityof(object))` returns a density +(`DensityKind(d) == IsDensity()`) that is equivalent to `object` in respect to +`logdensityof` and `densityof`, and that `funcdensity(densityof(object))` +behaves the same way. """ -function test_density_interface(d, x, ref_logd_at_x; kwargs...) - @testset "test_density_interface: $d with input $x" begin +function test_density_interface(object, x, ref_logd_at_x; kwargs...) + @testset "test_density_interface: $object with input $x" begin ref_d_at_x = exp(ref_logd_at_x) - @test hasdensity(d) == true - @test isapprox(logdensityof(d, x), ref_logd_at_x; kwargs...) - log_f = logdensityof(d) + @test DensityKind(object) isa IsOrHasDensity + + @test isapprox(logdensityof(object, x), ref_logd_at_x; kwargs...) + log_f = logdensityof(object) @test isapprox(log_f(x), ref_logd_at_x; kwargs...) - @test isapprox(densityof(d,x), ref_d_at_x; kwargs...) - @test isapprox(densityof(d)(x), ref_d_at_x; kwargs...) - - d2 = logfuncdensity(log_f) - @test hasdensity(d2) == true - @test isapprox(logdensityof(d2, x), ref_logd_at_x; kwargs...) - log_f2 = logdensityof(d2) - @test isapprox(log_f2(x), ref_logd_at_x; kwargs...) - @test isapprox(densityof(d2,x), ref_d_at_x; kwargs...) - @test isapprox(densityof(d2)(x), ref_d_at_x; kwargs...) + + @test isapprox(densityof(object,x), ref_d_at_x; kwargs...) + f = densityof(object) + @test isapprox(f(x), ref_d_at_x; kwargs...) + + for d in (logfuncdensity(log_f), funcdensity(f)) + @test DensityKind(d) == IsDensity() + @test isapprox(logdensityof(d, x), ref_logd_at_x; kwargs...) + @test isapprox(logdensityof(d)(x), ref_logd_at_x; kwargs...) + @test isapprox(densityof(d,x), ref_d_at_x; kwargs...) + @test isapprox(densityof(d)(x), ref_d_at_x; kwargs...) + end end end diff --git a/test/runtests.jl b/test/runtests.jl index 227145d..ff4f4c7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,8 +13,9 @@ Test.@testset "Package DensityInterface" begin :DocTestSetup, quote using DensityInterface - d = logfuncdensity(x -> x^2) - log_f = logdensityof(d) + object = logfuncdensity(x -> x^2) + log_f = logdensityof(object) + f = densityof(object) x = 4.2 end; recursive=true, diff --git a/test/test_interface.jl b/test/test_interface.jl index f408f49..8bc2264 100644 --- a/test/test_interface.jl +++ b/test/test_interface.jl @@ -5,24 +5,35 @@ using Test using LinearAlgebra, InverseFunctions - struct MyDensity end -@inline DensityInterface.hasdensity(::MyDensity) = true -DensityInterface.logdensityof(::MyDensity, x::Any) = norm(x)^2 +@inline DensityInterface.DensityKind(::MyDensity) = IsDensity() +DensityInterface.logdensityof(::MyDensity, x::Any) = -norm(x)^2 +struct MyMeasure end +@inline DensityInterface.DensityKind(::MyMeasure) = HasDensity() +DensityInterface.logdensityof(::MyMeasure, x::Any) = -norm(x)^2 @testset "interface" begin @test inverse(logdensityof) == logfuncdensity @test inverse(logfuncdensity) == logdensityof + @test inverse(densityof) == funcdensity + @test inverse(funcdensity) == densityof - @test @inferred(hasdensity("foo")) == false + @test @inferred(DensityKind("foo")) === NoDensity() @test_throws ArgumentError logdensityof("foo") + @test_throws ArgumentError densityof("foo") + + for object1 in (MyDensity(), MyMeasure()) + x = [1, 2, 3] + + DensityInterface.test_density_interface(object1, x, -norm(x)^2) - d1 = MyDensity() - x = [1, 2, 3] - DensityInterface.test_density_interface(d1, x, norm(x)^2) + object2 = logfuncdensity(x -> -norm(x)^2) + @test DensityKind(object2) === IsDensity() + DensityInterface.test_density_interface(object2, x, -norm(x)^2) - d2 = logfuncdensity(x -> norm(x)^2) - x = [1, 2, 3] - DensityInterface.test_density_interface(d2, x, norm(x)^2) + object3 = funcdensity(x -> exp(-norm(x)^2)) + @test DensityKind(object3) === IsDensity() + DensityInterface.test_density_interface(object3, x, -norm(x)^2) + end end