diff --git a/Project.toml b/Project.toml index e869850..4acd990 100644 --- a/Project.toml +++ b/Project.toml @@ -3,12 +3,26 @@ uuid = "b4d2a721-a957-46f4-b6b6-f139c9e7db21" version = "0.1.0" [deps] +ArraysOfArrays = "65a8f2f4-9b39-5baf-92e2-a9cc46fdf018" +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +ValueShapes = "136a8f8c-c49b-4edb-8b98-f3d64d48be8f" + [compat] -julia = "1" +ArraysOfArrays = "0.4, 0.5" +Distributions = "0.21, 0.22, 0.23, 0.24" +DocStringExtensions = "0.8" +StatsBase = "0.32, 0.33" +StructArrays = "0.4, 0.5" +ValueShapes = "0.8" +julia = "1.3" [extras] +StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["StructArrays", "Test"] diff --git a/README.md b/README.md index 9c91891..9891e84 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,11 @@ * [Documentation for stable version](https://bat.github.io/BATBase.jl/stable) * [Documentation for development version](https://bat.github.io/BATBase.jl/dev) + +**Note: Integration of BATBase.jl with BAT.jl is not complete yet.** + +BATBase.jl contains the core interface definition of [BAT.jl](https://github.com/bat/BAT.jl). + +BATBase.jl will serve as a lightweight and low-dependency package that packages +implementing BAT-compatible densities and algorithms can depend on instead +of the full BAT.jl. diff --git a/docs/src/index.md b/docs/src/index.md index db04b6e..a8ba302 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1 +1,9 @@ # BATBase.jl + +**Note: Integration of BATBase.jl with BAT.jl is not complete yet.** + +BATBase.jl contains the core interface definition of [BAT.jl](https://github.com/bat/BAT.jl). + +BATBase.jl will serve as a lightweight and low-dependency package that packages +implementing BAT-compatible densities and algorithms can depend on instead +of the full BAT.jl. diff --git a/src/BATBase.jl b/src/BATBase.jl index 620e42a..4f55a39 100644 --- a/src/BATBase.jl +++ b/src/BATBase.jl @@ -9,6 +9,15 @@ Template for Julia packages. """ module BATBase -include("hello_world.jl") +using Random + +using ArraysOfArrays +using Distributions +using DocStringExtensions +using StatsBase +using ValueShapes + +include("basetypes/basetypes.jl") +include("algotypes/algotypes.jl") end # module diff --git a/src/algotypes/algotypes.jl b/src/algotypes/algotypes.jl new file mode 100644 index 0000000..876124d --- /dev/null +++ b/src/algotypes/algotypes.jl @@ -0,0 +1,12 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +include("bat_default.jl") +include("initval_algorithm.jl") +include("transform_algorithm.jl") +include("sampling_algorithm.jl") +include("autocor_len.jl") +include("eff_sample_size.jl") +include("mode_estimator.jl") +include("median_estimator.jl") +include("differentiation_algorithm.jl") +include("integration_algorithm.jl") diff --git a/src/algotypes/autocor_len.jl b/src/algotypes/autocor_len.jl new file mode 100644 index 0000000..712a506 --- /dev/null +++ b/src/algotypes/autocor_len.jl @@ -0,0 +1,54 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type AutocorLenAlgorithm + +Abstract type for integrated autocorrelation length estimation algorithms. +""" +abstract type AutocorLenAlgorithm end +export AutocorLenAlgorithm + + + +""" + bat_integrated_autocorr_len( + v::Union{AbstractVector{<:Real},AbstractVectorOfSimilarVectors{<:Real}}, + algorithm::AutocorLenAlgorithm = GeyerAutocorLen() + ) + +Estimate the integrated autocorrelation length of variate series `v`, +separately for each degree of freedom. + +Returns a NamedTuple of the shape + +```julia +(result = integrated_autocorr_len, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_integrated_autocorr_len`, add methods to + `bat_integrated_autocorr_len_impl` instead. +""" +function bat_integrated_autocorr_len end +export bat_integrated_autocorr_len + +function bat_integrated_autocorr_len_impl end + + +function bat_integrated_autocorr_len( + v::Union{AbstractVector{<:Real},AbstractVectorOfSimilarVectors{<:Real}}, + algorithm = bat_default_withdebug(bat_integrated_autocorr_len, Val(:algorithm), v) +) + r = bat_integrated_autocorr_len_impl(v, algorithm) + result_with_args(r, (algorithm = algorithm,)) +end + + +function argchoice_msg(::typeof(bat_integrated_autocorr_len), ::Val{:algorithm}, x::AutocorLenAlgorithm) + "Using integrated autocorrelation length estimator $x" +end diff --git a/src/algotypes/bat_default.jl b/src/algotypes/bat_default.jl new file mode 100644 index 0000000..e005023 --- /dev/null +++ b/src/algotypes/bat_default.jl @@ -0,0 +1,60 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +""" + bat_default(f::Base.Callable, argname::Symbol, objective...) + bat_default(f::Base.Callable, argname::Val, objective...) + +Get the default value for argument `argname` of function `f` to use +for `objective`(s). + +`objective`(s) are mandatory arguments of function `f` that semantically +constitute it's main objective(s), and that that a good default choice of +optional arguments (e.g. choice of algorithm(s), etc.) may depend on. +Which arguments are considered to be objectives is function-specific. + +For example: + +```julia +bat_default(bat_sample, :algorithm, density::PosteriorDensity) == MetropolisHastings() +bat_default(bat_sample, Val(:algorithm), samples::DensitySampleVector) == OrderedResampling() +``` +""" +function bat_default end +export bat_default + +@inline bat_default(f::Base.Callable, argname::Symbol, objective...) = bat_default(f, Val{argname}(), objective...) + + + +""" + argchoice_msg(f::Base.Callable, argname::Val, x) + +Generates an information message regarding the choice of value `x` for +argument `argname` of function `f`. + +The value `x` will often be the result of [`bat_default`](@ref). +""" +function argchoice_msg end + + +function bat_default_withinfo(f::Base.Callable, argname::Val, objective...) + default = bat_default(f::Base.Callable, argname::Val, objective...) + @info argchoice_msg(f, argname::Val, default) + default +end + +function bat_default_withdebug(f::Base.Callable, argname::Val, objective...) + default = bat_default(f::Base.Callable, argname::Val, objective...) + @debug argchoice_msg(f, argname::Val, default) + default +end + + + +result_with_args(r::NamedTuple) = merge(r, (optargs = NamedTuple(), kwargs = NamedTuple())) + +result_with_args(r::NamedTuple, optargs::NamedTuple) = merge(r, (optargs = optargs, kwargs = NamedTuple())) + +result_with_args(r::NamedTuple, optargs::NamedTuple, kwargs::NamedTuple) = merge(r, (optargs = optargs, kwargs = kwargs)) + +result_with_args(r::NamedTuple, optargs::NamedTuple, kwargs::Base.Iterators.Pairs) = result_with_args(r, optargs, values(kwargs)) diff --git a/src/algotypes/differentiation_algorithm.jl b/src/algotypes/differentiation_algorithm.jl new file mode 100644 index 0000000..fa45070 --- /dev/null +++ b/src/algotypes/differentiation_algorithm.jl @@ -0,0 +1,76 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type DifferentiationAlgorithm + +*Experimental feature, not part of stable public API.* + +Abstract type for integrated autocorrelation length estimation algorithms. +""" +abstract type DifferentiationAlgorithm end +export DifferentiationAlgorithm + + + +""" + bat_valgrad(f::Function, [algorithm::DifferentiationAlgorithm]) + +*Experimental feature, not part of stable public API.* + +Generated a function that calculates both value and gradient of `f` at given +points. + +The function `f` must support `ValueShapes.varshape(f)` and +`ValueShapes.unshaped(f)`. + +Returns a NamedTuple of the shape + +```julia +(result = valgrad_f, ...) +``` + +with + +```julia +f_at_x, grad_of_f_at_x = valgrad_f(x) + +grad_of_f_at_x = zero(x) +f_at_x = valgrad_f(!, grad_of_f_at_x, x) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_valgrad`, add methods to + `bat_valgrad_impl` instead. +""" +function bat_valgrad end +export bat_valgrad + +function bat_valgrad_impl end + + +function bat_valgrad( + f::Function, + algorithm = bat_default_withdebug(bat_valgrad, Val(:algorithm), f) +) + r = bat_valgrad_impl(f, algorithm) + result_with_args(r, (algorithm = algorithm,)) +end + + +function argchoice_msg(::typeof(bat_valgrad), ::Val{:algorithm}, x::DifferentiationAlgorithm) + "Using automiatic differentiation algorithm $x" +end + + + +# ToDo, move to BAT.jl: +#= +gradient_shape(vs::AbstractValueShape) = replace_const_shapes(ValueShapes.const_zero_shape, vs) + +abstract type GradientFunction end +=# diff --git a/src/algotypes/eff_sample_size.jl b/src/algotypes/eff_sample_size.jl new file mode 100644 index 0000000..0c43abb --- /dev/null +++ b/src/algotypes/eff_sample_size.jl @@ -0,0 +1,69 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type EffSampleSizeAlgorithm + +Abstract type for integrated autocorrelation length estimation algorithms. +""" +abstract type EffSampleSizeAlgorithm end +export EffSampleSizeAlgorithm + + + +""" + bat_eff_sample_size( + v::Union{AbstractVector{<:Real},AbstractVectorOfSimilarVectors{<:Real}}, + [algorithm::EffSampleSizeAlgorithm] + ) + + bat_eff_sample_size( + smpls::DensitySampleVector, + [algorithm::EffSampleSizeAlgorithm] + ) + +Estimate effective sample size estimation for variate series `v`, resp. +density samples `smpls`, separately for each degree of freedom. + +Returns a NamedTuple of the shape + +```julia +(result = eff_sample_size, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_eff_sample_size`, add methods to + `bat_eff_sample_size_impl` instead. +""" +function bat_eff_sample_size end +export bat_eff_sample_size + +function bat_eff_sample_size_impl end + + +function bat_eff_sample_size( + v::Union{AbstractVector{<:Real},AbstractVectorOfSimilarVectors{<:Real}}, + algorithm = bat_default_withdebug(bat_eff_sample_size, Val(:algorithm), v) +) + r = bat_eff_sample_size_impl(v, algorithm) + result_with_args(r, (algorithm = algorithm,)) +end + + +function bat_eff_sample_size( + smpls::AbstractDensitySampleVector, + algorithm = bat_default_withdebug(bat_eff_sample_size, Val(:algorithm), smpls); + kwargs... +) + r = bat_eff_sample_size_impl(smpls, algorithm; kwargs...) + result_with_args(r, (algorithm = algorithm,)) +end + + +function argchoice_msg(::typeof(bat_eff_sample_size), ::Val{:algorithm}, x::EffSampleSizeAlgorithm) + "Using integrated autocorrelation length estimator $x" +end diff --git a/src/algotypes/initval_algorithm.jl b/src/algotypes/initval_algorithm.jl new file mode 100644 index 0000000..9e3b085 --- /dev/null +++ b/src/algotypes/initval_algorithm.jl @@ -0,0 +1,123 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +""" + abstract type BAT.InitvalAlgorithm + +Abstract type for BAT initial/starting value generation algorithms. + +Many algorithms in BAT, like MCMC and optimization, need initial/starting +values. +""" +abstract type InitvalAlgorithm end +export InitvalAlgorithm + + +""" + bat_initval( + [rng::AbstractRNG,] + target::BAT.AnyDensityLike, + [algorithm::BAT.InitvalAlgorithm], + )::V + + bat_initval( + [rng::AbstractRNG,] + target::BAT.AnyDensityLike, + n::Integer, + [algorithm::BAT.InitvalAlgorithm], + )::AbstractVector{<:V} + +Generate one or `n` random initial/starting value(s) suitable for +`target`. + +Assuming the variates of `target` are of type `T`, returns a NamedTuple of +the shape + +```julia +(result = X::AbstractVector{T}, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_initval`, add methods like + + ```julia + bat_initval_impl(rng::AbstractRNG, target::AnyDensityLike, algorithm::InitvalAlgorithm; kwargs...) + bat_initval_impl(rng::AbstractRNG, target::AnyDensityLike, n::Integer, algorithm::InitvalAlgorithm; kwargs...) + ``` + + to `bat_initval_impl` instead. +""" +function bat_initval end +export bat_initval + +function bat_initval_impl end + + +@inline function bat_initval(rng::AbstractRNG, target::AnyDensityLike, algorithm::InitvalAlgorithm; kwargs...) + r = bat_initval_impl(rng, target, algorithm; kwargs...) + result_with_args(r, (rng = rng, algorithm = algorithm), kwargs) +end + + +@inline function bat_initval(target::AnyDensityLike; kwargs...) + rng = bat_default_withinfo(bat_initval, Val(:rng), target) + algorithm = bat_default_withinfo(bat_initval, Val(:algorithm), target) + bat_initval(rng, target, algorithm; kwargs...) +end + + +@inline function bat_initval(target::AnyDensityLike, algorithm::InitvalAlgorithm; kwargs...) + rng = bat_default_withinfo(bat_initval, Val(:rng), target) + bat_initval(rng, target, algorithm; kwargs...) +end + + +@inline function bat_initval(rng::AbstractRNG, target::AnyDensityLike; kwargs...) + algorithm = bat_default_withinfo(bat_initval, Val(:algorithm), target) + bat_initval(rng, target, algorithm; kwargs...) +end + + +@inline function bat_initval(rng::AbstractRNG, target::AnyDensityLike, n::Integer, algorithm::InitvalAlgorithm; kwargs...) + r = bat_initval_impl(rng, target, n, algorithm; kwargs...) + result_with_args(r, (rng = rng, algorithm = algorithm), kwargs) +end + + +@inline function bat_initval(target::AnyDensityLike, n::Integer; kwargs...) + rng = bat_default_withinfo(bat_initval, Val(:rng), target) + algorithm = bat_default_withinfo(bat_initval, Val(:algorithm), target) + bat_initval(rng, target, n, algorithm; kwargs...) +end + + +@inline function bat_initval(target::AnyDensityLike, n::Integer, algorithm::InitvalAlgorithm; kwargs...) + rng = bat_default_withinfo(bat_initval, Val(:rng), target) + bat_initval(rng, target, n, algorithm; kwargs...) +end + + +@inline function bat_initval(rng::AbstractRNG, target::AnyDensityLike, n::Integer; kwargs...) + algorithm = bat_default_withinfo(bat_initval, Val(:algorithm), target) + bat_initval(rng, target, n, algorithm; kwargs...) +end + + +function argchoice_msg(::typeof(bat_initval), ::Val{:rng}, x::AbstractRNG) + "Initializing new RNG of type $(typeof(x))" +end + +function argchoice_msg(::typeof(bat_initval), ::Val{:algorithm}, x::InitvalAlgorithm) + "Using initval algorithm $x" +end + + +# ToDo, move to BAT.jl: +#= + +apply_trafo_to_init(trafo::AbstractVariateTransform, initalg::InitvalAlgorithm) = initalg + +=# diff --git a/src/algotypes/integration_algorithm.jl b/src/algotypes/integration_algorithm.jl new file mode 100644 index 0000000..a1cd9ae --- /dev/null +++ b/src/algotypes/integration_algorithm.jl @@ -0,0 +1,52 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type IntegrationAlgorithm + +Abstract type for integration algorithms. +""" +abstract type IntegrationAlgorithm end +export IntegrationAlgorithm + + +""" + bat_integrate( + target::AnySampleable, + [algorithm::IntegrationAlgorithm] + )::DensitySampleVector + +Calculate the integral (evidence) of `target`. + +Returns a NamedTuple of the shape + +```julia +(result = X::Measurements.Measurement, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_integrate`, add methods to + `bat_integrate_impl` instead. +""" +function bat_integrate end +export bat_integrate + +function bat_integrate_impl end + + +function bat_integrate( + target::AnySampleable, + algorithm::IntegrationAlgorithm = bat_default_withinfo(bat_integrate, Val(:algorithm), target) +) + r = bat_integrate_impl(target, algorithm) + result_with_args(r, (algorithm = algorithm,)) +end + + +function argchoice_msg(::typeof(bat_integrate), ::Val{:algorithm}, x::IntegrationAlgorithm) + "Using integration algorithm $x" +end diff --git a/src/algotypes/median_estimator.jl b/src/algotypes/median_estimator.jl new file mode 100644 index 0000000..71fbdec --- /dev/null +++ b/src/algotypes/median_estimator.jl @@ -0,0 +1,33 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +""" + bat_findmedian( + samples::DensitySampleVector + ) + +The function computes the median of marginalized `samples`. + +Returns a NamedTuple of the shape + +```julia +(result = v, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_findmedian`, add methods to + `bat_findmedian_impl` instead. +""" +function bat_findmedian end +export bat_findmedian + +function bat_findmedian_impl end + + +function bat_findmedian(samples::AbstractDensitySampleVector) + r = bat_findmedian_impl(samples) + result_with_args(r) +end diff --git a/src/algotypes/mode_estimator.jl b/src/algotypes/mode_estimator.jl new file mode 100644 index 0000000..c16c3a0 --- /dev/null +++ b/src/algotypes/mode_estimator.jl @@ -0,0 +1,114 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +""" + abstract type BAT.AbstractModeEstimator + +Abstract type for BAT optimization algorithms. + +A typical application for optimization in BAT is mode estimation +(see [`bat_findmode`](@ref)), +""" +abstract type AbstractModeEstimator end + + +""" + bat_findmode( + target::BAT.AnySampleable, + [algorithm::BAT.AbstractModeEstimator] + )::DensitySampleVector + +Estimate the global mode of `target`. + +Returns a NamedTuple of the shape + +```julia +(result = X::DensitySampleVector, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_findmode`, add methods to + `bat_findmode_impl` instead. +""" +function bat_findmode end +export bat_findmode + +function bat_findmode_impl end + + +function bat_findmode( + target::AnySampleable, + algorithm = bat_default_withdebug(bat_findmode, Val(:algorithm), target); + kwargs... +) + r = bat_findmode_impl(target, algorithm; kwargs...) + result_with_args(r, (algorithm = algorithm,)) +end + + +function argchoice_msg(::typeof(bat_findmode), ::Val{:algorithm}, x::AbstractModeEstimator) + "Using mode estimator $x" +end + + + +# ToDo, move to BAT.jl or add new algo type: +#= + +""" + bat_marginalmode( + samples::DensitySampleVector; + nbins::Union{Integer, Symbol} = 200 + )::DensitySampleVector + +*Experimental feature, not part of stable public API.* + +Estimates a marginal mode of `samples` by finding the maximum of marginalized posterior for each dimension. + +Returns a NamedTuple of the shape + +```julia +(result = X::DensitySampleVector, ...) +``` + +`nbins` specifies the number of bins that are used for marginalization. The default value is `nbins=200`. The optimal number of bins can be estimated using the following keywords: + +* `:sqrt` — Square-root choice + +* `:sturges` — Sturges' formula + +* `:rice` — Rice Rule + +* `:scott` — Scott's normal reference rule + +* `:fd` — Freedman–Diaconis rule + +Returns a NamedTuple of the shape + +```julia +(result = X::DensitySampleVector, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_marginalmode`, add methods to + `bat_marginalmode_impl` instead. +""" +function bat_marginalmode end +export bat_marginalmode + +function bat_marginalmode_impl end + + +function bat_marginalmode(samples::DensitySampleVector; kwargs...) + r = bat_marginalmode_impl(samples::DensitySampleVector; kwargs...) + result_with_args(r, NamedTuple(), kwargs) +end + +=# diff --git a/src/algotypes/sampling_algorithm.jl b/src/algotypes/sampling_algorithm.jl new file mode 100644 index 0000000..92066d7 --- /dev/null +++ b/src/algotypes/sampling_algorithm.jl @@ -0,0 +1,87 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type BAT.AbstractSamplingAlgorithm + +Abstract type for BAT sampling algorithms. See [`bat_sample`](@ref). +""" +abstract type AbstractSamplingAlgorithm end + + +""" + bat_sample( + [rng::AbstractRNG], + target::BAT.AnySampleable, + [algorithm::BAT.AbstractSamplingAlgorithm] + )::DensitySampleVector + +Draw samples from `target` using `algorithm`. + +Depending on sampling algorithm, the samples may be independent or correlated +(e.g. when using MCMC). + +Returns a NamedTuple of the shape + +```julia +(result = X::DensitySampleVector, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_sample`, add methods to + `bat_sample_impl` instead. +""" +function bat_sample end +export bat_sample + +function bat_sample_impl end + + +@inline function bat_sample(rng::AbstractRNG, target::AnySampleable, algorithm::AbstractSamplingAlgorithm; kwargs...) + r = bat_sample_impl(rng, target, algorithm; kwargs...) + result_with_args(r, (rng = rng, algorithm = algorithm), kwargs) +end + + +@inline function bat_sample(target::AnySampleable; kwargs...) + rng = bat_default_withinfo(bat_sample, Val(:rng), target) + algorithm = bat_default_withinfo(bat_sample, Val(:algorithm), target) + bat_sample(rng, target, algorithm; kwargs...) +end + + +@inline function bat_sample(target::AnySampleable, algorithm::AbstractSamplingAlgorithm; kwargs...) + rng = bat_default_withinfo(bat_sample, Val(:rng), target) + bat_sample(rng, target, algorithm; kwargs...) +end + + +@inline function bat_sample(rng::AbstractRNG, target::AnySampleable; kwargs...) + algorithm = bat_default_withinfo(bat_sample, Val(:algorithm), target) + bat_sample(rng, target, algorithm; kwargs...) +end + + +function argchoice_msg(::typeof(bat_sample), ::Val{:rng}, x::AbstractRNG) + "Initializing new RNG of type $(typeof(x))" +end + +function argchoice_msg(::typeof(bat_sample), ::Val{:algorithm}, x::AbstractSamplingAlgorithm) + "Using sampling algorithm $x" +end + + + +""" + abstract type AbstractSampleGenerator + +*BAT-internal, not part of stable public API.* + +Abstract super type for sample generators. +""" +abstract type AbstractSampleGenerator end +export AbstractSampleGenerator diff --git a/src/algotypes/transform_algorithm.jl b/src/algotypes/transform_algorithm.jl new file mode 100644 index 0000000..56a606f --- /dev/null +++ b/src/algotypes/transform_algorithm.jl @@ -0,0 +1,253 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type AbstractDensityTransformTarget + +Abstract type for probability density transformation targets. +""" +abstract type AbstractDensityTransformTarget end +export AbstractDensityTransformTarget + + + +""" + abstract type AbstractTransformToUnitspace <: AbstractDensityTransformTarget + +Abstract type for density transformation targets that specify a +transformation into the unit hypercube. +""" +abstract type AbstractTransformToUnitspace <: AbstractDensityTransformTarget end +export AbstractTransformToUnitspace + + +""" + abstract type AbstractTransformToInfinite <: AbstractDensityTransformTarget + +Abstract type for density transformation targets that specify are +transformation into unbounded space. + +The density that results from such a transformation must be unbounded in all +dimensions and that should taper off to zero somewhat smoothly in any +direction. +""" +abstract type AbstractTransformToInfinite <: AbstractDensityTransformTarget end +export AbstractTransformToInfinite + + +""" + abstract type TransformAlgorithm + +Abstract type for density transformation algorithms. +""" +abstract type TransformAlgorithm end +export TransformAlgorithm + + +""" + bat_transform( + target::AbstractDensityTransformTarget, + density::AnyDensityLike, + [algorithm::TransformAlgorithm] + )::AbstractDensity + +Transform `density` to another variate space defined/implied by `target`. + +Returns a NamedTuple of the shape + +```julia +(result = newdensity::AbstractDensity, trafo = vartrafo::AbstractVariateTransform, ...) +``` + +Result properties not listed here are algorithm-specific and are not part +of the stable public API. + +!!! note + + Do not add add methods to `bat_transform`, add methods to + `bat_transform_impl` instead. +""" +function bat_transform end +export bat_transform + +function bat_transform_impl end + + +function bat_transform( + target::AbstractDensityTransformTarget, + density::AnyDensityLike, + algorithm::TransformAlgorithm = bat_default_withinfo(bat_transform, Val(:algorithm), target, density) +) + r = bat_transform_impl(target, density, algorithm) + result_with_args(r, (algorithm = algorithm,)) +end + + +function argchoice_msg(::typeof(bat_transform), ::Val{:algorithm}, x::TransformAlgorithm) + "Using transform algorithm $x" +end + + + +""" + struct NoDensityTransform <: AbstractDensityTransformTarget + +The identity density transformation target, specifies that densities +should not be transformed. + +Constructors: + +* ```$(FUNCTIONNAME)()``` +""" +struct NoDensityTransform <: AbstractDensityTransformTarget end +export NoDensityTransform + + + +""" + struct DensityIdentityTransform <: TransformAlgorithm + +A no-op density transform algorithm that leaves any density unchanged. + +Constructors: + +* ```$(FUNCTIONNAME)()``` +""" +struct DensityIdentityTransform <: TransformAlgorithm end +export DensityIdentityTransform + +# ToDo, move to BAT.jl: +#= + +function bat_transform_impl(target::NoDensityTransform, density::AnyDensityLike, algorithm::DensityIdentityTransform) + (result = convert(AbstractDensity, density), trafo = IdentityVT(varshape(density))) +end + +=# + + +""" + struct PriorToUniform <: AbstractTransformToUnitspace + +Specifies that posterior densities should be transformed in a way that makes +their pior equivalent to a uniform distribution over the unit hypercube. + +Constructors: + +* ```$(FUNCTIONNAME)()``` +""" +struct PriorToUniform <: AbstractTransformToUnitspace end +export PriorToUniform + + +# ToDo, move to BAT.jl: +#= +function bat_transform_impl(target::PriorToUniform, density::StandardUniformDensity, algorithm::DensityIdentityTransform) + (result = density, trafo = IdentityVT(varshape(density))) +end + +_distribution_density_trafo(target::PriorToUniform, density::DistributionDensity) = DistributionTransform(Uniform, parent(density)) +=# + + + +""" + struct PriorToGaussian <: AbstractTransformToInfinite + +Specifies that posterior densities should be transformed in a way that makes +their pior equivalent to a standard multivariate normal distribution with an +identity covariance matrix. + +Constructors: + +* ```$(FUNCTIONNAME)()``` +""" +struct PriorToGaussian <: AbstractTransformToInfinite end +export PriorToGaussian + + +# ToDo, move to BAT.jl: +#= +function bat_transform_impl(target::PriorToGaussian, density::StandardNormalDensity, algorithm::DensityIdentityTransform) + (result = density, trafo = IdentityVT(varshape(density))) +end + +_distribution_density_trafo(target::PriorToGaussian, density::DistributionDensity) = DistributionTransform(Normal, parent(density)) +=# + + + +""" + struct FullDensityTransform <: TransformAlgorithm + +Transform the density as a whole a given specified target space. Operations +that use the gradient of the density will require to the `log(abs(jacobian))` +of the transformation to be auto-differentiable. + +Constructors: + +* ```$(FUNCTIONNAME)()``` +""" +struct FullDensityTransform <: TransformAlgorithm end +export FullDensityTransform + + + +# ToDo, move to BAT.jl: +#= + +_get_deep_prior_for_trafo(density::AbstractPosteriorDensity) = _get_deep_prior_for_trafo(getprior(density)) +_get_deep_prior_for_trafo(density::DistributionDensity) = density + +function bat_transform_impl(target::Union{PriorToUniform,PriorToGaussian}, density::AbstractPosteriorDensity, algorithm::FullDensityTransform) + orig_prior = _get_deep_prior_for_trafo(density) + trafo = _distribution_density_trafo(target, orig_prior) + (result = TransformedDensity(density, trafo, TDLADJCorr()), trafo = trafo) +end + + +function bat_transform_impl(target::Union{PriorToUniform,PriorToGaussian}, density::DistributionDensity, algorithm::FullDensityTransform) + trafo = _distribution_density_trafo(target, density) + (result = TransformedDensity(density, trafo, TDLADJCorr()), trafo = trafo) +end + +=# + + + +""" + struct PriorSubstitution <: TransformAlgorithm + +Substitute the prior by a given distribution and transform the +likelihood accordingly. The `log(abs(jacobian))` of the transformation does +not need to be auto-differentiable even for operations that use the +gradient of the posterior. + +Constructors: + +* ```$(FUNCTIONNAME)()``` +""" +struct PriorSubstitution <: TransformAlgorithm end +export PriorSubstitution + + +# ToDo, move to BAT.jl: +#= + + +function bat_transform_impl(target::Union{PriorToUniform,PriorToGaussian}, density::AbstractPosteriorDensity, algorithm::PriorSubstitution) + orig_prior = getprior(density) + orig_likelihood = getlikelihood(density) + new_prior, trafo = bat_transform_impl(target, orig_prior, algorithm) + new_likelihood = TransformedDensity(orig_likelihood, trafo, TDNoCorr()) + (result = PosteriorDensity(new_likelihood, new_prior), trafo = trafo) +end + + +function bat_transform_impl(target::Union{PriorToUniform,PriorToGaussian}, density::DistributionDensity, algorithm::PriorSubstitution) + trafo = _distribution_density_trafo(target, density) + transformed_density = DistributionDensity(trafo.target_dist) + (result = transformed_density, trafo = trafo) +end + +=# diff --git a/src/basetypes/abstract_density.jl b/src/basetypes/abstract_density.jl new file mode 100644 index 0000000..7cd0335 --- /dev/null +++ b/src/basetypes/abstract_density.jl @@ -0,0 +1,269 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type AbstractDensity + +Subtypes of `AbstractDensity` (e.g. `MyDensity`) must implement the methods + +* `BATBase.eval_logval_unchecked(density::MyDensity)` + +To query the logarithm of the density for a given point/variate `v`, use +the function [`logdensityof`](@ref). + +For likelihood densities, implementing `BATBase.eval_logval_unchecked is +typically sufficient, since shape, and variate bounds can be inferred from +the prior. + +Densities with a known variate shape may also implement + +* `ValueShapes.varshape(density::MyDensity)` + +Densities with known variate bounds may also implement + +* `BAT.var_bounds(density::MyDensity)` + +!!! note + + Only implement `BAT.var_bounds` in special cases. `BAT.var_bounds` is not + part of the stable public BATBase.jl and BAT.jl APIs and subject to change + without deprecation. +""" +abstract type AbstractDensity end +export AbstractDensity + +Base.convert(::Type{AbstractDensity}, d::ContinuousDistribution) = convert(AbstractDistributionDensity, d) + + +""" + BATBase.eval_logval_unchecked(density::AbstractDensity, v::Any) + +Compute log of the value of a multivariate density function for the given +variate/parameter-values. + +Input: + +* `density`: density function +* `v`: argument, i.e. variate / parameter-values + +Note: If `eval_logval_unchecked` is called with an argument that is out of bounds, +the behavior is undefined. The result for arguments that are not within +bounds is *implicitly* `-Inf`, but it is the caller's responsibility to handle +these cases. +""" +function eval_logval_unchecked end + + +""" + ValueShapes.totalndof(density::AbstractDensity)::Union{Int,Missing} + +Get the number of degrees of freedom of the variates of `density`. May return +`missing`, if the shape of the variates is not fixed. +""" +@inline function ValueShapes.totalndof(density::AbstractDensity) + shape = varshape(density) + ismissing(shape) ? missing : ValueShapes.totalndof(shape) +end + + +""" + ValueShapes.varshape( + density::AbstractDensity + )::Union{ValueShapes.AbstractValueShape,Missing} + + ValueShapes.varshape( + density::DistLikeDensity + )::ValueShapes.AbstractValueShape + +Get the shapes of the variates of `density`. + +For prior densities, the result must not be `missing`, but may be `nothing` if +the prior only supports unshaped variate/parameter vectors. +""" +ValueShapes.varshape(density::AbstractDensity) = missing + + + +""" + logdensityof(density::AbstractDensity, v)::Real + logdensityof(density::AbstractDensity)::Function + +Computes the logarithmic value of `density` at a given point, resp. returns a +function that does so: + +```julia +logdensityof(density, v) == logdensityof(density)(v) +``` + +The function returned by `logdensityof(density)` supports + +```julia +(- logdensityof(density))(v) == - logdensityof(density)(v) +``` + +Note: This function should *not* be specialized for custom density types, +implement [`BATBase.eval_logval_unchecked`](@ref) instead. + +!!! note + + This functionality is implemented in BAT.jl, not in BATBase.jl. +""" +function logdensityof end +export logdensityof + + +# Implemented in BAT.jl: +# logdensityof(density::AbstractDensity, v::Any) = ... +# logdensityof(density::AbstractDensity) = ... + + +# ToDo, move to BAT.jl: +#= + +logdensityof(density::AbstractDensity, v::Any) = eval_logval(density, v, default_dlt()) + +logdensityof(density::AbstractDensity) = LogDensityOf(density) + + +struct LogDensityOf{D<:AbstractDensity} <: Function + density::D +end + +@inline (lvd::LogDensityOf)(v::Any) = logdensityof(lvd.density, v) + +(Base.:-)(lvd::LogDensityOf) = NegLogDensityOf(lvd.density) + +ValueShapes.varshape(lvd::LogDensityOf) = varshape(lvd.density) +ValueShapes.unshaped(lvd::LogDensityOf) = LogDensityOf(unshaped(lvd.density)) + + +struct NegLogDensityOf{D<:AbstractDensity} <: Function + density::D +end + +@inline (lvd::NegLogDensityOf)(v::Any) = - logdensityof(lvd.density, v) + +(Base.:-)(lvd::NegLogDensityOf) = LogDensityOf(lvd.density) + +ValueShapes.varshape(lvd::NegLogDensityOf) = varshape(lvd.density) +ValueShapes.unshaped(lvd::NegLogDensityOf) = NegLogDensityOf(unshaped(lvd.density)) +=# + + + +""" + abstract type DistLikeDensity <: AbstractDensity + +A density that implements part of the `Distributions.Distribution` interface. +Such densities are suitable for use as a priors. + +In contrast to a distribution, the integral of a `DistLikeDensity` is not +required to be normalized to one (but will be, in many cases). + +Typically, custom priors should be implemented as subtypes of +`Distributions.Distribution`. BAT will automatically wrap them in a subtype of +[`AbstractDistributionDensity`](@ref), which is a subtype of +`DistLikeDensity`. + +Subtypes of `DistLikeDensity` are required to support more functionality +than an [`AbstractDensity`](@ref), but less than a +`Distribution{Multivariate,Continuous}`. + +A `d::ContinuousDistribution` can be converted into (wrapped +in) an suitable subtype of [`AbstractDistributionDensity`](@ref) via +`conv(DistLikeDensity, d)`. + +The following functions must be implemented for subtypes (e.g. +`MyDistLikeDensity`), in addition to the mandatory [`AbstractDensity`](@ref) +interface: + +* `ValueShapes.varshape(density::MyDistLikeDensity)` + +* `Distributions.sampler(density::MyDistLikeDensity)` + +!!! note + + The function `BAT.var_bounds` is not part of the stable public BATBase.jl + and BAT.jl APIs and subject to change without deprecation. +""" +abstract type DistLikeDensity <: AbstractDensity end +export DistLikeDensity + +Base.convert(::Type{DistLikeDensity}, d::ContinuousDistribution) = convert(AbstractDistributionDensity, d) + + + +""" + abstract type AbstractDistributionDensity <: DistLikeDensity + +A density that can be converted efficiently to a `Distributions.Distribution`. + +Subtypes (e.g. `MyDistributionDensity`) support, in addition to the +[`DistLikeDensity`](@ref) interface, support: + +* `Base.convert(Distributions.Distribution, density::MyDistributionDensity)` + +A `d::ContinuousDistribution` can be converted into (resp. wrapped in) an +suitable subtype of `DistLikeDensity` via `conv(AbstractDistributionDensity, d)`. + +!!! note + + This functionality is implemented in BAT.jl, not in BATBase.jl. +""" +abstract type AbstractDistributionDensity <: DistLikeDensity end +export AbstractDistributionDensity + +# Implemented in BAT.jl: +# Base.convert(::Type{AbstractDistributionDensity}, d::ContinuousDistribution) = ... + + + +""" + BAT.AnyDensityLike = Union{...} + +Union of all types that BAT will accept as a probability density, resp. that +`convert(AbstractDensity, d)` supports: + +* [`AbstractDensity`](@ref) +* `Distributions.Distribution` +""" +const AnyDensityLike = Union{ + AbstractDensity, + Distributions.ContinuousDistribution +} +export AnyDensityLike + + + +""" + BAT.AnySampleable = Union{...} + +Union of all types that BAT can sample from: + +* [`AbstractDensity`](@ref) +* [`AbstractDensitySampleVector`](@ref) +* `Distributions.Distribution` +""" +const AnySampleable = Union{ + AbstractDensity, + AbstractDensitySampleVector, + Distributions.Distribution +} +export AnySampleable + + + +""" + BAT.AnyIIDSampleable = Union{...} + +Union of all distribution/density-like types that BAT can draw i.i.d. +(independent and identically distributed) samples from: + +* [`DistLikeDensity`](@ref) +* `Distributions.Distribution` +""" +const AnyIIDSampleable = Union{ + DistLikeDensity, + Distributions.Distribution +} +export AnyIIDSampleable diff --git a/src/basetypes/abstract_density_sample.jl b/src/basetypes/abstract_density_sample.jl new file mode 100644 index 0000000..670f22a --- /dev/null +++ b/src/basetypes/abstract_density_sample.jl @@ -0,0 +1,55 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +""" + abstract type AbstractDensitySample + +Abstract type for samples drawn from an [`AbstractDensity`](@ref). + +Subtypes (e.g. `MyDensitySample`) must support: + +* `Base.getproperty(s::MyDensitySample, :v)::Any`: Variate value. + +* `Base.getproperty(s::MyDensitySample, :logd)::Real`: Result of + `logdensityof(some_density)(v)` + +* `Base.getproperty(s::MyDensitySample, :weight)::Real`: Weight of the sample. + +* `Base.getproperty(s::MyDensitySample, :info)::Any`: Additional info on the + provenance of the sample. Content depends on the sampling algorithm. + +* `Base.getproperty(s::MyDensitySample, :aux)::Any`: Custom user-defined + information attached to the sample. +""" +abstract type AbstractDensitySample end +export AbstractDensitySample + +# ToDo: Document and mock-test +# * `AbstractDensitySample(v = ..., logd = ..., weight = ..., info = ..., aux = ...)` +# * `density_sample_type(P, T, W, R, Q)` +# Actual implementation will be in BAT.jl. + + +""" + abstract type AbstractDensitySampleVector <: AbstractVector{<:AbstractDensitySample} + +Abstract type for multiple samples drawn from an [`AbstractDensity`](@ref). + +Subtypes must support the [Tables.jl](https://github.com/JuliaData/Tables.jl) +API with access both as a vector of structs and a struct of vectors, and + +* `Base.getproperty(s::MyDensitySample, :v)::AbstractVector{<:Any}` +* `Base.getproperty(s::MyDensitySample, :logd)::AbstractVector{<:Real}` +* `Base.getproperty(s::MyDensitySample, :weight)::AbstractVector{<:Real}` +* `Base.getproperty(s::MyDensitySample, :info)::AbstractVector{<:Any}` +* `Base.getproperty(s::MyDensitySample, :aux)::AbstractVector{<:Any}` + +See [`AbstractDensitySample`](@ref) for the semantics of the columns. +""" +abstract type AbstractDensitySampleVector end +export AbstractDensitySampleVector + +# ToDo: Document and mock-test +# * `AbstractDensitySampleVector(v = ..., logd = ..., weight = ..., info = ..., aux = ...)` +# * `density_sample_vector_type(PV, TV, WV, RV, QV)` +# Actual implementation will be in BAT.jl. diff --git a/src/basetypes/basetypes.jl b/src/basetypes/basetypes.jl new file mode 100644 index 0000000..5f0ecd0 --- /dev/null +++ b/src/basetypes/basetypes.jl @@ -0,0 +1,5 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + + +include("abstract_density_sample.jl") +include("abstract_density.jl") diff --git a/src/hello_world.jl b/src/hello_world.jl deleted file mode 100644 index 3449fcb..0000000 --- a/src/hello_world.jl +++ /dev/null @@ -1,23 +0,0 @@ -# This file is a part of BATBase.jl, licensed under the MIT License (MIT). - - -""" - BATBase.hello_world() - -Prints "Hello, World!" and returns 42. - -```jldoctest -using BATBase - -BATBase.hello_world() - -# output - -Hello, World! -42 -``` -""" -function hello_world() - println("Hello, World!") - return 42 -end diff --git a/test/basetypes/test_abstract_density.jl b/test/basetypes/test_abstract_density.jl new file mode 100644 index 0000000..0d08881 --- /dev/null +++ b/test/basetypes/test_abstract_density.jl @@ -0,0 +1,31 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +using BATBase +using Test + +using Distributions + + +@testset "abstract_density" begin + dist = MvNormal(float([1, 2, 3])) + + @test @inferred(MyMvDistributionDensity(dist)) isa AbstractDistributionDensity + @test @inferred(MyMvDistributionDensity(dist)) isa DistLikeDensity + @test @inferred(MyMvDistributionDensity(dist)) isa AbstractDensity + + density = MyMvDistributionDensity(dist) + v = rand(dist) + + @test @inferred(convert(Distribution, density)) == dist + @test @inferred BATBase.eval_logval_unchecked(density, v) == logpdf(dist, v) + @test @inferred(varshape(density)) == ArrayShape{Real}(3) + @test @inferred(totalndof(density)) == 3 + @test @inferred(sampler(density)) == sampler(dist) + + @test @inferred(convert(AbstractDistributionDensity, dist)) == density + @test @inferred(convert(DistLikeDensity, dist)) == density + @test @inferred(convert(AbstractDensity, dist)) == density + + @test @inferred(logdensityof(density, v)) == logpdf(dist, v) + @test @inferred(logdensityof(density)(v)) == logpdf(dist, v) +end diff --git a/test/basetypes/test_basetypes.jl b/test/basetypes/test_basetypes.jl new file mode 100644 index 0000000..a59feee --- /dev/null +++ b/test/basetypes/test_basetypes.jl @@ -0,0 +1,3 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +include("test_abstract_density.jl") diff --git a/test/my_mv_distribution_density.jl b/test/my_mv_distribution_density.jl new file mode 100644 index 0000000..af739e6 --- /dev/null +++ b/test/my_mv_distribution_density.jl @@ -0,0 +1,30 @@ +# This file is a part of BATBase.jl, licensed under the MIT License (MIT). + +using BATBase + +using Distributions, ValueShapes + + +struct MyMvDistributionDensity{D<:Distribution{Multivariate,Continuous}} <: AbstractDistributionDensity + d::D +end + +Base.convert(::Type{Distribution}, density::MyMvDistributionDensity) = density.d + +BATBase.eval_logval_unchecked(density::MyMvDistributionDensity, v::AbstractVector{<:Real}) = + logpdf(convert(Distribution, density), v) + +ValueShapes.varshape(density::MyMvDistributionDensity) = varshape(density.d) + +Distributions.sampler(density::MyMvDistributionDensity) = sampler(convert(Distribution, density)) + +# Note: Only for testing. Normally implemented generically in BAT.jl, not in BATBase.jl: +Base.convert(::Type{AbstractDistributionDensity}, d::Distribution{Multivariate,Continuous}) = + MyMvDistributionDensity(d) + +# Note: Only for testing. Normally implemented generically in BAT.jl, not in BATBase.jl: +BATBase.logdensityof(density::MyMvDistributionDensity, v::AbstractVector{<:Real}) = + BATBase.eval_logval_unchecked(density, v) + +# Note: Only for testing. Normally implemented generically in BAT.jl, not in BATBase.jl: +BATBase.logdensityof(density::MyMvDistributionDensity) = v -> logdensityof(density, v) diff --git a/test/runtests.jl b/test/runtests.jl index 90fe337..7d36b2f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,5 +3,6 @@ import Test Test.@testset "Package BATBase" begin - include("test_hello_world.jl") + include("my_mv_distribution_density.jl") + include("basetypes/test_basetypes.jl") end # testset diff --git a/test/test_algotypes.jl b/test/test_algotypes.jl new file mode 100644 index 0000000..e69de29