diff --git a/NEWS.md b/NEWS.md index 568a358e..4f7e3b48 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ProductManifold` type was migrated from Manifolds.jl. - `Fiber`, `VectorSpaceFiber` and `TangentSpace` types. `TangentSpace` is a generalized version of `TangentSpaceAtPoint` from Manifolds.jl. +- A keyword to `ValidationManifold` which `error=` mode to use. + This is by default the previous `:error` mode. - `change_representer!`, `change_metric!` and `Weingarten!` methods added to `PowerManifold`. - `×` now also works for retractions, inverse retractions, and vector transports to create their product versions - `retract`, `inverse_retract`, and `vector_transport_to` (and `_dir`) now also accept arbirtrary retractions on the product manifold. These act the same as the n-fold product of a retraction. @@ -23,6 +25,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Requires.jl` is added as a dependency to facilitate loading some methods related to `ProductManifolds` on Julia 1.6 to 1.8. Later versions rely on package extensions. - `Documenter.jl` was updated to 1.0. - `PowerManifold` can now store its size either in a field or in a type, similarly to `DefaultManifold`. By default the size is stored in a field. +- The signature of `is_point` was changed to be consistent with `isapprox.`. + The error positional symbol (third argument) is now a keyword argument. + We left the boolean shortcut in place. + That means + * `is_point(M, p, true)` works the same as before (`false` is the default anyways) + * `is_point(M, p, :warn)` has to be changed to `is_point(M, p; error=:warn)` +- The signature of `is_vector` was changed to be consistent with `isapprox` and `is_point`. + The error positional symbol (fourth argument) is now a keyword argument. + The error positional boolean (fourth argument) hence moved to fifth place (after `check_base_point`) + This means + * `is_vector(M, p, X, true)` should now be `is_vector(M, p, X; error=:error)` + * `is_vector(M, p, X, err, base)` for two booleans `err, base` should now be `is_vector(M, p, X, base, err)` + * `is_vector(M, p, X, err, base)` for a symbol `err` should now be `is_vector(M, p, X, base; error=err)` ### Removed diff --git a/docs/make.jl b/docs/make.jl index 7a3e2f93..6d4ac408 100755 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,6 +21,7 @@ if "--quarto" ∈ ARGS tutorials_folder = (@__DIR__) * "/../tutorials" # instantiate the tutorials environment if necessary Pkg.activate(tutorials_folder) + Pkg.develop(PackageSpec(; path = (@__DIR__) * "/../")) Pkg.resolve() Pkg.instantiate() Pkg.build("IJulia") # build IJulia to the right version. diff --git a/src/ManifoldsBase.jl b/src/ManifoldsBase.jl index 36707dc1..41eb275c 100644 --- a/src/ManifoldsBase.jl +++ b/src/ManifoldsBase.jl @@ -543,13 +543,14 @@ tensor is everywhere zero. is_flat(M::AbstractManifold) """ - isapprox(M::AbstractManifold, p, q; error::Symbol=none, kwargs...) + isapprox(M::AbstractManifold, p, q; error::Symbol=:none, kwargs...) Check if points `p` and `q` from [`AbstractManifold`](@ref) `M` are approximately equal. The keyword argument can be used to get more information for the case that the result is false, if the concrete manifold provides such information. Currently the following are supported + * `:error` - throws an error if `isapprox` evaluates to false, providing possibly a more detailed error. Note that this turns `isapprox` basically to an `@assert`. * `:info` – prints the information in an `@info` @@ -644,48 +645,52 @@ function _isapprox(M::AbstractManifold, p, X, Y; kwargs...) end """ - is_point(M::AbstractManifold, p, throw_error::Boolean = false; kwargs...) - is_point(M::AbstractManifold, p, report_error::Symbol; kwargs...) + is_point(M::AbstractManifold, p; error::Symbol = :none, kwargs...) + is_point(M::AbstractManifold, p, throw_error::Bool; kwargs...) Return whether `p` is a valid point on the [`AbstractManifold`](@ref) `M`. +By default the function calls [`check_point`](@ref), which returns an `ErrorException` or `nothing`. + +How to report a potential error can be set using the `error=` keyword -If `throw_error` is `false`, the function returns either `true` or `false`. If `throw_error` -is `true`, the function either returns `true` or throws an error. By default the function -calls [`check_point`](@ref) and checks whether the returned value -is `nothing` or an error. +* `:error` - throws an error if `p` is not a point +* `:info` - displays the error message as an `@info` +* `:warn` - displays the error message as a `@warning` +* `:none` (default) – the function just returns `true`/`false` -A more precise way can be set using a symbol as the optional parameter, where -' `:error` is the same as setting `throw_error=true` -' `:info` displays the error message as an `@info` -* `:warn` displays the error message as a `@warning` +all other symbols are equivalent to `error=:none`. -all other symbols are equivalent to `throw_error=false`. +The second signature is a shorthand, where the boolean is used for `error=:error` (`true`) +and `error=:none` (default, `false`). This case ignores the `error=` keyword """ -function is_point(M::AbstractManifold, p, throw_error = false; kwargs...) - mps = check_size(M, p) - if mps !== nothing - throw_error && throw(mps) - return false - end - mpe = check_point(M, p; kwargs...) - mpe === nothing && return true - return throw_error ? throw(mpe) : false +function is_point( + M::AbstractManifold, + p, + throw_error::Bool; + error::Symbol = :none, + kwargs..., +) + return is_point(M, p; error = throw_error ? :error : :none, kwargs...) end - -function is_point(M::AbstractManifold, p, error::Symbol; kwargs...) - (error === :error) && return is_point(M, p, true; kwargs...) +function is_point(M::AbstractManifold, p; error::Symbol = :none, kwargs...) mps = check_size(M, p) if mps !== nothing - s = "$(typeof(mps)) with $(mps.val)\n$(mps.msg)" - (error === :info) && @info s - (error === :warn) && @warn s + (error === :error) && throw(mps) + if (error === :info) || (error === :warn) + s = "$(typeof(mps)) with $(mps.val)\n$(mps.msg)" + (error === :info) && @info s + (error === :warn) && @warn s + end return false end mpe = check_point(M, p; kwargs...) if mpe !== nothing - s = "$(typeof(mpe)) with $(mpe.val)\n$(mpe.msg)" - (error === :info) && @info s - (error === :warn) && @warn s + (error === :error) && throw(mpe) + if (error === :info) || (error === :warn) + s = "$(typeof(mpe)) with $(mpe.val)\n$(mpe.msg)" + (error === :info) && @info s + (error === :warn) && @warn s + end return false end return true @@ -693,75 +698,77 @@ end """ - is_vector(M::AbstractManifold, p, X, throw_error = false, check_base_point=true; kwargs...) - is_vector(M::AbstractManifold, p, X, error::Symbol, check_base_point::Bool=true; kwargs...) + is_vector(M::AbstractManifold, p, X, check_base_point::Bool=true; error::Symbol=:none, kwargs...) + is_vector(M::AbstractManifold, p, X, check_base_point::Bool=true, throw_error::Boolean; kwargs...) Return whether `X` is a valid tangent vector at point `p` on the [`AbstractManifold`](@ref) `M`. Returns either `true` or `false`. -If `throw_error` is `false`, the function returns either `true` or `false`. If `throw_error` -is `true`, the function either returns `true` or throws an error. By default the function -calls [`check_vector`](@ref) and checks whether the returned +If `check_base_point` is set to true, this function also (first) calls [`is_point`](@ref) +on `p`. +Then, the function calls [`check_vector`](@ref) and checks whether the returned value is `nothing` or an error. -If `check_base_point` is true, then the point `p` will be first checked using the -[`check_point`](@ref) function. +How to report a potential error can be set using the `error=` keyword + +* `:error` - throws an error if `X` is not a tangent vector and/or `p` is not point +^ `:info` - displays the error message as an `@info` +* `:warn` - displays the error message as a `@warn`ing. +* `:none` - (default) the function just returns `true`/`false` -A more precise way can be set using a symbol as the optional parameter, where -' `:error` is the same as setting `throw_error=true` -' `:info` displays the error message as an `@info` -* `:warn` displays the error message as a `@warn`ing. +all other symbols are equivalent to `error=:none` -all other symbols are equivalent to `throw_error=false`. +The second signature is a shorthand, where `throw_error` is used for `error=:error` (`true`) +and `error=:none` (default, `false`). This case ignores the `error=` keyword. """ function is_vector( M::AbstractManifold, p, X, - throw_error = false, - check_base_point = true; + check_base_point::Bool, + throw_error::Bool; + error::Symbol = :none, kwargs..., ) - if check_base_point - s = is_point(M, p, throw_error; kwargs...) # if throw_error, is_point throws, - !s && return false # otherwise if not a point return false - end - mXs = check_size(M, p, X) - if mXs !== nothing - throw_error && throw(mXs) - return false - end - mXe = check_vector(M, p, X; kwargs...) - mXe === nothing && return true - throw_error && throw(mXe) - return false + return is_vector( + M, + p, + X, + check_base_point; + error = throw_error ? :error : :none, + kwargs..., + ) end - function is_vector( M::AbstractManifold, p, X, - error::Symbol, - check_base_point = true; + check_base_point::Bool = true; + error::Symbol = :none, kwargs..., ) - (error === :error) && return is_vector(M, p, X, true, check_base_point; kwargs...) if check_base_point - s = is_point(M, p, error; kwargs...) # if error, is_point throws, - !s && return false # otherwise if not a point return false + # if error, is_point throws, otherwise if not a point return false + !is_point(M, p; error = error, kwargs...) && return false end mXs = check_size(M, p, X) if mXs !== nothing - s = "$(typeof(mXs)) with $(mXs.val)\n$(mXs.msg)" - (error === :info) && @info s - (error === :warn) && @warn s + (error === :error) && throw(mXs) + if (error === :info) || (error === :warn) + s = "$(typeof(mXs)) with $(mXs.val)\n$(mXs.msg)" + (error === :info) && @info s + (error === :warn) && @warn s + end return false end mXe = check_vector(M, p, X; kwargs...) if mXe !== nothing - s = "$(typeof(mXe)) with $(mXe.val)\n$(mXe.msg)" - (error === :info) && @info s - (error === :warn) && @warn s + (error === :error) && throw(mXe) + if (error === :info) || (error === :warn) + s = "$(typeof(mXe)) with $(mXe.val)\n$(mXe.msg)" + (error === :info) && @info s + (error === :warn) && @warn s + end return false end return true diff --git a/src/ValidationManifold.jl b/src/ValidationManifold.jl index 17b983b4..417db8d1 100644 --- a/src/ValidationManifold.jl +++ b/src/ValidationManifold.jl @@ -8,9 +8,20 @@ encapsulated/stripped automatically when needed. This manifold is a decorator for a manifold, i.e. it decorates a [`AbstractManifold`](@ref) `M` with types points, vectors, and covectors. + +# Constructor + + ValidationManifold(M::AbstractManifold; error::Symbol = :error) + +Generate the Validation manifold, where `error` is used as the symbol passed to all checks. +This `:error`s by default but could also be set to `:warn` for example """ struct ValidationManifold{𝔽,M<:AbstractManifold{𝔽}} <: AbstractDecoratorManifold{𝔽} manifold::M + mode::Symbol +end +function ValidationManifold(M::AbstractManifold; error::Symbol = :error) + return ValidationManifold(M, error) end """ @@ -99,9 +110,9 @@ function convert( end function copyto!(M::ValidationManifold, q::ValidationMPoint, p::ValidationMPoint; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) copyto!(M.manifold, q.value, p.value) - is_point(M, q, true; kwargs...) + is_point(M, q; error = M.mode, kwargs...) return q end function copyto!( @@ -111,35 +122,35 @@ function copyto!( X::ValidationFibreVector{TType}; kwargs..., ) where {TType} - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) copyto!(M.manifold, Y.value, p.value, X.value) return p end function distance(M::ValidationManifold, p, q; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_point(M, q; error = M.mode, kwargs...) return distance(M.manifold, array_value(p), array_value(q)) end function exp(M::ValidationManifold, p, X; kwargs...) - is_point(M, p, true; kwargs...) - is_vector(M, p, X, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) y = exp(M.manifold, array_value(p), array_value(X)) - is_point(M, y, true; kwargs...) + is_point(M, y; error = M.mode, kwargs...) return ValidationMPoint(y) end function exp!(M::ValidationManifold, q, p, X; kwargs...) - is_point(M, p, true; kwargs...) - is_vector(M, p, X, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) exp!(M.manifold, array_value(q), array_value(p), array_value(X)) - is_point(M, q, true; kwargs...) + is_point(M, q; error = M.mode, kwargs...) return q end function get_basis(M::ValidationManifold, p, B::AbstractBasis; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) Ξ = get_basis(M.manifold, array_value(p), B) bvectors = get_vectors(M, p, Ξ) N = length(bvectors) @@ -159,7 +170,7 @@ function get_basis(M::ValidationManifold, p, B::AbstractBasis; kwargs...) ), ) end - map(X -> is_vector(M, p, X, true; kwargs...), bvectors) + map(X -> is_vector(M, p, X; error = M.mode, kwargs...), bvectors) return Ξ end function get_basis( @@ -168,7 +179,7 @@ function get_basis( B::Union{AbstractOrthogonalBasis,CachedBasis{𝔽,<:AbstractOrthogonalBasis{𝔽}}}; kwargs..., ) where {𝔽} - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) Ξ = invoke(get_basis, Tuple{ValidationManifold,Any,AbstractBasis}, M, p, B; kwargs...) bvectors = get_vectors(M, p, Ξ) N = length(bvectors) @@ -192,7 +203,7 @@ function get_basis( B::Union{AbstractOrthonormalBasis,CachedBasis{𝔽,<:AbstractOrthonormalBasis{𝔽}}}; kwargs..., ) where {𝔽} - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) get_basis_invoke_types = Tuple{ ValidationManifold, Any, @@ -214,19 +225,19 @@ function get_basis( end function get_coordinates(M::ValidationManifold, p, X, B::AbstractBasis; kwargs...) - is_point(M, p, true; kwargs...) - is_vector(M, p, X, true; kwargs...) + is_point(M, p; error = :error, kwargs...) + is_vector(M, p, X; error = :error, kwargs...) return get_coordinates(M.manifold, p, X, B) end function get_coordinates!(M::ValidationManifold, Y, p, X, B::AbstractBasis; kwargs...) - is_vector(M, p, X, true; kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) get_coordinates!(M.manifold, Y, p, X, B) return Y end function get_vector(M::ValidationManifold, p, X, B::AbstractBasis; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) size(X) == (manifold_dimension(M),) || error("Incorrect size of coefficient vector X") Y = get_vector(M.manifold, p, X, B) size(Y) == representation_size(M) || error("Incorrect size of tangent vector Y") @@ -234,7 +245,7 @@ function get_vector(M::ValidationManifold, p, X, B::AbstractBasis; kwargs...) end function get_vector!(M::ValidationManifold, Y, p, X, B::AbstractBasis; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) size(X) == (manifold_dimension(M),) || error("Incorrect size of coefficient vector X") get_vector!(M.manifold, Y, p, X, B) size(Y) == representation_size(M) || error("Incorrect size of tangent vector Y") @@ -246,7 +257,7 @@ function injectivity_radius(M::ValidationManifold, method::AbstractRetractionMet return injectivity_radius(M.manifold, method) end function injectivity_radius(M::ValidationManifold, p; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) return injectivity_radius(M.manifold, array_value(p)) end function injectivity_radius( @@ -255,72 +266,65 @@ function injectivity_radius( method::AbstractRetractionMethod; kwargs..., ) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) return injectivity_radius(M.manifold, array_value(p), method) end function inner(M::ValidationManifold, p, X, Y; kwargs...) - is_point(M, p, true; kwargs...) - is_vector(M, p, X, true; kwargs...) - is_vector(M, p, Y, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) + is_vector(M, p, Y; error = M.mode, kwargs...) return inner(M.manifold, array_value(p), array_value(X), array_value(Y)) end - -function is_point(M::ValidationManifold, p, te::Bool = false; kw...) - return is_point(M.manifold, array_value(p), te; kw...) -end -function is_point(M::ValidationManifold, p, e::Symbol; kw...) - return is_point(M.manifold, array_value(p), e; kw...) -end -function is_vector(M::ValidationManifold, p, X, te::Bool = false, cbp = true; kw...) - return is_vector(M.manifold, array_value(p), array_value(X), te, cbp; kw...) +function is_point(M::ValidationManifold, p; kw...) + return is_point(M.manifold, array_value(p); kw...) end -function is_vector(M::ValidationManifold, p, X, e::Symbol, cbp = true; kw...) - return is_vector(M.manifold, array_value(p), array_value(X), e, cbp; kw...) +function is_vector(M::ValidationManifold, p, X, cbp::Bool = true; kw...) + return is_vector(M.manifold, array_value(p), array_value(X), cbp; kw...) end function isapprox(M::ValidationManifold, p, q; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_point(M, q; error = M.mode, kwargs...) return isapprox(M.manifold, array_value(p), array_value(q); kwargs...) end function isapprox(M::ValidationManifold, p, X, Y; kwargs...) - is_point(M, p, true; kwargs...) - is_vector(M, p, X, true; kwargs...) - is_vector(M, p, Y, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) + is_vector(M, p, Y; error = M.mode, kwargs...) return isapprox(M.manifold, array_value(p), array_value(X), array_value(Y); kwargs...) end function log(M::ValidationManifold, p, q; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_point(M, q; error = M.mode, kwargs...) X = log(M.manifold, array_value(p), array_value(q)) - is_vector(M, p, X, true; kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) return ValidationTVector(X) end function log!(M::ValidationManifold, X, p, q; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) + is_point(M, q; error = M.mode, kwargs...) log!(M.manifold, array_value(X), array_value(p), array_value(q)) - is_vector(M, p, X, true; kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) return X end function mid_point(M::ValidationManifold, p1, p2; kwargs...) - is_point(M, p1, true; kwargs...) - is_point(M, p2, true; kwargs...) + is_point(M, p1; error = M.mode, kwargs...) + is_point(M, p2; error = M.mode, kwargs...) q = mid_point(M.manifold, array_value(p1), array_value(p2)) - is_point(M, q, true; kwargs...) + is_point(M, q; error = M.mode, kwargs...) return q end function mid_point!(M::ValidationManifold, q, p1, p2; kwargs...) - is_point(M, p1, true; kwargs...) - is_point(M, p2, true; kwargs...) + is_point(M, p1; error = M.mode, kwargs...) + is_point(M, p2; error = M.mode, kwargs...) mid_point!(M.manifold, array_value(q), array_value(p1), array_value(p2)) - is_point(M, q, true; kwargs...) + is_point(M, q; error = M.mode, kwargs...) return q end @@ -328,9 +332,9 @@ number_eltype(::Type{ValidationMPoint{V}}) where {V} = number_eltype(V) number_eltype(::Type{ValidationFibreVector{TType,V}}) where {TType,V} = number_eltype(V) function project!(M::ValidationManifold, Y, p, X; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) project!(M.manifold, array_value(Y), array_value(p), array_value(X)) - is_vector(M, p, Y, true; kwargs...) + is_vector(M, p, Y; error = M.mode, kwargs...) return Y end @@ -342,9 +346,9 @@ function vector_transport_along( m::AbstractVectorTransportMethod; kwargs..., ) - is_vector(M, p, X, true; kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) Y = vector_transport_along(M.manifold, array_value(p), array_value(X), c, m) - is_vector(M, c[end], Y, true; kwargs...) + is_vector(M, c[end], Y; error = M.mode, kwargs...) return Y end @@ -357,7 +361,7 @@ function vector_transport_along!( m::AbstractVectorTransportMethod; kwargs..., ) - is_vector(M, p, X, true; kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) vector_transport_along!( M.manifold, array_value(Y), @@ -366,7 +370,7 @@ function vector_transport_along!( c, m, ) - is_vector(M, c[end], Y, true; kwargs...) + is_vector(M, c[end], Y; error = M.mode, kwargs...) return Y end @@ -378,10 +382,10 @@ function vector_transport_to( m::AbstractVectorTransportMethod; kwargs..., ) - is_point(M, q, true; kwargs...) - is_vector(M, p, X, true; kwargs...) + is_point(M, q; error = M.mode, kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) Y = vector_transport_to(M.manifold, array_value(p), array_value(X), array_value(q), m) - is_vector(M, q, Y, true; kwargs...) + is_vector(M, q, Y; error = M.mode, kwargs...) return Y end function vector_transport_to!( @@ -393,8 +397,8 @@ function vector_transport_to!( m::AbstractVectorTransportMethod; kwargs..., ) - is_point(M, q, true; kwargs...) - is_vector(M, p, X, true; kwargs...) + is_point(M, q; error = M.mode, kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) vector_transport_to!( M.manifold, array_value(Y), @@ -403,20 +407,20 @@ function vector_transport_to!( array_value(q), m, ) - is_vector(M, q, Y, true; kwargs...) + is_vector(M, q, Y; error = M.mode, kwargs...) return Y end function zero_vector(M::ValidationManifold, p; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) w = zero_vector(M.manifold, array_value(p)) - is_vector(M, p, w, true; kwargs...) + is_vector(M, p, w; error = M.mode, kwargs...) return w end function zero_vector!(M::ValidationManifold, X, p; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error = M.mode, kwargs...) zero_vector!(M.manifold, array_value(X), array_value(p); kwargs...) - is_vector(M, p, X, true; kwargs...) + is_vector(M, p, X; error = M.mode, kwargs...) return X end diff --git a/src/decorator_trait.jl b/src/decorator_trait.jl index d46e872e..e916660b 100644 --- a/src/decorator_trait.jl +++ b/src/decorator_trait.jl @@ -379,24 +379,26 @@ end @trait_function is_flat(M::AbstractDecoratorManifold) # Introduce Deco Trait | automatic foward | fallback -@trait_function is_point(M::AbstractDecoratorManifold, p, te::Bool = false; kwargs...) -@trait_function is_point(M::AbstractDecoratorManifold, p, e::Symbol; kwargs...) +@trait_function is_point(M::AbstractDecoratorManifold, p; kwargs...) # Embedded function is_point( ::TraitList{IsEmbeddedManifold}, M::AbstractDecoratorManifold, - p, - te::Bool = false; + p; + error::Symbol = :none, kwargs..., ) # to be safe check_size first es = check_size(M, p) if es !== nothing - te && throw(es) + (error === :error) && throw(es) + s = "$(typeof(es)) with $(es)" + (error === :info) && @info s + (error === :warn) && @warn s return false end try - pt = is_point(get_embedding(M, p), embed(M, p), te; kwargs...) + pt = is_point(get_embedding(M, p), embed(M, p); error = error, kwargs...) !pt && return false # no error thrown (deactivated) but returned false -> return false catch e if e isa DomainError || e isa AbstractManifoldDomainError @@ -408,28 +410,18 @@ function is_point( throw(e) #an error occured that we do not handle ourselves -> rethrow. end mpe = check_point(M, p; kwargs...) - mpe === nothing && return true - te && throw(mpe) - return false + if mpe !== nothing + (error === :error) && throw(mpe) + s = "$(typeof(mpe)) with $(mpe.val)\n$(mpe.msg)" + (error === :info) && @info s + (error === :warn) && @warn s + return false + end + return true end # Introduce Deco Trait | automatic foward | fallback -@trait_function is_vector( - M::AbstractDecoratorManifold, - p, - X, - te::Bool = false, - cbp = true; - kwargs..., -) -@trait_function is_vector( - M::AbstractDecoratorManifold, - p, - X, - e::Symbol, - cbp = true; - kwargs..., -) +@trait_function is_vector(M::AbstractDecoratorManifold, p, X, cbp::Bool = true; kwargs...) # EmbeddedManifold # I am not yet sure how to properly document this embedding behaviour here in a docstring. function is_vector( @@ -437,34 +429,41 @@ function is_vector( M::AbstractDecoratorManifold, p, X, - te::Bool = false, - cbp = true; + check_base_point::Bool = true; + error::Symbol = :none, kwargs..., ) es = check_size(M, p, X) if es !== nothing - te && throw(es) # error & throw? + (error === :error) && throw(es) + s = "$(typeof(es)) with $(es)" + (error === :info) && @info s + (error === :warn) && @warn s return false end - if cbp - # check whether p is valid before embedding the tangent vector - # throws it te=true + if check_base_point try - ep = is_point(M, p, te; kwargs...) + ep = is_point(M, p; error = error, kwargs...) !ep && return false catch e if e isa DomainError || e isa AbstractManifoldDomainError - e = ManifoldDomainError( - "$X is not a tangent vector to $p on $M because its bas epoint is not valid point on $M.", + ManifoldDomainError( + "$X is not a tangent vector to $p on $M because $p is not a valid point on $p", e, ) end throw(e) end end - # Check vector in embedding try - tv = is_vector(get_embedding(M, p), embed(M, p), embed(M, p, X), te, cbp; kwargs...) + tv = is_vector( + get_embedding(M, p), + embed(M, p), + embed(M, p, X), + check_base_point; + error = error, + kwargs..., + ) !tv && return false # no error thrown (deactivated) but returned false -> return false catch e if e isa DomainError || e isa AbstractManifoldDomainError @@ -476,9 +475,12 @@ function is_vector( throw(e) end # Check (additional) local stuff - mtve = check_vector(M, p, X; kwargs...) - mtve === nothing && return true - te && throw(mtve) + mXe = check_vector(M, p, X; kwargs...) + mXe === nothing && return true + (error === :error) && throw(mXe) + s = "$(typeof(mXe)) with $(mXe.val)\n$(mXe.msg)" + (error === :info) && @info s + (error === :warn) && @warn s return false end diff --git a/test/default_manifold.jl b/test/default_manifold.jl index e6b363cc..db3000f0 100644 --- a/test/default_manifold.jl +++ b/test/default_manifold.jl @@ -854,8 +854,8 @@ Base.size(x::MatrixVectorTransport) = (size(x.m, 2),) XF = [0.0, 0.0] m = ExponentialRetraction() @test_throws DomainError is_point(M, pF, true) - @test_throws DomainError is_vector(M, p, XF, true) - @test_throws DomainError is_vector(M, pF, XF, true; check_point = true) + @test_throws DomainError is_vector(M, p, XF; error = :error) + @test_throws DomainError is_vector(M, pF, XF, true; error = :error) @test injectivity_radius(M) == Inf @test injectivity_radius(M, p) == Inf @test injectivity_radius(M, p, m) == Inf diff --git a/test/domain_errors.jl b/test/domain_errors.jl index ca049b28..7e23e5fc 100644 --- a/test/domain_errors.jl +++ b/test/domain_errors.jl @@ -36,38 +36,38 @@ end @test !is_point(M, [-1, 1]) @test !is_point(M, [1, 1, 1]) # checksize fails @test_throws DomainError is_point(M, [-1, 1, 1], true) # checksize errors - @test_throws DomainError is_point(M, [-1, 1, 1], :error) # checksize errors + @test_throws DomainError is_point(M, [-1, 1, 1]; error = :error) # checksize errors cs = "DomainError with (3,)\nsize [-1, 1, 1] not (2,)" - @test_logs (:info, cs) is_point(M, [-1, 1, 1], :info) - @test_logs (:warn, cs) is_point(M, [-1, 1, 1], :warn) + @test_logs (:info, cs) is_point(M, [-1, 1, 1]; error = :info) + @test_logs (:warn, cs) is_point(M, [-1, 1, 1]; error = :warn) @test is_point(M, [1, 1]) - @test is_point(M, [1, 1], :error) + @test is_point(M, [1, 1]; error = :error) @test_throws DomainError is_point(M, [-1, 1], true) - @test_throws DomainError is_point(M, [-1, 1], :error) + @test_throws DomainError is_point(M, [-1, 1]; error = :error) ps = "DomainError with [-1, 1]\n<0" - @test_logs (:info, ps) is_point(M, [-1, 1], :info) - @test_logs (:warn, ps) is_point(M, [-1, 1], :warn) + @test_logs (:info, ps) is_point(M, [-1, 1]; error = :info) + @test_logs (:warn, ps) is_point(M, [-1, 1]; error = :warn) @test isa(ManifoldsBase.check_vector(M, [1, 1], [-1, 1]), DomainError) @test ManifoldsBase.check_vector(M, [1, 1], [1, 1]) === nothing @test !is_vector(M, [1, 1], [-1, 1]) @test !is_vector(M, [1, 1], [1, 1, 1]) - @test_throws DomainError is_vector(M, [1, 1], [-1, 1, 1], true) - @test_throws DomainError is_vector(M, [1, 1], [-1, 1, 1], :error) + @test_throws DomainError is_vector(M, [1, 1], [-1, 1, 1], false, true) + @test_throws DomainError is_vector(M, [1, 1], [-1, 1, 1]; error = :error) vs = "DomainError with (3,)\nsize [-1, 1, 1] not (2,)" - @test_logs (:info, vs) is_vector(M, [1, 1], [-1, 1, 1], :info) - @test_logs (:warn, vs) is_vector(M, [1, 1], [-1, 1, 1], :warn) - @test !is_vector(M, [1, 1, 1], [1, 1, 1], false, true) + @test_logs (:info, vs) is_vector(M, [1, 1], [-1, 1, 1]; error = :info) + @test_logs (:warn, vs) is_vector(M, [1, 1], [-1, 1, 1]; error = :warn) + @test !is_vector(M, [1, 1, 1], [1, 1, 1], false) @test_throws DomainError is_vector(M, [1, 1, 1], [1, 1], true, true) - @test_throws DomainError is_vector(M, [1, 1, 1], [1, 1], :error, true) + @test_throws DomainError is_vector(M, [1, 1, 1], [1, 1], true; error = :error) ps2 = "DomainError with (3,)\nsize [1, 1, 1] not (2,)" - @test_logs (:info, ps2) is_vector(M, [1, 1, 1], [1, 1], :info, true) - @test_logs (:warn, ps2) is_vector(M, [1, 1, 1], [1, 1], :warn, true) + @test_logs (:info, ps2) is_vector(M, [1, 1, 1], [1, 1], true; error = :info) + @test_logs (:warn, ps2) is_vector(M, [1, 1, 1], [1, 1], true; error = :warn) @test is_vector(M, [1, 1], [1, 1]) - @test is_vector(M, [1, 1], [1, 1], :none) #default just true/false - @test_throws DomainError is_vector(M, [1, 1], [-1, 1], true) - @test_throws DomainError is_vector(M, [1, 1], [-1, 1], :error, true) + @test is_vector(M, [1, 1], [1, 1]; error = :none) + @test_throws DomainError is_vector(M, [1, 1], [-1, 1]; error = :error) + @test_throws DomainError is_vector(M, [1, 1], [-1, 1], true; error = :error) ps3 = "DomainError with [-1, 1]\n<0" - @test_logs (:info, ps3) is_vector(M, [1, 1], [-1, 1], :info, true) - @test_logs (:warn, ps3) is_vector(M, [1, 1], [-1, 1], :warn, true) + @test_logs (:info, ps3) is_vector(M, [1, 1], [-1, 1], true; error = :info) + @test_logs (:warn, ps3) is_vector(M, [1, 1], [-1, 1], true; error = :warn) end diff --git a/test/embedded_manifold.jl b/test/embedded_manifold.jl index 8b90a9af..a9b6b0e3 100644 --- a/test/embedded_manifold.jl +++ b/test/embedded_manifold.jl @@ -216,21 +216,21 @@ ManifoldsBase.decorated_manifold(::FallbackManifold) = DefaultManifold(3) @test !is_point(M, [-1 0 0]) # right size but <0 1st @test_throws DomainError is_point(M, [-1 0 0], true) # right size but <0 1st @test !is_vector(M, [1 0 0], [1]) # right point, wrong size vector - @test_throws ManifoldDomainError is_vector(M, [1 0 0], [1], true) + @test_throws ManifoldDomainError is_vector(M, [1 0 0], [1]; error = :error) @test !is_vector(M, [1 0 0], [1]) - @test_throws DomainError is_vector(M, [1 0 0], [-1 0 0], true) # right point, vec 1st <0 + @test_throws DomainError is_vector(M, [1 0 0], [-1 0 0]; error = :error) # right point, vec 1st <0 @test !is_vector(M, [1 0 0], [-1 0 0]) @test is_vector(M, [1 0 0], [1 0 1], true) @test !is_vector(M, [-1, 0, 0], [0, 0, 0]) - @test_throws ManifoldDomainError is_vector(M, [-1, 0, 0], [0, 0, 0], true) - @test_throws DomainError is_vector(M, [1, 0, 0], [-1, 0, 0], true) + @test_throws DomainError is_vector(M, [-1, 0, 0], [0, 0, 0]; error = :error) + @test_throws DomainError is_vector(M, [1, 0, 0], [-1, 0, 0]; error = :error) @test !is_vector(M, [-1, 0, 0], [0, 0, 0]) @test !is_vector(M, [1, 0, 0], [-1, 0, 0]) # check manifold domain error from embedding to obtain ManifoldDomainErrors @test !is_point(N, [0, 0, 0]) - @test_throws ManifoldDomainError is_point(N, [0, 0, 0], true) + @test_throws ManifoldDomainError is_point(N, [0, 0, 0]; error = :error) @test !is_vector(N, [1, 1, 0], [0, 0, 0]) - @test_throws ManifoldDomainError is_vector(N, [1, 1, 0], [0, 0, 0], true) + @test_throws ManifoldDomainError is_vector(N, [1, 1, 0], [0, 0, 0]; error = :error) p = [1.0 1.0 0.0] q = [1.0 0.0 0.0] X = q - p diff --git a/test/power.jl b/test/power.jl index 87192833..ae58b309 100644 --- a/test/power.jl +++ b/test/power.jl @@ -118,8 +118,18 @@ struct TestArrayRepresentation <: AbstractPowerRepresentation end @test is_vector(N, p, p, true) @test !is_vector(N, p, pE1) @test !is_vector(N, p, pE2) - @test_throws ComponentManifoldError is_vector(N, p, pE1, true) - @test_throws CompositeManifoldError is_vector(N, p, pE2, true) + @test_throws ComponentManifoldError is_vector( + N, + p, + pE1; + error = :error, + ) + @test_throws CompositeManifoldError is_vector( + N, + p, + pE2; + error = :error, + ) end end @testset "specific functions" begin diff --git a/test/product_manifold.jl b/test/product_manifold.jl index 3f124c5c..10ced9b9 100644 --- a/test/product_manifold.jl +++ b/test/product_manifold.jl @@ -47,7 +47,13 @@ include("test_sphere.jl") # test that arrays are not points @test_throws DomainError is_point(M, [1, 2], true) @test ManifoldsBase.check_point(M, [1, 2]) isa DomainError - @test_throws DomainError is_vector(M, 1, [1, 2], true; check_base_point = false) + @test_throws DomainError is_vector( + M, + 1, + [1, 2]; + error = :error, + check_base_point = false, + ) @test ManifoldsBase.check_vector(M, 1, [1, 2]; check_base_point = false) isa DomainError #default fallbacks for check_size, Product not working with Arrays @test ManifoldsBase.check_size(M, zeros(2)) isa DomainError @@ -247,8 +253,8 @@ include("test_sphere.jl") Xf = ArrayPartition(X1, p2) @test is_point(Mpr, p, true) @test_throws CompositeManifoldError is_point(Mpr, X, true) - @test_throws ComponentManifoldError is_vector(Mpr, pf, X, true) - @test_throws ComponentManifoldError is_vector(Mpr, p, Xf, true) + @test_throws ComponentManifoldError is_vector(Mpr, pf, X; error = :error) + @test_throws ComponentManifoldError is_vector(Mpr, p, Xf; error = :error) end end diff --git a/test/validation_manifold.jl b/test/validation_manifold.jl index 5b2340de..0dbf97a7 100644 --- a/test/validation_manifold.jl +++ b/test/validation_manifold.jl @@ -36,10 +36,10 @@ end @test ManifoldsBase.check_size(A, x2) === ManifoldsBase.check_size(M, x) end @testset "is_point / is_vector error." begin - @test is_point(A, x, :error) - @test_throws DomainError is_point(A, [1, 2, 3, 4], :error) - @test is_vector(A, x, v, :error) - @test_throws DomainError is_vector(A, x, [1, 2, 3, 4], :error) + @test is_point(A, x; error = :error) + @test_throws DomainError is_point(A, [1, 2, 3, 4]; error = :error) + @test is_vector(A, x, v; error = :error) + @test_throws DomainError is_vector(A, x, [1, 2, 3, 4]; error = :error) end @testset "Types and Conversion" begin @test convert(typeof(M), A) == M diff --git a/tutorials/Project.toml b/tutorials/Project.toml index bca47ffb..f7d96b90 100644 --- a/tutorials/Project.toml +++ b/tutorials/Project.toml @@ -5,5 +5,5 @@ Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] IJulia = "1" -ManifoldsBase = "0.14.11, 0.15" +ManifoldsBase = "0.15" Plots = "1.38" diff --git a/tutorials/implement-a-manifold.qmd b/tutorials/implement-a-manifold.qmd index 1aec6a2d..c483b660 100644 --- a/tutorials/implement-a-manifold.qmd +++ b/tutorials/implement-a-manifold.qmd @@ -151,9 +151,9 @@ end And we can directly test the function. To see all 3 failing ones, we switch from errors to warnings in the check ```{julia} -is_point(M, [1.5, 0.0], :warn) # wrong size -is_point(M, [1.0, 0.0, 0.0], :warn) # wrong norm -is_point(M, [1.5, 0.0, 0.0], :warn) # on the manifold, returns true +is_point(M, [1.5, 0.0], error=:warn) # wrong size +is_point(M, [1.0, 0.0, 0.0], error=:warn) # wrong norm +is_point(M, [1.5, 0.0, 0.0], error=:warn) # on the manifold, returns true ``` similarly for vectors, we just have to implement the orthogonality check. @@ -176,10 +176,10 @@ and again, the high level interface can be used to display warning for vectors n activate a check for the point using the last positional argument ```{julia} -is_vector(M, [1.5, 0.0, 0.0], [0.0, 1.0], :warn) # wrong size -is_vector(M, [1.5, 0.0, 0.0], [1.0, 1.0, 0.0], :warn) # not orthogonal norm -is_vector(M, [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], :warn, true) # point not valid -is_vector(M, [1.5, 0.0, 0.0], [0.0, 1.0, 0.0], :warn, true) # returns true +is_vector(M, [1.5, 0.0, 0.0], [0.0, 1.0]; error=:warn) # wrong size +is_vector(M, [1.5, 0.0, 0.0], [1.0, 1.0, 0.0]; error=:warn) # not orthogonal norm +is_vector(M, [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], true; error=:warn) # point not valid +is_vector(M, [1.5, 0.0, 0.0], [0.0, 1.0, 0.0], true; error=:warn) # returns true ```