diff --git a/Project.toml b/Project.toml index e5db2787..1c10d260 100644 --- a/Project.toml +++ b/Project.toml @@ -28,6 +28,8 @@ SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" Static = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +StructArrays = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Tricks = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775" @@ -39,7 +41,7 @@ MeasureBaseChainRulesCoreExt = "ChainRulesCore" [compat] ChainRulesCore = "1" -ChangesOfVariables = "0.1.3" +ChangesOfVariables = "0.1" Compat = "3.35, 4" ConstantRNGs = "0.1.1" ConstructionBase = "1.3" @@ -56,13 +58,15 @@ LogarithmicNumbers = "1" MappedArrays = "0.4" NaNMath = "0.3, 1" PrettyPrinting = "0.3, 0.4" -PropertyFunctions = "0.2.2" +PropertyFunctions = "0.2" Random = "1" Reexport = "1" SpecialFunctions = "2" Static = "0.8, 1" StaticArrays = "1.5" Statistics = "1" +StatsBase = "0.34" +StructArrays = "0.7" Test = "1" Tricks = "0.1" julia = "1.10" diff --git a/src/MeasureBase.jl b/src/MeasureBase.jl index 2bad7d92..e2dcaa47 100644 --- a/src/MeasureBase.jl +++ b/src/MeasureBase.jl @@ -118,7 +118,6 @@ include("smf.jl") include("getdof.jl") include("transport.jl") include("schema.jl") -include("splat.jl") include("proxies.jl") include("kernel.jl") include("parameterized.jl") diff --git a/src/combinators/conditional.jl b/src/combinators/conditional.jl index a329b90f..42025261 100644 --- a/src/combinators/conditional.jl +++ b/src/combinators/conditional.jl @@ -37,7 +37,7 @@ condition(μ, constraint) = ConditionalMeasure(μ, constraint) # end # end -function Base.:|( +function condition( μ::ProductMeasure{NamedTuple{M,T}}, constraint::NamedTuple{N}, ) where {M,T,N} diff --git a/src/combinators/smart-constructors.jl b/src/combinators/smart-constructors.jl index 26ba3948..6bc31b10 100644 --- a/src/combinators/smart-constructors.jl +++ b/src/combinators/smart-constructors.jl @@ -56,7 +56,7 @@ end productmeasure(nt::NamedTuple) = ProductMeasure(nt) productmeasure(tup::Tuple) = ProductMeasure(tup) -productmeasure(f, param_maps, pars) = ProductMeasure(kernel(f, param_maps), pars) +productmeasure(f, param_maps, pars) = productmeasure(kernel(f, param_maps), pars) function productmeasure(k::ParameterizedTransitionKernel, pars) productmeasure(k.suff, k.param_maps, pars) diff --git a/src/combinators/superpose.jl b/src/combinators/superpose.jl index 099ee806..9cea3c8a 100644 --- a/src/combinators/superpose.jl +++ b/src/combinators/superpose.jl @@ -135,7 +135,18 @@ end function basemeasure(μ::SuperpositionMeasure{Tuple{A,B}}) where {A,B} superpose(map(basemeasure, μ.components)...) end -basemeasure(μ::SuperpositionMeasure) = superpose(map(basemeasure, μ.components)) + +# basemeasure(μ::SuperpositionMeasure) = superpose(map(basemeasure, μ.components)) + +function basemeasure(μ::SuperpositionMeasure{NTuple{N, M}}) where {N, M<:AbstractMeasure} + rootmeasure(first(μ.components)) +end + +function logdensity_def(μ::SuperpositionMeasure{NTuple{N, M}}, x) where {N, M<:AbstractMeasure} + ℓs = (logdensityof(c, x) for c in μ.components) + logsumexp(ℓs) +end + # TODO: Fix `rand` method (this one is wrong) # function Base.rand(μ::SuperpositionMeasure{X,N}) where {X,N} diff --git a/src/density-core.jl b/src/density-core.jl index 6ac3d01e..8dc9d182 100644 --- a/src/density-core.jl +++ b/src/density-core.jl @@ -30,7 +30,8 @@ To compute a log-density relative to a specific base-measure, see """ @inline function logdensityof(μ::AbstractMeasure, x) result = dynamic(unsafe_logdensityof(μ, x)) - _checksupport(insupport(μ, x), result) + # _checksupport(insupport(μ, x), result) + result end @inline function logdensityof_rt(::T, ::U) where {T,U} @@ -125,7 +126,7 @@ See also `logdensity_rel`. end # Note that this method assumes `μ` and `ν` to have the same type -function logdensity_def(μ::T, ν::T, x) where {T} +function logdensity_def(μ::T, ν::T, x) where {T<:AbstractMeasure} if μ === ν return zero(logdensity_def(μ, x)) else @@ -167,6 +168,5 @@ end @inline density_rel(μ, ν, x) = exp(logdensity_rel(μ, ν, x)) -# TODO: Do we need this method? -density_def(μ, ν::AbstractMeasure, x) = exp(logdensity_def(μ, ν, x)) -density_def(μ, x) = exp(logdensity_def(μ, x)) +density_def(μ::AbstractMeasure, ν::AbstractMeasure, x) = exp(logdensity_def(μ, ν, x)) +density_def(μ::AbstractMeasure, x) = exp(logdensity_def(μ, x)) diff --git a/src/domains.jl b/src/domains.jl index e03f753c..48cf79ab 100644 --- a/src/domains.jl +++ b/src/domains.jl @@ -80,7 +80,9 @@ struct ZeroSet{F,G} <: AbstractDomain end # Based on some quick tests, but may need some adjustment -Base.in(x::AbstractArray{T}, z::ZeroSet) where {T} = abs(z.f(x)) < ldexp(eps(float(T)), 6) +function Base.in(x::AbstractArray{T}, z::ZeroSet) where {T<:Real} + abs(z.f(x)) < ldexp(eps(float(T)), 6) +end ########################################################### # CodimOne diff --git a/src/interface.jl b/src/interface.jl index 18080ac7..a93b8adc 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -41,9 +41,10 @@ function dynamic_basemeasure_depth(μ::M) where {M} return depth end -function test_interface(μ::M) where {M} +function test_interface(μ::M, f = identity) where {M} @eval begin μ = $μ + f = $f @testset "$μ" begin μ = $μ @@ -64,6 +65,7 @@ function test_interface(μ::M) where {M} # testvalue, logdensityof x = @inferred testvalue(Float64, μ) + x = f(x) β = @inferred basemeasure(μ, x) ℓμ = @inferred logdensityof(μ, x) @@ -71,7 +73,7 @@ function test_interface(μ::M) where {M} @test ℓμ ≈ logdensity_def(μ, x) + ℓβ - @test logdensity_def(μ, testvalue(Float64, μ)) isa Real + @test logdensity_def(μ, x) isa Real end end end @@ -106,7 +108,7 @@ function test_transport(ν, μ) end end -function test_smf(μ, n = 100) +function test_smf(μ, n = 100, k=10) @testset "smf($μ)" begin # Get `n` sorted uniforms in O(n) time p = rand(n) @@ -121,16 +123,21 @@ function test_smf(μ, n = 100) @test issorted(x) @test all(istrue ∘ insupport(μ), x) - @test all((Finv ∘ F).(x) .≈ x) - for j in 1:n - a = rand() - b = rand() - a, b = minmax(a, b) - x = Finv(a) - y = Finv(b) - @test μ(Interval{:open,:closed}(x, y)) ≈ (F(y) - F(x)) + for (xj, pj) in zip(x, p) + # Ideally this would be exactly zero, but in practice we need to allow for + # numerical errors in the implementation of `smf` and `invsmf`. + + # Numerical errors are surprisingly large: + # smf(Beta(α = 0.18454471614214718, β = 0.0648526227363212)): Test Failed at /home/chad/git/MeasureBase.jl/src/interface.jl:130 + # Expression: F(xj) - pj ≥ -1.0e-10 + # Evaluated: -0.00010369484104344462 ≥ -1.0e-10 + @test F(xj) - pj ≥ -1e-3 end + + p .= F.(x) + + @test all(Finv.(p) .≈ x) end end diff --git a/src/mass-interface.jl b/src/mass-interface.jl index 7b0518f9..c27ce214 100644 --- a/src/mass-interface.jl +++ b/src/mass-interface.jl @@ -1,3 +1,4 @@ + import LinearAlgebra: normalize import Base diff --git a/src/parameterized.jl b/src/parameterized.jl index 78e43995..bde408a4 100644 --- a/src/parameterized.jl +++ b/src/parameterized.jl @@ -13,7 +13,7 @@ end function Pretty.tile(d::ParameterizedMeasure) result = Pretty.literal(nameof(typeof(d))) par = getfield(d, :par) - result *= Pretty.literal(sprint(show, par; context = :compact => true)) + result *= Pretty.tile(par) result end @@ -67,6 +67,28 @@ function ConstructionBase.setproperties( return constructorof(P)(merge(params(d), nt)) end +############################################################################### +# StructArrays + +# TODO: Can this be in an extension? + + +import StructArrays + +function StructArrays.staticschema(::Type{P}) where {N,P<:ParameterizedMeasure{N}} + par_type = fieldtype(P, :par) + types = Tuple{(fieldtype(par_type, n) for n in N)...} + NamedTuple{N,types} +end + +function StructArrays.component(m::ParameterizedMeasure, key::Symbol) + getfield(getfield(m, :par), key) +end + +function StructArrays.createinstance(::Type{P}, args...) where {N,P<:ParameterizedMeasure{N}} + constructorof(P)(NamedTuple{N}(args)) +end + ############################################################################### # params diff --git a/src/primitives/dirac.jl b/src/primitives/dirac.jl index 01297486..052143f0 100644 --- a/src/primitives/dirac.jl +++ b/src/primitives/dirac.jl @@ -20,15 +20,8 @@ basemeasure(d::Dirac) = CountingBase() massof(::Dirac) = static(1.0) -function logdensityof(μ::Dirac, x::Real) - R = float(typeof(x)) - insupport(μ, x) ? zero(R) : R(-Inf) -end - -logdensityof(μ::Dirac, x) = insupport(μ, x) ? 0.0 : -Inf -logdensity_def(::Dirac, x::Real) = zero(float(typeof(x))) -logdensity_def(::Dirac, x) = 0.0 +logdensity_def(μ::Dirac, x) = insupport(μ, x) ? 0.0 : -Inf Base.rand(::Random.AbstractRNG, T::Type, μ::Dirac) = μ.x diff --git a/src/primitives/lebesgue.jl b/src/primitives/lebesgue.jl index 3846eaf5..8c00843c 100644 --- a/src/primitives/lebesgue.jl +++ b/src/primitives/lebesgue.jl @@ -48,7 +48,9 @@ gentype(::Lebesgue) = Float64 Lebesgue() = Lebesgue(ℝ) -testvalue(::Type{T}, d::Lebesgue) where {T} = testvalue(T, d.support)::T +function Base.rand(rng::ConstantRNG, ::Type{T}, d::Lebesgue) where {T} + zero(T) +end proxy(d::Lebesgue) = restrict(in(d.support), LebesgueBase()) proxy(::Lebesgue{MeasureBase.RealNumbers}) = LebesgueBase() diff --git a/src/rand.jl b/src/rand.jl index f92cb16a..0eb56432 100644 --- a/src/rand.jl +++ b/src/rand.jl @@ -1,13 +1,35 @@ import Base -Base.rand(d::AbstractMeasure) = rand(Random.GLOBAL_RNG, Float64, d) +Base.rand(d::AbstractMeasure) = rand(Random.default_rng(), Float64, d) -Base.rand(T::Type, μ::AbstractMeasure) = rand(Random.GLOBAL_RNG, T, μ) +Base.rand(T::Type, μ::AbstractMeasure) = rand(Random.default_rng(), T, μ) + +@nospecialize +function Base.rand(rng::AbstractRNG, ::Type{T}, d::M) where {T,M<:AbstractMeasure} + @error "No method defined for rand(::AbstractRNG, ::Type{T}, $M)" +end +@specialize Base.rand(rng::AbstractRNG, d::AbstractMeasure) = rand(rng, Float64, d) @inline Random.rand!(d::AbstractMeasure, args...) = rand!(GLOBAL_RNG, d, args...) +@inline function Base.rand( + rng::AbstractRNG, + ::Type{T}, + d::ProductMeasure{A}, +) where {T,A<:AbstractArray} + mar = marginals(d) + elT = typeof(rand(rng, T, first(mar))) + + sz = size(mar) + x = Array{elT,length(sz)}(undef, sz) + @inbounds @simd for j in eachindex(mar) + x[j] = rand(rng, T, mar[j]) + end + x +end + # TODO: Make this work # function Base.rand(rng::AbstractRNG, ::Type{T}, d::AbstractMeasure) where {T} # x = testvalue(d) diff --git a/src/splat.jl b/src/splat.jl deleted file mode 100644 index d1df4f17..00000000 --- a/src/splat.jl +++ /dev/null @@ -1,11 +0,0 @@ -struct Splat{F} - f::F -end - -function (s::Splat{F})(x) where {F} - s.f(x...) -end - -unsplat(s::Splat) = s.f - -splat(f) = Splat(f) diff --git a/src/utils.jl b/src/utils.jl index 0ec81a50..6a9dcfd5 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -19,7 +19,7 @@ testvalue(::Type{T}) where {T} = zero(T) export rootmeasure -basemeasure(μ, x) = basemeasure(μ) +@inline basemeasure(μ, x) = basemeasure(μ) """ rootmeasure(μ::AbstractMeasure) @@ -180,3 +180,15 @@ isapproxzero(A::AbstractArray) = all(isapproxzero, A) isapproxone(x::T) where {T<:Real} = x ≈ one(T) isapproxone(A::AbstractArray) = all(isapproxone, A) + +import Statistics +import StatsBase + +using Statistics +using StatsBase: entropy + +StatsBase.entropy(m::AbstractMeasure, b::Real) = entropy(proxy(m), b) +Statistics.mean(m::AbstractMeasure) = mean(proxy(m)) +Statistics.std(m::AbstractMeasure) = std(proxy(m)) +Statistics.var(m::AbstractMeasure) = var(proxy(m)) +Statistics.quantile(m::AbstractMeasure, q) = quantile(proxy(m), q) diff --git a/test/runtests.jl b/test/runtests.jl index c2f63c4e..d223c488 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,7 +7,11 @@ import LogarithmicNumbers using MeasureBase using MeasureBase: test_interface, test_smf -include("test_aqua.jl") +using Aqua +@testset "Code quality (Aqua.jl)" begin + Aqua.test_all(MeasureBase, ambiguities = false) + # Aqua.test_ambiguities(MeasureBase) +end include("static.jl")