diff --git a/Project.toml b/Project.toml index ce537938c..23508c026 100644 --- a/Project.toml +++ b/Project.toml @@ -8,10 +8,12 @@ Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7" Functors = "d9f16b24-f501-4c13-a1f2-28368ffc5196" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Requires = "ae029012-a4dd-5104-9daa-d747884805df" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" ZygoteRules = "700de1a5-db45-46bc-99cf-38207098b444" [compat] diff --git a/src/KernelFunctions.jl b/src/KernelFunctions.jl index 2c22a5292..eeb57a9c7 100644 --- a/src/KernelFunctions.jl +++ b/src/KernelFunctions.jl @@ -44,6 +44,8 @@ export NystromFact, nystrom export spectral_mixture_kernel, spectral_mixture_product_kernel +export ColVecs, RowVecs + export MOInput export IndependentMOKernel, LatentFactorMOKernel @@ -108,6 +110,8 @@ include(joinpath("mokernels", "slfm.jl")) include("zygote_adjoints.jl") +include("test_utils.jl") + function __init__() @require Kronecker="2c470bb0-bcc8-11e8-3dad-c9649493f05e" begin include(joinpath("matrix", "kernelkroneckermat.jl")) diff --git a/src/basekernels/gabor.jl b/src/basekernels/gabor.jl index bf3cf115e..a72f4eb8f 100644 --- a/src/basekernels/gabor.jl +++ b/src/basekernels/gabor.jl @@ -57,17 +57,10 @@ end Base.show(io::IO, κ::GaborKernel) = print(io, "Gabor Kernel (ell = ", κ.ell, ", p = ", κ.p, ")") -function kernelmatrix(κ::GaborKernel, X::AbstractMatrix; obsdim::Int=defaultobs) - return kernelmatrix(κ.kernel, X; obsdim=obsdim) -end +kernelmatrix(κ::GaborKernel, x::AbstractVector) = kernelmatrix(κ.kernel, x) -function kernelmatrix( - κ::GaborKernel, X::AbstractMatrix, Y::AbstractMatrix; - obsdim::Int=defaultobs, -) - return kernelmatrix(κ.kernel, X, Y; obsdim=obsdim) +function kernelmatrix(κ::GaborKernel, x::AbstractVector, y::AbstractVector) + return kernelmatrix(κ.kernel, x, y) end -function kerneldiagmatrix(κ::GaborKernel, X::AbstractMatrix; obsdim::Int=defaultobs) #TODO Add test - return kerneldiagmatrix(κ.kernel, X; obsdim=obsdim) -end +kerneldiagmatrix(κ::GaborKernel, x::AbstractVector) = kerneldiagmatrix(κ.kernel, x) diff --git a/src/basekernels/nn.jl b/src/basekernels/nn.jl index be1d35361..afc9d5a24 100644 --- a/src/basekernels/nn.jl +++ b/src/basekernels/nn.jl @@ -42,13 +42,13 @@ function kernelmatrix(::NeuralNetworkKernel, x::RowVecs, y::RowVecs) X_2 = sum(x.X .* x.X; dims=2) Y_2 = sum(y.X .* y.X; dims=2) XY = x.X * y.X' - return asin.(XY ./ sqrt.((X_2 .+ 1)' * (Y_2 .+ 1))) + return asin.(XY ./ sqrt.((X_2 .+ 1) * (Y_2 .+ 1)')) end function kernelmatrix(::NeuralNetworkKernel, x::RowVecs) X_2_1 = sum(x.X .* x.X; dims=2) .+ 1 XX = x.X * x.X' - return asin.(XX ./ sqrt.(X_2_1' * X_2_1)) + return asin.(XX ./ sqrt.(X_2_1 * X_2_1')) end Base.show(io::IO, κ::NeuralNetworkKernel) = print(io, "Neural Network Kernel") diff --git a/src/basekernels/periodic.jl b/src/basekernels/periodic.jl index 63140f061..6c19518e8 100644 --- a/src/basekernels/periodic.jl +++ b/src/basekernels/periodic.jl @@ -26,4 +26,6 @@ metric(κ::PeriodicKernel) = Sinus(κ.r) kappa(κ::PeriodicKernel, d::Real) = exp(- 0.5d) -Base.show(io::IO, κ::PeriodicKernel) = print(io, "Periodic Kernel (length(r) = ", length(κ.r), ")") +function Base.show(io::IO, κ::PeriodicKernel) + print(io, "Periodic Kernel, length(r) = $(length(κ.r))") +end diff --git a/src/basekernels/rationalquad.jl b/src/basekernels/rationalquad.jl index 519de706e..f59bccd56 100644 --- a/src/basekernels/rationalquad.jl +++ b/src/basekernels/rationalquad.jl @@ -1,48 +1,62 @@ """ - RationalQuadraticKernel(; α = 2.0) + RationalQuadraticKernel(; α=2.0) The rational-quadratic kernel is a Mercer kernel given by the formula: ``` - κ(x,y)=(1+||x−y||²/α)^(-α) + κ(x, y) = (1 + ||x − y||² / (2α))^(-α) ``` -where `α` is a shape parameter of the Euclidean distance. Check [`GammaRationalQuadraticKernel`](@ref) for a generalization. +where `α` is a shape parameter of the Euclidean distance. Check +[`GammaRationalQuadraticKernel`](@ref) for a generalization. """ struct RationalQuadraticKernel{Tα<:Real} <: SimpleKernel α::Vector{Tα} function RationalQuadraticKernel(;alpha::T=2.0, α::T=alpha) where {T} - @check_args(RationalQuadraticKernel, α, α > zero(T), "α > 1") + @check_args(RationalQuadraticKernel, α, α > zero(T), "α > 0") return new{T}([α]) end end @functor RationalQuadraticKernel -kappa(κ::RationalQuadraticKernel, d²::T) where {T<:Real} = (one(T)+d²/first(κ.α))^(-first(κ.α)) +function kappa(κ::RationalQuadraticKernel, d²::T) where {T<:Real} + return (one(T) + d² / (2 * first(κ.α)))^(-first(κ.α)) +end + metric(::RationalQuadraticKernel) = SqEuclidean() -Base.show(io::IO, κ::RationalQuadraticKernel) = print(io, "Rational Quadratic Kernel (α = ", first(κ.α), ")") +function Base.show(io::IO, κ::RationalQuadraticKernel) + print(io, "Rational Quadratic Kernel (α = $(first(κ.α)))") +end """ -`GammaRationalQuadraticKernel([ρ=1.0[,α=2.0[,γ=2.0]]])` +`GammaRationalQuadraticKernel([α=2.0 [, γ=2.0]])` + The Gamma-rational-quadratic kernel is an isotropic Mercer kernel given by the formula: ``` - κ(x,y)=(1+ρ^(2γ)||x−y||^(2γ)/α)^(-α) + κ(x, y) = (1 + ||x−y||^γ / α)^(-α) ``` where `α` is a shape parameter of the Euclidean distance and `γ` is another shape parameter. """ struct GammaRationalQuadraticKernel{Tα<:Real, Tγ<:Real} <: SimpleKernel α::Vector{Tα} γ::Vector{Tγ} - function GammaRationalQuadraticKernel(;alpha::Tα=2.0, gamma::Tγ=2.0, α::Tα=alpha, γ::Tγ=gamma) where {Tα<:Real, Tγ<:Real} - @check_args(GammaRationalQuadraticKernel, α, α > one(Tα), "α > 1") - @check_args(GammaRationalQuadraticKernel, γ, γ >= one(Tγ), "γ >= 1") + function GammaRationalQuadraticKernel( + ;alpha::Tα=2.0, gamma::Tγ=2.0, α::Tα=alpha, γ::Tγ=gamma, + ) where {Tα<:Real, Tγ<:Real} + @check_args(GammaRationalQuadraticKernel, α, α > zero(Tα), "α > 0") + @check_args(GammaRationalQuadraticKernel, γ, zero(γ) < γ <= 2, "0 < γ <= 2") return new{Tα, Tγ}([α], [γ]) end end @functor GammaRationalQuadraticKernel -kappa(κ::GammaRationalQuadraticKernel, d²::T) where {T<:Real} = (one(T)+d²^first(κ.γ)/first(κ.α))^(-first(κ.α)) +function kappa(κ::GammaRationalQuadraticKernel, d²::Real) + return (one(d²) + d²^(first(κ.γ) / 2) / first(κ.α))^(-first(κ.α)) +end + metric(::GammaRationalQuadraticKernel) = SqEuclidean() -Base.show(io::IO, κ::GammaRationalQuadraticKernel) = print(io, "Gamma Rational Quadratic Kernel (α = ", first(κ.α), ", γ = ", first(κ.γ), ")") +function Base.show(io::IO, κ::GammaRationalQuadraticKernel) + print(io, "Gamma Rational Quadratic Kernel (α = $(first(κ.α)), γ = $(first(κ.γ)))") +end diff --git a/src/basekernels/sm.jl b/src/basekernels/sm.jl index 1b4875ecf..cfb8bf2e0 100644 --- a/src/basekernels/sm.jl +++ b/src/basekernels/sm.jl @@ -54,7 +54,7 @@ function spectral_mixture_kernel( γs::AbstractMatrix{<:Real}, ωs::AbstractMatrix{<:Real} ) - spectral_mixture_kernel(SqExponentialKernel(), αs, γs, ωs) + return spectral_mixture_kernel(SqExponentialKernel(), αs, γs, ωs) end """ @@ -95,7 +95,7 @@ function spectral_mixture_product_kernel( throw(DimensionMismatch("The dimensions of αs, γs, ans ωs do not match")) end return TensorProduct(spectral_mixture_kernel(h, α, reshape(γ, 1, :), reshape(ω, 1, :)) - for (α, γ, ω) in zip(eachrow(αs), eachrow(γs), eachrow(ωs))) + for (α, γ, ω) in zip(eachrow(αs), eachrow(γs), eachrow(ωs))) end function spectral_mixture_product_kernel( @@ -103,6 +103,6 @@ function spectral_mixture_product_kernel( γs::AbstractMatrix{<:Real}, ωs::AbstractMatrix{<:Real} ) - spectral_mixture_product_kernel(SqExponentialKernel(), αs, γs, ωs) + return spectral_mixture_product_kernel(SqExponentialKernel(), αs, γs, ωs) end diff --git a/src/test_utils.jl b/src/test_utils.jl new file mode 100644 index 000000000..996cea498 --- /dev/null +++ b/src/test_utils.jl @@ -0,0 +1,135 @@ +module TestUtils + +const __ATOL = 1e-9 + +using LinearAlgebra +using KernelFunctions +using Random +using Test + +""" + test_interface( + k::Kernel, + x0::AbstractVector, + x1::AbstractVector, + x2::AbstractVector; + atol=__ATOL, + ) + +Run various consistency checks on `k` at the inputs `x0`, `x1`, and `x2`. +`x0` and `x1` should be of the same length with different values, while `x0` and `x2` should +be of different lengths. + + test_interface([rng::AbstractRNG], k::Kernel, T::Type{<:AbstractVector}; atol=__ATOL) + +`test_interface` offers certain types of test data generation to make running these tests +require less code for common input types. For example, `Vector{<:Real}`, `ColVecs{<:Real}`, +and `RowVecs{<:Real}` are supported. For other input vector types, please provide the data +manually. +""" +function test_interface( + k::Kernel, + x0::AbstractVector, + x1::AbstractVector, + x2::AbstractVector; + atol=__ATOL, +) + # TODO: uncomment the tests of ternary kerneldiagmatrix. + + # Ensure that we have the required inputs. + @assert length(x0) == length(x1) + @assert length(x0) ≠ length(x2) + + # Check that kerneldiagmatrix basically works. + # @test kerneldiagmatrix(k, x0, x1) isa AbstractVector + # @test length(kerneldiagmatrix(k, x0, x1)) == length(x0) + + # Check that pairwise basically works. + @test kernelmatrix(k, x0, x2) isa AbstractMatrix + @test size(kernelmatrix(k, x0, x2)) == (length(x0), length(x2)) + + # Check that elementwise is consistent with pairwise. + # @test kerneldiagmatrix(k, x0, x1) ≈ diag(kernelmatrix(k, x0, x1)) atol=atol + + # Check additional binary elementwise properties for kernels. + # @test kerneldiagmatrix(k, x0, x1) ≈ kerneldiagmatrix(k, x1, x0) + @test kernelmatrix(k, x0, x2) ≈ kernelmatrix(k, x2, x0)' atol=atol + + # Check that unary elementwise basically works. + @test kerneldiagmatrix(k, x0) isa AbstractVector + @test length(kerneldiagmatrix(k, x0)) == length(x0) + + # Check that unary pairwise basically works. + @test kernelmatrix(k, x0) isa AbstractMatrix + @test size(kernelmatrix(k, x0)) == (length(x0), length(x0)) + @test kernelmatrix(k, x0) ≈ kernelmatrix(k, x0)' atol=atol + + # Check that unary elementwise is consistent with unary pairwise. + @test kerneldiagmatrix(k, x0) ≈ diag(kernelmatrix(k, x0)) atol=atol + + # Check that unary pairwise produces a positive definite matrix (approximately). + @test eigmin(Matrix(kernelmatrix(k, x0))) > -atol + + # Check that unary elementwise / pairwise are consistent with the binary versions. + # @test kerneldiagmatrix(k, x0) ≈ kerneldiagmatrix(k, x0, x0) atol=atol + @test kernelmatrix(k, x0) ≈ kernelmatrix(k, x0, x0) atol=atol + + # Check that basic kernel evaluation succeeds and is consistent with `kernelmatrix`. + @test k(first(x0), first(x1)) isa Real + @test kernelmatrix(k, x0, x2) ≈ [k(xl, xr) for xl in x0, xr in x2] + + tmp = Matrix{Float64}(undef, length(x0), length(x2)) + @test kernelmatrix!(tmp, k, x0, x2) ≈ kernelmatrix(k, x0, x2) + + tmp_square = Matrix{Float64}(undef, length(x0), length(x0)) + @test kernelmatrix!(tmp_square, k, x0) ≈ kernelmatrix(k, x0) + + tmp_diag = Vector{Float64}(undef, length(x0)) + @test kerneldiagmatrix!(tmp_diag, k, x0) ≈ kerneldiagmatrix(k, x0) +end + +function test_interface( + rng::AbstractRNG, k::Kernel, ::Type{Vector{T}}; kwargs... +) where {T<:Real} + test_interface(k, randn(rng, T, 3), randn(rng, T, 3), randn(rng, T, 2); kwargs...) +end + +function test_interface( + rng::AbstractRNG, k::Kernel, ::Type{<:ColVecs{T}}; dim_in=2, kwargs..., +) where {T<:Real} + test_interface( + k, + ColVecs(randn(rng, T, dim_in, 3)), + ColVecs(randn(rng, T, dim_in, 3)), + ColVecs(randn(rng, T, dim_in, 2)); + kwargs..., + ) +end + +function test_interface( + rng::AbstractRNG, k::Kernel, ::Type{<:RowVecs{T}}; dim_in=2, kwargs..., +) where {T<:Real} + test_interface( + k, + RowVecs(randn(rng, T, 3, dim_in)), + RowVecs(randn(rng, T, 3, dim_in)), + RowVecs(randn(rng, T, 2, dim_in)); + kwargs..., + ) +end + +function test_interface(k::Kernel, T::Type{<:AbstractVector}; kwargs...) + test_interface(Random.GLOBAL_RNG, k, T; kwargs...) +end + +function test_interface(rng::AbstractRNG, k::Kernel, T::Type{<:Real}; kwargs...) + test_interface(rng, k, Vector{T}; kwargs...) + test_interface(rng, k, ColVecs{T}; kwargs...) + test_interface(rng, k, RowVecs{T}; kwargs...) +end + +function test_interface(k::Kernel, T::Type{<:Real}=Float64; kwargs...) + test_interface(Random.GLOBAL_RNG, k, T; kwargs...) +end + +end # module diff --git a/src/transform/lineartransform.jl b/src/transform/lineartransform.jl index a58a6457f..dd1e4db1b 100644 --- a/src/transform/lineartransform.jl +++ b/src/transform/lineartransform.jl @@ -29,7 +29,7 @@ end (t::LinearTransform)(x::Real) = vec(t.A * x) (t::LinearTransform)(x::AbstractVector{<:Real}) = t.A * x -_map(t::LinearTransform, x::AbstractVector{<:Real}) = ColVecs(t.A * x') +_map(t::LinearTransform, x::AbstractVector{<:Real}) = ColVecs(t.A * collect(x')) _map(t::LinearTransform, x::ColVecs) = ColVecs(t.A * x.X) _map(t::LinearTransform, x::RowVecs) = RowVecs(x.X * t.A') diff --git a/src/utils.jl b/src/utils.jl index 07aa4bc7d..3bddf6557 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -70,6 +70,8 @@ struct RowVecs{T, TX<:AbstractMatrix{T}, S} <: AbstractVector{S} end end +RowVecs(x::AbstractVector) = RowVecs(reshape(x, :, 1)) + Base.size(D::RowVecs) = (size(D.X, 1),) Base.getindex(D::RowVecs, i::Int) = view(D.X, i, :) Base.getindex(D::RowVecs, i::CartesianIndex{1}) = view(D.X, i, :) diff --git a/test/basekernels/constant.jl b/test/basekernels/constant.jl index 5a2049675..a98f3e514 100644 --- a/test/basekernels/constant.jl +++ b/test/basekernels/constant.jl @@ -2,31 +2,40 @@ @testset "ZeroKernel" begin k = ZeroKernel() @test eltype(k) == Any - @test kappa(k,2.0) == 0.0 + @test kappa(k, 2.0) == 0.0 @test KernelFunctions.metric(ZeroKernel()) == KernelFunctions.Delta() @test repr(k) == "Zero Kernel" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(ZeroKernel) end @testset "WhiteKernel" begin k = WhiteKernel() @test eltype(k) == Any - @test kappa(k,1.0) == 1.0 - @test kappa(k,0.0) == 0.0 + @test kappa(k, 1.0) == 1.0 + @test kappa(k, 0.0) == 0.0 @test EyeKernel == WhiteKernel @test metric(WhiteKernel()) == KernelFunctions.Delta() @test repr(k) == "White Kernel" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(WhiteKernel) end @testset "ConstantKernel" begin c = 2.0 k = ConstantKernel(c=c) @test eltype(k) == Any - @test kappa(k,1.0) == c - @test kappa(k,0.5) == c + @test kappa(k, 1.0) == c + @test kappa(k, 0.5) == c @test metric(ConstantKernel()) == KernelFunctions.Delta() @test metric(ConstantKernel(c=2.0)) == KernelFunctions.Delta() @test repr(k) == "Constant Kernel (c = $(c))" test_params(k, ([c],)) + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(c->ConstantKernel(c=first(c)), [c]) end end diff --git a/test/basekernels/cosine.jl b/test/basekernels/cosine.jl index bf4c060b4..0c6792767 100644 --- a/test/basekernels/cosine.jl +++ b/test/basekernels/cosine.jl @@ -1,6 +1,6 @@ @testset "cosine" begin rng = MersenneTwister(123456) - x = rand(rng)*2 + x = rand(rng) * 2 v1 = rand(rng, 3) v2 = rand(rng, 3) @@ -9,8 +9,11 @@ @test kappa(k, 1.0) ≈ -1.0 atol=1e-5 @test kappa(k, 2.0) ≈ 1.0 atol=1e-5 @test kappa(k, 1.5) ≈ 0.0 atol=1e-5 - @test kappa(k,x) ≈ cospi(x) atol=1e-5 - @test k(v1, v2) ≈ cospi(sqrt(sum(abs2.(v1-v2)))) atol=1e-5 + @test kappa(k, x) ≈ cospi(x) atol=1e-5 + @test k(v1, v2) ≈ cospi(sqrt(sum(abs2.(v1 - v2)))) atol=1e-5 @test repr(k) == "Cosine Kernel" + + # Standardised tests. + TestUtils.test_interface(k, Vector{Float64}) test_ADs(CosineKernel) end diff --git a/test/basekernels/exponential.jl b/test/basekernels/exponential.jl index 4ea375748..d3e3a82d2 100644 --- a/test/basekernels/exponential.jl +++ b/test/basekernels/exponential.jl @@ -14,6 +14,9 @@ @test SEKernel == SqExponentialKernel @test repr(k) == "Squared Exponential Kernel" @test KernelFunctions.iskroncompatible(k) == true + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(SEKernel) end @testset "ExponentialKernel" begin @@ -25,6 +28,9 @@ @test repr(k) == "Exponential Kernel" @test LaplacianKernel == ExponentialKernel @test KernelFunctions.iskroncompatible(k) == true + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(ExponentialKernel) end @testset "GammaExponentialKernel" begin @@ -37,6 +43,7 @@ @test metric(GammaExponentialKernel(γ=2.0)) == Euclidean() @test repr(k) == "Gamma Exponential Kernel (γ = $(γ))" @test KernelFunctions.iskroncompatible(k) == true + test_ADs(γ -> GammaExponentialKernel(gamma=first(γ)), [γ]) test_params(k, ([γ],)) #Coherence : diff --git a/test/basekernels/exponentiated.jl b/test/basekernels/exponentiated.jl index a8c117b3b..5e608e7ca 100644 --- a/test/basekernels/exponentiated.jl +++ b/test/basekernels/exponentiated.jl @@ -1,6 +1,6 @@ @testset "exponentiated" begin rng = MersenneTwister(123456) - x = rand(rng)*2 + x = rand(rng) * 2 v1 = rand(rng, 3) v2 = rand(rng, 3) @@ -10,5 +10,8 @@ @test k(v1,v2) ≈ exp(dot(v1,v2)) @test metric(ExponentiatedKernel()) == KernelFunctions.DotProduct() @test repr(k) == "Exponentiated Kernel" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(ExponentiatedKernel) end diff --git a/test/basekernels/fbm.jl b/test/basekernels/fbm.jl index c7b0eb620..5f651cce0 100644 --- a/test/basekernels/fbm.jl +++ b/test/basekernels/fbm.jl @@ -2,25 +2,12 @@ rng = MersenneTwister(42) h = 0.3 k = FBMKernel(h = h) - v1 = rand(rng, 3); v2 = rand(rng, 3) + v1 = rand(rng, 3) + v2 = rand(rng, 3) @test k(v1,v2) ≈ (sqeuclidean(v1, zero(v1))^h + sqeuclidean(v2, zero(v2))^h - sqeuclidean(v1-v2, zero(v1-v2))^h)/2 atol=1e-5 + @test repr(k) == "Fractional Brownian Motion Kernel (h = $(h))" - # kernelmatrix tests - m1 = rand(rng, 3, 3) - m2 = rand(rng, 3, 3) - Kref = kernelmatrix(k, m1, m1) - @test kernelmatrix(k, m1) ≈ Kref atol=1e-5 - K = zeros(3, 3) - kernelmatrix!(K, k, m1, m1) - @test K ≈ Kref atol=1e-5 - fill!(K, 0) - kernelmatrix!(K, k, m1) - @test K ≈ Kref atol=1e-5 - - x1 = rand(rng) - x2 = rand(rng) - @test kernelmatrix(k, x1*ones(1,1), x2*ones(1,1))[1] ≈ k(x1, x2) atol=1e-5 - + test_interface(k) @test repr(k) == "Fractional Brownian Motion Kernel (h = $(h))" test_ADs(FBMKernel, ADs = [:ReverseDiff, :Zygote]) @test_broken "Tests failing for kernelmatrix(k, x) for ForwardDiff" diff --git a/test/basekernels/gabor.jl b/test/basekernels/gabor.jl index 5d15d636f..2d8ccfc94 100644 --- a/test/basekernels/gabor.jl +++ b/test/basekernels/gabor.jl @@ -1,5 +1,6 @@ @testset "Gabor" begin - v1 = rand(3); v2 = rand(3) + v1 = rand(3) + v2 = rand(3) ell = abs(rand()) p = abs(rand()) k = GaborKernel(ell=ell, p=p) @@ -17,6 +18,10 @@ @test k.ell ≈ 1.0 atol=1e-5 @test k.p ≈ 1.0 atol=1e-5 @test repr(k) == "Gabor Kernel (ell = 1.0, p = 1.0)" + + test_interface(k) + test_ADs(x -> GaborKernel(ell = x[1], p = x[2]), [ell, p], ADs = [:Zygote]) + # Tests are also failing randomly for ForwardDiff and ReverseDiff but randomly end diff --git a/test/basekernels/maha.jl b/test/basekernels/maha.jl index 1daf3cd69..fa57f5682 100644 --- a/test/basekernels/maha.jl +++ b/test/basekernels/maha.jl @@ -1,12 +1,15 @@ @testset "maha" begin rng = MersenneTwister(123456) x = 2 * rand(rng) - v1 = rand(rng, 3) - v2 = rand(rng, 3) + D_in = 3 + v1 = rand(rng, D_in) + v2 = rand(rng, D_in) + U = UpperTriangular(rand(rng, 3,3)) P = Matrix(Cholesky(U, 'U', 0)) @assert isposdef(P) + k = MahalanobisKernel(P=P) @test kappa(k, x) == exp(-x) @@ -40,5 +43,18 @@ # test_ADs(U -> MahalanobisKernel(P=Array(U' * U)), U, ADs=[:Zygote]) @test_broken "Nothing passes (problem with Mahalanobis distance in Distances)" + # Standardised tests. + @testset "ColVecs" begin + x0 = ColVecs(randn(D_in, 3)) + x1 = ColVecs(randn(D_in, 3)) + x2 = ColVecs(randn(D_in, 2)) + TestUtils.test_interface(k, x0, x1, x2) + end + @testset "RowVecs" begin + x0 = RowVecs(randn(3, D_in)) + x1 = RowVecs(randn(3, D_in)) + x2 = RowVecs(randn(2, D_in)) + TestUtils.test_interface(k, x0, x1, x2) + end test_params(k, (P,)) end diff --git a/test/basekernels/matern.jl b/test/basekernels/matern.jl index 332e20cb6..061601b3e 100644 --- a/test/basekernels/matern.jl +++ b/test/basekernels/matern.jl @@ -16,6 +16,9 @@ @test repr(k) == "Matern Kernel (ν = $(ν))" # test_ADs(x->MaternKernel(nu=first(x)),[ν]) @test_broken "All fails (because of logabsgamma for ForwardDiff and ReverseDiff and because of nu for Zygote)" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_params(k, ([ν],)) end @testset "Matern32Kernel" begin @@ -25,6 +28,9 @@ @test kappa(Matern32Kernel(),x) == kappa(k,x) @test metric(Matern32Kernel()) == Euclidean() @test repr(k) == "Matern 3/2 Kernel" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(Matern32Kernel) end @testset "Matern52Kernel" begin @@ -34,11 +40,14 @@ @test kappa(Matern52Kernel(),x) == kappa(k,x) @test metric(Matern52Kernel()) == Euclidean() @test repr(k) == "Matern 5/2 Kernel" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(Matern52Kernel) end @testset "Coherence Materns" begin - @test kappa(MaternKernel(ν=0.5),x) ≈ kappa(ExponentialKernel(),x) - @test kappa(MaternKernel(ν=1.5),x) ≈ kappa(Matern32Kernel(),x) - @test kappa(MaternKernel(ν=2.5),x) ≈ kappa(Matern52Kernel(),x) + @test kappa(MaternKernel(ν=0.5), x) ≈ kappa(ExponentialKernel(), x) + @test kappa(MaternKernel(ν=1.5), x) ≈ kappa(Matern32Kernel(), x) + @test kappa(MaternKernel(ν=2.5), x) ≈ kappa(Matern52Kernel(), x) end end diff --git a/test/basekernels/nn.jl b/test/basekernels/nn.jl index a46208505..717319097 100644 --- a/test/basekernels/nn.jl +++ b/test/basekernels/nn.jl @@ -4,44 +4,8 @@ v1 = rand(3); v2 = rand(3) @test k(v1,v2) ≈ asin(v1' * v2 / sqrt((1 + v1' * v1) * (1 + v2' * v2))) atol=1e-5 - # kernelmatrix tests - m1 = rand(3,4) - m2 = rand(3,4) - @test kernelmatrix(k, m1, m1) ≈ kernelmatrix(k, m1) atol=1e-5 - @test_broken kernelmatrix(k, m1, m2) ≈ k(m1, m2) atol=1e-5 - - - x1 = rand() - x2 = rand() - @test kernelmatrix(k, x1*ones(1,1), x2*ones(1,1))[1] ≈ k(x1, x2) atol=1e-5 - - @test k(v1, v2) ≈ k(v1, v2) atol=1e-5 - @test typeof(k(v1, v2)) <: Real - - @test_broken size(k(m1, m2)) == (4, 4) - @test_broken size(k(m1)) == (4, 4) - - A1 = ones(4, 4) - kernelmatrix!(A1, k, m1, m2) - @test A1 ≈ kernelmatrix(k, m1, m2) atol=1e-5 - - A2 = ones(4, 4) - kernelmatrix!(A2, k, m1) - @test A2 ≈ kernelmatrix(k, m1) atol=1e-5 - - @test size(kerneldiagmatrix(k, m1)) == (4,) - A3 = kernelmatrix(k, m1) - @test kerneldiagmatrix(k, m1) ≈ [A3[i, i] for i in 1:LinearAlgebra.checksquare(A3)] atol=1e-5 - - A4 = ones(4) - kerneldiagmatrix!(A4, k, m1) - @test kerneldiagmatrix(k, m1) ≈ A4 atol=1e-5 - - A5 = ones(4,4) - @test_throws AssertionError kernelmatrix!(A5, k, m1, m2; obsdim=3) - @test_throws AssertionError kernelmatrix!(A5, k, m1; obsdim=3) - @test_throws DimensionMismatch kernelmatrix!(A5, k, ones(4,3), ones(3,4)) - - @test k([x1], [x2]) ≈ k(x1, x2) atol=1e-5 + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(NeuralNetworkKernel) + @test_broken "Zygote uncompatible with BaseKernel" end diff --git a/test/basekernels/periodic.jl b/test/basekernels/periodic.jl index a4a2c0fdd..b41ef88b2 100644 --- a/test/basekernels/periodic.jl +++ b/test/basekernels/periodic.jl @@ -1,12 +1,20 @@ @testset "Periodic Kernel" begin - x = rand()*2; v1 = rand(3); v2 = rand(3); + x = rand()*2 + v1 = rand(3) + v2 = rand(3) r = rand(3) k = PeriodicKernel(r = r) @test kappa(k, x) ≈ exp(-0.5x) @test k(v1, v2) ≈ exp(-0.5 * sum(abs2, sinpi.(v1 - v2) ./ r)) @test k(v1, v2) == k(v2, v1) @test PeriodicKernel(3)(v1, v2) == PeriodicKernel(r = ones(3))(v1, v2) - @test repr(k) == "Periodic Kernel (length(r) = $(length(r)))" + @test repr(k) == "Periodic Kernel, length(r) = $(length(r))" + + # Standardised tests. + TestUtils.test_interface(PeriodicKernel(r=[0.9]), Vector{Float64}) + TestUtils.test_interface(PeriodicKernel(r=[0.9, 0.9]), ColVecs{Float64}) + TestUtils.test_interface(PeriodicKernel(r=[0.8, 0.7]), RowVecs{Float64}) + # test_ADs(r->PeriodicKernel(r =exp.(r)), log.(r), ADs = [:ForwardDiff, :ReverseDiff]) @test_broken "Undefined adjoint for Sinus metric, and failing randomly for ForwardDiff and ReverseDiff" test_params(k, (r,)) diff --git a/test/basekernels/piecewisepolynomial.jl b/test/basekernels/piecewisepolynomial.jl index 7aa71e8b4..617dffefe 100644 --- a/test/basekernels/piecewisepolynomial.jl +++ b/test/basekernels/piecewisepolynomial.jl @@ -1,9 +1,8 @@ @testset "piecewisepolynomial" begin - v1 = rand(3) - v2 = rand(3) - m1 = rand(3, 4) - m2 = rand(3, 4) - maha = ones(3, 3) + D = 2 + v1 = rand(D) + v2 = rand(D) + maha = Matrix{Float64}(I, D, D) v = 3 k = PiecewisePolynomialKernel{v}(maha) @@ -11,27 +10,13 @@ @test k2(v1, v2) ≈ k(v1, v2) atol=1e-5 - @test typeof(k(v1, v2)) <: Real - @test size(kernelmatrix(k, m1, m2)) == (4, 4) - @test size(kernelmatrix(k, m1)) == (4, 4) - - A1 = ones(4, 4) - kernelmatrix!(A1, k, m1, m2) - @test A1 ≈ kernelmatrix(k, m1, m2) atol=1e-5 - - A2 = ones(4, 4) - kernelmatrix!(A2, k, m1) - @test A2 ≈ kernelmatrix(k, m1) atol=1e-5 - - @test size(kerneldiagmatrix(k, m1)) == (4,) - @test kerneldiagmatrix(k, m1) == ones(4) - A3 = ones(4) - kerneldiagmatrix!(A3, k, m1) - @test A3 == kerneldiagmatrix(k, m1) - @test_throws ErrorException PiecewisePolynomialKernel{4}(maha) @test repr(k) == "Piecewise Polynomial Kernel (v = $(v), size(maha) = $(size(maha)))" + + # Standardised tests. + TestUtils.test_interface(k, ColVecs{Float64}; dim_in=2) + TestUtils.test_interface(k, RowVecs{Float64}; dim_in=2) # test_ADs(maha-> PiecewisePolynomialKernel(v=2, maha = maha), maha) @test_broken "Nothing passes (problem with Mahalanobis distance in Distances)" diff --git a/test/basekernels/polynomial.jl b/test/basekernels/polynomial.jl index 5c76f21eb..a9e6d8e5d 100644 --- a/test/basekernels/polynomial.jl +++ b/test/basekernels/polynomial.jl @@ -12,6 +12,9 @@ @test metric(LinearKernel()) == KernelFunctions.DotProduct() @test metric(LinearKernel(c=2.0)) == KernelFunctions.DotProduct() @test repr(k) == "Linear Kernel (c = 0.0)" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(x->LinearKernel(c=x[1]), [c]) test_params(LinearKernel(; c=c), ([c],)) end @@ -21,11 +24,15 @@ @test k(v1,v2) ≈ dot(v1,v2)^2 @test kappa(PolynomialKernel(),x) == kappa(k,x) @test repr(k) == "Polynomial Kernel (c = 0.0, d = 2.0)" - #Coherence test + + # Coherence tests. @test kappa(PolynomialKernel(d=1.0,c=c),x) ≈ kappa(LinearKernel(c=c),x) @test metric(PolynomialKernel()) == KernelFunctions.DotProduct() @test metric(PolynomialKernel(d=3.0)) == KernelFunctions.DotProduct() @test metric(PolynomialKernel(d=3.0,c=2.0)) == KernelFunctions.DotProduct() + + # Standardised tests. + TestUtils.test_interface(k, Float64) # test_ADs(x->PolynomialKernel(d=x[1], c=x[2]),[2.0, c]) @test_broken "All, because of the power" test_params(PolynomialKernel(; d=x, c=c), ([x], [c])) diff --git a/test/basekernels/rationalquad.jl b/test/basekernels/rationalquad.jl index 6df59ae81..90b877308 100644 --- a/test/basekernels/rationalquad.jl +++ b/test/basekernels/rationalquad.jl @@ -3,34 +3,86 @@ x = rand(rng)*2 v1 = rand(rng, 3) v2 = rand(rng, 3) + @testset "RationalQuadraticKernel" begin α = 2.0 k = RationalQuadraticKernel(α=α) - @test RationalQuadraticKernel(alpha=α).α == [α] - @test kappa(k,x) ≈ (1.0+x/2.0)^-2 - @test k(v1,v2) ≈ (1.0+norm(v1-v2)^2/2.0)^-2 - @test kappa(RationalQuadraticKernel(α=α),x) == kappa(k,x) + + @testset "RQ ≈ EQ for large α" begin + @test isapprox( + RationalQuadraticKernel(α=1e9)(v1, v2), + SqExponentialKernel()(v1, v2); + atol=1e-6, rtol=1e-6, + ) + end + @test metric(RationalQuadraticKernel()) == SqEuclidean() @test metric(RationalQuadraticKernel(α=2.0)) == SqEuclidean() @test repr(k) == "Rational Quadratic Kernel (α = $(α))" + + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(x->RationalQuadraticKernel(alpha=x[1]),[α]) test_params(k, ([α],)) end + @testset "GammaRationalQuadraticKernel" begin k = GammaRationalQuadraticKernel() - @test kappa(k,x) ≈ (1.0+x^2.0/2.0)^-2 - @test k(v1,v2) ≈ (1.0+norm(v1-v2)^4.0/2.0)^-2 - @test kappa(GammaRationalQuadraticKernel(),x) == kappa(k,x) - a = 1.0 + rand() - @test GammaRationalQuadraticKernel(alpha=a).α == [a] + @test repr(k) == "Gamma Rational Quadratic Kernel (α = 2.0, γ = 2.0)" - #Coherence test - @test kappa(GammaRationalQuadraticKernel(α=a, γ=1.0), x) ≈ kappa(RationalQuadraticKernel(α=a), x) + + @testset "Default GammaRQ ≈ RQ for large α with rescaled inputs" begin + @test isapprox( + GammaRationalQuadraticKernel()(v1 ./ sqrt(2), v2 ./ sqrt(2)), + RationalQuadraticKernel()(v1, v2), + ) + a = 1.0 + rand() + @test isapprox( + GammaRationalQuadraticKernel(α=a)(v1 ./ sqrt(2), v2 ./ sqrt(2)), + RationalQuadraticKernel(α=a)(v1, v2), + ) + end + + @testset "GammaRQ ≈ EQ for large α with rescaled inputs" begin + v1 = randn(2) + v2 = randn(2) + @test isapprox( + GammaRationalQuadraticKernel(α=1e9)(v1 ./ sqrt(2), v2 ./ sqrt(2)), + SqExponentialKernel()(v1, v2); + atol=1e-6, rtol=1e-6, + ) + end + + @testset "GammaRQ(γ=1) ≈ Exponential with rescaled inputs for large α" begin + v1 = randn(4) + v2 = randn(4) + @test isapprox( + GammaRationalQuadraticKernel(α=1e9, γ=1.0)(v1, v2), + ExponentialKernel()(v1, v2); + atol=1e-6, rtol=1e-6, + ) + end + + @testset "GammaRQ ≈ GammaExponential for same γ and large α" begin + v1 = randn(3) + v2 = randn(3) + γ = rand() + 0.5 + @test isapprox( + GammaRationalQuadraticKernel(α=1e9, γ=γ)(v1, v2), + GammaExponentialKernel(γ=γ)(v1, v2); + atol=1e-6, rtol=1e-6, + ) + end + @test metric(GammaRationalQuadraticKernel()) == SqEuclidean() @test metric(GammaRationalQuadraticKernel(γ=2.0)) == SqEuclidean() @test metric(GammaRationalQuadraticKernel(γ=2.0, α=3.0)) == SqEuclidean() + + # Standardised tests. + TestUtils.test_interface(k, Float64) # test_ADs(x->GammaRationalQuadraticKernel(α=x[1], γ=x[2]), [a, 2.0]) @test_broken "All (problem with power operation)" + a = 1.0 + rand() test_params(GammaRationalQuadraticKernel(; α=a, γ=x), ([a], [x])) end end diff --git a/test/basekernels/sm.jl b/test/basekernels/sm.jl index 91e55f52b..0505f6560 100644 --- a/test/basekernels/sm.jl +++ b/test/basekernels/sm.jl @@ -1,11 +1,13 @@ @testset "sm" begin - v1 = rand(5) - v2 = rand(5) + + D_in = 5 + v1 = rand(D_in) + v2 = rand(D_in) αs₁ = rand(3) - αs₂ = rand(5, 3) - γs = rand(5, 3) - ωs = rand(5, 3) + αs₂ = rand(D_in, 3) + γs = rand(D_in, 3) + ωs = rand(D_in, 3) k1 = spectral_mixture_kernel(αs₁, γs, ωs) k2 = spectral_mixture_product_kernel(αs₂, γs, ωs) @@ -26,6 +28,22 @@ @test_throws DimensionMismatch spectral_mixture_kernel(rand(5) ,rand(4,3), rand(4,3)) @test_throws DimensionMismatch spectral_mixture_kernel(rand(3) ,rand(4,3), rand(5,3)) @test_throws DimensionMismatch spectral_mixture_product_kernel(rand(5,3) ,rand(4,3), rand(5,3)) + + # Standardised tests. Choose input dims carefully. + @testset "ColVecs" begin + x0 = ColVecs(randn(D_in, 3)) + x1 = ColVecs(randn(D_in, 3)) + x2 = ColVecs(randn(D_in, 2)) + TestUtils.test_interface(k1, x0, x1, x2) + TestUtils.test_interface(k2, x0, x1, x2) + end + @testset "RowVecs" begin + x0 = RowVecs(randn(3, D_in)) + x1 = RowVecs(randn(3, D_in)) + x2 = RowVecs(randn(2, D_in)) + TestUtils.test_interface(k1, x0, x1, x2) + TestUtils.test_interface(k2, x0, x1, x2) + end # test_ADs(x->spectral_mixture_kernel(exp.(x[1:3]), reshape(x[4:18], 5, 3), reshape(x[19:end], 5, 3)), vcat(log.(αs₁), γs[:], ωs[:]), dims = [5,5]) @test_broken "No tests passing (BaseKernel)" end diff --git a/test/basekernels/wiener.jl b/test/basekernels/wiener.jl index 624837b8c..55218483a 100644 --- a/test/basekernels/wiener.jl +++ b/test/basekernels/wiener.jl @@ -3,16 +3,16 @@ @test typeof(k_1) <: WhiteKernel k0 = WienerKernel() - @test typeof(k0) <: WienerKernel{0} + @test k0 isa WienerKernel{0} k1 = WienerKernel(i=1) - @test typeof(k1) <: WienerKernel{1} + @test k1 isa WienerKernel{1} k2 = WienerKernel(i=2) - @test typeof(k2) <: WienerKernel{2} + @test k2 isa WienerKernel{2} k3 = WienerKernel(i=3) - @test typeof(k3) <: WienerKernel{3} + @test k3 isa WienerKernel{3} @test_throws AssertionError WienerKernel(i=4) @test_throws AssertionError WienerKernel(i=-2) @@ -31,26 +31,14 @@ @test k3(v1, v2) ≈ 1 / 252 * minXY^7 + 1 / 720 * minXY^4 * euclidean(v1, v2) * ( 5 * max(X, Y)^2 + 2 * X * Y + 3 * minXY^2 ) - # kernelmatrix tests - m1 = rand(3,4) - m2 = rand(3,4) - @test kernelmatrix(k0, m1, m1) ≈ kernelmatrix(k0, m1) atol=1e-5 - - K = zeros(4,4) - kernelmatrix!(K,k0,m1,m2) - @test K ≈ kernelmatrix(k0, m1, m2) atol=1e-5 - - V = zeros(4) - kerneldiagmatrix!(V,k0,m1) - @test V ≈ kerneldiagmatrix(k0,m1) atol=1e-5 - - x1 = rand() - x2 = rand() - @test kernelmatrix(k0, x1*ones(1,1), x2*ones(1,1))[1] ≈ k0(x1, x2) atol=1e-5 - @test kernelmatrix(k1, x1*ones(1,1), x2*ones(1,1))[1] ≈ k1(x1, x2) atol=1e-5 - @test kernelmatrix(k2, x1*ones(1,1), x2*ones(1,1))[1] ≈ k2(x1, x2) atol=1e-5 - @test kernelmatrix(k3, x1*ones(1,1), x2*ones(1,1))[1] ≈ k3(x1, x2) atol=1e-5 - + # Standardised tests. Requires careful input choice. + x0 = rand(3) + x1 = rand(3) + x2 = rand(2) + TestUtils.test_interface(k0, x0, x1, x2) + TestUtils.test_interface(k1, x0, x1, x2) + TestUtils.test_interface(k2, x0, x1, x2) + TestUtils.test_interface(k3, x0, x1, x2) # test_ADs(()->WienerKernel(i=1)) @test_broken "No tests passing" end diff --git a/test/kernels/kernelproduct.jl b/test/kernels/kernelproduct.jl index 3304a15a8..4d9afc065 100644 --- a/test/kernels/kernelproduct.jl +++ b/test/kernels/kernelproduct.jl @@ -32,41 +32,12 @@ @test (KernelProduct((k1, k2)) * k3).kernels == (k1, k2, k3) @test (k3 * KernelProduct((k1, k2))).kernels == (k3, k1, k2) - @testset "kernelmatrix" begin - rng = MersenneTwister(123456) - - Nx = 5 - Ny = 4 - D = 3 - - w1 = rand(rng) + 1e-3 - w2 = rand(rng) + 1e-3 - k1 = w1 * SqExponentialKernel() - k2 = w2 * LinearKernel() - k = k1 * k2 - - @testset "$(typeof(x))" for (x, y) in [ - (ColVecs(randn(rng, D, Nx)), ColVecs(randn(rng, D, Ny))), - (RowVecs(randn(rng, Nx, D)), RowVecs(randn(rng, Ny, D))), - ] - @test kernelmatrix(k, x, y) ≈ kernelmatrix(k1, x, y) .* kernelmatrix(k2, x, y) - - @test kernelmatrix(k, x) ≈ kernelmatrix(k1, x) .* kernelmatrix(k2, x) - - K_diag_manual = kerneldiagmatrix(k1, x) .* kerneldiagmatrix(k2, x) - @test kerneldiagmatrix(k, x) ≈ K_diag_manual - - tmp = Matrix{Float64}(undef, length(x), length(y)) - @test kernelmatrix!(tmp, k, x, y) ≈ kernelmatrix(k, x, y) - - tmp_square = Matrix{Float64}(undef, length(x), length(x)) - @test kernelmatrix!(tmp_square, k, x) ≈ kernelmatrix(k, x) - - tmp_diag = Vector{Float64}(undef, length(x)) - @test kerneldiagmatrix!(tmp_diag, k, x) ≈ kerneldiagmatrix(k, x) - end - end - test_ADs(x->SqExponentialKernel() * LinearKernel(c= x[1]), rand(1), ADs = [:ForwardDiff, :ReverseDiff, :Zygote]) + # Standardised tests. + TestUtils.test_interface(k, Float64) + test_ADs( + x->SqExponentialKernel() * LinearKernel(c= x[1]), rand(1); + ADs = [:ForwardDiff, :ReverseDiff, :Zygote], + ) test_params(k1 * k2, (k1, k2)) end diff --git a/test/kernels/kernelsum.jl b/test/kernels/kernelsum.jl index 0e05978ae..3cd59a5b0 100644 --- a/test/kernels/kernelsum.jl +++ b/test/kernels/kernelsum.jl @@ -32,40 +32,12 @@ @test (KernelSum((k1, k2)) + k3).kernels == (k1, k2, k3) @test (k3 + KernelSum((k1, k2))).kernels == (k3, k1, k2) - @testset "kernelmatrix" begin - rng = MersenneTwister(123456) - - Nx = 5 - Ny = 4 - D = 3 - - w1 = rand(rng) + 1e-3 - w2 = rand(rng) + 1e-3 - k1 = w1 * SqExponentialKernel() - k2 = w2 * LinearKernel() - k = k1 + k2 - - @testset "$(typeof(x))" for (x, y) in [ - (ColVecs(randn(rng, D, Nx)), ColVecs(randn(rng, D, Ny))), - (RowVecs(randn(rng, Nx, D)), RowVecs(randn(rng, Ny, D))), - ] - @test kernelmatrix(k, x, y) ≈ kernelmatrix(k1, x, y) + kernelmatrix(k2, x, y) - - @test kernelmatrix(k, x) ≈ kernelmatrix(k1, x) + kernelmatrix(k2, x) - - @test kerneldiagmatrix(k, x) ≈ kerneldiagmatrix(k1, x) + kerneldiagmatrix(k2, x) - - tmp = Matrix{Float64}(undef, length(x), length(y)) - @test kernelmatrix!(tmp, k, x, y) ≈ kernelmatrix(k, x, y) - - tmp_square = Matrix{Float64}(undef, length(x), length(x)) - @test kernelmatrix!(tmp_square, k, x) ≈ kernelmatrix(k, x) - - tmp_diag = Vector{Float64}(undef, length(x)) - @test kerneldiagmatrix!(tmp_diag, k, x) ≈ kerneldiagmatrix(k, x) - end - end - test_ADs(x->KernelSum(SqExponentialKernel(),LinearKernel(c= x[1])), rand(1), ADs = [:ForwardDiff, :ReverseDiff, :Zygote]) + # Standardised tests. + TestUtils.test_interface(k, Float64) + test_ADs( + x->KernelSum(SqExponentialKernel(), LinearKernel(c=x[1])), rand(1); + ADs = [:ForwardDiff, :ReverseDiff, :Zygote], + ) test_params(k1 + k2, (k1, k2)) end diff --git a/test/kernels/scaledkernel.jl b/test/kernels/scaledkernel.jl index 151d37190..99bc71a18 100644 --- a/test/kernels/scaledkernel.jl +++ b/test/kernels/scaledkernel.jl @@ -9,37 +9,8 @@ @test ks(x, y) == s * k(x, y) @test ks(x, y) == (s * k)(x, y) - @testset "kernelmatrix" begin - rng = MersenneTwister(123456) - - Nx = 5 - Ny = 4 - D = 3 - - k = SqExponentialKernel() - s = rand(rng) + 1e-3 - ks = s * k - - @testset "$(typeof(x))" for (x, y) in [ - (ColVecs(randn(rng, D, Nx)), ColVecs(randn(rng, D, Ny))), - (RowVecs(randn(rng, Nx, D)), RowVecs(randn(rng, Ny, D))), - ] - @test kernelmatrix(ks, x, y) ≈ s .* kernelmatrix(k, x, y) - - @test kernelmatrix(ks, x) ≈ s .* kernelmatrix(k, x) - - @test kerneldiagmatrix(ks, x) ≈ s .* kerneldiagmatrix(k, x) - - tmp = Matrix{Float64}(undef, length(x), length(y)) - @test_broken kernelmatrix!(tmp, ks, x, y) ≈ kernelmatrix(ks, x, y) - - tmp_square = Matrix{Float64}(undef, length(x), length(x)) - @test_broken kernelmatrix!(tmp_square, ks, x) ≈ kernelmatrix(ks, x) - - tmp_diag = Vector{Float64}(undef, length(x)) - @test_broken kerneldiagmatrix!(tmp_diag, ks, x) ≈ kerneldiagmatrix(ks, x) - end - end + # Standardised tests. + TestUtils.test_interface(k, Float64) test_ADs(x->exp(x[1]) * SqExponentialKernel(), rand(1)) test_params(s * k, (k, [s])) diff --git a/test/kernels/tensorproduct.jl b/test/kernels/tensorproduct.jl index 78ed81e26..9896950c4 100644 --- a/test/kernels/tensorproduct.jl +++ b/test/kernels/tensorproduct.jl @@ -24,39 +24,14 @@ end end - @testset "kernelmatrix and kerneldiagmatrix" begin - X = rand(rng, 2, 10) - x_cols = ColVecs(X) - x_rows = RowVecs(X') - Y = rand(rng, 2, 10) - y_cols = ColVecs(Y) - y_rows = RowVecs(Y') - - trueX = kernelmatrix(k1, X[1, :]) .* kernelmatrix(k2, X[2, :]) - trueXY = kernelmatrix(k1, X[1, :], Y[1, :]) .* kernelmatrix(k2, X[2, :], Y[2, :]) - tmp = Matrix{Float64}(undef, 10, 10) - tmp_diag = Vector{Float64}(undef, 10) - - for kernel in (kernel1, kernel2), (x, y) in ((x_cols, y_cols), (x_rows, y_rows)) - @test kernelmatrix(kernel, x) ≈ trueX - - @test kernelmatrix(kernel, x, y) ≈ trueXY - - fill!(tmp, 0) - kernelmatrix!(tmp, kernel, x) - @test tmp ≈ trueX - - fill!(tmp, 0) - kernelmatrix!(tmp, kernel, x, y) - @test tmp ≈ trueXY - - @test kerneldiagmatrix(kernel, x) ≈ diag(kernelmatrix(kernel, x)) - - fill!(tmp_diag, 0) - kerneldiagmatrix!(tmp_diag, kernel, x) - @test tmp_diag ≈ diag(kernelmatrix(kernel, x)) - end - end + # Standardised tests. + TestUtils.test_interface(kernel1, ColVecs{Float64}) + TestUtils.test_interface(kernel1, RowVecs{Float64}) + test_ADs( + ()->TensorProduct(SqExponentialKernel(), LinearKernel()); + dims = [2, 2], + ) # ADs = [:ForwardDiff, :ReverseDiff]) + test_params(TensorProduct(k1, k2), (k1, k2)) @testset "single kernel" begin kernel = TensorProduct(k1) @@ -69,49 +44,5 @@ @test kernel(x, y) == val end end - - @testset "kernelmatrix" begin - N = 10 - - x = randn(rng, N) - y = randn(rng, N) - vectors = (x, y) - - X = reshape(x, 1, :) - x_cols = ColVecs(X) - x_rows = RowVecs(X') - Y = reshape(y, 1, :) - y_cols = ColVecs(Y) - y_rows = RowVecs(Y') - - trueX = kernelmatrix(k1, x) - trueXY = kernelmatrix(k1, x, y) - tmp = Matrix{Float64}(undef, N, N) - tmp_diag = Vector{Float64}(undef, N) - - for (x, y) in ((x, y), (x_cols, y_cols), (x_rows, y_rows)) - - @test kernelmatrix(kernel, x) ≈ trueX - - @test kernelmatrix(kernel, x, y) ≈ trueXY - - fill!(tmp, 0) - kernelmatrix!(tmp, kernel, x) - @test tmp ≈ trueX - - fill!(tmp, 0) - kernelmatrix!(tmp, kernel, x, y) - @test tmp ≈ trueXY - - @test kerneldiagmatrix(kernel, x) ≈ diag(kernelmatrix(kernel, x)) - - fill!(tmp_diag, 0) - kerneldiagmatrix!(tmp_diag, kernel, x) - @test tmp_diag ≈ diag(kernelmatrix(kernel, x)) - end - end end - test_ADs(()->TensorProduct(SqExponentialKernel(), LinearKernel()), dims = [2, 2]) # ADs = [:ForwardDiff, :ReverseDiff]) - - test_params(TensorProduct(k1, k2), (k1, k2)) end diff --git a/test/kernels/transformedkernel.jl b/test/kernels/transformedkernel.jl index f936af47f..fc93aa635 100644 --- a/test/kernels/transformedkernel.jl +++ b/test/kernels/transformedkernel.jl @@ -20,53 +20,7 @@ @test KernelFunctions.kernel(kt) == k @test repr(kt) == repr(k) * "\n\t- " * repr(ScaleTransform(s)) - @testset "kernelmatrix" begin - rng = MersenneTwister(123456) - - Nx = 5 - Ny = 4 - D = 3 - - k = SqExponentialKernel() - t = ScaleTransform(randn(rng)) - kt = TransformedKernel(k, t) - - @testset "$(typeof(x))" for (x, y) in [ - (ColVecs(randn(rng, D, Nx)), ColVecs(randn(rng, D, Ny))), - (RowVecs(randn(rng, Nx, D)), RowVecs(randn(rng, Ny, D))), - ] - @test kernelmatrix(kt, x, y) ≈ kernelmatrix(k, map(t, x), map(t, y)) - - @test kernelmatrix(kt, x) ≈ kernelmatrix(k, map(t, x)) - - @test kerneldiagmatrix(kt, x) ≈ kerneldiagmatrix(k, map(t, x)) - - tmp = Matrix{Float64}(undef, length(x), length(y)) - @test kernelmatrix!(tmp, kt, x, y) ≈ kernelmatrix(kt, x, y) - - tmp_square = Matrix{Float64}(undef, length(x), length(x)) - @test kernelmatrix!(tmp_square, kt, x) ≈ kernelmatrix(kt, x) - - tmp_diag = Vector{Float64}(undef, length(x)) - @test kerneldiagmatrix!(tmp_diag, kt, x) ≈ kerneldiagmatrix(kt, x) - end - - @testset "mixed inputs" begin - k = transform(SqExponentialKernel(), 10.0) - x = rand(rng, 5, 3) - X1 = collect(eachcol(x)) - Y1 = KernelFunctions.ColVecs(x) - @test_nowarn Zygote.gradient(k-> sum(kernelmatrix(k, X1, Y1)), k) - @test_nowarn Zygote.gradient(k-> sum(kernelmatrix(k, Y1, X1)), k) - @test kernelmatrix(k, X1, Y1) ≈ kernelmatrix(k, X1, X1) ≈ kernelmatrix(k, Y1, Y1) - - X2 = collect(eachrow(x)) - Y2 = KernelFunctions.RowVecs(x) - @test_nowarn Zygote.gradient(k-> sum(kernelmatrix(k, X2, Y2)), k) - @test_nowarn Zygote.gradient(k-> sum(kernelmatrix(k, Y2, X2)), k) - @test kernelmatrix(k, X2, Y2) ≈ kernelmatrix(k, X2, X2) ≈ kernelmatrix(k, Y2, Y2) - end - end + TestUtils.test_interface(k, Float64) test_ADs(x->transform(SqExponentialKernel(), x[1]), rand(1))# ADs = [:ForwardDiff, :ReverseDiff]) # Test implicit gradients @testset "Implicit gradients" begin diff --git a/test/runtests.jl b/test/runtests.jl index c90b82772..1f73a7a6f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,7 +10,9 @@ using Test using Flux import Zygote, ForwardDiff, ReverseDiff, FiniteDifferences -using KernelFunctions: SimpleKernel, metric, kappa, ColVecs, RowVecs +using KernelFunctions: SimpleKernel, metric, kappa, ColVecs, RowVecs, TestUtils + +using KernelFunctions.TestUtils: test_interface # Writing tests: # 1. The file structure of the test should match precisely the file structure of src. @@ -40,7 +42,8 @@ using KernelFunctions: SimpleKernel, metric, kappa, ColVecs, RowVecs # 9. List out all test files explicitly (eg. don't loop over them). This makes it easy to # disable tests by simply commenting them out, and makes it very clear which tests are not # currently being run. -# 10. If utility files are required. +# 10. If utility functionality is required, it should be placed in `src/test_utils.jl` so +# that other packages can benefit from it when implementing new kernels. @info "Packages Loaded" include("test_utils.jl") diff --git a/test/test_utils.jl b/test/test_utils.jl index f6fd39400..a94f1c54b 100644 --- a/test/test_utils.jl +++ b/test/test_utils.jl @@ -1,3 +1,6 @@ +# More test utilities. Can't be included in KernelFunctions because they introduce a number +# of additional deps that we don't want to have in the main package. + # Check parameters of kernels function test_params(kernel, reference)