From 65780dc51475b255258dfd35a225f09a0cd2810b Mon Sep 17 00:00:00 2001 From: Ronny Bergmann Date: Fri, 15 Dec 2023 18:58:06 +0100 Subject: [PATCH] Introduce AbstractApproximationMethod (#177) * Initial sketch of an abstract estimation method type. * Rename AbstractEstimationMethod to AbstractApproximationMethod * Fix docs. * Update our reference. --------- Co-authored-by: Mateusz Baran --- NEWS.md | 4 +- Readme.md | 19 +++++--- docs/make.jl | 31 +++++++++++-- docs/src/functions.md | 8 ++++ docs/src/index.md | 16 ++++--- docs/src/references.bib | 12 ++++- src/ManifoldsBase.jl | 13 ++++++ src/approximation_methods.jl | 90 ++++++++++++++++++++++++++++++++++++ src/retractions.jl | 23 +++++++-- src/vector_transport.jl | 36 ++++++++++++++- test/default_manifold.jl | 34 ++++++++++++++ test/manifold_fallbacks.jl | 4 ++ 12 files changed, 264 insertions(+), 26 deletions(-) create mode 100644 src/approximation_methods.jl diff --git a/NEWS.md b/NEWS.md index 7550710a..71f9a90a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* `EmbeddedVectorTransport` to use a vector transport in the embedding and a final projection. +* An `AbstractApproximationMethod` to specify estimation methods for other more general functions, +as well as a `default_approximation_method` to specify defaults on manifolds. +* An `EmbeddedVectorTransport` to use a vector transport in the embedding and a final projection. ### Fixed diff --git a/Readme.md b/Readme.md index f0f1045f..2efff979 100644 --- a/Readme.md +++ b/Readme.md @@ -29,18 +29,23 @@ We would be very interested to hear where you are using the interface or manifol ## Citation -If you use `ManifoldsBase.jl` in your work, please cite the following +If you use `ManifoldsBase.jl` in your work, please cite the following open access article ```biblatex -@online{2106.08777, -Author = {Seth D. Axen and Mateusz Baran and Ronny Bergmann and Krzysztof Rzecki}, -Title = {Manifolds.jl: An Extensible Julia Framework for Data Analysis on Manifolds}, -Year = {2021}, -Eprint = {2106.08777}, -Eprinttype = {arXiv}, +@article{AxenBaranBergmannRzecki:2023, + author = {Axen, Seth D. and Baran, Mateusz and Bergmann, Ronny and Rzecki, Krzysztof}, + articleno = {33}, + doi = {10.1145/3618296}, + journal = {ACM Transactions on Mathematical Software}, + month = {dec}, + number = {4}, + title = {Manifolds.Jl: An Extensible Julia Framework for Data Analysis on Manifolds}, + volume = {49}, + year = {2023}, } ``` + To refer to a certain version we recommend to also cite for example ```biblatex diff --git a/docs/make.jl b/docs/make.jl index 6d4ac408..736a6beb 100755 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,6 +2,26 @@ # # +if "--help" ∈ ARGS + println( + """ +docs/make.jl + +Render the `Manopt.jl` documenation with optinal arguments + +Arguments +* `--help` - print this help and exit without rendering the documentation +* `--prettyurls` – toggle the prettyurls part to true (which is otherwise only true on CI) +* `--quarto` – run the Quarto notebooks from the `tutorials/` folder before generating the documentation + this has to be run locally at least once for the `tutorials/*.md` files to exist that are included in + the documentation (see `--exclude-tutorials`) for the alternative. + If they are generated ones they are cached accordingly. + Then you can spare time in the rendering by not passing this argument. +""", + ) + exit(0) +end + # # (a) if docs is not the current active environment, switch to it # (from https://github.com/JuliaIO/HDF5.jl/pull/1020/)  @@ -30,7 +50,7 @@ if "--quarto" ∈ ARGS end end -using Documenter: DocMeta, HTML, MathJax3, deploydocs, makedocs +using Documenter using DocumenterCitations using ManifoldsBase @@ -38,10 +58,11 @@ using ManifoldsBase bib = CitationBibliography(joinpath(@__DIR__, "src", "references.bib"); style = :alpha) makedocs(; # for development, we disable prettyurls - format = HTML(; - mathengine = MathJax3(), - prettyurls = get(ENV, "CI", nothing) == "true", - assets = ["assets/favicon.ico"], + format = Documenter.HTML(; + prettyurls = (get(ENV, "CI", nothing) == "true") || ("--prettyurls" ∈ ARGS), + assets = ["assets/favicon.ico", "assets/citations.css"], + size_threshold_warn = 200 * 2^10, # raise slightly from 100 to 200 KiB + size_threshold = 300 * 2^10, # raise slightly 200 to to 300 KiB ), modules = [ManifoldsBase], authors = "Seth Axen, Mateusz Baran, Ronny Bergmann, and contributors.", diff --git a/docs/src/functions.md b/docs/src/functions.md index 7e214f3d..64e895d4 100644 --- a/docs/src/functions.md +++ b/docs/src/functions.md @@ -58,6 +58,14 @@ Public=false Private=true ``` +## Approximation Methods + +```@autodocs +Modules = [ManifoldsBase] +Pages = ["approximation_methods.jl"] +Order = [:type, :function] +``` + ## Error Messages This interface introduces a small set of own error messages. diff --git a/docs/src/index.md b/docs/src/index.md index 461125b1..30bf6026 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -21,12 +21,16 @@ If you use `ManifoldsBase.jl` in your work, please cite the following paper, which covers both the basic interface as well as the performance for `Manifolds.jl`. ```biblatex -@online{2106.08777, - Author = {Seth D. Axen and Mateusz Baran and Ronny Bergmann and Krzysztof Rzecki}, - Title = {Manifolds.jl: An Extensible Julia Framework for Data Analysis on Manifolds}, - Year = {2021}, - Eprint = {2106.08777}, - Eprinttype = {arXiv}, +@article{AxenBaranBergmannRzecki:2023, + AUTHOR = {Axen, Seth D. and Baran, Mateusz and Bergmann, Ronny and Rzecki, Krzysztof}, + ARTICLENO = {33}, + DOI = {10.1145/3618296}, + JOURNAL = {ACM Transactions on Mathematical Software}, + MONTH = {dec}, + NUMBER = {4}, + TITLE = {Manifolds.Jl: An Extensible Julia Framework for Data Analysis on Manifolds}, + VOLUME = {49}, + YEAR = {2023} } ``` diff --git a/docs/src/references.bib b/docs/src/references.bib index a19a9d81..c0e97eaf 100644 --- a/docs/src/references.bib +++ b/docs/src/references.bib @@ -14,7 +14,17 @@ @book{AbsilMahonySepulchre:2008 TITLE = {Optimization Algorithms on Matrix Manifolds}, YEAR = {2008}, } - +@article{AxenBaranBergmannRzecki:2023, + AUTHOR = {Axen, Seth D. and Baran, Mateusz and Bergmann, Ronny and Rzecki, Krzysztof}, + ARTICLENO = {33}, + DOI = {10.1145/3618296}, + JOURNAL = {ACM Transactions on Mathematical Software}, + MONTH = {dec}, + NUMBER = {4}, + TITLE = {Manifolds.Jl: An Extensible Julia Framework for Data Analysis on Manifolds}, + VOLUME = {49}, + YEAR = {2023} +} @article{EhlersPiraniSchild:1972, DOI = {10.1007/s10714-012-1353-4}, YEAR = {1972}, diff --git a/src/ManifoldsBase.jl b/src/ManifoldsBase.jl index f5e30784..fa348299 100644 --- a/src/ManifoldsBase.jl +++ b/src/ManifoldsBase.jl @@ -29,6 +29,7 @@ include("maintypes.jl") include("numbers.jl") include("Fiber.jl") include("bases.jl") +include("approximation_methods.jl") include("retractions.jl") include("exp_log_geo.jl") include("projections.jl") @@ -1022,6 +1023,17 @@ export AbstractPowerRepresentation, NestedPowerRepresentation, NestedReplacingPowerRepresentation export ProductManifold +# (b) Generic Estimation Types + +export GeodesicInterpolationWithinRadius, + CyclicProximalPointEstimation, + ExtrinsicEstimation, + GradientDescentEstimation, + WeiszfeldEstimation, + AbstractApproximationMethod, + GeodesicInterpolation + + # (b) Retraction Types export AbstractRetractionMethod, ApproximateInverseRetraction, @@ -1100,6 +1112,7 @@ export ×, change_representer!, copy, copyto!, + default_approximation_method, default_inverse_retraction_method, default_retraction_method, default_vector_transport_method, diff --git a/src/approximation_methods.jl b/src/approximation_methods.jl new file mode 100644 index 00000000..706afd57 --- /dev/null +++ b/src/approximation_methods.jl @@ -0,0 +1,90 @@ +@doc raw""" + AbstractApproximationMethod + +Abstract type for defining estimation methods on manifolds. +""" +abstract type AbstractApproximationMethod end + +@doc raw""" + GradientDescentEstimation <: AbstractApproximationMethod + +Method for estimation using [📖 gradient descent](https://en.wikipedia.org/wiki/Gradient_descent). +""" +struct GradientDescentEstimation <: AbstractApproximationMethod end + +@doc raw""" + CyclicProximalPointEstimation <: AbstractApproximationMethod + +Method for estimation using the cyclic proximal point technique, which is based on [📖 proximal maps](https://en.wikipedia.org/wiki/Proximal_operator). +""" +struct CyclicProximalPointEstimation <: AbstractApproximationMethod end + +@doc raw""" + EfficientEstimator <: AbstractApproximationMethod + +Method for estimation in the best possible sense, see [📖 Efficiency (Statictsics)](https://en.wikipedia.org/wiki/Efficiency_(statistics)) for more details. +This can for example be used when computing the usual mean on an Euclidean space, which is the best estimator. +""" +struct EfficientEstimator <: AbstractApproximationMethod end + + +@doc raw""" + ExtrinsicEstimation{T} <: AbstractApproximationMethod + +Method for estimation in the ambient space with a method of type `T` and projecting the result back +to the manifold. +""" +struct ExtrinsicEstimation{T<:AbstractApproximationMethod} <: AbstractApproximationMethod + extrinsic_estimation::T +end + +@doc raw""" + WeiszfeldEstimation <: AbstractApproximationMethod + +Method for estimation using the Weiszfeld algorithm, compare for example the computation of the +[📖 Geometric median](https://en.wikipedia.org/wiki/Geometric_median). +""" +struct WeiszfeldEstimation <: AbstractApproximationMethod end + +@doc raw""" + GeodesicInterpolation <: AbstractApproximationMethod + +Method for estimation based on geodesic interpolation. +""" +struct GeodesicInterpolation <: AbstractApproximationMethod end + +@doc raw""" + GeodesicInterpolationWithinRadius{T} <: AbstractApproximationMethod + +Method for estimation based on geodesic interpolation that is restricted to some `radius` + +# Constructor + + GeodesicInterpolationWithinRadius(radius::Real) +""" +struct GeodesicInterpolationWithinRadius{T<:Real} <: AbstractApproximationMethod + radius::T + function GeodesicInterpolationWithinRadius(radius::T) where {T<:Real} + radius > 0 && return new{T}(radius) + return throw( + DomainError("The radius must be strictly postive, received $(radius)."), + ) + end +end + +@doc raw""" + default_approximation_method(M::AbstractManifold, f) + default_approximation_method(M::AbtractManifold, f, T) + +Specify a default estimation method for an [`AbstractManifold`](@ref) and a specific function `f` +and optionally as well a type `T` to distinguish different (point or vector) representations on `M`. + +By default, all functions `f` call the signature for just a manifold. +The exceptional functions are: + +* `retract` and `retract!` which fall back to [`default_retraction_method`](@ref) +* `inverse_retract` and `inverse_retract!` which fall back to [`default_inverse_retraction_method`](@ref) +* any of the vector transport mehods fall back to [`default_vector_transport_method`](@ref) +""" +default_approximation_method(M::AbstractManifold, f) +default_approximation_method(M::AbstractManifold, f, T) = default_approximation_method(M, f) diff --git a/src/retractions.jl b/src/retractions.jl index e4320d63..5499b7a1 100644 --- a/src/retractions.jl +++ b/src/retractions.jl @@ -1,16 +1,16 @@ """ - AbstractInverseRetractionMethod + AbstractInverseRetractionMethod <: AbstractApproximationMethod Abstract type for methods for inverting a retraction (see [`inverse_retract`](@ref)). """ -abstract type AbstractInverseRetractionMethod end +abstract type AbstractInverseRetractionMethod <: AbstractApproximationMethod end """ - AbstractRetractionMethod + AbstractRetractionMethod <: AbstractApproximationMethod Abstract type for methods for [`retract`](@ref)ing a tangent vector to a manifold. """ -abstract type AbstractRetractionMethod end +abstract type AbstractRetractionMethod <: AbstractApproximationMethod end """ ApproximateInverseRetraction <: AbstractInverseRetractionMethod @@ -934,3 +934,18 @@ function retract_sasaki! end Base.show(io::IO, ::CayleyRetraction) = print(io, "CayleyRetraction()") Base.show(io::IO, ::PadeRetraction{m}) where {m} = print(io, "PadeRetraction($m)") + +# +# default estimation methods pass down with and without the point type +function default_approximation_method(M::AbstractManifold, ::typeof(inverse_retract)) + return default_inverse_retraction_method(M) +end +function default_approximation_method(M::AbstractManifold, ::typeof(inverse_retract), T) + return default_inverse_retraction_method(M, T) +end +function default_approximation_method(M::AbstractManifold, ::typeof(retract)) + return default_retraction_method(M) +end +function default_approximation_method(M::AbstractManifold, ::typeof(retract), T) + return default_retraction_method(M, T) +end diff --git a/src/vector_transport.jl b/src/vector_transport.jl index e92fcdcb..0105facd 100644 --- a/src/vector_transport.jl +++ b/src/vector_transport.jl @@ -1,6 +1,6 @@ """ - AbstractVectorTransportMethod + AbstractVectorTransportMethod <: AbstractApproximationMethod Abstract type for methods for transporting vectors. Such vector transports are not necessarily linear. @@ -9,7 +9,7 @@ necessarily linear. [`AbstractLinearVectorTransportMethod`](@ref) """ -abstract type AbstractVectorTransportMethod end +abstract type AbstractVectorTransportMethod <: AbstractApproximationMethod end """ AbstractLinearVectorTransportMethod <: AbstractVectorTransportMethod @@ -326,6 +326,7 @@ function default_vector_transport_method(M::AbstractManifold, ::Type{T}) where { return default_vector_transport_method(M) end + @doc raw""" pole_ladder( M, @@ -1312,3 +1313,34 @@ function vector_transport_to_project!(M::AbstractManifold, Y, p, X, q; kwargs... # Note that we have to use embed (not embed!) since we do not have memory to store this embedded value in return project!(M, Y, q, embed(M, p, X); kwargs...) end + +# default estimation fallbacks with and without the T +function default_approximation_method( + M::AbstractManifold, + ::typeof(vector_transport_direction), +) + return default_vector_transport_method(M) +end +function default_approximation_method(M::AbstractManifold, ::typeof(vector_transport_along)) + return default_vector_transport_method(M) +end +function default_approximation_method(M::AbstractManifold, ::typeof(vector_transport_to)) + return default_vector_transport_method(M) +end +function default_approximation_method( + M::AbstractManifold, + ::typeof(vector_transport_direction), + T, +) + return default_vector_transport_method(M, T) +end +function default_approximation_method( + M::AbstractManifold, + ::typeof(vector_transport_along), + T, +) + return default_vector_transport_method(M, T) +end +function default_approximation_method(M::AbstractManifold, ::typeof(vector_transport_to), T) + return default_vector_transport_method(M, T) +end diff --git a/test/default_manifold.jl b/test/default_manifold.jl index e5e9e1e4..b0d74941 100644 --- a/test/default_manifold.jl +++ b/test/default_manifold.jl @@ -887,4 +887,38 @@ Base.size(x::MatrixVectorTransport) = (size(x.m, 2),) @test isapprox(vee(MC, p, [1 + 2im, 3 + 4im, 5 + 6im]), [1, 3, 5, 2, 4, 6]) @test isapprox(hat(MC, p, [1, 3, 5, 2, 4, 6]), [1 + 2im, 3 + 4im, 5 + 6im]) end + + ManifoldsBase.default_approximation_method( + ::ManifoldsBase.DefaultManifold, + ::typeof(exp), + ) = GradientDescentEstimation() + @testset "Estimation Method defaults" begin + M = ManifoldsBase.DefaultManifold(3) + # Point generic type fallback + @test default_approximation_method(M, exp, Float64) == + default_approximation_method(M, exp) + # Retraction + @test default_approximation_method(M, retract) == default_retraction_method(M) + @test default_approximation_method(M, retract, DefaultPoint) == + default_retraction_method(M) + # Inverse Retraction + @test default_approximation_method(M, inverse_retract) == + default_inverse_retraction_method(M) + @test default_approximation_method(M, inverse_retract, DefaultPoint) == + default_inverse_retraction_method(M) + # Vector Transsports – all 3: to + @test default_approximation_method(M, vector_transport_to) == + default_vector_transport_method(M) + @test default_approximation_method(M, vector_transport_to, DefaultPoint) == + default_vector_transport_method(M) + # along + @test default_approximation_method(M, vector_transport_along) == + default_vector_transport_method(M) + @test default_approximation_method(M, vector_transport_along, DefaultPoint) == + default_vector_transport_method(M) + @test default_approximation_method(M, vector_transport_direction) == + default_vector_transport_method(M) + @test default_approximation_method(M, vector_transport_direction, DefaultPoint) == + default_vector_transport_method(M) + end end diff --git a/test/manifold_fallbacks.jl b/test/manifold_fallbacks.jl index 143bf84a..4507ac61 100644 --- a/test/manifold_fallbacks.jl +++ b/test/manifold_fallbacks.jl @@ -96,3 +96,7 @@ end @test_throws DomainError ODEExponentialRetraction(ExponentialRetraction(), B) @test_throws ErrorException PadeRetraction(0) end + +@testset "Approximation errors" begin + @test_throws DomainError GeodesicInterpolationWithinRadius(-1) +end