From 85e39407f3e45b8eb9703bda40c2c28a08af5561 Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Fri, 8 Jan 2021 14:00:38 +0100 Subject: [PATCH] Improve Test style and coverage (#319) * refactor runtests to work ina single testset and still print the interims tests via @info. * increase test covaerage and resolve an error for projection on the PoincareBallTVectors * Increase test coverage further. * runs formatter. * adds another small test for utils. * Fix a test on group general * trying to cover two things * removing eigvals and some old version tests * updates for distributions and formatting * add support of NormalRotationDistribution * metric test updates * run formatter * remove one more version check * typo in docs * fixes a small error that always compared SArrays to SArrays such that the Array variant was never used and never checked/compared. * removes a line that is never reached (note the injectivity atop before the loop) * Revert "removes a line that is never reached (note the injectivity atop before the loop)" This reverts commit 6e71c6ce07f0e9817952ec1a8a66ed9d8d805d73. Co-authored-by: Mateusz Baran --- docs/src/features/distributions.md | 7 +- src/distributions.jl | 9 -- src/groups/array_manifold.jl | 26 ++- src/groups/group.jl | 24 ++- src/groups/group_action.jl | 16 +- src/groups/product_group.jl | 82 +++++----- src/manifolds/CenteredMatrices.jl | 4 +- src/manifolds/Circle.jl | 10 +- src/manifolds/Euclidean.jl | 52 +++--- src/manifolds/HyperbolicHyperboloid.jl | 6 +- src/manifolds/HyperbolicPoincareBall.jl | 9 ++ src/manifolds/HyperbolicPoincareHalfspace.jl | 9 ++ src/manifolds/MetricManifold.jl | 28 +--- src/manifolds/MultinomialDoublyStochastic.jl | 7 +- src/manifolds/ProbabilitySimplex.jl | 10 +- src/manifolds/ProductManifold.jl | 106 +++++++----- src/manifolds/ProjectiveSpace.jl | 12 -- src/manifolds/Rotations.jl | 13 +- src/manifolds/Sphere.jl | 12 -- src/manifolds/Stiefel.jl | 10 +- .../SymmetricPositiveDefiniteLinearAffine.jl | 4 +- .../SymmetricPositiveSemidefiniteFixedRank.jl | 2 +- src/manifolds/VectorBundle.jl | 16 +- src/product_representations.jl | 4 +- src/projected_distribution.jl | 21 +++ src/recipes.jl | 6 +- src/riemannian_diff.jl | 21 +-- src/statistics.jl | 34 +++- src/tests/tests_general.jl | 20 ++- src/tests/tests_group.jl | 80 ++++----- src/utils.jl | 5 - test/assets/Hyp2SurfPlot.png | Bin 0 -> 13112 bytes test/assets/Sphere2SurfPlot.png | Bin 0 -> 60555 bytes test/differentiation.jl | 2 +- test/euclidean.jl | 18 +-- test/groups/groups_general.jl | 21 ++- test/groups/special_orthogonal.jl | 14 +- test/hyperbolic.jl | 27 ++++ test/metric.jl | 8 +- test/multinomial_symmetric.jl | 5 + test/probability_simplex.jl | 1 + test/product_manifold.jl | 45 +++--- test/projective_space.jl | 8 +- test/recipes.jl | 20 +++ test/rotations.jl | 13 +- test/runtests.jl | 152 +++++++++--------- test/sphere.jl | 8 +- test/statistics.jl | 4 +- test/stiefel.jl | 4 +- test/utils.jl | 16 ++ 50 files changed, 581 insertions(+), 450 deletions(-) create mode 100644 test/assets/Hyp2SurfPlot.png create mode 100644 test/assets/Sphere2SurfPlot.png diff --git a/docs/src/features/distributions.md b/docs/src/features/distributions.md index 2f96725de4..e6fbfd51cc 100644 --- a/docs/src/features/distributions.md +++ b/docs/src/features/distributions.md @@ -8,7 +8,8 @@ Pages = ["distributions.jl"] Order = [:type, :function] ``` -```@docs -Manifolds.ProjectedPointDistribution -Manifolds.ProjectedFVectorDistribution +```@autodocs +Modules = [Manifolds] +Pages = ["projected_distribution.jl"] +Order = [:type, :function] ``` diff --git a/src/distributions.jl b/src/distributions.jl index 362e3a8999..9918275832 100644 --- a/src/distributions.jl +++ b/src/distributions.jl @@ -62,12 +62,3 @@ Get the object of type `FVectorSupport` for the distribution `d`. function Distributions.support(::T) where {T<:FVectorDistribution} return error("support not implemented for type $T") end - -@decorator_transparent_signature normal_tvector_distribution( - M::AbstractDecoratorManifold, - p, - σ, -) - -@decorator_transparent_signature projected_distribution(M::AbstractDecoratorManifold, d, p) -@decorator_transparent_signature projected_distribution(M::AbstractDecoratorManifold, d) diff --git a/src/groups/array_manifold.jl b/src/groups/array_manifold.jl index 6e3047f9c2..4e821eaddd 100644 --- a/src/groups/array_manifold.jl +++ b/src/groups/array_manifold.jl @@ -91,13 +91,9 @@ function translate_diff(M::ValidationManifold, p, q, X, conv::ActionDirection; k is_manifold_point(M, p, true; kwargs...) is_manifold_point(M, q, true; kwargs...) is_tangent_vector(M, q, X, true; kwargs...) - Y = ValidationTVector(translate_diff( - M.manifold, - array_value(p), - array_value(q), - array_value(X), - conv, - )) + Y = ValidationTVector( + translate_diff(M.manifold, array_value(p), array_value(q), array_value(X), conv), + ) pq = translate(M, p, q, conv) is_tangent_vector(M, pq, Y, true; kwargs...) return Y @@ -139,13 +135,15 @@ function inverse_translate_diff( is_manifold_point(M, p, true; kwargs...) is_manifold_point(M, q, true; kwargs...) is_tangent_vector(M, q, X, true; kwargs...) - Y = ValidationTVector(inverse_translate_diff( - M.manifold, - array_value(p), - array_value(q), - array_value(X), - conv, - )) + Y = ValidationTVector( + inverse_translate_diff( + M.manifold, + array_value(p), + array_value(q), + array_value(X), + conv, + ), + ) pinvq = inverse_translate(M, p, q, conv) is_tangent_vector(M, pinvq, Y, true; kwargs...) return Y diff --git a/src/groups/group.jl b/src/groups/group.jl index 50b11f6ec9..39ed1106a1 100644 --- a/src/groups/group.jl +++ b/src/groups/group.jl @@ -194,10 +194,14 @@ function decorator_transparent_dispatch( end function allocate_result(M::Manifold, f::typeof(get_vector), e::Identity, Xⁱ) is_group_decorator(M) && return allocate_result(base_group(M), f, e, Xⁱ) - return error("allocate_result not implemented for manifold $(M), function $(f), point $(e), and vector $(Xⁱ).") + return error( + "allocate_result not implemented for manifold $(M), function $(f), point $(e), and vector $(Xⁱ).", + ) end function allocate_result(M::AbstractGroupManifold, f::typeof(get_vector), e::Identity, Xⁱ) - return error("allocate_result not implemented for group manifold $(M), function $(f), $(e), and vector $(Xⁱ).") + return error( + "allocate_result not implemented for group manifold $(M), function $(f), $(e), and vector $(Xⁱ).", + ) end function allocate_result( G::GT, @@ -223,7 +227,9 @@ function allocate_result( X, ) is_group_decorator(M) && return allocate_result(base_group(M), f, e, X) - return error("allocate_result not implemented for manifold $(M), function $(f), point $(e), and vector $(X).") + return error( + "allocate_result not implemented for manifold $(M), function $(f), point $(e), and vector $(X).", + ) end function allocate_result( M::AbstractGroupManifold, @@ -231,7 +237,9 @@ function allocate_result( e::Identity, X, ) - return error("allocate_result not implemented for group manifold $(M), function $(f), $(e), and vector $(X).") + return error( + "allocate_result not implemented for group manifold $(M), function $(f), $(e), and vector $(X).", + ) end function allocate_result( G::GT, @@ -1025,7 +1033,9 @@ Base.identity(::MultiplicationGroup, p) = one(p) function identity!(G::GT, q, p) where {GT<:MultiplicationGroup} isa(p, Identity{GT}) || return copyto!(q, one(p)) - return error("identity! not implemented on $(typeof(G)) for points $(typeof(q)) and $(typeof(p))") + return error( + "identity! not implemented on $(typeof(G)) for points $(typeof(q)) and $(typeof(p))", + ) end identity!(::MultiplicationGroup, q::AbstractMatrix, p) = copyto!(q, I) @@ -1047,5 +1057,7 @@ end function group_exp!(G::MultiplicationGroup, q, X) X isa Union{Number,AbstractMatrix} && return copyto!(q, exp(X)) - return error("group_exp! not implemented on $(typeof(G)) for vector $(typeof(X)) and element $(typeof(q)).") + return error( + "group_exp! not implemented on $(typeof(G)) for vector $(typeof(X)) and element $(typeof(q)).", + ) end diff --git a/src/groups/group_action.jl b/src/groups/group_action.jl index 823061dac7..ce603a02ad 100644 --- a/src/groups/group_action.jl +++ b/src/groups/group_action.jl @@ -50,7 +50,9 @@ Apply action `a` to the point `p` with the rule specified by `A`. The result is saved in `q`. """ function apply!(A::AbstractGroupAction{LeftAction}, q, a, p) - return error("apply! not implemented for action $(typeof(A)) and points $(typeof(q)), $(typeof(p)) and $(typeof(a)).") + return error( + "apply! not implemented for action $(typeof(A)) and points $(typeof(q)), $(typeof(p)) and $(typeof(a)).", + ) end function apply!(A::AbstractGroupAction{RightAction}, q, a, p) ainv = inv(base_group(A), a) @@ -91,11 +93,15 @@ differential transports vectors ```` """ function apply_diff(A::AbstractGroupAction, a, p, X) - return error("apply_diff not implemented for action $(typeof(A)), points $(typeof(a)) and $(typeof(p)), and vector $(typeof(X))") + return error( + "apply_diff not implemented for action $(typeof(A)), points $(typeof(a)) and $(typeof(p)), and vector $(typeof(X))", + ) end function apply_diff!(A::AbstractGroupAction, Y, a, p, X) - return error("apply_diff! not implemented for action $(typeof(A)), points $(typeof(a)) and $(typeof(p)), vectors $(typeof(Y)) and $(typeof(X))") + return error( + "apply_diff! not implemented for action $(typeof(A)), points $(typeof(a)) and $(typeof(p)), vectors $(typeof(Y)) and $(typeof(X))", + ) end @doc raw""" @@ -135,7 +141,9 @@ the element closest to `q` in the metric of the G-manifold: where $\mathcal{G}$ is the group that acts on the G-manifold $\mathcal M$. """ function optimal_alignment(A::AbstractGroupAction, p, q) - return error("optimal_alignment not implemented for $(typeof(A)) and points $(typeof(p)) and $(typeof(q)).") + return error( + "optimal_alignment not implemented for $(typeof(A)) and points $(typeof(p)) and $(typeof(q)).", + ) end """ diff --git a/src/groups/product_group.jl b/src/groups/product_group.jl index 9486c374bd..77da490cb1 100644 --- a/src/groups/product_group.jl +++ b/src/groups/product_group.jl @@ -100,12 +100,14 @@ compose(G::GT, ::Identity{GT}, p) where {GT<:ProductGroup} = p compose(G::GT, p, ::Identity{GT}) where {GT<:ProductGroup} = p compose(G::GT, e::E, ::E) where {GT<:ProductGroup,E<:Identity{GT}} = e function compose(M::ProductManifold, p::ProductRepr, q::ProductRepr) - return ProductRepr(map( - compose, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - )...) + return ProductRepr( + map( + compose, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + )..., + ) end function compose(M::ProductManifold, p, q) x = allocate_result(M, compose, p, q) @@ -131,13 +133,15 @@ function translate( q::ProductRepr, conv::ActionDirection, ) - return ProductRepr(map( - translate, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - repeated(conv), - )...) + return ProductRepr( + map( + translate, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + repeated(conv), + )..., + ) end function translate(M::ProductManifold, p, q, conv::ActionDirection) x = allocate_result(M, translate, p, q) @@ -168,13 +172,15 @@ function inverse_translate( q::ProductRepr, conv::ActionDirection, ) - return ProductRepr(map( - inverse_translate, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - repeated(conv), - )...) + return ProductRepr( + map( + inverse_translate, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + repeated(conv), + )..., + ) end function inverse_translate(M::ProductManifold, p, q, conv::ActionDirection) x = allocate_result(M, inverse_translate, p, q) @@ -206,14 +212,16 @@ function translate_diff( X::ProductRepr, conv::ActionDirection, ) - return ProductRepr(map( - translate_diff, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - submanifold_components(M, X), - repeated(conv), - )...) + return ProductRepr( + map( + translate_diff, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + submanifold_components(M, X), + repeated(conv), + )..., + ) end function translate_diff(M::ProductManifold, p, q, X, conv::ActionDirection) Y = allocate_result(M, translate_diff, X, p, q) @@ -246,14 +254,16 @@ function inverse_translate_diff( X::ProductRepr, conv::ActionDirection, ) - return ProductRepr(map( - inverse_translate_diff, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - submanifold_components(M, X), - repeated(conv), - )...) + return ProductRepr( + map( + inverse_translate_diff, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + submanifold_components(M, X), + repeated(conv), + )..., + ) end function inverse_translate_diff(M::ProductManifold, p, q, X, conv::ActionDirection) Y = allocate_result(M, inverse_translate_diff, X, p, q) diff --git a/src/manifolds/CenteredMatrices.jl b/src/manifolds/CenteredMatrices.jl index 4e3c4cee70..f00c2b8684 100644 --- a/src/manifolds/CenteredMatrices.jl +++ b/src/manifolds/CenteredMatrices.jl @@ -34,7 +34,9 @@ function check_manifold_point(M::CenteredMatrices{m,n,𝔽}, p; kwargs...) where if !isapprox(sum(p, dims=1), zeros(1, n); kwargs...) return DomainError( p, - string("The point $(p) does not lie on $(M), since its columns do not sum to zero."), + string( + "The point $(p) does not lie on $(M), since its columns do not sum to zero.", + ), ) end return nothing diff --git a/src/manifolds/Circle.jl b/src/manifolds/Circle.jl index b254f0d959..47c890bbd5 100644 --- a/src/manifolds/Circle.jl +++ b/src/manifolds/Circle.jl @@ -356,8 +356,9 @@ angles. mean(::Circle{ℂ}, ::Any) function Statistics.mean(M::Circle{ℂ}, x::AbstractVector{<:Complex}; kwargs...) s = sum(x) - abs(s) == 0 && - return error("The mean for $(x) on $(M) is not defined/unique, since the sum of the complex numbers is zero") + abs(s) == 0 && return error( + "The mean for $(x) on $(M) is not defined/unique, since the sum of the complex numbers is zero", + ) return s / abs(s) end function Statistics.mean( @@ -367,8 +368,9 @@ function Statistics.mean( kwargs..., ) s = sum(w .* x) - abs(s) == 0 && - error("The mean for $(x) on $(M) is not defined/unique, since the sum of the complex numbers is zero") + abs(s) == 0 && error( + "The mean for $(x) on $(M) is not defined/unique, since the sum of the complex numbers is zero", + ) return s /= abs(s) end diff --git a/src/manifolds/Euclidean.jl b/src/manifolds/Euclidean.jl index d2c44acb1c..e7dc5a7c85 100644 --- a/src/manifolds/Euclidean.jl +++ b/src/manifolds/Euclidean.jl @@ -149,10 +149,16 @@ function embed!( ln = length(n) m = size(q) lm = length(m) - (length(n) > length(m)) && - throw(DomainError("Invalid embedding, since Euclidean dimension ($(n)) is longer than embedding dimension $(m).")) - any(n .> m[1:ln]) && - throw(DomainError("Invalid embedding, since Euclidean dimension ($(n)) has entry larger than embedding dimensions ($(m)).")) + (length(n) > length(m)) && throw( + DomainError( + "Invalid embedding, since Euclidean dimension ($(n)) is longer than embedding dimension $(m).", + ), + ) + any(n .> m[1:ln]) && throw( + DomainError( + "Invalid embedding, since Euclidean dimension ($(n)) has entry larger than embedding dimensions ($(m)).", + ), + ) # put p into q fill!(q, 0) # fill „top left edge“ of q with p. @@ -384,17 +390,6 @@ in this case, just the (Frobenius) norm of `X`. LinearAlgebra.norm(::Euclidean, p, X) = norm(X) LinearAlgebra.norm(::MetricManifold{ℝ,<:Manifold,EuclideanMetric}, p, X) = norm(X) -""" - normal_tvector_distribution(M::Euclidean, p, σ) - -Normal distribution in ambient space with standard deviation `σ` -projected to tangent space at `p`. -""" -function normal_tvector_distribution(M::Euclidean{Tuple{N}}, p, σ) where {N} - d = Distributions.MvNormal(zero(p), σ) - return ProjectedFVectorDistribution(TangentBundleFibers(M), p, d, project!, p) -end - function project!( ::EmbeddedManifold{𝔽,Euclidean{nL,𝔽},Euclidean{mL,𝔽2}}, q, @@ -404,10 +399,16 @@ function project!( ln = length(n) m = size(q) lm = length(m) - (length(n) < length(m)) && - throw(DomainError("Invalid embedding, since Euclidean dimension ($(n)) is longer than embedding dimension $(m).")) - any(n .< m[1:ln]) && - throw(DomainError("Invalid embedding, since Euclidean dimension ($(n)) has entry larger than embedding dimensions ($(m)).")) + (length(n) < length(m)) && throw( + DomainError( + "Invalid embedding, since Euclidean dimension ($(n)) is longer than embedding dimension $(m).", + ), + ) + any(n .< m[1:ln]) && throw( + DomainError( + "Invalid embedding, since Euclidean dimension ($(n)) has entry larger than embedding dimensions ($(m)).", + ), + ) # fill q with the „top left edge“ of p. q .= p[map(i -> Base.OneTo(i), m)..., ntuple(_ -> 1, lm - ln)...] return q @@ -434,19 +435,6 @@ project(::Euclidean, ::Any, ::Any) project!(::Euclidean, Y, p, X) = copyto!(Y, X) -""" - projected_distribution(M::Euclidean, d, [p]) - -Wrap the standard distribution `d` into a manifold-valued distribution. Generated -points will be of similar type to `p`. By default, the type is not changed. -""" -function projected_distribution(M::Euclidean, d, p) - return ProjectedPointDistribution(M, d, project!, p) -end -function projected_distribution(M::Euclidean, d) - return ProjectedPointDistribution(M, d, project!, rand(d)) -end - """ representation_size(M::Euclidean) diff --git a/src/manifolds/HyperbolicHyperboloid.jl b/src/manifolds/HyperbolicHyperboloid.jl index 41a03034e0..7533538e5b 100644 --- a/src/manifolds/HyperbolicHyperboloid.jl +++ b/src/manifolds/HyperbolicHyperboloid.jl @@ -242,7 +242,8 @@ end function get_basis(M::Hyperbolic, p, B::DefaultOrthonormalBasis) n = manifold_dimension(M) V = [ - _hyperbolize(M, p, [i == k ? one(eltype(p)) : zero(eltype(p)) for k in 1:n]) for i in 1:n + _hyperbolize(M, p, [i == k ? one(eltype(p)) : zero(eltype(p)) for k in 1:n]) for + i in 1:n ] return CachedBasis(B, gram_schmidt(M, p, V)) end @@ -251,7 +252,8 @@ function get_basis(M::Hyperbolic, p, B::DiagonalizingOrthonormalBasis) n = manifold_dimension(M) X = B.frame_direction V = [ - _hyperbolize(M, p, [i == k ? one(eltype(p)) : zero(eltype(p)) for k in 1:n]) for i in 1:n + _hyperbolize(M, p, [i == k ? one(eltype(p)) : zero(eltype(p)) for k in 1:n]) for + i in 1:n ] κ = -ones(n) if norm(M, p, X) != 0 diff --git a/src/manifolds/HyperbolicPoincareBall.jl b/src/manifolds/HyperbolicPoincareBall.jl index 84a1c29d08..b1bf4ab634 100644 --- a/src/manifolds/HyperbolicPoincareBall.jl +++ b/src/manifolds/HyperbolicPoincareBall.jl @@ -227,6 +227,15 @@ the tangent space consists of all $ℝ^n$. """ project(::Hyperbolic, ::PoincareBallPoint, ::PoincareBallTVector) +function allocate_result( + ::Hyperbolic, + ::typeof(project), + X::PoincareBallTVector, + ::PoincareBallPoint, +) + return PoincareBallTVector(allocate(X.value)) +end + function project!( ::Hyperbolic, Y::PoincareBallTVector, diff --git a/src/manifolds/HyperbolicPoincareHalfspace.jl b/src/manifolds/HyperbolicPoincareHalfspace.jl index 1426194c46..dbc4addb7a 100644 --- a/src/manifolds/HyperbolicPoincareHalfspace.jl +++ b/src/manifolds/HyperbolicPoincareHalfspace.jl @@ -216,6 +216,15 @@ the tangent space consists of all $ℝ^n$. """ project(::Hyperbolic, ::PoincareHalfSpacePoint::PoincareHalfSpaceTVector) +function allocate_result( + ::Hyperbolic, + ::typeof(project), + X::PoincareHalfSpaceTVector, + ::PoincareHalfSpacePoint, +) + return PoincareHalfSpaceTVector(allocate(X.value)) +end + function project!( ::Hyperbolic, Y::PoincareHalfSpaceTVector, diff --git a/src/manifolds/MetricManifold.jl b/src/manifolds/MetricManifold.jl index c5f5f0c459..9cc6c5ec31 100644 --- a/src/manifolds/MetricManifold.jl +++ b/src/manifolds/MetricManifold.jl @@ -7,10 +7,8 @@ varying inner products on the tangent space. See [`inner`](@ref). abstract type Metric end # piping syntax for decoration -if VERSION ≥ v"1.3" - (metric::Metric)(M::Manifold) = MetricManifold(M, metric) - (::Type{T})(M::Manifold) where {T<:Metric} = MetricManifold(M, T()) -end +(metric::Metric)(M::Manifold) = MetricManifold(M, metric) +(::Type{T})(M::Manifold) where {T<:Metric} = MetricManifold(M, T()) """ MetricManifold{𝔽,M<:Manifold{𝔽},G<:Metric} <: AbstractDecoratorManifold{𝔽} @@ -202,13 +200,6 @@ decorator_transparent_dispatch(::typeof(median), M::MetricManifold, args...) = V function decorator_transparent_dispatch(::typeof(median!), M::MetricManifold, args...) return Val(:intransparent) end -function decorator_transparent_dispatch( - ::typeof(normal_tvector_distribution), - M::MetricManifold, - arge..., -) - return Val(:intransparent) -end function decorator_transparent_dispatch(::typeof(norm), M::MetricManifold, args...) return Val(:intransparent) end @@ -216,13 +207,6 @@ decorator_transparent_dispatch(::typeof(project), M::MetricManifold, args...) = function decorator_transparent_dispatch(::typeof(project!), M::MetricManifold, args...) return Val(:intransparent) end -function decorator_transparent_dispatch( - ::typeof(projected_distribution), - M::MetricManifold, - arge..., -) - return Val(:intransparent) -end decorator_transparent_dispatch(::typeof(sharp), M::MetricManifold, args...) = Val(:parent) function decorator_transparent_dispatch(::typeof(sharp!), M::MetricManifold, args...) return Val(:intransparent) @@ -441,7 +425,9 @@ function _convert_with_default(M::MT, T::Type{<:Metric}, ::Val{true}) where {MT< return MetricManifold(M, T()) end function _convert_with_default(M::MT, T::Type{<:Metric}, ::Val{false}) where {MT<:Manifold} - return error("Can not convert $(M) to a MetricManifold{$(MT),$(T)}, since $(T) is not the default metric.") + return error( + "Can not convert $(M) to a MetricManifold{$(MT),$(T)}, since $(T) is not the default metric.", + ) end @doc raw""" @@ -655,5 +641,7 @@ in an embedded space. ``` """ function solve_exp_ode(M, p, X, tspan; kwargs...) - return error("solve_exp_ode not implemented on $(typeof(M)) for point $(typeof(p)), vector $(typeof(X)), and timespan $(typeof(tspan)). For a suitable default, enter `using OrdinaryDiffEq` on Julia 1.1 or greater.") + return error( + "solve_exp_ode not implemented on $(typeof(M)) for point $(typeof(p)), vector $(typeof(X)), and timespan $(typeof(tspan)). For a suitable default, enter `using OrdinaryDiffEq` on Julia 1.1 or greater.", + ) end diff --git a/src/manifolds/MultinomialDoublyStochastic.jl b/src/manifolds/MultinomialDoublyStochastic.jl index 8441173b32..65fabb69e3 100644 --- a/src/manifolds/MultinomialDoublyStochastic.jl +++ b/src/manifolds/MultinomialDoublyStochastic.jl @@ -185,8 +185,11 @@ function project!( maxiter=100, tolerance=eps(eltype(p)), ) where {n} - any(p .<= 0) && - throw(DomainError("The matrix $p can not be projected, since it has nonpositive entries.")) + any(p .<= 0) && throw( + DomainError( + "The matrix $p can not be projected, since it has nonpositive entries.", + ), + ) iter = 0 d1 = sum(p, dims=1) d2 = 1 ./ (p * d1') diff --git a/src/manifolds/ProbabilitySimplex.jl b/src/manifolds/ProbabilitySimplex.jl index abbf6165c8..51c5ebdf70 100644 --- a/src/manifolds/ProbabilitySimplex.jl +++ b/src/manifolds/ProbabilitySimplex.jl @@ -309,10 +309,12 @@ project(::ProbabilitySimplex, ::Any, ::Any) function project!(::ProbabilitySimplex, q, p) if any(x -> x <= 0, p) - throw(DomainError( - p, - "All coordinates of point from the embedding, that should be projected, must be positive, otherwise the projection is not well defined.", - )) + throw( + DomainError( + p, + "All coordinates of point from the embedding, that should be projected, must be positive, otherwise the projection is not well defined.", + ), + ) end q .= p ./ sum(p) return q diff --git a/src/manifolds/ProductManifold.jl b/src/manifolds/ProductManifold.jl index 857c62c393..e372d44523 100644 --- a/src/manifolds/ProductManifold.jl +++ b/src/manifolds/ProductManifold.jl @@ -223,14 +223,16 @@ Compute the distance between two points `p` and `q` on the [`ProductManifold`](@ the 2-norm of the elementwise distances on the internal manifolds that build `M`. """ function distance(M::ProductManifold, p, q) - return sqrt(sum( - map( - distance, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - ) .^ 2, - )) + return sqrt( + sum( + map( + distance, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + ) .^ 2, + ), + ) end @doc raw""" @@ -241,12 +243,14 @@ which is the elementwise exponential map on the internal manifolds that build `M """ exp(::ProductManifold, ::Any...) function Base.exp(M::ProductManifold, p::ProductRepr, X::ProductRepr) - return ProductRepr(map( - exp, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )...) + return ProductRepr( + map( + exp, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, X), + )..., + ) end function exp!(M::ProductManifold, q, p, X) @@ -285,11 +289,13 @@ function get_basis(M::ProductManifold, p, B::CachedBasis) return invoke(get_basis, Tuple{Manifold,Any,CachedBasis}, M, p, B) end function get_basis(M::ProductManifold, p, B::DiagonalizingOrthonormalBasis) - vs = map(ziptuples( - M.manifolds, - submanifold_components(p), - submanifold_components(B.frame_direction), - )) do t + vs = map( + ziptuples( + M.manifolds, + submanifold_components(p), + submanifold_components(B.frame_direction), + ), + ) do t return get_basis(t[1], t[2], DiagonalizingOrthonormalBasis(t[3])) end return CachedBasis(B, ProductBasisData(vs)) @@ -561,7 +567,9 @@ for BT in PRODUCT_BASIS_LIST ) end function get_vector!(M::ProductManifold, Y, p, X, B::CachedBasis) - return error("get_vector! called on $M with an incorrect CachedBasis. Expected a CachedBasis with ProductBasisData, given $B") + return error( + "get_vector! called on $M with an incorrect CachedBasis. Expected a CachedBasis with ProductBasisData, given $B", + ) end function get_vectors( @@ -615,19 +623,23 @@ function injectivity_radius(M::ProductManifold, p) return min(map(injectivity_radius, M.manifolds, submanifold_components(M, p))...) end function injectivity_radius(M::ProductManifold, p, m::AbstractRetractionMethod) - return min(map( - (lM, lp) -> injectivity_radius(lM, lp, m), - M.manifolds, - submanifold_components(M, p), - )...) + return min( + map( + (lM, lp) -> injectivity_radius(lM, lp, m), + M.manifolds, + submanifold_components(M, p), + )..., + ) end function injectivity_radius(M::ProductManifold, p, m::ProductRetraction) - return min(map( - (lM, lp, lm) -> injectivity_radius(lM, lp, lm), - M.manifolds, - submanifold_components(M, p), - m.retractions, - )...) + return min( + map( + (lM, lp, lm) -> injectivity_radius(lM, lp, lm), + M.manifolds, + submanifold_components(M, p), + m.retractions, + )..., + ) end eval( quote @@ -722,12 +734,14 @@ which can be computed using the logarithmic maps of the manifolds elementwise. """ log(::ProductManifold, ::Any...) function Base.log(M::ProductManifold, p::ProductRepr, q::ProductRepr) - return ProductRepr(map( - log, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - )...) + return ProductRepr( + map( + log, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + )..., + ) end function log!(M::ProductManifold, X, p, q) @@ -807,7 +821,9 @@ function ProductFVectorDistribution(distributions::FVectorDistribution...) M = ProductManifold(map(d -> support(d).space.manifold, distributions)...) fiber = support(distributions[1]).space.fiber if !all(d -> support(d).space.fiber == fiber, distributions) - error("Not all distributions have support in vector spaces of the same type, which is currently not supported") + error( + "Not all distributions have support in vector spaces of the same type, which is currently not supported", + ) end # Probably worth considering sum spaces in the future? x = ProductRepr(map(d -> support(d).point, distributions)...) @@ -832,12 +848,14 @@ function project!(M::ProductManifold, q, p) end function project(M::ProductManifold, p::ProductRepr, X::ProductRepr) - return ProductRepr(map( - project, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )...) + return ProductRepr( + map( + project, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, X), + )..., + ) end function project!(M::ProductManifold, Y, p, X) diff --git a/src/manifolds/ProjectiveSpace.jl b/src/manifolds/ProjectiveSpace.jl index 23d0cdb721..c1559b5a6c 100644 --- a/src/manifolds/ProjectiveSpace.jl +++ b/src/manifolds/ProjectiveSpace.jl @@ -392,18 +392,6 @@ function mid_point!(M::ProjectiveSpace, q, p1, p2) return q end -""" - normal_tvector_distribution(M::ProjectiveSpace{n,ℝ}, p, σ) - -Generate a distribution in the tangent space at `p` by generating a -normal distribution in ambient space with standard deviation `σ` -projected to the tangent space at `p`. -""" -function normal_tvector_distribution(M::ProjectiveSpace{n,ℝ}, p, σ) where {n} - d = Distributions.MvNormal(zero(p), σ) - return ProjectedFVectorDistribution(TangentBundleFibers(M), p, d, project!, p) -end - @doc raw""" project(M::AbstractProjectiveSpace, p) diff --git a/src/manifolds/Rotations.jl b/src/manifolds/Rotations.jl index 34de62923e..aed0b73c04 100644 --- a/src/manifolds/Rotations.jl +++ b/src/manifolds/Rotations.jl @@ -587,17 +587,6 @@ function normal_rotation_distribution(M::Rotations{N}, p, σ::Real) where {N} return NormalRotationDistribution(M, d, p) end -""" - normal_tvector_distribution(M::Rotations, p, σ) - -Normal distribution in ambient space with standard deviation `σ` -projected to tangent space at `p`. -""" -function normal_tvector_distribution(M::Rotations, p, σ) - d = Distributions.MvNormal(reshape(zero(p), :), σ) - return ProjectedFVectorDistribution(TangentBundleFibers(M), p, d, project!, p) -end - @doc raw""" project(M::Rotations, p; check_det = true) @@ -727,6 +716,8 @@ end Base.show(io::IO, ::Rotations{N}) where {N} = print(io, "Rotations($(N))") +Distributions.support(d::NormalRotationDistribution) = MPointSupport(d.manifold) + @doc raw""" zero_tangent_vector(M::Rotations, p) diff --git a/src/manifolds/Sphere.jl b/src/manifolds/Sphere.jl index c7cab35410..e3f689dffa 100644 --- a/src/manifolds/Sphere.jl +++ b/src/manifolds/Sphere.jl @@ -372,18 +372,6 @@ function mid_point!(S::Sphere, q, p1, p2) return q end -""" - normal_tvector_distribution(S::Sphere{n,ℝ}, p, σ) - -Generate a distribution in the tangent space at `p` by generating a -normal distribution in ambient space with standard deviation `σ` -projected to the tangent space at `p`. -""" -function normal_tvector_distribution(S::Sphere{n,ℝ}, p, σ) where {n} - d = Distributions.MvNormal(zero(p), σ) - return ProjectedFVectorDistribution(TangentBundleFibers(S), p, d, project!, p) -end - @doc raw""" project(M::AbstractSphere, p) diff --git a/src/manifolds/Stiefel.jl b/src/manifolds/Stiefel.jl index 0157d3475c..243d2f2e5b 100644 --- a/src/manifolds/Stiefel.jl +++ b/src/manifolds/Stiefel.jl @@ -43,8 +43,9 @@ A retraction based on the Padé approximation of order $m$ struct PadeRetraction{m} <: AbstractRetractionMethod end function PadeRetraction(m::Int) - (m < 1) && - error("The Padé based retraction is only available for positive orders, not for order $m.") + (m < 1) && error( + "The Padé based retraction is only available for positive orders, not for order $m.", + ) return PadeRetraction{m}() end @doc raw""" @@ -199,8 +200,9 @@ end function get_vector!(M::Stiefel{n,k,ℝ}, X, p, c, B::DefaultOrthonormalBasis{ℝ}) where {n,k} V = get_vectors(M, p, B) zero_tangent_vector!(M, X, p) - length(c) < length(V) && - error("Coordinate vector too short. Excpected $(length(V)), but only got $(length(c)) entries.") + length(c) < length(V) && error( + "Coordinate vector too short. Excpected $(length(V)), but only got $(length(c)) entries.", + ) @inbounds for i in 1:length(V) X .+= c[i] .* V[i] end diff --git a/src/manifolds/SymmetricPositiveDefiniteLinearAffine.jl b/src/manifolds/SymmetricPositiveDefiniteLinearAffine.jl index 2e8d10bb6d..f71a01550e 100644 --- a/src/manifolds/SymmetricPositiveDefiniteLinearAffine.jl +++ b/src/manifolds/SymmetricPositiveDefiniteLinearAffine.jl @@ -82,8 +82,8 @@ function get_basis( V = eigv.vectors Ξ = [ (i == j ? 1 / 2 : 1 / sqrt(2)) * - (V[:, i] * transpose(V[:, j]) + V[:, j] * transpose(V[:, i])) for i in 1:N - for j in i:N + (V[:, i] * transpose(V[:, j]) + V[:, j] * transpose(V[:, i])) for i in 1:N for + j in i:N ] λ = eigv.values κ = [-1 / 4 * (λ[i] - λ[j])^2 for i in 1:N for j in i:N] diff --git a/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl b/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl index d868f9f7b7..6b848a09e2 100644 --- a/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl +++ b/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl @@ -78,7 +78,7 @@ function check_manifold_point( invoke(check_manifold_point, Tuple{supertype(typeof(M)),typeof(q)}, M, q; kwargs...) mpv === nothing || return mpv p = q * q' - r = (VERSION >= v"1.1") ? rank(p * p'; kwargs...) : rank(p * p') + r = rank(p * p'; kwargs...) if r < k return DomainError( r, diff --git a/src/manifolds/VectorBundle.jl b/src/manifolds/VectorBundle.jl index fd33110bd5..b66e4c5998 100644 --- a/src/manifolds/VectorBundle.jl +++ b/src/manifolds/VectorBundle.jl @@ -467,7 +467,9 @@ for BT in [ ) end function get_coordinates!(M::VectorBundle, Y, p, X, B::CachedBasis) - return error("get_coordinates! called on $M with an incorrect CachedBasis. Expected a CachedBasis with VectorBundleBasisData, given $B") + return error( + "get_coordinates! called on $M with an incorrect CachedBasis. Expected a CachedBasis with VectorBundleBasisData, given $B", + ) end function get_coordinates!(M::TangentSpaceAtPoint, Y, p, X, B::CachedBasis) return get_coordinates!(M.fiber.manifold, Y, M.point, X, B) @@ -826,7 +828,9 @@ function project!(B::VectorBundleFibers{<:TangentSpaceType}, Y, p, X) return project!(B.manifold, Y, p, X) end function project!(B::VectorBundleFibers, Y, p, X) - return error("project! not implemented for vector space family of type $(typeof(B)), output vector of type $(typeof(Y)) and input vector at point $(typeof(p)) with type of w $(typeof(X)).") + return error( + "project! not implemented for vector space family of type $(typeof(B)), output vector of type $(typeof(Y)) and input vector at point $(typeof(p)) with type of w $(typeof(X)).", + ) end Base.@propagate_inbounds Base.setindex!(x::FVector, val, i) = setindex!(x.data, val, i) @@ -983,7 +987,9 @@ vector_bundle_transport(::VectorSpaceType, ::Manifold) = ParallelTransport() Dimension of the vector space of type `B`. """ function vector_space_dimension(B::VectorBundleFibers) - return error("vector_space_dimension not implemented for vector space family $(typeof(B)).") + return error( + "vector_space_dimension not implemented for vector space family $(typeof(B)).", + ) end function vector_space_dimension(B::VectorBundleFibers{<:TCoTSpaceType}) return manifold_dimension(B.manifold) @@ -1070,7 +1076,9 @@ Save the zero vector from the vector space of type `B.fiber` at point `p` from manifold `B.manifold` to `X`. """ function zero_vector!(B::VectorBundleFibers, X, p) - return error("zero_vector! not implemented for vector space family of type $(typeof(B)).") + return error( + "zero_vector! not implemented for vector space family of type $(typeof(B)).", + ) end function zero_vector!(B::VectorBundleFibers{<:TangentSpaceType}, X, p) return zero_tangent_vector!(B.manifold, X, p) diff --git a/src/product_representations.jl b/src/product_representations.jl index ea424ed46b..51f4573c26 100644 --- a/src/product_representations.jl +++ b/src/product_representations.jl @@ -13,7 +13,9 @@ struct StaticReshaper <: AbstractReshaper end Reshape array `data` to size `Size` using method provided by `reshaper`. """ function make_reshape(reshaper::AbstractReshaper, ::Type{Size}, data) where {Size} - return error("make_reshape is not defined for reshaper of type $(typeof(reshaper)), size $(Size) and data of type $(typeof(data)).") + return error( + "make_reshape is not defined for reshaper of type $(typeof(reshaper)), size $(Size) and data of type $(typeof(data)).", + ) end function make_reshape(::StaticReshaper, ::Type{Size}, data) where {Size} return SizedArray{Size}(data) diff --git a/src/projected_distribution.jl b/src/projected_distribution.jl index b4ea5c9903..6f8fd5969c 100644 --- a/src/projected_distribution.jl +++ b/src/projected_distribution.jl @@ -25,6 +25,16 @@ function ProjectedPointDistribution( ) end +""" + projected_distribution(M::Manifold, d, [p=rand(d)]) + +Wrap the standard distribution `d` into a manifold-valued distribution. Generated +points will be of similar type to `p`. By default, the type is not changed. +""" +function projected_distribution(M::Manifold, d, p=rand(d)) + return ProjectedPointDistribution(M, d, project!, p) +end + function Random.rand( rng::AbstractRNG, d::ProjectedPointDistribution{TResult}, @@ -103,6 +113,17 @@ function Distributions._rand!( return copyto!(X, rand(rng, d)) end +""" + normal_tvector_distribution(M::Euclidean, p, σ) + +Normal distribution in ambient space with standard deviation `σ` +projected to tangent space at `p`. +""" +function normal_tvector_distribution(M::Manifold, p, σ) + d = Distributions.MvNormal(zero(vec(p)), σ) + return ProjectedFVectorDistribution(TangentBundleFibers(M), p, d, project!, p) +end + function Distributions.support(tvd::ProjectedFVectorDistribution) return FVectorSupport(tvd.type, tvd.point) end diff --git a/src/recipes.jl b/src/recipes.jl index 4b34809a9f..ace1b292f2 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -150,14 +150,14 @@ end x, y, z end end - # part II: solid sphere + # part II: solid hyperboloid if surface x = range(min(px...), max(px...), length=surface_resolution_x) y = range(min(py...), max(py...), length=surface_resolution_y) z = sqrt.(1 .+ (x .^ 2)' .+ y .^ 2) @series begin seriestype := :surface - color := surface_color + seriescolor := surface_color x, y, z end end @@ -253,7 +253,7 @@ end z = repeat(cos.(v)', outer=[wires_lon + 1, 1]) @series begin seriestype := :surface - color := surface_color + seriescolor := surface_color return x, y, z end end diff --git a/src/riemannian_diff.jl b/src/riemannian_diff.jl index 7ef0c9c40b..c4b4e7b1ba 100644 --- a/src/riemannian_diff.jl +++ b/src/riemannian_diff.jl @@ -134,12 +134,14 @@ globally default differentiation backend for calculating gradients. [`Manifolds.gradient(::Manifold, ::Any, ::Any, ::AbstractRiemannianDiffBackend)`](@ref) """ -const _current_rgradient_backend = CurrentRiemannianDiffBackend(RiemannianONBDiffBackend( - diff_backend(), - ExponentialRetraction(), - LogarithmicInverseRetraction(), - DefaultOrthonormalBasis(), -),) +const _current_rgradient_backend = CurrentRiemannianDiffBackend( + RiemannianONBDiffBackend( + diff_backend(), + ExponentialRetraction(), + LogarithmicInverseRetraction(), + DefaultOrthonormalBasis(), + ), +) """ _current_rdifferential_backend @@ -151,13 +153,14 @@ globally default differentiation backend for calculating differentials. [`Manifolds.differential`](@ref) """ -const _current_rdifferential_backend = - CurrentRiemannianDiffBackend(RiemannianONBDiffBackend( +const _current_rdifferential_backend = CurrentRiemannianDiffBackend( + RiemannianONBDiffBackend( diff_backend(), ExponentialRetraction(), LogarithmicInverseRetraction(), DefaultOrthonormalBasis(), - ),) + ), +) """ rgradient_backend() -> AbstractRiemannianDiffBackend diff --git a/src/statistics.jl b/src/statistics.jl index 64fb79240b..59eec7f8b6 100644 --- a/src/statistics.jl +++ b/src/statistics.jl @@ -125,7 +125,9 @@ struct GeodesicInterpolationWithinRadius{T} <: AbstractEstimationMethod function GeodesicInterpolationWithinRadius(radius::T) where {T} radius > 0 && return new{T}(radius) - return throw(DomainError("The radius must be strictly postive, received $(radius).")) + return throw( + DomainError("The radius must be strictly postive, received $(radius)."), + ) end end @@ -261,7 +263,11 @@ function Statistics.mean!( ) n = length(x) if length(w) != n - throw(DimensionMismatch("The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).")) + throw( + DimensionMismatch( + "The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).", + ), + ) end copyto!(y, p0) yold = allocate_result(M, mean, y) @@ -319,7 +325,11 @@ function Statistics.mean!( ) n = length(x) if length(w) != n - throw(DimensionMismatch("The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).")) + throw( + DimensionMismatch( + "The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).", + ), + ) end order = shuffle_rng === nothing ? (1:n) : shuffle(shuffle_rng, 1:n) @inbounds begin @@ -395,7 +405,11 @@ function Statistics.mean!( ) n = length(x) if length(w) != n - throw(DimensionMismatch("The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).")) + throw( + DimensionMismatch( + "The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).", + ), + ) end copyto!(q, p0) yold = allocate_result(M, mean, q) @@ -603,7 +617,11 @@ function Statistics.median!( ) n = length(x) if length(w) != n - throw(DimensionMismatch("The number of weights ($(length(w))) does not match the number of points for the median ($(n)).")) + throw( + DimensionMismatch( + "The number of weights ($(length(w))) does not match the number of points for the median ($(n)).", + ), + ) end copyto!(q, p0) yold = allocate_result(M, median, q) @@ -787,7 +805,11 @@ function StatsBase.mean_and_var( ) n = length(x) if length(w) != n - throw(DimensionMismatch("The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).")) + throw( + DimensionMismatch( + "The number of weights ($(length(w))) does not match the number of points for the mean ($(n)).", + ), + ) end order = shuffle_rng === nothing ? (1:n) : shuffle(shuffle_rng, 1:n) @inbounds begin diff --git a/src/tests/tests_general.jl b/src/tests/tests_general.jl index fbc9a0e2b6..54eefb4b32 100644 --- a/src/tests/tests_general.jl +++ b/src/tests/tests_general.jl @@ -134,19 +134,15 @@ function ManifoldTests.test_manifold( ) for i in 1:n ] end - Test.Test.@testset "dimension" begin + Test.@testset "dimension" begin Test.@test isa(manifold_dimension(M), expected_dimension_type) Test.@test manifold_dimension(M) ≥ 0 - Test.@test manifold_dimension(M) == - vector_space_dimension(Manifolds.VectorBundleFibers( - Manifolds.TangentSpace, - M, - )) - Test.@test manifold_dimension(M) == - vector_space_dimension(Manifolds.VectorBundleFibers( - Manifolds.CotangentSpace, - M, - )) + Test.@test manifold_dimension(M) == vector_space_dimension( + Manifolds.VectorBundleFibers(Manifolds.TangentSpace, M), + ) + Test.@test manifold_dimension(M) == vector_space_dimension( + Manifolds.VectorBundleFibers(Manifolds.CotangentSpace, M), + ) end test_representation_size && Test.@testset "representation" begin @@ -629,6 +625,7 @@ function ManifoldTests.test_manifold( for p in pts prand = allocate(p) for pd in point_distributions + Test.@test Manifolds.support(pd) isa Manifolds.MPointSupport{typeof(M)} for _ in 1:10 Test.@test is_manifold_point(M, rand(pd)) if test_mutating_rand @@ -643,6 +640,7 @@ function ManifoldTests.test_manifold( Test.@testset "tangent vector distributions" begin for tvd in tvector_distributions supp = Manifolds.support(tvd) + Test.@test supp isa Manifolds.FVectorSupport{TangentBundleFibers{typeof(M)}} for _ in 1:10 randtv = rand(tvd) atol = rand_tvector_atol_multiplier * ManifoldTests.find_eps(randtv) diff --git a/src/tests/tests_group.jl b/src/tests/tests_group.jl index 43a0e6128c..84b36a6f3a 100644 --- a/src/tests/tests_group.jl +++ b/src/tests/tests_group.jl @@ -34,20 +34,20 @@ function ManifoldTests.test_group( ) e = make_identity(G, g_pts[1]) - Test.Test.@testset "Basic group properties" begin - Test.Test.@testset "Closed" begin + Test.@testset "Basic group properties" begin + Test.@testset "Closed" begin for g1 in g_pts, g2 in g_pts g3 = compose(G, g1, g2) Test.@test is_manifold_point(G, g3, true; atol=atol) end end - Test.Test.@testset "Associative" begin + Test.@testset "Associative" begin g12_3 = compose(G, compose(G, g_pts[1], g_pts[2]), g_pts[3]) g1_23 = compose(G, g_pts[1], compose(G, g_pts[2], g_pts[3])) Test.@test isapprox(G, g12_3, g1_23; atol=atol) - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin g12, g23, g12_3, g1_23 = allocate.(repeat([g_pts[1]], 4)) Test.@test compose!(G, g12, g_pts[1], g_pts[2]) === g12 Test.@test compose!(G, g23, g_pts[2], g_pts[3]) === g23 @@ -57,7 +57,7 @@ function ManifoldTests.test_group( end end - Test.Test.@testset "Identity" begin + Test.@testset "Identity" begin Test.@test isapprox(G, e, e) Test.@test identity(G, e) === e Test.@test compose(G, e, e) === e @@ -72,7 +72,7 @@ function ManifoldTests.test_group( Test.@test isapprox(G, compose(G, ge, g), g) end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for g in g_pts h = allocate(g) Test.@test compose!(G, h, g, e) === h @@ -93,7 +93,7 @@ function ManifoldTests.test_group( end end - Test.Test.@testset "Inverse" begin + Test.@testset "Inverse" begin for g in g_pts ginv = inv(G, g) Test.@test isapprox(G, compose(G, g, ginv), e; atol=atol) @@ -102,7 +102,7 @@ function ManifoldTests.test_group( Test.@test isapprox(G, e, compose(G, ginv, g); atol=atol) Test.@test inv(G, e) === e - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin ginv = allocate(g) Test.@test inv!(G, ginv, g) === ginv Test.@test isapprox(G, compose(G, g, ginv), e; atol=atol) @@ -117,7 +117,7 @@ function ManifoldTests.test_group( end end - Test.Test.@testset "translation" begin + Test.@testset "translation" begin convs = ((), (LeftAction(),), (RightAction(),)) Test.@test isapprox( @@ -164,7 +164,7 @@ function ManifoldTests.test_group( ) end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for conv in convs g = allocate(g_pts[1]) Test.@test translate!(G, g, g_pts[1], g_pts[2], conv...) === g @@ -188,7 +188,7 @@ function ManifoldTests.test_group( end end - test_diff && Test.Test.@testset "translation differential" begin + test_diff && Test.@testset "translation differential" begin X = v_pts[1] g21 = compose(G, g_pts[2], g_pts[1]) g12 = compose(G, g_pts[1], g_pts[2]) @@ -254,7 +254,7 @@ function ManifoldTests.test_group( ) end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for conv in diff_convs g2g1 = translate(G, g_pts[2], g_pts[1], conv...) g2invg1 = inverse_translate(G, g_pts[2], g_pts[1], conv...) @@ -281,13 +281,13 @@ function ManifoldTests.test_group( end end - test_group_exp_log && Test.Test.@testset "group exp/log properties" begin - Test.Test.@testset "e = exp(0)" begin + test_group_exp_log && Test.@testset "group exp/log properties" begin + Test.@testset "e = exp(0)" begin v = group_log(G, identity(G, g_pts[1])) g = group_exp(G, v) Test.@test isapprox(G, make_identity(G, g_pts[1]), g; atol=atol) - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin v = allocate(ve_pts[1]) Test.@test group_log!(G, v, identity(G, g_pts[1])) === v g = allocate(g_pts[1]) @@ -296,7 +296,7 @@ function ManifoldTests.test_group( end end - Test.Test.@testset "v = log(exp(v))" begin + Test.@testset "v = log(exp(v))" begin for v in ve_pts g = group_exp(G, v) Test.@test is_manifold_point(G, g; atol=atol) @@ -304,7 +304,7 @@ function ManifoldTests.test_group( Test.@test isapprox(G, make_identity(G, g_pts[1]), v2, v; atol=atol) end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for v in ve_pts g = allocate(g_pts[1]) Test.@test group_exp!(G, g, v) === g @@ -317,14 +317,14 @@ function ManifoldTests.test_group( end end - Test.Test.@testset "inv(g) = exp(-log(g))" begin + Test.@testset "inv(g) = exp(-log(g))" begin g = g_pts[1] v = group_log(G, g) ginv = group_exp(G, -v) Test.@test isapprox(G, ginv, inv(G, g); atol=atol) end - Test.Test.@testset "exp(sv)∘exp(tv) = exp((s+t)v)" begin + Test.@testset "exp(sv)∘exp(tv) = exp((s+t)v)" begin g1 = group_exp(G, 0.2 * ve_pts[1]) g2 = group_exp(G, 0.3 * ve_pts[1]) g12 = group_exp(G, 0.5 * ve_pts[1]) @@ -337,7 +337,7 @@ function ManifoldTests.test_group( test_group_exp_log && test_diff && - Test.Test.@testset "exp/log retract/inverse_retract" begin + Test.@testset "exp/log retract/inverse_retract" begin for conv in diff_convs y = retract( G, @@ -355,7 +355,7 @@ function ManifoldTests.test_group( Test.@test isapprox(G, g_pts[1], v2, v_pts[1]; atol=atol) end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for conv in diff_convs y = allocate(g_pts[1]) Test.@test retract!( @@ -379,9 +379,9 @@ function ManifoldTests.test_group( end end - test_invariance && Test.Test.@testset "metric invariance" begin + test_invariance && Test.@testset "metric invariance" begin if has_invariant_metric(G, LeftAction()) - Test.Test.@testset "left-invariant" begin + Test.@testset "left-invariant" begin Test.@test has_approx_invariant_metric( G, g_pts[1], @@ -393,7 +393,7 @@ function ManifoldTests.test_group( end end if invariant_metric_dispatch(G, RightAction()) === Val(true) - Test.Test.@testset "right-invariant" begin + Test.@testset "right-invariant" begin Test.@test has_approx_invariant_metric( G, g_pts[1], @@ -446,8 +446,8 @@ function ManifoldTests.test_action( M = g_manifold(A) e = make_identity(G, a_pts[1]) - Test.Test.@testset "Basic action properties" begin - test_switch_direction && Test.Test.@testset "Direction" begin + Test.@testset "Basic action properties" begin + test_switch_direction && Test.@testset "Direction" begin Aswitch = switch_direction(A) if isa(A, AbstractGroupAction{LeftAction}) Test.@test direction(A) === LeftAction() @@ -460,14 +460,14 @@ function ManifoldTests.test_action( end end - Test.Test.@testset "Closed" begin - Test.Test.@testset "over actions" begin + Test.@testset "Closed" begin + Test.@testset "over actions" begin for a1 in a_pts, a2 in a_pts a3 = compose(A, a1, a2) Test.@test is_manifold_point(G, a3, true; atol=atol) end end - Test.Test.@testset "over g-manifold" begin + Test.@testset "over g-manifold" begin for a in a_pts, m in m_pts Test.@test is_manifold_point(M, apply(A, a, m), true; atol=atol) Test.@test is_manifold_point(M, inverse_apply(A, a, m), true; atol=atol) @@ -475,17 +475,17 @@ function ManifoldTests.test_action( end end - Test.Test.@testset "Associative" begin + Test.@testset "Associative" begin a12 = compose(A, a_pts[1], a_pts[2]) a23 = compose(A, a_pts[2], a_pts[3]) - Test.Test.@testset "over compose" begin + Test.@testset "over compose" begin a12_a3 = compose(A, a12, a_pts[3]) a1_a23 = compose(A, a_pts[1], a23) Test.@test isapprox(G, a12_a3, a1_a23; atol=atol) end - Test.Test.@testset "over apply" begin + Test.@testset "over apply" begin for m in m_pts a12_a3_m = apply(A, a12, apply(A, a_pts[3], m)) a1_a23_m = apply(A, a_pts[1], apply(A, a23, m)) @@ -493,7 +493,7 @@ function ManifoldTests.test_action( end end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin a12, a23, a12_3, a1_23 = allocate.(repeat([a_pts[1]], 4)) Test.@test compose!(A, a12, a_pts[1], a_pts[2]) === a12 Test.@test compose!(A, a23, a_pts[2], a_pts[3]) === a23 @@ -510,7 +510,7 @@ function ManifoldTests.test_action( end end - Test.Test.@testset "Identity" begin + Test.@testset "Identity" begin Test.@test compose(A, e, e) === e for a in a_pts @@ -529,7 +529,7 @@ function ManifoldTests.test_action( end end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for a in a_pts h = allocate(a) Test.@test compose!(A, h, a, e) === h @@ -564,7 +564,7 @@ function ManifoldTests.test_action( end end - Test.Test.@testset "Inverse" begin + Test.@testset "Inverse" begin for a in a_pts ainv = inv(G, a) Test.@test isapprox(G, compose(A, a, ainv), e; atol=atol) @@ -590,7 +590,7 @@ function ManifoldTests.test_action( end end - test_diff && Test.Test.@testset "apply differential" begin + test_diff && Test.@testset "apply differential" begin for (m, v) in zip(m_pts, v_pts) for a in a_pts am, av = apply(A, a, m), apply_diff(A, a, m, v) @@ -609,7 +609,7 @@ function ManifoldTests.test_action( Test.@test isapprox(M, m, inverse_apply_diff(A, e, m, v), v; atol=atol) end - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin for (m, v) in zip(m_pts, v_pts) for a in a_pts am = apply(A, a, m) @@ -641,14 +641,14 @@ function ManifoldTests.test_action( end end - test_optimal_alignment && Test.Test.@testset "Center of orbit" begin + test_optimal_alignment && Test.@testset "Center of orbit" begin act = center_of_orbit(A, [m_pts[1]], m_pts[2]) act2 = center_of_orbit(A, [m_pts[1]], m_pts[2], GradientDescentEstimation()) act_opt = optimal_alignment(A, m_pts[2], m_pts[1]) Test.@test isapprox(G, act, act_opt; atol=atol) Test.@test isapprox(G, act2, act_opt; atol=atol) - test_mutating && Test.Test.@testset "mutating" begin + test_mutating && Test.@testset "mutating" begin act_opt2 = allocate(act_opt) optimal_alignment!(A, act_opt2, m_pts[2], m_pts[1]) Test.@test isapprox(G, act_opt, act_opt2; atol=atol) diff --git a/src/utils.jl b/src/utils.jl index 6036a644f3..367c5d2089 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -162,8 +162,3 @@ end end return ex end - -# TODO: make a better implementation for StaticArrays -function LinearAlgebra.eigvals(A::StaticArray, B::StaticArray; kwargs...) - return eigvals(Array(A), Array(B); kwargs...) -end diff --git a/test/assets/Hyp2SurfPlot.png b/test/assets/Hyp2SurfPlot.png new file mode 100644 index 0000000000000000000000000000000000000000..b973c3e2c56107a7528449b32eb994e7f1fc9b9b GIT binary patch literal 13112 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 v"1.1") && @testset "Ambiguities" begin - # TODO: reduce the number of ambiguities - if VERSION.prerelease == () # - @test length(Test.detect_ambiguities(ManifoldsBase)) <= 17 - @test length(Test.detect_ambiguities(Manifolds)) == 0 - @test length(our_base_ambiguities()) <= 24 - else - @info "Skipping Ambiguity tests for pre-release versions" + @testset "utils test" begin + @test Manifolds.usinc_from_cos(-1) == 0 + @test Manifolds.usinc_from_cos(-1.0) == 0.0 end -end -include("utils.jl") + include_test("groups/group_utils.jl") + include_test("notation.jl") + # starting with tests of simple manifolds + include_test("centered_matrices.jl") + include_test("circle.jl") + include_test("cholesky_space.jl") + include_test("elliptope.jl") + include_test("euclidean.jl") + include_test("fixed_rank.jl") + include_test("generalized_grassmann.jl") + include_test("generalized_stiefel.jl") + include_test("grassmann.jl") + include_test("hyperbolic.jl") + include_test("multinomial_doubly_stochastic.jl") + include_test("multinomial_symmetric.jl") + include_test("positive_numbers.jl") + include_test("probability_simplex.jl") + include_test("projective_space.jl") + include_test("rotations.jl") + include_test("skewsymmetric.jl") + include_test("spectrahedron.jl") + include_test("sphere.jl") + include_test("sphere_symmetric_matrices.jl") + include_test("stiefel.jl") + include_test("symmetric.jl") + include_test("symmetric_positive_definite.jl") + include_test("symmetric_positive_semidefinite_fixed_rank.jl") -@info "Manifolds.jl Test settings:\n\n" * - "Testing Float32: $(TEST_FLOAT32)\n" * - "Testing Double64: $(TEST_DOUBLE64)\n" * - "Testing Static: $(TEST_STATIC_SIZED)\n" + include_test("multinomial_matrices.jl") + include_test("oblique.jl") + include_test("torus.jl") -include("groups/group_utils.jl") -include("notation.jl") -# starting with tests of simple manifolds -include("centered_matrices.jl") -include("circle.jl") -include("cholesky_space.jl") -include("elliptope.jl") -include("euclidean.jl") -include("fixed_rank.jl") -include("generalized_grassmann.jl") -include("generalized_stiefel.jl") -include("grassmann.jl") -include("hyperbolic.jl") -include("multinomial_doubly_stochastic.jl") -include("multinomial_symmetric.jl") -include("positive_numbers.jl") -include("probability_simplex.jl") -include("projective_space.jl") -include("rotations.jl") -include("skewsymmetric.jl") -include("spectrahedron.jl") -include("sphere.jl") -include("sphere_symmetric_matrices.jl") -include("stiefel.jl") -include("symmetric.jl") -include("symmetric_positive_definite.jl") -include("symmetric_positive_semidefinite_fixed_rank.jl") + #meta manifolds + include_test("product_manifold.jl") + include_test("power_manifold.jl") + include_test("vector_bundle.jl") + include_test("graph.jl") -include("multinomial_matrices.jl") -include("oblique.jl") -include("torus.jl") + include_test("metric.jl") + include_test("statistics.jl") -#meta manifolds -include("product_manifold.jl") -include("power_manifold.jl") -include("vector_bundle.jl") -include("graph.jl") + # Lie groups and actions + include_test("groups/groups_general.jl") + include_test("groups/array_manifold.jl") + include_test("groups/circle_group.jl") + include_test("groups/translation_group.jl") + include_test("groups/special_orthogonal.jl") + include_test("groups/product_group.jl") + include_test("groups/semidirect_product_group.jl") + include_test("groups/special_euclidean.jl") + include_test("groups/group_operation_action.jl") + include_test("groups/rotation_action.jl") + include_test("groups/translation_action.jl") + include_test("groups/metric.jl") -include("metric.jl") -include("statistics.jl") - -# Lie groups and actions -include("groups/groups_general.jl") -include("groups/array_manifold.jl") -include("groups/circle_group.jl") -include("groups/translation_group.jl") -include("groups/special_orthogonal.jl") -include("groups/product_group.jl") -include("groups/semidirect_product_group.jl") -include("groups/special_euclidean.jl") -include("groups/group_operation_action.jl") -include("groups/rotation_action.jl") -include("groups/translation_action.jl") -include("groups/metric.jl") - -include("recipes.jl") + include_test("recipes.jl") +end diff --git a/test/sphere.jl b/test/sphere.jl index 5fc13dfd7e..b1c753b4d2 100644 --- a/test/sphere.jl +++ b/test/sphere.jl @@ -49,11 +49,9 @@ include("utils.jl") ], test_mutating_rand=isa(T, Vector), point_distributions=[Manifolds.uniform_distribution(M, pts[1])], - tvector_distributions=[Manifolds.normal_tvector_distribution( - M, - pts[1], - 1.0, - )], + tvector_distributions=[ + Manifolds.normal_tvector_distribution(M, pts[1], 1.0), + ], basis_types_vecs=(DiagonalizingOrthonormalBasis([0.0, 1.0, 2.0]),), basis_types_to_from=basis_types, test_vee_hat=false, diff --git a/test/statistics.jl b/test/statistics.jl index 9ebb524c14..77f9582adc 100644 --- a/test/statistics.jl +++ b/test/statistics.jl @@ -388,8 +388,8 @@ end p = [0.0, 0.0, 1.0] n = 3 x = [ - exp(M, p, π / 6 * [cos(α), sin(α), 0.0]) - for α in range(0, 2 * π - 2 * π / n, length=n) + exp(M, p, π / 6 * [cos(α), sin(α), 0.0]) for + α in range(0, 2 * π - 2 * π / n, length=n) ] test_mean(M, x) test_median(M, x; atol=10^-12) diff --git a/test/stiefel.jl b/test/stiefel.jl index c401dbe50e..561830cc44 100644 --- a/test/stiefel.jl +++ b/test/stiefel.jl @@ -62,8 +62,8 @@ include("utils.jl") @testset "inverse QR retraction cases" begin M43 = Stiefel(4, 3) - p = SA[1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0; 0.0 0.0 0.0] - Xinit = SA[0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0; 1.0 1.0 1.0] + p = [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0; 0.0 0.0 0.0] + Xinit = [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0; 1.0 1.0 1.0] q = retract(M43, p, Xinit, QRRetraction()) X1 = inverse_retract( M43, diff --git a/test/utils.jl b/test/utils.jl index 63b4d6bdb3..9f8def6f14 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -21,3 +21,19 @@ using LightGraphs using SimpleWeightedGraphs using Manifolds.ManifoldTests + +function include_test(path) + @info "Testing $path" + @time include(path) # show basic timing, (this will print a newline at end) +end + +function our_base_ambiguities() + ambigs = Test.detect_ambiguities(Base) + modules_we_care_about = + [Base, LinearAlgebra, Manifolds, ManifoldsBase, StaticArrays, Statistics, StatsBase] + our_ambigs = filter(ambigs) do (m1, m2) + we_care = m1.module in modules_we_care_about && m2.module in modules_we_care_about + return we_care && (m1.module === Manifolds || m2.module === Manifolds) + end + return our_ambigs +end