diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cabaab2e47..d3f0e3b3be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - julia-version: ["1.6", "1.8", "~1.9.0-0"] + julia-version: ["1.6", "~1.9.0-0"] os: [ubuntu-latest, macOS-latest] group: - 'test_manifolds' diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000000..eedd76e525 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,107 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.9.0] - 2023-mm-dd + +### Added + +- Vector bundles are generalized to fiber bundles. Old `BundleFibers` functionality was reworked to better match mathematical abstractions. Fiber bundle functionality is experimental and minor changes may happen without a breaking release, with the exception of `TangentBundle` which is considered to be stable. +- `RotationTranslationAction` is introduced. + +### Changed + +- Sizes of all manifolds can now be either encoded in type or stored in a field to avoid over-specialization. + The default is set to store the size in type parameter (except for `PowerManifold` and its variants), replicating the previous behavior. + For field storage, pass the `parameter=:field` keyword argument to manifold constructor. + For example statically sized `CenteredMatrices{m,n}` is now `CenteredMatrices{TypeParameter{Tuple{m,n}}}`, whereas the type of special Euclidean group with field-stored size is `CenteredMatrices{Tuple{Int,Int}}`. Similar change applies to: + - `CenteredMatrices{m,n}`, + - `CholeskySpace{N}`, + - `Elliptope{N,K}`, + - `Euclidean`, + - `FixedRankMatrices{m,n,k}`, + - `KendallsPreShapeSpace{n,k}`, + - `KendallsShapeSpace{n,k}`, + - `GeneralLinear{n}`, + - `GeneralUnitaryMultiplicationGroup{n}`, + - `GeneralizedGrassmann{n,k}`, + - `GeneralizedStiefel{n,k}`, + - `Grassmann{n,k}`, + - `Heisenberg{n}`, + - `Hyperbolic{n}`, + - `MultinomialMatrices{N,M}`, + - `MultinomialDoublyStochastic{n}`, + - `MultinomialSymmetric{n}`, + - `Orthogonal{n}`, + - `PowerManifold`, + - `PositiveArrays`, + - `PositiveMatrices`, + - `PositiveNumbers`, + - `ProbabilitySimplex{n}`, + - `SPDFixedDeterminant{n}`, + - `SpecialLinear{n}`, + - `SpecialOrthogonal{n}`, + - `SpecialUnitary{n}`, + - `SpecialEuclidean{n}`, + - `SpecialEuclideanManifold{n}`, + - `Spectrahedron{n,k}`, + - `SphereSymmetricMatrices{N}`, + - `Stiefel{n,k}`, + - `SymmetricMatrices{N}`, + - `SymmetricPositiveDefinite{n}`, + - `SymmetricPositiveSemidefiniteFixedRank{n,k}`, + - `Symplectic{n}`, + - `SymplecticStiefel{n,k}`, + - `TranslationGroup`, + - `Tucker`. + + For example + + ```{julia} + function Base.show(io::IO, ::CenteredMatrices{m,n}) where {m,n} + return print(io, "CenteredMatrices($m, $n)") + end + ``` + + needs to be replaced with + + ```{julia} + function Base.show(io::IO, ::CenteredMatrices{TypeParameter{Tuple{m,n}}}) where {m,n} + return print(io, "CenteredMatrices($m, $n)") + end + ``` + + for statically-sized groups and + + ```{julia} + function Base.show(io::IO, M::CenteredMatrices{Tuple{Int,Int}}) + m, n = get_parameter(M.size) + return print(io, "CenteredMatrices($m, $n; parameter=:field)") + end + ``` + + for groups with size stored in field. Alternatively, you can use a single generic method like this: + + ```{julia} + function Base.show(io::IO, M::CenteredMatrices{T}) where {T} + m, n = get_parameter(M) + if T <: TypeParameter + return print(io, "CenteredMatrices($m, $n)") + else + return print(io, "CenteredMatrices($m, $n; parameter=:field)") + end + end + ``` + +- Argument order for type aliases `RotationActionOnVector` and `RotationTranslationActionOnVector`: most often dispatched on argument is now first. +- A more consistent handling of action direction was introduced. 4-valued `ActionDirection` was split into 2-valued `ActionDirection` (either left or right action) and `GroupActionSide` (action acting from the left or right side). See [https://github.com/JuliaManifolds/Manifolds.jl/issues/637](https://github.com/JuliaManifolds/Manifolds.jl/issues/637) for a design discussion. + +### Removed + +- `ProductRepr` is removed; please use `ArrayPartition` instead. +- Default methods throwing "not implemented" `ErrorException` for some group-related operations. Standard `MethodError` is now thrown instead. +- `LinearAffineMetric` was deprecated in a previous release and the symbol is now removed. + Please use `AffineInvariantMetric` instead. diff --git a/Project.toml b/Project.toml index 2338851467..8c5a5e0c7f 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Manifolds" uuid = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e" authors = ["Seth Axen ", "Mateusz Baran ", "Ronny Bergmann ", "Antoine Levitt "] -version = "0.8.81" +version = "0.9.0" [deps] Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" @@ -49,8 +49,8 @@ Einsum = "0.4" Graphs = "1.4" HybridArrays = "0.4" Kronecker = "0.4, 0.5" -ManifoldDiff = "0.3.6" -ManifoldsBase = "0.14.12" +ManifoldDiff = "0.3.7" +ManifoldsBase = "0.15.0" MatrixEquations = "2.2" OrdinaryDiffEq = "6.31" Plots = "1" diff --git a/benchmark/benchmarks.jl b/benchmark/benchmarks.jl index b6d7d98165..ec5b8722c8 100644 --- a/benchmark/benchmarks.jl +++ b/benchmark/benchmarks.jl @@ -141,7 +141,7 @@ function add_manifold_benchmarks() inverse_retraction_methods=inverse_retraction_methods_rot, ) - pts_prod_mpoints = [Manifolds.ProductRepr(p[1], p[2]) for p in zip(pts_s2, pts_r2)] + pts_prod_mpoints = [ArrayPartition(p[1], p[2]) for p in zip(pts_s2, pts_r2)] add_manifold( m_prod, pts_prod_mpoints, @@ -155,14 +155,14 @@ function add_manifold_benchmarks() TB = TangentBundle(s2) pts_tb = [ - ProductRepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, -1.0, -1.0])), - ProductRepr(convert(T, [0.0, 1.0, 0.0]), convert(T, [2.0, 0.0, 1.0])), - ProductRepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, 2.0, -1.0])), + ArrayPartition(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, -1.0, -1.0])), + ArrayPartition(convert(T, [0.0, 1.0, 0.0]), convert(T, [2.0, 0.0, 1.0])), + ArrayPartition(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, 2.0, -1.0])), ] add_manifold( TB, pts_tb, - "Tangent bundle of S² using MVectors, ProductRepr"; + "Tangent bundle of S² using MVectors, ArrayPartition"; test_tangent_vector_broadcasting=false, ) end @@ -203,13 +203,11 @@ function add_manifold_benchmarks() sphere_tv_dist = Manifolds.normal_tvector_distribution(Ms, (@MVector [1.0, 0.0, 0.0]), 1.0) power_s1_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Ms1), - rand(power_s1_pt_dist), + TangentSpace(Ms1, rand(power_s1_pt_dist)), sphere_tv_dist, ) power_s2_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Ms2), - rand(power_s2_pt_dist), + TangentSpace(Ms2, rand(power_s2_pt_dist)), sphere_tv_dist, ) @@ -224,13 +222,11 @@ function add_manifold_benchmarks() ) rotations_tv_dist = Manifolds.normal_tvector_distribution(Mr, MMatrix(id_rot), 1.0) power_r1_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Mr1), - rand(power_r1_pt_dist), + TangentSpace(Mr1, rand(power_r1_pt_dist)), rotations_tv_dist, ) power_r2_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Mr2), - rand(power_r2_pt_dist), + TangentSpace(Mr2, rand(power_r2_pt_dist)), rotations_tv_dist, ) diff --git a/docs/Project.toml b/docs/Project.toml index c051de4de2..2a2eac142a 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -30,7 +30,7 @@ FiniteDifferences = "0.12" Graphs = "1.4" HybridArrays = "0.4" IJulia = "1" -ManifoldsBase = "0.14.1" +ManifoldsBase = "0.15.0" OrdinaryDiffEq = "6" Plots = "1" PythonPlot = "1" diff --git a/docs/make.jl b/docs/make.jl index cdd8d0f344..0caf926f5a 100755 --- a/docs/make.jl +++ b/docs/make.jl @@ -41,23 +41,25 @@ using OrdinaryDiffEq, BoundaryValueDiffEq, DiffEqCallbacks using Test, FiniteDifferences ENV["GKSwstype"] = "100" -# (d) add contributing.md to docs +# (d) add CONTRIBUTING.md and NEWS.md to docs generated_path = joinpath(@__DIR__, "src", "misc") base_url = "https://github.com/JuliaManifolds/Manifolds.jl/blob/master/" isdir(generated_path) || mkdir(generated_path) -open(joinpath(generated_path, "contributing.md"), "w") do io - # Point to source license file - println( - io, - """ - ```@meta - EditURL = "$(base_url)CONTRIBUTING.md" - ``` - """, - ) - # Write the contents out below the meta block - for line in eachline(joinpath(dirname(@__DIR__), "CONTRIBUTING.md")) - println(io, line) +for fname in ["CONTRIBUTING.md", "NEWS.md"] + open(joinpath(generated_path, fname), "w") do io + # Point to source license file + println( + io, + """ + ```@meta + EditURL = "$(base_url)$(fname)" + ``` + """, + ) + # Write the contents out below the meta block + for line in eachline(joinpath(dirname(@__DIR__), fname)) + println(io, line) + end end end @@ -138,6 +140,7 @@ makedocs(; "Unit-norm symmetric matrices" => "manifolds/spheresymmetricmatrices.md", ], "Combined manifolds" => [ + "Fiber bundle" => "manifolds/fiber_bundle.md", "Graph manifold" => "manifolds/graph.md", "Power manifold" => "manifolds/power.md", "Product manifold" => "manifolds/product.md", @@ -154,6 +157,7 @@ makedocs(; "Atlases and charts" => "features/atlases.md", "Differentiation" => "features/differentiation.md", "Distributions" => "features/distributions.md", + "Group actions" => "features/group_actions.md", "Integration" => "features/integration.md", "Statistics" => "features/statistics.md", "Testing" => "features/testing.md", @@ -161,7 +165,8 @@ makedocs(; ], "Miscellanea" => [ "About" => "misc/about.md", - "Contributing" => "misc/contributing.md", + "Changelog" => "misc/NEWS.md", + "Contributing" => "misc/CONTRIBUTING.md", "Internals" => "misc/internals.md", "Notation" => "misc/notation.md", "References" => "misc/references.md", diff --git a/docs/src/features/group_actions.md b/docs/src/features/group_actions.md new file mode 100644 index 0000000000..22bf676ba9 --- /dev/null +++ b/docs/src/features/group_actions.md @@ -0,0 +1,63 @@ +# Group actions + +Group actions represent actions of a given group on a specified manifold. +The following operations are available: + +* [`action_side`](@ref): whether action acts from the [`LeftSide`](@ref) or [`RightSide`](@ref) (not to be confused with action direction). +* [`apply`](@ref): performs given action of an element of the group on an object of compatible type. +* [`apply_diff`](@ref): differential of [`apply`](@ref) with respect to the object it acts upon. +* [`direction`](@ref): tells whether a given action is [`LeftAction`](@ref), [`RightAction`](@ref). +* [`inverse_apply`](@ref): performs given action of the inverse of an element of the group on an object of compatible type. By default inverts the element and calls [`apply`](@ref) but it may be have a faster implementation for some actions. +* [`inverse_apply_diff`](@ref): counterpart of [`apply_diff`](@ref) for [`inverse_apply`](@ref). +* [`optimal_alignment`](@ref): determine the element of a group that, when it acts upon a point, produces the element closest to another given point in the metric of the G-manifold. + +Furthermore, group operation action features the following: + +* [`translate`](@ref Main.Manifolds.translate): an operation that performs either ([`LeftAction`](@ref)) on the [`LeftSide`](@ref) or ([`RightAction`](@ref)) on the [`RightSide`](@ref) translation, or actions by inverses of elements ([`RightAction`](@ref) on the [`LeftSide`](@ref) and [`LeftAction`](@ref) on the [`RightSide`](@ref)). This is by default performed by calling [`compose`](@ref) with appropriate order of arguments. This function is separated from `compose` mostly to easily represent its differential, [`translate_diff`](@ref). +* [`translate_diff`](@ref): differential of [`translate`](@ref Main.Manifolds.translate) with respect to the point being translated. +* [`adjoint_action`](@ref): adjoint action of a given element of a Lie group on an element of its Lie algebra. +* [`lie_bracket`](@ref): Lie bracket of two vectors from a Lie algebra corresponding to a given group. + +The following group actions are available: + +* Group operation action [`GroupOperationAction`](@ref) that describes action of a group on itself. +* [`RotationAction`](@ref), that is action of [`SpecialOrthogonal`](@ref) group on different manifolds. +* [`TranslationAction`](@ref), which is the action of [`TranslationGroup`](@ref) group on different manifolds. + +```@autodocs +Modules = [Manifolds] +Pages = ["groups/group_action.jl"] +Order = [:type, :function] +``` + +## Group operation action + +```@autodocs +Modules = [Manifolds] +Pages = ["groups/group_operation_action.jl"] +Order = [:type, :function] +``` + +## Rotation action + +```@autodocs +Modules = [Manifolds] +Pages = ["groups/rotation_action.jl"] +Order = [:type, :function] +``` + +## Translation action + +```@autodocs +Modules = [Manifolds] +Pages = ["groups/translation_action.jl"] +Order = [:type, :function] +``` + +## Rotation-translation action (special Euclidean) + +```@autodocs +Modules = [Manifolds] +Pages = ["groups/rotation_translation_action.jl"] +Order = [:type, :const, :function] +``` diff --git a/docs/src/features/utilities.md b/docs/src/features/utilities.md index 988feaa0f9..b1ecffe3c5 100644 --- a/docs/src/features/utilities.md +++ b/docs/src/features/utilities.md @@ -1,13 +1,14 @@ -# Ease of notation +# Utilities + +## Ease of notation The following terms introduce a nicer notation for some operations, for example using the ∈ operator, $p ∈ \mathcal M$, to determine whether $p$ is a point on the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) $\mathcal M$. ````@docs in -TangentSpace ```` -# Fallback for the exponential map: Solving the corresponding ODE +## Fallback for the exponential map: Solving the corresponding ODE When additionally loading [`NLSolve.jl`](https://github.com/JuliaNLSolvers/NLsolve.jl) the following fallback for the exponential map is available. @@ -17,17 +18,9 @@ Pages = ["nlsolve.jl"] Order = [:type, :function] ``` -# Public documentation - -The following functions are of interest for extending and using the [`ProductManifold`](@ref). - -```@docs -submanifold_component -submanifold_components -ProductRepr -``` +## Public documentation -## Specific exception types +### Specific exception types For some manifolds it is useful to keep an extra index, at which point on the manifold, the error occurred as well as to collect all errors that occurred on a manifold. This page contains the manifold-specific error messages this package introduces. diff --git a/docs/src/manifolds/fiber_bundle.md b/docs/src/manifolds/fiber_bundle.md new file mode 100644 index 0000000000..9acbe33db4 --- /dev/null +++ b/docs/src/manifolds/fiber_bundle.md @@ -0,0 +1,17 @@ +# [Fiber bundles](@id FiberBundleSection) + +Fiber bundle $E$ is a manifold that is built on top of another manifold $\mathcal M$ (base space). +It is characterized by a continuous function $Π : E → \mathcal M$. For each point $p ∈ \mathcal M$ the preimage of $p$ by $Π$, $Π^{-1}(\{p\})$ is called a fiber $F$. +Bundle projection can be performed using function [`bundle_projection`](@ref). + +`Manifolds.jl` primarily deals with the case of trivial bundles, where $E$ can be identified with a product $M \times F$. + +[Vector bundles](@ref VectorBundleSection) is a special case of a fiber bundle. Other examples include unit tangent bundle. Note that in general fiber bundles don't have a canonical Riemannian structure but can at least be equipped with an [Ehresmann connection](https://en.wikipedia.org/wiki/Ehresmann_connection), providing notions of parallel transport and curvature. + +## Documentation + +```@autodocs +Modules = [Manifolds, ManifoldsBase] +Pages = ["manifolds/Fiber.jl", "manifolds/FiberBundle.jl"] +Order = [:constant, :type, :function] +``` diff --git a/docs/src/manifolds/group.md b/docs/src/manifolds/group.md index 67ae1a5fab..c7c328fcf2 100644 --- a/docs/src/manifolds/group.md +++ b/docs/src/manifolds/group.md @@ -1,4 +1,4 @@ -# [Group manifolds and actions](@id GroupManifoldSection) +# [Group manifolds](@id GroupManifoldSection) Lie groups, groups that are Riemannian manifolds with a smooth binary group operation [`AbstractGroupOperation`](@ref), are implemented as [`AbstractDecoratorManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/decorator.html#ManifoldsBase.AbstractDecoratorManifold) and specifying the group operation using the [`IsGroupManifold`](@ref) or by decorating an existing manifold with a group operation using [`GroupManifold`](@ref). @@ -180,61 +180,6 @@ Pages = ["groups/translation_group.jl"] Order = [:constant, :type, :function] ``` -## Group actions - -Group actions represent actions of a given group on a specified manifold. -The following operations are available: - -* [`apply`](@ref): performs given action of an element of the group on an object of compatible type. -* [`apply_diff`](@ref): differential of [`apply`](@ref) with respect to the object it acts upon. -* [`direction`](@ref): tells whether a given action is [`LeftForwardAction`](@ref), [`RightForwardAction`](@ref), [`LeftBackwardAction`](@ref) or [`RightBackwardAction`](@ref). -* [`inverse_apply`](@ref): performs given action of the inverse of an element of the group on an object of compatible type. By default inverts the element and calls [`apply`](@ref) but it may be have a faster implementation for some actions. -* [`inverse_apply_diff`](@ref): counterpart of [`apply_diff`](@ref) for [`inverse_apply`](@ref). -* [`optimal_alignment`](@ref): determine the element of a group that, when it acts upon a point, produces the element closest to another given point in the metric of the G-manifold. - -Furthermore, group operation action features the following: - -* [`translate`](@ref Main.Manifolds.translate): an operation that performs either left ([`LeftForwardAction`](@ref)) or right ([`RightBackwardAction`](@ref)) translation, or actions by inverses of elements ([`RightForwardAction`](@ref) and [`LeftBackwardAction`](@ref)). This is by default performed by calling [`compose`](@ref) with appropriate order of arguments. This function is separated from `compose` mostly to easily represent its differential, [`translate_diff`](@ref). -* [`translate_diff`](@ref): differential of [`translate`](@ref Main.Manifolds.translate) with respect to the point being translated. -* [`adjoint_action`](@ref): adjoint action of a given element of a Lie group on an element of its Lie algebra. -* [`lie_bracket`](@ref): Lie bracket of two vectors from a Lie algebra corresponding to a given group. - -The following group actions are available: - -* Group operation action [`GroupOperationAction`](@ref) that describes action of a group on itself. -* [`RotationAction`](@ref), that is action of [`SpecialOrthogonal`](@ref) group on different manifolds. -* [`TranslationAction`](@ref), which is the action of [`TranslationGroup`](@ref) group on different manifolds. - -```@autodocs -Modules = [Manifolds] -Pages = ["groups/group_action.jl"] -Order = [:type, :function] -``` - -### Group operation action - -```@autodocs -Modules = [Manifolds] -Pages = ["groups/group_operation_action.jl"] -Order = [:type, :function] -``` - -### Rotation action - -```@autodocs -Modules = [Manifolds] -Pages = ["groups/rotation_action.jl"] -Order = [:type, :function] -``` - -### Translation action - -```@autodocs -Modules = [Manifolds] -Pages = ["groups/translation_action.jl"] -Order = [:type, :function] -``` - ## Metrics on groups Lie groups by default typically forward all metric-related operations like exponential or logarithmic map to the underlying manifold, for example [`SpecialOrthogonal`](@ref) uses methods for [`Rotations`](@ref) (which is, incidentally, bi-invariant), or [`SpecialEuclidean`](@ref) uses product metric of the translation and rotation parts (which is not invariant under group operation). diff --git a/docs/src/manifolds/power.md b/docs/src/manifolds/power.md index 7d5f898f80..3b9f70a3ee 100644 --- a/docs/src/manifolds/power.md +++ b/docs/src/manifolds/power.md @@ -70,7 +70,7 @@ which is again a valid point so `is_point(M, p)` here also yields true. A disadvantage might be that with nested arrays one loses a little bit of performance. The data however is nicely encapsulated. Accessing the first data item is just `p[1]`. -For accessing points on power manifolds in both representations you can use [`get_component`](@ref) and [`set_component!`](@ref) functions. +For accessing points on power manifolds in both representations you can use [`get_component`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.get_component-Tuple%7BAbstractPowerManifold,%20Any,%20Vararg%7BAny%7D%7D) and [`set_component!`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.set_component!-Tuple%7BAbstractPowerManifold,%20Any,%20Any,%20Vararg%7BAny%7D%7D) functions. They work work both point representations. ```@example 3 @@ -98,8 +98,8 @@ N = 5 GN = PowerManifold(G, NestedReplacingPowerRepresentation(), N) q = [1.0 0.0; 0.0 1.0] -p1 = [ProductRepr(SVector{2,Float64}([i - 0.1, -i]), SMatrix{2,2,Float64}(exp(R2, q, hat(R2, q, i)))) for i in 1:N] -p2 = [ProductRepr(SVector{2,Float64}([i - 0.1, -i]), SMatrix{2,2,Float64}(exp(R2, q, hat(R2, q, -i)))) for i in 1:N] +p1 = [ArrayPartition(SVector{2,Float64}([i - 0.1, -i]), SMatrix{2,2,Float64}(exp(R2, q, hat(R2, q, i)))) for i in 1:N] +p2 = [ArrayPartition(SVector{2,Float64}([i - 0.1, -i]), SMatrix{2,2,Float64}(exp(R2, q, hat(R2, q, -i)))) for i in 1:N] X = similar(p1); diff --git a/docs/src/manifolds/product.md b/docs/src/manifolds/product.md index 99c37988dc..cd1e38aafb 100644 --- a/docs/src/manifolds/product.md +++ b/docs/src/manifolds/product.md @@ -1,7 +1,7 @@ # [Product manifold](@id ProductManifoldSection) Product manifold $\mathcal M = \mathcal{M}_1 × \mathcal{M}_2 × … × \mathcal{M}_n$ of manifolds $\mathcal{M}_1, \mathcal{M}_2, …, \mathcal{M}_n$. -Points on the product manifold can be constructed using [`ProductRepr`](@ref) with canonical projections $Π_i : \mathcal{M} → \mathcal{M}_i$ for $i ∈ 1, 2, …, n$ provided by [`submanifold_component`](@ref). +Points on the product manifold can be constructed using `ArrayPartition` (from `RecursiveArrayTools.jl`) with canonical projections $Π_i : \mathcal{M} → \mathcal{M}_i$ for $i ∈ 1, 2, …, n$ provided by [`submanifold_component`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.submanifold_component-Tuple). ```@autodocs Modules = [Manifolds] diff --git a/docs/src/manifolds/sphere.md b/docs/src/manifolds/sphere.md index 8db38c7e1d..3153ffddc5 100644 --- a/docs/src/manifolds/sphere.md +++ b/docs/src/manifolds/sphere.md @@ -11,7 +11,7 @@ Sphere ``` For the higher-dimensional arrays, for example unit (Frobenius) norm matrices, the manifold is generated using the size of the matrix. -To create the unit sphere of $3×2$ real-valued matrices, write `ArraySphere(3,2)` and the complex case is done – as for the [`Euclidean`](@ref) case – with an keyword argument `ArraySphere(3,2; field = ℂ)`. This case also covers the classical sphere as a special case, but you specify the size of the vectors/embedding instead: The 2-sphere can here be generated `ArraySphere(3)`. +To create the unit sphere of $3×2$ real-valued matrices, write `ArraySphere(3,2)` and the complex case is done – as for the [`Euclidean`](@ref) case – with an keyword argument `ArraySphere(3,2; field=ℂ)`. This case also covers the classical sphere as a special case, but you specify the size of the vectors/embedding instead: The 2-sphere can here be generated `ArraySphere(3)`. ```@docs ArraySphere diff --git a/docs/src/manifolds/vector_bundle.md b/docs/src/manifolds/vector_bundle.md index 306c2d5e62..19d732ca6d 100644 --- a/docs/src/manifolds/vector_bundle.md +++ b/docs/src/manifolds/vector_bundle.md @@ -1,19 +1,11 @@ # [Vector bundles](@id VectorBundleSection) -Vector bundle $E$ is a manifold that is built on top of another manifold $\mathcal M$ (base space). -It is characterized by a continuous function $Π : E → \mathcal M$, such that for each point $p ∈ \mathcal M$ the preimage of $p$ by $Π$, $Π^{-1}(\{p\})$, has a structure of a vector space. -These vector spaces are called fibers. -Bundle projection can be performed using function [`bundle_projection`](@ref). +Vector bundle $E$ is a special case of a [fiber bundle](@ref FiberBundleSection) where each fiber is a vector space. -Tangent bundle is a simple example of a vector bundle, where each fiber is the tangent space at the specified point $x$. +Tangent bundle is a simple example of a vector bundle, where each fiber is the tangent space at the specified point $p$. An object representing a tangent bundle can be obtained using the constructor called `TangentBundle`. -Fibers of a vector bundle are represented by the type `VectorBundleFibers`. -The important difference between functions operating on `VectorBundle` and `VectorBundleFibers` is that in the first case both a point on the underlying manifold and the vector are represented together (by a single argument) while in the second case only the vector part is present, while the point is supplied in a different argument where needed. - -`VectorBundleFibers` refers to the whole set of fibers of a vector bundle. -There is also another type, [`VectorSpaceAtPoint`](@ref), that represents a specific fiber at a given point. -This distinction is made to reduce the need to repeatedly construct objects of type [`VectorSpaceAtPoint`](@ref) in certain usage scenarios. +There is also another type, [`VectorSpaceFiber`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.VectorSpaceFiber), that represents a specific fiber at a given point. This is also considered a manifold. ## FVector @@ -25,7 +17,7 @@ It is used for example in musical isomorphisms (the [`flat`](@ref) and [`sharp`] ```@autodocs Modules = [Manifolds, ManifoldsBase] -Pages = ["manifolds/VectorBundle.jl"] +Pages = ["manifolds/VectorFiber.jl", "manifolds/VectorBundle.jl"] Order = [:constant, :type, :function] ``` @@ -37,8 +29,8 @@ The following code defines a point on the tangent bundle of the sphere $S^2$ and using Manifolds M = Sphere(2) TB = TangentBundle(M) -p = ProductRepr([1.0, 0.0, 0.0], [0.0, 1.0, 3.0]) -X = ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0, -2.0]) +p = ArrayPartition([1.0, 0.0, 0.0], [0.0, 1.0, 3.0]) +X = ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0, -2.0]) ``` An approximation of the exponential in the Sasaki metric using 1000 steps can be calculated as follows. diff --git a/docs/src/misc/about.md b/docs/src/misc/about.md index 31c790515d..b94d74e017 100644 --- a/docs/src/misc/about.md +++ b/docs/src/misc/about.md @@ -14,4 +14,4 @@ See the [GitHub contributors page](https://github.com/JuliaManifolds/Manifolds.jl/graphs/contributors). -[Contributions](contributing.md) are welcome! +[Contributions](CONTRIBUTING.md) are welcome! diff --git a/docs/src/misc/internals.md b/docs/src/misc/internals.md index 3d5d10b2b4..835cdb838e 100644 --- a/docs/src/misc/internals.md +++ b/docs/src/misc/internals.md @@ -13,14 +13,12 @@ Manifolds.mul!_safe Manifolds.nzsign Manifolds.realify Manifolds.realify! -Manifolds.select_from_tuple Manifolds.symmetrize Manifolds.symmetrize! Manifolds.unrealify! Manifolds.usinc Manifolds.usinc_from_cos Manifolds.vec2skew! -Manifolds.ziptuples ``` ## Types in Extensions @@ -29,4 +27,4 @@ Manifolds.ziptuples Modules = [Manifolds] Pages = ["../ext/ManifoldsOrdinaryDiffEqDiffEqCallbacksExt.jl"] Order = [:type, :function] -``` \ No newline at end of file +``` diff --git a/docs/src/misc/notation.md b/docs/src/misc/notation.md index 768c426c78..ee7975861d 100644 --- a/docs/src/misc/notation.md +++ b/docs/src/misc/notation.md @@ -11,7 +11,7 @@ Within the documented functions, the utf8 symbols are used whenever possible, as |:--:|:--------------- |:--:|:-- | | ``\tau_p`` | action map by group element ``p`` | ``\mathrm{L}_p``, ``\mathrm{R}_p`` | either left or right | | ``\operatorname{Ad}_p(X)`` | adjoint action of element ``p`` of a Lie group on the element ``X`` of the corresponding Lie algebra | | | -| ``\times`` | Cartesian product of two manifolds | | see [`ProductManifold`](@ref) | +| ``\times`` | Cartesian product of two manifolds | | see [`ProductManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.ProductManifold) | | ``^{\wedge}`` | (n-ary) Cartesian power of a manifold | | see [`PowerManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.PowerManifold) | | ``\cdot^\mathrm{H}`` | conjugate/Hermitian transpose | | | ``a`` | coordinates of a point in a chart | | see [`get_parameters`](@ref) | @@ -22,7 +22,7 @@ Within the documented functions, the utf8 symbols are used whenever possible, as | ``n`` | dimension (of a manifold) | ``n_1,n_2,\ldots,m, \dim(\mathcal M)``| for the real dimension sometimes also ``\dim_{\mathbb R}(\mathcal M)``| | ``d(\cdot,\cdot)`` | (Riemannian) distance | ``d_{\mathcal M}(\cdot,\cdot)`` | | | ``\exp_p X`` | exponential map at ``p \in \mathcal M`` of a vector ``X \in T_p \mathcal M`` | ``\exp_p(X)`` | | -| ``F`` | a fiber | | see [`VectorBundleFibers`](@ref) | +| ``F`` | a fiber | | see [`Fiber`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#Fiber) | | ``\mathbb F`` | a field, usually ``\mathbb F \in \{\mathbb R,\mathbb C, \mathbb H\}``, i.e. the real, complex, and quaternion numbers, respectively. | |field a manifold or a basis is based on | | ``\gamma`` | a geodesic | ``\gamma_{p;q}``, ``\gamma_{p,X}`` | connecting two points ``p,q`` or starting in ``p`` with velocity ``X``. | | ``\operatorname{grad} f(p)`` | (Riemannian) gradient of function ``f \colon \mathcal{M} \to \mathbb{R}`` at ``p \in \mathcal{M}`` | | | diff --git a/ext/ManifoldsRecipesBaseExt.jl b/ext/ManifoldsRecipesBaseExt.jl index 6d9b831124..65068c4553 100644 --- a/ext/ManifoldsRecipesBaseExt.jl +++ b/ext/ManifoldsRecipesBaseExt.jl @@ -2,6 +2,7 @@ module ManifoldsRecipesBaseExt if isdefined(Base, :get_extension) using Manifolds + using Manifolds: TypeParameter using Colors: RGBA using RecipesBase: @recipe, @series @@ -9,6 +10,7 @@ else # imports need to be relative for Requires.jl-based workflows: # https://github.com/JuliaArrays/ArrayInterface.jl/pull/387 using ..Manifolds + using ..Manifolds: TypeParameter using ..RecipesBase: @recipe, @series using ..Colors: RGBA @@ -24,7 +26,7 @@ SURFACE_RESOLUTION_DEFAULT = 32 # Plotting Recipe – Poincaré Ball # @recipe function f( - M::Hyperbolic{2}, + M::Hyperbolic{TypeParameter{Tuple{2}}}, pts::AbstractVector{P}, vecs::Union{AbstractVector{T},Nothing}=nothing; circle_points=CIRCLE_DEFAULT_PLOT_POINTS, @@ -87,7 +89,7 @@ end # Plotting Recipe – Poincaré Half plane # @recipe function f( - M::Hyperbolic{2}, + M::Hyperbolic{TypeParameter{Tuple{2}}}, pts::AbstractVector{P}, vecs::Union{AbstractVector{T},Nothing}=nothing; geodesic_interpolation=-1, @@ -133,7 +135,7 @@ end # Plotting Recipe – Hyperboloid # @recipe function f( - M::Hyperbolic{2}, + M::Hyperbolic{TypeParameter{Tuple{2}}}, pts::Union{AbstractVector{P},Nothing}=nothing, vecs::Union{AbstractVector{T},Nothing}=nothing; geodesic_interpolation=-1, @@ -229,7 +231,7 @@ end # Plotting Recipe – Sphere # @recipe function f( - M::Sphere{2,ℝ}, + M::Sphere{TypeParameter{Tuple{2}},ℝ}, pts::Union{AbstractVector{P},Nothing}=nothing, vecs::Union{AbstractVector{T},Nothing}=nothing; geodesic_interpolation=-1, diff --git a/ext/ManifoldsTestExt/tests_general.jl b/ext/ManifoldsTestExt/tests_general.jl index 7e43010977..435f1a7484 100644 --- a/ext/ManifoldsTestExt/tests_general.jl +++ b/ext/ManifoldsTestExt/tests_general.jl @@ -156,12 +156,6 @@ function test_manifold( 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), - ) end test_representation_size && Test.@testset "representation" begin @@ -174,9 +168,6 @@ function test_manifold( end test_repr(Manifolds.representation_size(M)) - for fiber in (Manifolds.TangentSpace, Manifolds.CotangentSpace) - test_repr(Manifolds.representation_size(Manifolds.VectorBundleFibers(fiber, M))) - end end test_injectivity_radius && Test.@testset "injectivity radius" begin @@ -396,10 +387,10 @@ function test_manifold( test_vector_spaces && Test.@testset "vector spaces tests" begin for p in pts X = zero_vector(M, p) - mts = Manifolds.VectorBundleFibers(Manifolds.TangentSpace, M) - Test.@test isapprox(M, p, X, zero_vector(mts, p)) + mts = TangentSpace(M, p) + Test.@test isapprox(M, p, X, zero_vector(mts, X)) if is_mutating - zero_vector!(mts, X, p) + zero_vector!(mts, X, X) Test.@test isapprox(M, p, X, zero_vector(M, p)) end end @@ -759,12 +750,12 @@ function test_manifold( test_rand_point && Test.@testset "Base.rand point generation" begin rng_a = MersenneTwister(123) rng_b = MersenneTwister(123) - Test.@test is_point(M, rand(M), true) + Test.@test is_point(M, rand(M); error=:error) # ensure that the RNG source is actually used Test.@test rand(rng_a, M) == rand(rng_b, M) # generation of multiple points - Test.@test all(p -> is_point(M, p, true), rand(M, 3)) - Test.@test all(p -> is_point(M, p, true), rand(rng_a, M, 3)) + Test.@test all(p -> is_point(M, p; error=:error), rand(M, 3)) + Test.@test all(p -> is_point(M, p; error=:error), rand(rng_a, M, 3)) if test_inplace && is_mutating rng_a = MersenneTwister(123) @@ -772,10 +763,10 @@ function test_manifold( p = allocate(pts[1]) rand!(M, p) - Test.@test is_point(M, p, true) + Test.@test is_point(M, p; error=:error) p = allocate(pts[1]) rand!(rng_a, M, p) - Test.@test is_point(M, p, true) + Test.@test is_point(M, p; error=:error) # ensure that the RNG source is actually used q = allocate(pts[1]) rand!(rng_b, M, q) @@ -808,7 +799,7 @@ function test_manifold( Test.@test is_vector(M, p, X, true; atol=atol) X = allocate(tv[1]) rand!(rng_a, M, X; vector_at=p) - Test.@test is_point(M, p, true) + Test.@test is_point(M, p; error=:error) # ensure that the RNG source is actually used Y = allocate(tv[1]) rand!(rng_b, M, Y; vector_at=p) @@ -819,11 +810,12 @@ function 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)}} + Test.@test supp isa + Manifolds.FVectorSupport{<:TangentSpace{number_system(M),typeof(M)}} for _ in 1:10 randtv = rand(tvd) atol = rand_tvector_atol_multiplier * find_eps(randtv) - Test.@test is_vector(M, supp.point, randtv, true; atol=atol) + Test.@test is_vector(M, supp.space.point, randtv, true; atol=atol) end end end @@ -897,7 +889,7 @@ function test_parallel_transport( Test.@test isapprox(M, q, Y1, Y2) end # Test that Y is a tangent vector at q - Test.@test is_vector(M, p, Y1, true) + Test.@test is_vector(M, p, Y1; error=:error) end end end diff --git a/ext/ManifoldsTestExt/tests_group.jl b/ext/ManifoldsTestExt/tests_group.jl index 5c833e0445..10ead50598 100644 --- a/ext/ManifoldsTestExt/tests_group.jl +++ b/ext/ManifoldsTestExt/tests_group.jl @@ -1,4 +1,8 @@ using Base: IdentityUnitRange + +using Manifolds: + LeftForwardAction, LeftBackwardAction, RightForwardAction, RightBackwardAction + """ test_group( G, @@ -552,18 +556,18 @@ function test_action( test_mutating_group=true, test_mutating_action=true, test_diff=false, - test_switch_direction=Manifolds.LeftRightSwitch(), + test_switch_direction=true, ) G = base_group(A) M = group_manifold(A) e = Identity(G) Test.@testset "Basic action properties" begin # COV_EXCL_LINE - test_switch_direction !== false && Test.@testset "Direction" begin + test_switch_direction && Test.@testset "Direction" begin Aswitch = switch_direction(A) Test.@test direction(A) === _direction_from_type(A) - sd = switch_direction(_direction_from_type(A), test_switch_direction) + sd = switch_direction(_direction_from_type(A)) Test.@test isa(Aswitch, AbstractGroupAction{typeof(sd)}) Test.@test direction(Aswitch) === sd end @@ -741,10 +745,10 @@ function test_action( eX = allocate(X) Test.@test apply_diff!(A, eX, e, m, X) === eX - Test.@test isapprox(G, m, eX, X; atol=atol) + Test.@test isapprox(M, m, eX, X; atol=atol) eX = allocate(X) Test.@test inverse_apply_diff!(A, eX, e, m, X) === eX - Test.@test isapprox(G, m, eX, X; atol=atol) + Test.@test isapprox(M, m, eX, X; atol=atol) end end end diff --git a/src/Manifolds.jl b/src/Manifolds.jl index 259c4a7b17..e56361fcac 100644 --- a/src/Manifolds.jl +++ b/src/Manifolds.jl @@ -58,6 +58,7 @@ import ManifoldsBase: embed!, exp, exp!, + fiber_dimension, get_basis, get_basis_default, get_basis_diagonalizing, @@ -99,7 +100,6 @@ import ManifoldsBase: is_vector, inverse_retract, inverse_retract!, - _inverse_retract, _inverse_retract!, inverse_retract_cayley!, inverse_retract_embedded!, @@ -138,10 +138,14 @@ import ManifoldsBase: retract_polar!, retract_project!, retract_qr!, + retract_sasaki!, retract_softmax!, riemann_tensor, riemann_tensor!, set_component!, + submanifold, + submanifold_component, + submanifold_components, vector_space_dimension, vector_transport_along, # just specified in Euclidean - the next 5 as well vector_transport_along_diff, @@ -168,9 +172,7 @@ import ManifoldsBase: Weingarten, Weingarten!, zero_vector, - zero_vector!, - CotangentSpace, - TangentSpace + zero_vector! import ManifoldDiff: adjoint_Jacobi_field, adjoint_Jacobi_field!, @@ -217,8 +219,10 @@ using ManifoldsBase: ComplexNumbers, ComponentManifoldError, CompositeManifoldError, + CotangentSpace, CotangentSpaceType, CoTFVector, + CoTVector, DefaultBasis, DefaultOrthogonalBasis, DefaultOrthonormalBasis, @@ -230,7 +234,10 @@ using ManifoldsBase: EmptyTrait, EuclideanMetric, ExponentialRetraction, + Fiber, + FiberType, FVector, + InverseProductRetraction, IsIsometricEmbeddedManifold, IsEmbeddedManifold, IsEmbeddedSubmanifold, @@ -249,9 +256,15 @@ using ManifoldsBase: PolarInverseRetraction, PolarRetraction, PoleLadderTransport, + PowerBasisData, PowerManifold, PowerManifoldNested, PowerManifoldNestedReplacing, + ProductBasisData, + ProductManifold, + ProductMetric, + ProductRetraction, + ProductVectorTransport, ProjectedOrthonormalBasis, ProjectionInverseRetraction, ProjectionRetraction, @@ -261,18 +274,23 @@ using ManifoldsBase: QRRetraction, RealNumbers, RiemannianMetric, + SasakiRetraction, ScaledVectorTransport, SchildsLadderTransport, ShootingInverseRetraction, SoftmaxRetraction, SoftmaxInverseRetraction, + TangentSpace, TangentSpaceType, TCoTSpaceType, TFVector, TVector, + TypeParameter, + ValidationCoTVector, ValidationManifold, ValidationMPoint, ValidationTVector, + VectorSpaceFiber, VectorSpaceType, VeeOrthogonalBasis, @invoke_maker, @@ -280,15 +298,19 @@ using ManifoldsBase: combine_allocation_promotion_functions, geodesic, geodesic!, + get_parameter, merge_traits, next_trait, + number_of_components, number_system, real_dimension, rep_size_to_colons, shortest_geodesic, shortest_geodesic!, size_to_tuple, - trait + trait, + wrap_type_parameter, + ziptuples using ManifoldDiff: ManifoldDiff using ManifoldDiff: default_differential_backend, @@ -339,6 +361,9 @@ include("manifold_fallbacks.jl") include("manifolds/ConnectionManifold.jl") include("manifolds/MetricManifold.jl") include("manifolds/QuotientManifold.jl") +include("manifolds/Fiber.jl") +include("manifolds/FiberBundle.jl") +include("manifolds/VectorFiber.jl") include("manifolds/VectorBundle.jl") include("groups/group.jl") @@ -357,7 +382,7 @@ METAMANIFOLDS = [ PowerManifoldNested, PowerManifoldNestedReplacing, ProductManifold, - TangentSpaceAtPoint, + TangentSpace, ValidationManifold, VectorBundle, ] @@ -468,6 +493,8 @@ include("groups/rotation_action.jl") include("groups/special_euclidean.jl") +include("groups/rotation_translation_action.jl") + # final utilities include("trait_recursion_breaking.jl") @@ -481,14 +508,15 @@ This method employs [`is_point`](https://juliamanifolds.github.io/ManifoldsBase. Base.in(p, M::AbstractManifold; kwargs...) = is_point(M, p, false; kwargs...) @doc raw""" - Base.in(p, TpM::TangentSpaceAtPoint; kwargs...) - X ∈ TangentSpaceAtPoint(M,p) + Base.in(p, TpM::TangentSpace; kwargs...) + X ∈ TangentSpace(M, p) Check whether `X` is a tangent vector from (in) the tangent space $T_p\mathcal M$, i.e. -the [`TangentSpaceAtPoint`](@ref) at `p` on the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) `M`. +the [`TangentSpace`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.TangentSpace) +at `p` on the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) `M`. This method uses [`is_vector`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/functions.html#ManifoldsBase.is_vector) deactivating the error throw option. """ -function Base.in(X, TpM::TangentSpaceAtPoint; kwargs...) +function Base.in(X, TpM::TangentSpace; kwargs...) return is_vector(base_manifold(TpM), TpM.point, X, false; kwargs...) end @@ -650,8 +678,7 @@ export AbstractDecoratorManifold export IsIsometricEmbeddedManifold, IsEmbeddedManifold, IsEmbeddedSubmanifold export IsDefaultMetric, IsDefaultConnection, IsMetricManifold, IsConnectionManifold export ValidationManifold, ValidationMPoint, ValidationTVector, ValidationCoTVector -export CotangentBundle, - CotangentSpaceAtPoint, CotangentBundleFibers, CotangentSpace, FVector +export Fiber, FiberBundle, CotangentBundle, CotangentSpace, FVector export AbstractPowerManifold, AbstractPowerRepresentation, ArrayPowerRepresentation, @@ -661,16 +688,14 @@ export AbstractPowerManifold, QuotientManifold export ProductManifold, EmbeddedManifold export GraphManifold, GraphManifoldType, VertexManifold, EdgeManifold -export ProductRepr, ArrayPartition -export ProjectedPointDistribution, TangentBundle, TangentBundleFibers -export TangentSpace, TangentSpaceAtPoint, VectorSpaceAtPoint, VectorSpaceType, VectorBundle -export VectorBundleFibers +export ArrayPartition +export ProjectedPointDistribution, TangentBundle +export TangentSpace, VectorSpaceFiber, VectorSpaceType, VectorBundle export AbstractVectorTransportMethod, DifferentiatedRetractionVectorTransport, ParallelTransport, ProjectedPointDistribution export PoleLadderTransport, SchildsLadderTransport export ProductVectorTransport -export AbstractAffineConnection, - AbstractConnectionManifold, ConnectionManifold, LeviCivitaConnection +export AbstractAffineConnection, ConnectionManifold, LeviCivitaConnection export AbstractCartanSchoutenConnection, CartanSchoutenMinus, CartanSchoutenPlus, CartanSchoutenZero export MetricManifold @@ -706,7 +731,6 @@ export AbstractRetractionMethod, ODEExponentialRetraction, PadeRetraction, ProductRetraction, - PowerRetraction, SasakiRetraction # Inverse Retraction types export AbstractInverseRetractionMethod, @@ -738,6 +762,7 @@ export CachedBasis, export ComponentManifoldError, CompositeManifoldError # Functions on Manifolds export ×, + action_side, allocate, allocate_result, base_manifold, @@ -770,7 +795,6 @@ export ×, einstein_tensor, embed, embed!, - equiv, exp, exp!, flat, @@ -783,8 +807,6 @@ export ×, get_embedding, get_orbit_action, get_total_space, - grad_euclidean_to_manifold, - grad_euclidean_to_manifold!, hat, hat!, horizontal_component, @@ -896,11 +918,9 @@ export AbstractGroupAction, GroupManifold, GroupOperationAction, Identity, - InvariantMetric, LeftAction, - LeftBackwardAction, - LeftForwardAction, LeftInvariantMetric, + LeftSide, MultiplicationOperation, Orthogonal, PowerGroup, @@ -908,10 +928,11 @@ export AbstractGroupAction, ProductOperation, RealCircleGroup, RightAction, - RightBackwardAction, - RightForwardAction, RightInvariantMetric, + RightSide, RotationAction, + RotationTranslationAction, + RotationTranslationActionOnVector, SemidirectProductGroup, SpecialEuclidean, SpecialLinear, @@ -941,6 +962,7 @@ export adjoint_action, compose, compose!, direction, + direction_and_side, exp_lie, exp_lie!, group_manifold, @@ -979,6 +1001,7 @@ export adjoint_action, optimal_alignment!, screw_matrix, switch_direction, + switch_side, translate, translate!, translate_diff, diff --git a/src/atlases.jl b/src/atlases.jl index 9b4bbfa110..b1ca2e4a0e 100644 --- a/src/atlases.jl +++ b/src/atlases.jl @@ -352,7 +352,8 @@ end The basis induced by chart with index `i` from an [`AbstractAtlas`](@ref) `A` of vector space of type `vs`. -For the `vs` a [`TangentSpace`](@ref) this works as follows: +For the `vs` a [`TangentSpace`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.TangentSpace) +this works as follows: Let ``n`` denote the dimension of the manifold ``\mathcal M``. @@ -380,7 +381,7 @@ struct InducedBasis{𝔽,VST<:VectorSpaceType,TA<:AbstractAtlas,TI} <: AbstractB end """ - induced_basis(::AbstractManifold, A::AbstractAtlas, i, VST::VectorSpaceType = TangentSpace) + induced_basis(::AbstractManifold, A::AbstractAtlas, i, VST::VectorSpaceType = TangentSpaceType()) Get the basis induced by chart with index `i` from an [`AbstractAtlas`](@ref) `A` of vector space of type `vs`. Returns an object of type [`InducedBasis`](@ref). @@ -393,7 +394,7 @@ function induced_basis( ::AbstractManifold{𝔽}, A::AbstractAtlas, i, - VST::VectorSpaceType=TangentSpace, + VST::VectorSpaceType=TangentSpaceType(), ) where {𝔽} return InducedBasis{𝔽,typeof(VST),typeof(A),typeof(i)}(VST, A, i) end @@ -410,14 +411,14 @@ function dual_basis( ::Any, B::InducedBasis{𝔽,TangentSpaceType}, ) where {𝔽} - return induced_basis(M, B.A, B.i, CotangentSpace) + return induced_basis(M, B.A, B.i, CotangentSpaceType()) end function dual_basis( M::AbstractManifold{𝔽}, ::Any, B::InducedBasis{𝔽,CotangentSpaceType}, ) where {𝔽} - return induced_basis(M, B.A, B.i, TangentSpace) + return induced_basis(M, B.A, B.i, TangentSpaceType()) end function ManifoldsBase._get_coordinates(M::AbstractManifold, p, X, B::InducedBasis) diff --git a/src/deprecated.jl b/src/deprecated.jl index 306e1334c0..8b13789179 100644 --- a/src/deprecated.jl +++ b/src/deprecated.jl @@ -1,2 +1 @@ -Base.@deprecate_binding LinearAffineMetric AffineInvariantMetric -export LinearAffineMetric + diff --git a/src/distributions.jl b/src/distributions.jl index f1ac656e62..78aee049c6 100644 --- a/src/distributions.jl +++ b/src/distributions.jl @@ -7,26 +7,25 @@ is a vector from a fiber of a vector bundle. struct FVectorvariate <: VariateForm end """ - FVectorSupport(space::AbstractManifold, VectorBundleFibers) + FVectorSupport(space::AbstractManifold, VectorSpaceFiber) Value support for vector bundle fiber-valued distributions (values from a fiber of a vector bundle at a `point` from the given manifold). For example used for tangent vector-valued distributions. """ -struct FVectorSupport{TSpace<:VectorBundleFibers,T} <: ValueSupport +struct FVectorSupport{TSpace<:VectorSpaceFiber} <: ValueSupport space::TSpace - point::T end """ - FVectorDistribution{TSpace<:VectorBundleFibers, T} + FVectorDistribution{TSpace<:VectorSpaceFiber, T} An abstract distribution for vector bundle fiber-valued distributions (values from a fiber of a vector bundle at point `x` from the given manifold). For example used for tangent vector-valued distributions. """ -abstract type FVectorDistribution{TSpace<:VectorBundleFibers,T} <: - Distribution{FVectorvariate,FVectorSupport{TSpace,T}} end +abstract type FVectorDistribution{TSpace<:VectorSpaceFiber} <: + Distribution{FVectorvariate,FVectorSupport{TSpace}} end """ MPointvariate diff --git a/src/groups/GroupManifold.jl b/src/groups/GroupManifold.jl index 9e117b3a79..c8983731be 100644 --- a/src/groups/GroupManifold.jl +++ b/src/groups/GroupManifold.jl @@ -46,7 +46,7 @@ function inverse_retract( q, method::GroupLogarithmicInverseRetraction, ) - conv = direction(method) + conv = direction_and_side(method) pinvq = inverse_translate(G, p, q, conv) Xₑ = log_lie(G, pinvq) return translate_diff(G, p, Identity(G), Xₑ, conv) @@ -60,7 +60,7 @@ function inverse_retract!( q, method::GroupLogarithmicInverseRetraction, ) - conv = direction(method) + conv = direction_and_side(method) pinvq = inverse_translate(G, p, q, conv) Xₑ = log_lie(G, pinvq) return translate_diff!(G, X, p, Identity(G), Xₑ, conv) @@ -69,12 +69,17 @@ end function is_point( ::TraitList{<:IsGroupManifold}, G::GroupManifold, - e::Identity, - te::Bool=false; + e::Identity; + error::Symbol=:none, kwargs..., ) ie = is_identity(G, e; kwargs...) - (te && !ie) && throw(DomainError(e, "The provided identity is not a point on $G.")) + if !ie + s = "The provided identity is not a point on $G." + (error === :error) && throw(DomainError(e, s)) + (error === :info) && @info s + (error === :warn) && @warn s + end return ie end @@ -83,16 +88,21 @@ function is_vector( G::GroupManifold, e::Identity, X, - te::Bool=false, - cbp=true; + cbp::Bool; + error::Symbol=:none, kwargs..., ) if cbp ie = is_identity(G, e; kwargs...) - (te && !ie) && throw(DomainError(e, "The provided identity is not a point on $G.")) - (!te && !ie) && return false + if !ie + s = "The provided identity is not a point on $G." + (error === :error) && throw(DomainError(e, s)) + (error === :info) && @info s + (error === :warn) && @warn s + return false + end end - return is_vector(G.manifold, identity_element(G), X, te, false; kwargs...) + return is_vector(G.manifold, identity_element(G), X, false, te; kwargs...) end Base.show(io::IO, G::GroupManifold) = print(io, "GroupManifold($(G.manifold), $(G.op))") diff --git a/src/groups/addition_operation.jl b/src/groups/addition_operation.jl index be37c9b688..2c0f110d47 100644 --- a/src/groups/addition_operation.jl +++ b/src/groups/addition_operation.jl @@ -93,7 +93,7 @@ function inverse_translate_diff( p, q, X, - ::ActionDirection, + ::ActionDirectionAndSide, ) return X end @@ -105,7 +105,7 @@ function inverse_translate_diff!( p, q, X, - ::ActionDirection, + ::ActionDirectionAndSide, ) return copyto!(G, Y, p, X) end @@ -138,7 +138,7 @@ function translate_diff( p, q, X, - ::ActionDirection, + ::ActionDirectionAndSide, ) return X end @@ -150,7 +150,7 @@ function translate_diff!( p, q, X, - ::ActionDirection, + ::ActionDirectionAndSide, ) return copyto!(G, Y, p, X) end diff --git a/src/groups/circle_group.jl b/src/groups/circle_group.jl index cb057778b6..34c97fb430 100644 --- a/src/groups/circle_group.jl +++ b/src/groups/circle_group.jl @@ -15,7 +15,6 @@ CircleGroup() = GroupManifold(Circle{ℂ}(), MultiplicationOperation()) else return merge_traits( IsGroupManifold(M.op), - HasBiinvariantMetric(), IsDefaultMetric(EuclideanMetric()), active_traits(f, M.manifold, args...), IsExplicitDecorator(), #pass to Euclidean by default/last fallback @@ -48,6 +47,10 @@ function compose!( return copyto!(x, compose(G, p, q)) end +has_biinvariant_metric(::CircleGroup) = true + +has_invariant_metric(::CircleGroup, ::ActionDirectionAndSide) = true + identity_element(G::CircleGroup) = 1.0 identity_element(::CircleGroup, p::Number) = one(p) @@ -74,18 +77,20 @@ lie_bracket(::CircleGroup, X, Y) = zero(X) lie_bracket!(::CircleGroup, Z, X, Y) = fill!(Z, 0) -translate_diff(::GT, p, q, X, ::ActionDirection) where {GT<:CircleGroup} = map(*, p, X) +function translate_diff(::GT, p, q, X, ::ActionDirectionAndSide) where {GT<:CircleGroup} + return map(*, p, X) +end function translate_diff( ::CircleGroup, ::Identity{MultiplicationOperation}, q, X, - ::ActionDirection, + ::ActionDirectionAndSide, ) return X end -function translate_diff!(G::CircleGroup, Y, p, q, X, conv::ActionDirection) +function translate_diff!(G::CircleGroup, Y, p, q, X, conv::ActionDirectionAndSide) return copyto!(Y, translate_diff(G, p, q, X, conv)) end diff --git a/src/groups/general_linear.jl b/src/groups/general_linear.jl index 844d3b429c..8f2f24d21b 100644 --- a/src/groups/general_linear.jl +++ b/src/groups/general_linear.jl @@ -1,6 +1,5 @@ @doc raw""" - GeneralLinear{n,𝔽} <: - AbstractDecoratorManifold{𝔽} + GeneralLinear{T,𝔽} <: AbstractDecoratorManifold{𝔽} The general linear group, that is, the group of all invertible matrices in ``𝔽^{n×n}``. @@ -16,7 +15,9 @@ vector in the Lie algebra, and ``⟨⋅,⋅⟩_\mathrm{F}`` denotes the Frobeniu By default, tangent vectors ``X_p`` are represented with their corresponding Lie algebra vectors ``X_e = p^{-1}X_p``. """ -struct GeneralLinear{n,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct GeneralLinear{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end function active_traits(f, ::GeneralLinear, args...) return merge_traits( @@ -27,9 +28,12 @@ function active_traits(f, ::GeneralLinear, args...) ) end -GeneralLinear(n, 𝔽::AbstractNumbers=ℝ) = GeneralLinear{n,𝔽}() +function GeneralLinear(n::Int, 𝔽::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return GeneralLinear{typeof(size),𝔽}(size) +end -function allocation_promotion_function(::GeneralLinear{n,ℂ}, f, ::Tuple) where {n} +function allocation_promotion_function(::GeneralLinear{<:Any,ℂ}, f, ::Tuple) return complex end @@ -88,12 +92,12 @@ end function exp!(G::GeneralLinear, q, p, X, t::Number) return exp!(G, q, p, t * X) end -function exp!(::GeneralLinear{1}, q, p, X) +function exp!(::GeneralLinear{TypeParameter{Tuple{1}}}, q, p, X) p1 = p isa Identity ? p : p[1] q[1] = p1 * exp(X[1]) return q end -function exp!(G::GeneralLinear{2}, q, p, X) +function exp!(G::GeneralLinear{TypeParameter{Tuple{2}}}, q, p, X) if isnormal(X; atol=sqrt(eps(real(eltype(X))))) return compose!(G, q, p, exp(SizedMatrix{2,2}(X))) end @@ -105,57 +109,66 @@ function exp!(G::GeneralLinear{2}, q, p, X) end function get_coordinates( - ::GeneralLinear{n,ℝ}, + ::GeneralLinear{<:Any,ℝ}, p, X, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}, -) where {n} +) return vec(X) end function get_coordinates!( - ::GeneralLinear{n,ℝ}, + ::GeneralLinear{<:Any,ℝ}, Xⁱ, p, X, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}, -) where {n} +) return copyto!(Xⁱ, X) end -get_embedding(::GeneralLinear{n,𝔽}) where {n,𝔽} = Euclidean(n, n; field=𝔽) +function get_embedding(::GeneralLinear{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return Euclidean(n, n; field=𝔽) +end +function get_embedding(M::GeneralLinear{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return Euclidean(n, n; field=𝔽, parameter=:field) +end function get_vector( - ::GeneralLinear{n,ℝ}, + M::GeneralLinear{<:Any,ℝ}, p, Xⁱ, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}, -) where {n} +) + n = get_parameter(M.size)[1] return reshape(Xⁱ, n, n) end function get_vector!( - ::GeneralLinear{n,ℝ}, + ::GeneralLinear{<:Any,ℝ}, X, p, Xⁱ, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}, -) where {n} +) return copyto!(X, Xⁱ) end -function exp_lie!(::GeneralLinear{1}, q, X) +function exp_lie!(::GeneralLinear{TypeParameter{Tuple{1}}}, q, X) q[1] = exp(X[1]) return q end -exp_lie!(::GeneralLinear{2}, q, X) = copyto!(q, exp(SizedMatrix{2,2}(X))) +function exp_lie!(::GeneralLinear{TypeParameter{Tuple{2}}}, q, X) + return copyto!(q, exp(SizedMatrix{2,2}(X))) +end inner(::GeneralLinear, p, X, Y) = dot(X, Y) inverse_translate_diff(::GeneralLinear, p, q, X, ::LeftForwardAction) = X inverse_translate_diff(::GeneralLinear, p, q, X, ::RightBackwardAction) = p * X / p -function inverse_translate_diff!(G::GeneralLinear, Y, p, q, X, conv::ActionDirection) +function inverse_translate_diff!(G::GeneralLinear, Y, p, q, X, conv::ActionDirectionAndSide) return copyto!(Y, inverse_translate_diff(G, p, q, X, conv)) end @@ -189,7 +202,8 @@ function log(M::GeneralLinear, p, q) return log!(M, X, p, q) end -function log!(G::GeneralLinear{n,𝔽}, X, p, q) where {n,𝔽} +function log!(G::GeneralLinear{<:Any,𝔽}, X, p, q) where {𝔽} + n = get_parameter(G.size)[1] pinvq = inverse_translate(G, p, q, LeftForwardAction()) 𝔽 === ℝ && det(pinvq) ≤ 0 && throw(OutOfInjectivityRadiusError()) if isnormal(pinvq; atol=sqrt(eps(real(eltype(pinvq))))) @@ -208,13 +222,13 @@ function log!(G::GeneralLinear{n,𝔽}, X, p, q) where {n,𝔽} translate_diff!(G, X, p, Identity(G), X, LeftForwardAction()) return X end -function log!(::GeneralLinear{1}, X, p, q) +function log!(::GeneralLinear{TypeParameter{Tuple{1}}}, X, p, q) p1 = p isa Identity ? p : p[1] X[1] = log(p1 \ q[1]) return X end -function _log_lie!(::GeneralLinear{1}, X, p) +function _log_lie!(::GeneralLinear{TypeParameter{Tuple{1}}}, X, p) X[1] = log(p[1]) return X end @@ -253,11 +267,17 @@ function Random.rand!(rng::AbstractRNG, G::GeneralLinear, pX; kwargs...) return pX end -Base.show(io::IO, ::GeneralLinear{n,𝔽}) where {n,𝔽} = print(io, "GeneralLinear($n, $𝔽)") +function Base.show(io::IO, ::GeneralLinear{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return print(io, "GeneralLinear($n, $𝔽)") +end +function Base.show(io::IO, M::GeneralLinear{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return print(io, "GeneralLinear($n, $𝔽; parameter=:field)") +end translate_diff(::GeneralLinear, p, q, X, ::LeftForwardAction) = X translate_diff(::GeneralLinear, p, q, X, ::RightBackwardAction) = p \ X * p -function translate_diff!(G::GeneralLinear, Y, p, q, X, conv::ActionDirection) +function translate_diff!(G::GeneralLinear, Y, p, q, X, conv::ActionDirectionAndSide) return copyto!(Y, translate_diff(G, p, q, X, conv)) end diff --git a/src/groups/general_unitary_groups.jl b/src/groups/general_unitary_groups.jl index 2ceba9969f..40c56a179d 100644 --- a/src/groups/general_unitary_groups.jl +++ b/src/groups/general_unitary_groups.jl @@ -1,11 +1,11 @@ @doc raw""" - GeneralUnitaryMultiplicationGroup{n,𝔽,M} = GroupManifold{𝔽,M,MultiplicationOperation} + GeneralUnitaryMultiplicationGroup{T,𝔽,M} = GroupManifold{𝔽,M,MultiplicationOperation} A generic type for Lie groups based on a unitary property and matrix multiplcation, see e.g. [`Orthogonal`](@ref), [`SpecialOrthogonal`](@ref), [`Unitary`](@ref), and [`SpecialUnitary`](@ref) """ -struct GeneralUnitaryMultiplicationGroup{n,𝔽,S} <: AbstractDecoratorManifold{𝔽} - manifold::GeneralUnitaryMatrices{n,𝔽,S} +struct GeneralUnitaryMultiplicationGroup{T,𝔽,S} <: AbstractDecoratorManifold{𝔽} + manifold::GeneralUnitaryMatrices{T,𝔽,S} end @inline function active_traits(f, ::GeneralUnitaryMultiplicationGroup, args...) @@ -45,8 +45,8 @@ end decorated_manifold(G::GeneralUnitaryMultiplicationGroup) = G.manifold @doc raw""" - exp_lie(G::Orthogonal{2}, X) - exp_lie(G::SpecialOrthogonal{2}, X) + exp_lie(G::Orthogonal{TypeParameter{Tuple{2}}}, X) + exp_lie(G::SpecialOrthogonal{TypeParameter{Tuple{2}}}, X) Compute the Lie group exponential map on the [`Orthogonal`](@ref)`(2)` or [`SpecialOrthogonal`](@ref)`(2)` group. Given ``X = \begin{pmatrix} 0 & -θ \\ θ & 0 \end{pmatrix}``, the group exponential is @@ -55,18 +55,18 @@ Given ``X = \begin{pmatrix} 0 & -θ \\ θ & 0 \end{pmatrix}``, the group exponen \exp_e \colon X ↦ \begin{pmatrix} \cos θ & -\sin θ \\ \sin θ & \cos θ \end{pmatrix}. ``` """ -exp_lie(::GeneralUnitaryMultiplicationGroup{2,ℝ}, X) +exp_lie(::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{2}},ℝ}, X) @doc raw""" - exp_lie(G::Orthogonal{4}, X) - exp_lie(G::SpecialOrthogonal{4}, X) + exp_lie(G::Orthogonal{TypeParameter{Tuple{4}}}, X) + exp_lie(G::SpecialOrthogonal{TypeParameter{Tuple{4}}}, X) Compute the group exponential map on the [`Orthogonal`](@ref)`(4)` or the [`SpecialOrthogonal`](@ref) group. The algorithm used is a more numerically stable form of those proposed in [GallierXu:2002](@cite), [AndricaRohan:2013](@cite). """ -exp_lie(::GeneralUnitaryMultiplicationGroup{4,ℝ}, X) +exp_lie(::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{4}},ℝ}, X) -function exp_lie!(::GeneralUnitaryMultiplicationGroup{2,ℝ}, q, X) +function exp_lie!(::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{2}},ℝ}, q, X) @assert size(X) == (2, 2) @inbounds θ = (X[2, 1] - X[1, 2]) / 2 sinθ, cosθ = sincos(θ) @@ -78,7 +78,7 @@ function exp_lie!(::GeneralUnitaryMultiplicationGroup{2,ℝ}, q, X) end return q end -function exp_lie!(::GeneralUnitaryMultiplicationGroup{3,ℝ}, q, X) +function exp_lie!(::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{3}},ℝ}, q, X) θ = norm(X) / sqrt(2) if θ ≈ 0 a = 1 - θ^2 / 6 @@ -92,7 +92,7 @@ function exp_lie!(::GeneralUnitaryMultiplicationGroup{3,ℝ}, q, X) mul!(q, X, X, b, true) return q end -function exp_lie!(::GeneralUnitaryMultiplicationGroup{4,ℝ}, q, X) +function exp_lie!(::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{4}},ℝ}, q, X) T = eltype(X) α, β = angles_4d_skew_sym_matrix(X) sinα, cosα = sincos(α) @@ -169,7 +169,7 @@ function inverse_translate_diff( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return translate_diff(G, inv(G, p), q, X, conv) end @@ -179,7 +179,7 @@ function inverse_translate_diff!( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return copyto!(Y, inverse_translate_diff(G, p, q, X, conv)) end @@ -198,23 +198,23 @@ function log!( end function log_lie!( - G::GeneralUnitaryMultiplicationGroup{n,ℝ}, + G::GeneralUnitaryMultiplicationGroup{<:Any,ℝ}, X::AbstractMatrix, q::AbstractMatrix, -) where {n} +) log_safe!(X, q) return project!(G, X, Identity(G), X) end function log_lie!( - ::GeneralUnitaryMultiplicationGroup{n,ℝ}, + ::GeneralUnitaryMultiplicationGroup{<:Any,ℝ}, X, ::Identity{MultiplicationOperation}, -) where {n} +) fill!(X, 0) return X end function log_lie!( - G::GeneralUnitaryMultiplicationGroup{2,ℝ}, + G::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{2}},ℝ}, X::AbstractMatrix, q::AbstractMatrix, ) @@ -223,7 +223,7 @@ function log_lie!( return get_vector!(G, X, Identity(G), θ, DefaultOrthogonalBasis()) end function log_lie!( - G::GeneralUnitaryMultiplicationGroup{3,ℝ}, + G::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{3}},ℝ}, X::AbstractMatrix, q::AbstractMatrix, ) @@ -240,7 +240,7 @@ function log_lie!( return project!(G, X, e, X) end function log_lie!( - G::GeneralUnitaryMultiplicationGroup{4,ℝ}, + G::GeneralUnitaryMultiplicationGroup{TypeParameter{Tuple{4}},ℝ}, X::AbstractMatrix, q::AbstractMatrix, ) diff --git a/src/groups/group.jl b/src/groups/group.jl index b91587236b..b9c6608b20 100644 --- a/src/groups/group.jl +++ b/src/groups/group.jl @@ -1,7 +1,7 @@ @doc raw""" AbstractGroupOperation -Abstract type for smooth binary operations $∘$ on elements of a Lie group $\mathcal{G}$: +Abstract type for smooth binary operations ``∘`` on elements of a Lie group ``\mathcal{G}``: ```math ∘ : \mathcal{G} × \mathcal{G} → \mathcal{G} ``` @@ -13,7 +13,8 @@ number system `𝔽` or in general, by defining for an operation `Op` the follow _compose!(::AbstractDecoratorManifold, x, p, q) Note that a manifold is connected with an operation by wrapping it with a decorator, -[`AbstractDecoratorManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/decorator.html#ManifoldsBase.AbstractDecoratorManifold) using the [`IsGroupManifold`](@ref) to specify the operation. +[`AbstractDecoratorManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/decorator.html#ManifoldsBase.AbstractDecoratorManifold) +using the [`IsGroupManifold`](@ref) to specify the operation. For a concrete case the concrete wrapper [`GroupManifold`](@ref) can be used. """ abstract type AbstractGroupOperation end @@ -30,7 +31,7 @@ see [`GroupManifold`](@ref). # Constructor - IsGroupManifold(op) + IsGroupManifold(op::AbstractGroupOperation) """ struct IsGroupManifold{O<:AbstractGroupOperation} <: AbstractTrait op::O @@ -46,27 +47,30 @@ abstract type AbstractInvarianceTrait <: AbstractTrait end """ HasLeftInvariantMetric <: AbstractInvarianceTrait -Specify that a certain the metric of a [`GroupManifold`](@ref) is a left-invariant metric +Specify that the default metric functions for the left-invariant metric on a [`GroupManifold`](@ref) +are to be used. """ struct HasLeftInvariantMetric <: AbstractInvarianceTrait end -direction(::HasLeftInvariantMetric) = LeftForwardAction() -direction(::Type{HasLeftInvariantMetric}) = LeftForwardAction() +direction_and_side(::HasLeftInvariantMetric) = LeftForwardAction() +direction_and_side(::Type{HasLeftInvariantMetric}) = LeftForwardAction() """ HasRightInvariantMetric <: AbstractInvarianceTrait -Specify that a certain the metric of a [`GroupManifold`](@ref) is a right-invariant metric +Specify that the default metric functions for the right-invariant metric on a [`GroupManifold`](@ref) +are to be used. """ struct HasRightInvariantMetric <: AbstractInvarianceTrait end -direction(::HasRightInvariantMetric) = RightBackwardAction() -direction(::Type{HasRightInvariantMetric}) = RightBackwardAction() +direction_and_side(::HasRightInvariantMetric) = RightBackwardAction() +direction_and_side(::Type{HasRightInvariantMetric}) = RightBackwardAction() """ HasBiinvariantMetric <: AbstractInvarianceTrait -Specify that a certain the metric of a [`GroupManifold`](@ref) is a bi-invariant metric +Specify that the default metric functions for the bi-invariant metric on a [`GroupManifold`](@ref) +are to be used. """ struct HasBiinvariantMetric <: AbstractInvarianceTrait end function parent_trait(::HasBiinvariantMetric) @@ -75,10 +79,10 @@ end """ is_group_manifold(G::GroupManifold) - is_group_manifoldd(G::AbstractManifold, o::AbstractGroupOperation) + is_group_manifold(G::AbstractManifold, o::AbstractGroupOperation) -returns whether an [`AbstractDecoratorManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/decorator.html#ManifoldsBase.AbstractDecoratorManifold) is a group manifold with -[`AbstractGroupOperation`](@ref) `o`. +returns whether an [`AbstractDecoratorManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/decorator.html#ManifoldsBase.AbstractDecoratorManifold) +is a group manifold with [`AbstractGroupOperation`](@ref) `o`. For a [`GroupManifold`](@ref) `G` this checks whether the right operations is stored within `G`. """ is_group_manifold(::AbstractManifold, ::AbstractGroupOperation) = false @@ -107,112 +111,86 @@ base_group(M::AbstractDecoratorManifold) = M """ ActionDirection -Direction of action on a manifold, either [`LeftForwardAction`](@ref), -[`LeftBackwardAction`](@ref), [`RightForwardAction`](@ref) or [`RightBackwardAction`](@ref). +Direction of action on a manifold, either [`LeftAction`](@ref) or [`RightAction`](@ref). """ abstract type ActionDirection end @doc raw""" - LeftForwardAction() + LeftAction() -Left action of a group on a manifold. For an action ``α: G × X → X`` it is characterized by +Left action of a group on a manifold. For a forward action ``α: G × X → X`` it is characterized by ```math α(g, α(h, x)) = α(gh, x) ``` for all ``g, h ∈ G`` and ``x ∈ X``. """ -struct LeftForwardAction <: ActionDirection end +struct LeftAction <: ActionDirection end -@doc raw""" - LeftBackwardAction() - -Left action of a group on a manifold. For an action ``α: X × G → X`` it is characterized by -```math -α(α(x, h), g) = α(x, gh) -``` -for all ``g, h ∈ G`` and ``x ∈ X``. - -Note that a left action may still act from the right side in an expression. """ -struct LeftBackwardAction <: ActionDirection end - -const LeftAction = LeftForwardAction + RightAction() -""" - RightForwardAction() - -Right action of a group on a manifold. For an action ``α: G × X → X`` it is characterized by +Right action of a group on a manifold. For a forward action ``α: G × X → X`` it is characterized by ```math α(g, α(h, x)) = α(hg, x) ``` for all ``g, h ∈ G`` and ``x ∈ X``. -Note that a right action may still act from the left side in an expression. +Note that a right action may act from either left or right side in an expression. """ -struct RightForwardAction <: ActionDirection end +struct RightAction <: ActionDirection end """ - RightBackwardAction() - -Right action of a group on a manifold. For an action ``α: X × G → X`` it is characterized by -```math -α(α(x, h), g) = α(x, hg) -``` -for all ``g, h ∈ G`` and ``x ∈ X``. + GroupActionSide -Note that a right action may still act from the left side in an expression. +Side of action on a manifold, either [`LeftSide`](@ref) or [`RightSide`](@ref). """ -struct RightBackwardAction <: ActionDirection end - -const RightAction = RightBackwardAction - -abstract type AbstractDirectionSwitchType end +abstract type GroupActionSide end """ - struct LeftRightSwitch <: AbstractDirectionSwitchType end + LeftSide() -Switch between left and right action, maintaining forward/backward direction. +An action of a group on a manifold that acts from the left side, i.e. ``α: G × X → X``. """ -struct LeftRightSwitch <: AbstractDirectionSwitchType end +struct LeftSide <: GroupActionSide end """ - struct ForwardBackwardSwitch <: AbstractDirectionSwitchType end + RightSide() -Switch between forward and backward action, maintaining left/right direction. +An action of a group on a manifold that acts from the right side, i.e. ``α: X × G → X``. """ -struct ForwardBackwardSwitch <: AbstractDirectionSwitchType end +struct RightSide <: GroupActionSide end """ - struct LeftRightSwitch <: AbstractDirectionSwitchType end + switch_direction(::ActionDirection) -Simultaneously switch left/right and forward/backward directions. +Returns type of action between left and right. +This function does not affect side of action, see [`switch_side`](@ref). """ -struct SimultaneousSwitch <: AbstractDirectionSwitchType end +switch_direction(::ActionDirection) +switch_direction(::LeftAction) = RightAction() +switch_direction(::RightAction) = LeftAction() """ - switch_direction(::ActionDirection, type::AbstractDirectionSwitchType = SimultaneousSwitch()) + switch_side(::GroupActionSide) -Returns type of action between left and right, forward or backward, or both at the same type, -depending on `type`, which is either of `LeftRightSwitch`, `ForwardBackwardSwitch` or -`SimultaneousSwitch`. +Returns side of action between left and right. +This function does not affect the action being left or right, see [`switch_direction`](@ref). """ -switch_direction(::ActionDirection, type::AbstractDirectionSwitchType) -switch_direction(AD::ActionDirection) = switch_direction(AD, SimultaneousSwitch()) +switch_side(::GroupActionSide) +switch_side(::LeftSide) = RightSide() +switch_side(::RightSide) = LeftSide() -switch_direction(::LeftForwardAction, ::LeftRightSwitch) = RightForwardAction() -switch_direction(::LeftBackwardAction, ::LeftRightSwitch) = RightBackwardAction() -switch_direction(::RightForwardAction, ::LeftRightSwitch) = LeftForwardAction() -switch_direction(::RightBackwardAction, ::LeftRightSwitch) = LeftBackwardAction() +const ActionDirectionAndSide = Tuple{ActionDirection,GroupActionSide} -switch_direction(::LeftForwardAction, ::ForwardBackwardSwitch) = LeftBackwardAction() -switch_direction(::LeftBackwardAction, ::ForwardBackwardSwitch) = LeftForwardAction() -switch_direction(::RightForwardAction, ::ForwardBackwardSwitch) = RightBackwardAction() -switch_direction(::RightBackwardAction, ::ForwardBackwardSwitch) = RightForwardAction() +const LeftForwardAction = Tuple{LeftAction,LeftSide} +const LeftBackwardAction = Tuple{LeftAction,RightSide} +const RightForwardAction = Tuple{RightAction,LeftSide} +const RightBackwardAction = Tuple{RightAction,RightSide} -switch_direction(::LeftForwardAction, ::SimultaneousSwitch) = RightBackwardAction() -switch_direction(::LeftBackwardAction, ::SimultaneousSwitch) = RightForwardAction() -switch_direction(::RightForwardAction, ::SimultaneousSwitch) = LeftBackwardAction() -switch_direction(::RightBackwardAction, ::SimultaneousSwitch) = LeftForwardAction() +LeftForwardAction() = (LeftAction(), LeftSide()) +LeftBackwardAction() = (LeftAction(), RightSide()) +RightForwardAction() = (RightAction(), LeftSide()) +RightBackwardAction() = (RightAction(), RightSide()) @doc raw""" Identity{O<:AbstractGroupOperation} @@ -249,7 +227,7 @@ Identity(::Type{O}) where {O<:AbstractGroupOperation} = Identity{O}() number_eltype(::Identity) = Bool @doc raw""" - identity_element(G) + identity_element(G::AbstractDecoratorManifold) Return a point representation of the [`Identity`](@ref) on the [`IsGroupManifold`](@ref) `G`. By default this representation is the default array or number representation. @@ -407,12 +385,17 @@ end function is_point( ::TraitList{<:IsGroupManifold}, G::AbstractDecoratorManifold, - e::Identity, - te::Bool=false; + e::Identity; + error::Symbol=:none, kwargs..., ) ie = is_identity(G, e; kwargs...) - (te && !ie) && throw(DomainError(e, "The provided identity is not a point on $G.")) + if !ie + s = "The provided identity is not a point on $G." + (error === :error) && throw(DomainError(e, s)) + (error === :info) && @info s + (error === :warn) && @warn s + end return ie end @@ -421,16 +404,24 @@ function is_vector( G::AbstractDecoratorManifold, e::Identity, X, - te::Bool=false, - cbp=true; + cbp::Bool=true; + error::Symbol=:none, kwargs..., ) if cbp - # pass te down so this throws an error if te=true - # if !te and is_point was false -> return false, otherwise continue - (!te && !is_point(G, e, te; kwargs...)) && return false + # pass te down so this throws an error if error=:error + # if error is not `:error` and is_point was false -> return false, otherwise continue + (!is_point(G, e; error=error, kwargs...)) && return false end - return is_vector(next_trait(t), G, identity_element(G), X, te, false; kwargs...) + return is_vector( + next_trait(t), + G, + identity_element(G), + X, + false; + error=error, + kwargs..., + ) end @doc raw""" @@ -446,7 +437,7 @@ The formula reads ````math \operatorname{Ad}_p(X) = dΨ_p(e)[X] ```` -where $e$ is the identity element of `G`. +where ``e`` is the identity element of `G`. Note that the adjoint representation of a Lie group isn't generally faithful. Notably the adjoint representation of SO(2) is trivial. @@ -498,9 +489,9 @@ end @doc raw""" inv(G::AbstractDecoratorManifold, p) -Inverse $p^{-1} ∈ \mathcal{G}$ of an element $p ∈ \mathcal{G}$, such that -$p \circ p^{-1} = p^{-1} \circ p = e ∈ \mathcal{G}$, where $e$ is the [`Identity`](@ref) -element of $\mathcal{G}$. +Inverse ``p^{-1} ∈ \mathcal{G}`` of an element ``p ∈ \mathcal{G}``, such that +``p \circ p^{-1} = p^{-1} \circ p = e ∈ \mathcal{G}``, where ``e`` is the [`Identity`](@ref) +element of ``\mathcal{G}``. """ inv(::AbstractDecoratorManifold, ::Any...) @trait_function Base.inv(G::AbstractDecoratorManifold, p) @@ -643,7 +634,7 @@ Base.transpose(e::Identity) = e @doc raw""" hat(M::AbstractDecoratorManifold{𝔽,O}, ::Identity{O}, Xⁱ) where {𝔽,O<:AbstractGroupOperation} -Given a basis $e_i$ on the tangent space at a the [`Identity`](@ref) and tangent +Given a basis ``e_i`` on the tangent space at a the [`Identity`](@ref) and tangent component vector ``X^i``, compute the equivalent vector representation ``X=X^i e_i**, where Einstein summation notation is used: @@ -685,8 +676,8 @@ end @doc raw""" vee(M::AbstractManifold, p, X) -Given a basis $e_i$ on the tangent space at a point `p` and tangent -vector `X`, compute the vector components $X^i$, such that $X = X^i e_i$, where +Given a basis ``e_i`` on the tangent space at a point `p` and tangent +vector `X`, compute the vector components ``X^i``, such that ``X = X^i e_i``, where Einstein summation notation is used: ````math @@ -742,11 +733,11 @@ _action_order(BG::AbstractDecoratorManifold, p, q, ::RightForwardAction) = (inv( _action_order(BG::AbstractDecoratorManifold, p, q, ::RightBackwardAction) = (q, p) @doc raw""" - translate(G::AbstractDecoratorManifold, p, q, conv::ActionDirection=LeftForwardAction()]) + translate(G::AbstractDecoratorManifold, p, q, conv::ActionDirectionAndSide=LeftForwardAction()]) -Translate group element $q$ by $p$ with the translation $τ_p$ with the specified -`conv`ention, either left forward ($L_p$), left backward ($R'_p$), right backward ($R_p$) -or right forward ($L'_p$), defined as +Translate group element ``q`` by ``p`` with the translation ``τ_p`` with the specified +`conv`ention, either left forward (``L_p``), left backward (``R'_p``), right backward (``R_p``) +or right forward (``L'_p``), defined as ```math \begin{aligned} L_p &: q ↦ p \circ q\\ @@ -761,14 +752,14 @@ translate(::AbstractDecoratorManifold, ::Any...) G::AbstractDecoratorManifold, p, q, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function translate( ::TraitList{<:IsGroupManifold}, G::AbstractDecoratorManifold, p, q, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) BG = base_group(G) return compose(BG, _action_order(BG, p, q, conv)...) @@ -779,7 +770,7 @@ end X, p, q, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function translate!( ::TraitList{<:IsGroupManifold}, @@ -787,37 +778,39 @@ function translate!( X, p, q, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) BG = base_group(G) return compose!(BG, X, _action_order(BG, p, q, conv)...) end @doc raw""" - inverse_translate(G::AbstractDecoratorManifold, p, q, conv::ActionDirection=LeftForwardAction()) + inverse_translate(G::AbstractDecoratorManifold, p, q, conv::ActionDirectionAndSide=LeftForwardAction()) -Inverse translate group element $q$ by $p$ with the inverse translation $τ_p^{-1}$ with the -specified `conv`ention, either left ($L_p^{-1}$) or right ($R_p^{-1}$), defined as +Inverse translate group element ``q`` by ``p`` with the translation ``τ_p^{-1}`` +with the specified `conv`ention, either left forward (``L_p^{-1}``), left backward +(``R'_p^{-1}``), right backward (``R_p^{-1}``) or right forward (``L'_p^{-1}``), defined as ```math \begin{aligned} L_p^{-1} &: q ↦ p^{-1} \circ q\\ -R_p^{-1} &: q ↦ q \circ p^{-1}. +L'_p^{-1} &: q ↦ p \circ q\\ +R_p^{-1} &: q ↦ q \circ p^{-1}\\ +R'_p^{-1} &: q ↦ q \circ p. \end{aligned} -``` """ inverse_translate(::AbstractDecoratorManifold, ::Any...) @trait_function inverse_translate( G::AbstractDecoratorManifold, p, q, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function inverse_translate( ::TraitList{<:IsGroupManifold}, G::AbstractDecoratorManifold, p, q, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) BG = base_group(G) return translate(BG, inv(BG, p), q, conv) @@ -828,7 +821,7 @@ end X, p, q, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function inverse_translate!( ::TraitList{<:IsGroupManifold}, @@ -836,17 +829,17 @@ function inverse_translate!( X, p, q, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) BG = base_group(G) return translate!(BG, X, inv(BG, p), q, conv) end @doc raw""" - translate_diff(G::AbstractDecoratorManifold, p, q, X, conv::ActionDirection=LeftForwardAction()) + translate_diff(G::AbstractDecoratorManifold, p, q, X, conv::ActionDirectionAndSide=LeftForwardAction()) -For group elements $p, q ∈ \mathcal{G}$ and tangent vector $X ∈ T_q \mathcal{G}$, compute -the action of the differential of the translation $τ_p$ by $p$ on $X$, with the specified +For group elements ``p, q ∈ \mathcal{G}`` and tangent vector ``X ∈ T_q \mathcal{G}``, compute +the action of the differential of the translation ``τ_p`` by ``p`` on ``X``, with the specified left or right `conv`ention. The differential transports vectors: ```math (\mathrm{d}τ_p)_q : T_q \mathcal{G} → T_{τ_p q} \mathcal{G}\\ @@ -858,7 +851,7 @@ translate_diff(::AbstractDecoratorManifold, ::Any...) p, q, X, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function translate_diff( ::TraitList{<:IsGroupManifold}, @@ -866,7 +859,7 @@ function translate_diff( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) Y = allocate_result(G, translate_diff, X, p, q) BG = base_group(G) @@ -879,14 +872,14 @@ end p, q, X, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) @doc raw""" - inverse_translate_diff(G::AbstractDecoratorManifold, p, q, X, conv::ActionDirection=LeftForwardAction()) + inverse_translate_diff(G::AbstractDecoratorManifold, p, q, X, conv::ActionDirectionAndSide=LeftForwardAction()) -For group elements $p, q ∈ \mathcal{G}$ and tangent vector $X ∈ T_q \mathcal{G}$, compute -the action on $X$ of the differential of the inverse translation $τ_p$ by $p$, with the +For group elements ``p, q ∈ \mathcal{G}`` and tangent vector ``X ∈ T_q \mathcal{G}``, compute +the action on ``X`` of the differential of the inverse translation ``τ_p`` by ``p``, with the specified left or right `conv`ention. The differential transports vectors: ```math (\mathrm{d}τ_p^{-1})_q : T_q \mathcal{G} → T_{τ_p^{-1} q} \mathcal{G}\\ @@ -898,7 +891,7 @@ inverse_translate_diff(::AbstractDecoratorManifold, ::Any...) p, q, X, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function inverse_translate_diff( ::TraitList{<:IsGroupManifold}, @@ -906,7 +899,7 @@ function inverse_translate_diff( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) BG = base_group(G) return translate_diff(BG, inv(BG, p), q, X, conv) @@ -918,7 +911,7 @@ end p, q, X, - conv::ActionDirection=LeftForwardAction(), + conv::ActionDirectionAndSide=LeftForwardAction(), ) function inverse_translate_diff!( ::TraitList{<:IsGroupManifold}, @@ -927,7 +920,7 @@ function inverse_translate_diff!( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) BG = base_group(G) return translate_diff!(BG, Y, inv(BG, p), q, X, conv) @@ -940,13 +933,13 @@ end Compute the group exponential of the Lie algebra element `X`. It is equivalent to the exponential map defined by the [`CartanSchoutenMinus`](@ref) connection. -Given an element $X ∈ 𝔤 = T_e \mathcal{G}$, where $e$ is the [`Identity`](@ref) element of -the group $\mathcal{G}$, and $𝔤$ is its Lie algebra, the group exponential is the map +Given an element ``X ∈ 𝔤 = T_e \mathcal{G}``, where ``e`` is the [`Identity`](@ref) element of +the group ``\mathcal{G}``, and ``𝔤`` is its Lie algebra, the group exponential is the map ````math \exp : 𝔤 → \mathcal{G}, ```` -such that for $t,s ∈ ℝ$, $γ(t) = \exp (t X)$ defines a one-parameter subgroup with the +such that for ``t,s ∈ ℝ``, ``γ(t) = \exp (t X)`` defines a one-parameter subgroup with the following properties. Note that one-parameter subgroups are commutative (see [Suhubi:2013](@cite), section 3.5), even if the Lie group itself is not commutative. @@ -990,9 +983,9 @@ end Compute the Lie group logarithm of the Lie group element `q`. It is equivalent to the logarithmic map defined by the [`CartanSchoutenMinus`](@ref) connection. -Given an element $q ∈ \mathcal{G}$, compute the right inverse of the group exponential map -[`exp_lie`](@ref), that is, the element $\log q = X ∈ 𝔤 = T_e \mathcal{G}$, such that -$q = \exp X$ +Given an element ``q ∈ \mathcal{G}``, compute the right inverse of the group exponential map +[`exp_lie`](@ref), that is, the element ``\log q = X ∈ 𝔤 = T_e \mathcal{G}``, such that +``q = \exp X`` !!! note In general, the group logarithm map is distinct from the Riemannian logarithm map @@ -1004,8 +997,8 @@ $q = \exp X$ \log q = \operatorname{Log} q = \sum_{n=1}^∞ \frac{(-1)^{n+1}}{n} (q - e)^n, ```` -where $e$ here is the [`Identity`](@ref) element, that is, $1$ for numeric $q$ or the -identity matrix $I_m$ for matrix $q ∈ ℝ^{m × m}$. +where ``e`` here is the [`Identity`](@ref) element, that is, ``1`` for numeric ``q`` or the +identity matrix ``I_m`` for matrix ``q ∈ ℝ^{m × m}``. Since this function also depends on the group operation, make sure to implement either @@ -1048,7 +1041,7 @@ function log_lie!( end """ - GroupExponentialRetraction{D<:ActionDirection} <: AbstractRetractionMethod + GroupExponentialRetraction{D<:ActionDirectionAndSide} <: AbstractRetractionMethod Retraction using the group exponential [`exp_lie`](@ref) "translated" to any point on the manifold. @@ -1058,16 +1051,16 @@ For more details, see # Constructor - GroupExponentialRetraction(conv::ActionDirection = LeftForwardAction()) + GroupExponentialRetraction(conv::ActionDirectionAndSide = LeftAction()) """ -struct GroupExponentialRetraction{D<:ActionDirection} <: AbstractRetractionMethod end +struct GroupExponentialRetraction{D<:ActionDirectionAndSide} <: AbstractRetractionMethod end -function GroupExponentialRetraction(conv::ActionDirection=LeftForwardAction()) +function GroupExponentialRetraction(conv::ActionDirectionAndSide=LeftForwardAction()) return GroupExponentialRetraction{typeof(conv)}() end """ - GroupLogarithmicInverseRetraction{D<:ActionDirection} <: AbstractInverseRetractionMethod + GroupLogarithmicInverseRetraction{D<:ActionDirectionAndSide} <: AbstractInverseRetractionMethod Retraction using the group logarithm [`log_lie`](@ref) "translated" to any point on the manifold. @@ -1077,37 +1070,37 @@ For more details, see # Constructor - GroupLogarithmicInverseRetraction(conv::ActionDirection = LeftForwardAction()) + GroupLogarithmicInverseRetraction(conv::ActionDirectionAndSide = LeftForwardAction()) """ -struct GroupLogarithmicInverseRetraction{D<:ActionDirection} <: +struct GroupLogarithmicInverseRetraction{D<:ActionDirectionAndSide} <: AbstractInverseRetractionMethod end -function GroupLogarithmicInverseRetraction(conv::ActionDirection=LeftForwardAction()) +function GroupLogarithmicInverseRetraction(conv::ActionDirectionAndSide=LeftForwardAction()) return GroupLogarithmicInverseRetraction{typeof(conv)}() end -direction(::GroupExponentialRetraction{D}) where {D} = D() -direction(::GroupLogarithmicInverseRetraction{D}) where {D} = D() +direction_and_side(::GroupExponentialRetraction{D}) where {D} = D() +direction_and_side(::GroupLogarithmicInverseRetraction{D}) where {D} = D() @doc raw""" retract( G::AbstractDecoratorManifold, p, X, - method::GroupExponentialRetraction{<:ActionDirection}, + method::GroupExponentialRetraction, ) Compute the retraction using the group exponential [`exp_lie`](@ref) "translated" to any point on the manifold. -With a group translation ([`translate`](@ref)) $τ_p$ in a specified direction, the +With a group translation ([`translate`](@ref)) ``τ_p`` in a specified direction, the retraction is ````math \operatorname{retr}_p = τ_p \circ \exp \circ (\mathrm{d}τ_p^{-1})_p, ```` -where $\exp$ is the group exponential ([`exp_lie`](@ref)), and $(\mathrm{d}τ_p^{-1})_p$ is -the action of the differential of inverse translation $τ_p^{-1}$ evaluated at $p$ (see +where ``\exp`` is the group exponential ([`exp_lie`](@ref)), and ``(\mathrm{d}τ_p^{-1})_p`` is +the action of the differential of inverse translation ``τ_p^{-1}`` evaluated at ``p`` (see [`inverse_translate_diff`](@ref)). """ function retract( @@ -1117,7 +1110,7 @@ function retract( X, method::GroupExponentialRetraction, ) - conv = direction(method) + conv = direction_and_side(method) Xₑ = inverse_translate_diff(G, p, p, X, conv) pinvq = exp_lie(G, Xₑ) q = translate(G, p, pinvq, conv) @@ -1142,7 +1135,7 @@ function retract!( X, method::GroupExponentialRetraction, ) - conv = direction(method) + conv = direction_and_side(method) Xₑ = inverse_translate_diff(G, p, p, X, conv) pinvq = exp_lie(G, Xₑ) return translate!(G, q, p, pinvq, conv) @@ -1164,20 +1157,20 @@ end G::AbstractDecoratorManifold, p, X, - method::GroupLogarithmicInverseRetraction{<:ActionDirection}, + method::GroupLogarithmicInverseRetraction, ) Compute the inverse retraction using the group logarithm [`log_lie`](@ref) "translated" to any point on the manifold. -With a group translation ([`translate`](@ref)) $τ_p$ in a specified direction, the +With a group translation ([`translate`](@ref)) ``τ_p`` in a specified direction, the retraction is ````math \operatorname{retr}_p^{-1} = (\mathrm{d}τ_p)_e \circ \log \circ τ_p^{-1}, ```` -where $\log$ is the group logarithm ([`log_lie`](@ref)), and $(\mathrm{d}τ_p)_e$ is the -action of the differential of translation $τ_p$ evaluated at the identity element $e$ +where ``\log`` is the group logarithm ([`log_lie`](@ref)), and ``(\mathrm{d}τ_p)_e`` is the +action of the differential of translation ``τ_p`` evaluated at the identity element ``e`` (see [`translate_diff`](@ref)). """ function inverse_retract( @@ -1187,7 +1180,7 @@ function inverse_retract( q, method::GroupLogarithmicInverseRetraction, ) - conv = direction(method) + conv = direction_and_side(method) pinvq = inverse_translate(G, p, q, conv) Xₑ = log_lie(G, pinvq) return translate_diff(G, p, Identity(G), Xₑ, conv) @@ -1201,7 +1194,7 @@ function inverse_retract!( q, method::GroupLogarithmicInverseRetraction, ) - conv = direction(method) + conv = direction_and_side(method) pinvq = inverse_translate(G, p, q, conv) Xₑ = log_lie(G, pinvq) return translate_diff!(G, X, p, Identity(G), Xₑ, conv) diff --git a/src/groups/group_action.jl b/src/groups/group_action.jl index 6060dae02d..782ce45ecb 100644 --- a/src/groups/group_action.jl +++ b/src/groups/group_action.jl @@ -1,25 +1,24 @@ """ - AbstractGroupAction + AbstractGroupAction{AD<:ActionDirection} -An abstract group action on a manifold. +An abstract group action on a manifold. [`ActionDirection`](@ref)`AD` indicates whether it +is a left or right action. """ abstract type AbstractGroupAction{AD<:ActionDirection} end """ base_group(A::AbstractGroupAction) -The group that acts in action `A`. +The group that acts in [`AbstractGroupAction`](@ref) `A`. """ -base_group(A::AbstractGroupAction) = error("base_group not implemented for $(typeof(A)).") +base_group(A::AbstractGroupAction) """ group_manifold(A::AbstractGroupAction) The manifold the action `A` acts upon. """ -function group_manifold(A::AbstractGroupAction) - return error("group_manifold not implemented for $(typeof(A)).") -end +group_manifold(A::AbstractGroupAction) function allocate_result(A::AbstractGroupAction, f, p...) return allocate_result(group_manifold(A), f, p...) @@ -28,7 +27,7 @@ end """ direction(::AbstractGroupAction{AD}) -> AD -Get the direction of the action +Get the direction of the action: either [`LeftAction`](@ref) or [`RightAction`](@ref). """ direction(::AbstractGroupAction{AD}) where {AD} = AD() @@ -46,7 +45,7 @@ adjoint_apply_diff_group(A::AbstractGroupAction, a, X, p) @doc raw""" apply(A::AbstractGroupAction, a, p) -Apply action `a` to the point `p` using map $τ_a$, specified by `A`. +Apply action `a` to the point `p` using map ``τ_a``, specified by `A`. Unless otherwise specified, the right action is defined in terms of the left action: ````math @@ -65,14 +64,10 @@ end Apply action `a` to the point `p` with the rule specified by `A`. The result is saved in `q`. """ -function apply!(A::AbstractGroupAction{LeftForwardAction}, q, a, p) - return error( - "apply! not implemented for action $(typeof(A)) and points $(typeof(q)), $(typeof(p)) and $(typeof(a)).", - ) -end -function apply!(A::AbstractGroupAction{RightForwardAction}, q, a, p) +apply!(A::AbstractGroupAction, q, a, p) +function apply!(A::AbstractGroupAction{RightAction}, q, a, p) ainv = inv(base_group(A), a) - apply!(switch_direction(A, LeftRightSwitch()), q, ainv, p) + apply!(switch_direction(A), q, ainv, p) return q end @@ -102,26 +97,16 @@ end @doc raw""" apply_diff(A::AbstractGroupAction, a, p, X) -For point $p ∈ \mathcal M$ and tangent vector $X ∈ T_p \mathcal M$, compute the action -on $X$ of the differential of the action of $a ∈ \mathcal{G}$, specified by rule `A`. -Written as $(\mathrm{d}τ_a)_p$, with the specified left or right convention, the +For point ``p ∈ \mathcal M`` and tangent vector ``X ∈ T_p \mathcal M``, compute the action +on ``X`` of the differential of the action of ``a ∈ \mathcal{G}``, specified by rule `A`. +Written as ``(\mathrm{d}τ_a)_p``, with the specified left or right convention, the differential transports vectors ````math (\mathrm{d}τ_a)_p : T_p \mathcal M → T_{τ_a p} \mathcal M ```` """ -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))", - ) -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))", - ) -end +apply_diff(A::AbstractGroupAction, a, p, X) @doc raw""" apply_diff_group(A::AbstractGroupAction, a, X, p) @@ -149,10 +134,10 @@ apply_diff_group(A::AbstractGroupAction, a, X, p) @doc raw""" inverse_apply_diff(A::AbstractGroupAction, a, p, X) -For group point $p ∈ \mathcal M$ and tangent vector $X ∈ T_p \mathcal M$, compute the action -on $X$ of the differential of the inverse action of $a ∈ \mathcal{G}$, specified by rule -`A`. Written as $(\mathrm{d}τ_a^{-1})_p$, with the specified left or right convention, -the differential transports vectors +For group point ``p ∈ \mathcal M`` and tangent vector ``X ∈ T_p \mathcal M``, compute the +action on ``X`` of the differential of the inverse action of ``a ∈ \mathcal{G}``, specified +by rule `A`. Written as ``(\mathrm{d}τ_a^{-1})_p``, with the specified left or right +convention, the differential transports vectors. ````math (\mathrm{d}τ_a^{-1})_p : T_p \mathcal M → T_{τ_a^{-1} p} \mathcal M @@ -166,53 +151,31 @@ function inverse_apply_diff!(A::AbstractGroupAction, Y, a, p, X) return apply_diff!(A, Y, inv(base_group(A), a), p, X) end -function compose( - A::AbstractGroupAction{<:Union{LeftForwardAction,LeftBackwardAction}}, - a, - b, -) +function compose(A::AbstractGroupAction{LeftAction}, a, b) return compose(base_group(A), a, b) end -function compose( - A::AbstractGroupAction{<:Union{RightForwardAction,RightBackwardAction}}, - a, - b, -) +function compose(A::AbstractGroupAction{RightAction}, a, b) return compose(base_group(A), b, a) end -function compose!( - A::AbstractGroupAction{<:Union{LeftForwardAction,LeftBackwardAction}}, - q, - a, - b, -) +function compose!(A::AbstractGroupAction{LeftAction}, q, a, b) return compose!(base_group(A), q, a, b) end -function compose!( - A::AbstractGroupAction{<:Union{RightForwardAction,RightBackwardAction}}, - q, - a, - b, -) +function compose!(A::AbstractGroupAction{RightAction}, q, a, b) return compose!(base_group(A), q, b, a) end @doc raw""" optimal_alignment(A::AbstractGroupAction, p, q) -Calculate an action element $a$ of action `A` that acts upon `p` to produce +Calculate an action element ``a`` of action `A` that acts upon `p` to produce the element closest to `q` in the metric of the G-manifold: ```math \arg\min_{a ∈ \mathcal{G}} d_{\mathcal M}(τ_a p, q) ``` -where $\mathcal{G}$ is the group that acts on the G-manifold $\mathcal M$. +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)).", - ) -end +optimal_alignment(A::AbstractGroupAction, p, q) """ optimal_alignment!(A::AbstractGroupAction, x, p, q) @@ -233,11 +196,11 @@ end mean_method::AbstractEstimationMethod = GradientDescentEstimation(), ) -Calculate an action element $a$ of action `A` that is the mean element of the orbit of `p` +Calculate an action element ``a`` of action `A` that is the mean element of the orbit of `p` with respect to given set of points `pts`. The [`mean`](@ref) is calculated using the method `mean_method`. -The orbit of $p$ with respect to the action of a group $\mathcal{G}$ is the set +The orbit of ``p`` with respect to the action of a group ``\mathcal{G}`` is the set ````math O = \{ τ_a p : a ∈ \mathcal{G} \}. ```` diff --git a/src/groups/group_operation_action.jl b/src/groups/group_operation_action.jl index 3963d5135b..34e6de888c 100644 --- a/src/groups/group_operation_action.jl +++ b/src/groups/group_operation_action.jl @@ -1,21 +1,54 @@ @doc raw""" - GroupOperationAction(group::AbstractDecoratorManifold, AD::ActionDirection = LeftForwardAction()) + GroupOperationAction{AD<:ActionDirection,AS<:GroupActionSide,G<:AbstractDecoratorManifold} <: AbstractGroupAction{AD} -Action of a group upon itself via left or right translation. +Action of a group upon itself via left or right translation, either from left or right side. +An element `p` of the group can act upon another another element by either: +* left action from the left side: ``L_p: q ↦ p \circ q``, +* right action from the left side: ``L'_p: q ↦ p^{-1} \circ q``, +* right action from the right side: ``R_p: q ↦ q \circ p``, +* left action from the right side: ``R'_p: q ↦ q \circ p^{-1}``. + +# Constructor + + GroupOperationAction(group::AbstractDecoratorManifold, AD::ActionDirectionAndSide = LeftForwardAction()) """ -struct GroupOperationAction{G,AD} <: AbstractGroupAction{AD} +struct GroupOperationAction{ + AD<:ActionDirection, + AS<:GroupActionSide, + G<:AbstractDecoratorManifold, +} <: AbstractGroupAction{AD} group::G end function GroupOperationAction( G::TM, ::TAD=LeftForwardAction(), -) where {TM<:AbstractDecoratorManifold,TAD<:ActionDirection} - return GroupOperationAction{TM,TAD}(G) +) where {TM<:AbstractDecoratorManifold,TAD<:ActionDirectionAndSide} + return GroupOperationAction{TAD.parameters[1],TAD.parameters[2],TM}(G) +end + +""" + action_side(A::GroupOperationAction) + +Return whether [`GroupOperationAction`](@ref) `A` acts on the [`LeftSide`](@ref) or +[`RightSide`](@ref). +""" +action_side(::GroupOperationAction{AD,AS}) where {AD<:ActionDirection,AS<:GroupActionSide} = + AS() + +function direction_and_side( + ::GroupOperationAction{AD,AS}, +) where {AD<:ActionDirection,AS<:GroupActionSide} + return (AD(), AS()) +end +function reverse_direction_and_side( + ::GroupOperationAction{AD,AS}, +) where {AD<:ActionDirection,AS<:GroupActionSide} + return (switch_direction(AD()), switch_side(AS())) end function Base.show(io::IO, A::GroupOperationAction) - return print(io, "GroupOperationAction($(A.group), $(direction(A)))") + return print(io, "GroupOperationAction($(A.group), $(direction_and_side(A)))") end base_group(A::GroupOperationAction) = A.group @@ -23,84 +56,68 @@ base_group(A::GroupOperationAction) = A.group group_manifold(A::GroupOperationAction) = A.group function switch_direction(A::GroupOperationAction) - return GroupOperationAction(A.group, switch_direction(direction(A))) + return GroupOperationAction(A.group, (switch_direction(direction(A)), action_side(A))) end -function adjoint_apply_diff_group( - A::GroupOperationAction{<:AbstractDecoratorManifold,AD}, - a, - X, - p, -) where {AD<:ActionDirection} +function switch_direction_and_side(A::GroupOperationAction) + return GroupOperationAction(A.group, reverse_direction_and_side(A)) +end + +function adjoint_apply_diff_group(A::GroupOperationAction, a, X, p) G = base_group(A) - return inverse_translate_diff(G, a, p, X, switch_direction(AD())) + return inverse_translate_diff(G, a, p, X, reverse_direction_and_side(A)) end -function adjoint_apply_diff_group!( - A::GroupOperationAction{<:AbstractDecoratorManifold,AD}, - Y, - a, - X, - p, -) where {AD<:ActionDirection} +function adjoint_apply_diff_group!(A::GroupOperationAction, Y, a, X, p) G = base_group(A) - return inverse_translate_diff!(G, Y, a, p, X, switch_direction(AD())) + return inverse_translate_diff!(G, Y, a, p, X, reverse_direction_and_side(A)) end -apply(A::GroupOperationAction, a, p) = translate(A.group, a, p, direction(A)) +apply(A::GroupOperationAction, a, p) = translate(A.group, a, p, direction_and_side(A)) -apply!(A::GroupOperationAction, q, a, p) = translate!(A.group, q, a, p, direction(A)) +function apply!(A::GroupOperationAction, q, a, p) + return translate!(A.group, q, a, p, direction_and_side(A)) +end function inverse_apply(A::GroupOperationAction, a, p) - return inverse_translate(A.group, a, p, direction(A)) + return inverse_translate(A.group, a, p, direction_and_side(A)) end function inverse_apply!(A::GroupOperationAction, q, a, p) - return inverse_translate!(A.group, q, a, p, direction(A)) + return inverse_translate!(A.group, q, a, p, direction_and_side(A)) end function apply_diff(A::GroupOperationAction, a, p, X) - return translate_diff(A.group, a, p, X, direction(A)) + return translate_diff(A.group, a, p, X, direction_and_side(A)) end function apply_diff!(A::GroupOperationAction, Y, a, p, X) - return translate_diff!(A.group, Y, a, p, X, direction(A)) + return translate_diff!(A.group, Y, a, p, X, direction_and_side(A)) end -function apply_diff_group( - A::GroupOperationAction{<:AbstractDecoratorManifold,AD}, - a, - X, - p, -) where {AD<:ActionDirection} +function apply_diff_group(A::GroupOperationAction, a, X, p) G = base_group(A) - return translate_diff(G, p, a, X, switch_direction(AD())) + return translate_diff(G, p, a, X, reverse_direction_and_side(A)) end -function apply_diff_group!( - A::GroupOperationAction{<:AbstractDecoratorManifold,AD}, - Y, - a, - X, - p, -) where {AD<:ActionDirection} +function apply_diff_group!(A::GroupOperationAction, Y, a, X, p) G = base_group(A) - return translate_diff!(G, Y, p, a, X, switch_direction(AD())) + return translate_diff!(G, Y, p, a, X, reverse_direction_and_side(A)) end function inverse_apply_diff(A::GroupOperationAction, a, p, X) - return inverse_translate_diff(A.group, a, p, X, direction(A)) + return inverse_translate_diff(A.group, a, p, X, direction_and_side(A)) end function inverse_apply_diff!(A::GroupOperationAction, Y, a, p, X) - return inverse_translate_diff!(A.group, Y, a, p, X, direction(A)) + return inverse_translate_diff!(A.group, Y, a, p, X, direction_and_side(A)) end function optimal_alignment(A::GroupOperationAction, p, q) - return inverse_apply(switch_direction(A), p, q) + return inverse_apply(switch_direction_and_side(A), p, q) end function optimal_alignment!(A::GroupOperationAction, x, p, q) - return inverse_apply!(switch_direction(A), x, p, q) + return inverse_apply!(switch_direction_and_side(A), x, p, q) end function center_of_orbit( @@ -110,9 +127,9 @@ function center_of_orbit( mean_method::AbstractEstimationMethod, ) μ = mean(A.group, pts, mean_method) - return inverse_apply(switch_direction(A), p, μ) + return inverse_apply(switch_direction_and_side(A), p, μ) end function center_of_orbit(A::GroupOperationAction, pts::AbstractVector, p) μ = mean(A.group, pts) - return inverse_apply(switch_direction(A), p, μ) + return inverse_apply(switch_direction_and_side(A), p, μ) end diff --git a/src/groups/heisenberg.jl b/src/groups/heisenberg.jl index e155b9337f..2c5793cae3 100644 --- a/src/groups/heisenberg.jl +++ b/src/groups/heisenberg.jl @@ -1,6 +1,6 @@ @doc raw""" - HeisenbergGroup{n} <: AbstractDecoratorManifold{ℝ} + HeisenbergGroup{T} <: AbstractDecoratorManifold{ℝ} Heisenberg group `HeisenbergGroup(n)` is the group of ``(n+2) × (n+2)`` matrices [BinzPods:2008](@cite) @@ -16,26 +16,30 @@ The group operation is matrix multiplication. The left-invariant metric on the manifold is used. """ -struct HeisenbergGroup{n} <: AbstractDecoratorManifold{ℝ} end +struct HeisenbergGroup{T} <: AbstractDecoratorManifold{ℝ} + size::T +end + +function HeisenbergGroup(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return HeisenbergGroup{typeof(size)}(size) +end function active_traits(f, ::HeisenbergGroup, args...) - return merge_traits( - IsGroupManifold(MultiplicationOperation()), - IsEmbeddedManifold(), - HasLeftInvariantMetric(), - ) + return merge_traits(IsGroupManifold(MultiplicationOperation()), IsEmbeddedManifold()) end -function _heisenberg_a_view(::HeisenbergGroup{n}, p) where {n} +function _heisenberg_a_view(M::HeisenbergGroup, p) + n = get_parameter(M.size)[1] return view(p, 1, 2:(n + 1)) end -function _heisenberg_b_view(::HeisenbergGroup{n}, p) where {n} +function _heisenberg_b_view(M::HeisenbergGroup, p) + n = get_parameter(M.size)[1] return view(p, 2:(n + 1), n + 2) end -HeisenbergGroup(n::Int) = HeisenbergGroup{n}() - -function check_point(G::HeisenbergGroup{n}, p; kwargs...) where {n} +function check_point(G::HeisenbergGroup, p; kwargs...) + n = get_parameter(G.size)[1] if !isone(p[1, 1]) return DomainError( p[1, 1], @@ -70,7 +74,8 @@ function check_point(G::HeisenbergGroup{n}, p; kwargs...) where {n} return nothing end -function check_vector(G::HeisenbergGroup{n}, p, X; kwargs...) where {n} +function check_vector(G::HeisenbergGroup, p, X; kwargs...) + n = get_parameter(G.size)[1] if !iszero(X[1, 1]) return DomainError( X[1, 1], @@ -109,24 +114,26 @@ the coordinates are concatenated vectors ``\mathbf{a}``, ``\mathbf{b}``, and num """ get_coordinates(::HeisenbergGroup, p, X, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}) -function get_coordinates_orthonormal(M::HeisenbergGroup{n}, p, X, ::RealNumbers) where {n} +function get_coordinates_orthonormal(M::HeisenbergGroup, p, X, ::RealNumbers) + n = get_parameter(M.size)[1] return vcat(_heisenberg_a_view(M, X), _heisenberg_b_view(M, X), X[1, n + 2]) end -function get_coordinates_orthonormal!( - M::HeisenbergGroup{n}, - Xⁱ, - p, - X, - ::RealNumbers, -) where {n} +function get_coordinates_orthonormal!(M::HeisenbergGroup, Xⁱ, p, X, ::RealNumbers) + n = get_parameter(M.size)[1] Xⁱ[1:n] .= _heisenberg_a_view(M, X) Xⁱ[(n + 1):(2 * n)] .= _heisenberg_b_view(M, X) Xⁱ[2 * n + 1] = X[1, n + 2] return Xⁱ end -get_embedding(::HeisenbergGroup{n}) where {n} = Euclidean(n + 2, n + 2) +function get_embedding(::HeisenbergGroup{TypeParameter{Tuple{n}}}) where {n} + return Euclidean(n + 2, n + 2) +end +function get_embedding(M::HeisenbergGroup{Tuple{Int}}) + n = get_parameter(M.size)[1] + return Euclidean(n + 2, n + 2; parameter=:field) +end @doc raw""" get_vector(M::HeisenbergGroup, p, Xⁱ, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}) @@ -141,7 +148,8 @@ Given a vector of coordinates ``\begin{bmatrix}\mathbb{a} & \mathbb{b} & c\end{b """ get_vector(M::HeisenbergGroup, p, c, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}) -function get_vector_orthonormal(::HeisenbergGroup{n}, p, Xⁱ, ::RealNumbers) where {n} +function get_vector_orthonormal(M::HeisenbergGroup, p, Xⁱ, ::RealNumbers) + n = get_parameter(M.size)[1] return [ 0 Xⁱ[1:n] Xⁱ[2 * n + 1] zeros(n, n + 1) Xⁱ[(n + 1):(2 * n)]' @@ -149,7 +157,8 @@ function get_vector_orthonormal(::HeisenbergGroup{n}, p, Xⁱ, ::RealNumbers) wh ] end -function get_vector_orthonormal!(::HeisenbergGroup{n}, X, p, Xⁱ, ::RealNumbers) where {n} +function get_vector_orthonormal!(M::HeisenbergGroup, X, p, Xⁱ, ::RealNumbers) + n = get_parameter(M.size)[1] fill!(X, 0) X[1, 2:(n + 1)] .= Xⁱ[1:n] X[2:(n + 1), n + 2] .= Xⁱ[(n + 1):(2 * n)] @@ -174,7 +183,8 @@ and ``\mathbf{a}⋅\mathbf{b}`` is dot product of vectors. """ exp_lie(M::HeisenbergGroup, X) -function exp_lie!(M::HeisenbergGroup{n}, q, X) where {n} +function exp_lie!(M::HeisenbergGroup, q, X) + n = get_parameter(M.size)[1] copyto!(q, I) a_view = _heisenberg_a_view(M, X) b_view = _heisenberg_b_view(M, X) @@ -204,7 +214,8 @@ and ``\mathbf{a}⋅\mathbf{b}`` is dot product of vectors. """ exp(M::HeisenbergGroup, p, X) -function exp!(M::HeisenbergGroup{n}, q, p, X) where {n} +function exp!(M::HeisenbergGroup, q, p, X) + n = get_parameter(M.size)[1] copyto!(q, I) a_p_view = _heisenberg_a_view(M, p) b_p_view = _heisenberg_b_view(M, p) @@ -224,7 +235,8 @@ Return the injectivity radius on the [`HeisenbergGroup`](@ref) `M`, which is `` """ injectivity_radius(::HeisenbergGroup) = Inf -function inner(M::HeisenbergGroup{n}, p, X, Y) where {n} +function inner(M::HeisenbergGroup, p, X, Y) + n = get_parameter(M.size)[1] X_a_view = _heisenberg_a_view(M, X) X_b_view = _heisenberg_b_view(M, X) Y_a_view = _heisenberg_a_view(M, Y) @@ -254,7 +266,8 @@ and ``\mathbf{a}⋅\mathbf{b}`` is dot product of vectors. """ log(::HeisenbergGroup, p, q) -function log!(M::HeisenbergGroup{n}, X, p, q) where {n} +function log!(M::HeisenbergGroup, X, p, q) + n = get_parameter(M.size)[1] fill!(X, 0) a_p_view = _heisenberg_a_view(M, p) b_p_view = _heisenberg_b_view(M, p) @@ -285,7 +298,8 @@ and ``\mathbf{a}⋅\mathbf{b}`` is dot product of vectors. """ log_lie(M::HeisenbergGroup, p) -function log_lie!(M::HeisenbergGroup{n}, X, p) where {n} +function log_lie!(M::HeisenbergGroup, X, p) + n = get_parameter(M.size)[1] fill!(X, 0) view_a_X = _heisenberg_a_view(M, X) view_b_X = _heisenberg_b_view(M, X) @@ -300,20 +314,21 @@ function log_lie!(::HeisenbergGroup, X, ::Identity{MultiplicationOperation}) return X end -manifold_dimension(::HeisenbergGroup{n}) where {n} = 2 * n + 1 +manifold_dimension(M::HeisenbergGroup) = 2 * get_parameter(M.size)[1] + 1 parallel_transport_to(::HeisenbergGroup, p, X, q) = X parallel_transport_to!(::HeisenbergGroup, Y, p, X, q) = copyto!(Y, X) """ - project(M::HeisenbergGroup{n}, p) + project(M::HeisenbergGroup, p) Project a matrix `p` in the Euclidean embedding onto the [`HeisenbergGroup`](@ref) `M`. Sets the diagonal elements to 1 and all non-diagonal elements except the first row and the last column to 0. """ -function project(M::HeisenbergGroup{n}, p) where {n} +function project(M::HeisenbergGroup, p) + n = get_parameter(M.size)[1] return [ 1 p[1, 2:(n + 2)]' zeros(n, 1) Matrix(I, n, n) _heisenberg_b_view(M, p) @@ -321,14 +336,15 @@ function project(M::HeisenbergGroup{n}, p) where {n} ] end """ - project(M::HeisenbergGroup{n}, p, X) + project(M::HeisenbergGroup, p, X) Project a matrix `X` in the Euclidean embedding onto the Lie algebra of [`HeisenbergGroup`](@ref) `M`. Sets the diagonal elements to 0 and all non-diagonal elements except the first row and the last column to 0. """ -function project(M::HeisenbergGroup{n}, p, X) where {n} +function project(M::HeisenbergGroup, p, X) + n = get_parameter(M.size)[1] return [ 0 X[1, 2:(n + 2)]' zeros(n, n + 1) _heisenberg_b_view(M, X) @@ -336,13 +352,15 @@ function project(M::HeisenbergGroup{n}, p, X) where {n} ] end -function project!(M::HeisenbergGroup{n}, q, p) where {n} +function project!(M::HeisenbergGroup, q, p) + n = get_parameter(M.size)[1] copyto!(q, I) q[1, 2:(n + 2)] .= p[1, 2:(n + 2)] q[2:(n + 1), n + 2] .= _heisenberg_b_view(M, p) return q end -function project!(M::HeisenbergGroup{n}, Y, p, X) where {n} +function project!(M::HeisenbergGroup, Y, p, X) + n = get_parameter(M.size)[1] fill!(Y, 0) Y[1, 2:(n + 2)] .= X[1, 2:(n + 2)] Y[2:(n + 1), n + 2] .= _heisenberg_b_view(M, X) @@ -364,11 +382,12 @@ rand(M::HeisenbergGroup; vector_at=nothing, σ::Real=1.0) function Random.rand!( rng::AbstractRNG, - ::HeisenbergGroup{n}, + M::HeisenbergGroup, pX; σ::Real=one(eltype(pX)), vector_at=nothing, -) where {n} +) + n = get_parameter(M.size)[1] if vector_at === nothing copyto!(pX, I) va = view(pX, 1, 2:(n + 2)) @@ -386,11 +405,17 @@ function Random.rand!( return pX end -Base.show(io::IO, ::HeisenbergGroup{n}) where {n} = print(io, "HeisenbergGroup($n)") +function Base.show(io::IO, ::HeisenbergGroup{TypeParameter{Tuple{n}}}) where {n} + return print(io, "HeisenbergGroup($(n))") +end +function Base.show(io::IO, M::HeisenbergGroup{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "HeisenbergGroup($(n); parameter=:field)") +end translate_diff(::HeisenbergGroup, p, q, X, ::LeftForwardAction) = X translate_diff(::HeisenbergGroup, p, q, X, ::RightBackwardAction) = p \ X * p -function translate_diff!(G::HeisenbergGroup, Y, p, q, X, conv::ActionDirection) +function translate_diff!(G::HeisenbergGroup, Y, p, q, X, conv::ActionDirectionAndSide) return copyto!(Y, translate_diff(G, p, q, X, conv)) end diff --git a/src/groups/metric.jl b/src/groups/metric.jl index 19a946dfd9..ed3552421b 100644 --- a/src/groups/metric.jl +++ b/src/groups/metric.jl @@ -5,7 +5,7 @@ X, Y, qs::AbstractVector, - conv::ActionDirection = LeftForwardAction(); + conv::ActionDirectionAndSide = LeftForwardAction(); kwargs..., ) -> Bool @@ -22,14 +22,21 @@ This is necessary but not sufficient for invariance. Optionally, `kwargs` passed to `isapprox` may be provided. """ -has_approx_invariant_metric(::AbstractDecoratorManifold, p, X, Y, qs, ::ActionDirection) +has_approx_invariant_metric( + ::AbstractDecoratorManifold, + p, + X, + Y, + qs, + ::ActionDirectionAndSide, +) @trait_function has_approx_invariant_metric( M::AbstractDecoratorManifold, p, X, Y, qs, - conv::ActionDirection=LeftForwardAction(); + conv::ActionDirectionAndSide=LeftForwardAction(); kwargs..., ) function has_approx_invariant_metric( @@ -39,7 +46,7 @@ function has_approx_invariant_metric( X, Y, qs, - conv::ActionDirection; + conv::ActionDirectionAndSide; kwargs..., ) gpXY = inner(M, p, X, Y) @@ -55,17 +62,33 @@ end """ direction(::AbstractDecoratorManifold) -> AD -Get the direction of the action a certain Lie group with its implicit metric has +Get the direction of the action a certain Lie group with its implicit metric has. """ direction(::AbstractDecoratorManifold) @trait_function direction(M::AbstractDecoratorManifold) function direction(::TraitList{HasLeftInvariantMetric}, ::AbstractDecoratorManifold) - return LeftForwardAction() + return LeftAction() end function direction(::TraitList{HasRightInvariantMetric}, ::AbstractDecoratorManifold) + return RightAction() +end + +@trait_function direction_and_side(M::AbstractDecoratorManifold) + +function direction_and_side( + ::TraitList{HasLeftInvariantMetric}, + ::AbstractDecoratorManifold, +) + return LeftForwardAction() +end + +function direction_and_side( + ::TraitList{HasRightInvariantMetric}, + ::AbstractDecoratorManifold, +) return RightBackwardAction() end @@ -139,7 +162,7 @@ function get_coordinates( X, B::AbstractBasis, ) where {IT<:AbstractInvarianceTrait} - conv = direction(t, M) + conv = direction_and_side(t, M) Xₑ = inverse_translate_diff(M, p, p, X, conv) return get_coordinates_lie(next_trait(t), M, Xₑ, B) end @@ -151,7 +174,7 @@ function get_coordinates!( X, B::AbstractBasis, ) where {IT<:AbstractInvarianceTrait} - conv = direction(t, M) + conv = direction_and_side(t, M) Xₑ = inverse_translate_diff(M, p, p, X, conv) return get_coordinates_lie!(next_trait(t), M, c, Xₑ, B) end @@ -163,7 +186,7 @@ function get_vector( c, B::AbstractBasis, ) where {IT<:AbstractInvarianceTrait} - conv = direction(t, M) + conv = direction_and_side(t, M) Xₑ = get_vector_lie(next_trait(t), M, c, B) return translate_diff(M, p, Identity(M), Xₑ, conv) end @@ -175,15 +198,18 @@ function get_vector!( c, B::AbstractBasis, ) where {IT<:AbstractInvarianceTrait} - conv = direction(t, M) + conv = direction_and_side(t, M) Xₑ = get_vector_lie(next_trait(t), M, c, B) return translate_diff!(M, X, p, Identity(M), Xₑ, conv) end -@trait_function has_invariant_metric(M::AbstractDecoratorManifold, op::ActionDirection) +@trait_function has_invariant_metric( + M::AbstractDecoratorManifold, + op::ActionDirectionAndSide, +) # Fallbacks / false -has_invariant_metric(::AbstractManifold, op::ActionDirection) = false +has_invariant_metric(::AbstractManifold, op::ActionDirectionAndSide) = false function has_invariant_metric( ::TraitList{<:HasLeftInvariantMetric}, ::AbstractDecoratorManifold, @@ -217,7 +243,7 @@ function inner( X, Y, ) where {IT<:AbstractInvarianceTrait} - conv = direction(t, M) + conv = direction_and_side(t, M) Xₑ = inverse_translate_diff(M, p, p, X, conv) Yₑ = inverse_translate_diff(M, p, p, Y, conv) return inner(next_trait(t), M, Identity(M), Xₑ, Yₑ) @@ -238,7 +264,7 @@ function inverse_translate_diff( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return inverse_translate_diff(M.manifold, p, q, X, conv) end @@ -249,7 +275,7 @@ function inverse_translate_diff!( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return inverse_translate_diff!(M.manifold, Y, p, q, X, conv) end @@ -296,7 +322,7 @@ function LinearAlgebra.norm( p, X, ) where {IT<:AbstractInvarianceTrait} - conv = direction(t, M) + conv = direction_and_side(t, M) Xₑ = inverse_translate_diff(M, p, p, X, conv) return norm(next_trait(t), M, Identity(M), Xₑ) end @@ -315,7 +341,7 @@ function translate_diff( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return translate_diff(M.manifold, p, q, X, conv) end @@ -326,7 +352,7 @@ function translate_diff!( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return translate_diff!(M.manifold, Y, p, q, X, conv) end @@ -366,3 +392,6 @@ end ) return merge_traits(HasRightInvariantMetric(), IsExplicitDecorator()) end + +direction(::LeftInvariantMetric) = LeftAction() +direction(::RightInvariantMetric) = RightAction() diff --git a/src/groups/multiplication_operation.jl b/src/groups/multiplication_operation.jl index 689a134c1b..de21633021 100644 --- a/src/groups/multiplication_operation.jl +++ b/src/groups/multiplication_operation.jl @@ -155,7 +155,7 @@ function inverse_translate!( x, p, q, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return copyto!(x, inverse_translate(G, p, q, conv)) end diff --git a/src/groups/orthogonal.jl b/src/groups/orthogonal.jl index ade8de09f5..2448d0356a 100644 --- a/src/groups/orthogonal.jl +++ b/src/groups/orthogonal.jl @@ -1,14 +1,22 @@ @doc raw""" - Orthogonal{n} = GeneralUnitaryMultiplicationGroup{n,ℝ,AbsoluteDeterminantOneMatrices} + Orthogonal{T} = GeneralUnitaryMultiplicationGroup{T,ℝ,AbsoluteDeterminantOneMatrices} Orthogonal group $\mathrm{O}(n)$ represented by [`OrthogonalMatrices`](@ref). # Constructor - Orthogonal(n) + Orthogonal(n::Int; parameter::Symbol=:type) """ -const Orthogonal{n} = GeneralUnitaryMultiplicationGroup{n,ℝ,AbsoluteDeterminantOneMatrices} +const Orthogonal{T} = GeneralUnitaryMultiplicationGroup{T,ℝ,AbsoluteDeterminantOneMatrices} -Orthogonal(n) = Orthogonal{n}(OrthogonalMatrices(n)) +function Orthogonal(n::Int; parameter::Symbol=:type) + return GeneralUnitaryMultiplicationGroup(OrthogonalMatrices(n; parameter=parameter)) +end -show(io::IO, ::Orthogonal{n}) where {n} = print(io, "Orthogonal($(n))") +function Base.show(io::IO, ::Orthogonal{TypeParameter{Tuple{n}}}) where {n} + return print(io, "Orthogonal($(n))") +end +function Base.show(io::IO, M::Orthogonal{Tuple{Int}}) + n = get_parameter(M.manifold.size)[1] + return print(io, "Orthogonal($(n); parameter=:field)") +end diff --git a/src/groups/power_group.jl b/src/groups/power_group.jl index 235d65780c..d454e7e63e 100644 --- a/src/groups/power_group.jl +++ b/src/groups/power_group.jl @@ -154,10 +154,10 @@ function _compose!(M::PowerManifoldNestedReplacing, x, p, q) return x end -function translate!(G::PowerGroup, x, p, q, conv::ActionDirection) +function translate!(G::PowerGroup, x, p, q, conv::ActionDirectionAndSide) return translate!(G.manifold, x, p, q, conv) end -function translate!(M::AbstractPowerManifold, x, p, q, conv::ActionDirection) +function translate!(M::AbstractPowerManifold, x, p, q, conv::ActionDirectionAndSide) N = M.manifold rep_size = representation_size(N) for i in get_iterator(M) @@ -171,7 +171,7 @@ function translate!(M::AbstractPowerManifold, x, p, q, conv::ActionDirection) end return x end -function translate!(M::PowerManifoldNestedReplacing, x, p, q, conv::ActionDirection) +function translate!(M::PowerManifoldNestedReplacing, x, p, q, conv::ActionDirectionAndSide) N = M.manifold rep_size = representation_size(N) for i in get_iterator(M) @@ -180,10 +180,10 @@ function translate!(M::PowerManifoldNestedReplacing, x, p, q, conv::ActionDirect return x end -function inverse_translate!(G::PowerGroup, x, p, q, conv::ActionDirection) +function inverse_translate!(G::PowerGroup, x, p, q, conv::ActionDirectionAndSide) return inverse_translate!(G.manifold, x, p, q, conv) end -function inverse_translate!(M::AbstractPowerManifold, x, p, q, conv::ActionDirection) +function inverse_translate!(M::AbstractPowerManifold, x, p, q, conv::ActionDirectionAndSide) N = M.manifold rep_size = representation_size(N) for i in get_iterator(M) @@ -197,7 +197,13 @@ function inverse_translate!(M::AbstractPowerManifold, x, p, q, conv::ActionDirec end return x end -function inverse_translate!(M::PowerManifoldNestedReplacing, x, p, q, conv::ActionDirection) +function inverse_translate!( + M::PowerManifoldNestedReplacing, + x, + p, + q, + conv::ActionDirectionAndSide, +) N = M.manifold rep_size = representation_size(N) for i in get_iterator(M) @@ -207,7 +213,7 @@ function inverse_translate!(M::PowerManifoldNestedReplacing, x, p, q, conv::Acti return x end -function translate_diff!(G::PowerGroup, Y, p, q, X, conv::ActionDirection) +function translate_diff!(G::PowerGroup, Y, p, q, X, conv::ActionDirectionAndSide) GM = G.manifold N = GM.manifold rep_size = representation_size(N) @@ -223,7 +229,14 @@ function translate_diff!(G::PowerGroup, Y, p, q, X, conv::ActionDirection) end return Y end -function translate_diff!(G::PowerGroupNestedReplacing, Y, p, q, X, conv::ActionDirection) +function translate_diff!( + G::PowerGroupNestedReplacing, + Y, + p, + q, + X, + conv::ActionDirectionAndSide, +) GM = G.manifold N = GM.manifold rep_size = representation_size(N) @@ -239,7 +252,7 @@ function translate_diff!(G::PowerGroupNestedReplacing, Y, p, q, X, conv::ActionD return Y end -function inverse_translate_diff!(G::PowerGroup, Y, p, q, X, conv::ActionDirection) +function inverse_translate_diff!(G::PowerGroup, Y, p, q, X, conv::ActionDirectionAndSide) GM = G.manifold N = GM.manifold rep_size = representation_size(N) @@ -261,7 +274,7 @@ function inverse_translate_diff!( p, q, X, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) GM = G.manifold N = GM.manifold diff --git a/src/groups/product_group.jl b/src/groups/product_group.jl index 842dd4af61..571f108ed6 100644 --- a/src/groups/product_group.jl +++ b/src/groups/product_group.jl @@ -62,7 +62,7 @@ function Base.show(io::IO, ::MIME"text/plain", G::ProductGroup) io, "ProductGroup with $(length(base_manifold(G).manifolds)) subgroup$(length(base_manifold(G).manifolds) == 1 ? "" : "s"):", ) - return _show_product_manifold_no_header(io, base_manifold(G)) + return ManifoldsBase._show_product_manifold_no_header(io, base_manifold(G)) end function Base.show(io::IO, G::ProductGroup) @@ -103,8 +103,8 @@ end inv!(::ProductGroup, q::Identity{ProductOperation}, ::Identity{ProductOperation}) = q _compose(G::ProductGroup, p, q) = _compose(G.manifold, p, q) -function _compose(M::ProductManifold, p::ProductRepr, q::ProductRepr) - return ProductRepr( +function _compose(M::ProductManifold, p::ArrayPartition, q::ArrayPartition) + return ArrayPartition( map( compose, M.manifolds, @@ -126,22 +126,11 @@ function _compose!(M::ProductManifold, x, p, q) return x end -function translate(M::ProductGroup, p::ProductRepr, q::ProductRepr, conv::ActionDirection) - return ProductRepr( - map( - translate, - M.manifold.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - repeated(conv), - )..., - ) -end function translate( M::ProductGroup, p::ArrayPartition, q::ArrayPartition, - conv::ActionDirection, + conv::ActionDirectionAndSide, ) return ArrayPartition( map( @@ -154,7 +143,7 @@ function translate( ) end -function translate!(M::ProductGroup, x, p, q, conv::ActionDirection) +function translate!(M::ProductGroup, x, p, q, conv::ActionDirectionAndSide) map( translate!, M.manifold.manifolds, @@ -166,9 +155,9 @@ function translate!(M::ProductGroup, x, p, q, conv::ActionDirection) return x end -function inverse_translate(G::ProductGroup, p, q, conv::ActionDirection) +function inverse_translate(G::ProductGroup, p, q, conv::ActionDirectionAndSide) M = G.manifold - return ProductRepr( + return ArrayPartition( map( inverse_translate, M.manifolds, @@ -179,7 +168,7 @@ function inverse_translate(G::ProductGroup, p, q, conv::ActionDirection) ) end -function inverse_translate!(G::ProductGroup, x, p, q, conv::ActionDirection) +function inverse_translate!(G::ProductGroup, x, p, q, conv::ActionDirectionAndSide) M = G.manifold map( inverse_translate!, @@ -192,7 +181,7 @@ function inverse_translate!(G::ProductGroup, x, p, q, conv::ActionDirection) return x end -function translate_diff(G::ProductGroup, p, q, X, conv::ActionDirection) +function translate_diff(G::ProductGroup, p, q, X, conv::ActionDirectionAndSide) M = G.manifold return ArrayPartition( map( @@ -206,7 +195,7 @@ function translate_diff(G::ProductGroup, p, q, X, conv::ActionDirection) ) end -function translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirection) +function translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirectionAndSide) M = G.manifold map( translate_diff!, @@ -220,9 +209,9 @@ function translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirection) return Y end -function inverse_translate_diff(G::ProductGroup, p, q, X, conv::ActionDirection) +function inverse_translate_diff(G::ProductGroup, p, q, X, conv::ActionDirectionAndSide) M = G.manifold - return ProductRepr( + return ArrayPartition( map( inverse_translate_diff, M.manifolds, @@ -234,7 +223,7 @@ function inverse_translate_diff(G::ProductGroup, p, q, X, conv::ActionDirection) ) end -function inverse_translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirection) +function inverse_translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirectionAndSide) M = G.manifold map( inverse_translate_diff!, @@ -248,16 +237,6 @@ function inverse_translate_diff!(G::ProductGroup, Y, p, q, X, conv::ActionDirect return Y end -function Base.exp(M::ProductGroup, p::Identity{ProductOperation}, X::ProductRepr) - return ProductRepr( - map( - exp, - M.manifold.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )..., - ) -end function Base.exp(M::ProductGroup, p::Identity{ProductOperation}, X::ArrayPartition) return ArrayPartition( map( @@ -291,16 +270,6 @@ function exp_lie!(G::ProductGroup, q, X) return q end -function Base.log(M::ProductGroup, p::Identity{ProductOperation}, q::ProductRepr) - return ProductRepr( - map( - log, - M.manifold.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - )..., - ) -end function Base.log(M::ProductGroup, p::Identity{ProductOperation}, q::ArrayPartition) return ArrayPartition( map( @@ -338,7 +307,7 @@ function log_lie!(G::ProductGroup, X, q::Identity{ProductOperation}) end Base.@propagate_inbounds function Base.getindex( - p::Union{ProductRepr,ArrayPartition}, + p::ArrayPartition, M::ProductGroup, i::Union{Integer,Colon,AbstractVector,Val}, ) @@ -346,7 +315,7 @@ Base.@propagate_inbounds function Base.getindex( end Base.@propagate_inbounds function Base.setindex!( - q::Union{ProductRepr,ArrayPartition}, + q::ArrayPartition, p, M::ProductGroup, i::Union{Integer,Colon,AbstractVector,Val}, diff --git a/src/groups/rotation_action.jl b/src/groups/rotation_action.jl index 4ae5fc77af..cebd3016a4 100644 --- a/src/groups/rotation_action.jl +++ b/src/groups/rotation_action.jl @@ -2,13 +2,13 @@ RotationAction( M::AbstractManifold, SOn::SpecialOrthogonal, - AD::ActionDirection = LeftForwardAction(), + AD::ActionDirection = LeftAction(), ) Space of actions of the [`SpecialOrthogonal`](@ref) group $\mathrm{SO}(n)$ on a Euclidean-like manifold `M` of dimension `n`. """ -struct RotationAction{TM<:AbstractManifold,TSO<:SpecialOrthogonal,TAD<:ActionDirection} <: +struct RotationAction{TAD<:ActionDirection,TM<:AbstractManifold,TSO<:SpecialOrthogonal} <: AbstractGroupAction{TAD} manifold::TM SOn::TSO @@ -17,118 +17,78 @@ end function RotationAction( M::AbstractManifold, SOn::SpecialOrthogonal, - ::TAD=LeftForwardAction(), + ::TAD=LeftAction(), ) where {TAD<:ActionDirection} - return RotationAction{typeof(M),typeof(SOn),TAD}(M, SOn) + return RotationAction{TAD,typeof(M),typeof(SOn)}(M, SOn) end function Base.show(io::IO, A::RotationAction) return print(io, "RotationAction($(A.manifold), $(A.SOn), $(direction(A)))") end -const RotationActionOnVector{N,F,TAD} = RotationAction{ - <:Union{Euclidean{Tuple{N},F},TranslationGroup{Tuple{N},F}}, - SpecialOrthogonal{N}, +const RotationActionOnVector{TAD,𝔽,TE,TSO} = RotationAction{ TAD, -} + <:Union{Euclidean{TE,𝔽},TranslationGroup{TE,𝔽}}, + SpecialOrthogonal{TSO}, +} where {TAD<:ActionDirection,𝔽,TE,TSO} base_group(A::RotationAction) = A.SOn group_manifold(A::RotationAction) = A.manifold -function switch_direction( - A::RotationAction{TM,TSO,TAD}, - ::LeftRightSwitch=LeftRightSwitch(), -) where {TM<:AbstractManifold,TSO<:SpecialOrthogonal,TAD<:ActionDirection} - return RotationAction(A.manifold, A.SOn, switch_direction(TAD(), LeftRightSwitch())) +function switch_direction(A::RotationAction{TAD}) where {TAD<:ActionDirection} + return RotationAction(A.manifold, A.SOn, switch_direction(TAD())) end -apply(::RotationActionOnVector{N,F,LeftForwardAction}, a, p) where {N,F} = a * p -function apply(A::RotationActionOnVector{N,F,RightForwardAction}, a, p) where {N,F} +apply(::RotationActionOnVector{LeftAction}, a, p) = a * p +function apply(A::RotationActionOnVector{RightAction}, a, p) return inv(base_group(A), a) * p end -apply!(::RotationActionOnVector{N,F,LeftForwardAction}, q, a, p) where {N,F} = mul!(q, a, p) +apply!(::RotationActionOnVector{LeftAction}, q, a, p) = mul!(q, a, p) -function inverse_apply(A::RotationActionOnVector{N,F,LeftForwardAction}, a, p) where {N,F} +function inverse_apply(A::RotationActionOnVector{LeftAction}, a, p) return inv(base_group(A), a) * p end -inverse_apply(::RotationActionOnVector{N,F,RightForwardAction}, a, p) where {N,F} = a * p +inverse_apply(::RotationActionOnVector{RightAction}, a, p) = a * p -apply_diff(::RotationActionOnVector{N,F,LeftForwardAction}, a, p, X) where {N,F} = a * X +apply_diff(::RotationActionOnVector{LeftAction}, a, p, X) = a * X function apply_diff( - ::RotationActionOnVector{N,F,LeftForwardAction}, + ::RotationActionOnVector{LeftAction}, ::Identity{MultiplicationOperation}, p, X, -) where {N,F} +) return X end -function apply_diff(A::RotationActionOnVector{N,F,RightForwardAction}, a, p, X) where {N,F} +function apply_diff(A::RotationActionOnVector{RightAction}, a, p, X) return inv(base_group(A), a) * X end -function apply_diff!( - ::RotationActionOnVector{N,F,LeftForwardAction}, - Y, - a, - p, - X, -) where {N,F} +function apply_diff!(::RotationActionOnVector{LeftAction}, Y, a, p, X) return mul!(Y, a, X) end -function apply_diff!( - A::RotationActionOnVector{N,F,RightForwardAction}, - Y, - a, - p, - X, -) where {N,F} +function apply_diff!(A::RotationActionOnVector{RightAction}, Y, a, p, X) return mul!(Y, inv(base_group(A), a), X) end -function apply_diff_group( - ::RotationActionOnVector{N,F,LeftForwardAction}, - ::Identity, - X, - p, -) where {N,F} +function apply_diff_group(::RotationActionOnVector{LeftAction}, ::Identity, X, p) return X * p end -function apply_diff_group!( - ::RotationActionOnVector{N,F,LeftForwardAction}, - Y, - ::Identity, - X, - p, -) where {N,F} +function apply_diff_group!(::RotationActionOnVector{LeftAction}, Y, ::Identity, X, p) Y .= X * p return Y end -function inverse_apply_diff( - A::RotationActionOnVector{N,F,LeftForwardAction}, - a, - p, - X, -) where {N,F} +function inverse_apply_diff(A::RotationActionOnVector{LeftAction}, a, p, X) return inv(base_group(A), a) * X end -function inverse_apply_diff( - A::RotationActionOnVector{N,F,RightForwardAction}, - a, - p, - X, -) where {N,F} +function inverse_apply_diff(::RotationActionOnVector{RightAction}, a, p, X) return a * X end -function optimal_alignment( - ::RotationActionOnVector{N,T,LeftForwardAction}, - p, - q, -) where {N,T} +function optimal_alignment(::RotationActionOnVector{LeftAction}, p, q) Xmul = p * transpose(q) F = svd(Xmul) L = size(Xmul)[2] @@ -136,11 +96,7 @@ function optimal_alignment( Ostar = det(UVt) ≥ 0 ? UVt : F.U * Diagonal([i < L ? 1 : -1 for i in 1:L]) * F.Vt return convert(typeof(Xmul), Ostar) end -function optimal_alignment( - A::RotationActionOnVector{N,T,RightForwardAction}, - p, - q, -) where {N,T} +function optimal_alignment(A::RotationActionOnVector{RightAction}, p, q) return optimal_alignment(switch_direction(A), q, p) end @@ -149,8 +105,7 @@ end Space of actions of the circle group [`RealCircleGroup`](@ref) on $ℝ^3$ around given `axis`. """ -struct RotationAroundAxisAction{TA<:AbstractVector} <: - AbstractGroupAction{LeftForwardAction} +struct RotationAroundAxisAction{TA<:AbstractVector} <: AbstractGroupAction{LeftAction} axis::TA end @@ -191,9 +146,9 @@ end @doc raw""" RowwiseMultiplicationAction{ + TAD<:ActionDirection, TM<:AbstractManifold, TO<:GeneralUnitaryMultiplicationGroup, - TAD<:ActionDirection, } <: AbstractGroupAction{TAD} Action of the (special) unitary or orthogonal group [`GeneralUnitaryMultiplicationGroup`](@ref) @@ -204,13 +159,13 @@ of type `On` columns of points on a matrix manifold `M`. RowwiseMultiplicationAction( M::AbstractManifold, On::GeneralUnitaryMultiplicationGroup, - AD::ActionDirection = LeftForwardAction(), + AD::ActionDirection = LeftAction(), ) """ struct RowwiseMultiplicationAction{ + TAD<:ActionDirection, TM<:AbstractManifold, TO<:GeneralUnitaryMultiplicationGroup, - TAD<:ActionDirection, } <: AbstractGroupAction{TAD} manifold::TM On::TO @@ -219,15 +174,15 @@ end function RowwiseMultiplicationAction( M::AbstractManifold, On::GeneralUnitaryMultiplicationGroup, - ::TAD=LeftForwardAction(), + ::TAD=LeftAction(), ) where {TAD<:ActionDirection} - return RowwiseMultiplicationAction{typeof(M),typeof(On),TAD}(M, On) + return RowwiseMultiplicationAction{TAD,typeof(M),typeof(On)}(M, On) end const LeftRowwiseMultiplicationAction{ TM<:AbstractManifold, TO<:GeneralUnitaryMultiplicationGroup, -} = RowwiseMultiplicationAction{TM,TO,LeftForwardAction} +} = RowwiseMultiplicationAction{LeftAction,TM,TO} function apply(::LeftRowwiseMultiplicationAction, a, p) return (a * p')' @@ -265,13 +220,13 @@ of type `On` columns of points on a matrix manifold `M`. ColumnwiseMultiplicationAction( M::AbstractManifold, On::GeneralUnitaryMultiplicationGroup, - AD::ActionDirection = LeftForwardAction(), + AD::ActionDirection = LeftAction(), ) """ struct ColumnwiseMultiplicationAction{ + TAD<:ActionDirection, TM<:AbstractManifold, TO<:GeneralUnitaryMultiplicationGroup, - TAD<:ActionDirection, } <: AbstractGroupAction{TAD} manifold::TM On::TO @@ -280,15 +235,15 @@ end function ColumnwiseMultiplicationAction( M::AbstractManifold, On::GeneralUnitaryMultiplicationGroup, - ::TAD=LeftForwardAction(), + ::TAD=LeftAction(), ) where {TAD<:ActionDirection} - return ColumnwiseMultiplicationAction{typeof(M),typeof(On),TAD}(M, On) + return ColumnwiseMultiplicationAction{TAD,typeof(M),typeof(On)}(M, On) end const LeftColumnwiseMultiplicationAction{ TM<:AbstractManifold, TO<:GeneralUnitaryMultiplicationGroup, -} = ColumnwiseMultiplicationAction{TM,TO,LeftForwardAction} +} = ColumnwiseMultiplicationAction{LeftAction,TM,TO} function apply(::LeftColumnwiseMultiplicationAction, a, p) return a * p @@ -319,7 +274,7 @@ of computation are described in Section 2.2.1 of [SrivastavaKlassen:2016](@cite) The formula reads ```math O^{*} = \begin{cases} -UV^T & \text{if } \operatorname{det}(p q^{\mathrm{T}})\\ +UV^T & \text{if } \operatorname{det}(p q^{\mathrm{T}}) \geq 0\\ U K V^{\mathrm{T}} & \text{otherwise} \end{cases} ``` @@ -327,8 +282,8 @@ where ``U \Sigma V^{\mathrm{T}}`` is the SVD decomposition of ``p q^{\mathrm{T}} is the unit diagonal matrix with the last element on the diagonal replaced with -1. """ function optimal_alignment(A::LeftColumnwiseMultiplicationAction, p, q) - is_point(A.manifold, p, true) - is_point(A.manifold, q, true) + is_point(A.manifold, p; error=:error) + is_point(A.manifold, q; error=:error) Xmul = p * transpose(q) F = svd(Xmul) diff --git a/src/groups/rotation_translation_action.jl b/src/groups/rotation_translation_action.jl new file mode 100644 index 0000000000..ea706e3639 --- /dev/null +++ b/src/groups/rotation_translation_action.jl @@ -0,0 +1,354 @@ +@doc raw""" + RotationTranslationAction( + M::AbstractManifold, + SOn::SpecialEuclidean, + AD::ActionDirection = LeftAction(), + ) + +Space of actions of the [`SpecialEuclidean`](@ref) group ``\mathrm{SE}(n)`` on a +Euclidean-like manifold `M` of dimension `n`. + +Left actions corresponds to active transformations while right actions +can be identified with passive transformations for a particular choice of a basis. +""" +struct RotationTranslationAction{ + TAD<:ActionDirection, + TM<:AbstractManifold, + TSE<:SpecialEuclidean, +} <: AbstractGroupAction{TAD} + manifold::TM + SEn::TSE +end + +function RotationTranslationAction( + M::AbstractManifold, + SEn::SpecialEuclidean, + ::TAD=LeftAction(), +) where {TAD<:ActionDirection} + return RotationTranslationAction{TAD,typeof(M),typeof(SEn)}(M, SEn) +end + +function Base.show(io::IO, A::RotationTranslationAction) + return print(io, "RotationTranslationAction($(A.manifold), $(A.SEn), $(direction(A)))") +end + +""" + RotationTranslationActionOnVector{TAD,𝔽,TE,TSE} + +Alias for [`RotationTranslationAction`](@ref) where the manifold `M` is [`Euclidean`](@ref) +or [`TranslationGroup`](@ref) with size of type `TE`, and [`SpecialEuclidean`](@ref) +group has size type `TSE`. +""" +const RotationTranslationActionOnVector{TAD,𝔽,TE,TSE} = RotationTranslationAction{ + TAD, + <:Union{Euclidean{TE,𝔽},TranslationGroup{TE,𝔽}}, + SpecialEuclidean{TSE}, +} where {TAD<:ActionDirection,𝔽,TE,TSE} + +base_group(A::RotationTranslationAction) = A.SEn + +group_manifold(A::RotationTranslationAction) = A.manifold + +function switch_direction(A::RotationTranslationAction{TAD}) where {TAD<:ActionDirection} + return RotationTranslationAction(A.manifold, A.SEn, switch_direction(TAD())) +end + +""" + apply(::RotationTranslationActionOnVector{LeftAction}, a::ArrayPartition, p) + +Rotate point `p` by `a.x[2]` and translate it by `a.x[1]`. +""" +function apply(::RotationTranslationActionOnVector{LeftAction}, a::ArrayPartition, p) + return a.x[2] * p + a.x[1] +end +function apply( + ::RotationTranslationActionOnVector{LeftAction}, + a::SpecialEuclideanIdentity, + p, +) + return p +end +""" + apply(::RotationTranslationActionOnVector{RightAction}, a::ArrayPartition, p) + +Translate point `p` by `-a.x[1]` and rotate it by inverse of `a.x[2]`. +""" +function apply(::RotationTranslationActionOnVector{RightAction}, a::ArrayPartition, p) + return a.x[2] \ (p - a.x[1]) +end +function apply( + ::RotationTranslationActionOnVector{RightAction}, + a::SpecialEuclideanIdentity, + p, +) + return p +end + +function apply!(::RotationTranslationActionOnVector{LeftAction}, q, a::ArrayPartition, p) + mul!(q, a.x[2], p) + q .+= a.x[1] + return q +end +function apply!( + ::RotationTranslationActionOnVector{LeftAction}, + q, + a::SpecialEuclideanIdentity, + p, +) + copyto!(q, p) + return q +end + +""" + inverse_apply(::RotationTranslationActionOnVector{LeftAction}, a::ArrayPartition, p) + +Translate point `p` by `-a.x[1]` and rotate it by inverse of `a.x[2]`. +""" +function inverse_apply( + ::RotationTranslationActionOnVector{LeftAction}, + a::ArrayPartition, + p, +) + return a.x[2] \ (p - a.x[1]) +end +""" + inverse_apply(::RotationTranslationActionOnVector{RightAction}, a::ArrayPartition, p) + +Rotate point `p` by `a.x[2]` and translate it by `a.x[1]`. +""" +function inverse_apply( + ::RotationTranslationActionOnVector{RightAction}, + a::ArrayPartition, + p, +) + return a.x[2] * p + a.x[1] +end + +""" + apply_diff( + ::RotationTranslationActionOnVector{LeftAction}, + a::ArrayPartition, + p, + X, + ) + +Compute differential of `apply` on left [`RotationTranslationActionOnVector`](@ref), +with respect to `p`, i.e. left-multiply vector `X` tangent at `p` by `a.x[2]`. +""" +function apply_diff( + ::RotationTranslationActionOnVector{LeftAction}, + a::ArrayPartition, + p, + X, +) + return a.x[2] * X +end +function apply_diff( + ::RotationTranslationActionOnVector{LeftAction}, + ::SpecialEuclideanIdentity, + p, + X, +) + return X +end +""" + apply_diff( + ::RotationTranslationActionOnVector{RightAction}, + a::ArrayPartition, + p, + X, + ) + +Compute differential of `apply` on right [`RotationTranslationActionOnVector`](@ref), +with respect to `p`, i.e. left-divide vector `X` tangent at `p` by `a.x[2]`. +""" +function apply_diff( + ::RotationTranslationActionOnVector{RightAction}, + a::ArrayPartition, + p, + X, +) + return a.x[2] \ X +end +function apply_diff( + ::RotationTranslationActionOnVector{RightAction}, + a::SpecialEuclideanIdentity, + p, + X, +) + return X +end + +function apply_diff!( + ::RotationTranslationActionOnVector{LeftAction}, + Y, + a::ArrayPartition, + p, + X, +) + return mul!(Y, a.x[2], X) +end +function apply_diff!( + ::RotationTranslationActionOnVector{LeftAction}, + Y, + a::SpecialEuclideanIdentity, + p, + X, +) + return copyto!(Y, X) +end +function apply_diff!( + ::RotationTranslationActionOnVector{RightAction}, + Y, + a::ArrayPartition, + p, + X, +) + Y .= a.x[2] \ X + return Y +end +function apply_diff!( + ::RotationTranslationActionOnVector{RightAction}, + Y, + a::SpecialEuclideanIdentity, + p, + X, +) + return copyto!(Y, X) +end + +""" + apply_diff_group( + ::RotationTranslationActionOnVector{LeftAction}, + ::SpecialEuclideanIdentity, + X, + p, + ) + +Compute differential of `apply` on left [`RotationTranslationActionOnVector`](@ref), +with respect to `a` at identity, i.e. left-multiply point `p` by `X.x[2]`. +""" +function apply_diff_group( + ::RotationTranslationActionOnVector{LeftAction}, + ::SpecialEuclideanIdentity, + X, + p, +) + return X.x[2] * p +end + +function apply_diff_group!( + ::RotationTranslationActionOnVector{LeftAction}, + Y, + ::SpecialEuclideanIdentity, + X::ArrayPartition, + p, +) + Y .= X.x[2] * p + return Y +end + +function inverse_apply_diff( + ::RotationTranslationActionOnVector{LeftAction}, + a::ArrayPartition, + p, + X, +) + return a.x[2] \ X +end +function inverse_apply_diff( + ::RotationTranslationActionOnVector{RightAction}, + a::ArrayPartition, + p, + X, +) + return a.x[2] * X +end + +### + +@doc raw""" + ColumnwiseSpecialEuclideanAction{ + TM<:AbstractManifold, + TSE<:SpecialEuclidean, + TAD<:ActionDirection, + } <: AbstractGroupAction{TAD} + +Action of the special Euclidean group [`SpecialEuclidean`](@ref) +of type `SE` columns of points on a matrix manifold `M`. + +# Constructor + + ColumnwiseSpecialEuclideanAction( + M::AbstractManifold, + SE::SpecialEuclidean, + AD::ActionDirection = LeftAction(), + ) +""" +struct ColumnwiseSpecialEuclideanAction{ + TAD<:ActionDirection, + TM<:AbstractManifold, + TSE<:SpecialEuclidean, +} <: AbstractGroupAction{TAD} + manifold::TM + SE::TSE +end + +function ColumnwiseSpecialEuclideanAction( + M::AbstractManifold, + SE::SpecialEuclidean, + ::TAD=LeftAction(), +) where {TAD<:ActionDirection} + return ColumnwiseSpecialEuclideanAction{TAD,typeof(M),typeof(SE)}(M, SE) +end + +const LeftColumnwiseSpecialEuclideanAction{TM<:AbstractManifold,TSE<:SpecialEuclidean} = + ColumnwiseSpecialEuclideanAction{LeftAction,TM,TSE} + +function apply(::LeftColumnwiseSpecialEuclideanAction, a::ArrayPartition, p) + return a.x[2] * p .+ a.x[1] +end +function apply(::LeftColumnwiseSpecialEuclideanAction, ::SpecialEuclideanIdentity, p) + return p +end + +function apply!(::LeftColumnwiseSpecialEuclideanAction, q, a::ArrayPartition, p) + map((qrow, prow) -> mul!(qrow, a.x[2], prow), eachcol(q), eachcol(p)) + q .+= a.x[1] + return q +end +function apply!(::LeftColumnwiseSpecialEuclideanAction, q, a::SpecialEuclideanIdentity, p) + copyto!(q, p) + return q +end + +base_group(A::LeftColumnwiseSpecialEuclideanAction) = A.SE + +group_manifold(A::LeftColumnwiseSpecialEuclideanAction) = A.manifold + +function inverse_apply(::LeftColumnwiseSpecialEuclideanAction, a::ArrayPartition, p) + return a.x[2] \ (p .- a.x[1]) +end + +@doc raw""" + optimal_alignment(A::LeftColumnwiseSpecialEuclideanAction, p, q) + +Compute optimal alignment of `p` to `q` under the forward left [`ColumnwiseSpecialEuclideanAction`](@ref). +The algorithm, in sequence, computes optimal translation and optimal rotation. +""" +function optimal_alignment( + A::LeftColumnwiseSpecialEuclideanAction{<:AbstractManifold,<:SpecialEuclidean}, + p, + q, +) + N = _get_parameter(A.SE) + tr_opt = mean(q; dims=1) - mean(p; dims=1) + p_moved = p .+ tr_opt + + Ostar = optimal_alignment( + ColumnwiseMultiplicationAction(A.manifold, SpecialOrthogonal(N)), + p_moved, + q, + ) + return ArrayPartition(tr_opt, Ostar) +end diff --git a/src/groups/semidirect_product_group.jl b/src/groups/semidirect_product_group.jl index f144bc7ebb..5d850fed4e 100644 --- a/src/groups/semidirect_product_group.jl +++ b/src/groups/semidirect_product_group.jl @@ -249,7 +249,7 @@ function isapprox( end Base.@propagate_inbounds function Base.getindex( - p::Union{ProductRepr,ArrayPartition}, + p::ArrayPartition, M::SemidirectProductGroup, i::Union{Integer,Colon,AbstractVector,Val}, ) @@ -257,7 +257,7 @@ Base.@propagate_inbounds function Base.getindex( end Base.@propagate_inbounds function Base.setindex!( - q::Union{ProductRepr,ArrayPartition}, + q::ArrayPartition, p, M::SemidirectProductGroup, i::Union{Integer,Colon,AbstractVector,Val}, diff --git a/src/groups/special_euclidean.jl b/src/groups/special_euclidean.jl index a6c32ec4a9..337ffa424e 100644 --- a/src/groups/special_euclidean.jl +++ b/src/groups/special_euclidean.jl @@ -25,34 +25,53 @@ $\mathrm{T}(n) × \mathrm{SO}(n)$. For group-specific functions, they may also b represented as affine matrices with size `(n + 1, n + 1)` (see [`affine_matrix`](@ref)), for which the group operation is [`MultiplicationOperation`](@ref). """ -const SpecialEuclidean{N} = SemidirectProductGroup{ +const SpecialEuclidean{T} = SemidirectProductGroup{ ℝ, - TranslationGroup{Tuple{N},ℝ}, - SpecialOrthogonal{N}, - RotationAction{TranslationGroup{Tuple{N},ℝ},SpecialOrthogonal{N},LeftForwardAction}, + TranslationGroup{T,ℝ}, + SpecialOrthogonal{T}, + RotationAction{LeftAction,TranslationGroup{T,ℝ},SpecialOrthogonal{T}}, } const SpecialEuclideanManifold{N} = - ProductManifold{ℝ,Tuple{TranslationGroup{Tuple{N},ℝ},SpecialOrthogonal{N}}} + ProductManifold{ℝ,Tuple{TranslationGroup{N,ℝ},SpecialOrthogonal{N}}} -function SpecialEuclidean(n) - Tn = TranslationGroup(n) - SOn = SpecialOrthogonal(n) +function SpecialEuclidean(n; parameter::Symbol=:type) + Tn = TranslationGroup(n; parameter=parameter) + SOn = SpecialOrthogonal(n; parameter=parameter) A = RotationAction(Tn, SOn) return SemidirectProductGroup(Tn, SOn, A) end const SpecialEuclideanOperation{N} = SemidirectProductOperation{ - RotationAction{TranslationGroup{Tuple{N},ℝ},SpecialOrthogonal{N},LeftForwardAction}, + RotationAction{LeftAction,TranslationGroup{N,ℝ},SpecialOrthogonal{N}}, } const SpecialEuclideanIdentity{N} = Identity{SpecialEuclideanOperation{N}} -Base.show(io::IO, ::SpecialEuclidean{n}) where {n} = print(io, "SpecialEuclidean($(n))") +function Base.show(io::IO, ::SpecialEuclidean{TypeParameter{Tuple{n}}}) where {n} + return print(io, "SpecialEuclidean($(n))") +end +function Base.show(io::IO, G::SpecialEuclidean{Tuple{Int}}) + n = _get_parameter(G) + return print(io, "SpecialEuclidean($(n); parameter=:field)") +end @inline function active_traits(f, M::SpecialEuclidean, args...) return merge_traits(IsGroupManifold(M.op), IsExplicitDecorator()) end +""" + _get_parameter(M::AbstractManifold) + +Similar to `get_parameter` but it can be specialized for manifolds without breaking +manifolds being parametrized by other manifolds. +""" +_get_parameter(::AbstractManifold) + +_get_parameter(::SpecialEuclidean{TypeParameter{Tuple{N}}}) where {N} = N +_get_parameter(M::SpecialEuclidean{Tuple{Int}}) = _get_parameter(M.manifold) +_get_parameter(::SpecialEuclideanManifold{TypeParameter{Tuple{N}}}) where {N} = N +_get_parameter(M::SpecialEuclideanManifold{Tuple{Int}}) = manifold_dimension(M.manifolds[1]) + Base.@propagate_inbounds function Base.getindex( p::AbstractMatrix, M::Union{SpecialEuclidean,SpecialEuclideanManifold}, @@ -72,24 +91,27 @@ Base.@propagate_inbounds function Base.setindex!( end Base.@propagate_inbounds function submanifold_component( - ::Union{SpecialEuclidean{n},SpecialEuclideanManifold{n}}, + G::Union{SpecialEuclidean,SpecialEuclideanManifold}, p::AbstractMatrix, ::Val{1}, -) where {n} +) + n = _get_parameter(G) return view(p, 1:n, n + 1) end Base.@propagate_inbounds function submanifold_component( - ::Union{SpecialEuclidean{n},SpecialEuclideanManifold{n}}, + G::Union{SpecialEuclidean,SpecialEuclideanManifold}, p::AbstractMatrix, ::Val{2}, -) where {n} +) + n = _get_parameter(G) return view(p, 1:n, 1:n) end function submanifold_components( - G::Union{SpecialEuclidean{n},SpecialEuclideanManifold{n}}, + G::Union{SpecialEuclidean,SpecialEuclideanManifold}, p::AbstractMatrix, -) where {n} +) + n = _get_parameter(G) @assert size(p) == (n + 1, n + 1) @inbounds t = submanifold_component(G, p, Val(1)) @inbounds R = submanifold_component(G, p, Val(2)) @@ -97,9 +119,10 @@ function submanifold_components( end Base.@propagate_inbounds function _padpoint!( - ::Union{SpecialEuclidean{n},SpecialEuclideanManifold{n}}, + G::Union{SpecialEuclidean,SpecialEuclideanManifold}, q::AbstractMatrix, -) where {n} +) + n = _get_parameter(G) for i in 1:n q[n + 1, i] = 0 end @@ -108,9 +131,10 @@ Base.@propagate_inbounds function _padpoint!( end Base.@propagate_inbounds function _padvector!( - ::Union{SpecialEuclidean{n},SpecialEuclideanManifold{n}}, + G::Union{SpecialEuclidean,SpecialEuclideanManifold}, X::AbstractMatrix, -) where {n} +) + n = _get_parameter(G) for i in 1:(n + 1) X[n + 1, i] = 0 end @@ -118,7 +142,7 @@ Base.@propagate_inbounds function _padvector!( end @doc raw""" - adjoint_action(::SpecialEuclidean{3}, p, fX::TFVector{<:Any,VeeOrthogonalBasis{ℝ}}) + adjoint_action(::SpecialEuclidean{TypeParameter{Tuple{3}}}, p, fX::TFVector{<:Any,VeeOrthogonalBasis{ℝ}}) Adjoint action of the [`SpecialEuclidean`](@ref) group on the vector with coefficients `fX` tangent at point `p`. @@ -128,7 +152,11 @@ The formula for the coefficients reads ``t×(R⋅ω) + R⋅r`` for the translati matrix part of `p`, `r` is the translation part of `fX` and `ω` is the rotation part of `fX`, ``×`` is the cross product and ``⋅`` is the matrix product. """ -function adjoint_action(::SpecialEuclidean{3}, p, fX::TFVector{<:Any,VeeOrthogonalBasis{ℝ}}) +function adjoint_action( + ::SpecialEuclidean{TypeParameter{Tuple{3}}}, + p, + fX::TFVector{<:Any,VeeOrthogonalBasis{ℝ}}, +) t, R = submanifold_components(p) r = fX.data[SA[1, 2, 3]] ω = fX.data[SA[4, 5, 6]] @@ -156,21 +184,32 @@ It is an isometric embedding and group homomorphism [RicoMartinez:1988](@cite). See also [`screw_matrix`](@ref) for matrix representations of the Lie algebra. """ -function affine_matrix(G::SpecialEuclidean{n}, p) where {n} +function affine_matrix(G::SpecialEuclidean, p) pis = submanifold_components(G, p) pmat = allocate_result(G, affine_matrix, pis...) map(copyto!, submanifold_components(G, pmat), pis) @inbounds _padpoint!(G, pmat) return pmat end -affine_matrix(::SpecialEuclidean{n}, p::AbstractMatrix) where {n} = p -function affine_matrix(::SpecialEuclidean{n}, ::SpecialEuclideanIdentity{n}) where {n} +affine_matrix(::SpecialEuclidean, p::AbstractMatrix) = p +function affine_matrix( + ::SpecialEuclidean{TypeParameter{Tuple{n}}}, + ::SpecialEuclideanIdentity{TypeParameter{Tuple{n}}}, +) where {n} s = maybesize(Size(n, n)) s isa Size && return SDiagonal{n,Float64}(I) return Diagonal{Float64}(I, n) end +function affine_matrix( + G::SpecialEuclidean{Tuple{Int}}, + ::SpecialEuclideanIdentity{Tuple{Int}}, +) + n = _get_parameter(G) + return Diagonal{Float64}(I, n) +end -function check_point(G::SpecialEuclideanManifold{n}, p::AbstractMatrix; kwargs...) where {n} +function check_point(G::SpecialEuclideanManifold, p::AbstractMatrix; kwargs...) + n = _get_parameter(G) errs = DomainError[] # homogeneous if !isapprox(p[end, :], [zeros(size(p, 2) - 1)..., 1]; kwargs...) @@ -194,24 +233,27 @@ function check_point(G::SpecialEuclideanManifold{n}, p::AbstractMatrix; kwargs.. return length(errs) == 0 ? nothing : first(errs) end -function check_size(G::SpecialEuclideanManifold{n}, p::AbstractMatrix; kwargs...) where {n} +function check_size(G::SpecialEuclideanManifold, p::AbstractMatrix; kwargs...) + n = _get_parameter(G) return check_size(Euclidean(n + 1, n + 1), p) end function check_size( - G::SpecialEuclideanManifold{n}, + G::SpecialEuclideanManifold, p::AbstractMatrix, X::AbstractMatrix; kwargs..., -) where {n} +) + n = _get_parameter(G) return check_size(Euclidean(n + 1, n + 1), X) end function check_vector( - G::SpecialEuclideanManifold{n}, + G::SpecialEuclideanManifold, p::AbstractMatrix, X::AbstractMatrix; kwargs..., -) where {n} +) + n = _get_parameter(G) errs = DomainError[] # homogeneous if !isapprox(X[end, :], zeros(size(X, 2)); kwargs...) @@ -252,19 +294,21 @@ a homomorphic embedding (see [`SpecialEuclideanInGeneralLinear`](@ref) for a hom See also [`affine_matrix`](@ref) for matrix representations of the Lie group. """ -function screw_matrix(G::SpecialEuclidean{n}, X) where {n} +function screw_matrix(G::SpecialEuclidean, X) Xis = submanifold_components(G, X) Xmat = allocate_result(G, screw_matrix, Xis...) map(copyto!, submanifold_components(G, Xmat), Xis) @inbounds _padvector!(G, Xmat) return Xmat end -screw_matrix(::SpecialEuclidean{n}, X::AbstractMatrix) where {n} = X +screw_matrix(::SpecialEuclidean, X::AbstractMatrix) = X -function allocate_result(::SpecialEuclidean{n}, ::typeof(affine_matrix), p...) where {n} +function allocate_result(G::SpecialEuclidean, ::typeof(affine_matrix), p...) + n = _get_parameter(G) return allocate(p[1], Size(n + 1, n + 1)) end -function allocate_result(::SpecialEuclidean{n}, ::typeof(screw_matrix), X...) where {n} +function allocate_result(G::SpecialEuclidean, ::typeof(screw_matrix), X...) + n = _get_parameter(G) return allocate(X[1], Size(n + 1, n + 1)) end @@ -321,7 +365,7 @@ exponential (see [`exp_lie`](@ref)). exp_lie(::SpecialEuclidean, ::Any) @doc raw""" - exp_lie(G::SpecialEuclidean{2}, X) + exp_lie(G::SpecialEuclidean{TypeParameter{Tuple{2}}}, X) Compute the group exponential of $X = (b, Ω) ∈ 𝔰𝔢(2)$, where $b ∈ 𝔱(2)$ and $Ω ∈ 𝔰𝔬(2)$: @@ -338,10 +382,10 @@ U(θ) = \frac{\sin θ}{θ} I_2 + \frac{1 - \cos θ}{θ^2} Ω, and $θ = \frac{1}{\sqrt{2}} \lVert Ω \rVert_e$ (see [`norm`](@ref norm(M::Rotations, p, X))) is the angle of the rotation. """ -exp_lie(::SpecialEuclidean{2}, ::Any) +exp_lie(::SpecialEuclidean{TypeParameter{Tuple{2}}}, ::Any) @doc raw""" - exp_lie(G::SpecialEuclidean{3}, X) + exp_lie(G::SpecialEuclidean{TypeParameter{Tuple{3}}}, X) Compute the group exponential of $X = (b, Ω) ∈ 𝔰𝔢(3)$, where $b ∈ 𝔱(3)$ and $Ω ∈ 𝔰𝔬(3)$: @@ -358,7 +402,7 @@ U(θ) = I_3 + \frac{1 - \cos θ}{θ^2} Ω + \frac{θ - \sin θ}{θ^3} Ω^2, and $θ = \frac{1}{\sqrt{2}} \lVert Ω \rVert_e$ (see [`norm`](@ref norm(M::Rotations, p, X))) is the angle of the rotation. """ -exp_lie(::SpecialEuclidean{3}, ::Any) +exp_lie(::SpecialEuclidean{TypeParameter{Tuple{3}}}, ::Any) function exp_lie!(G::SpecialEuclidean, q, X) Xmat = screw_matrix(G, X) @@ -367,7 +411,7 @@ function exp_lie!(G::SpecialEuclidean, q, X) _padpoint!(G, q) return q end -function exp_lie!(G::SpecialEuclidean{2}, q, X) +function exp_lie!(G::SpecialEuclidean{TypeParameter{Tuple{2}}}, q, X) SO2 = submanifold(G, 2) b, Ω = submanifold_components(G, X) t, R = submanifold_components(G, q) @@ -396,7 +440,7 @@ function exp_lie!(G::SpecialEuclidean{2}, q, X) end return q end -function exp_lie!(G::SpecialEuclidean{3}, q, X) +function exp_lie!(G::SpecialEuclidean{TypeParameter{Tuple{3}}}, q, X) SO3 = submanifold(G, 2) b, Ω = submanifold_components(G, X) t, R = submanifold_components(G, q) @@ -425,7 +469,7 @@ function exp_lie!(G::SpecialEuclidean{3}, q, X) end @doc raw""" - log_lie(G::SpecialEuclidean{n}, p) where {n} + log_lie(G::SpecialEuclidean, p) Compute the group logarithm of $p = (t, R) ∈ \mathrm{SE}(n)$, where $t ∈ \mathrm{T}(n)$ and $R ∈ \mathrm{SO}(n)$: @@ -442,7 +486,7 @@ In the [`affine_matrix`](@ref) representation, the group logarithm is the matrix log_lie(::SpecialEuclidean, ::Any) @doc raw""" - log_lie(G::SpecialEuclidean{2}, p) + log_lie(G::SpecialEuclidean{TypeParameter{Tuple{2}}}, p) Compute the group logarithm of $p = (t, R) ∈ \mathrm{SE}(2)$, where $t ∈ \mathrm{T}(2)$ and $R ∈ \mathrm{SO}(2)$: @@ -460,10 +504,10 @@ U(θ) = \frac{\sin θ}{θ} I_2 + \frac{1 - \cos θ}{θ^2} Ω, and $θ = \frac{1}{\sqrt{2}} \lVert Ω \rVert_e$ (see [`norm`](@ref norm(M::Rotations, p, X))) is the angle of the rotation. """ -log_lie(::SpecialEuclidean{2}, ::Any) +log_lie(::SpecialEuclidean{TypeParameter{Tuple{2}}}, ::Any) @doc raw""" - log_lie(G::SpecialEuclidean{3}, p) + log_lie(G::SpecialEuclidean{TypeParameter{Tuple{3}}}, p) Compute the group logarithm of $p = (t, R) ∈ \mathrm{SE}(3)$, where $t ∈ \mathrm{T}(3)$ and $R ∈ \mathrm{SO}(3)$: @@ -481,7 +525,7 @@ U(θ) = I_3 + \frac{1 - \cos θ}{θ^2} Ω + \frac{θ - \sin θ}{θ^3} Ω^2, and $θ = \frac{1}{\sqrt{2}} \lVert Ω \rVert_e$ (see [`norm`](@ref norm(M::Rotations, p, X))) is the angle of the rotation. """ -log_lie(::SpecialEuclidean{3}, ::Any) +log_lie(::SpecialEuclidean{TypeParameter{Tuple{3}}}, ::Any) function _log_lie!(G::SpecialEuclidean, X, q) qmat = affine_matrix(G, q) @@ -490,7 +534,7 @@ function _log_lie!(G::SpecialEuclidean, X, q) _padvector!(G, X) return X end -function _log_lie!(G::SpecialEuclidean{2}, X, q) +function _log_lie!(G::SpecialEuclidean{TypeParameter{Tuple{2}}}, X, q) SO2 = submanifold(G, 2) b, Ω = submanifold_components(G, X) t, R = submanifold_components(G, q) @@ -508,7 +552,7 @@ function _log_lie!(G::SpecialEuclidean{2}, X, q) end return X end -function _log_lie!(G::SpecialEuclidean{3}, X, q) +function _log_lie!(G::SpecialEuclidean{TypeParameter{Tuple{3}}}, X, q) b, Ω = submanifold_components(G, X) t, R = submanifold_components(G, q) @assert size(Ω) == (3, 3) @@ -548,20 +592,14 @@ function log!(M::SpecialEuclideanManifold, X::AbstractMatrix, p, q) end """ - lie_bracket(G::SpecialEuclidean, X::ProductRepr, Y::ProductRepr) lie_bracket(G::SpecialEuclidean, X::ArrayPartition, Y::ArrayPartition) lie_bracket(G::SpecialEuclidean, X::AbstractMatrix, Y::AbstractMatrix) Calculate the Lie bracket between elements `X` and `Y` of the special Euclidean Lie algebra. For the matrix representation (which can be obtained using [`screw_matrix`](@ref)) -the formula is ``[X, Y] = XY-YX``, while in the [`ProductRepr`](@ref) representation the +the formula is ``[X, Y] = XY-YX``, while in the `ArrayPartition` representation the formula reads ``[X, Y] = [(t_1, R_1), (t_2, R_2)] = (R_1 t_2 - R_2 t_1, R_1 R_2 - R_2 R_1)``. """ -function lie_bracket(G::SpecialEuclidean, X::ProductRepr, Y::ProductRepr) - nX, hX = submanifold_components(G, X) - nY, hY = submanifold_components(G, Y) - return ProductRepr(hX * nY - hY * nX, lie_bracket(G.manifold.manifolds[2], hX, hY)) -end function lie_bracket(G::SpecialEuclidean, X::ArrayPartition, Y::ArrayPartition) nX, hX = submanifold_components(G, X) nY, hY = submanifold_components(G, Y) @@ -725,7 +763,11 @@ function get_coordinates( get_coordinates(M2.manifold, p.x[2], X.x[2], basis), ) end -function hat(M::SpecialEuclidean{2}, p::ArrayPartition, c::AbstractVector) +function hat( + M::SpecialEuclidean{TypeParameter{Tuple{2}}}, + p::ArrayPartition, + c::AbstractVector, +) M1, M2 = M.manifold.manifolds return ArrayPartition( get_vector_orthogonal(M1.manifold, p.x[1], c[SOneTo(2)], ℝ), @@ -733,7 +775,7 @@ function hat(M::SpecialEuclidean{2}, p::ArrayPartition, c::AbstractVector) ) end function get_vector( - M::SpecialEuclidean{2}, + M::SpecialEuclidean{TypeParameter{Tuple{2}}}, p::ArrayPartition, c::AbstractVector, basis::DefaultOrthogonalBasis, @@ -744,7 +786,11 @@ function get_vector( ) end -function hat(M::SpecialEuclidean{3}, p::ArrayPartition, c::AbstractVector) +function hat( + M::SpecialEuclidean{TypeParameter{Tuple{3}}}, + p::ArrayPartition, + c::AbstractVector, +) M1, M2 = M.manifold.manifolds return ArrayPartition( get_vector_orthogonal(M1.manifold, p.x[1], c[SOneTo(3)], ℝ), @@ -752,7 +798,7 @@ function hat(M::SpecialEuclidean{3}, p::ArrayPartition, c::AbstractVector) ) end function get_vector( - M::SpecialEuclidean{3}, + M::SpecialEuclidean{TypeParameter{Tuple{3}}}, p::ArrayPartition, c::AbstractVector, basis::DefaultOrthogonalBasis, diff --git a/src/groups/special_linear.jl b/src/groups/special_linear.jl index 7d2f848d99..7ffb5fde1e 100644 --- a/src/groups/special_linear.jl +++ b/src/groups/special_linear.jl @@ -1,5 +1,5 @@ @doc raw""" - SpecialLinear{n,𝔽} <: AbstractDecoratorManifold + SpecialLinear{T,𝔽} <: AbstractDecoratorManifold The special linear group ``\mathrm{SL}(n,𝔽)`` that is, the group of all invertible matrices with unit determinant in ``𝔽^{n×n}``. @@ -15,9 +15,14 @@ metric used for [`GeneralLinear(n, 𝔽)`](@ref). The resulting geodesic on an element of ``𝔰𝔩(n, 𝔽)`` is a closed subgroup of ``\mathrm{SL}(n,𝔽)``. As a result, most metric functions forward to [`GeneralLinear`](@ref). """ -struct SpecialLinear{n,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct SpecialLinear{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -SpecialLinear(n, 𝔽::AbstractNumbers=ℝ) = SpecialLinear{n,𝔽}() +function SpecialLinear(n, 𝔽::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return SpecialLinear{typeof(size),𝔽}(size) +end @inline function active_traits(f, ::SpecialLinear, args...) return merge_traits( @@ -28,11 +33,11 @@ SpecialLinear(n, 𝔽::AbstractNumbers=ℝ) = SpecialLinear{n,𝔽}() ) end -function allocation_promotion_function(::SpecialLinear{n,ℂ}, f, args::Tuple) where {n} +function allocation_promotion_function(::SpecialLinear{<:Any,ℂ}, f, args::Tuple) return complex end -function check_point(G::SpecialLinear{n,𝔽}, p; kwargs...) where {n,𝔽} +function check_point(G::SpecialLinear, p; kwargs...) detp = det(p) if !isapprox(detp, 1; kwargs...) return DomainError( @@ -59,12 +64,18 @@ end embed(::SpecialLinear, p) = p embed(::SpecialLinear, p, X) = X -get_embedding(::SpecialLinear{n,𝔽}) where {n,𝔽} = GeneralLinear(n, 𝔽) +function get_embedding(::SpecialLinear{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return GeneralLinear(n, 𝔽) +end +function get_embedding(M::SpecialLinear{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return GeneralLinear(n, 𝔽; parameter=:field) +end inverse_translate_diff(::SpecialLinear, p, q, X, ::LeftForwardAction) = X inverse_translate_diff(::SpecialLinear, p, q, X, ::RightBackwardAction) = p * X / p -function inverse_translate_diff!(G::SpecialLinear, Y, p, q, X, conv::ActionDirection) +function inverse_translate_diff!(G::SpecialLinear, Y, p, q, X, conv::ActionDirectionAndSide) return copyto!(Y, inverse_translate_diff(G, p, q, X, conv)) end @@ -95,7 +106,8 @@ D_{ij} = δ_{ij} \begin{cases} """ project(::SpecialLinear, p) -function project!(::SpecialLinear{n}, q, p) where {n} +function project!(M::SpecialLinear, q, p) + n = get_parameter(M.size)[1] detp = det(p) isapprox(detp, 1) && return copyto!(q, p) F = svd(p) @@ -119,18 +131,25 @@ where the last expression uses the tangent space representation as the Lie algeb """ project(::SpecialLinear, p, X) -function project!(G::SpecialLinear{n}, Y, p, X) where {n} +function project!(G::SpecialLinear, Y, p, X) + n = get_parameter(G.size)[1] inverse_translate_diff!(G, Y, p, p, X, LeftForwardAction()) Y[diagind(n, n)] .-= tr(Y) / n translate_diff!(G, Y, p, p, Y, LeftForwardAction()) return Y end -Base.show(io::IO, ::SpecialLinear{n,𝔽}) where {n,𝔽} = print(io, "SpecialLinear($n, $𝔽)") +function Base.show(io::IO, ::SpecialLinear{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return print(io, "SpecialLinear($n, $𝔽)") +end +function Base.show(io::IO, M::SpecialLinear{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return print(io, "SpecialLinear($n, $𝔽; parameter=:field)") +end translate_diff(::SpecialLinear, p, q, X, ::LeftForwardAction) = X translate_diff(::SpecialLinear, p, q, X, ::RightBackwardAction) = p \ X * p -function translate_diff!(G::SpecialLinear, Y, p, q, X, conv::ActionDirection) +function translate_diff!(G::SpecialLinear, Y, p, q, X, conv::ActionDirectionAndSide) return copyto!(Y, translate_diff(G, p, q, X, conv)) end diff --git a/src/groups/special_orthogonal.jl b/src/groups/special_orthogonal.jl index d49bffbde7..2baf90822f 100644 --- a/src/groups/special_orthogonal.jl +++ b/src/groups/special_orthogonal.jl @@ -8,9 +8,17 @@ Special orthogonal group ``\mathrm{SO}(n)`` represented by rotation matrices, se """ const SpecialOrthogonal{n} = GeneralUnitaryMultiplicationGroup{n,ℝ,DeterminantOneMatrices} -SpecialOrthogonal(n) = SpecialOrthogonal{n}(Rotations(n)) +function SpecialOrthogonal(n; parameter::Symbol=:type) + return GeneralUnitaryMultiplicationGroup(Rotations(n; parameter=parameter)) +end Base.inv(::SpecialOrthogonal, p) = transpose(p) Base.inv(::SpecialOrthogonal, e::Identity{MultiplicationOperation}) = e -Base.show(io::IO, ::SpecialOrthogonal{n}) where {n} = print(io, "SpecialOrthogonal($(n))") +function Base.show(io::IO, ::SpecialOrthogonal{TypeParameter{Tuple{n}}}) where {n} + return print(io, "SpecialOrthogonal($(n))") +end +function Base.show(io::IO, M::SpecialOrthogonal{Tuple{Int}}) + n = get_parameter(M.manifold.size)[1] + return print(io, "SpecialOrthogonal($(n); parameter=:field)") +end diff --git a/src/groups/special_unitary.jl b/src/groups/special_unitary.jl index 7e69e22de8..be2d3af60d 100644 --- a/src/groups/special_unitary.jl +++ b/src/groups/special_unitary.jl @@ -18,9 +18,13 @@ or in other words we represent the tangent spaces employing the Lie algebra ``\m Generate the Lie group of ``n×n`` unitary matrices with determinant +1. """ -const SpecialUnitary{n} = GeneralUnitaryMultiplicationGroup{n,ℂ,DeterminantOneMatrices} +const SpecialUnitary{T} = GeneralUnitaryMultiplicationGroup{T,ℂ,DeterminantOneMatrices} -SpecialUnitary(n) = SpecialUnitary{n}(GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}()) +function SpecialUnitary(n::Int; parameter::Symbol=:type) + return GeneralUnitaryMultiplicationGroup( + GeneralUnitaryMatrices(n, ℂ, DeterminantOneMatrices; parameter=parameter), + ) +end @doc raw""" project(G::SpecialUnitary, p) @@ -50,7 +54,8 @@ function project(G::SpecialUnitary, p, X) return Y end -function project!(::SpecialUnitary{n}, q, p) where {n} +function project!(G::SpecialUnitary, q, p) + n = get_parameter(G.manifold.size)[1] F = svd(p) detUVt = det(F.U) * det(F.Vt) if !isreal(detUVt) || real(detUVt) < 0 @@ -63,7 +68,8 @@ function project!(::SpecialUnitary{n}, q, p) where {n} end return q end -function project!(G::SpecialUnitary{n}, Y, p, X) where {n} +function project!(G::SpecialUnitary, Y, p, X) + n = get_parameter(G.manifold.size)[1] inverse_translate_diff!(G, Y, p, p, X, LeftForwardAction()) project!(SkewHermitianMatrices(n, ℂ), Y, Y) Y[diagind(n, n)] .-= tr(Y) / n @@ -71,4 +77,10 @@ function project!(G::SpecialUnitary{n}, Y, p, X) where {n} return Y end -Base.show(io::IO, ::SpecialUnitary{n}) where {n} = print(io, "SpecialUnitary($(n))") +function Base.show(io::IO, ::SpecialUnitary{TypeParameter{Tuple{n}}}) where {n} + return print(io, "SpecialUnitary($(n))") +end +function Base.show(io::IO, G::SpecialUnitary{Tuple{Int}}) + n = get_parameter(G.manifold.size)[1] + return print(io, "SpecialUnitary($(n); parameter=:field)") +end diff --git a/src/groups/translation_action.jl b/src/groups/translation_action.jl index 53b493bee7..44daebc7c0 100644 --- a/src/groups/translation_action.jl +++ b/src/groups/translation_action.jl @@ -2,7 +2,7 @@ TranslationAction( M::AbstractManifold, Rn::TranslationGroup, - AD::ActionDirection = LeftForwardAction(), + AD::ActionDirection = LeftAction(), ) Space of actions of the [`TranslationGroup`](@ref) $\mathrm{T}(n)$ on a Euclidean-like @@ -10,7 +10,7 @@ manifold `M`. The left and right actions are equivalent. """ -struct TranslationAction{TM<:AbstractManifold,TRn<:TranslationGroup,TAD<:ActionDirection} <: +struct TranslationAction{TAD<:ActionDirection,TM<:AbstractManifold,TRn<:TranslationGroup} <: AbstractGroupAction{TAD} manifold::TM Rn::TRn @@ -19,9 +19,9 @@ end function TranslationAction( M::AbstractManifold, Rn::TranslationGroup, - ::TAD=LeftForwardAction(), + ::TAD=LeftAction(), ) where {TAD<:ActionDirection} - return TranslationAction{typeof(M),typeof(Rn),TAD}(M, Rn) + return TranslationAction{TAD,typeof(M),typeof(Rn)}(M, Rn) end function Base.show(io::IO, A::TranslationAction) @@ -32,11 +32,8 @@ base_group(A::TranslationAction) = A.Rn group_manifold(A::TranslationAction) = A.manifold -function switch_direction( - A::TranslationAction{TM,TSO,TAD}, - ::LeftRightSwitch=LeftRightSwitch(), -) where {TM<:AbstractManifold,TSO<:TranslationGroup,TAD<:ActionDirection} - return TranslationAction(A.manifold, A.Rn, switch_direction(TAD(), LeftRightSwitch())) +function switch_direction(A::TranslationAction{TAD}) where {TAD<:ActionDirection} + return TranslationAction(A.manifold, A.Rn, switch_direction(TAD())) end adjoint_apply_diff_group(::TranslationAction, a, X, p) = X @@ -48,7 +45,7 @@ end apply(::TranslationAction, a, p) = p + a apply!(::TranslationAction, q, a, p) = (q .= p .+ a) -function apply!(A::TranslationAction, q, e::Identity{AdditionOperation}, p) +function apply!(A::TranslationAction, q, ::Identity{AdditionOperation}, p) return copyto!(A.manifold, q, p) end @@ -65,17 +62,11 @@ function apply_diff!(A::TranslationAction, Y, a, p, X) return copyto!(A.manifold, Y, p, X) end -function apply_diff_group(::TranslationAction{N,F,LeftForwardAction}, a, X, p) where {N,F} +function apply_diff_group(::TranslationAction{LeftAction}, a, X, p) return X end -function apply_diff_group!( - A::TranslationAction{N,F,LeftForwardAction}, - Y, - a, - X, - p, -) where {N,F} +function apply_diff_group!(A::TranslationAction{LeftAction}, Y, a, X, p) copyto!(A.manifold, Y, p, X) return Y end diff --git a/src/groups/translation_group.jl b/src/groups/translation_group.jl index 28397606c5..cbc752acab 100644 --- a/src/groups/translation_group.jl +++ b/src/groups/translation_group.jl @@ -1,19 +1,20 @@ @doc raw""" - TranslationGroup{T<:Tuple,𝔽} <: GroupManifold{Euclidean{T,𝔽},AdditionOperation} + TranslationGroup{T,𝔽} <: GroupManifold{Euclidean{T,𝔽},AdditionOperation} Translation group $\mathrm{T}(n)$ represented by translation arrays. # Constructor - TranslationGroup(n₁,...,nᵢ; field = 𝔽) + TranslationGroup(n₁,...,nᵢ; field=𝔽, parameter::Symbol=:type) Generate the translation group on -$𝔽^{n₁,…,nᵢ}$ = `Euclidean(n₁,...,nᵢ; field = 𝔽)`, which is isomorphic to the group itself. +$𝔽^{n₁,…,nᵢ}$ = `Euclidean(n₁,...,nᵢ; field=𝔽)`, which is isomorphic to the group itself. """ -const TranslationGroup{T<:Tuple,𝔽} = GroupManifold{𝔽,Euclidean{T,𝔽},AdditionOperation} +const TranslationGroup{T,𝔽} = GroupManifold{𝔽,Euclidean{T,𝔽},AdditionOperation} -function TranslationGroup(n::Int...; field::AbstractNumbers=ℝ) - return TranslationGroup{Tuple{n...},field}( - Euclidean(n...; field=field), +function TranslationGroup(n::Int...; field::AbstractNumbers=ℝ, parameter::Symbol=:type) + size = wrap_type_parameter(parameter, n) + return TranslationGroup{typeof(size),field}( + Euclidean(n...; field=field, parameter=parameter), AdditionOperation(), ) end @@ -25,7 +26,6 @@ end else return merge_traits( IsGroupManifold(M.op), - HasBiinvariantMetric(), IsDefaultMetric(EuclideanMetric()), active_traits(f, M.manifold, args...), IsExplicitDecorator(), #pass to Euclidean by default/last fallback @@ -35,6 +35,10 @@ end exp!(::TranslationGroup, q, ::Identity{AdditionOperation}, X) = copyto!(q, X) +has_biinvariant_metric(::TranslationGroup) = true + +has_invariant_metric(::TranslationGroup, ::ActionDirectionAndSide) = true + identity_element!(::TranslationGroup, p) = fill!(p, 0) log(::TranslationGroup, ::Identity{AdditionOperation}, q) = q @@ -44,6 +48,11 @@ function log!(::TranslationGroup, X, p::Identity{AdditionOperation}, q) return X end -function Base.show(io::IO, ::TranslationGroup{N,𝔽}) where {N,𝔽} - return print(io, "TranslationGroup($(join(N.parameters, ", ")); field = $(𝔽))") +function Base.show(io::IO, M::TranslationGroup{N,𝔽}) where {N<:Tuple,𝔽} + size = get_parameter(M.manifold.size) + return print(io, "TranslationGroup($(join(size, ", ")); field=$(𝔽), parameter=:field)") +end +function Base.show(io::IO, M::TranslationGroup{N,𝔽}) where {N<:TypeParameter,𝔽} + size = get_parameter(M.manifold.size) + return print(io, "TranslationGroup($(join(size, ", ")); field=$(𝔽))") end diff --git a/src/groups/unitary.jl b/src/groups/unitary.jl index 127c6850da..d775f401ef 100644 --- a/src/groups/unitary.jl +++ b/src/groups/unitary.jl @@ -26,7 +26,9 @@ See also [`Orthogonal(n)`](@ref) for the real-valued case. """ const Unitary{n,𝔽} = GeneralUnitaryMultiplicationGroup{n,𝔽,AbsoluteDeterminantOneMatrices} -Unitary(n, 𝔽::AbstractNumbers=ℂ) = Unitary{n,𝔽}(UnitaryMatrices(n, 𝔽)) +function Unitary(n, 𝔽::AbstractNumbers=ℂ; parameter::Symbol=:type) + return GeneralUnitaryMultiplicationGroup(UnitaryMatrices(n, 𝔽; parameter=parameter)) +end @doc raw""" exp_lie(G::Unitary{2,ℂ}, X) @@ -39,18 +41,18 @@ Compute the group exponential map on the [`Unitary(2)`](@ref) group, which is where ``θ = \frac{1}{2} \sqrt{4\det(X) - \operatorname{tr}(X)^2}``. """ -exp_lie(::Unitary{2,ℂ}, X) +exp_lie(::Unitary{TypeParameter{Tuple{2}},ℂ}, X) -function exp_lie(::Unitary{1,ℍ}, X::Number) +function exp_lie(::Unitary{TypeParameter{Tuple{1}},ℍ}, X::Number) return exp(X) end -function exp_lie!(::Unitary{1}, q, X) +function exp_lie!(::Unitary{TypeParameter{Tuple{1}}}, q, X) q[] = exp(X[]) return q end -function exp_lie!(::Unitary{2,ℂ}, q, X) +function exp_lie!(::Unitary{TypeParameter{Tuple{2}},ℂ}, q, X) size(X) === (2, 2) && size(q) === (2, 2) || throw(DomainError()) @inbounds a, d = imag(X[1, 1]), imag(X[2, 2]) @inbounds b = (X[2, 1] - X[1, 2]') / 2 @@ -75,11 +77,11 @@ function exp_lie!(G::Unitary, q, X) return q end -function log_lie!(::Unitary{1}, X, p) +function log_lie!(::Unitary{TypeParameter{Tuple{1}}}, X, p) X[] = log(p[]) return X end -function log_lie!(::Unitary{1}, X::AbstractMatrix, p::AbstractMatrix) +function log_lie!(::Unitary{TypeParameter{Tuple{1}}}, X::AbstractMatrix, p::AbstractMatrix) X[] = log(p[]) return X end @@ -89,13 +91,25 @@ function log_lie!(G::Unitary, X, p) return X end -identity_element(::Unitary{1,ℍ}) = Quaternions.quat(1.0) +identity_element(::Unitary{TypeParameter{Tuple{1}},ℍ}) = Quaternions.quat(1.0) -function log_lie(::Unitary{1}, q::Number) +function log_lie(::Unitary{TypeParameter{Tuple{1}}}, q::Number) return log(q) end Base.inv(::Unitary, p) = adjoint(p) -show(io::IO, ::Unitary{n,ℂ}) where {n} = print(io, "Unitary($(n))") -show(io::IO, ::Unitary{n,ℍ}) where {n} = print(io, "Unitary($(n), ℍ)") +function Base.show(io::IO, ::Unitary{TypeParameter{Tuple{n}},ℂ}) where {n} + return print(io, "Unitary($(n))") +end +function Base.show(io::IO, M::Unitary{Tuple{Int},ℂ}) + n = get_parameter(M.manifold.size)[1] + return print(io, "Unitary($(n); parameter=:field)") +end +function Base.show(io::IO, ::Unitary{TypeParameter{Tuple{n}},ℍ}) where {n} + return print(io, "Unitary($(n), ℍ)") +end +function Base.show(io::IO, M::Unitary{Tuple{Int},ℍ}) + n = get_parameter(M.manifold.size)[1] + return print(io, "Unitary($(n), ℍ; parameter=:field)") +end diff --git a/src/groups/validation_group.jl b/src/groups/validation_group.jl index 98ec45915d..be45d09460 100644 --- a/src/groups/validation_group.jl +++ b/src/groups/validation_group.jl @@ -7,20 +7,20 @@ array_point(p) = ValidationMPoint(p) array_point(p::ValidationMPoint) = p function adjoint_action(M::ValidationManifold, p, X; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) eM = Identity(M.manifold) - is_vector(M, eM, X, true; kwargs...) + is_vector(M, eM, X; error=M.mode, kwargs...) Y = ValidationTVector(adjoint_action(M.manifold, array_value(p), array_value(X))) - is_vector(M, eM, Y, true; kwargs...) + is_vector(M, eM, Y; error=M.mode, kwargs...) return Y end function adjoint_action!(M::ValidationManifold, Y, p, X; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) eM = Identity(M.manifold) - is_vector(M, eM, X, true; kwargs...) + is_vector(M, eM, X; error=M.mode, kwargs...) adjoint_action!(M.manifold, array_value(Y), array_value(p), array_value(X)) - is_vector(M, eM, Y, true; kwargs...) + is_vector(M, eM, Y; error=M.mode, kwargs...) return Y end @@ -28,103 +28,109 @@ Identity(M::ValidationManifold) = array_point(Identity(M.manifold)) identity_element!(M::ValidationManifold, p) = identity_element!(M.manifold, array_value(p)) function Base.inv(M::ValidationManifold, p; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) q = array_point(inv(M.manifold, array_value(p))) - is_point(M, q, true; kwargs...) + is_point(M, q; error=M.mode, kwargs...) return q end function inv!(M::ValidationManifold, q, p; kwargs...) - is_point(M, p, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) inv!(M.manifold, array_value(q), array_value(p)) - is_point(M, q, true; kwargs...) + is_point(M, q; error=M.mode, kwargs...) return q end function lie_bracket(M::ValidationManifold, X, Y) eM = Identity(M.manifold) - is_vector(M, eM, X, true) - is_vector(M, eM, Y, true) + is_vector(M, eM, X; error=M.mode) + is_vector(M, eM, Y; error=M.mode) Z = ValidationTVector(lie_bracket(M.manifold, array_value(X), array_value(Y))) - is_vector(M, eM, Z, true) + is_vector(M, eM, Z; error=M.mode) return Z end function lie_bracket!(M::ValidationManifold, Z, X, Y) eM = Identity(M.manifold) - is_vector(M, eM, X, true) - is_vector(M, eM, Y, true) + is_vector(M, eM, X; error=M.mode) + is_vector(M, eM, Y; error=M.mode) lie_bracket!(M.manifold, array_value(Z), array_value(X), array_value(Y)) - is_vector(M, eM, Z, true) + is_vector(M, eM, Z; error=M.mode) return Z end function compose(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 = array_point(compose(M.manifold, array_value(p), array_value(q))) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end function compose(M::ValidationManifold, p::Identity, 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 = array_point(compose(M.manifold, p, array_value(q))) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end function compose(M::ValidationManifold, p, q::Identity; 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 = array_point(compose(M.manifold, array_value(p), q)) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end function compose!(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...) compose!(M.manifold, array_value(x), array_value(p), array_value(q)) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end function compose!(M::ValidationManifold, x, p::Identity, 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...) compose!(M.manifold, array_value(x), array_value(p), array_value(q)) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end function compose!(M::ValidationManifold, x, p, q::Identity; 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...) compose!(M.manifold, array_value(x), array_value(p), array_value(q)) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end -function translate(M::ValidationManifold, p, q, conv::ActionDirection; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) +function translate(M::ValidationManifold, p, q, conv::ActionDirectionAndSide; kwargs...) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) x = array_point(translate(M.manifold, array_value(p), array_value(q), conv)) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end -function translate!(M::ValidationManifold, x, p, q, conv::ActionDirection; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) +function translate!(M::ValidationManifold, x, p, q, conv::ActionDirectionAndSide; kwargs...) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) translate!(M.manifold, array_value(x), array_value(p), array_value(q), conv) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end -function inverse_translate(M::ValidationManifold, p, q, conv::ActionDirection; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) +function inverse_translate( + M::ValidationManifold, + p, + q, + conv::ActionDirectionAndSide; + kwargs..., +) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) x = array_point(inverse_translate(M.manifold, array_value(p), array_value(q), conv)) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end @@ -133,25 +139,32 @@ function inverse_translate!( x, p, q, - conv::ActionDirection; + conv::ActionDirectionAndSide; 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...) inverse_translate!(M.manifold, array_value(x), array_value(p), array_value(q), conv) - is_point(M, x, true; kwargs...) + is_point(M, x; error=M.mode, kwargs...) return x end -function translate_diff(M::ValidationManifold, p, q, X, conv::ActionDirection; kwargs...) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) - is_vector(M, q, X, true; kwargs...) +function translate_diff( + M::ValidationManifold, + p, + q, + X, + conv::ActionDirectionAndSide; + kwargs..., +) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) + is_vector(M, q, X; error=M.mode, kwargs...) Y = ValidationTVector( translate_diff(M.manifold, array_value(p), array_value(q), array_value(X), conv), ) pq = translate(M, p, q, conv) - is_vector(M, pq, Y, true; kwargs...) + is_vector(M, pq, Y; error=M.mode, kwargs...) return Y end @@ -161,12 +174,12 @@ function translate_diff!( p, q, X, - conv::ActionDirection; + conv::ActionDirectionAndSide; kwargs..., ) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) - is_vector(M, q, X, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) + is_vector(M, q, X; error=M.mode, kwargs...) translate_diff!( M.manifold, array_value(Y), @@ -176,7 +189,7 @@ function translate_diff!( conv, ) pq = translate(M, p, q, conv) - is_vector(M, pq, Y, true; kwargs...) + is_vector(M, pq, Y; error=M.mode, kwargs...) return Y end @@ -185,12 +198,12 @@ function inverse_translate_diff( p, q, X, - conv::ActionDirection; + conv::ActionDirectionAndSide; kwargs..., ) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) - is_vector(M, q, X, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) + is_vector(M, q, X; error=M.mode, kwargs...) Y = ValidationTVector( inverse_translate_diff( M.manifold, @@ -201,7 +214,7 @@ function inverse_translate_diff( ), ) pinvq = inverse_translate(M, p, q, conv) - is_vector(M, pinvq, Y, true; kwargs...) + is_vector(M, pinvq, Y; error=M.mode, kwargs...) return Y end @@ -211,12 +224,12 @@ function inverse_translate_diff!( p, q, X, - conv::ActionDirection; + conv::ActionDirectionAndSide; kwargs..., ) - is_point(M, p, true; kwargs...) - is_point(M, q, true; kwargs...) - is_vector(M, q, X, true; kwargs...) + is_point(M, p; error=M.mode, kwargs...) + is_point(M, q; error=M.mode, kwargs...) + is_vector(M, q, X; error=M.mode, kwargs...) inverse_translate_diff!( M.manifold, array_value(Y), @@ -226,34 +239,34 @@ function inverse_translate_diff!( conv, ) pinvq = inverse_translate(M, p, q, conv) - is_vector(M, pinvq, Y, true; kwargs...) + is_vector(M, pinvq, Y; error=M.mode, kwargs...) return Y end function exp_lie(M::ValidationManifold, X; kwargs...) - is_vector(M, Identity(M.manifold), array_value(X), true; kwargs...) + is_vector(M, Identity(M.manifold), array_value(X); error=M.mode, kwargs...) q = array_point(exp_lie(M.manifold, array_value(X))) - is_point(M, q, true; kwargs...) + is_point(M, q; error=M.mode, kwargs...) return q end function exp_lie!(M::ValidationManifold, q, X; kwargs...) - is_vector(M, Identity(M.manifold), array_value(X), true; kwargs...) + is_vector(M, Identity(M.manifold), array_value(X); error=M.mode, kwargs...) exp_lie!(M.manifold, array_value(q), array_value(X)) - is_point(M, q, true; kwargs...) + is_point(M, q; error=M.mode, kwargs...) return q end function log_lie(M::ValidationManifold, q; kwargs...) - is_point(M, q, true; kwargs...) + is_point(M, q; error=M.mode, kwargs...) X = ValidationTVector(log_lie(M.manifold, array_value(q))) - is_vector(M, Identity(M.manifold), array_value(X), true; kwargs...) + is_vector(M, Identity(M.manifold), array_value(X); error=M.mode, kwargs...) return X end function log_lie!(M::ValidationManifold, X, q; kwargs...) - is_point(M, q, true; kwargs...) + is_point(M, q; error=M.mode, kwargs...) log_lie!(M.manifold, array_value(X), array_value(q)) - is_vector(M, Identity(M.manifold), array_value(X), true; kwargs...) + is_vector(M, Identity(M.manifold), array_value(X); error=M.mode, kwargs...) return X end diff --git a/src/manifolds/CenteredMatrices.jl b/src/manifolds/CenteredMatrices.jl index 49c9342bd7..0856c49a52 100644 --- a/src/manifolds/CenteredMatrices.jl +++ b/src/manifolds/CenteredMatrices.jl @@ -1,5 +1,5 @@ @doc raw""" - CenteredMatrices{m,n,𝔽} <: AbstractDecoratorManifold{𝔽} + CenteredMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} The manifold of $m × n$ real-valued or complex-valued matrices whose columns sum to zero, i.e. ````math @@ -8,20 +8,26 @@ The manifold of $m × n$ real-valued or complex-valued matrices whose columns su where $𝔽 ∈ \{ℝ,ℂ\}$. # Constructor - CenteredMatrices(m, n[, field=ℝ]) + CenteredMatrices(m, n[, field=ℝ]; parameter::Symbol=:type) Generate the manifold of `m`-by-`n` (`field`-valued) matrices whose columns sum to zero. + +`parameter`: whether a type parameter should be used to store `m` and `n`. By default size +is stored in type. Value can either be `:field` or `:type`. """ -struct CenteredMatrices{M,N,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct CenteredMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -function CenteredMatrices(m::Int, n::Int, field::AbstractNumbers=ℝ) - return CenteredMatrices{m,n,field}() +function CenteredMatrices(m::Int, n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (m, n)) + return CenteredMatrices{typeof(size),field}(size) end active_traits(f, ::CenteredMatrices, args...) = merge_traits(IsEmbeddedSubmanifold()) @doc raw""" - check_point(M::CenteredMatrices{m,n,𝔽}, p; kwargs...) + check_point(M::CenteredMatrices, p; kwargs...) Check whether the matrix is a valid point on the [`CenteredMatrices`](@ref) `M`, i.e. is an `m`-by-`n` matrix whose columns sum to @@ -29,7 +35,8 @@ zero. The tolerance for the column sums of `p` can be set using `kwargs...`. """ -function check_point(M::CenteredMatrices{m,n,𝔽}, p; kwargs...) where {m,n,𝔽} +function check_point(M::CenteredMatrices, p; kwargs...) + m, n = get_parameter(M.size) if !isapprox(sum(p, dims=1), zeros(1, n); kwargs...) return DomainError( p, @@ -42,14 +49,15 @@ function check_point(M::CenteredMatrices{m,n,𝔽}, p; kwargs...) where {m,n, end """ - check_vector(M::CenteredMatrices{m,n,𝔽}, p, X; kwargs... ) + check_vector(M::CenteredMatrices, p, X; kwargs... ) Check whether `X` is a tangent vector to manifold point `p` on the [`CenteredMatrices`](@ref) `M`, i.e. that `X` is a matrix of size `(m, n)` whose columns sum to zero and its values are from the correct [`AbstractNumbers`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#number-system). The tolerance for the column sums of `p` and `X` can be set using `kwargs...`. """ -function check_vector(M::CenteredMatrices{m,n,𝔽}, p, X; kwargs...) where {m,n,𝔽} +function check_vector(M::CenteredMatrices, p, X; kwargs...) + m, n = get_parameter(M.size) if !isapprox(sum(X, dims=1), zeros(1, n); kwargs...) return DomainError( X, @@ -62,7 +70,13 @@ end embed(::CenteredMatrices, p) = p embed(::CenteredMatrices, p, X) = X -get_embedding(::CenteredMatrices{m,n,𝔽}) where {m,n,𝔽} = Euclidean(m, n; field=𝔽) +function get_embedding(::CenteredMatrices{TypeParameter{Tuple{m,n}},𝔽}) where {m,n,𝔽} + return Euclidean(m, n; field=𝔽) +end +function get_embedding(M::CenteredMatrices{Tuple{Int,Int},𝔽}) where {𝔽} + m, n = get_parameter(M.size) + return Euclidean(m, n; field=𝔽, parameter=:field) +end """ is_flat(::CenteredMatrices) @@ -72,7 +86,7 @@ Return true. [`CenteredMatrices`](@ref) is a flat manifold. is_flat(M::CenteredMatrices) = true @doc raw""" - manifold_dimension(M::CenteredMatrices{m,n,𝔽}) + manifold_dimension(M::CenteredMatrices) Return the manifold dimension of the [`CenteredMatrices`](@ref) `m`-by-`n` matrix `M` over the number system `𝔽`, i.e. @@ -82,7 +96,8 @@ Return the manifold dimension of the [`CenteredMatrices`](@ref) `m`-by-`n` matri ```` where $\dim_ℝ 𝔽$ is the [`real_dimension`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.real_dimension-Tuple{ManifoldsBase.AbstractNumbers}) of `𝔽`. """ -function manifold_dimension(::CenteredMatrices{m,n,𝔽}) where {m,n,𝔽} +function manifold_dimension(M::CenteredMatrices{<:Any,𝔽}) where {𝔽} + m, n = get_parameter(M.size) return (m * n - n) * real_dimension(𝔽) end @@ -122,11 +137,15 @@ project(::CenteredMatrices, ::Any, ::Any) project!(::CenteredMatrices, Y, p, X) = (Y .= X .- mean(X, dims=1)) -@generated representation_size(::CenteredMatrices{m,n,𝔽}) where {m,n,𝔽} = (m, n) +representation_size(M::CenteredMatrices) = get_parameter(M.size) -function Base.show(io::IO, ::CenteredMatrices{m,n,𝔽}) where {m,n,𝔽} +function Base.show(io::IO, ::CenteredMatrices{TypeParameter{Tuple{m,n}},𝔽}) where {m,n,𝔽} return print(io, "CenteredMatrices($(m), $(n), $(𝔽))") end +function Base.show(io::IO, M::CenteredMatrices{Tuple{Int,Int},𝔽}) where {𝔽} + m, n = get_parameter(M.size) + return print(io, "CenteredMatrices($(m), $(n), $(𝔽); parameter=:field)") +end @doc raw""" Y = Weingarten(M::CenteredMatrices, p, X, V) diff --git a/src/manifolds/CholeskySpace.jl b/src/manifolds/CholeskySpace.jl index 40f8cbd56f..ff3b9af276 100644 --- a/src/manifolds/CholeskySpace.jl +++ b/src/manifolds/CholeskySpace.jl @@ -1,5 +1,5 @@ @doc raw""" - CholeskySpace{N} <: AbstractManifold{ℝ} + CholeskySpace{T} <: AbstractManifold{ℝ} The manifold of lower triangular matrices with positive diagonal and a metric based on the cholesky decomposition. The formulae for this manifold @@ -7,13 +7,18 @@ are for example summarized in Table 1 of [Lin:2019](@cite). # Constructor - CholeskySpace(n) + CholeskySpace(n; parameter::Symbol=:type) Generate the manifold of $n× n$ lower triangular matrices with positive diagonal. """ -struct CholeskySpace{N} <: AbstractManifold{ℝ} end +struct CholeskySpace{T} <: AbstractManifold{ℝ} + size::T +end -CholeskySpace(n::Int) = CholeskySpace{n}() +function CholeskySpace(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return CholeskySpace{typeof(size)}(size) +end @doc raw""" check_point(M::CholeskySpace, p; kwargs...) @@ -164,16 +169,28 @@ Return the manifold dimension for the [`CholeskySpace`](@ref) `M`, i.e. \dim(\mathcal M) = \frac{N(N+1)}{2}. ```` """ -@generated manifold_dimension(::CholeskySpace{N}) where {N} = div(N * (N + 1), 2) +function manifold_dimension(M::CholeskySpace) + N = get_parameter(M.size)[1] + return div(N * (N + 1), 2) +end @doc raw""" representation_size(M::CholeskySpace) Return the representation size for the [`CholeskySpace`](@ref)`{N}` `M`, i.e. `(N,N)`. """ -@generated representation_size(::CholeskySpace{N}) where {N} = (N, N) +function representation_size(M::CholeskySpace) + N = get_parameter(M.size)[1] + return (N, N) +end -Base.show(io::IO, ::CholeskySpace{N}) where {N} = print(io, "CholeskySpace($(N))") +function Base.show(io::IO, ::CholeskySpace{TypeParameter{Tuple{n}}}) where {n} + return print(io, "CholeskySpace($(n))") +end +function Base.show(io::IO, M::CholeskySpace{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "CholeskySpace($(n); parameter=:field)") +end # two small helpers for strictly lower and upper triangulars strictlyLowerTriangular(p) = LowerTriangular(p) - Diagonal(diag(p)) diff --git a/src/manifolds/ConnectionManifold.jl b/src/manifolds/ConnectionManifold.jl index 1f234ec9cc..c8fa3a5aad 100644 --- a/src/manifolds/ConnectionManifold.jl +++ b/src/manifolds/ConnectionManifold.jl @@ -372,7 +372,7 @@ function solve_exp_ode end @doc raw""" solve_exp_ode( - M::AbstractConnectionManifold, + M::ConnectionManifold, p, X, t::Number, diff --git a/src/manifolds/Elliptope.jl b/src/manifolds/Elliptope.jl index 0f52dcce53..18b428c3af 100644 --- a/src/manifolds/Elliptope.jl +++ b/src/manifolds/Elliptope.jl @@ -1,5 +1,5 @@ @doc raw""" - Elliptope{N,K} <: AbstractDecoratorManifold{ℝ} + Elliptope{T} <: AbstractDecoratorManifold{ℝ} The Elliptope manifold, also known as the set of correlation matrices, consists of all symmetric positive semidefinite matrices of rank $k$ with unit diagonal, i.e., @@ -35,15 +35,23 @@ investigated in[JourneeBachAbsilSepulchre:2010](@cite). # Constructor - Elliptope(n,k) + Elliptope(n::Int, k::Int; parameter::Symbol=:type) generates the manifold $\mathcal E(n,k) \subset ℝ^{n × n}$. + +`parameter`: whether a type parameter should be used to store `n` and `k`. By default size +is stored in type. Value can either be `:field` or `:type`. """ -struct Elliptope{N,K} <: AbstractDecoratorManifold{ℝ} end +struct Elliptope{T} <: AbstractDecoratorManifold{ℝ} + size::T +end active_traits(f, ::Elliptope, args...) = merge_traits(IsEmbeddedManifold()) -Elliptope(n::Int, k::Int) = Elliptope{n,k}() +function Elliptope(n::Int, k::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, k)) + return Elliptope{typeof(size)}(size) +end @doc raw""" check_point(M::Elliptope, q; kwargs...) @@ -55,7 +63,7 @@ Since by construction $p$ is symmetric, this is not explicitly checked. Since $p$ is by construction positive semidefinite, this is not checked. The tolerances for positive semidefiniteness and unit trace can be set using the `kwargs...`. """ -function check_point(M::Elliptope{N,K}, q; kwargs...) where {N,K} +function check_point(M::Elliptope, q; kwargs...) row_norms_sq = sum(abs2, q; dims=2) if !all(isapprox.(row_norms_sq, 1.0; kwargs...)) return DomainError( @@ -77,7 +85,7 @@ zero diagonal. The tolerance for the base point check and zero diagonal can be set using the `kwargs...`. Note that symmetric of $X$ holds by construction an is not explicitly checked. """ -function check_vector(M::Elliptope{N,K}, q, Y; kwargs...) where {N,K} +function check_vector(M::Elliptope, q, Y; kwargs...) X = q * Y' + Y * q' n = diag(X) if !all(isapprox.(n, 0.0; kwargs...)) @@ -89,7 +97,13 @@ function check_vector(M::Elliptope{N,K}, q, Y; kwargs...) where {N,K} return nothing end -get_embedding(M::Elliptope) = Euclidean(representation_size(M)...; field=ℝ) +function get_embedding(::Elliptope{TypeParameter{Tuple{n,k}}}) where {n,k} + return Euclidean(n, k) +end +function get_embedding(M::Elliptope{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return Euclidean(n, k; parameter=:field) +end """ is_flat(::Elliptope) @@ -107,7 +121,8 @@ returns the dimension of \dim \mathcal E(n,k) = n(k-1) - \frac{k(k-1)}{2}. ```` """ -@generated function manifold_dimension(::Elliptope{N,K}) where {N,K} +function manifold_dimension(M::Elliptope) + N, K = get_parameter(M.size) return N * (K - 1) - div(K * (K - 1), 2) end @@ -154,10 +169,14 @@ Return the size of an array representing an element on the [`Elliptope`](@ref) manifold `M`, i.e. $n × k$, the size of such factor of $p=qq^{\mathrm{T}}$ on $\mathcal M = \mathcal E(n,k)$. """ -@generated representation_size(::Elliptope{N,K}) where {N,K} = (N, K) +representation_size(M::Elliptope) = get_parameter(M.size) -function Base.show(io::IO, ::Elliptope{N,K}) where {N,K} - return print(io, "Elliptope($(N), $(K))") +function Base.show(io::IO, ::Elliptope{TypeParameter{Tuple{n,k}}}) where {n,k} + return print(io, "Elliptope($(n), $(k))") +end +function Base.show(io::IO, M::Elliptope{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return print(io, "Elliptope($(n), $(k); parameter=:field)") end """ @@ -177,4 +196,4 @@ definite matrix `p` on the [`Elliptope`](@ref) manifold `M`. """ zero_vector(::Elliptope, ::Any...) -zero_vector!(::Elliptope{N,K}, v, ::Any) where {N,K} = fill!(v, 0) +zero_vector!(::Elliptope, X, ::Any) = fill!(X, 0) diff --git a/src/manifolds/EssentialManifold.jl b/src/manifolds/EssentialManifold.jl index a68d8dea8e..8d46f2ffca 100644 --- a/src/manifolds/EssentialManifold.jl +++ b/src/manifolds/EssentialManifold.jl @@ -49,12 +49,15 @@ Generate the manifold of essential matrices, either the signed (`is_signed=true` unsigned (`is_signed=false`) variant. """ -struct EssentialManifold <: AbstractPowerManifold{ℝ,Rotations{3},NestedPowerRepresentation} +struct EssentialManifold <: + AbstractPowerManifold{ℝ,Rotations{TypeParameter{Tuple{3}}},NestedPowerRepresentation} is_signed::Bool - manifold::Rotations{3} + manifold::Rotations{TypeParameter{Tuple{3}}} end -EssentialManifold(is_signed::Bool=true) = EssentialManifold(is_signed, Rotations(3)) +function EssentialManifold(is_signed::Bool=true) + return EssentialManifold(is_signed, Rotations(3; parameter=:type)) +end @doc raw""" check_point(M::EssentialManifold, p; kwargs...) @@ -482,6 +485,6 @@ pose of camera $i$ $g_i = (R_i,T'_i) ∈ \text{SE}(3)$ and $R_0 ∈ \text{SO}(3) function vert_proj(M::EssentialManifold, p, X) return sum(vert_proj.(Ref(M.manifold), p, X)) end -function vert_proj(M::Rotations{3}, p, X) +function vert_proj(M::Rotations{TypeParameter{Tuple{3}}}, p, X) return (p[3, :]' * get_coordinates(M, p, X, DefaultOrthogonalBasis())) end diff --git a/src/manifolds/Euclidean.jl b/src/manifolds/Euclidean.jl index 34d5ac01ca..c5173c21c0 100644 --- a/src/manifolds/Euclidean.jl +++ b/src/manifolds/Euclidean.jl @@ -1,5 +1,5 @@ @doc raw""" - Euclidean{T<:Tuple,𝔽} <: AbstractManifold{𝔽} + Euclidean{T,𝔽} <: AbstractManifold{𝔽} Euclidean vector space. @@ -9,7 +9,7 @@ Euclidean vector space. Generate the ``n``-dimensional vector space ``ℝ^n``. - Euclidean(n₁,n₂,...,nᵢ; field=ℝ) + Euclidean(n₁,n₂,...,nᵢ; field=ℝ, parameter::Symbol = :field) 𝔽^(n₁,n₂,...,nᵢ) = Euclidean(n₁,n₂,...,nᵢ; field=𝔽) Generate the vector space of ``k = n_1 \cdot n_2 \cdot … \cdot n_i`` values, i.e. the @@ -20,15 +20,25 @@ The default `field=ℝ` can also be set to `field=ℂ`. The dimension of this space is ``k \dim_ℝ 𝔽``, where ``\dim_ℝ 𝔽`` is the [`real_dimension`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.real_dimension-Tuple{ManifoldsBase.AbstractNumbers}) of the field ``𝔽``. +`parameter`: whether a type parameter should be used to store `n`. By default size +is stored in type. Value can either be `:field` or `:type`. + Euclidean(; field=ℝ) Generate the 1D Euclidean manifold for an `ℝ`-, `ℂ`-valued real- or complex-valued immutable values (in contrast to 1-element arrays from the constructor above). """ -struct Euclidean{N,𝔽} <: AbstractDecoratorManifold{𝔽} where {N<:Tuple} end +struct Euclidean{T,𝔽} <: AbstractDecoratorManifold{𝔽} where {T} + size::T +end -function Euclidean(n::Vararg{Int,I}; field::AbstractNumbers=ℝ) where {I} - return Euclidean{Tuple{n...},field}() +function Euclidean( + n::Vararg{Int,I}; + field::AbstractNumbers=ℝ, + parameter::Symbol=:type, +) where {I} + size = wrap_type_parameter(parameter, n) + return Euclidean{typeof(size),field}(size) end function active_traits(f, ::Euclidean, args...) @@ -41,16 +51,31 @@ end function adjoint_Jacobi_field(::Euclidean{Tuple{}}, p, q, t, X, β::Tβ) where {Tβ} return X end +function adjoint_Jacobi_field( + ::Euclidean{TypeParameter{Tuple{}}}, + p, + q, + t, + X, + β::Tβ, +) where {Tβ} + return X +end Base.:^(𝔽::AbstractNumbers, n) = Euclidean(n...; field=𝔽) Base.:^(M::Euclidean, n::Int) = ^(M, (n,)) -function Base.:^(::Euclidean{T,𝔽}, n::NTuple{N,Int}) where {T,𝔽,N} - return Euclidean{Tuple{T.parameters...,n...},𝔽}() +function Base.:^(M::Euclidean{<:Tuple,𝔽}, n::NTuple{N,Int}) where {𝔽,N} + size = get_parameter(M.size) + return Euclidean(size..., n...; field=𝔽, parameter=:field) +end +function Base.:^(M::Euclidean{<:TypeParameter,𝔽}, n::NTuple{N,Int}) where {𝔽,N} + size = get_parameter(M.size) + return Euclidean(size..., n...; field=𝔽, parameter=:type) end function allocation_promotion_function( - ::Euclidean{<:Tuple,ℂ}, + ::Euclidean{<:Any,ℂ}, ::Union{typeof(get_vector),typeof(get_coordinates)}, ::Tuple, ) @@ -106,7 +131,7 @@ end Compute the Euclidean distance between two points on the [`Euclidean`](@ref) manifold `M`, i.e. for vectors it's just the norm of the difference, for matrices -and higher order arrays, the matrix and ternsor Frobenius norm, respectively. +and higher order arrays, the matrix and tensor Frobenius norm, respectively. """ Base.@propagate_inbounds function distance(M::Euclidean, p, q) # Inspired by euclidean distance calculation in Distances.jl @@ -124,7 +149,9 @@ Base.@propagate_inbounds function distance(M::Euclidean, p, q) end return sqrt(s) end -distance(::Euclidean{Tuple{1}}, p::Number, q::Number) = abs(p - q) +distance(::Euclidean{TypeParameter{Tuple{1}}}, p::Number, q::Number) = abs(p - q) +distance(::Euclidean{TypeParameter{Tuple{}}}, p::Number, q::Number) = abs(p - q) +distance(::Euclidean{Tuple{Int}}, p::Number, q::Number) = abs(p - q) # for 1-dimensional Euclidean distance(::Euclidean{Tuple{}}, p::Number, q::Number) = abs(p - q) """ @@ -214,13 +241,7 @@ function get_coordinates_induced_basis!( return c end -function get_coordinates_orthonormal!( - M::Euclidean{<:Tuple,ℂ}, - c, - ::Any, - X, - ::ComplexNumbers, -) +function get_coordinates_orthonormal!(M::Euclidean{<:Any,ℂ}, c, ::Any, X, ::ComplexNumbers) S = representation_size(M) PS = prod(S) c .= [reshape(real.(X), PS)..., reshape(imag(X), PS)...] @@ -228,7 +249,7 @@ function get_coordinates_orthonormal!( end function get_coordinates_diagonalizing!( - M::Euclidean{<:Tuple,ℂ}, + M::Euclidean{<:Any,ℂ}, c, ::Any, X, @@ -256,15 +277,29 @@ function get_vector_orthonormal(M::Euclidean, ::Any, c, ::RealNumbers) S = representation_size(M) return reshape(c, S) end -function get_vector_orthonormal(::Euclidean{Tuple{N},ℝ}, ::Any, c, ::RealNumbers) where {N} +function get_vector_orthonormal( + ::Euclidean{TypeParameter{Tuple{N}},ℝ}, + ::Any, + c, + ::RealNumbers, +) where {N} # this method is defined just to skip a reshape return c end -function get_vector_orthonormal(::Euclidean, ::SArray{S}, c, ::RealNumbers) where {S} +function get_vector_orthonormal(::Euclidean{Tuple{Int},ℝ}, ::Any, c, ::RealNumbers) + # this method is defined just to skip a reshape + return c +end +function get_vector_orthonormal( + ::Euclidean{<:TypeParameter}, + ::SArray{S}, + c, + ::RealNumbers, +) where {S} return SArray{S}(c) end function get_vector_orthonormal( - ::Euclidean{Tuple{N},ℝ}, + ::Euclidean{TypeParameter{Tuple{N}},ℝ}, ::SArray{S}, c, ::RealNumbers, @@ -272,11 +307,16 @@ function get_vector_orthonormal( # probably doesn't need rewrapping in SArray return c end -function get_vector_orthonormal(::Euclidean, ::SizedArray{S}, c, ::RealNumbers) where {S} +function Manifolds.get_vector_orthonormal( + ::Euclidean{TypeParameter{Tuple{N}}}, + ::SizedArray{S}, + c, + ::RealNumbers, +) where {N,S} return SizedArray{S}(c) end function get_vector_orthonormal( - ::Euclidean{Tuple{N},ℝ}, + ::Euclidean{TypeParameter{Tuple{N}},ℝ}, ::SizedArray{S}, c, ::RealNumbers, @@ -286,7 +326,7 @@ function get_vector_orthonormal( end function get_vector_orthonormal!( - ::Euclidean{Tuple{N},ℝ}, + ::Euclidean{TypeParameter{Tuple{N}},ℝ}, Y, ::Any, c, @@ -317,14 +357,14 @@ function get_vector_induced_basis!(M::Euclidean, Y, ::Any, c, B::InducedBasis) copyto!(Y, reshape(c, S)) return Y end -function get_vector_orthonormal!(M::Euclidean{<:Tuple,ℂ}, Y, ::Any, c, ::ComplexNumbers) +function get_vector_orthonormal!(M::Euclidean{<:Any,ℂ}, Y, ::Any, c, ::ComplexNumbers) S = representation_size(M) N = div(length(c), 2) copyto!(Y, reshape(c[1:N] + im * c[(N + 1):end], S)) return Y end function get_vector_diagonalizing!( - M::Euclidean{<:Tuple,ℂ}, + M::Euclidean{<:Any,ℂ}, Y, ::Any, c, @@ -398,6 +438,9 @@ Return true. [`Euclidean`](@ref) is a flat manifold. """ is_flat(M::Euclidean) = true +function jacobi_field(::Euclidean{TypeParameter{Tuple{}}}, p, q, t, X, β::Tβ) where {Tβ} + return X +end function jacobi_field(::Euclidean{Tuple{}}, p, q, t, X, β::Tβ) where {Tβ} return X end @@ -427,7 +470,7 @@ which in this case is just ```` """ Base.log(::Euclidean, ::Any...) -Base.log(::Euclidean{Tuple{}}, p::Number, q::Number) = q - p +Base.log(::Euclidean{TypeParameter{Tuple{}}}, p::Number, q::Number) = q - p Base.log(::Euclidean, p, q) = q .- p log!(::Euclidean, X, p, q) = (X .= q .- p) @@ -440,7 +483,7 @@ function log_local_metric_density( return zero(eltype(p)) end -@generated _product_of_dimensions(::Euclidean{N}) where {N} = prod(N.parameters) +_product_of_dimensions(M::Euclidean) = prod(get_parameter(M.size)) """ manifold_dimension(M::Euclidean) @@ -449,10 +492,10 @@ Return the manifold dimension of the [`Euclidean`](@ref) `M`, i.e. the product of all array dimensions and the [`real_dimension`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.real_dimension-Tuple{ManifoldsBase.AbstractNumbers}) of the underlying number system. """ -function manifold_dimension(M::Euclidean{N,𝔽}) where {N,𝔽} +function manifold_dimension(M::Euclidean{<:Any,𝔽}) where {𝔽} return _product_of_dimensions(M) * real_dimension(𝔽) end -manifold_dimension(::Euclidean{Tuple{},𝔽}) where {𝔽} = real_dimension(𝔽) +manifold_dimension(::Euclidean{TypeParameter{Tuple{}},𝔽}) where {𝔽} = real_dimension(𝔽) """ manifold_volume(::Euclidean) @@ -463,7 +506,14 @@ manifold_volume(::Euclidean) = Inf Statistics.mean(::Euclidean{Tuple{}}, x::AbstractVector{<:Number}; kwargs...) = mean(x) function Statistics.mean( - ::Euclidean{Tuple{}}, + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, + x::AbstractVector{<:Number}; + kwargs..., +) + return mean(x) +end +function Statistics.mean( + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, x::AbstractVector{<:Number}, w::AbstractWeights; kwargs..., @@ -473,7 +523,7 @@ end Statistics.mean(::Euclidean, x::AbstractVector; kwargs...) = mean(x) function StatsBase.mean_and_var( - ::Euclidean{Tuple{}}, + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, x::AbstractVector{<:Number}; kwargs..., ) @@ -481,7 +531,7 @@ function StatsBase.mean_and_var( return m, sum(v) end function StatsBase.mean_and_var( - ::Euclidean{Tuple{}}, + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, x::AbstractVector{<:Number}, w::AbstractWeights; corrected=false, @@ -491,9 +541,15 @@ function StatsBase.mean_and_var( return m, sum(v) end -Statistics.median(::Euclidean{Tuple{}}, x::AbstractVector{<:Number}; kwargs...) = median(x) function Statistics.median( - ::Euclidean{Tuple{}}, + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, + x::AbstractVector{<:Number}; + kwargs..., +) + return median(x) +end +function Statistics.median( + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, x::AbstractVector{<:Number}, w::AbstractWeights; kwargs..., @@ -502,7 +558,13 @@ function Statistics.median( end mid_point(::Euclidean, p1, p2) = (p1 .+ p2) ./ 2 -mid_point(::Euclidean{Tuple{}}, p1::Number, p2::Number) = (p1 + p2) / 2 +function mid_point( + ::Union{Euclidean{TypeParameter{Tuple{}}},Euclidean{Tuple{}}}, + p1::Number, + p2::Number, +) + return (p1 + p2) / 2 +end function mid_point!(::Euclidean, q, p1, p2) q .= (p1 .+ p2) ./ 2 @@ -574,6 +636,7 @@ Project an arbitrary point `p` onto the [`Euclidean`](@ref) manifold `M`, which is of course just the identity map. """ project(::Euclidean, ::Any) +project(::Euclidean{TypeParameter{Tuple{}}}, p::Number) = p project(::Euclidean{Tuple{}}, p::Number) = p project!(::Euclidean, q, p) = copyto!(q, p) @@ -586,6 +649,7 @@ Project an arbitrary vector `X` into the tangent space of a point `p` on the space of `M` can be identified with all of `M`. """ project(::Euclidean, ::Any, ::Any) +project(::Euclidean{TypeParameter{Tuple{}}}, ::Number, X::Number) = X project(::Euclidean{Tuple{}}, ::Number, X::Number) = X project!(::Euclidean, Y, p, X) = copyto!(Y, X) @@ -607,13 +671,17 @@ end Return the array dimensions required to represent an element on the [`Euclidean`](@ref) `M`, i.e. the vector of all array dimensions. """ -@generated representation_size(::Euclidean{N}) where {N} = size_to_tuple(N) -@generated representation_size(::Euclidean{Tuple{}}) = () +representation_size(M::Euclidean) = get_parameter(M.size) -function retract(M::Euclidean{Tuple{}}, p::Number, q::Number) +function retract(M::Euclidean{TypeParameter{Tuple{}}}, p::Number, q::Number) return retract(M, p, q, ExponentialRetraction()) end -function retract(M::Euclidean{Tuple{}}, p::Number, q::Number, ::ExponentialRetraction) +function retract( + M::Euclidean{TypeParameter{Tuple{}}}, + p::Number, + q::Number, + ::ExponentialRetraction, +) return exp(M, p, q) end @@ -630,15 +698,20 @@ function riemann_tensor!(::Euclidean, Xresult, p, X, Y, Z) return fill!(Xresult, 0) end -function Base.show(io::IO, ::Euclidean{N,𝔽}) where {N,𝔽} - return print(io, "Euclidean($(join(N.parameters, ", ")); field = $(𝔽))") +function Base.show(io::IO, M::Euclidean{N,𝔽}) where {N<:Tuple,𝔽} + size = get_parameter(M.size) + return print(io, "Euclidean($(join(size, ", ")); field=$(𝔽), parameter=:field)") +end +function Base.show(io::IO, M::Euclidean{N,𝔽}) where {N<:TypeParameter,𝔽} + size = get_parameter(M.size) + return print(io, "Euclidean($(join(size, ", ")); field=$(𝔽))") end # # Vector Transport # # The following functions are defined on layer 1 already, since # a) its independent of the transport or retraction method -# b) no amibuities occur +# b) no ambiguities occur # c) Euclidean is so basic, that these are plain defaults # function vector_transport_along( @@ -745,6 +818,7 @@ Return the zero vector in the tangent space of `x` on the [`Euclidean`](@ref) `M`, which here is just a zero filled array the same size as `x`. """ zero_vector(::Euclidean, ::Any...) +zero_vector(::Euclidean{TypeParameter{Tuple{}}}, p::Number) = zero(p) zero_vector(::Euclidean{Tuple{}}, p::Number) = zero(p) zero_vector!(::Euclidean, v, ::Any) = fill!(v, 0) diff --git a/src/manifolds/Fiber.jl b/src/manifolds/Fiber.jl new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/manifolds/Fiber.jl @@ -0,0 +1 @@ + diff --git a/src/manifolds/FiberBundle.jl b/src/manifolds/FiberBundle.jl new file mode 100644 index 0000000000..97e5ae41d5 --- /dev/null +++ b/src/manifolds/FiberBundle.jl @@ -0,0 +1,466 @@ + +@doc raw""" + FiberBundleProductVectorTransport{ + TMP<:AbstractVectorTransportMethod, + TMV<:AbstractVectorTransportMethod, + } <: AbstractVectorTransportMethod + +Vector transport type on [`FiberBundle`](@ref). + +# Fields + +* `method_horizonal` – vector transport method of the horizontal part (related to manifold M) +* `method_vertical` – vector transport method of the vertical part (related to fibers). + +The vector transport is derived as a product manifold-style vector transport. +The considered product manifold is the product between the manifold ``\mathcal M`` +and the topological vector space isometric to the fiber. + +# Constructor + + FiberBundleProductVectorTransport( + M::AbstractManifold=DefaultManifold(); + vector_transport_method_horizontal::AbstractVectorTransportMethod = default_vector_transport_method(M), + vector_transport_method_vertical::AbstractVectorTransportMethod = default_vector_transport_method(M), + ) + +Construct the `FiberBundleProductVectorTransport` using the [`default_vector_transport_method`](@ref), +which uses [`ParallelTransport`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/vector_transports/#ManifoldsBase.ParallelTransport) +if no manifold is provided. +""" +struct FiberBundleProductVectorTransport{ + TMP<:AbstractVectorTransportMethod, + TMV<:AbstractVectorTransportMethod, +} <: AbstractVectorTransportMethod + method_horizontal::TMP + method_vertical::TMV +end +function FiberBundleProductVectorTransport( + M::AbstractManifold=ManifoldsBase.DefaultManifold(), + fiber::FiberType=ManifoldsBase.TangentSpaceType(); + vector_transport_method_horizontal::AbstractVectorTransportMethod=default_vector_transport_method( + M, + ), + vector_transport_method_vertical::AbstractVectorTransportMethod=fiber_bundle_transport( + M, + fiber, + ), +) + return FiberBundleProductVectorTransport( + vector_transport_method_horizontal, + vector_transport_method_vertical, + ) +end + +""" + FiberBundle{𝔽,TVS<:FiberType,TM<:AbstractManifold{𝔽},TVT<:FiberBundleProductVectorTransport} <: AbstractManifold{𝔽} + +Fiber bundle on a [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types/#ManifoldsBase.AbstractManifold) +`M` of type [`FiberType`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.FiberType). +Examples include vector bundles, principal bundles or unit tangent bundles, see also [📖 Fiber Bundle](https://en.wikipedia.org/wiki/Fiber_bundle). + +# Fields +* `manifold` – the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types/#ManifoldsBase.AbstractManifold) + manifold the Fiber bundle is defined on, +* `type` – representing the type of fiber we use. + +# Constructor + + FiberBundle(M::AbstractManifold, type::FiberType) +""" +struct FiberBundle{ + 𝔽, + TF<:FiberType, + TM<:AbstractManifold{𝔽}, + TVT<:FiberBundleProductVectorTransport, +} <: AbstractManifold{𝔽} + type::TF + manifold::TM + vector_transport::TVT +end + +function FiberBundle(fiber::FiberType, M::AbstractManifold) + vtmm = vector_bundle_transport(fiber, M) + vtbm = FiberBundleProductVectorTransport(vtmm, vtmm) + return FiberBundle(fiber, M, vtbm) +end + +@doc raw""" + struct FiberBundleInverseProductRetraction <: AbstractInverseRetractionMethod end + +Inverse retraction of the point `y` at point `p` from vector bundle `B` over manifold +`B.fiber` (denoted ``\mathcal M``). The inverse retraction is derived as a product manifold-style +approximation to the logarithmic map in the Sasaki metric. The considered product manifold +is the product between the manifold ``\mathcal M`` and the topological vector space isometric +to the fiber. + +## Notation +The point ``p = (x_p, V_p)`` where ``x_p ∈ \mathcal M`` and ``V_p`` belongs to +the fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the canonical +projection of that vector bundle ``B``. Similarly, ``q = (x_q, V_q)``. + +The inverse retraction is calculated as + +```math +\operatorname{retr}^{-1}_p q = (\operatorname{retr}^{-1}_{x_p}(x_q), V_{\operatorname{retr}^{-1}} - V_p) +``` + +where ``V_{\operatorname{retr}^{-1}}`` is the result of vector transport of ``V_q`` to the point ``x_p``. +The difference ``V_{\operatorname{retr}^{-1}} - V_p`` corresponds to the logarithmic map in +the vector space ``F``. + +See also [`FiberBundleProductRetraction`](@ref). +""" +struct FiberBundleInverseProductRetraction <: AbstractInverseRetractionMethod end + +@doc raw""" + struct FiberBundleProductRetraction <: AbstractRetractionMethod end + +Product retraction map of tangent vector ``X`` at point ``p`` from vector bundle `B` over +manifold `B.fiber` (denoted ``\mathcal M``). The retraction is derived as a product manifold-style +approximation to the exponential map in the Sasaki metric. The considered product manifold +is the product between the manifold ``\mathcal M`` and the topological vector space isometric +to the fiber. + +## Notation: +* The point ``p = (x_p, V_p)`` where ``x_p ∈ \mathcal M`` and ``V_p`` belongs to the + fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the + canonical projection of that vector bundle ``B``. +* The tangent vector ``X = (V_{X,M}, V_{X,F}) ∈ T_pB`` where + ``V_{X,M}`` is a tangent vector from the tangent space ``T_{x_p}\mathcal M`` and + ``V_{X,F}`` is a tangent vector from the tangent space ``T_{V_p}F`` (isomorphic to ``F``). + +The retraction is calculated as + +```math +\operatorname{retr}_p(X) = (\exp_{x_p}(V_{X,M}), V_{\exp}) +```` + +where ``V_{\exp}`` is the result of vector transport of ``V_p + V_{X,F}`` +to the point ``\exp_{x_p}(V_{X,M})``. +The sum ``V_p + V_{X,F}`` corresponds to the exponential map in the vector space ``F``. + +See also [`FiberBundleInverseProductRetraction`](@ref). +""" +struct FiberBundleProductRetraction <: AbstractRetractionMethod end + +vector_bundle_transport(::FiberType, M::AbstractManifold) = ParallelTransport() + +struct FiberBundleBasisData{BBasis<:CachedBasis,TBasis<:CachedBasis} + base_basis::BBasis + fiber_basis::TBasis +end + +""" + base_manifold(B::FiberBundle) + +Return the manifold the [`FiberBundle`](@ref)s is build on. +""" +base_manifold(B::FiberBundle) = base_manifold(B.manifold) + +@doc raw""" + bundle_transport_to(B::FiberBundle, p, X, q) + +Given a fiber bundle ``B=F \mathcal M``, points ``p, q\in\mathcal M``, an element ``X`` of +the fiber over ``p``, transport ``X`` to fiber over ``q``. + +Exact meaning of the operation depends on the fiber bundle, or may even be undefined. +Some fiber bundles may declare a default local section around each point crossing `X`, +represented by this function. +""" +function bundle_transport_to(B::FiberBundle, p, X, q) + Y = allocate(X) + return bundle_transport_to!(B, Y, p, X, q) +end + +@doc raw""" + bundle_transport_tangent_direction(B::FiberBundle, p, pf, X, d) + +Compute parallel transport of vertical vector `X` according to Ehresmann connection on +[`FiberBundle`](@ref) `B`, in direction ``d\in T_p \mathcal M``. ``X`` is an element of the +vertical bundle ``VF\mathcal M`` at `pf` from tangent to fiber ``\pi^{-1}({p})``, +``p\in \mathcal M``. +""" +function bundle_transport_tangent_direction( + B::FiberBundle, + p, + pf, + X, + d, + m::AbstractVectorTransportMethod=default_vector_transport_method(B.manifold), +) + Y = allocate(X) + return bundle_transport_tangent_direction!(B, Y, p, pf, X, d, m) +end + +@doc raw""" + bundle_transport_tangent_to(B::FiberBundle, p, pf, X, q) + +Compute parallel transport of vertical vector `X` according to Ehresmann connection on +[`FiberBundle`](@ref) `B`, to point ``q\in \mathcal M``. ``X`` is an element of the vertical +bundle ``VF\mathcal M`` at `pf` from tangent to fiber ``\pi^{-1}({p})``, +``p\in \mathcal M``. +""" +function bundle_transport_tangent_to( + B::FiberBundle, + p, + pf, + X, + q, + m::AbstractVectorTransportMethod=default_vector_transport_method(B.manifold), +) + Y = allocate(X) + return bundle_transport_tangent_to!(B, Y, p, pf, X, q, m) +end + +""" + bundle_projection(B::FiberBundle, p) + +Projection of point `p` from the bundle `M` to the base manifold. +Returns the point on the base manifold `B.manifold` at which the vector part +of `p` is attached. +""" +bundle_projection(B::FiberBundle, p) = submanifold_component(B.manifold, p, Val(1)) + +function get_basis(M::FiberBundle, p, B::AbstractBasis) + xp1, xp2 = submanifold_components(M, p) + base_basis = get_basis(M.manifold, xp1, B) + F = Fiber(M.manifold, xp1, M.type) + fiber_basis = get_basis(F, xp2, B) + return CachedBasis(B, FiberBundleBasisData(base_basis, fiber_basis)) +end +function get_basis(M::FiberBundle, p, B::CachedBasis) + return invoke(get_basis, Tuple{AbstractManifold,Any,CachedBasis}, M, p, B) +end + +function get_coordinates(M::FiberBundle, p, X, B::AbstractBasis) + px, Vx = submanifold_components(M.manifold, p) + VXM, VXF = submanifold_components(M.manifold, X) + F = Fiber(M.manifold, px, M.type) + return vcat(get_coordinates(M.manifold, px, VXM, B), get_coordinates(F, Vx, VXF, B)) +end + +function get_coordinates!(M::FiberBundle, Y, p, X, B::AbstractBasis) + px, Vx = submanifold_components(M.manifold, p) + VXM, VXF = submanifold_components(M.manifold, X) + n = manifold_dimension(M.manifold) + get_coordinates!(M.manifold, view(Y, 1:n), px, VXM, B) + F = Fiber(M.manifold, px, M.type) + get_coordinates!(F, view(Y, (n + 1):length(Y)), Vx, VXF, B) + return Y +end + +function get_coordinates( + M::FiberBundle, + p, + X, + B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:FiberBundleBasisData}, +) where {𝔽} + px, Vx = submanifold_components(M.manifold, p) + VXM, VXF = submanifold_components(M.manifold, X) + F = Fiber(M.manifold, px, M.type) + return vcat( + get_coordinates(M.manifold, px, VXM, B.data.base_basis), + get_coordinates(F, Vx, VXF, B.data.fiber_basis), + ) +end + +function get_coordinates!( + M::FiberBundle, + Y, + p, + X, + B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:FiberBundleBasisData}, +) where {𝔽} + px, Vx = submanifold_components(M.manifold, p) + VXM, VXF = submanifold_components(M.manifold, X) + n = manifold_dimension(M.manifold) + F = Fiber(M.manifold, px, M.type) + get_coordinates!(M.manifold, view(Y, 1:n), px, VXM, B.data.base_basis) + get_coordinates!(F, view(Y, (n + 1):length(Y)), Vx, VXF, B.data.fiber_basis) + return Y +end + +function get_vector!(M::FiberBundle, Y, p, X, B::AbstractBasis) + n = manifold_dimension(M.manifold) + xp1, xp2 = submanifold_components(M, p) + Yp1, Yp2 = submanifold_components(M, Y) + F = Fiber(M.manifold, xp1, M.type) + get_vector!(M.manifold, Yp1, xp1, X[1:n], B) + get_vector!(F, Yp2, xp2, X[(n + 1):end], B) + return Y +end + +function get_vector!( + M::FiberBundle, + Y, + p, + X, + B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:FiberBundleBasisData}, +) where {𝔽} + n = manifold_dimension(M.manifold) + xp1, xp2 = submanifold_components(M, p) + Yp1, Yp2 = submanifold_components(M, Y) + F = Fiber(M.manifold, xp1, M.type) + get_vector!(M.manifold, Yp1, xp1, X[1:n], B.data.base_basis) + get_vector!(F, Yp2, xp2, X[(n + 1):end], B.data.fiber_basis) + return Y +end + +function _isapprox(B::FiberBundle, p, q; kwargs...) + xp, Vp = submanifold_components(B.manifold, p) + xq, Vq = submanifold_components(B.manifold, q) + return isapprox(B.manifold, xp, xq; kwargs...) && + isapprox(Fiber(B.manifold, xp, B.type), Vp, Vq; kwargs...) +end +function _isapprox(B::FiberBundle, p, X, Y; kwargs...) + px, Vx = submanifold_components(B.manifold, p) + VXM, VXF = submanifold_components(B.manifold, X) + VYM, VYF = submanifold_components(B.manifold, Y) + return isapprox(B.manifold, VXM, VYM; kwargs...) && + isapprox(Fiber(B.manifold, px, B.type), Vx, VXF, VYF; kwargs...) +end + +function manifold_dimension(B::FiberBundle) + return manifold_dimension(B.manifold) + fiber_dimension(B.manifold, B.type) +end + +function Random.rand!(M::FiberBundle, pX; vector_at=nothing) + return rand!(Random.default_rng(), M, pX; vector_at=vector_at) +end +function Random.rand!(rng::AbstractRNG, M::FiberBundle, pX; vector_at=nothing) + pXM, pXF = submanifold_components(M.manifold, pX) + if vector_at === nothing + rand!(rng, M.manifold, pXM) + rand!(rng, Fiber(M.manifold, pXM, M.type), pXF) + else + vector_atM, vector_atF = submanifold_components(M.manifold, vector_at) + rand!(rng, M.manifold, pXM; vector_at=vector_atM) + rand!(rng, Fiber(M.manifold, pXM, M.type), pXF; vector_at=vector_atF) + end + return pX +end + +@doc raw""" + zero_vector(B::FiberBundle, p) + +Zero tangent vector at point `p` from the fiber bundle `B` +over manifold `B.fiber` (denoted ``\mathcal M``). The zero vector belongs to the space ``T_{p}B`` + +Notation: + * The point ``p = (x_p, V_p)`` where ``x_p ∈ \mathcal M`` and ``V_p`` belongs to the + fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the + canonical projection of that vector bundle ``B``. + +The zero vector is calculated as + +``\mathbf{0}_{p} = (\mathbf{0}_{x_p}, \mathbf{0}_F)`` + +where ``\mathbf{0}_{x_p}`` is the zero tangent vector from ``T_{x_p}\mathcal M`` and +``\mathbf{0}_F`` is the zero element of the vector space ``F``. +""" +zero_vector(::FiberBundle, ::Any...) + +function zero_vector!(B::FiberBundle, X, p) + xp, Vp = submanifold_components(B.manifold, p) + VXM, VXF = submanifold_components(B.manifold, X) + F = Fiber(B.manifold, xp, B.type) + zero_vector!(B.manifold, VXM, xp) + zero_vector!(F, VXF, Vp) + return X +end + +@inline function allocate_result(M::FiberBundle, f::TF) where {TF} + p = allocate_result(M.manifold, f) + X = allocate_result(Fiber(M.manifold, p, M.type), f) + return ArrayPartition(p, X) +end + +function get_vector(M::FiberBundle, p, X, B::AbstractBasis) + n = manifold_dimension(M.manifold) + xp1, xp2 = submanifold_components(M, p) + F = Fiber(M.manifold, xp1, M.type) + return ArrayPartition( + get_vector(M.manifold, xp1, X[1:n], B), + get_vector(F, xp2, X[(n + 1):end], B), + ) +end +function get_vector( + M::FiberBundle, + p, + X, + B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:FiberBundleBasisData}, +) where {𝔽} + n = manifold_dimension(M.manifold) + xp1, xp2 = submanifold_components(M, p) + F = Fiber(M.manifold, xp1, M.type) + return ArrayPartition( + get_vector(M.manifold, xp1, X[1:n], B.data.base_basis), + get_vector(F, xp2, X[(n + 1):end], B.data.fiber_basis), + ) +end + +function get_vectors( + M::FiberBundle, + p::ArrayPartition, + B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:FiberBundleBasisData}, +) where {𝔽} + xp1, xp2 = submanifold_components(M, p) + zero_m = zero_vector(M.manifold, xp1) + F = Fiber(M.manifold, xp1, M.type) + zero_f = zero_vector(F, xp1) + vs = typeof(ArrayPartition(zero_m, zero_f))[] + for bv in get_vectors(M.manifold, xp1, B.data.base_basis) + push!(vs, ArrayPartition(bv, zero_f)) + end + for bv in get_vectors(F, xp2, B.data.fiber_basis) + push!(vs, ArrayPartition(zero_m, bv)) + end + return vs +end + +""" + getindex(p::ArrayPartition, M::FiberBundle, s::Symbol) + p[M::FiberBundle, s] + +Access the element(s) at index `s` of a point `p` on a [`FiberBundle`](@ref) `M` by +using the symbols `:point` and `:vector` or `:fiber` for the base and vector or fiber +component, respectively. +""" +@inline function Base.getindex(p::ArrayPartition, M::FiberBundle, s::Symbol) + (s === :point) && return p.x[1] + (s === :vector || s === :fiber) && return p.x[2] + return throw(DomainError(s, "unknown component $s on $M.")) +end + +""" + setindex!(p::ArrayPartition, val, M::FiberBundle, s::Symbol) + p[M::VectorBundle, s] = val + +Set the element(s) at index `s` of a point `p` on a [`FiberBundle`](@ref) `M` to `val` by +using the symbols `:point` and `:fiber` or `:vector` for the base and fiber or vector +component, respectively. + +!!! note + + The *content* of element of `p` is replaced, not the element itself. +""" +@inline function Base.setindex!(x::ArrayPartition, val, M::FiberBundle, s::Symbol) + if s === :point + return copyto!(x.x[1], val) + elseif s === :vector || s === :fiber + return copyto!(x.x[2], val) + else + throw(DomainError(s, "unknown component $s on $M.")) + end +end + +function Base.show(io::IO, B::FiberBundle) + return print(io, "FiberBundle($(B.type), $(B.manifold), $(B.vector_transport))") +end + +@inline function Base.view(x::ArrayPartition, M::FiberBundle, s::Symbol) + (s === :point) && return x.x[1] + (s === :vector || s === :fiber) && return x.x[2] + throw(DomainError(s, "unknown component $s on $M.")) +end diff --git a/src/manifolds/FixedRankMatrices.jl b/src/manifolds/FixedRankMatrices.jl index 12f57674fb..9b09232b1c 100644 --- a/src/manifolds/FixedRankMatrices.jl +++ b/src/manifolds/FixedRankMatrices.jl @@ -1,5 +1,5 @@ @doc raw""" - FixedRankMatrices{m,n,k,𝔽} <: AbstractDecoratorManifold{𝔽} + FixedRankMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} The manifold of ``m × n`` real-valued or complex-valued matrices of fixed rank ``k``, i.e. ````math @@ -36,9 +36,19 @@ on ``ℝ^{m × n}`` to the tangent bundle [Vandereycken:2013](@cite). Generate the manifold of `m`-by-`n` (`field`-valued) matrices of rank `k`. """ -struct FixedRankMatrices{M,N,K,𝔽} <: AbstractDecoratorManifold{𝔽} end -function FixedRankMatrices(m::Int, n::Int, k::Int, field::AbstractNumbers=ℝ) - return FixedRankMatrices{m,n,k,field}() +struct FixedRankMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end + +function FixedRankMatrices( + m::Int, + n::Int, + k::Int, + field::AbstractNumbers=ℝ; + parameter::Symbol=:type, +) + size = wrap_type_parameter(parameter, (m, n, k)) + return FixedRankMatrices{typeof(size),field}(size) end active_traits(f, ::FixedRankMatrices, args...) = merge_traits(IsEmbeddedManifold()) @@ -117,27 +127,11 @@ function allocate(X::UMVTVector, ::Type{T}) where {T} return UMVTVector(allocate(X.U, T), allocate(X.M, T), allocate(X.Vt, T)) end -function allocate_result( - ::FixedRankMatrices{m,n,k}, - ::typeof(project), - X, - p, - vals..., -) where {m,n,k} - # vals are p and X, so we can use their fields to set up those of the UMVTVector - return UMVTVector(allocate(p.U, m, k), allocate(p.S, k, k), allocate(p.Vt, k, n)) -end -function allocate_result(::FixedRankMatrices{m,n,k}, ::typeof(rand), p) where {m,n,k} +function allocate_result(M::FixedRankMatrices, ::typeof(project), X, p, vals...) + m, n, k = get_parameter(M.size) # vals are p and X, so we can use their fields to set up those of the UMVTVector return UMVTVector(allocate(p.U, m, k), allocate(p.S, k, k), allocate(p.Vt, k, n)) end -function allocate_result(::FixedRankMatrices{m,n,k}, ::typeof(rand)) where {m,n,k} - return SVDMPoint( - Matrix{Float64}(undef, m, k), - Vector{Float64}(undef, k), - Matrix{Float64}(undef, k, n), - ) -end Base.copy(v::UMVTVector) = UMVTVector(copy(v.U), copy(v.M), copy(v.Vt)) @@ -195,15 +189,16 @@ Base.axes(::UMVTVector) = () end @doc raw""" - check_point(M::FixedRankMatrices{m,n,k}, p; kwargs...) + check_point(M::FixedRankMatrices, p; kwargs...) Check whether the matrix or [`SVDMPoint`](@ref) `x` ids a valid point on the -[`FixedRankMatrices`](@ref)`{m,n,k,𝔽}` `M`, i.e. is an `m`-by`n` matrix of +[`FixedRankMatrices`](@ref) `M`, i.e. is an `m`-by`n` matrix of rank `k`. For the [`SVDMPoint`](@ref) the internal representation also has to have the right shape, i.e. `p.U` and `p.Vt` have to be unitary. The keyword arguments are passed to the `rank` function that verifies the rank of `p`. """ -function check_point(M::FixedRankMatrices{m,n,k}, p; kwargs...) where {m,n,k} +function check_point(M::FixedRankMatrices, p; kwargs...) + m, n, k = get_parameter(M.size) r = rank(p; kwargs...) s = "The point $(p) does not lie on $(M), " if r > k @@ -211,7 +206,8 @@ function check_point(M::FixedRankMatrices{m,n,k}, p; kwargs...) where {m,n,k} end return nothing end -function check_point(M::FixedRankMatrices{m,n,k}, p::SVDMPoint; kwargs...) where {m,n,k} +function check_point(M::FixedRankMatrices, p::SVDMPoint; kwargs...) + m, n, k = get_parameter(M.size) s = "The point $(p) does not lie on $(M), " if !isapprox(p.U' * p.U, one(zeros(k, k)); kwargs...) return DomainError( @@ -228,7 +224,8 @@ function check_point(M::FixedRankMatrices{m,n,k}, p::SVDMPoint; kwargs...) where return nothing end -function check_size(M::FixedRankMatrices{m,n,k}, p::SVDMPoint) where {m,n,k} +function check_size(M::FixedRankMatrices, p::SVDMPoint) + m, n, k = get_parameter(M.size) if (size(p.U) != (m, k)) || (length(p.S) != k) || (size(p.Vt) != (k, n)) return DomainError( [size(p.U)..., length(p.S), size(p.Vt)...], @@ -236,7 +233,8 @@ function check_size(M::FixedRankMatrices{m,n,k}, p::SVDMPoint) where {m,n,k} ) end end -function check_size(M::FixedRankMatrices{m,n,k}, p) where {m,n,k} +function check_size(M::FixedRankMatrices, p) + m, n, k = get_parameter(M.size) pS = svd(p) if (size(pS.U) != (m, k)) || (length(pS.S) != k) || (size(pS.Vt) != (k, n)) return DomainError( @@ -245,7 +243,8 @@ function check_size(M::FixedRankMatrices{m,n,k}, p) where {m,n,k} ) end end -function check_size(M::FixedRankMatrices{m,n,k}, p, X::UMVTVector) where {m,n,k} +function check_size(M::FixedRankMatrices, p, X::UMVTVector) + m, n, k = get_parameter(M.size) if (size(X.U) != (m, k)) || (size(X.Vt) != (k, n)) || (size(X.M) != (k, k)) return DomainError( cat(size(X.U), size(X.M), size(X.Vt), dims=1), @@ -255,18 +254,14 @@ function check_size(M::FixedRankMatrices{m,n,k}, p, X::UMVTVector) where {m,n,k} end @doc raw""" - check_vector(M:FixedRankMatrices{m,n,k}, p, X; kwargs...) + check_vector(M:FixedRankMatrices, p, X; kwargs...) Check whether the tangent [`UMVTVector`](@ref) `X` is from the tangent space of the [`SVDMPoint`](@ref) `p` on the [`FixedRankMatrices`](@ref) `M`, i.e. that `v.U` and `v.Vt` are (columnwise) orthogonal to `x.U` and `x.Vt`, respectively, and its dimensions are consistent with `p` and `X.M`, i.e. correspond to `m`-by-`n` matrices of rank `k`. """ -function check_vector( - M::FixedRankMatrices{m,n,k}, - p::SVDMPoint, - X::UMVTVector; - kwargs..., -) where {m,n,k} +function check_vector(M::FixedRankMatrices, p::SVDMPoint, X::UMVTVector; kwargs...) + m, n, k = get_parameter(M.size) if !isapprox(X.U' * p.U, zeros(k, k); kwargs...) return DomainError( norm(X.U' * p.U - zeros(k, k)), @@ -355,7 +350,13 @@ function embed!(::FixedRankMatrices, Y, p::SVDMPoint, X::UMVTVector) return mul!(Y, p.U, X.Vt, true, true) end -get_embedding(::FixedRankMatrices{m,n,k,𝔽}) where {m,n,k,𝔽} = Euclidean(m, n; field=𝔽) +function get_embedding(::FixedRankMatrices{TypeParameter{Tuple{m,n,k}},𝔽}) where {m,n,k,𝔽} + return Euclidean(m, n; field=𝔽) +end +function get_embedding(M::FixedRankMatrices{Tuple{Int,Int,Int},𝔽}) where {𝔽} + m, n, k = get_parameter(M.size) + return Euclidean(m, n; field=𝔽, parameter=:field) +end """ injectivity_radius(::FixedRankMatrices) @@ -363,7 +364,7 @@ get_embedding(::FixedRankMatrices{m,n,k,𝔽}) where {m,n,k,𝔽} = Euclidean(m, Return the incjectivity radius of the manifold of [`FixedRankMatrices`](@ref), i.e. 0. See [HosseiniUschmajew:2017](@cite). """ -function injectivity_radius(::FixedRankMatrices{m,n,k}) where {m,n,k} +function injectivity_radius(::FixedRankMatrices) return 0.0 end @@ -409,7 +410,7 @@ function number_eltype(X::UMVTVector) end @doc raw""" - manifold_dimension(M::FixedRankMatrices{m,n,k,𝔽}) + manifold_dimension(M::FixedRankMatrices) Return the manifold dimension for the `𝔽`-valued [`FixedRankMatrices`](@ref) `M` of dimension `m`x`n` of rank `k`, namely @@ -420,7 +421,8 @@ of dimension `m`x`n` of rank `k`, namely where ``\dim_ℝ 𝔽`` is the [`real_dimension`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.real_dimension-Tuple{ManifoldsBase.AbstractNumbers}) of `𝔽`. """ -function manifold_dimension(::FixedRankMatrices{m,n,k,𝔽}) where {m,n,k,𝔽} +function manifold_dimension(M::FixedRankMatrices{<:Any,𝔽}) where {𝔽} + m, n, k = get_parameter(M.size) return (m + n - k) * k * real_dimension(𝔽) end @@ -459,14 +461,36 @@ and the singular values are sampled uniformly at random. If `vector_at` is not `nothing`, generate a random tangent vector in the tangent space of the point `vector_at` on the `FixedRankMatrices` manifold `M`. """ -Random.rand(M::FixedRankMatrices; vector_at=nothing, kwargs...) +function Random.rand(M::FixedRankMatrices; vector_at=nothing, kwargs...) + return rand(Random.default_rng(), M; vector_at=vector_at, kwargs...) +end +function Random.rand(rng::AbstractRNG, M::FixedRankMatrices; vector_at=nothing, kwargs...) + m, n, k = get_parameter(M.size) + if vector_at === nothing + p = SVDMPoint( + Matrix{Float64}(undef, m, k), + Vector{Float64}(undef, k), + Matrix{Float64}(undef, k, n), + ) + return rand!(rng, M, p; kwargs...) + else + X = UMVTVector( + Matrix{Float64}(undef, m, k), + Matrix{Float64}(undef, k, k), + Matrix{Float64}(undef, k, n), + ) + return rand!(rng, M, X; vector_at, kwargs...) + end +end + function Random.rand!( rng::AbstractRNG, - ::FixedRankMatrices{m,n,k}, + M::FixedRankMatrices, pX; vector_at=nothing, kwargs..., -) where {m,n,k} +) + m, n, k = get_parameter(M.size) if vector_at === nothing U = rand(rng, Stiefel(m, k); kwargs...) S = sort(rand(rng, k); rev=true) @@ -489,12 +513,15 @@ function Random.rand!( end @doc raw""" - representation_size(M::FixedRankMatrices{m,n,k}) + representation_size(M::FixedRankMatrices) Return the element size of a point on the [`FixedRankMatrices`](@ref) `M`, i.e. the size of matrices on this manifold ``(m,n)``. """ -@generated representation_size(::FixedRankMatrices{m,n}) where {m,n} = (m, n) +function representation_size(M::FixedRankMatrices) + m, n, k = get_parameter(M.size) + return (m, n) +end @doc raw""" retract(M, p, X, ::PolarRetraction) @@ -510,18 +537,19 @@ singular values and ``U`` and ``V`` are shortened accordingly. retract(::FixedRankMatrices, ::Any, ::Any, ::PolarRetraction) function retract_polar!( - ::FixedRankMatrices{m,n,k}, + M::FixedRankMatrices, q::SVDMPoint, p::SVDMPoint, X::UMVTVector, t::Number, -) where {m,n,k} +) + m, n, k = get_parameter(M.size) tX = t * X QU, RU = qr([p.U tX.U]) QV, RV = qr([p.Vt' tX.Vt']) # Compute T = svd(RU * [diagm(p.S) + X.M I; I zeros(k, k)] * RV') - @views begin + @views begin # COV_EXCL_LINE RU11 = RU[:, 1:k] RU12 = RU[:, (k + 1):(2 * k)] RV11 = RV[:, 1:k] @@ -562,8 +590,15 @@ function riemannian_Hessian!(M::FixedRankMatrices, Y, p, G, H, X) return Y end -function Base.show(io::IO, ::FixedRankMatrices{M,N,K,𝔽}) where {M,N,K,𝔽} - return print(io, "FixedRankMatrices($(M), $(N), $(K), $(𝔽))") +function Base.show( + io::IO, + ::FixedRankMatrices{TypeParameter{Tuple{m,n,k}},𝔽}, +) where {m,n,k,𝔽} + return print(io, "FixedRankMatrices($(m), $(n), $(k), $(𝔽))") +end +function Base.show(io::IO, M::FixedRankMatrices{Tuple{Int,Int,Int},𝔽}) where {𝔽} + m, n, k = get_parameter(M.size) + return print(io, "FixedRankMatrices($(m), $(n), $(k), $(𝔽); parameter=:field)") end function Base.show(io::IO, ::MIME"text/plain", p::SVDMPoint) pre = " " @@ -618,7 +653,8 @@ Return a [`UMVTVector`](@ref) representing the zero tangent vector in the tangen `p` on the [`FixedRankMatrices`](@ref) `M`, for example all three elements of the resulting structure are zero matrices. """ -function zero_vector(::FixedRankMatrices{m,n,k}, p::SVDMPoint) where {m,n,k} +function zero_vector(M::FixedRankMatrices, p::SVDMPoint) + m, n, k = get_parameter(M.size) v = UMVTVector( zeros(eltype(p.U), m, k), zeros(eltype(p.S), k, k), @@ -627,9 +663,9 @@ function zero_vector(::FixedRankMatrices{m,n,k}, p::SVDMPoint) where {m,n,k} return v end -function zero_vector!(::FixedRankMatrices{m,n,k}, X::UMVTVector, p::SVDMPoint) where {m,n,k} - X.U .= zeros(eltype(X.U), m, k) - X.M .= zeros(eltype(X.M), k, k) - X.Vt .= zeros(eltype(X.Vt), k, n) +function zero_vector!(::FixedRankMatrices, X::UMVTVector, p::SVDMPoint) + X.U .= zero(eltype(X.U)) + X.M .= zero(eltype(X.M)) + X.Vt .= zero(eltype(X.Vt)) return X end diff --git a/src/manifolds/Flag.jl b/src/manifolds/Flag.jl index 46ba6b6d13..542d9bb5b2 100644 --- a/src/manifolds/Flag.jl +++ b/src/manifolds/Flag.jl @@ -45,7 +45,7 @@ function Base.getindex(t::ZeroTuple, i::Int) end @doc raw""" - Flag{N,d} <: AbstractDecoratorManifold{ℝ} + Flag{T,d} <: AbstractDecoratorManifold{ℝ} Flag manifold of ``d`` subspaces of ``ℝ^N`` [YeWongLim:2021](@cite). By default the manifold uses the Stiefel coordinates representation, embedding it in the [`Stiefel`](@ref) manifold. @@ -56,7 +56,7 @@ Tangent space is represented in the block-skew-symmetric form. # Constructor - Flag(N, n1, n2, ..., nd) + Flag(N, n1, n2, ..., nd; parameter::Symbol=:type) Generate the manifold ``\operatorname{Flag}(n_1, n_2, ..., n_d; N)`` of subspaces ```math @@ -64,12 +64,16 @@ Generate the manifold ``\operatorname{Flag}(n_1, n_2, ..., n_d; N)`` of subspace ``` where ``𝕍_i`` for ``i ∈ 1, 2, …, d`` are subspaces of ``ℝ^N`` of dimension ``\operatorname{dim} 𝕍_i = n_i``. + +`parameter`: whether a type parameter should be used to store `n`. By default size +is stored in type. Value can either be `:field` or `:type`. """ -struct Flag{N,dp1} <: AbstractDecoratorManifold{ℝ} +struct Flag{T,dp1} <: AbstractDecoratorManifold{ℝ} subspace_dimensions::ZeroTuple{NTuple{dp1,Int}} + size::T end -function Flag(N, ns::Vararg{Int,I}) where {I} +function Flag(N::Int, ns::Vararg{Int,I}; parameter::Symbol=:type) where {I} if ns[1] <= 0 error( "First dimension in the sequence ns must be strictly positive, but is $(ns[1]).", @@ -85,7 +89,8 @@ function Flag(N, ns::Vararg{Int,I}) where {I} "Last dimension in sequence (given: $(ns[end])) must be strictly lower than N (given: $N).", ) end - return Flag{N,I + 1}(ZeroTuple(tuple(ns..., N))) + size = wrap_type_parameter(parameter, (N,)) + return Flag{typeof(size),I + 1}(ZeroTuple(tuple(ns..., N)), size) end function active_traits(f, ::Flag, args...) @@ -97,7 +102,12 @@ end Get the embedding of the [`Flag`](@ref) manifold `M`, i.e. the [`Stiefel`](@ref) manifold. """ -get_embedding(M::Flag{N,dp1}) where {N,dp1} = Stiefel(N, M.subspace_dimensions[dp1 - 1]) +function get_embedding(M::Flag{Tuple{Int},dp1}) where {dp1} + return Stiefel(M.size[1], M.subspace_dimensions[dp1 - 1]; parameter=:field) +end +function get_embedding(M::Flag{TypeParameter{Tuple{N}},dp1}) where {N,dp1} + return Stiefel(N, M.subspace_dimensions[dp1 - 1]) +end @doc raw""" injectivity_radius(M::Flag) @@ -124,7 +134,8 @@ end Return dimension of flag manifold ``\operatorname{Flag}(n_1, n_2, ..., n_d; N)``. The formula reads ``\sum_{i=1}^d (n_i-n_{i-1})(N-n_i)``. """ -function manifold_dimension(M::Flag{N,dp1}) where {N,dp1} +function manifold_dimension(M::Flag{<:Any,dp1}) where {dp1} + N = get_parameter(M.size)[1] dim = 0 for i in 1:(dp1 - 1) dim += @@ -134,13 +145,21 @@ function manifold_dimension(M::Flag{N,dp1}) where {N,dp1} return dim end -function Base.show(io::IO, M::Flag{N}) where {N} +function Base.show(io::IO, M::Flag{TypeParameter{Tuple{N}}}) where {N} print(io, "Flag($(N)") for d_i in M.subspace_dimensions.x[1:(end - 1)] print(io, ", $d_i") end return print(io, ")") end +function Base.show(io::IO, M::Flag{Tuple{Int}}) + N = get_parameter(M.size)[1] + print(io, "Flag($(N)") + for d_i in M.subspace_dimensions.x[1:(end - 1)] + print(io, ", $d_i") + end + return print(io, "; parameter=:field)") +end """ convert(::Type{AbstractMatrix}, M::Flag, p::OrthogonalPoint, X::OrthogonalTVector) diff --git a/src/manifolds/FlagOrthogonal.jl b/src/manifolds/FlagOrthogonal.jl index bd5c948bc5..c6d897b70a 100644 --- a/src/manifolds/FlagOrthogonal.jl +++ b/src/manifolds/FlagOrthogonal.jl @@ -16,11 +16,11 @@ X = \begin{bmatrix} where ``B_{i,j} ∈ ℝ^{(n_i - n_{i-1}) × (n_j - n_{j-1})}``, for ``1 ≤ i < j ≤ d+1``. """ function check_vector( - M::Flag{N,dp1}, + M::Flag{<:Any,dp1}, p::OrthogonalPoint, X::OrthogonalTVector; kwargs..., -) where {N,dp1} +) where {dp1} for i in 1:dp1 for j in i:dp1 if i == j @@ -59,7 +59,12 @@ end Get embedding of [`Flag`](@ref) manifold `M`, i.e. the manifold [`OrthogonalMatrices`](@ref). """ -get_embedding(::Flag{N}, p::OrthogonalPoint) where {N} = OrthogonalMatrices(N) +function get_embedding(::Flag{TypeParameter{Tuple{N}}}, p::OrthogonalPoint) where {N} + return OrthogonalMatrices(N) +end +function get_embedding(M::Flag{Tuple{Int}}, p::OrthogonalPoint) + return OrthogonalMatrices(M.size[1]; parameter=:field) +end function _extract_flag(M::Flag, p::AbstractMatrix, i::Int) range = (M.subspace_dimensions[i - 1] + 1):M.subspace_dimensions[i] @@ -77,11 +82,12 @@ function inner(::Flag, p::OrthogonalPoint, X::OrthogonalTVector, Y::OrthogonalTV end function project!( - M::Flag{N,dp1}, + M::Flag{<:Any,dp1}, Y::OrthogonalTVector, ::OrthogonalPoint, X::OrthogonalTVector, -) where {N,dp1} +) where {dp1} + N = get_parameter(M.size)[1] project!(SkewHermitianMatrices(N), Y.value, X.value) for i in 1:dp1 Bi = _extract_flag(M, Y.value, i) @@ -107,7 +113,8 @@ X = \begin{bmatrix} ```` where ``B_{i,j} ∈ ℝ^{(n_i - n_{i-1}) × (n_j - n_{j-1})}``, for ``1 ≤ i < j ≤ d+1``. """ -function project(M::Flag{N,dp1}, ::OrthogonalPoint, X::OrthogonalTVector) where {N,dp1} +function project(M::Flag{<:Any,dp1}, ::OrthogonalPoint, X::OrthogonalTVector) where {dp1} + N = get_parameter(M.size)[1] Y = project(SkewHermitianMatrices(N), X.value) for i in 1:dp1 Bi = _extract_flag(M, Y, i) @@ -118,11 +125,12 @@ end function Random.rand!( rng::AbstractRNG, - M::Flag{N,dp1}, + M::Flag{<:Any,dp1}, pX::Union{OrthogonalPoint,OrthogonalTVector}; vector_at=nothing, -) where {N,dp1} +) where {dp1} if vector_at === nothing + N = get_parameter(M.size)[1] RN = Rotations(N) rand!(rng, RN, pX.value) else diff --git a/src/manifolds/FlagStiefel.jl b/src/manifolds/FlagStiefel.jl index 40cd6aee59..11d2bee355 100644 --- a/src/manifolds/FlagStiefel.jl +++ b/src/manifolds/FlagStiefel.jl @@ -24,11 +24,11 @@ as the default vector transport method for the [`Flag`](@ref) manifold. default_vector_transport_method(::Flag) = ProjectionTransport() function inner( - M::Flag{N,dp1}, + M::Flag{<:Any,dp1}, p::AbstractMatrix, X::AbstractMatrix, Y::AbstractMatrix, -) where {N,dp1} +) where {dp1} inner_prod = zero(eltype(X)) pX = p' * X pY = p' * Y @@ -84,11 +84,11 @@ X = \begin{bmatrix} where ``B_{i,j} ∈ ℝ^{(n_i - n_{i-1}) × (n_j - n_{j-1})}``, for ``1 ≤ i < j ≤ d+1``. """ function check_vector( - M::Flag{N,dp1}, + M::Flag{<:Any,dp1}, p::AbstractMatrix, X::AbstractMatrix; atol=sqrt(eps(eltype(X))), -) where {N,dp1} +) where {dp1} for i in 1:(dp1 - 1) p_i = _extract_flag_stiefel(M, p, i) X_i = _extract_flag_stiefel(M, X, i) @@ -132,11 +132,11 @@ matrices for consecutive subspaces of the flag. project(::Flag, p, X) function project!( - M::Flag{N,dp1}, + M::Flag{<:Any,dp1}, Y::AbstractMatrix, p::AbstractMatrix, X::AbstractMatrix, -) where {N,dp1} +) where {dp1} Xc = X .- p * (p' * X) ./ 2 for i in 1:(dp1 - 1) Y_i = _extract_flag_stiefel(M, Y, i) @@ -158,12 +158,7 @@ function project!(M::Flag, q::AbstractMatrix, p::AbstractMatrix) return project!(get_embedding(M), q, p) end -function Random.rand!( - rng::AbstractRNG, - M::Flag{N,dp1}, - pX::AbstractMatrix; - vector_at=nothing, -) where {N,dp1} +function Random.rand!(rng::AbstractRNG, M::Flag, pX::AbstractMatrix; vector_at=nothing) EM = get_embedding(M) if vector_at === nothing rand!(rng, EM, pX) diff --git a/src/manifolds/GeneralUnitaryMatrices.jl b/src/manifolds/GeneralUnitaryMatrices.jl index db1930c27d..c31bcf84da 100644 --- a/src/manifolds/GeneralUnitaryMatrices.jl +++ b/src/manifolds/GeneralUnitaryMatrices.jl @@ -22,12 +22,24 @@ i.e. that the absolute value of the determinant is 1. struct AbsoluteDeterminantOneMatrices <: AbstractMatrixType end @doc raw""" - GeneralUnitaryMatrices{n,𝔽,S<:AbstractMatrixType} <: AbstractDecoratorManifold + GeneralUnitaryMatrices{T,𝔽,S<:AbstractMatrixType} <: AbstractDecoratorManifold -A common parametric type for matrices with a unitary property of size ``n×n`` over the field ``\mathbb F`` +A common parametric type for matrices with a unitary property of size ``n×n`` over the field ``𝔽`` which additionally have the `AbstractMatrixType`, e.g. are `DeterminantOneMatrices`. """ -struct GeneralUnitaryMatrices{n,𝔽,S<:AbstractMatrixType} <: AbstractDecoratorManifold{𝔽} end +struct GeneralUnitaryMatrices{T,𝔽,S<:AbstractMatrixType} <: AbstractDecoratorManifold{𝔽} + size::T +end + +function GeneralUnitaryMatrices( + n::Int, + field, + matrix_type::Type{<:AbstractMatrixType}; + parameter::Symbol=:type, +) + size = wrap_type_parameter(parameter, (n,)) + return GeneralUnitaryMatrices{typeof(size),field,matrix_type}(size) +end function active_traits(f, ::GeneralUnitaryMatrices, args...) return merge_traits(IsEmbeddedManifold(), IsDefaultMetric(EuclideanMetric())) @@ -36,7 +48,7 @@ end @doc raw""" check_point(M::UnitaryMatrices, p; kwargs...) check_point(M::OrthogonalMatrices, p; kwargs...) - check_point(M::GeneralUnitaryMatrices{n,𝔽}, p; kwargs...) + check_point(M::GeneralUnitaryMatrices, p; kwargs...) Check whether `p` is a valid point on the [`UnitaryMatrices`](@ref) or [`OrthogonalMatrices`] `M`, i.e. that ``p`` has an determinante of absolute value one @@ -44,10 +56,10 @@ i.e. that ``p`` has an determinante of absolute value one The tolerance for the last test can be set using the `kwargs...`. """ function check_point( - M::GeneralUnitaryMatrices{n,𝔽,AbsoluteDeterminantOneMatrices}, + M::GeneralUnitaryMatrices{<:Any,𝔽,AbsoluteDeterminantOneMatrices}, p; kwargs..., -) where {n,𝔽} +) where {𝔽} if !isapprox(abs(det(p)), 1; kwargs...) return DomainError( abs(det(p)), @@ -72,10 +84,10 @@ i.e. that ``p`` has an determinante of absolute value one, i.e. that ``p^{\mathr The tolerance for the last test can be set using the `kwargs...`. """ function check_point( - M::GeneralUnitaryMatrices{n,𝔽,DeterminantOneMatrices}, + M::GeneralUnitaryMatrices{<:Any,𝔽,DeterminantOneMatrices}, p; kwargs..., -) where {n,𝔽} +) where {𝔽} if !isapprox(det(p), 1; kwargs...) return DomainError(det(p), "The determinant of $p has to be +1 but it is $(det(p))") end @@ -88,7 +100,8 @@ function check_point( return nothing end -function check_size(::GeneralUnitaryMatrices{n}, p) where {n} +function check_size(M::GeneralUnitaryMatrices, p) + n = get_parameter(M.size)[1] m = size(p) if length(m) != 2 return DomainError( @@ -104,7 +117,8 @@ function check_size(::GeneralUnitaryMatrices{n}, p) where {n} end return nothing end -function check_size(::GeneralUnitaryMatrices{n}, p, X) where {n} +function check_size(M::GeneralUnitaryMatrices, p, X) + n = get_parameter(M.size)[1] m = size(X) if length(size(X)) != 2 return DomainError( @@ -122,10 +136,10 @@ function check_size(::GeneralUnitaryMatrices{n}, p, X) where {n} end @doc raw""" - check_vector(M::UnitaryMatrices{n}, p, X; kwargs... ) - check_vector(M::OrthogonalMatrices{n}, p, X; kwargs... ) - check_vector(M::Rotations{n}, p, X; kwargs... ) - check_vector(M::GeneralUnitaryMatrices{n,𝔽}, p, X; kwargs... ) + check_vector(M::UnitaryMatrices, p, X; kwargs... ) + check_vector(M::OrthogonalMatrices, p, X; kwargs... ) + check_vector(M::Rotations, p, X; kwargs... ) + check_vector(M::GeneralUnitaryMatrices, p, X; kwargs... ) Check whether `X` is a tangent vector to `p` on the [`UnitaryMatrices`](@ref) space `M`, i.e. after [`check_point`](@ref)`(M,p)`, `X` has to be skew symmetric (Hermitian) @@ -133,7 +147,8 @@ and orthogonal to `p`. The tolerance for the last test can be set using the `kwargs...`. """ -function check_vector(M::GeneralUnitaryMatrices{n,𝔽}, p, X; kwargs...) where {n,𝔽} +function check_vector(M::GeneralUnitaryMatrices{<:Any,𝔽}, p, X; kwargs...) where {𝔽} + n = get_parameter(M.size)[1] return check_point(SkewHermitianMatrices(n, 𝔽), X; kwargs...) end @@ -163,14 +178,14 @@ function cos_angles_4d_rotation_matrix(R) return ((a + b) / 4, (a - b) / 4) end -function default_estimation_method(::GeneralUnitaryMatrices{n,ℝ}, ::typeof(mean)) where {n} +function default_estimation_method(::GeneralUnitaryMatrices{<:Any,ℝ}, ::typeof(mean)) return GeodesicInterpolationWithinRadius(π / 2 / √2) end embed(::GeneralUnitaryMatrices, p) = p @doc raw""" - embed(M::GeneralUnitaryMatrices{n,𝔽}, p, X) + embed(M::GeneralUnitaryMatrices, p, X) Embed the tangent vector `X` at point `p` in `M` from its Lie algebra representation (set of skew matrices) into the @@ -198,7 +213,7 @@ Compute the exponential map, that is, since ``X`` is represented in the Lie alge exp_p(X) = p\mathrm{e}^X ``` -For different sizes, like ``n=2,3,4`` there is specialised implementations +For different sizes, like ``n=2,3,4``, there are specialized implementations. The algorithm used is a more numerically stable form of those proposed in [GallierXu:2002](@cite) and [AndricaRohan:2013](@cite). @@ -212,15 +227,20 @@ function exp!(M::GeneralUnitaryMatrices, q, p, X, t::Number) return copyto!(M, q, p * exp(t * X)) end -function exp(M::GeneralUnitaryMatrices{2,ℝ}, p::SMatrix, X::SMatrix) +function exp(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, p::SMatrix, X::SMatrix) θ = get_coordinates(M, p, X, DefaultOrthogonalBasis())[1] sinθ, cosθ = sincos(θ) return p * SA[cosθ -sinθ; sinθ cosθ] end -function exp(M::GeneralUnitaryMatrices{2,ℝ}, p::SMatrix, X::SMatrix, t::Real) +function exp( + M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, + p::SMatrix, + X::SMatrix, + t::Real, +) return exp(M, p, t * X) end -function exp(M::GeneralUnitaryMatrices{3,ℝ}, p::SMatrix, X::SMatrix) +function exp(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, p::SMatrix, X::SMatrix) θ = norm(M, p, X) / sqrt(2) if θ ≈ 0 a = 1 - θ^2 / 6 @@ -232,20 +252,22 @@ function exp(M::GeneralUnitaryMatrices{3,ℝ}, p::SMatrix, X::SMatrix) pinvq = I + a .* X .+ b .* (X^2) return p * pinvq end -function exp!(M::GeneralUnitaryMatrices{2,ℝ}, q, p, X) +function exp!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, q, p, X) @assert size(q) == (2, 2) θ = get_coordinates(M, p, X, DefaultOrthogonalBasis())[1] sinθ, cosθ = sincos(θ) return copyto!(q, p * SA[cosθ -sinθ; sinθ cosθ]) end -function exp!(M::GeneralUnitaryMatrices{2,ℝ}, q, p, X, t::Real) +function exp!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, q, p, X, t::Real) @assert size(q) == (2, 2) θ = get_coordinates(M, p, X, DefaultOrthogonalBasis())[1] sinθ, cosθ = sincos(t * θ) return copyto!(q, p * SA[cosθ -sinθ; sinθ cosθ]) end -exp!(M::GeneralUnitaryMatrices{3,ℝ}, q, p, X) = exp!(M, q, p, X, one(eltype(X))) -function exp!(M::GeneralUnitaryMatrices{3,ℝ}, q, p, X, t::Real) +function exp!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, q, p, X) + return exp!(M, q, p, X, one(eltype(X))) +end +function exp!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, q, p, X, t::Real) θ = abs(t) * norm(M, p, X) / sqrt(2) if θ ≈ 0 a = 1 - θ^2 / 6 @@ -257,8 +279,10 @@ function exp!(M::GeneralUnitaryMatrices{3,ℝ}, q, p, X, t::Real) pinvq = I + a .* t .* X .+ b .* t^2 .* (X^2) return copyto!(q, p * pinvq) end -exp!(M::GeneralUnitaryMatrices{4,ℝ}, q, p, X, t::Real) = exp!(M, q, p, t * X) -function exp!(::GeneralUnitaryMatrices{4,ℝ}, q, p, X) +function exp!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{4}},ℝ}, q, p, X, t::Real) + return exp!(M, q, p, t * X) +end +function exp!(::GeneralUnitaryMatrices{TypeParameter{Tuple{4}},ℝ}, q, p, X) T = eltype(X) α, β = angles_4d_skew_sym_matrix(X) sinα, cosα = sincos(α) @@ -318,9 +342,9 @@ along the axis of rotation. For $\mathrm{SO}(n)$ where $n ≥ 4$, the additional elements of $X^i$ are $X^{j (j - 3)/2 + k + 1} = X_{jk}$, for $j ∈ [4,n], k ∈ [1,j)$. """ -get_coordinates(::GeneralUnitaryMatrices{n,ℝ}, ::Any...) where {n} +get_coordinates(::GeneralUnitaryMatrices{<:Any,ℝ}, ::Any...) function get_coordinates( - ::GeneralUnitaryMatrices{2,ℝ}, + ::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, p, X, ::DefaultOrthogonalBasis{ℝ,TangentSpaceType}, @@ -328,35 +352,48 @@ function get_coordinates( return [X[2]] end function get_coordinates( - ::GeneralUnitaryMatrices{2,ℝ}, + ::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, p::SMatrix, X::SMatrix, ::DefaultOrthogonalBasis{ℝ,TangentSpaceType}, ) return SA[X[2]] end + function get_coordinates( - ::Manifolds.GeneralUnitaryMatrices{3,ℝ}, + ::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, p::SMatrix, X::SMatrix, ::DefaultOrthogonalBasis{ℝ,TangentSpaceType}, ) return SA[X[3, 2], X[1, 3], X[2, 1]] end -function get_coordinates_orthogonal(M::GeneralUnitaryMatrices{n,ℝ}, p, X, N) where {n} +function get_coordinates_orthogonal(M::GeneralUnitaryMatrices{<:Any,ℝ}, p, X, N) Y = allocate_result(M, get_coordinates, p, X, DefaultOrthogonalBasis(N)) return get_coordinates_orthogonal!(M, Y, p, X, N) end -function get_coordinates_orthogonal!(::GeneralUnitaryMatrices{1,ℝ}, Xⁱ, p, X, ::RealNumbers) +function get_coordinates_orthogonal!( + ::GeneralUnitaryMatrices{TypeParameter{Tuple{1}},ℝ}, + Xⁱ, + p, + X, + ::RealNumbers, +) return Xⁱ end -function get_coordinates_orthogonal!(::GeneralUnitaryMatrices{2,ℝ}, Xⁱ, p, X, ::RealNumbers) +function get_coordinates_orthogonal!( + ::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, + Xⁱ, + p, + X, + ::RealNumbers, +) Xⁱ[1] = X[2] return Xⁱ end function get_coordinates_orthogonal!( - M::GeneralUnitaryMatrices{n,ℝ}, + M::GeneralUnitaryMatrices{TypeParameter{Tuple{n}},ℝ}, Xⁱ, p, X, @@ -377,13 +414,40 @@ function get_coordinates_orthogonal!( end return Xⁱ end +function get_coordinates_orthogonal!( + M::GeneralUnitaryMatrices{Tuple{Int},ℝ}, + Xⁱ, + p, + X, + ::RealNumbers, +) + n = get_parameter(M.size)[1] + @assert length(Xⁱ) == manifold_dimension(M) + @assert size(X) == (n, n) + if n == 2 + Xⁱ[1] = X[2] + elseif n > 2 + @inbounds begin + Xⁱ[1] = X[3, 2] + Xⁱ[2] = X[1, 3] + Xⁱ[3] = X[2, 1] + + k = 4 + for i in 4:n, j in 1:(i - 1) + Xⁱ[k] = X[i, j] + k += 1 + end + end + end + return Xⁱ +end function get_coordinates_orthonormal!( - M::GeneralUnitaryMatrices{n,ℝ}, + M::GeneralUnitaryMatrices{<:Any,ℝ}, Xⁱ, p, X, num::RealNumbers, -) where {n} +) T = Base.promote_eltype(p, X) get_coordinates_orthogonal!(M, Xⁱ, p, X, num) Xⁱ .*= sqrt(T(2)) @@ -391,14 +455,20 @@ function get_coordinates_orthonormal!( end @doc raw""" - get_embedding(M::OrthogonalMatrices{n}) - get_embedding(M::Rotations{n}) - get_embedding(M::UnitaryMatrices{n}) + get_embedding(M::OrthogonalMatrices) + get_embedding(M::Rotations) + get_embedding(M::UnitaryMatrices) Return the embedding, i.e. The ``\mathbb F^{n×n}``, where ``\mathbb F = \mathbb R`` for the first two and ``\mathbb F = \mathbb C`` for the unitary matrices. """ -get_embedding(::GeneralUnitaryMatrices{n,𝔽}) where {n,𝔽} = Euclidean(n, n; field=𝔽) +function get_embedding(::GeneralUnitaryMatrices{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return Euclidean(n, n; field=𝔽) +end +function get_embedding(M::GeneralUnitaryMatrices{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return Euclidean(n, n; field=𝔽, parameter=:field) +end @doc raw""" get_vector(M::OrthogonalMatrices, p, Xⁱ, B::DefaultOrthogonalBasis) @@ -407,35 +477,52 @@ get_embedding(::GeneralUnitaryMatrices{n,𝔽}) where {n,𝔽} = Euclidean(n, n; Convert the unique tangent vector components `Xⁱ` at point `p` on [`Rotations`](@ref) or [`OrthogonalMatrices`](@ref) to the matrix representation $X$ of the tangent vector. See -[`get_coordinates`](@ref get_coordinates(::GeneralUnitaryMatrices{n,ℝ} where {n}, ::Any...)) for the conventions used. +[`get_coordinates`](@ref get_coordinates(::GeneralUnitaryMatrices, ::Any...)) for the conventions used. """ -get_vector(::GeneralUnitaryMatrices{n,ℝ}, ::Any...) where {n} +get_vector(::GeneralUnitaryMatrices{<:Any,ℝ}, ::Any...) -function get_vector_orthogonal( - M::GeneralUnitaryMatrices{n,ℝ}, - p, - c, - N::RealNumbers, -) where {n} +function get_vector_orthogonal(M::GeneralUnitaryMatrices{<:Any,ℝ}, p, c, N::RealNumbers) Y = allocate_result(M, get_vector, p, c) return get_vector_orthogonal!(M, Y, p, c, N) end -function get_vector_orthogonal(::GeneralUnitaryMatrices{2,ℝ}, p::SMatrix, Xⁱ, ::RealNumbers) +function get_vector_orthogonal( + ::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, + p::SMatrix, + Xⁱ, + ::RealNumbers, +) return @SMatrix [0 -Xⁱ[]; Xⁱ[] 0] end -function get_vector_orthogonal(::GeneralUnitaryMatrices{3,ℝ}, p::SMatrix, Xⁱ, ::RealNumbers) +function get_vector_orthogonal( + ::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, + p::SMatrix, + Xⁱ, + ::RealNumbers, +) return @SMatrix [0 -Xⁱ[3] Xⁱ[2]; Xⁱ[3] 0 -Xⁱ[1]; -Xⁱ[2] Xⁱ[1] 0] end -function get_vector_orthogonal!(::GeneralUnitaryMatrices{1,ℝ}, X, p, Xⁱ, N::RealNumbers) +function get_vector_orthogonal!( + ::GeneralUnitaryMatrices{TypeParameter{Tuple{1}},ℝ}, + X, + p, + Xⁱ, + N::RealNumbers, +) return X .= 0 end -function get_vector_orthogonal!(M::GeneralUnitaryMatrices{2,ℝ}, X, p, Xⁱ, N::RealNumbers) +function get_vector_orthogonal!( + M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, + X, + p, + Xⁱ, + N::RealNumbers, +) return get_vector_orthogonal!(M, X, p, Xⁱ[1], N) end function get_vector_orthogonal!( - ::GeneralUnitaryMatrices{2,ℝ}, + ::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, X, p, Xⁱ::Real, @@ -451,7 +538,7 @@ function get_vector_orthogonal!( return X end function get_vector_orthogonal!( - M::GeneralUnitaryMatrices{n,ℝ}, + M::GeneralUnitaryMatrices{TypeParameter{Tuple{n}},ℝ}, X, p, Xⁱ, @@ -459,6 +546,7 @@ function get_vector_orthogonal!( ) where {n} @assert size(X) == (n, n) @assert length(Xⁱ) == manifold_dimension(M) + @assert n > 2 @inbounds begin X[1, 1] = 0 X[1, 2] = -Xⁱ[3] @@ -481,22 +569,60 @@ function get_vector_orthogonal!( end return X end -function get_vector_orthonormal( - M::GeneralUnitaryMatrices{n,ℝ}, +function get_vector_orthogonal!( + M::GeneralUnitaryMatrices{Tuple{Int},ℝ}, + X, p, Xⁱ, - N::RealNumbers, -) where {n} + ::RealNumbers, +) + n = get_parameter(M.size)[1] + @assert size(X) == (n, n) + @assert length(Xⁱ) == manifold_dimension(M) + if n == 1 + X .= 0 + elseif n == 2 + @inbounds begin + X[1] = 0 + X[2] = Xⁱ[1] + X[3] = -Xⁱ[1] + X[4] = 0 + end + else + @inbounds begin + X[1, 1] = 0 + X[1, 2] = -Xⁱ[3] + X[1, 3] = Xⁱ[2] + X[2, 1] = Xⁱ[3] + X[2, 2] = 0 + X[2, 3] = -Xⁱ[1] + X[3, 1] = -Xⁱ[2] + X[3, 2] = Xⁱ[1] + X[3, 3] = 0 + k = 4 + for i in 4:n + for j in 1:(i - 1) + X[i, j] = Xⁱ[k] + X[j, i] = -Xⁱ[k] + k += 1 + end + X[i, i] = 0 + end + end + end + return X +end +function get_vector_orthonormal(M::GeneralUnitaryMatrices{<:Any,ℝ}, p, Xⁱ, N::RealNumbers) return get_vector_orthogonal(M, p, Xⁱ, N) ./ sqrt(eltype(Xⁱ)(2)) end function get_vector_orthonormal!( - M::GeneralUnitaryMatrices{n,ℝ}, + M::GeneralUnitaryMatrices{<:Any,ℝ}, X, p, Xⁱ, N::RealNumbers, -) where {n} +) T = Base.promote_eltype(p, X) get_vector_orthogonal!(M, X, p, Xⁱ, N) X ./= sqrt(T(2)) @@ -515,7 +641,7 @@ Return the injectivity radius for general unitary matrix manifolds, which is[^1] injectivity_radius(::GeneralUnitaryMatrices) = π @doc raw""" - injectivity_radius(G::GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}) + injectivity_radius(G::GeneralUnitaryMatrices{<:Any,ℂ,DeterminantOneMatrices}) Return the injectivity radius for general complex unitary matrix manifolds, where the determinant is $+1$, which is[^1] @@ -524,9 +650,7 @@ which is[^1] \operatorname{inj}_{\mathrm{SU}(n)} = π \sqrt{2}. ``` """ -function injectivity_radius( - ::GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}, -) where {n,ℂ} +function injectivity_radius(::GeneralUnitaryMatrices{<:Any,ℂ,DeterminantOneMatrices}) return π * sqrt(2.0) end @@ -542,24 +666,23 @@ Return the radius of injectivity on the [`Rotations`](@ref) manifold `M`, which [^1]: > For a derivation of the injectivity radius, see [sethaxen.com/blog/2023/02/the-injectivity-radii-of-the-unitary-groups/](https://sethaxen.com/blog/2023/02/the-injectivity-radii-of-the-unitary-groups/). """ -injectivity_radius(::GeneralUnitaryMatrices{n,ℝ}) where {n} = π * sqrt(2.0) -injectivity_radius(::GeneralUnitaryMatrices{1,ℝ}) = 0.0 - -# Resolve ambiguity on Rotations and Orthogonal -function _injectivity_radius( - ::GeneralUnitaryMatrices{n,ℝ}, - ::ExponentialRetraction, -) where {n} +function injectivity_radius(::GeneralUnitaryMatrices{TypeParameter{Tuple{n}},ℝ}) where {n} return π * sqrt(2.0) end -function _injectivity_radius(::GeneralUnitaryMatrices{n,ℝ}, ::PolarRetraction) where {n} - return π / sqrt(2.0) +function injectivity_radius(M::GeneralUnitaryMatrices{Tuple{Int},ℝ}) + n = get_parameter(M.size)[1] + return n == 1 ? 0.0 : π * sqrt(2.0) end -function _injectivity_radius(::GeneralUnitaryMatrices{1,ℝ}, ::ExponentialRetraction) - return 0.0 +injectivity_radius(::GeneralUnitaryMatrices{TypeParameter{Tuple{1}},ℝ}) = 0.0 + +# Resolve ambiguity on Rotations and Orthogonal +function _injectivity_radius(M::GeneralUnitaryMatrices{<:Any,ℝ}, ::ExponentialRetraction) + n = get_parameter(M.size)[1] + return n == 1 ? 0.0 : π * sqrt(2.0) end -function _injectivity_radius(::GeneralUnitaryMatrices{1,ℝ}, ::PolarRetraction) - return 0.0 +function _injectivity_radius(M::GeneralUnitaryMatrices{<:Any,ℝ}, ::PolarRetraction) + n = get_parameter(M.size)[1] + return n == 1 ? 0.0 : π / sqrt(2.0) end inner(::GeneralUnitaryMatrices, p, X, Y) = dot(X, Y) @@ -570,8 +693,14 @@ inner(::GeneralUnitaryMatrices, p, X, Y) = dot(X, Y) Return true if [`GeneralUnitaryMatrices`](@ref) `M` is SO(2) or U(1) and false otherwise. """ is_flat(M::GeneralUnitaryMatrices) = false -is_flat(M::GeneralUnitaryMatrices{2,ℝ}) = true -is_flat(M::GeneralUnitaryMatrices{1,ℂ}) = true +is_flat(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}) = true +is_flat(M::GeneralUnitaryMatrices{TypeParameter{Tuple{1}},ℂ}) = true +function is_flat(M::GeneralUnitaryMatrices{Tuple{Int64},ℝ}) + return M.size[1] == 2 +end +function is_flat(M::GeneralUnitaryMatrices{Tuple{Int64},ℂ}) + return M.size[1] == 1 +end @doc raw""" log(M::Rotations, p, X) @@ -603,39 +732,41 @@ the result is projected onto the set of skew symmetric matrices. For antipodal rotations the function returns deterministically one of the tangent vectors that point at `q`. """ -log(::GeneralUnitaryMatrices{n,ℝ}, ::Any...) where {n} -function ManifoldsBase.log(M::GeneralUnitaryMatrices{2,ℝ}, p, q) +log(::GeneralUnitaryMatrices{<:Any,ℝ}, ::Any...) +function ManifoldsBase.log(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, p, q) U = transpose(p) * q @assert size(U) == (2, 2) @inbounds θ = atan(U[2], U[1]) return get_vector(M, p, θ, DefaultOrthogonalBasis()) end -function log(M::Manifolds.GeneralUnitaryMatrices{3,ℝ}, p::SMatrix, q::SMatrix) + +function log(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, p::SMatrix, q::SMatrix) U = transpose(p) * q cosθ = (tr(U) - 1) / 2 if cosθ ≈ -1 - eig = Manifolds.eigen_safe(U) + eig = eigen_safe(U) ival = findfirst(λ -> isapprox(λ, 1), eig.values) inds = SVector{3}(1:3) #TODO this is to stop convert error of ax as a complex number ax::Vector{Float64} = eig.vectors[inds, ival] return get_vector(M, p, π * ax, DefaultOrthogonalBasis()) end - X = U ./ Manifolds.usinc_from_cos(cosθ) + X = U ./ usinc_from_cos(cosθ) return (X .- X') ./ 2 end -function log!(::GeneralUnitaryMatrices{n,ℝ}, X, p, q) where {n} +function log!(M::GeneralUnitaryMatrices{<:Any,ℝ}, X, p, q) U = transpose(p) * q X .= real(log_safe(U)) + n = get_parameter(M.size)[1] return project!(SkewSymmetricMatrices(n), X, p, X) end -function log!(M::GeneralUnitaryMatrices{2,ℝ}, X, p, q) +function log!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, X, p, q) U = transpose(p) * q @assert size(U) == (2, 2) @inbounds θ = atan(U[2], U[1]) return get_vector!(M, X, p, θ, DefaultOrthogonalBasis()) end -function log!(M::GeneralUnitaryMatrices{3,ℝ}, X, p, q) +function log!(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, X, p, q) U = transpose(p) * q cosθ = (tr(U) - 1) / 2 if cosθ ≈ -1 @@ -648,7 +779,7 @@ function log!(M::GeneralUnitaryMatrices{3,ℝ}, X, p, q) X .= U ./ usinc_from_cos(cosθ) return project!(SkewSymmetricMatrices(3), X, p, X) end -function log!(::GeneralUnitaryMatrices{4,ℝ}, X, p, q) +function log!(::GeneralUnitaryMatrices{TypeParameter{Tuple{4}},ℝ}, X, p, q) U = transpose(p) * q cosα, cosβ = Manifolds.cos_angles_4d_rotation_matrix(U) α = acos(clamp(cosα, -1, 1)) @@ -674,8 +805,9 @@ function log!(::GeneralUnitaryMatrices{4,ℝ}, X, p, q) return project!(SkewSymmetricMatrices(4), X, p, X) end -function log!(::GeneralUnitaryMatrices{n,𝔽}, X, p, q) where {n,𝔽} +function log!(M::GeneralUnitaryMatrices{<:Any,𝔽}, X, p, q) where {𝔽} log_safe!(X, adjoint(p) * q) + n = get_parameter(M.size)[1] project!(SkewHermitianMatrices(n, 𝔽), X, X) return X end @@ -691,19 +823,25 @@ Return the dimension of the manifold orthogonal matrices and of the manifold of \dim_{\mathrm{O}(n)} = \dim_{\mathrm{SO}(n)} = \frac{n(n-1)}{2}. ``` """ -manifold_dimension(::GeneralUnitaryMatrices{n,ℝ}) where {n} = div(n * (n - 1), 2) +function manifold_dimension(M::GeneralUnitaryMatrices{<:Any,ℝ}) + n = get_parameter(M.size)[1] + return div(n * (n - 1), 2) +end @doc raw""" - manifold_dimension(M::GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}) + manifold_dimension(M::GeneralUnitaryMatrices{<:Any,ℂ,DeterminantOneMatrices}) Return the dimension of the manifold of special unitary matrices. ```math \dim_{\mathrm{SU}(n)} = n^2-1. ``` """ -manifold_dimension(::GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}) where {n} = n^2 - 1 +function manifold_dimension(M::GeneralUnitaryMatrices{<:Any,ℂ,DeterminantOneMatrices}) + n = get_parameter(M.size)[1] + return n^2 - 1 +end @doc raw""" - manifold_volume(::GeneralUnitaryMatrices{n,ℝ,AbsoluteDeterminantOneMatrices}) where {n} + manifold_volume(::GeneralUnitaryMatrices{<:Any,ℝ,AbsoluteDeterminantOneMatrices}) Volume of the manifold of real orthogonal matrices of absolute determinant one. The formula reads [BoyaSudarshanTilma:2003](@cite): @@ -715,13 +853,12 @@ formula reads [BoyaSudarshanTilma:2003](@cite): \end{cases} ``` """ -function manifold_volume( - ::GeneralUnitaryMatrices{n,ℝ,AbsoluteDeterminantOneMatrices}, -) where {n} - return 2 * manifold_volume(GeneralUnitaryMatrices{n,ℝ,DeterminantOneMatrices}()) +function manifold_volume(M::GeneralUnitaryMatrices{<:Any,ℝ,AbsoluteDeterminantOneMatrices}) + n = get_parameter(M.size)[1] + return 2 * manifold_volume(GeneralUnitaryMatrices(n, ℝ, DeterminantOneMatrices)) end @doc raw""" - manifold_volume(::GeneralUnitaryMatrices{n,ℝ,DeterminantOneMatrices}) where {n} + manifold_volume(::GeneralUnitaryMatrices{<:Any,ℝ,DeterminantOneMatrices}) Volume of the manifold of real orthogonal matrices of determinant one. The formula reads [BoyaSudarshanTilma:2003](@cite): @@ -737,7 +874,8 @@ formula reads [BoyaSudarshanTilma:2003](@cite): It differs from the paper by a factor of `sqrt(2)` due to a different choice of normalization. """ -function manifold_volume(::GeneralUnitaryMatrices{n,ℝ,DeterminantOneMatrices}) where {n} +function manifold_volume(M::GeneralUnitaryMatrices{<:Any,ℝ,DeterminantOneMatrices}) + n = get_parameter(M.size)[1] vol = 1.0 if n % 2 == 0 k = div(n, 2) @@ -758,7 +896,7 @@ function manifold_volume(::GeneralUnitaryMatrices{n,ℝ,DeterminantOneMatrices}) return vol end @doc raw""" - manifold_volume(::GeneralUnitaryMatrices{n,ℂ,AbsoluteDeterminantOneMatrices}) where {n} + manifold_volume(::GeneralUnitaryMatrices{<:Any,ℂ,AbsoluteDeterminantOneMatrices}) Volume of the manifold of complex general unitary matrices of absolute determinant one. The formula reads [BoyaSudarshanTilma:2003](@cite) @@ -767,9 +905,8 @@ formula reads [BoyaSudarshanTilma:2003](@cite) \sqrt{n 2^{n+1}} π^{n(n+1)/2} \prod_{k=1}^{n-1}\frac{1}{k!} ``` """ -function manifold_volume( - ::GeneralUnitaryMatrices{n,ℂ,AbsoluteDeterminantOneMatrices}, -) where {n} +function manifold_volume(M::GeneralUnitaryMatrices{<:Any,ℂ,AbsoluteDeterminantOneMatrices}) + n = get_parameter(M.size)[1] vol = sqrt(n * 2^(n + 1)) * π^(((n + 1) * n) // 2) kf = 1 for k in 1:(n - 1) @@ -779,7 +916,7 @@ function manifold_volume( return vol end @doc raw""" - manifold_volume(::GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}) where {n} + manifold_volume(::GeneralUnitaryMatrices{<:Any,ℂ,DeterminantOneMatrices}) Volume of the manifold of complex general unitary matrices of determinant one. The formula reads [BoyaSudarshanTilma:2003](@cite) @@ -788,7 +925,8 @@ reads [BoyaSudarshanTilma:2003](@cite) \sqrt{n 2^{n-1}} π^{(n-1)(n+2)/2} \prod_{k=1}^{n-1}\frac{1}{k!} ``` """ -function manifold_volume(::GeneralUnitaryMatrices{n,ℂ,DeterminantOneMatrices}) where {n} +function manifold_volume(M::GeneralUnitaryMatrices{<:Any,ℂ,DeterminantOneMatrices}) + n = get_parameter(M.size)[1] vol = sqrt(n * 2^(n - 1)) * π^(((n - 1) * (n + 2)) // 2) kf = 1 for k in 1:(n - 1) @@ -810,11 +948,11 @@ end Compute the Riemannian [`mean`](@ref mean(M::AbstractManifold, args...)) of `x` using [`GeodesicInterpolationWithinRadius`](@ref). """ -mean(::GeneralUnitaryMatrices{n,ℝ}, ::Any) where {n} +mean(::GeneralUnitaryMatrices{<:Any,ℝ}, ::Any) @doc raw""" - project(G::UnitaryMatrices{n}, p) - project(G::OrthogonalMatrices{n}, p) + project(G::UnitaryMatrices, p) + project(G::OrthogonalMatrices, p) Project the point ``p ∈ 𝔽^{n × n}`` to the nearest point in ``\mathrm{U}(n,𝔽)=``[`Unitary(n,𝔽)`](@ref) under the Frobenius norm. @@ -825,22 +963,22 @@ is \operatorname{proj}_{\mathrm{U}(n,𝔽)} \colon p ↦ U V^\mathrm{H}. ```` """ -project(::GeneralUnitaryMatrices{n,𝔽,AbsoluteDeterminantOneMatrices}, p) where {n,𝔽} +project(::GeneralUnitaryMatrices{<:Any,𝔽,AbsoluteDeterminantOneMatrices}, p) where {𝔽} function project!( - ::GeneralUnitaryMatrices{n,𝔽,AbsoluteDeterminantOneMatrices}, + ::GeneralUnitaryMatrices{<:Any,𝔽,AbsoluteDeterminantOneMatrices}, q, p, -) where {n,𝔽} +) where {𝔽} F = svd(p) mul!(q, F.U, F.Vt) return q end @doc raw""" - project(M::OrthogonalMatrices{n}, p, X) - project(M::Rotations{n}, p, X) - project(M::UnitaryMatrices{n}, p, X) + project(M::OrthogonalMatrices, p, X) + project(M::Rotations, p, X) + project(M::UnitaryMatrices, p, X) Orthogonally project the tangent vector ``X ∈ 𝔽^{n × n}``, ``\mathbb F ∈ \{\mathbb R, \mathbb C\}`` to the tangent space of `M` at `p`, @@ -852,7 +990,8 @@ and change the representer to use the corresponding Lie algebra, i.e. we compute """ project(::GeneralUnitaryMatrices, p, X) -function project!(::GeneralUnitaryMatrices{n,𝔽}, Y, p, X) where {n,𝔽} +function project!(M::GeneralUnitaryMatrices{<:Any,𝔽}, Y, p, X) where {𝔽} + n = get_parameter(M.size)[1] project!(SkewHermitianMatrices(n, 𝔽), Y, p \ X) return Y end @@ -875,7 +1014,7 @@ be the singular value decomposition, then the formula reads \operatorname{retr}_p X = UV^\mathrm{T}. ```` """ -retract(::GeneralUnitaryMatrices{n,𝔽}, ::Any, ::Any, ::PolarRetraction) where {n,𝔽} +retract(::GeneralUnitaryMatrices, ::Any, ::Any, ::PolarRetraction) @doc raw""" retract(M::Rotations, p, X, ::QRRetraction) @@ -886,22 +1025,22 @@ Compute the QR-based retraction on the [`Rotations`](@ref) and [`OrthogonalMatri This is also the default retraction on these manifolds. """ -retract(::GeneralUnitaryMatrices{n,𝔽}, ::Any, ::Any, ::QRRetraction) where {n,𝔽} +retract(::GeneralUnitaryMatrices, ::Any, ::Any, ::QRRetraction) function retract_qr!( - ::GeneralUnitaryMatrices{n,𝔽}, + ::GeneralUnitaryMatrices, q::AbstractArray{T}, p, X, t::Number, -) where {n,𝔽,T} +) where {T} A = p + p * (t * X) qr_decomp = qr(A) d = diag(qr_decomp.R) D = Diagonal(sign.(d .+ convert(T, 0.5))) return copyto!(q, qr_decomp.Q * D) end -function retract_polar!(M::GeneralUnitaryMatrices{n,𝔽}, q, p, X, t::Number) where {n,𝔽} +function retract_polar!(M::GeneralUnitaryMatrices, q, p, X, t::Number) A = p + p * (t * X) return project!(M, q, A; check_det=false) end @@ -921,14 +1060,14 @@ function riemann_tensor!(::GeneralUnitaryMatrices, Xresult, p, X, Y, Z) end @doc raw""" - volume_density(M::GeneralUnitaryMatrices{n,ℝ}, p, X) where {n} + volume_density(M::GeneralUnitaryMatrices{<:Any,ℝ}, p, X) Compute volume density function of a sphere, i.e. determinant of the differential of exponential map `exp(M, p, X)`. It is derived from Eq. (4.1) and Corollary 4.4 in [ChevallierLiLuDunson:2022](@cite). See also Theorem 4.1 in [FalorsideHaanDavidsonForre:2019](@cite), (note that it uses a different convention). """ -function volume_density(M::GeneralUnitaryMatrices{n,ℝ}, p, X) where {n} +function volume_density(M::GeneralUnitaryMatrices{<:Any,ℝ}, p, X) dens = one(eltype(X)) B = get_basis(M, p, DefaultOrthonormalBasis()) Ys = get_vectors(M, p, B) @@ -950,7 +1089,7 @@ function volume_density(M::GeneralUnitaryMatrices{n,ℝ}, p, X) where {n} end @doc raw""" - volume_density(M::GeneralUnitaryMatrices{3,ℝ}, p, X) + volume_density(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, p, X) Compute the volume density on O(3)/SO(3). The formula reads [FalorsideHaanDavidsonForre:2019](@cite) @@ -958,7 +1097,7 @@ Compute the volume density on O(3)/SO(3). The formula reads [FalorsideHaanDavids \frac{1-1\cos(\sqrt{2}\lVert X \rVert)}{\lVert X \rVert^2}. ``` """ -function volume_density(M::GeneralUnitaryMatrices{3,ℝ}, p, X) +function volume_density(M::GeneralUnitaryMatrices{TypeParameter{Tuple{3}},ℝ}, p, X) nX = norm(M, p, X) if nX > eps(eltype(X)) return (1 - 1 * cos(sqrt(2) * nX)) / nX^2 @@ -968,10 +1107,10 @@ function volume_density(M::GeneralUnitaryMatrices{3,ℝ}, p, X) end @doc raw""" - volume_density(M::GeneralUnitaryMatrices{2,ℝ}, p, X) + volume_density(M::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, p, X) Volume density on O(2)/SO(2) is equal to 1. """ -function volume_density(::GeneralUnitaryMatrices{2,ℝ}, p, X) +function volume_density(::GeneralUnitaryMatrices{TypeParameter{Tuple{2}},ℝ}, p, X) return one(eltype(X)) end diff --git a/src/manifolds/GeneralizedGrassmann.jl b/src/manifolds/GeneralizedGrassmann.jl index ee6456c951..2fe79dd922 100644 --- a/src/manifolds/GeneralizedGrassmann.jl +++ b/src/manifolds/GeneralizedGrassmann.jl @@ -1,5 +1,5 @@ @doc raw""" - GeneralizedGrassmann{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} + GeneralizedGrassmann{T,𝔽,TB<:AbstractMatrix} <: AbstractDecoratorManifold{𝔽} The generalized Grassmann manifold $\operatorname{Gr}(n,k,B)$ consists of all subspaces spanned by $k$ linear independent vectors $𝔽^n$, where $𝔽 ∈ \{ℝ, ℂ\}$ is either the real- (or complex-) valued vectors. @@ -43,7 +43,8 @@ The manifold is named after Generate the (real-valued) Generalized Grassmann manifold of $n\times k$ dimensional orthonormal matrices with scalar product `B`. """ -struct GeneralizedGrassmann{n,k,𝔽,TB<:AbstractMatrix} <: AbstractDecoratorManifold{𝔽} +struct GeneralizedGrassmann{T,𝔽,TB<:AbstractMatrix} <: AbstractDecoratorManifold{𝔽} + size::T B::TB end @@ -51,9 +52,11 @@ function GeneralizedGrassmann( n::Int, k::Int, B::AbstractMatrix=Matrix{Float64}(I, n, n), - field::AbstractNumbers=ℝ, + 𝔽::AbstractNumbers=ℝ; + parameter::Symbol=:type, ) - return GeneralizedGrassmann{n,k,field,typeof(B)}(B) + size = wrap_type_parameter(parameter, (n, k)) + return GeneralizedGrassmann{typeof(size),𝔽,typeof(B)}(size, B) end active_traits(f, ::GeneralizedGrassmann, args...) = merge_traits(IsEmbeddedManifold()) @@ -93,18 +96,18 @@ function change_metric!(M::GeneralizedGrassmann, Y, ::EuclideanMetric, p, X) end @doc raw""" - check_point(M::GeneralizedGrassmann{n,k,𝔽}, p) + check_point(M::GeneralizedGrassmann, p) Check whether `p` is representing a point on the [`GeneralizedGrassmann`](@ref) `M`, i.e. its a `n`-by-`k` matrix of unitary column vectors with respect to the B inner prudct and of correct `eltype` with respect to `𝔽`. """ -function check_point(M::GeneralizedGrassmann{n,k,𝔽}, p; kwargs...) where {n,k,𝔽} +function check_point(M::GeneralizedGrassmann, p; kwargs...) return nothing # everything already checked in the embedding (generalized Stiefel) end @doc raw""" - check_vector(M::GeneralizedGrassmann{n,k,𝔽}, p, X; kwargs...) + check_vector(M::GeneralizedGrassmann, p, X; kwargs...) Check whether `X` is a tangent vector in the tangent space of `p` on the [`GeneralizedGrassmann`](@ref) `M`, i.e. that `X` is of size and type as well as that @@ -116,7 +119,7 @@ the [`GeneralizedGrassmann`](@ref) `M`, i.e. that `X` is of size and type as wel where $\cdot^{\mathrm{H}}$ denotes the complex conjugate transpose or Hermitian, $\overline{\cdot}$ the (elementwise) complex conjugate, and $0_k$ denotes the $k × k$ zero natrix. """ -function check_vector(M::GeneralizedGrassmann{n,k,𝔽}, p, X; kwargs...) where {n,k,𝔽} +function check_vector(M::GeneralizedGrassmann, p, X; kwargs...) return nothing # everything already checked in the embedding (generalized Stiefel) end @@ -188,8 +191,12 @@ Return true if [`GeneralizedGrassmann`](@ref) `M` is one-dimensional. """ is_flat(M::GeneralizedGrassmann) = manifold_dimension(M) == 1 -function get_embedding(M::GeneralizedGrassmann{N,K,𝔽}) where {N,K,𝔽} - return GeneralizedStiefel(N, K, M.B, 𝔽) +function get_embedding(M::GeneralizedGrassmann{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return GeneralizedStiefel(n, k, M.B, 𝔽) +end +function get_embedding(M::GeneralizedGrassmann{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return GeneralizedStiefel(n, k, M.B, 𝔽; parameter=:field) end @doc raw""" @@ -204,7 +211,7 @@ g_p(X,Y) = \operatorname{tr}(X^{\mathrm{H}}BY), where $\cdot^{\mathrm{H}}$ denotes the complex conjugate transposed or Hermitian. """ -inner(M::GeneralizedGrassmann{n,k}, p, X, Y) where {n,k} = dot(X, M.B, Y) +inner(M::GeneralizedGrassmann, p, X, Y) = dot(X, M.B, Y) function _isapprox(M::GeneralizedGrassmann, p, X, Y; atol=sqrt(max_eps(X, Y)), kwargs...) return isapprox(norm(M, p, X - Y), 0; atol=atol, kwargs...) @@ -254,7 +261,8 @@ Return the dimension of the [`GeneralizedGrassmann(n,k,𝔽)`](@ref) manifold `M where $\dim_ℝ 𝔽$ is the [`real_dimension`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.real_dimension-Tuple{ManifoldsBase.AbstractNumbers}) of `𝔽`. """ -function manifold_dimension(::GeneralizedGrassmann{n,k,𝔽}) where {n,k,𝔽} +function manifold_dimension(M::GeneralizedGrassmann{<:Any,𝔽}) where {𝔽} + n, k = get_parameter(M.size) return k * (n - k) * real_dimension(𝔽) end @@ -270,7 +278,7 @@ end Compute the Riemannian [`mean`](@ref mean(M::AbstractManifold, args...)) of `x` using [`GeodesicInterpolationWithinRadius`](@ref). """ -mean(::GeneralizedGrassmann{n,k} where {n,k}, ::Any...) +mean(::GeneralizedGrassmann, ::Any...) function default_estimation_method(::GeneralizedGrassmann, ::typeof(mean)) return GeodesicInterpolationWithinRadius(π / 4) @@ -331,11 +339,12 @@ rand(::GeneralizedGrassmann; σ::Real=1.0) function Random.rand!( rng::AbstractRNG, - M::GeneralizedGrassmann{n,k,ℝ}, + M::GeneralizedGrassmann{<:Any,ℝ}, pX; vector_at=nothing, σ::Real=one(real(eltype(pX))), -) where {n,k} +) + n, k = get_parameter(M.size) if vector_at === nothing A = σ * randn(rng, eltype(pX), n, k) project!(M, pX, Matrix(qr(A).Q)) @@ -348,12 +357,12 @@ function Random.rand!( end @doc raw""" - representation_size(M::GeneralizedGrassmann{n,k}) + representation_size(M::GeneralizedGrassmann) Return the represenation size or matrix dimension of a point on the [`GeneralizedGrassmann`](@ref) `M`, i.e. $(n,k)$ for both the real-valued and the complex value case. """ -@generated representation_size(::GeneralizedGrassmann{n,k}) where {n,k} = (n, k) +representation_size(M::GeneralizedGrassmann) = get_parameter(M.size) @doc raw""" retract(M::GeneralizedGrassmann, p, X, ::PolarRetraction) @@ -375,9 +384,16 @@ function retract_project!(M::GeneralizedGrassmann, q, p, X, t::Number) return q end -function Base.show(io::IO, M::GeneralizedGrassmann{n,k,𝔽}) where {n,k,𝔽} +function Base.show( + io::IO, + M::GeneralizedGrassmann{TypeParameter{Tuple{n,k}},𝔽}, +) where {n,k,𝔽} return print(io, "GeneralizedGrassmann($(n), $(k), $(M.B), $(𝔽))") end +function Base.show(io::IO, M::GeneralizedGrassmann{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return print(io, "GeneralizedGrassmann($(n), $(k), $(M.B), $(𝔽); parameter=:field)") +end @doc raw""" zero_vector(M::GeneralizedGrassmann, p) diff --git a/src/manifolds/GeneralizedStiefel.jl b/src/manifolds/GeneralizedStiefel.jl index 755284c41f..5510abe875 100644 --- a/src/manifolds/GeneralizedStiefel.jl +++ b/src/manifolds/GeneralizedStiefel.jl @@ -1,5 +1,5 @@ @doc raw""" - GeneralizedStiefel{n,k,𝔽,B} <: AbstractDecoratorManifold{𝔽} + GeneralizedStiefel{T,𝔽,B} <: AbstractDecoratorManifold{𝔽} The Generalized Stiefel manifold consists of all $n\times k$, $n\geq k$ orthonormal matrices w.r.t. an arbitrary scalar product with symmetric positive definite matrix @@ -35,7 +35,8 @@ The manifold is named after Generate the (real-valued) Generalized Stiefel manifold of $n\times k$ dimensional orthonormal matrices with scalar product `B`. """ -struct GeneralizedStiefel{n,k,𝔽,TB<:AbstractMatrix} <: AbstractDecoratorManifold{𝔽} +struct GeneralizedStiefel{T,𝔽,TB<:AbstractMatrix} <: AbstractDecoratorManifold{𝔽} + size::T B::TB end @@ -43,9 +44,11 @@ function GeneralizedStiefel( n::Int, k::Int, B::AbstractMatrix=Matrix{Float64}(I, n, n), - 𝔽::AbstractNumbers=ℝ, + 𝔽::AbstractNumbers=ℝ; + parameter::Symbol=:type, ) - return GeneralizedStiefel{n,k,𝔽,typeof(B)}(B) + size = wrap_type_parameter(parameter, (n, k)) + return GeneralizedStiefel{typeof(size),𝔽,typeof(B)}(size, B) end active_traits(f, ::GeneralizedStiefel, args...) = merge_traits(IsEmbeddedManifold()) @@ -58,7 +61,7 @@ i.e. that it has the right [`AbstractNumbers`](https://juliamanifolds.github.io/ is (approximately) the identity, where $\cdot^{\mathrm{H}}$ is the complex conjugate transpose. The settings for approximately can be set with `kwargs...`. """ -function check_point(M::GeneralizedStiefel{n,k,𝔽}, p; kwargs...) where {n,k,𝔽} +function check_point(M::GeneralizedStiefel, p; kwargs...) c = p' * M.B * p if !isapprox(c, one(c); kwargs...) return DomainError( @@ -70,10 +73,10 @@ function check_point(M::GeneralizedStiefel{n,k,𝔽}, p; kwargs...) where {n,k, end # overwrite passing to embedding -function check_size(M::GeneralizedStiefel{n,k,𝔽}, p) where {n,k,𝔽} +function check_size(M::GeneralizedStiefel, p) return check_size(get_embedding(M), p) #avoid embed, since it uses copyto! end -function check_size(M::GeneralizedStiefel{n,k,𝔽}, p, X) where {n,k,𝔽} +function check_size(M::GeneralizedStiefel, p, X) return check_size(get_embedding(M), p, X) #avoid embed, since it uses copyto! end @@ -86,7 +89,7 @@ Check whether `X` is a valid tangent vector at `p` on the [`GeneralizedStiefel`] it (approximately) holds that $p^{\mathrm{H}}BX + \overline{X^{\mathrm{H}}Bp} = 0$, where `kwargs...` is passed to the `isapprox`. """ -function check_vector(M::GeneralizedStiefel{n,k,𝔽}, p, X; kwargs...) where {n,k,𝔽} +function check_vector(M::GeneralizedStiefel, p, X; kwargs...) if !isapprox(p' * M.B * X, -conj(X' * M.B * p); kwargs...) return DomainError( norm(p' * M.B * X + conj(X' * M.B * p)), @@ -96,7 +99,13 @@ function check_vector(M::GeneralizedStiefel{n,k,𝔽}, p, X; kwargs...) where {n return nothing end -get_embedding(::GeneralizedStiefel{N,K,𝔽}) where {N,K,𝔽} = Euclidean(N, K; field=𝔽) +function get_embedding(::GeneralizedStiefel{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return Euclidean(n, k; field=𝔽) +end +function get_embedding(M::GeneralizedStiefel{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return Euclidean(n, k; field=𝔽, parameter=:field) +end @doc raw""" inner(M::GeneralizedStiefel, p, X, Y) @@ -133,14 +142,21 @@ The dimension is given by \end{aligned} ```` """ -function manifold_dimension(::GeneralizedStiefel{n,k,ℝ}) where {n,k} +function manifold_dimension(M::GeneralizedStiefel{<:Any,ℝ}) + n, k = get_parameter(M.size) return n * k - div(k * (k + 1), 2) end -manifold_dimension(::GeneralizedStiefel{n,k,ℂ}) where {n,k} = 2 * n * k - k * k -manifold_dimension(::GeneralizedStiefel{n,k,ℍ}) where {n,k} = 4 * n * k - k * (2k - 1) +function manifold_dimension(M::GeneralizedStiefel{<:Any,ℂ}) + n, k = get_parameter(M.size) + return 2 * n * k - k * k +end +function manifold_dimension(M::GeneralizedStiefel{<:Any,ℍ}) + n, k = get_parameter(M.size) + return 4 * n * k - k * (2k - 1) +end @doc raw""" - project(M::GeneralizedStiefel,p) + project(M::GeneralizedStiefel, p) Project `p` from the embedding onto the [`GeneralizedStiefel`](@ref) `M`, i.e. compute `q` as the polar decomposition of $p$ such that $q^{\mathrm{H}}Bq$ is the identity, @@ -194,11 +210,12 @@ rand(::GeneralizedStiefel; σ::Real=1.0) function Random.rand!( rng::AbstractRNG, - M::GeneralizedStiefel{n,k,ℝ}, + M::GeneralizedStiefel{<:Any,ℝ}, pX; vector_at=nothing, σ::Real=one(real(eltype(pX))), -) where {n,k} +) + n, k = get_parameter(M.size) if vector_at === nothing A = σ * randn(rng, eltype(pX), n, k) project!(M, pX, Matrix(qr(A).Q)) @@ -237,6 +254,10 @@ function retract_project!(M::GeneralizedStiefel, q, p, X, t::Number) return q end -function Base.show(io::IO, M::GeneralizedStiefel{n,k,𝔽}) where {n,k,𝔽} +function Base.show(io::IO, M::GeneralizedStiefel{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} return print(io, "GeneralizedStiefel($(n), $(k), $(M.B), $(𝔽))") end +function Base.show(io::IO, M::GeneralizedStiefel{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return print(io, "GeneralizedStiefel($(n), $(k), $(M.B), $(𝔽); parameter=:field)") +end diff --git a/src/manifolds/Grassmann.jl b/src/manifolds/Grassmann.jl index 85a176f8f6..c8f23a7612 100644 --- a/src/manifolds/Grassmann.jl +++ b/src/manifolds/Grassmann.jl @@ -1,5 +1,5 @@ @doc raw""" - Grassmann{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} + Grassmann{T,𝔽} <: AbstractDecoratorManifold{𝔽} The Grassmann manifold $\operatorname{Gr}(n,k)$ consists of all subspaces spanned by $k$ linear independent vectors $𝔽^n$, where $𝔽 ∈ \{ℝ, ℂ\}$ is either the real- (or complex-) valued vectors. @@ -65,23 +65,28 @@ A good overview can be found in[BendokatZimmermannAbsil:2020](@cite). # Constructor - Grassmann(n,k,field=ℝ) + Grassmann(n, k, field=ℝ, parameter::Symbol=:type) Generate the Grassmann manifold $\operatorname{Gr}(n,k)$, where the real-valued -case `field = ℝ` is the default. +case `field=ℝ` is the default. """ -struct Grassmann{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct Grassmann{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end # # Generic functions independent of the representation of points # -Grassmann(n::Int, k::Int, field::AbstractNumbers=ℝ) = Grassmann{n,k,field}() +function Grassmann(n::Int, k::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, k)) + return Grassmann{typeof(size),field}(size) +end function active_traits(f, ::Grassmann, args...) return merge_traits(IsIsometricEmbeddedManifold(), IsQuotientManifold()) end -function allocation_promotion_function(::Grassmann{n,k,ℂ}, f, args::Tuple) where {n,k} +function allocation_promotion_function(::Grassmann{<:Any,ℂ}, f, args::Tuple) return complex end @@ -147,7 +152,10 @@ Return the dimension of the [`Grassmann(n,k,𝔽)`](@ref) manifold `M`, i.e. where $\dim_ℝ 𝔽$ is the [`real_dimension`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.real_dimension-Tuple{ManifoldsBase.AbstractNumbers}) of `𝔽`. """ -manifold_dimension(::Grassmann{n,k,𝔽}) where {n,k,𝔽} = k * (n - k) * real_dimension(𝔽) +function manifold_dimension(M::Grassmann{<:Any,𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return k * (n - k) * real_dimension(𝔽) +end """ mean( @@ -161,23 +169,30 @@ manifold_dimension(::Grassmann{n,k,𝔽}) where {n,k,𝔽} = k * (n - k) * real_ Compute the Riemannian [`mean`](@ref mean(M::AbstractManifold, args...)) of `x` using [`GeodesicInterpolationWithinRadius`](@ref). """ -mean(::Grassmann{n,k} where {n,k}, ::Any...) +mean(::Grassmann, ::Any...) function default_estimation_method(::Grassmann, ::typeof(mean)) return GeodesicInterpolationWithinRadius(π / 4) end -function get_orbit_action(M::Grassmann{n,k,ℝ}) where {n,k} +function get_orbit_action(M::Grassmann{<:Any,ℝ}) + n, k = get_parameter(M.size) return RowwiseMultiplicationAction(M, Orthogonal(k)) end @doc raw""" - get_total_space(::Grassmann{n,k}) + get_total_space(::Grassmann) Return the total space of the [`Grassmann`](@ref) manifold, which is the corresponding Stiefel manifold, independent of whether the points are represented already in the total space or as [`ProjectorPoint`](@ref)s. """ -get_total_space(::Grassmann{n,k,𝔽}) where {n,k,𝔽} = Stiefel(n, k, 𝔽) +function get_total_space(::Grassmann{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return Stiefel(n, k, 𝔽) +end +function get_total_space(M::Grassmann{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return Stiefel(n, k, 𝔽; parameter=:field) +end # # Reprenter specific implementations in their corresponding subfiles diff --git a/src/manifolds/GrassmannProjector.jl b/src/manifolds/GrassmannProjector.jl index 2d5984a89f..459cb718ba 100644 --- a/src/manifolds/GrassmannProjector.jl +++ b/src/manifolds/GrassmannProjector.jl @@ -21,13 +21,14 @@ ManifoldsBase.@manifold_vector_forwards ProjectorTVector value ManifoldsBase.@manifold_element_forwards ProjectorPoint value @doc raw""" - check_point(::Grassmann{n,k}, p::ProjectorPoint; kwargs...) + check_point(::Grassmann, p::ProjectorPoint; kwargs...) Check whether an orthogonal projector is a point from the [`Grassmann`](@ref)`(n,k)` manifold, i.e. the [`ProjectorPoint`](@ref) ``p ∈ \mathbb F^{n×n}``, ``\mathbb F ∈ \{\mathbb R, \mathbb C\}`` has to fulfill ``p^{\mathrm{T}} = p``, ``p^2=p``, and ``\operatorname{rank} p = k`. """ -function check_point(M::Grassmann{n,k,𝔽}, p::ProjectorPoint; kwargs...) where {n,k,𝔽} +function check_point(M::Grassmann, p::ProjectorPoint; kwargs...) + n, k = get_parameter(M.size) c = p.value * p.value if !isapprox(c, p.value; kwargs...) return DomainError( @@ -52,16 +53,16 @@ function check_point(M::Grassmann{n,k,𝔽}, p::ProjectorPoint; kwargs...) where end @doc raw""" - check_size(M::Grassmann{n,k,𝔽}, p::ProjectorPoint; kwargs...) where {n,k} + check_size(M::Grassmann, p::ProjectorPoint; kwargs...) Check that the [`ProjectorPoint`](@ref) is of correct size, i.e. from ``\mathbb F^{n×n}`` """ -function check_size(M::Grassmann{n,k,𝔽}, p::ProjectorPoint; kwargs...) where {n,k,𝔽} +function check_size(M::Grassmann, p::ProjectorPoint; kwargs...) return check_size(get_embedding(M, p), p.value; kwargs...) end @doc raw""" - check_vector(::Grassmann{n,k,𝔽}, p::ProjectorPoint, X::ProjectorTVector; kwargs...) where {n,k,𝔽} + check_vector(::Grassmann, p::ProjectorPoint, X::ProjectorTVector; kwargs...) Check whether the [`ProjectorTVector`](@ref) `X` is from the tangent space ``T_p\operatorname{Gr}(n,k) `` at the [`ProjectorPoint`](@ref) `p` on the [`Grassmann`](@ref) manifold ``\operatorname{Gr}(n,k)``. @@ -74,12 +75,7 @@ Xp + pX = X must hold, where the `kwargs` can be used to check both for symmetrix of ``X``` and this equality up to a certain tolerance. """ -function check_vector( - M::Grassmann{n,k,𝔽}, - p::ProjectorPoint, - X::ProjectorTVector; - kwargs..., -) where {n,k,𝔽} +function check_vector(M::Grassmann, p::ProjectorPoint, X::ProjectorTVector; kwargs...) if !isapprox(X.value, X.value'; kwargs...) return DomainError( norm(X.value - X.value'), @@ -101,23 +97,35 @@ embed(::Grassmann, p::ProjectorPoint) = p.value embed(::Grassmann, p, X::ProjectorTVector) = X.value @doc raw""" - get_embedding(M::Grassmann{n,k,𝔽}, p::ProjectorPoint) where {n,k,𝔽} + get_embedding(M::Grassmann, p::ProjectorPoint) Return the embedding of the [`ProjectorPoint`](@ref) representation of the [`Grassmann`](@ref) manifold, i.e. the Euclidean space ``\mathbb F^{n×n}``. """ -get_embedding(::Grassmann{n,k,𝔽}, ::ProjectorPoint) where {n,k,𝔽} = Euclidean(n, n; field=𝔽) +function get_embedding( + ::Grassmann{TypeParameter{Tuple{n,k}},𝔽}, + ::ProjectorPoint, +) where {n,k,𝔽} + return Euclidean(n, n; field=𝔽) +end +function get_embedding(M::Grassmann{Tuple{Int,Int},𝔽}, ::ProjectorPoint) where {𝔽} + n, k = get_parameter(M.size) + return Euclidean(n, n; field=𝔽, parameter=:field) +end @doc raw""" - representation_size(M::Grassmann{n,k}, p::ProjectorPoint) + representation_size(M::Grassmann, p::ProjectorPoint) Return the represenation size or matrix dimension of a point on the [`Grassmann`](@ref) `M` when using [`ProjectorPoint`](@ref)s, i.e. ``(n,n)``. """ -@generated representation_size(::Grassmann{n,k}, p::ProjectorPoint) where {n,k} = (n, n) +function representation_size(M::Grassmann, p::ProjectorPoint) + n, k = get_parameter(M.size) + return (n, n) +end @doc raw""" - canonical_project!(M::Grassmann{n,k}, q::ProjectorPoint, p) + canonical_project!(M::Grassmann, q::ProjectorPoint, p) Compute the canonical projection ``π(p)`` from the [`Stiefel`](@ref) manifold onto the [`Grassmann`](@ref) manifold when represented as [`ProjectorPoint`](@ref), i.e. @@ -126,27 +134,20 @@ manifold when represented as [`ProjectorPoint`](@ref), i.e. π^{\mathrm{SG}}(p) = pp^{\mathrm{T}} ``` """ -function canonical_project!(::Grassmann{n,k}, q::ProjectorPoint, p) where {n,k} +function canonical_project!(::Grassmann, q::ProjectorPoint, p) q.value .= p * p' return q end -function canonical_project!( - M::Grassmann{n,k}, - q::ProjectorPoint, - p::StiefelPoint, -) where {n,k} +function canonical_project!(M::Grassmann, q::ProjectorPoint, p::StiefelPoint) return canonical_project!(M, q, p.value) end -function allocate_result( - ::Grassmann{n,k}, - ::typeof(canonical_project), - p::StiefelPoint, -) where {n,k} +function allocate_result(M::Grassmann, ::typeof(canonical_project), p::StiefelPoint) + n, k = get_parameter(M.size) return ProjectorPoint(allocate(p.value, (n, n))) end @doc raw""" - canonical_project!(M::Grassmann{n,k}, q::ProjectorPoint, p) + canonical_project!(M::Grassmann, q::ProjectorPoint, p) Compute the canonical projection ``π(p)`` from the [`Stiefel`](@ref) manifold onto the [`Grassmann`](@ref) manifold when represented as [`ProjectorPoint`](@ref), i.e. @@ -155,39 +156,31 @@ manifold when represented as [`ProjectorPoint`](@ref), i.e. Dπ^{\mathrm{SG}}(p)[X] = Xp^{\mathrm{T}} + pX^{\mathrm{T}} ``` """ -function differential_canonical_project!( - ::Grassmann{n,k}, - Y::ProjectorTVector, - p, - X, -) where {n,k} +function differential_canonical_project!(::Grassmann, Y::ProjectorTVector, p, X) Xpt = X * p' Y.value .= Xpt .+ Xpt' return Y end function differential_canonical_project!( - M::Grassmann{n,k}, + M::Grassmann, Y::ProjectorTVector, p::StiefelPoint, X::StiefelTVector, -) where {n,k} +) differential_canonical_project!(M, Y, p.value, X.value) return Y end function allocate_result( - ::Grassmann{n,k}, + M::Grassmann, ::typeof(differential_canonical_project), p::StiefelPoint, X::StiefelTVector, -) where {n,k} +) + n, k = get_parameter(M.size) return ProjectorTVector(allocate(p.value, (n, n))) end -function allocate_result( - ::Grassmann{n,k}, - ::typeof(differential_canonical_project), - p, - X, -) where {n,k} +function allocate_result(M::Grassmann, ::typeof(differential_canonical_project), p, X) + n, k = get_parameter(M.size) return ProjectorTVector(allocate(p, (n, n))) end diff --git a/src/manifolds/GrassmannStiefel.jl b/src/manifolds/GrassmannStiefel.jl index bc67a36f94..5fa2382b61 100644 --- a/src/manifolds/GrassmannStiefel.jl +++ b/src/manifolds/GrassmannStiefel.jl @@ -28,7 +28,7 @@ end ManifoldsBase.@manifold_element_forwards StiefelPoint value ManifoldsBase.@manifold_vector_forwards StiefelTVector value ManifoldsBase.@default_manifold_fallbacks Stiefel StiefelPoint StiefelTVector value value -ManifoldsBase.@default_manifold_fallbacks (Stiefel{n,k,ℝ} where {n,k}) StiefelPoint StiefelTVector value value +ManifoldsBase.@default_manifold_fallbacks (Stiefel{<:Any,ℝ}) StiefelPoint StiefelTVector value value ManifoldsBase.@default_manifold_fallbacks Grassmann StiefelPoint StiefelTVector value value function default_vector_transport_method(::Grassmann, ::Type{<:AbstractArray}) @@ -93,8 +93,12 @@ function exp!(M::Grassmann, q, p, X) return copyto!(q, Array(qr(z).Q)) end -function get_embedding(::Grassmann{N,K,𝔽}) where {N,K,𝔽} - return Stiefel(N, K, 𝔽) +function get_embedding(::Grassmann{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return Stiefel(n, k, 𝔽) +end +function get_embedding(M::Grassmann{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return Stiefel(n, k, 𝔽; parameter=:field) end @doc raw""" @@ -228,12 +232,13 @@ rand(M::Grassmann; σ::Real=1.0) function Random.rand!( rng::AbstractRNG, - M::Grassmann{n,k,𝔽}, + M::Grassmann{<:Any,𝔽}, pX; σ::Real=one(real(eltype(pX))), vector_at=nothing, -) where {n,k,𝔽} +) where {𝔽} if vector_at === nothing + n, k = get_parameter(M.size) V = σ * randn(rng, 𝔽 === ℝ ? Float64 : ComplexF64, (n, k)) pX .= qr(V).Q[:, 1:k] else @@ -245,12 +250,12 @@ function Random.rand!( end @doc raw""" - representation_size(M::Grassmann{n,k}) + representation_size(M::Grassmann) -Return the represenation size or matrix dimension of a point on the [`Grassmann`](@ref) +Return the representation size or matrix dimension of a point on the [`Grassmann`](@ref) `M`, i.e. $(n,k)$ for both the real-valued and the complex value case. """ -@generated representation_size(::Grassmann{n,k}) where {n,k} = (n, k) +representation_size(M::Grassmann) = get_parameter(M.size) @doc raw""" retract(M::Grassmann, p, X, ::PolarRetraction) @@ -286,7 +291,7 @@ D = \operatorname{diag}\left( \operatorname{sgn}\left(R_{ii}+\frac{1}{2}\right)_ """ retract(::Grassmann, ::Any, ::Any, ::QRRetraction) -function retract_qr!(::Grassmann{N,K}, q, p, X, t::Number) where {N,K} +function retract_qr!(::Grassmann, q, p, X, t::Number) q .= p .+ t .* X qrfac = qr(q) d = diag(qrfac.R) @@ -320,15 +325,15 @@ function riemannian_Hessian!(M::Grassmann, Y, p, G, H, X) end @doc raw""" - riemann_tensor(::Grassmann{n,k,ℝ}, p, X, Y, Z) where {n,k} + riemann_tensor(::Grassmann{<:Any,ℝ}, p, X, Y, Z) Compute the value of Riemann tensor on the real [`Grassmann`](@ref) manifold. The formula reads [Rentmeesters:2011](@cite) ``R(X,Y)Z = (XY^\mathrm{T} - YX^\mathrm{T})Z + Z(Y^\mathrm{T}X - X^\mathrm{T}Y)``. """ -riemann_tensor(::Grassmann{n,k,ℝ}, p, X, Y, Z) where {n,k} +riemann_tensor(::Grassmann{<:Any,ℝ}, p, X, Y, Z) -function riemann_tensor!(::Grassmann{n,k,ℝ}, Xresult, p, X, Y, Z) where {n,k} +function riemann_tensor!(::Grassmann{<:Any,ℝ}, Xresult, p, X, Y, Z) XYᵀ = X * Y' YXᵀ = XYᵀ' YᵀX = Y' * X @@ -337,14 +342,18 @@ function riemann_tensor!(::Grassmann{n,k,ℝ}, Xresult, p, X, Y, Z) where {n,k} return Xresult end -function Base.show(io::IO, ::Grassmann{n,k,𝔽}) where {n,k,𝔽} +function Base.show(io::IO, ::Grassmann{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} return print(io, "Grassmann($(n), $(k), $(𝔽))") end +function Base.show(io::IO, M::Grassmann{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return print(io, "Grassmann($(n), $(k), $(𝔽); parameter=:field)") +end Base.show(io::IO, p::StiefelPoint) = print(io, "StiefelPoint($(p.value))") Base.show(io::IO, X::StiefelTVector) = print(io, "StiefelTVector($(X.value))") """ - uniform_distribution(M::Grassmann{n,k,ℝ}, p) + uniform_distribution(M::Grassmann{<:Any,ℝ}, p) Uniform distribution on given (real-valued) [`Grassmann`](@ref) `M`. Specifically, this is the normalized Haar measure on `M`. @@ -353,7 +362,8 @@ Generated points will be of similar type as `p`. The implementation is based on Section 2.5.1 in [Chikuse:2003](@cite); see also Theorem 2.2.2(iii) in [Chikuse:2003](@cite). """ -function uniform_distribution(M::Grassmann{n,k,ℝ}, p) where {n,k} +function uniform_distribution(M::Grassmann{<:Any,ℝ}, p) + n, k = get_parameter(M.size) μ = Distributions.Zeros(n, k) σ = one(eltype(p)) Σ1 = Distributions.PDMats.ScalMat(n, σ) @@ -364,7 +374,7 @@ function uniform_distribution(M::Grassmann{n,k,ℝ}, p) where {n,k} end @doc raw""" - vector_transport_to(M::Grassmann,p,X,q,::ProjectionTransport) + vector_transport_to(M::Grassmann, p, X, q, ::ProjectionTransport) compute the projection based transport on the [`Grassmann`](@ref) `M` by interpreting `X` from the tangent space at `p` as a point in the embedding and diff --git a/src/manifolds/Hyperbolic.jl b/src/manifolds/Hyperbolic.jl index 37b62e5718..5fd2e15a10 100644 --- a/src/manifolds/Hyperbolic.jl +++ b/src/manifolds/Hyperbolic.jl @@ -1,5 +1,5 @@ @doc raw""" - Hyperbolic{N} <: AbstractDecoratorManifold{ℝ} + Hyperbolic{T} <: AbstractDecoratorManifold{ℝ} The hyperbolic space $\mathcal H^n$ represented by $n+1$-Tuples, i.e. embedded in the [`Lorentz`](@ref)ian manifold equipped with the [`MinkowskiMetric`](@ref) @@ -29,13 +29,18 @@ and the Poincaré half space model, see [`PoincareHalfSpacePoint`](@ref) and [`P # Constructor - Hyperbolic(n) + Hyperbolic(n::Int; parameter::Symbol=:type) Generate the Hyperbolic manifold of dimension `n`. """ -struct Hyperbolic{N} <: AbstractDecoratorManifold{ℝ} end +struct Hyperbolic{T} <: AbstractDecoratorManifold{ℝ} + size::T +end -Hyperbolic(n::Int) = Hyperbolic{n}() +function Hyperbolic(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return Hyperbolic{typeof(size)}(size) +end function active_traits(f, ::Hyperbolic, args...) return merge_traits(IsIsometricEmbeddedManifold(), IsDefaultMetric(MinkowskiMetric())) @@ -152,7 +157,7 @@ last entry, i.e. $p_n>0$ check_point(::Hyperbolic, ::Any) @doc raw""" - check_vector(M::Hyperbolic{n}, p, X; kwargs... ) + check_vector(M::Hyperbolic, p, X; kwargs... ) Check whether `X` is a tangent vector to `p` on the [`Hyperbolic`](@ref) `M`, i.e. after [`check_point`](@ref)`(M,p)`, `X` has to be of the same dimension as `p`. @@ -166,12 +171,13 @@ For a the Poincaré ball as well as the Poincaré half plane model, `X` has to b check_vector(::Hyperbolic, ::Any, ::Any) function check_vector( - M::Hyperbolic{N}, + M::Hyperbolic, p, X::Union{PoincareBallTVector,PoincareHalfSpaceTVector}; kwargs..., -) where {N} - return check_point(Euclidean(N), X.value; kwargs...) +) + n = get_parameter(M.size)[1] + return check_point(Euclidean(n), X.value; kwargs...) end # Define self conversions @@ -195,7 +201,13 @@ function diagonalizing_projectors(M::Hyperbolic, p, X) ) end -get_embedding(::Hyperbolic{N}) where {N} = Lorentz(N + 1, MinkowskiMetric()) +function get_embedding(::Hyperbolic{TypeParameter{Tuple{n}}}) where {n} + return Lorentz(n + 1, MinkowskiMetric()) +end +function get_embedding(M::Hyperbolic{Tuple{Int}}) + n = get_parameter(M.size)[1] + return Lorentz(n + 1, MinkowskiMetric(); parameter=:field) +end embed(::Hyperbolic, p::AbstractArray) = p embed(::Hyperbolic, p::AbstractArray, X::AbstractArray) = X @@ -297,7 +309,7 @@ end Return the dimension of the hyperbolic space manifold $\mathcal H^n$, i.e. $\dim(\mathcal H^n) = n$. """ -manifold_dimension(::Hyperbolic{N}) where {N} = N +manifold_dimension(M::Hyperbolic) = get_parameter(M.size)[1] @doc raw""" manifold_dimension(M::Hyperbolic) @@ -342,7 +354,13 @@ the [`Lorentz`](@ref)ian manifold. """ project(::Hyperbolic, ::Any, ::Any) -Base.show(io::IO, ::Hyperbolic{N}) where {N} = print(io, "Hyperbolic($(N))") +function Base.show(io::IO, ::Hyperbolic{TypeParameter{Tuple{n}}}) where {n} + return print(io, "Hyperbolic($(n))") +end +function Base.show(io::IO, M::Hyperbolic{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "Hyperbolic($(n); parameter=:field)") +end for T in _HyperbolicTypes @eval Base.show(io::IO, p::$T) = print(io, "$($T)($(p.value))") end diff --git a/src/manifolds/HyperbolicHyperboloid.jl b/src/manifolds/HyperbolicHyperboloid.jl index 4bd14068e7..f0b15489b8 100644 --- a/src/manifolds/HyperbolicHyperboloid.jl +++ b/src/manifolds/HyperbolicHyperboloid.jl @@ -1,5 +1,5 @@ @doc raw""" - change_representer(M::Hyperbolic{n}, ::EuclideanMetric, p, X) + change_representer(M::Hyperbolic, ::EuclideanMetric, p, X) Change the Eucliden representer `X` of a cotangent vector at point `p`. We only have to correct for the metric, which means that the sign of the last entry changes, since @@ -262,7 +262,8 @@ function _get_basis( return get_basis_orthonormal(M, p, ℝ) end -function get_basis_orthonormal(M::Hyperbolic{n}, p, r::RealNumbers) where {n} +function get_basis_orthonormal(M::Hyperbolic, p, r::RealNumbers) + n = get_parameter(M.size)[1] V = [ _hyperbolize(M, p, [i == k ? one(eltype(p)) : zero(eltype(p)) for k in 1:n]) for i in 1:n @@ -364,8 +365,8 @@ i.e. $X_{n+1} = \frac{⟨\tilde p, Y⟩}{p_{n+1}}$, where $\tilde p = (p_1,\ldot _hyperbolize(::Hyperbolic, p, Y) = vcat(Y, dot(p[1:(end - 1)], Y) / p[end]) @doc raw""" - inner(M::Hyperbolic{n}, p, X, Y) - inner(M::Hyperbolic{n}, p::HyperboloidPoint, X::HyperboloidTVector, Y::HyperboloidTVector) + inner(M::Hyperbolic, p, X, Y) + inner(M::Hyperbolic, p::HyperboloidPoint, X::HyperboloidTVector, Y::HyperboloidTVector) Cmpute the inner product in the Hyperboloid model, i.e. the [`minkowski_metric`](@ref) in the embedding. The formula reads @@ -409,11 +410,12 @@ end function Random.rand!( rng::AbstractRNG, - M::Hyperbolic{N}, + M::Hyperbolic, pX; vector_at=nothing, - σ=one(eltype(pX)), -) where {N} + σ::Real=one(eltype(pX)), +) + N = get_parameter(M.size)[1] if vector_at === nothing a = randn(rng, N) f = 1 + σ * abs(randn(rng)) diff --git a/src/manifolds/HyperbolicPoincareBall.jl b/src/manifolds/HyperbolicPoincareBall.jl index a5252c963e..81726e55da 100644 --- a/src/manifolds/HyperbolicPoincareBall.jl +++ b/src/manifolds/HyperbolicPoincareBall.jl @@ -1,5 +1,5 @@ @doc raw""" - change_representer(M::Hyperbolic{n}, ::EuclideanMetric, p::PoincareBallPoint, X::PoincareBallTVector) + change_representer(M::Hyperbolic, ::EuclideanMetric, p::PoincareBallPoint, X::PoincareBallTVector) Since in the metric we have the term `` α = \frac{2}{1-\sum_{i=1}^n p_i^2}`` per element, the correction for the gradient reads `` Y = \frac{1}{α^2}X``. @@ -24,7 +24,7 @@ function change_representer!( end @doc raw""" - change_metric(M::Hyperbolic{n}, ::EuclideanMetric, p::PoincareBallPoint, X::PoincareBallTVector) + change_metric(M::Hyperbolic, ::EuclideanMetric, p::PoincareBallPoint, X::PoincareBallTVector) Since in the metric we always have the term `` α = \frac{2}{1-\sum_{i=1}^n p_i^2}`` per element, the correction for the metric reads ``Z = \frac{1}{α}X``. @@ -43,7 +43,7 @@ function change_metric!( return Y end -function check_point(M::Hyperbolic{N}, p::PoincareBallPoint; kwargs...) where {N} +function check_point(M::Hyperbolic, p::PoincareBallPoint; kwargs...) if !(norm(p.value) < 1) return DomainError( norm(p.value), @@ -52,7 +52,8 @@ function check_point(M::Hyperbolic{N}, p::PoincareBallPoint; kwargs...) where {N end end -function check_size(M::Hyperbolic{N}, p::PoincareBallPoint) where {N} +function check_size(M::Hyperbolic, p::PoincareBallPoint) + N = get_parameter(M.size)[1] if size(p.value, 1) != N !(norm(p.value) < 1) return DomainError( @@ -62,12 +63,8 @@ function check_size(M::Hyperbolic{N}, p::PoincareBallPoint) where {N} end end -function check_size( - M::Hyperbolic{N}, - p::PoincareBallPoint, - X::PoincareBallTVector; - kwargs..., -) where {N} +function check_size(M::Hyperbolic, p::PoincareBallPoint, X::PoincareBallTVector; kwargs...) + N = get_parameter(M.size)[1] if size(X.value, 1) != N return DomainError( size(X.value, 1), @@ -273,12 +270,19 @@ embed(::Hyperbolic, p::PoincareBallPoint) = p.value embed!(::Hyperbolic, q, p::PoincareBallPoint) = copyto!(q, p.value) embed(::Hyperbolic, p::PoincareBallPoint, X::PoincareBallTVector) = X.value embed!(::Hyperbolic, Y, p::PoincareBallPoint, X::PoincareBallTVector) = copyto!(Y, X.value) -get_embedding(::Hyperbolic{n}, p::PoincareBallPoint) where {n} = Euclidean(n) + +function get_embedding(::Hyperbolic{TypeParameter{Tuple{n}}}, ::PoincareBallPoint) where {n} + return Euclidean(n) +end +function get_embedding(M::Hyperbolic{Tuple{Int}}, ::PoincareBallPoint) + n = get_parameter(M.size)[1] + return Euclidean(n; parameter=:field) +end @doc raw""" inner(::Hyperbolic, p::PoincareBallPoint, X::PoincareBallTVector, Y::PoincareBallTVector) -Compute the inner producz in the Poincaré ball model. The formula reads +Compute the inner product in the Poincaré ball model. The formula reads ````math g_p(X,Y) = \frac{4}{(1-\lVert p \rVert^2)^2} ⟨X, Y⟩ . ```` diff --git a/src/manifolds/HyperbolicPoincareHalfspace.jl b/src/manifolds/HyperbolicPoincareHalfspace.jl index 973a39b8e7..0bc0c14457 100644 --- a/src/manifolds/HyperbolicPoincareHalfspace.jl +++ b/src/manifolds/HyperbolicPoincareHalfspace.jl @@ -1,4 +1,4 @@ -function check_point(M::Hyperbolic{N}, p::PoincareHalfSpacePoint; kwargs...) where {N} +function check_point(M::Hyperbolic, p::PoincareHalfSpacePoint; kwargs...) if !(last(p.value) > 0) return DomainError( norm(p.value), @@ -7,7 +7,8 @@ function check_point(M::Hyperbolic{N}, p::PoincareHalfSpacePoint; kwargs...) whe end end -function check_size(M::Hyperbolic{N}, p::PoincareHalfSpacePoint) where {N} +function check_size(M::Hyperbolic, p::PoincareHalfSpacePoint) + N = get_parameter(M.size)[1] if size(p.value, 1) != N !(norm(p.value) < 1) return DomainError( @@ -18,11 +19,12 @@ function check_size(M::Hyperbolic{N}, p::PoincareHalfSpacePoint) where {N} end function check_size( - M::Hyperbolic{N}, + M::Hyperbolic, p::PoincareHalfSpacePoint, X::PoincareHalfSpaceTVector; kwargs..., -) where {N} +) + N = get_parameter(M.size)[1] if size(X.value, 1) != N return DomainError( size(X.value, 1), @@ -210,11 +212,21 @@ embed(::Hyperbolic, p::PoincareHalfSpacePoint, X::PoincareHalfSpaceTVector) = X. function embed!(::Hyperbolic, Y, p::PoincareHalfSpacePoint, X::PoincareHalfSpaceTVector) return copyto!(Y, X.value) end -get_embedding(::Hyperbolic{n}, p::PoincareHalfSpacePoint) where {n} = Euclidean(n) + +function get_embedding( + ::Hyperbolic{TypeParameter{Tuple{n}}}, + ::PoincareHalfSpacePoint, +) where {n} + return Euclidean(n) +end +function get_embedding(M::Hyperbolic{Tuple{Int}}, ::PoincareHalfSpacePoint) + n = get_parameter(M.size)[1] + return Euclidean(n; parameter=:field) +end @doc raw""" inner( - ::Hyperbolic{n}, + ::Hyperbolic, p::PoincareHalfSpacePoint, X::PoincareHalfSpaceTVector, Y::PoincareHalfSpaceTVector diff --git a/src/manifolds/KendallsPreShapeSpace.jl b/src/manifolds/KendallsPreShapeSpace.jl index f069a493af..3a34719ad9 100644 --- a/src/manifolds/KendallsPreShapeSpace.jl +++ b/src/manifolds/KendallsPreShapeSpace.jl @@ -1,6 +1,6 @@ @doc raw""" - KendallsPreShapeSpace{n,k} <: AbstractSphere{ℝ} + KendallsPreShapeSpace{T} <: AbstractSphere{ℝ} Kendall's pre-shape space of ``k`` landmarks in ``ℝ^n`` represented by n×k matrices. In each row the sum of elements of a matrix is equal to 0. The Frobenius norm of the matrix @@ -11,20 +11,25 @@ translation and scaling of all points, so this can be thought of as a quotient m # Constructor - KendallsPreShapeSpace(n::Int, k::Int) + KendallsPreShapeSpace(n::Int, k::Int; parameter::Symbol=:type) # See also [`KendallsShapeSpace`](@ref), esp. for the references """ -struct KendallsPreShapeSpace{n,k} <: AbstractSphere{ℝ} end +struct KendallsPreShapeSpace{T} <: AbstractSphere{ℝ} + size::T +end -KendallsPreShapeSpace(n::Int, k::Int) = KendallsPreShapeSpace{n,k}() +function KendallsPreShapeSpace(n::Int, k::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, k)) + return KendallsPreShapeSpace{typeof(size)}(size) +end function active_traits(f, ::KendallsPreShapeSpace, args...) return merge_traits(IsEmbeddedSubmanifold()) end -representation_size(::KendallsPreShapeSpace{n,k}) where {n,k} = (n, k) +representation_size(M::KendallsPreShapeSpace) = get_parameter(M.size) """ check_point(M::KendallsPreShapeSpace, p; atol=sqrt(max_eps(X, Y)), kwargs...) @@ -71,8 +76,14 @@ embed(::KendallsPreShapeSpace, p, X) = X Return the space [`KendallsPreShapeSpace`](@ref) `M` is embedded in, i.e. [`ArraySphere`](@ref) of matrices of the same shape. """ -function get_embedding(::KendallsPreShapeSpace{N,K}) where {N,K} - return ArraySphere(N, K) +get_embedding(::KendallsPreShapeSpace) + +function get_embedding(::KendallsPreShapeSpace{TypeParameter{Tuple{n,k}}}) where {n,k} + return ArraySphere(n, k) +end +function get_embedding(M::KendallsPreShapeSpace{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return ArraySphere(n, k; parameter=:field) end @doc raw""" @@ -81,7 +92,10 @@ end Return the dimension of the [`KendallsPreShapeSpace`](@ref) manifold `M`. The dimension is given by ``n(k - 1) - 1``. """ -manifold_dimension(::KendallsPreShapeSpace{n,k}) where {n,k} = n * (k - 1) - 1 +function manifold_dimension(M::KendallsPreShapeSpace) + n, k = get_parameter(M.size) + return n * (k - 1) - 1 +end """ project(M::KendallsPreShapeSpace, p) @@ -131,3 +145,11 @@ function Random.rand!( end return pX end + +function Base.show(io::IO, ::KendallsPreShapeSpace{TypeParameter{Tuple{n,k}}}) where {n,k} + return print(io, "KendallsPreShapeSpace($n, $k)") +end +function Base.show(io::IO, M::KendallsPreShapeSpace{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return print(io, "KendallsPreShapeSpace($n, $k; parameter=:field)") +end diff --git a/src/manifolds/KendallsShapeSpace.jl b/src/manifolds/KendallsShapeSpace.jl index 0bcb29db52..0c3a76b698 100644 --- a/src/manifolds/KendallsShapeSpace.jl +++ b/src/manifolds/KendallsShapeSpace.jl @@ -1,6 +1,6 @@ @doc raw""" - KendallsShapeSpace{n,k} <: AbstractDecoratorManifold{ℝ} + KendallsShapeSpace{T} <: AbstractDecoratorManifold{ℝ} Kendall's shape space, defined as quotient of a [`KendallsPreShapeSpace`](@ref) (represented by n×k matrices) by the action [`ColumnwiseMultiplicationAction`](@ref). @@ -12,29 +12,45 @@ This manifold possesses the [`IsQuotientManifold`](@ref) trait. # Constructor - KendallsShapeSpace(n::Int, k::Int) + KendallsShapeSpace(n::Int, k::Int; parameter::Symbol=:type) # References """ -struct KendallsShapeSpace{n,k} <: AbstractDecoratorManifold{ℝ} end +struct KendallsShapeSpace{T} <: AbstractDecoratorManifold{ℝ} + size::T +end -KendallsShapeSpace(n::Int, k::Int) = KendallsShapeSpace{n,k}() +function KendallsShapeSpace(n::Int, k::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, k)) + return KendallsShapeSpace{typeof(size)}(size) +end function active_traits(f, ::KendallsShapeSpace, args...) return merge_traits(IsIsometricEmbeddedManifold(), IsQuotientManifold()) end -function get_orbit_action(M::KendallsShapeSpace{n,k}) where {n,k} +function get_orbit_action(M::KendallsShapeSpace{TypeParameter{Tuple{n,k}}}) where {n,k} return ColumnwiseMultiplicationAction(M, SpecialOrthogonal(n)) end +function get_orbit_action(M::KendallsShapeSpace{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return ColumnwiseMultiplicationAction(M, SpecialOrthogonal(n; parameter=:field)) +end @doc raw""" - get_total_space(::Grassmann{n,k}) + get_total_space(::KendallsShapeSpace) Return the total space of the [`KendallsShapeSpace`](@ref) manifold, which is the [`KendallsPreShapeSpace`](@ref) manifold. """ -get_total_space(::KendallsShapeSpace{n,k}) where {n,k} = KendallsPreShapeSpace(n, k) +get_total_space(::KendallsShapeSpace) +function get_total_space(::KendallsShapeSpace{TypeParameter{Tuple{n,k}}}) where {n,k} + return KendallsPreShapeSpace(n, k) +end +function get_total_space(M::KendallsShapeSpace{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return KendallsPreShapeSpace(n, k; parameter=:field) +end function distance(M::KendallsShapeSpace, p, q) A = get_orbit_action(M) @@ -66,8 +82,14 @@ embed(::KendallsShapeSpace, p, X) = X Get the manifold in which [`KendallsShapeSpace`](@ref) `M` is embedded, i.e. [`KendallsPreShapeSpace`](@ref) of matrices of the same shape. """ -function get_embedding(::KendallsShapeSpace{N,K}) where {N,K} - return KendallsPreShapeSpace(N, K) +get_embedding(::KendallsShapeSpace) + +function get_embedding(::KendallsShapeSpace{TypeParameter{Tuple{n,k}}}) where {n,k} + return KendallsPreShapeSpace(n, k) +end +function get_embedding(M::KendallsShapeSpace{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return KendallsPreShapeSpace(n, k; parameter=:field) end """ @@ -129,7 +151,8 @@ given by ``n(k - 1) - 1 - n(n - 1)/2`` in the typical case where ``k \geq n+1``, ``(k + 1)(k - 2) / 2`` otherwise, unless ``k`` is equal to 1, in which case the dimension is 0. See [Kendall:1984](@cite) for a discussion of the over-dimensioned case. """ -function manifold_dimension(::KendallsShapeSpace{n,k}) where {n,k} +function manifold_dimension(M::KendallsShapeSpace) + n, k = get_parameter(M.size) if k < n + 1 # over-dimensioned case if k == 1 return 0 @@ -167,11 +190,19 @@ rand(::KendallsShapeSpace; σ::Real=1.0) function Random.rand!( rng::AbstractRNG, - M::KendallsShapeSpace{n,k}, + M::KendallsShapeSpace, pX; vector_at=nothing, σ::Real=one(eltype(pX)), -) where {n,k} +) rand!(rng, get_embedding(M), pX; vector_at=vector_at, σ=σ) return pX end + +function Base.show(io::IO, ::KendallsShapeSpace{TypeParameter{Tuple{n,k}}}) where {n,k} + return print(io, "KendallsShapeSpace($n, $k)") +end +function Base.show(io::IO, M::KendallsShapeSpace{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return print(io, "KendallsShapeSpace($n, $k; parameter=:field)") +end diff --git a/src/manifolds/Lorentz.jl b/src/manifolds/Lorentz.jl index aab3701be3..438eaa068f 100644 --- a/src/manifolds/Lorentz.jl +++ b/src/manifolds/Lorentz.jl @@ -16,7 +16,7 @@ see [`minkowski_metric`](@ref) for the formula. struct MinkowskiMetric <: LorentzMetric end @doc raw""" - Lorentz{N} = MetricManifold{Euclidean{N,ℝ},LorentzMetric} + Lorentz{T} = MetricManifold{Euclidean{T,ℝ},LorentzMetric} The Lorentz manifold (or Lorentzian) is a pseudo-Riemannian manifold. @@ -27,24 +27,26 @@ The Lorentz manifold (or Lorentzian) is a pseudo-Riemannian manifold. Generate the Lorentz manifold of dimension `n` with the [`LorentzMetric`](@ref) `m`, which is by default set to the [`MinkowskiMetric`](@ref). """ -const Lorentz = MetricManifold{ℝ,Euclidean{Tuple{N},ℝ},<:LorentzMetric} where {N} +const Lorentz = MetricManifold{ℝ,Euclidean{T,ℝ},<:LorentzMetric} where {T} -function Lorentz(n, m::MT=MinkowskiMetric()) where {MT<:LorentzMetric} - return Lorentz{n,typeof(m)}(Euclidean(n), m) +function Lorentz(n::Int, m::LorentzMetric=MinkowskiMetric(); parameter::Symbol=:type) + E = Euclidean(n; parameter=parameter) + return Lorentz(E, m) +end +function Lorentz(E::Euclidean{T}, m::LorentzMetric=MinkowskiMetric()) where {T} + return Lorentz{T,typeof(m)}(E, m) end -function local_metric( - ::MetricManifold{ℝ,Euclidean{Tuple{N},ℝ},MinkowskiMetric}, - p, -) where {N} - return Diagonal([ones(N - 1)..., -1]) +function local_metric(M::Lorentz{<:Any,MinkowskiMetric}, p) + n = get_parameter(M.manifold.size)[1] + return Diagonal([ones(n - 1)..., -1]) end -function inner(::MetricManifold{ℝ,Euclidean{Tuple{N},ℝ},MinkowskiMetric}, p, X, Y) where {N} +function inner(::Lorentz{<:Any,MinkowskiMetric}, p, X, Y) return minkowski_metric(X, Y) end @doc raw""" - minkowski_metric(a,b) + minkowski_metric(a, b) Compute the minkowski metric on $\mathbb R^n$ is given by ````math diff --git a/src/manifolds/MetricManifold.jl b/src/manifolds/MetricManifold.jl index e83539a050..87ba053734 100644 --- a/src/manifolds/MetricManifold.jl +++ b/src/manifolds/MetricManifold.jl @@ -426,11 +426,10 @@ is_default_metric(::AbstractManifold, ::AbstractMetric) = false function is_point( ::TraitList{IsMetricManifold}, M::MetricManifold{𝔽,TM,G}, - p, - te::Bool=false; + p; kwargs..., ) where {𝔽,G<:AbstractMetric,TM<:AbstractManifold} - return is_point(M.manifold, p, te; kwargs...) + return is_point(M.manifold, p; kwargs...) end function is_vector( @@ -438,11 +437,10 @@ function is_vector( M::MetricManifold{𝔽,TM,G}, p, X, - te::Bool=false, - cbp=true; + cbp::Bool=true; kwargs..., ) where {𝔽,G<:AbstractMetric,TM<:AbstractManifold} - return is_vector(M.manifold, p, X, te, cbp; kwargs...) + return is_vector(M.manifold, p, X, cbp; kwargs...) end @doc raw""" diff --git a/src/manifolds/Multinomial.jl b/src/manifolds/Multinomial.jl index 53e5aad0da..e01f2b763d 100644 --- a/src/manifolds/Multinomial.jl +++ b/src/manifolds/Multinomial.jl @@ -18,22 +18,32 @@ The [`ProbabilitySimplex`](@ref) is stored internally within `M.manifold`, such # Constructor - MultinomialMatrices(n, m) + MultinomialMatrices(n::Int, m::Int; parameter::Symbol=:type) Generate the manifold of matrices $\mathbb R^{n×m}$ such that the $m$ columns are discrete probability distributions, i.e. sum up to one. + +`parameter`: whether a type parameter should be used to store `n` and `m`. By default size +is stored in type. Value can either be `:field` or `:type`. """ -struct MultinomialMatrices{N,M,S} <: - AbstractPowerManifold{ℝ,ProbabilitySimplex{S},ArrayPowerRepresentation} where {N,M} - manifold::ProbabilitySimplex{S} +struct MultinomialMatrices{T,TPM<:ProbabilitySimplex} <: + AbstractPowerManifold{ℝ,TPM,ArrayPowerRepresentation} + size::T + manifold::TPM end -function MultinomialMatrices(n::Int, m::Int) - return MultinomialMatrices{n,m,n - 1}(ProbabilitySimplex(n - 1)) +function MultinomialMatrices(n::Int, m::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, m)) + MPS = ProbabilitySimplex(n - 1; parameter=parameter) + return MultinomialMatrices{typeof(size),typeof(MPS)}(size, MPS) end -function Base.:^(M::ProbabilitySimplex{N}, m::Int) where {N} - return MultinomialMatrices{manifold_dimension(M) + 1,m,N}(M) +function Base.:^(::ProbabilitySimplex{TypeParameter{Tuple{N}}}, m::Int) where {N} + return MultinomialMatrices(N + 1, m) +end +function Base.:^(M::ProbabilitySimplex{Tuple{Int}}, m::Int) + n = get_parameter(M.size)[1] + return MultinomialMatrices(n + 1, m; parameter=:field) end @doc raw""" @@ -44,7 +54,8 @@ of `m` discrete probability distributions as columns from $\mathbb R^{n}$, i.e. [`ProbabilitySimplex`](@ref)`(n-1)`. """ check_point(::MultinomialMatrices, ::Any) -function check_point(M::MultinomialMatrices{n,m}, p; kwargs...) where {n,m} +function check_point(M::MultinomialMatrices, p; kwargs...) + n, m = get_parameter(M.size) return check_point(PowerManifold(M.manifold, m), p; kwargs...) end @@ -55,17 +66,31 @@ Checks whether `X` is a valid tangent vector to `p` on the [`MultinomialMatrices This means, that `p` is valid, that `X` is of correct dimension and columnswise a tangent vector to the columns of `p` on the [`ProbabilitySimplex`](@ref). """ -function check_vector(M::MultinomialMatrices{n,m}, p, X; kwargs...) where {n,m} +function check_vector(M::MultinomialMatrices, p, X; kwargs...) + n, m = get_parameter(M.size) return check_vector(PowerManifold(M.manifold, m), p, X; kwargs...) end -get_iterator(::MultinomialMatrices{n,m}) where {n,m} = Base.OneTo(m) +function get_iterator(M::MultinomialMatrices) + n, m = get_parameter(M.size) + return Base.OneTo(m) +end -@generated manifold_dimension(::MultinomialMatrices{n,m}) where {n,m} = (n - 1) * m -@generated power_dimensions(::MultinomialMatrices{n,m}) where {n,m} = (m,) +function manifold_dimension(M::MultinomialMatrices) + n, m = get_parameter(M.size) + return (n - 1) * m +end +function power_dimensions(M::MultinomialMatrices) + n, m = get_parameter(M.size) + return (m,) +end -@generated representation_size(::MultinomialMatrices{n,m}) where {n,m} = (n, m) +representation_size(M::MultinomialMatrices) = get_parameter(M.size) -function Base.show(io::IO, ::MultinomialMatrices{n,m}) where {n,m} - return print(io, "MultinomialMatrices($(n),$(m))") +function Base.show(io::IO, ::MultinomialMatrices{TypeParameter{Tuple{n,m}}}) where {n,m} + return print(io, "MultinomialMatrices($(n), $(m))") +end +function Base.show(io::IO, M::MultinomialMatrices{Tuple{Int,Int}}) + n, m = get_parameter(M.size) + return print(io, "MultinomialMatrices($(n), $(m); parameter=:field)") end diff --git a/src/manifolds/MultinomialDoublyStochastic.jl b/src/manifolds/MultinomialDoublyStochastic.jl index 9e02dd6e88..d8dfa1dcac 100644 --- a/src/manifolds/MultinomialDoublyStochastic.jl +++ b/src/manifolds/MultinomialDoublyStochastic.jl @@ -1,18 +1,18 @@ @doc raw""" - AbstractMultinomialDoublyStochastic{N} <: AbstractDecoratorManifold{ℝ} + AbstractMultinomialDoublyStochastic <: AbstractDecoratorManifold{ℝ} A common type for manifolds that are doubly stochastic, for example by direct constraint [`MultinomialDoubleStochastic`](@ref) or by symmetry [`MultinomialSymmetric`](@ref), as long as they are also modeled as [`IsIsometricEmbeddedManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/decorator.html#ManifoldsBase.IsIsometricEmbeddedManifold). """ -abstract type AbstractMultinomialDoublyStochastic{N} <: AbstractDecoratorManifold{ℝ} end +abstract type AbstractMultinomialDoublyStochastic <: AbstractDecoratorManifold{ℝ} end function active_traits(f, ::AbstractMultinomialDoublyStochastic, args...) return merge_traits(IsIsometricEmbeddedManifold()) end @doc raw""" - MultinomialDoublyStochastic{n} <: AbstractMultinomialDoublyStochastic{N} + MultinomialDoublyStochastic{T} <: AbstractMultinomialDoublyStochastic The set of doubly stochastic multinomial matrices consists of all $n×n$ matrices with stochastic columns and rows, i.e. @@ -41,14 +41,17 @@ More details can be found in Section III [DouikHassibi:2019](@cite). # Constructor - MultinomialDoubleStochastic(n) + MultinomialDoubleStochastic(n::Int; parameter::Symbol=:type) Generate the manifold of matrices $\mathbb R^{n×n}$ that are doubly stochastic and symmetric. """ -struct MultinomialDoubleStochastic{N} <: AbstractMultinomialDoublyStochastic{N} end +struct MultinomialDoubleStochastic{T} <: AbstractMultinomialDoublyStochastic + size::T +end -function MultinomialDoubleStochastic(n::Int) - return MultinomialDoubleStochastic{n}() +function MultinomialDoubleStochastic(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return MultinomialDoubleStochastic{typeof(size)}(size) end @doc raw""" @@ -57,7 +60,8 @@ end Checks whether `p` is a valid point on the [`MultinomialDoubleStochastic`](@ref)`(n)` `M`, i.e. is a matrix with positive entries whose rows and columns sum to one. """ -function check_point(M::MultinomialDoubleStochastic{n}, p; kwargs...) where {n} +function check_point(M::MultinomialDoubleStochastic, p; kwargs...) + n = get_parameter(M.size)[1] r = sum(p, dims=2) if !isapprox(norm(r - ones(n, 1)), 0.0; kwargs...) return DomainError( @@ -74,7 +78,7 @@ Checks whether `X` is a valid tangent vector to `p` on the [`MultinomialDoubleSt This means, that `p` is valid, that `X` is of correct dimension and sums to zero along any column or row. """ -function check_vector(M::MultinomialDoubleStochastic{n}, p, X; kwargs...) where {n} +function check_vector(M::MultinomialDoubleStochastic, p, X; kwargs...) r = sum(X, dims=2) # check for stochastic rows if !isapprox(norm(r), 0.0; kwargs...) return DomainError( @@ -85,8 +89,12 @@ function check_vector(M::MultinomialDoubleStochastic{n}, p, X; kwargs...) where return nothing end -function get_embedding(::MultinomialDoubleStochastic{N}) where {N} - return MultinomialMatrices(N, N) +function get_embedding(::MultinomialDoubleStochastic{TypeParameter{Tuple{n}}}) where {n} + return MultinomialMatrices(n, n) +end +function get_embedding(M::MultinomialDoubleStochastic{Tuple{Int}}) + n = get_parameter(M.size)[1] + return MultinomialMatrices(n, n; parameter=:field) end """ @@ -97,7 +105,7 @@ Return false. [`MultinomialDoubleStochastic`](@ref) is not a flat manifold. is_flat(M::MultinomialDoubleStochastic) = false @doc raw""" - manifold_dimension(M::MultinomialDoubleStochastic{n}) where {n} + manifold_dimension(M::MultinomialDoubleStochastic) returns the dimension of the [`MultinomialDoubleStochastic`](@ref) manifold namely @@ -105,12 +113,13 @@ namely \operatorname{dim}_{\mathcal{DP}(n)} = (n-1)^2. ```` """ -@generated function manifold_dimension(::MultinomialDoubleStochastic{n}) where {n} +function manifold_dimension(M::MultinomialDoubleStochastic) + n = get_parameter(M.size)[1] return (n - 1)^2 end @doc raw""" - project(M::MultinomialDoubleStochastic{n}, p, Y) where {n} + project(M::MultinomialDoubleStochastic, p, Y) Project `Y` onto the tangent space at `p` on the [`MultinomialDoubleStochastic`](@ref) `M`, return the result in `X`. The formula reads @@ -130,7 +139,8 @@ where $I_n$ is the $n×n$ unit matrix and $\mathbf{1}_n$ is the vector of length """ project(::MultinomialDoubleStochastic, ::Any, ::Any) -function project!(::MultinomialDoubleStochastic{n}, X, p, Y) where {n} +function project!(M::MultinomialDoubleStochastic, X, p, Y) + n = get_parameter(M.size)[1] ζ = [I p; p I] \ [sum(Y, dims=2); sum(Y, dims=1)'] # Formula (25) from 1802.02628 return X .= Y .- (repeat(ζ[1:n], 1, 3) .+ repeat(ζ[(n + 1):end]', 3, 1)) .* p end @@ -153,12 +163,12 @@ function project(M::AbstractMultinomialDoublyStochastic, p; kwargs...) end function project!( - ::AbstractMultinomialDoublyStochastic{n}, + ::AbstractMultinomialDoublyStochastic, q, p; 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.", @@ -180,7 +190,8 @@ function project!( return q end -@generated function representation_size(::MultinomialDoubleStochastic{n}) where {n} +function representation_size(M::MultinomialDoubleStochastic) + n = get_parameter(M.size)[1] return (n, n) end @@ -197,6 +208,10 @@ function retract_project!(M::MultinomialDoubleStochastic, q, p, X, t::Number) return project!(M, q, p .* exp.(t .* X ./ p)) end -function Base.show(io::IO, ::MultinomialDoubleStochastic{n}) where {n} +function Base.show(io::IO, ::MultinomialDoubleStochastic{TypeParameter{Tuple{n}}}) where {n} return print(io, "MultinomialDoubleStochastic($(n))") end +function Base.show(io::IO, M::MultinomialDoubleStochastic{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "MultinomialDoubleStochastic($(n); parameter=:field)") +end diff --git a/src/manifolds/MultinomialSymmetric.jl b/src/manifolds/MultinomialSymmetric.jl index 3ee993c120..23b178b368 100644 --- a/src/manifolds/MultinomialSymmetric.jl +++ b/src/manifolds/MultinomialSymmetric.jl @@ -1,5 +1,5 @@ @doc raw""" - MultinomialSymmetric{n} <: AbstractMultinomialDoublyStochastic{N} + MultinomialSymmetric{T} <: AbstractMultinomialDoublyStochastic{N} The multinomial symmetric matrices manifold consists of all symmetric $n×n$ matrices with positive entries such that each column sums to one, i.e. @@ -39,10 +39,13 @@ More details can be found in Section IV [DouikHassibi:2019](@cite). Generate the manifold of matrices $\mathbb R^{n×n}$ that are doubly stochastic and symmetric. """ -struct MultinomialSymmetric{N} <: AbstractMultinomialDoublyStochastic{N} end +struct MultinomialSymmetric{T} <: AbstractMultinomialDoublyStochastic + size::T +end -function MultinomialSymmetric(n::Int) - return MultinomialSymmetric{n}() +function MultinomialSymmetric(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return MultinomialSymmetric{typeof(size)}(size) end @doc raw""" @@ -51,7 +54,8 @@ end Checks whether `p` is a valid point on the [`MultinomialSymmetric`](@ref)`(m,n)` `M`, i.e. is a symmetric matrix with positive entries whose rows sum to one. """ -function check_point(M::MultinomialSymmetric{n}, p; kwargs...) where {n} +function check_point(M::MultinomialSymmetric, p; kwargs...) + n = get_parameter(M.size)[1] return check_point(SymmetricMatrices(n, ℝ), p) end @doc raw""" @@ -61,17 +65,22 @@ Checks whether `X` is a valid tangent vector to `p` on the [`MultinomialSymmetri This means, that `p` is valid, that `X` is of correct dimension, symmetric, and sums to zero along any row. """ -function check_vector(M::MultinomialSymmetric{n}, p, X; kwargs...) where {n} +function check_vector(M::MultinomialSymmetric, p, X; kwargs...) + n = get_parameter(M.size)[1] return check_vector(SymmetricMatrices(n, ℝ), p, X; kwargs...) end -function get_embedding(::MultinomialSymmetric{N}) where {N} - return MultinomialMatrices(N, N) -end - embed!(::MultinomialSymmetric, q, p) = copyto!(q, p) embed!(::MultinomialSymmetric, Y, ::Any, X) = copyto!(Y, X) +function get_embedding(::MultinomialSymmetric{TypeParameter{Tuple{n}}}) where {n} + return MultinomialMatrices(n, n) +end +function get_embedding(M::MultinomialSymmetric{Tuple{Int}}) + n = get_parameter(M.size)[1] + return MultinomialMatrices(n, n; parameter=:field) +end + """ is_flat(::MultinomialSymmetric) @@ -80,7 +89,7 @@ Return false. [`MultinomialSymmetric`](@ref) is not a flat manifold. is_flat(M::MultinomialSymmetric) = false @doc raw""" - manifold_dimension(M::MultinomialSymmetric{n}) where {n} + manifold_dimension(M::MultinomialSymmetric) returns the dimension of the [`MultinomialSymmetric`](@ref) manifold namely @@ -88,12 +97,13 @@ namely \operatorname{dim}_{\mathcal{SP}(n)} = \frac{n(n-1)}{2}. ```` """ -@generated function manifold_dimension(::MultinomialSymmetric{n}) where {n} +function manifold_dimension(M::MultinomialSymmetric) + n = get_parameter(M.size)[1] return div(n * (n - 1), 2) end @doc raw""" - project(M::MultinomialSymmetric{n}, p, Y) where {n} + project(M::MultinomialSymmetric, p, Y) Project `Y` onto the tangent space at `p` on the [`MultinomialSymmetric`](@ref) `M`, return the result in `X`. The formula reads @@ -110,12 +120,13 @@ where $I_n$ is teh $n×n$ unit matrix and $\mathbf{1}_n$ is the vector of length """ project(::MultinomialSymmetric, ::Any, ::Any) -function project!(::MultinomialSymmetric{n}, X, p, Y) where {n} +function project!(::MultinomialSymmetric, X, p, Y) α = (I + p) \ sum(Y, dims=2) # Formula (49) from 1802.02628 return X .= Y .- (repeat(α, 1, 3) .+ repeat(α', 3, 1)) .* p end -@generated function representation_size(::MultinomialSymmetric{n}) where {n} +function representation_size(M::MultinomialSymmetric) + n = get_parameter(M.size)[1] return (n, n) end @@ -132,6 +143,10 @@ function retract_project!(M::MultinomialSymmetric, q, p, X, t::Number) return project!(M, q, p .* exp.(t .* X ./ p)) end -function Base.show(io::IO, ::MultinomialSymmetric{n}) where {n} +function Base.show(io::IO, ::MultinomialSymmetric{TypeParameter{Tuple{n}}}) where {n} return print(io, "MultinomialSymmetric($(n))") end +function Base.show(io::IO, M::MultinomialSymmetric{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "MultinomialSymmetric($(n); parameter=:field)") +end diff --git a/src/manifolds/Oblique.jl b/src/manifolds/Oblique.jl index f03a213fe1..9663f92633 100644 --- a/src/manifolds/Oblique.jl +++ b/src/manifolds/Oblique.jl @@ -1,5 +1,5 @@ @doc raw""" - Oblique{N,M,𝔽} <: AbstractPowerManifold{𝔽} + Oblique{T,𝔽,S} <: AbstractPowerManifold{𝔽} The oblique manifold $\mathcal{OB}(n,m)$ is the set of 𝔽-valued matrices with unit norm column endowed with the metric from the embedding. This yields exactly the same metric as @@ -11,31 +11,40 @@ The [`Sphere`](@ref) is stored internally within `M.manifold`, such that all fun # Constructor - Oblique(n,m) + Oblique(n::Int, m::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) Generate the manifold of matrices $\mathbb R^{n × m}$ such that the $m$ columns are unit vectors, i.e. from the [`Sphere`](@ref)`(n-1)`. """ -struct Oblique{N,M,𝔽,S} <: - AbstractPowerManifold{𝔽,Sphere{S,𝔽},ArrayPowerRepresentation} where {N,M} +struct Oblique{T,𝔽,S} <: AbstractPowerManifold{𝔽,Sphere{S,𝔽},ArrayPowerRepresentation} + size::T manifold::Sphere{S,𝔽} end -function Oblique(n::Int, m::Int, field::AbstractNumbers=ℝ) - return Oblique{n,m,field,n - 1}(Sphere(n - 1, field)) +function Oblique(n::Int, m::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + sphere = Sphere(n - 1, field; parameter=parameter) + size = wrap_type_parameter(parameter, (n, m)) + return Oblique{typeof(size),field,typeof(sphere).parameters[1]}(size, sphere) end -Base.:^(M::Sphere{N,𝔽}, m::Int) where {N,𝔽} = Oblique{manifold_dimension(M) + 1,m,𝔽,N}(M) +function Base.:^(::Sphere{TypeParameter{Tuple{N}},𝔽}, m::Int) where {N,𝔽} + return Oblique(N + 1, m, 𝔽) +end +function Base.:^(M::Sphere{Tuple{Int},𝔽}, m::Int) where {𝔽} + N = M.size[1] + return Oblique(N + 1, m, 𝔽; parameter=:field) +end @doc raw""" - check_point(M::Oblique{n,m},p) + check_point(M::Oblique, p) Checks whether `p` is a valid point on the [`Oblique`](@ref)`{m,n}` `M`, i.e. is a matrix of `m` unit columns from $\mathbb R^{n}$, i.e. each column is a point from [`Sphere`](@ref)`(n-1)`. """ check_point(::Oblique, ::Any) -function check_point(M::Oblique{n,m}, p; kwargs...) where {n,m} +function check_point(M::Oblique, p; kwargs...) + n, m = get_parameter(M.size) return check_point(PowerManifold(M.manifold, m), p; kwargs...) end @doc raw""" @@ -45,18 +54,20 @@ Checks whether `X` is a valid tangent vector to `p` on the [`Oblique`](@ref) `M` This means, that `p` is valid, that `X` is of correct dimension and columnswise a tangent vector to the columns of `p` on the [`Sphere`](@ref). """ -function check_vector(M::Oblique{n,m}, p, X; kwargs...) where {n,m} +function check_vector(M::Oblique, p, X; kwargs...) + n, m = get_parameter(M.size) return check_vector(PowerManifold(M.manifold, m), p, X; kwargs...) end -get_iterator(::Oblique{n,m}) where {n,m} = Base.OneTo(m) +get_iterator(M::Oblique) = Base.OneTo(get_parameter(M.size)[2]) -@generated function manifold_dimension(::Oblique{n,m,𝔽}) where {n,m,𝔽} +function manifold_dimension(M::Oblique{<:Any,𝔽}) where {𝔽} + n, m = get_parameter(M.size) return (n * real_dimension(𝔽) - 1) * m end -power_dimensions(::Oblique{n,m}) where {n,m} = (m,) +power_dimensions(M::Oblique) = get_parameter(M.size)[2] -@generated representation_size(::Oblique{n,m}) where {n,m} = (n, m) +representation_size(M::Oblique) = get_parameter(M.size) @doc raw""" parallel_transport_to(M::Oblique, p, X, q) @@ -67,6 +78,10 @@ doing a column wise parallel transport on the [`Sphere`](@ref) """ parallel_transport_to(::Oblique, p, X, q) -function Base.show(io::IO, ::Oblique{n,m,𝔽}) where {n,m,𝔽} - return print(io, "Oblique($(n),$(m); field = $(𝔽))") +function Base.show(io::IO, ::Oblique{TypeParameter{Tuple{n,m}},𝔽}) where {n,m,𝔽} + return print(io, "Oblique($(n), $(m); field=$(𝔽))") +end +function Base.show(io::IO, M::Oblique{Tuple{Int,Int},𝔽}) where {𝔽} + n, m = get_parameter(M.size) + return print(io, "Oblique($(n), $(m); field=$(𝔽), parameter=:field)") end diff --git a/src/manifolds/Orthogonal.jl b/src/manifolds/Orthogonal.jl index de3051a00e..b1db00d891 100644 --- a/src/manifolds/Orthogonal.jl +++ b/src/manifolds/Orthogonal.jl @@ -7,7 +7,10 @@ The manifold of (real) orthogonal matrices ``\mathrm{O}(n)``. """ const OrthogonalMatrices{n} = GeneralUnitaryMatrices{n,ℝ,AbsoluteDeterminantOneMatrices} -OrthogonalMatrices(n) = OrthogonalMatrices{n}() +function OrthogonalMatrices(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return OrthogonalMatrices{typeof(size)}(size) +end function Random.rand!( rng::AbstractRNG, @@ -33,6 +36,10 @@ function Random.rand!( return pX end -function Base.show(io::IO, ::OrthogonalMatrices{n}) where {n} +function Base.show(io::IO, ::OrthogonalMatrices{TypeParameter{Tuple{n}}}) where {n} return print(io, "OrthogonalMatrices($(n))") end +function Base.show(io::IO, M::OrthogonalMatrices{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "OrthogonalMatrices($n; parameter=:field)") +end diff --git a/src/manifolds/PositiveNumbers.jl b/src/manifolds/PositiveNumbers.jl index 81267c86a4..84313f329c 100644 --- a/src/manifolds/PositiveNumbers.jl +++ b/src/manifolds/PositiveNumbers.jl @@ -15,28 +15,40 @@ please use [`SymmetricPositiveDefinite`](@ref)`(1)`. struct PositiveNumbers <: AbstractManifold{ℝ} end """ - PositiveVectors(n) + PositiveVectors(n::Integer; parameter::Symbol=:type) Generate the manifold of vectors with positive entries. This manifold is modeled as a [`PowerManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.PowerManifold) of [`PositiveNumbers`](@ref). + +`parameter`: whether a type parameter should be used to store `n`. By default size +is stored in a type parameter. Value can either be `:field` or `:type`. """ -PositiveVectors(n::Integer) = PositiveNumbers()^n +PositiveVectors(n::Integer; parameter::Symbol=:type) = + PowerManifold(PositiveNumbers(), n; parameter=parameter) """ - PositiveMatrices(m,n) + PositiveMatrices(m::Integer, n::Integer; parameter::Symbol=:type) Generate the manifold of matrices with positive entries. This manifold is modeled as a [`PowerManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.PowerManifold) of [`PositiveNumbers`](@ref). + +`parameter`: whether a type parameter should be used to store `n`. By default size +is stored in a type parameter. Value can either be `:field` or `:type`. """ -PositiveMatrices(n::Integer, m::Integer) = PositiveNumbers()^(n, m) +PositiveMatrices(n::Integer, m::Integer; parameter::Symbol=:type) = + PowerManifold(PositiveNumbers(), n, m; parameter=parameter) """ - PositiveArrays(n₁,n₂,...,nᵢ) + PositiveArrays(n₁, n₂, ..., nᵢ; parameter::Symbol=:type) Generate the manifold of `i`-dimensional arrays with positive entries. This manifold is modeled as a [`PowerManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.PowerManifold) of [`PositiveNumbers`](@ref). + +`parameter`: whether a type parameter should be used to store `n`. By default size +is stored in a type parameter. Value can either be `:field` or `:type`. """ -PositiveArrays(n::Vararg{Int,I}) where {I} = PositiveNumbers()^(n) +PositiveArrays(n::Vararg{Int,I}; parameter::Symbol=:type) where {I} = + PowerManifold(PositiveNumbers(), n...; parameter=parameter) @doc raw""" change_representer(M::PositiveNumbers, E::EuclideanMetric, p, X) @@ -267,13 +279,23 @@ Base.show(io::IO, ::PositiveNumbers) = print(io, "PositiveNumbers()") function Base.show( io::IO, - ::PowerManifold{ℝ,PositiveNumbers,TSize,ArrayPowerRepresentation}, -) where {TSize} - s = [TSize.parameters...] + M::PowerManifold{ℝ,PositiveNumbers,TSize,ArrayPowerRepresentation}, +) where {TSize<:TypeParameter} + s = get_parameter(M.size) (length(s) == 1) && return print(io, "PositiveVectors($(s[1]))") (length(s) == 2) && return print(io, "PositiveMatrices($(s[1]), $(s[2]))") return print(io, "PositiveArrays($(join(s, ", ")))") end +function Base.show( + io::IO, + M::PowerManifold{ℝ,PositiveNumbers,TSize,ArrayPowerRepresentation}, +) where {TSize<:Tuple} + s = get_parameter(M.size) + (length(s) == 1) && return print(io, "PositiveVectors($(s[1]); parameter=:field)") + (length(s) == 2) && + return print(io, "PositiveMatrices($(s[1]), $(s[2]); parameter=:field)") + return print(io, "PositiveArrays($(join(s, ", ")); parameter=:field)") +end @doc raw""" parallel_transport_to(M::PositiveNumbers, p, X, q) diff --git a/src/manifolds/PowerManifold.jl b/src/manifolds/PowerManifold.jl index 56aaa46574..a52d535ee4 100644 --- a/src/manifolds/PowerManifold.jl +++ b/src/manifolds/PowerManifold.jl @@ -21,8 +21,13 @@ tangent space of the power manifold. """ struct PowerMetric <: AbstractMetric end -function PowerManifold(M::AbstractManifold{𝔽}, size::Integer...) where {𝔽} - return PowerManifold{𝔽,typeof(M),Tuple{size...},ArrayPowerRepresentation}(M) +function PowerManifold( + M::AbstractManifold{𝔽}, + size::Integer...; + parameter::Symbol=:field, +) where {𝔽} + size_w = wrap_type_parameter(parameter, size) + return PowerManifold{𝔽,typeof(M),typeof(size_w),ArrayPowerRepresentation}(M, size_w) end """ @@ -38,20 +43,16 @@ struct PowerPointDistribution{TM<:AbstractPowerManifold,TD<:MPointDistribution,T end """ - PowerFVectorDistribution([type::VectorBundleFibers], [x], distr) + PowerFVectorDistribution([type::VectorSpaceFiber], [x], distr) Generates a random vector at a `point` from vector space (a fiber of a tangent bundle) of type `type` using the power distribution of `distr`. Vector space type and `point` can be automatically inferred from distribution `distr`. """ -struct PowerFVectorDistribution{ - TSpace<:VectorBundleFibers{<:VectorSpaceType,<:AbstractPowerManifold}, - TD<:FVectorDistribution, - TX, -} <: FVectorDistribution{TSpace,TX} +struct PowerFVectorDistribution{TSpace<:VectorSpaceFiber,TD<:FVectorDistribution} <: + FVectorDistribution{TSpace} type::TSpace - point::TX distribution::TD end @@ -63,12 +64,6 @@ Base.:^(M::AbstractManifold, n) = PowerManifold(M, n...) function allocate(::PowerManifoldNestedReplacing, x::AbstractArray{<:SArray}) return similar(x) end -function allocate( - ::PowerManifoldNestedReplacing, - x::AbstractArray{<:ProductRepr{<:NTuple{N,SArray}}}, -) where {N} - return similar(x) -end function allocate( ::PowerManifoldNestedReplacing, x::AbstractArray{<:ArrayPartition{T,<:NTuple{N,SArray}}}, @@ -97,48 +92,6 @@ function allocate_result(M::PowerManifoldNestedReplacing, f, ::Identity, x...) return allocate_result(M, f, x...) end -""" - change_representer(M::AbstractPowerManifold, ::AbstractMetric, p, X) - -Since the metric on a power manifold decouples, the change of a representer can be done elementwise -""" -change_representer(::AbstractPowerManifold, ::AbstractMetric, ::Any, ::Any) - -function change_representer!(M::AbstractPowerManifold, Y, G::AbstractMetric, p, X) - rep_size = representation_size(M.manifold) - for i in get_iterator(M) - change_representer!( - M.manifold, - _write(M, rep_size, Y, i), - G, - _read(M, rep_size, p, i), - _read(M, rep_size, X, i), - ) - end - return Y -end - -""" - change_metric(M::AbstractPowerManifold, ::AbstractMetric, p, X) - -Since the metric on a power manifold decouples, the change of metric can be done elementwise. -""" -change_metric(M::AbstractPowerManifold, ::AbstractMetric, ::Any, ::Any) - -function change_metric!(M::AbstractPowerManifold, Y, G::AbstractMetric, p, X) - rep_size = representation_size(M.manifold) - for i in get_iterator(M) - change_metric!( - M.manifold, - _write(M, rep_size, Y, i), - G, - _read(M, rep_size, p, i), - _read(M, rep_size, X, i), - ) - end - return Y -end - @doc raw""" flat(M::AbstractPowerManifold, p, X) @@ -182,12 +135,13 @@ end Return the manifold volume of an [`PowerManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.PowerManifold) `M`. """ -function manifold_volume(M::PowerManifold{𝔽,<:AbstractManifold,TSize}) where {𝔽,TSize} - return manifold_volume(M.manifold)^prod(size_to_tuple(TSize)) +function manifold_volume(M::PowerManifold) + size = get_parameter(M.size) + return manifold_volume(M.manifold)^prod(size) end function Random.rand(rng::AbstractRNG, d::PowerFVectorDistribution) - fv = zero_vector(d.type, d.point) + fv = zero_vector(d.type.manifold, d.type.point) Distributions._rand!(rng, d, fv) return fv end @@ -205,7 +159,7 @@ function Distributions._rand!( PM = d.type.manifold rep_size = representation_size(PM.manifold) for i in get_iterator(d.type.manifold) - copyto!(d.distribution.point, _read(PM, rep_size, d.point, i)) + copyto!(d.distribution.type.point, _read(PM, rep_size, d.type.point, i)) Distributions._rand!(rng, d.distribution, _read(PM, rep_size, v, i)) end return v @@ -262,8 +216,8 @@ function Base.view( return _write(M, rep_size, p, I...) end -function representation_size(M::PowerManifold{𝔽,<:AbstractManifold,TSize}) where {𝔽,TSize} - return (representation_size(M.manifold)..., size_to_tuple(TSize)...) +function representation_size(M::PowerManifold) + return (representation_size(M.manifold)..., get_parameter(M.size)...) end @doc raw""" @@ -318,18 +272,24 @@ end function Base.show( io::IO, - M::PowerManifold{𝔽,TM,TSize,ArrayPowerRepresentation}, -) where {𝔽,TM,TSize} - return print(io, "PowerManifold($(M.manifold), $(join(TSize.parameters, ", ")))") + M::PowerManifold{𝔽,TM,TypeParameter{TSize},ArrayPowerRepresentation}, +) where {𝔽,TM<:AbstractManifold{𝔽},TSize} + return print( + io, + "PowerManifold($(M.manifold), $(join(TSize.parameters, ", ")), parameter=:type)", + ) +end +function Base.show( + io::IO, + M::PowerManifold{𝔽,TM,<:Tuple,ArrayPowerRepresentation}, +) where {𝔽,TM<:AbstractManifold{𝔽}} + size = get_parameter(M.size) + return print(io, "PowerManifold($(M.manifold), $(join(size, ", ")))") end -Distributions.support(tvd::PowerFVectorDistribution) = FVectorSupport(tvd.type, tvd.point) +Distributions.support(tvd::PowerFVectorDistribution) = FVectorSupport(tvd.type) Distributions.support(d::PowerPointDistribution) = MPointSupport(d.manifold) -function vector_bundle_transport(fiber::VectorSpaceType, M::PowerManifold) - return ParallelTransport() -end - @doc raw""" volume_density(M::PowerManifold, p, X) @@ -347,29 +307,6 @@ function volume_density(M::PowerManifold, p, X) return density end -@doc raw""" - Y = Weingarten(M::AbstractPowerManifold, p, X, V) - Weingarten!(M::AbstractPowerManifold, Y, p, X, V) - -Since the metric decouples, also the computation of the Weingarten map -``\mathcal W_p`` can be computed elementwise on the single elements of the [`PowerManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds/#sec-power-manifold) `M`. -""" -Weingarten(::AbstractPowerManifold, p, X, V) - -function Weingarten!(M::AbstractPowerManifold, Y, p, X, V) - rep_size = representation_size(M.manifold) - for i in get_iterator(M) - Weingarten!( - M.manifold, - _write(M, rep_size, Y, i), - _read(M, rep_size, p, i), - _read(M, rep_size, X, i), - _read(M, rep_size, V, i), - ) - end - return Y -end - @inline function _write( ::PowerManifoldMultidimensional, rep_size::Tuple, diff --git a/src/manifolds/ProbabilitySimplex.jl b/src/manifolds/ProbabilitySimplex.jl index 83fea8ec58..8a00af96c2 100644 --- a/src/manifolds/ProbabilitySimplex.jl +++ b/src/manifolds/ProbabilitySimplex.jl @@ -1,5 +1,5 @@ @doc raw""" - ProbabilitySimplex{n,boundary} <: AbstractDecoratorManifold{𝔽} + ProbabilitySimplex{T,boundary} <: AbstractDecoratorManifold{𝔽} The (relative interior of) the probability simplex is the set ````math @@ -34,9 +34,11 @@ This implementation follows the notation in [AastroemPetraSchmitzerSchnoerr:2017 ProbabilitySimplex(n::Int; boundary::Symbol=:open) """ -struct ProbabilitySimplex{n,boundary} <: AbstractDecoratorManifold{ℝ} end +struct ProbabilitySimplex{T,boundary} <: AbstractDecoratorManifold{ℝ} + size::T +end -function ProbabilitySimplex(n::Int; boundary::Symbol=:open) +function ProbabilitySimplex(n::Int; boundary::Symbol=:open, parameter::Symbol=:type) if boundary !== :open && boundary !== :closed throw( ArgumentError( @@ -44,7 +46,8 @@ function ProbabilitySimplex(n::Int; boundary::Symbol=:open) ), ) end - return ProbabilitySimplex{n,boundary}() + size = wrap_type_parameter(parameter, (n,)) + return ProbabilitySimplex{typeof(size),boundary}(size) end """ @@ -96,7 +99,7 @@ Check whether `p` is a valid point on the [`ProbabilitySimplex`](@ref) `M`, i.e. the embedding with positive entries that sum to one The tolerance for the last test can be set using the `kwargs...`. """ -function check_point(M::ProbabilitySimplex{n,boundary}, p; kwargs...) where {n,boundary} +function check_point(M::ProbabilitySimplex{<:Any,boundary}, p; kwargs...) where {boundary} if boundary === :closed && minimum(p) < 0 return DomainError( minimum(p), @@ -180,15 +183,10 @@ function exp!(::ProbabilitySimplex, q, p, X) return q end -function get_coordinates_orthonormal!( - M::ProbabilitySimplex{N}, - Xc, - p, - X, - R::RealNumbers, -) where {N} +function get_coordinates_orthonormal!(M::ProbabilitySimplex, Xc, p, X, R::RealNumbers) + n = get_parameter(M.size)[1] get_coordinates_orthonormal!( - Sphere(N), + Sphere(n), Xc, simplex_to_amplitude(M, p), simplex_to_amplitude_diff(M, p, X), @@ -197,28 +195,29 @@ function get_coordinates_orthonormal!( return Xc end -get_embedding(M::ProbabilitySimplex) = Euclidean(representation_size(M)...; field=ℝ) +function get_embedding(::ProbabilitySimplex{TypeParameter{Tuple{n}}}) where {n} + return Euclidean(n + 1) +end +function get_embedding(M::ProbabilitySimplex{Tuple{Int}}) + n = get_parameter(M.size)[1] + return Euclidean(n + 1; parameter=:field) +end -function get_vector_orthonormal!( - M::ProbabilitySimplex{N}, - Y, - p, - Xc, - R::RealNumbers, -) where {N} +function get_vector_orthonormal!(M::ProbabilitySimplex, Y, p, Xc, R::RealNumbers) + n = get_parameter(M.size)[1] ps = simplex_to_amplitude(M, p) - X = get_vector_orthonormal(Sphere(N), ps, Xc, R) + X = get_vector_orthonormal(Sphere(n), ps, Xc, R) return amplitude_to_simplex_diff!(M, Y, ps, X) end @doc raw""" - injectivity_radius(M, p) + injectivity_radius(M::ProbabilitySimplex, p) Compute the injectivity radius on the [`ProbabilitySimplex`](@ref) `M` at the point `p`, i.e. the distanceradius to a point near/on the boundary, that could be reached by following the geodesic. """ -function injectivity_radius(::ProbabilitySimplex{n}, p) where {n} +function injectivity_radius(::ProbabilitySimplex, p) i = argmin(p) s = sum(p) - p[i] return 2 * acos(sqrt(s)) @@ -240,7 +239,7 @@ g_p(X,Y) = \sum_{i=1}^{n+1}\frac{X_iY_i}{p_i} When `M` includes boundary, we can just skip coordinates where ``p_i`` is equal to 0, see Proposition 2.1 in [AyJostLeSchwachhoefer:2017](@cite). """ -function inner(::ProbabilitySimplex{n,boundary}, p, X, Y) where {n,boundary} +function inner(::ProbabilitySimplex{<:Any,boundary}, p, X, Y) where {boundary} d = zero(Base.promote_eltype(p, X, Y)) if boundary === :closed @inbounds for i in eachindex(p, X, Y) @@ -267,7 +266,7 @@ where $\mathbb{1}^{m,n}$ is the size `(m,n)` matrix containing ones, and $\log$ """ inverse_retract(::ProbabilitySimplex, ::Any, ::Any, ::SoftmaxInverseRetraction) -function inverse_retract_softmax!(::ProbabilitySimplex{n}, X, p, q) where {n} +function inverse_retract_softmax!(::ProbabilitySimplex, X, p, q) X .= log.(q) .- log.(p) meanlogdiff = mean(X) X .-= meanlogdiff @@ -306,23 +305,26 @@ function log!(::ProbabilitySimplex, X, p, q) end @doc raw""" - manifold_dimension(M::ProbabilitySimplex{n}) + manifold_dimension(M::ProbabilitySimplex) Returns the manifold dimension of the probability simplex in $ℝ^{n+1}$, i.e. ````math \dim_{Δ^n} = n. ```` """ -manifold_dimension(::ProbabilitySimplex{n}) where {n} = n +manifold_dimension(M::ProbabilitySimplex) = get_parameter(M.size)[1] @doc raw""" - manifold_volume(::ProbabilitySimplex{n}) where {n} + manifold_volume(::ProbabilitySimplex) Return the volume of the [`ProbabilitySimplex`](@ref), i.e. volume of the `n`-dimensional [`Sphere`](@ref) divided by ``2^{n+1}``, corresponding to the volume of its positive orthant. """ -manifold_volume(::ProbabilitySimplex{n}) where {n} = manifold_volume(Sphere(n)) / 2^(n + 1) +function manifold_volume(M::ProbabilitySimplex) + n = get_parameter(M.size)[1] + return manifold_volume(Sphere(n)) / 2^(n + 1) +end @doc raw""" mean( @@ -340,10 +342,11 @@ mean(::ProbabilitySimplex, ::Any...) default_estimation_method(::ProbabilitySimplex, ::typeof(mean)) = GeodesicInterpolation() -function parallel_transport_to!(M::ProbabilitySimplex{N}, Y, p, X, q) where {N} +function parallel_transport_to!(M::ProbabilitySimplex, Y, p, X, q) + n = get_parameter(M.size)[1] q_s = simplex_to_amplitude(M, q) Ys = parallel_transport_to( - Sphere(N), + Sphere(n), simplex_to_amplitude(M, p), simplex_to_amplitude_diff(M, p, X), q_s, @@ -430,12 +433,15 @@ function project!(::ProbabilitySimplex, q, p) end @doc raw""" - representation_size(::ProbabilitySimplex{n}) + representation_size(::ProbabilitySimplex) Return the representation size of points in the $n$-dimensional probability simplex, i.e. an array size of `(n+1,)`. """ -representation_size(::ProbabilitySimplex{n}) where {n} = (n + 1,) +function representation_size(M::ProbabilitySimplex) + n = get_parameter(M.size)[1] + return (n + 1,) +end @doc raw""" retract(M::ProbabilitySimplex, p, X, ::SoftmaxRetraction) @@ -461,8 +467,8 @@ function retract_softmax!(::ProbabilitySimplex, q, p, X, t::Number) end @doc raw""" - X = riemannian_gradient(M::ProbabilitySimplex{n}, p, Y) - riemannian_gradient!(M::ProbabilitySimplex{n}, X, p, Y) + X = riemannian_gradient(M::ProbabilitySimplex, p, Y) + riemannian_gradient!(M::ProbabilitySimplex, X, p, Y) Given a gradient ``Y = \operatorname{grad} \tilde f(p)`` in the embedding ``ℝ^{n+1}`` of the [`ProbabilitySimplex`](@ref) ``Δ^n``, this function computes the Riemannian gradient @@ -490,10 +496,11 @@ It is computed using isometry with positive orthant of a sphere. """ riemann_tensor(::ProbabilitySimplex, p, X, Y, Z) -function riemann_tensor!(M::ProbabilitySimplex{N}, Xresult, p, X, Y, Z) where {N} +function riemann_tensor!(M::ProbabilitySimplex, Xresult, p, X, Y, Z) + n = get_parameter(M.size)[1] pe = simplex_to_amplitude(M, p) Xrs = riemann_tensor( - Sphere(N), + Sphere(n), pe, simplex_to_amplitude_diff(M, p, X), simplex_to_amplitude_diff(M, p, Y), @@ -503,19 +510,27 @@ function riemann_tensor!(M::ProbabilitySimplex{N}, Xresult, p, X, Y, Z) where {N return Xresult end -function Base.show(io::IO, ::ProbabilitySimplex{n,boundary}) where {n,boundary} +function Base.show( + io::IO, + ::ProbabilitySimplex{TypeParameter{Tuple{n}},boundary}, +) where {n,boundary} return print(io, "ProbabilitySimplex($(n); boundary=:$boundary)") end +function Base.show(io::IO, M::ProbabilitySimplex{Tuple{Int},boundary}) where {boundary} + n = get_parameter(M.size)[1] + return print(io, "ProbabilitySimplex($(n); boundary=:$boundary, parameter=:field)") +end @doc raw""" - volume_density(M::ProbabilitySimplex{N}, p, X) where {N} + volume_density(M::ProbabilitySimplex, p, X) Compute the volume density at point `p` on [`ProbabilitySimplex`](@ref) `M` for tangent vector `X`. It is computed using isometry with positive orthant of a sphere. """ -function volume_density(M::ProbabilitySimplex{N}, p, X) where {N} +function volume_density(M::ProbabilitySimplex, p, X) + n = get_parameter(M.size)[1] pe = simplex_to_amplitude(M, p) - return volume_density(Sphere(N), pe, simplex_to_amplitude_diff(M, p, X)) + return volume_density(Sphere(n), pe, simplex_to_amplitude_diff(M, p, X)) end @doc raw""" @@ -545,7 +560,7 @@ function simplex_to_amplitude!(::ProbabilitySimplex, q, p) end @doc raw""" - amplitude_to_simplex(M::ProbabilitySimplex{N}, p) where {N} + amplitude_to_simplex(M::ProbabilitySimplex, p) Convert point (real) probability amplitude `p` on to a point on [`ProbabilitySimplex`](@ref Manifolds.ProbabilitySimplex). The formula reads ``(p_1^2, p_2^2, …, p_{N+1}^2)``. This is an isometry from the interior of diff --git a/src/manifolds/ProbabilitySimplexEuclideanMetric.jl b/src/manifolds/ProbabilitySimplexEuclideanMetric.jl index b44540a3a7..9ea96ab8e2 100644 --- a/src/manifolds/ProbabilitySimplexEuclideanMetric.jl +++ b/src/manifolds/ProbabilitySimplexEuclideanMetric.jl @@ -15,9 +15,8 @@ end Return the volume of the [`ProbabilitySimplex`](@ref) with the Euclidean metric. The formula reads ``\frac{\sqrt{n+1}}{n!}`` """ -function manifold_volume( - ::MetricManifold{ℝ,<:ProbabilitySimplex{n},<:EuclideanMetric}, -) where {n} +function manifold_volume(M::MetricManifold{ℝ,<:ProbabilitySimplex,<:EuclideanMetric}) + n = get_parameter(M.manifold.size)[1] return sqrt(n + 1) / factorial(n) end diff --git a/src/manifolds/ProductManifold.jl b/src/manifolds/ProductManifold.jl index 76b6e90c45..c906158e83 100644 --- a/src/manifolds/ProductManifold.jl +++ b/src/manifolds/ProductManifold.jl @@ -1,67 +1,14 @@ -@doc raw""" - ProductManifold{𝔽,TM<:Tuple} <: AbstractManifold{𝔽} - -Product manifold $M_1 × M_2 × … × M_n$ with product geometry. - -# Constructor - - ProductManifold(M_1, M_2, ..., M_n) -generates the product manifold $M_1 × M_2 × … × M_n$. -Alternatively, the same manifold can be contructed using the `×` operator: -`M_1 × M_2 × M_3`. -""" -struct ProductManifold{𝔽,TM<:Tuple} <: AbstractDecoratorManifold{𝔽} - manifolds::TM -end - -function ProductManifold(manifolds::AbstractManifold...) - 𝔽 = ManifoldsBase._unify_number_systems((number_system.(manifolds))...) - return ProductManifold{𝔽,typeof(manifolds)}(manifolds) +function active_traits(f, ::ProductManifold, args...) + return merge_traits(IsDefaultMetric(ProductMetric())) end -""" - getindex(M::ProductManifold, i) - M[i] - -access the `i`th manifold component from the [`ProductManifold`](@ref) `M`. -""" -@inline Base.getindex(M::ProductManifold, i::Integer) = M.manifolds[i] - -ProductManifold() = throw(MethodError("No method matching ProductManifold().")) - -const PRODUCT_BASIS_LIST = [ - VeeOrthogonalBasis, - DefaultBasis, - DefaultBasis{<:Any,TangentSpaceType}, - DefaultOrthogonalBasis, - DefaultOrthogonalBasis{<:Any,TangentSpaceType}, - DefaultOrthonormalBasis, - DefaultOrthonormalBasis{<:Any,TangentSpaceType}, - ProjectedOrthonormalBasis{:gram_schmidt,ℝ}, - ProjectedOrthonormalBasis{:svd,ℝ}, -] - -""" - ProductBasisData - -A typed tuple to store tuples of data of stored/precomputed bases for a [`ProductManifold`](@ref). -""" -struct ProductBasisData{T<:Tuple} - parts::T +function allocate_coordinates(::ProductManifold, p, T, n::Int) + return allocate(submanifold_component(p, 1), T, n) end -const PRODUCT_BASIS_LIST_CACHED = [CachedBasis] - -""" - ProductMetric <: AbstractMetric - -A type to represent the product of metrics for a [`ProductManifold`](@ref). -""" -struct ProductMetric <: AbstractMetric end - """ - ProductFVectorDistribution([type::VectorBundleFibers], [x], distrs...) + ProductFVectorDistribution([type::VectorSpaceFiber], [x], distrs...) Generates a random vector at point `x` from vector space (a fiber of a tangent bundle) of type `type` using the product distribution of given distributions. @@ -69,1191 +16,138 @@ bundle) of type `type` using the product distribution of given distributions. Vector space type and `x` can be automatically inferred from distributions `distrs`. """ struct ProductFVectorDistribution{ - TSpace<:VectorBundleFibers{<:VectorSpaceType,<:ProductManifold}, + TSpace<:VectorSpaceFiber{<:Any,<:ProductManifold}, TD<:(NTuple{N,Distribution} where {N}), - TX, -} <: FVectorDistribution{TSpace,TX} +} <: FVectorDistribution{TSpace} type::TSpace - x::TX - distributions::TD -end - -""" - ProductPointDistribution(M::ProductManifold, distributions) - -Product distribution on manifold `M`, combined from `distributions`. -""" -struct ProductPointDistribution{ - TM<:ProductManifold, - TD<:(NTuple{N,Distribution} where {N}), -} <: MPointDistribution{TM} - manifold::TM - distributions::TD -end - -""" - ProductRetraction(retractions::AbstractRetractionMethod...) - -Product retraction of `retractions`. Works on [`ProductManifold`](@ref). -""" -struct ProductRetraction{TR<:Tuple} <: AbstractRetractionMethod - retractions::TR -end - -function ProductRetraction(retractions::AbstractRetractionMethod...) - return ProductRetraction{typeof(retractions)}(retractions) -end - -""" - InverseProductRetraction(retractions::AbstractInverseRetractionMethod...) - -Product inverse retraction of `inverse retractions`. Works on [`ProductManifold`](@ref). -""" -struct InverseProductRetraction{TR<:Tuple} <: AbstractInverseRetractionMethod - inverse_retractions::TR -end - -function InverseProductRetraction(inverse_retractions::AbstractInverseRetractionMethod...) - return InverseProductRetraction{typeof(inverse_retractions)}(inverse_retractions) -end - -@inline function allocate_result(M::ProductManifold, f) - return ArrayPartition(map(N -> allocate_result(N, f), M.manifolds)) -end - -function allocation_promotion_function(M::ProductManifold, f, args::Tuple) - apfs = map(MM -> allocation_promotion_function(MM, f, args), M.manifolds) - return reduce(combine_allocation_promotion_functions, apfs) -end - -""" - ProductVectorTransport(methods::AbstractVectorTransportMethod...) - -Product vector transport type of `methods`. Works on [`ProductManifold`](@ref). -""" -struct ProductVectorTransport{TR<:Tuple} <: AbstractVectorTransportMethod - methods::TR -end - -function ProductVectorTransport(methods::AbstractVectorTransportMethod...) - return ProductVectorTransport{typeof(methods)}(methods) -end - -function active_traits(f, ::ProductManifold, args...) - return merge_traits(IsDefaultMetric(ProductMetric())) -end - -function adjoint_Jacobi_field( - M::ProductManifold, - p::ArrayPartition, - q::ArrayPartition, - t, - X::ArrayPartition, - β::Tβ, -) where {Tβ} - return ArrayPartition( - map( - adjoint_Jacobi_field, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - ntuple(_ -> t, length(M.manifolds)), - submanifold_components(M, X), - ntuple(_ -> β, length(M.manifolds)), - )..., - ) -end -function adjoint_Jacobi_field!(M::ProductManifold, Y, p, q, t, X, β::Tβ) where {Tβ} - map( - adjoint_Jacobi_field!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, q), - ntuple(_ -> t, length(M.manifolds)), - submanifold_components(M, X), - ntuple(_ -> β, length(M.manifolds)), - ) - return Y -end - -function allocate_coordinates(M::AbstractManifold, p::ArrayPartition, T, n::Int) - return allocate_coordinates(M, p.x[1], T, n) -end - -""" - change_representer(M::ProductManifold, ::AbstractMetric, p, X) - -Since the metric on a product manifold decouples, the change of a representer can be done elementwise -""" -change_representer(::ProductManifold, ::AbstractMetric, ::Any, ::Any) - -function change_representer!(M::ProductManifold, Y, G::AbstractMetric, p, X) - map( - (m, y, P, x) -> change_representer!(m, y, G, P, x), - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - ) - return Y -end - -""" - change_metric(M::ProductManifold, ::AbstractMetric, p, X) - -Since the metric on a product manifold decouples, the change of metric can be done elementwise. -""" -change_metric(::ProductManifold, ::AbstractMetric, ::Any, ::Any) - -function change_metric!(M::ProductManifold, Y, G::AbstractMetric, p, X) - map( - (m, y, P, x) -> change_metric!(m, y, G, P, x), - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - ) - return Y -end - -""" - check_point(M::ProductManifold, p; kwargs...) - -Check whether `p` is a valid point on the [`ProductManifold`](@ref) `M`. -If `p` is not a point on `M` a [`CompositeManifoldError`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/functions.html#ManifoldsBase.CompositeManifoldError).consisting of all error messages of the -components, for which the tests fail is returned. - -The tolerance for the last test can be set using the `kwargs...`. -""" -function check_point(M::ProductManifold, p::Union{ProductRepr,ArrayPartition}; kwargs...) - ts = ziptuples(Tuple(1:length(M.manifolds)), M.manifolds, submanifold_components(M, p)) - e = [(t[1], check_point(t[2:end]...; kwargs...)) for t in ts] - errors = filter((x) -> !(x[2] === nothing), e) - cerr = [ComponentManifoldError(er...) for er in errors] - (length(errors) > 1) && return CompositeManifoldError(cerr) - (length(errors) == 1) && return cerr[1] - return nothing -end -function check_point(M::ProductManifold, p; kwargs...) - return DomainError( - typeof(p), - "The point $p is not a point on $M, since currently only ProductRepr and ArrayPartition are supported types for points on arbitrary product manifolds", - ) -end - -""" - check_size(M::ProductManifold, p; kwargs...) - -Check whether `p` is of valid size on the [`ProductManifold`](@ref) `M`. -If `p` has components of wrong size a [`CompositeManifoldError`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/functions.html#ManifoldsBase.CompositeManifoldError).consisting of all error messages of the -components, for which the tests fail is returned. - -The tolerance for the last test can be set using the `kwargs...`. -""" -function check_size(M::ProductManifold, p::Union{ProductRepr,ArrayPartition}) - ts = ziptuples(Tuple(1:length(M.manifolds)), M.manifolds, submanifold_components(M, p)) - e = [(t[1], check_size(t[2:end]...)) for t in ts] - errors = filter((x) -> !(x[2] === nothing), e) - cerr = [ComponentManifoldError(er...) for er in errors] - (length(errors) > 1) && return CompositeManifoldError(cerr) - (length(errors) == 1) && return cerr[1] - return nothing -end -function check_size(M::ProductManifold, p; kwargs...) - return DomainError( - typeof(p), - "The point $p is not a point on $M, since currently only ProductRepr and ArrayPartition are supported types for points on arbitrary product manifolds", - ) -end -function check_size( - M::ProductManifold, - p::Union{ProductRepr,ArrayPartition}, - X::Union{ProductRepr,ArrayPartition}, -) - ts = ziptuples( - Tuple(1:length(M.manifolds)), - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - ) - e = [(t[1], check_size(t[2:end]...)) for t in ts] - errors = filter(x -> !(x[2] === nothing), e) - cerr = [ComponentManifoldError(er...) for er in errors] - (length(errors) > 1) && return CompositeManifoldError(cerr) - (length(errors) == 1) && return cerr[1] - return nothing -end -function check_size(M::ProductManifold, p, X; kwargs...) - return DomainError( - typeof(X), - "The vector $X is not a tangent vector to any tangent space on $M, since currently only ProductRepr and ArrayPartition are supported types for tangent vectors on arbitrary product manifolds", - ) -end -""" - check_vector(M::ProductManifold, p, X; kwargs... ) - -Check whether `X` is a tangent vector to `p` on the [`ProductManifold`](@ref) -`M`, i.e. all projections to base manifolds must be respective tangent vectors. -If `X` is not a tangent vector to `p` on `M` a [`CompositeManifoldError`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/functions.html#ManifoldsBase.CompositeManifoldError).consisting -of all error messages of the components, for which the tests fail is returned. - -The tolerance for the last test can be set using the `kwargs...`. -""" -function check_vector( - M::ProductManifold, - p::Union{ProductRepr,ArrayPartition}, - X::Union{ProductRepr,ArrayPartition}; - kwargs..., -) - ts = ziptuples( - Tuple(1:length(M.manifolds)), - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - ) - e = [(t[1], check_vector(t[2:end]...; kwargs...)) for t in ts] - errors = filter(x -> !(x[2] === nothing), e) - cerr = [ComponentManifoldError(er...) for er in errors] - (length(errors) > 1) && return CompositeManifoldError(cerr) - (length(errors) == 1) && return cerr[1] - return nothing -end -function check_vector(M::ProductManifold, p, X; kwargs...) - return DomainError( - typeof(X), - "The vector $X is not a tangent vector to any tangent space on $M, since currently only ProductRepr and ArrayPartition are supported types for tangent vectors on arbitrary product manifolds", - ) -end - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function copyto!(M::ProductManifold, q::$TP, p::$TP) - map( - copyto!, - M.manifolds, - submanifold_components(q), - submanifold_components(p), - ) - return q - end - function copyto!(M::ProductManifold, Y::$TP, p::$TP, X::$TP) - map( - copyto!, - M.manifolds, - submanifold_components(Y), - submanifold_components(p), - submanifold_components(X), - ) - return Y - end - end, - ) -end - -@doc raw""" - cross(M, N) - cross(M1, M2, M3,...) - -Return the [`ProductManifold`](@ref) For two `AbstractManifold`s `M` and `N`, -where for the case that one of them is a [`ProductManifold`](@ref) itself, -the other is either prepended (if `N` is a product) or appenden (if `M`) is. -If both are product manifold, they are combined into one product manifold, -keeping the order. - -For the case that more than one is a product manifold of these is build with the -same approach as above -""" -cross(::AbstractManifold...) -LinearAlgebra.cross(M1::AbstractManifold, M2::AbstractManifold) = ProductManifold(M1, M2) -function LinearAlgebra.cross(M1::ProductManifold, M2::AbstractManifold) - return ProductManifold(M1.manifolds..., M2) -end -function LinearAlgebra.cross(M1::AbstractManifold, M2::ProductManifold) - return ProductManifold(M1, M2.manifolds...) -end -function LinearAlgebra.cross(M1::ProductManifold, M2::ProductManifold) - return ProductManifold(M1.manifolds..., M2.manifolds...) -end - -function default_retraction_method(M::ProductManifold) - return ProductRetraction(map(default_retraction_method, M.manifolds)...) -end -function default_retraction_method(M::ProductManifold, ::Type{T}) where {T<:ArrayPartition} - return ProductRetraction( - map(default_retraction_method, M.manifolds, T.parameters[2].parameters)..., - ) -end -function default_retraction_method(M::ProductManifold, ::Type{T}) where {T<:ProductRepr} - return ProductRetraction( - map(default_retraction_method, M.manifolds, T.parameters[1].parameters)..., - ) -end - -function default_inverse_retraction_method(M::ProductManifold) - return InverseProductRetraction(map(default_inverse_retraction_method, M.manifolds)...) -end -function default_inverse_retraction_method( - M::ProductManifold, - ::Type{T}, -) where {T<:ArrayPartition} - return InverseProductRetraction( - map(default_inverse_retraction_method, M.manifolds, T.parameters[2].parameters)..., - ) -end -function default_inverse_retraction_method( - M::ProductManifold, - ::Type{T}, -) where {T<:ProductRepr} - return InverseProductRetraction( - map(default_inverse_retraction_method, M.manifolds, T.parameters[1].parameters)..., - ) -end - -function default_vector_transport_method(M::ProductManifold) - return ProductVectorTransport(map(default_vector_transport_method, M.manifolds)...) -end -function default_vector_transport_method( - M::ProductManifold, - ::Type{T}, -) where {T<:ArrayPartition} - return ProductVectorTransport( - map(default_vector_transport_method, M.manifolds, T.parameters[2].parameters)..., - ) -end -function default_vector_transport_method( - M::ProductManifold, - ::Type{T}, -) where {T<:ProductRepr} - return ProductVectorTransport( - map(default_vector_transport_method, M.manifolds, T.parameters[1].parameters)..., - ) -end - -@doc raw""" - distance(M::ProductManifold, p, q) - -Compute the distance between two points `p` and `q` on the [`ProductManifold`](@ref) `M`, which is -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, - ), - ) -end - -@doc raw""" - exp(M::ProductManifold, p, X) - -compute the exponential map from `p` in the direction of `X` on the [`ProductManifold`](@ref) `M`, -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), - )..., - ) -end -function Base.exp(M::ProductManifold, p::ArrayPartition, X::ArrayPartition) - return ArrayPartition( - map( - exp, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )..., - ) -end -function Base.exp(M::ProductManifold, p::ProductRepr, X::ProductRepr, t::Number) - return ProductRepr( - map( - (N, pc, Xc) -> exp(N, pc, Xc, t), - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )..., - ) -end -function Base.exp(M::ProductManifold, p::ArrayPartition, X::ArrayPartition, t::Number) - return ArrayPartition( - map( - (N, pc, Xc) -> exp(N, pc, Xc, t), - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )..., - ) -end - -function exp!(M::ProductManifold, q, p, X) - map( - exp!, - M.manifolds, - submanifold_components(M, q), - submanifold_components(M, p), - submanifold_components(M, X), - ) - return q -end -function exp!(M::ProductManifold, q, p, X, t::Number) - map( - (N, qc, pc, Xc) -> exp!(N, qc, pc, Xc, t), - M.manifolds, - submanifold_components(M, q), - submanifold_components(M, p), - submanifold_components(M, X), - ) - return q -end - -@doc raw""" - flat(M::ProductManifold, p, X::FVector{TangentSpaceType}) - -use the musical isomorphism to transform the tangent vector `X` from the tangent space at -`p` on the [`ProductManifold`](@ref) `M` to a cotangent vector. -This can be done elementwise for every entry of `X` (with respect to the corresponding -entry in `p`) separately. -""" -flat(::ProductManifold, ::Any...) - -function get_basis(M::ProductManifold, p, B::AbstractBasis) - parts = map(t -> get_basis(t..., B), ziptuples(M.manifolds, submanifold_components(p))) - return CachedBasis(B, ProductBasisData(parts)) -end -function get_basis(M::ProductManifold, p, B::CachedBasis) - return invoke(get_basis, Tuple{AbstractManifold,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 - return get_basis(t[1], t[2], DiagonalizingOrthonormalBasis(t[3])) - end - return CachedBasis(B, ProductBasisData(vs)) -end - -""" - get_component(M::ProductManifold, p, i) - -Get the `i`th component of a point `p` on a [`ProductManifold`](@ref) `M`. -""" -function get_component(M::ProductManifold, p, i) - return submanifold_component(M, p, i) -end - -function get_coordinates(M::ProductManifold, p, X, B::AbstractBasis) - reps = map( - t -> get_coordinates(t..., B), - ziptuples(M.manifolds, submanifold_components(M, p), submanifold_components(M, X)), - ) - return vcat(reps...) -end -function get_coordinates( - M::ProductManifold, - p, - X, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:ProductBasisData}, -) where {𝔽} - reps = map( - get_coordinates, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - B.data.parts, - ) - return vcat(reps...) -end - -function get_coordinates!(M::ProductManifold, Xⁱ, p, X, B::AbstractBasis) - dim = manifold_dimension(M) - @assert length(Xⁱ) == dim - i = one(dim) - ts = ziptuples(M.manifolds, submanifold_components(M, p), submanifold_components(M, X)) - for t in ts - SM = first(t) - dim = manifold_dimension(SM) - tXⁱ = @inbounds view(Xⁱ, i:(i + dim - 1)) - get_coordinates!(SM, tXⁱ, Base.tail(t)..., B) - i += dim - end - return Xⁱ -end -function get_coordinates!( - M::ProductManifold, - Xⁱ, - p, - X, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:ProductBasisData}, -) where {𝔽} - dim = manifold_dimension(M) - @assert length(Xⁱ) == dim - i = one(dim) - ts = ziptuples( - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - B.data.parts, - ) - for t in ts - SM = first(t) - dim = manifold_dimension(SM) - tXⁱ = @inbounds view(Xⁱ, i:(i + dim - 1)) - get_coordinates!(SM, tXⁱ, Base.tail(t)...) - i += dim - end - return Xⁱ -end - -function _get_dim_ranges(dims::NTuple{N,Any}) where {N} - dims_acc = accumulate(+, vcat(1, SVector(dims))) - return ntuple(i -> (dims_acc[i]:(dims_acc[i] + dims[i] - 1)), Val(N)) -end - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function get_vector( - M::ProductManifold, - p::$TP, - Xⁱ, - B::AbstractBasis{𝔽,TangentSpaceType}, - ) where {𝔽} - dims = map(manifold_dimension, M.manifolds) - @assert length(Xⁱ) == sum(dims) - dim_ranges = _get_dim_ranges(dims) - tXⁱ = map(dr -> (@inbounds view(Xⁱ, dr)), dim_ranges) - ts = ziptuples(M.manifolds, submanifold_components(M, p), tXⁱ) - return $TP(map((@inline t -> get_vector(t..., B)), ts)) - end - function get_vector( - M::ProductManifold, - p::$TP, - Xⁱ, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:ProductBasisData}, - ) where {𝔽} - dims = map(manifold_dimension, M.manifolds) - @assert length(Xⁱ) == sum(dims) - dim_ranges = _get_dim_ranges(dims) - tXⁱ = map(dr -> (@inbounds view(Xⁱ, dr)), dim_ranges) - ts = - ziptuples(M.manifolds, submanifold_components(M, p), tXⁱ, B.data.parts) - return $TP(map((@inline t -> get_vector(t...)), ts)) - end - end, - ) -end - -function get_vector!(M::ProductManifold, X, p, Xⁱ, B::AbstractBasis) - dims = map(manifold_dimension, M.manifolds) - @assert length(Xⁱ) == sum(dims) - dim_ranges = _get_dim_ranges(dims) - tXⁱ = map(dr -> (@inbounds view(Xⁱ, dr)), dim_ranges) - ts = ziptuples( - M.manifolds, - submanifold_components(M, X), - submanifold_components(M, p), - tXⁱ, - ) - map(ts) do t - return get_vector!(t..., B) - end - return X -end -function get_vector!( - M::ProductManifold, - X, - p, - Xⁱ, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:ProductBasisData}, -) where {𝔽} - dims = map(manifold_dimension, M.manifolds) - @assert length(Xⁱ) == sum(dims) - dim_ranges = _get_dim_ranges(dims) - tXⁱ = map(dr -> (@inbounds view(Xⁱ, dr)), dim_ranges) - ts = ziptuples( - M.manifolds, - submanifold_components(M, X), - submanifold_components(M, p), - tXⁱ, - B.data.parts, - ) - map(ts) do t - return get_vector!(t...) - end - return X -end - -function get_vectors( - M::ProductManifold, - p::ProductRepr, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:ProductBasisData}, -) where {𝔽} - N = number_of_components(M) - xparts = submanifold_components(p) - BVs = map(t -> get_vectors(t...), ziptuples(M.manifolds, xparts, B.data.parts)) - zero_tvs = map(t -> zero_vector(t...), ziptuples(M.manifolds, xparts)) - vs = typeof(ProductRepr(zero_tvs...))[] - for i in 1:N, k in 1:length(BVs[i]) - push!(vs, ProductRepr(zero_tvs[1:(i - 1)]..., BVs[i][k], zero_tvs[(i + 1):end]...)) - end - return vs -end -function get_vectors( - M::ProductManifold, - p::ArrayPartition, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:ProductBasisData}, -) where {𝔽} - N = number_of_components(M) - xparts = submanifold_components(p) - BVs = map(t -> get_vectors(t...), ziptuples(M.manifolds, xparts, B.data.parts)) - zero_tvs = map(t -> zero_vector(t...), ziptuples(M.manifolds, xparts)) - vs = typeof(ArrayPartition(zero_tvs...))[] - for i in 1:N, k in 1:length(BVs[i]) - push!( - vs, - ArrayPartition(zero_tvs[1:(i - 1)]..., BVs[i][k], zero_tvs[(i + 1):end]...), - ) - end - return vs -end - -""" - getindex(p, M::ProductManifold, i::Union{Integer,Colon,AbstractVector}) - p[M::ProductManifold, i] - -Access the element(s) at index `i` of a point `p` on a [`ProductManifold`](@ref) `M` by -linear indexing. -See also [Array Indexing](https://docs.julialang.org/en/v1/manual/arrays/#man-array-indexing-1) in Julia. -""" -Base.@propagate_inbounds function Base.getindex( - p::ProductRepr, - M::ProductManifold, - i::Union{Integer,Colon,AbstractVector,Val}, -) - return get_component(M, p, i) -end -Base.@propagate_inbounds function Base.getindex( - p::ArrayPartition, - M::ProductManifold, - i::Union{Integer,Colon,AbstractVector,Val}, -) - return get_component(M, p, i) -end - -@doc raw""" - injectivity_radius(M::ProductManifold) - injectivity_radius(M::ProductManifold, x) - -Compute the injectivity radius on the [`ProductManifold`](@ref), which is the -minimum of the factor manifolds. -""" -injectivity_radius(::ProductManifold, ::Any...) -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), - )..., - ) -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, - )..., - ) -end -injectivity_radius(M::ProductManifold) = min(map(injectivity_radius, M.manifolds)...) -function injectivity_radius(M::ProductManifold, m::AbstractRetractionMethod) - return min(map(manif -> injectivity_radius(manif, m), M.manifolds)...) -end -function injectivity_radius(M::ProductManifold, m::ProductRetraction) - return min(map((lM, lm) -> injectivity_radius(lM, lm), M.manifolds, m.retractions)...) -end - -@doc raw""" - inner(M::ProductManifold, p, X, Y) - -compute the inner product of two tangent vectors `X`, `Y` from the tangent space -at `p` on the [`ProductManifold`](@ref) `M`, which is just the sum of the -internal manifolds that build `M`. -""" -function inner(M::ProductManifold, p, X, Y) - subproducts = map( - inner, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, Y), - ) - return sum(subproducts) -end - -@doc raw""" - inverse_retract(M::ProductManifold, p, q, m::InverseProductRetraction) - -Compute the inverse retraction from `p` with respect to `q` on the [`ProductManifold`](@ref) -`M` using an [`InverseProductRetraction`](@ref), which by default encapsulates a inverse -retraction for each manifold of the product. Then this method is performed elementwise, -so the encapsulated inverse retraction methods have to be available per factor. -""" -inverse_retract(::ProductManifold, ::Any, ::Any, ::Any, ::InverseProductRetraction) - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function inverse_retract( - M::ProductManifold, - p::$TP, - q::$TP, - method::InverseProductRetraction, - ) - return $TP( - map( - inverse_retract, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - method.inverse_retractions, - ), - ) - end - end, - ) -end - -function inverse_retract!(M::ProductManifold, Y, p, q, method::InverseProductRetraction) - map( - inverse_retract!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, q), - method.inverse_retractions, - ) - return Y -end - -function _isapprox(M::ProductManifold, p, q; kwargs...) - return all( - t -> isapprox(t...; kwargs...), - ziptuples(M.manifolds, submanifold_components(M, p), submanifold_components(M, q)), - ) -end -function _isapprox(M::ProductManifold, p, X, Y; kwargs...) - return all( - t -> isapprox(t...; kwargs...), - ziptuples( - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, Y), - ), - ) -end - -""" - is_flat(::ProductManifold) - -Return true if and only if all component manifolds of [`ProductManifold`](@ref) `M` are flat. -""" -function is_flat(M::ProductManifold) - return all(is_flat, M.manifolds) -end - -@doc raw""" - log(M::ProductManifold, p, q) - -Compute the logarithmic map from `p` to `q` on the [`ProductManifold`](@ref) `M`, -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), - )..., - ) -end -function Base.log(M::ProductManifold, p::ArrayPartition, q::ArrayPartition) - return ArrayPartition( - map( - log, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - )..., - ) -end - -function jacobi_field( - M::ProductManifold, - p::ArrayPartition, - q::ArrayPartition, - t, - X::ArrayPartition, - β::Tβ, -) where {Tβ} - return ArrayPartition( - map( - jacobi_field, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, q), - ntuple(_ -> t, length(M.manifolds)), - submanifold_components(M, X), - ntuple(_ -> β, length(M.manifolds)), - )..., - ) -end -function jacobi_field!(M::ProductManifold, Y, p, q, t, X, β::Tβ) where {Tβ} - map( - jacobi_field!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, q), - ntuple(_ -> t, length(M.manifolds)), - submanifold_components(M, X), - ntuple(_ -> β, length(M.manifolds)), - ) - return Y -end - -function log!(M::ProductManifold, X, p, q) - map( - log!, - M.manifolds, - submanifold_components(M, X), - submanifold_components(M, p), - submanifold_components(M, q), - ) - return X -end - -@doc raw""" - manifold_dimension(M::ProductManifold) - -Return the manifold dimension of the [`ProductManifold`](@ref), which is the sum of the -manifold dimensions the product is made of. -""" -manifold_dimension(M::ProductManifold) = mapreduce(manifold_dimension, +, M.manifolds) - -""" - manifold_dimension(M::ProductManifold) - -Return the volume of [`ProductManifold`](@ref) `M`, i.e. product of volumes of the -manifolds `M` is constructed from. -""" -manifold_volume(M::ProductManifold) = mapreduce(manifold_volume, *, M.manifolds) - -function mid_point!(M::ProductManifold, q, p1, p2) - map( - mid_point!, - M.manifolds, - submanifold_components(M, q), - submanifold_components(M, p1), - submanifold_components(M, p2), - ) - return q -end - -@doc raw""" - norm(M::ProductManifold, p, X) - -Compute the norm of `X` from the tangent space of `p` on the [`ProductManifold`](@ref), -i.e. from the element wise norms the 2-norm is computed. -""" -function LinearAlgebra.norm(M::ProductManifold, p, X) - norms_squared = ( - map( - norm, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - ) .^ 2 - ) - return sqrt(sum(norms_squared)) -end - -""" - number_of_components(M::ProductManifold{<:NTuple{N,Any}}) where {N} - -Calculate the number of manifolds multiplied in the given [`ProductManifold`](@ref) `M`. -""" -number_of_components(::ProductManifold{𝔽,<:NTuple{N,Any}}) where {𝔽,N} = N - -function ProductFVectorDistribution( - type::VectorBundleFibers{<:VectorSpaceType,<:ProductManifold}, - p::Union{AbstractArray,AbstractManifoldPoint,ProductRepr}, - distributions::FVectorDistribution..., -) - return ProductFVectorDistribution{typeof(type),typeof(distributions),typeof(p)}( - type, - p, - distributions, - ) -end -function ProductFVectorDistribution( - type::VectorBundleFibers{<:VectorSpaceType,<:ProductManifold}, - distributions::FVectorDistribution..., -) - p = ProductRepr(map(d -> support(d).point, distributions)) - return ProductFVectorDistribution(type, p, distributions...) -end -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", - ) - end - # Probably worth considering sum spaces in the future? - x = ProductRepr(map(d -> support(d).point, distributions)...) - return ProductFVectorDistribution(VectorBundleFibers(fiber, M), x, distributions...) -end - -function ProductPointDistribution(M::ProductManifold, distributions::MPointDistribution...) - return ProductPointDistribution{typeof(M),typeof(distributions)}(M, distributions) -end -function ProductPointDistribution(distributions::MPointDistribution...) - M = ProductManifold(map(d -> support(d).manifold, distributions)...) - return ProductPointDistribution(M, distributions...) -end - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function parallel_transport_direction( - M::ProductManifold, - p::$TP, - X::$TP, - d::$TP, - ) - return $TP( - map( - parallel_transport_direction, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, d), - ), - ) - end - end, - ) -end -function parallel_transport_direction!(M::ProductManifold, Y, p, X, d) - map( - parallel_transport_direction!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, d), - ) - return Y + distributions::TD +end + +""" + ProductPointDistribution(M::ProductManifold, distributions) + +Product distribution on manifold `M`, combined from `distributions`. +""" +struct ProductPointDistribution{ + TM<:ProductManifold, + TD<:(NTuple{N,Distribution} where {N}), +} <: MPointDistribution{TM} + manifold::TM + distributions::TD end -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function parallel_transport_to(M::ProductManifold, p::$TP, X::$TP, q::$TP) - return $TP( - map( - parallel_transport_to, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, q), - ), - ) - end - end, +function adjoint_Jacobi_field( + M::ProductManifold, + p::ArrayPartition, + q::ArrayPartition, + t, + X::ArrayPartition, + β::Tβ, +) where {Tβ} + return ArrayPartition( + map( + adjoint_Jacobi_field, + M.manifolds, + submanifold_components(M, p), + submanifold_components(M, q), + ntuple(_ -> t, length(M.manifolds)), + submanifold_components(M, X), + ntuple(_ -> β, length(M.manifolds)), + )..., ) end -function parallel_transport_to!(M::ProductManifold, Y, p, X, q) +function adjoint_Jacobi_field!(M::ProductManifold, Y, p, q, t, X, β::Tβ) where {Tβ} map( - parallel_transport_to!, + adjoint_Jacobi_field!, M.manifolds, submanifold_components(M, Y), submanifold_components(M, p), - submanifold_components(M, X), submanifold_components(M, q), + ntuple(_ -> t, length(M.manifolds)), + submanifold_components(M, X), + ntuple(_ -> β, length(M.manifolds)), ) return Y end -function project(M::ProductManifold, p::ProductRepr) - return ProductRepr(map(project, M.manifolds, submanifold_components(M, p))...) -end -function project(M::ProductManifold, p::ArrayPartition) - return ArrayPartition(map(project, M.manifolds, submanifold_components(M, p))...) -end +@doc raw""" + flat(M::ProductManifold, p, X::FVector{TangentSpaceType}) -function project!(M::ProductManifold, q, p) - map(project!, M.manifolds, submanifold_components(M, q), submanifold_components(M, p)) - return q -end +use the musical isomorphism to transform the tangent vector `X` from the tangent space at +`p` on the [`ProductManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.ProductManifold) +`M` to a cotangent vector. This can be done elementwise for every entry of `X` (with respect +to the corresponding entry in `p`) separately. +""" +flat(::ProductManifold, ::Any...) -function project(M::ProductManifold, p::ProductRepr, X::ProductRepr) - return ProductRepr( - map( - project, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - )..., - ) -end -function project(M::ProductManifold, p::ArrayPartition, X::ArrayPartition) +function jacobi_field( + M::ProductManifold, + p::ArrayPartition, + q::ArrayPartition, + t, + X::ArrayPartition, + β::Tβ, +) where {Tβ} return ArrayPartition( map( - project, + jacobi_field, M.manifolds, submanifold_components(M, p), + submanifold_components(M, q), + ntuple(_ -> t, length(M.manifolds)), submanifold_components(M, X), + ntuple(_ -> β, length(M.manifolds)), )..., ) end - -function project!(M::ProductManifold, Y, p, X) +function jacobi_field!(M::ProductManifold, Y, p, q, t, X, β::Tβ) where {Tβ} map( - project!, + jacobi_field!, M.manifolds, submanifold_components(M, Y), submanifold_components(M, p), + submanifold_components(M, q), + ntuple(_ -> t, length(M.manifolds)), submanifold_components(M, X), + ntuple(_ -> β, length(M.manifolds)), ) return Y end -function Random.rand(rng::AbstractRNG, d::ProductPointDistribution) - return ProductRepr(map(d -> rand(rng, d), d.distributions)...) -end -function Random.rand(rng::AbstractRNG, d::ProductFVectorDistribution) - return ProductRepr(map(d -> rand(rng, d), d.distributions)...) -end - -@doc raw""" - rand(M::ProductManifold; parts_kwargs = map(_ -> (;), M.manifolds)) +""" + manifold_volume(M::ProductManifold) -Return a random point on [`ProductManifold`](@ref) `M`. `parts_kwargs` is -a tuple of keyword arguments for `rand` on each manifold in `M.manifolds`. +Return the volume of [`ProductManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.ProductManifold) +`M`, i.e. product of volumes of the manifolds `M` is constructed from. """ -function Random.rand( - M::ProductManifold; - vector_at=nothing, - parts_kwargs=map(_ -> (;), M.manifolds), -) - if vector_at === nothing - return ProductRepr( - map((N, kwargs) -> rand(N; kwargs...), M.manifolds, parts_kwargs)..., - ) - elseif isa(vector_at, ProductRepr) - return ProductRepr( - map( - (N, p, kwargs) -> rand(N; vector_at=p, kwargs...), - M.manifolds, - submanifold_components(M, vector_at), - parts_kwargs, - )..., - ) - else - return ArrayPartition( - map( - (N, p, kwargs) -> rand(N; vector_at=p, kwargs...), - M.manifolds, - submanifold_components(M, vector_at), - parts_kwargs, - )..., +manifold_volume(M::ProductManifold) = mapreduce(manifold_volume, *, M.manifolds) + +function ProductFVectorDistribution(distributions::FVectorDistribution...) + M = ProductManifold(map(d -> support(d).space.manifold, distributions)...) + fiber_type = support(distributions[1]).space.fiber_type + if !all(d -> support(d).space.fiber_type == fiber_type, distributions) + 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? + p = ArrayPartition(map(d -> support(d).space.point, distributions)...) + return ProductFVectorDistribution(Fiber(M, p, fiber_type), distributions) end -function Random.rand( - rng::AbstractRNG, - M::ProductManifold; - vector_at=nothing, - parts_kwargs=map(_ -> (;), M.manifolds), -) - if vector_at === nothing - return ProductRepr( - map((N, kwargs) -> rand(rng, N; kwargs...), M.manifolds, parts_kwargs)..., - ) - elseif isa(vector_at, ProductRepr) - return ProductRepr( - map( - (N, p, kwargs) -> rand(rng, N; vector_at=p, kwargs...), - M.manifolds, - submanifold_components(M, vector_at), - parts_kwargs, - )..., - ) - else - return ArrayPartition( - map( - (N, p, kwargs) -> rand(rng, N; vector_at=p, kwargs...), - M.manifolds, - submanifold_components(M, vector_at), - parts_kwargs, - )..., - ) - end + +function ProductPointDistribution(M::ProductManifold, distributions::MPointDistribution...) + return ProductPointDistribution{typeof(M),typeof(distributions)}(M, distributions) +end +function ProductPointDistribution(distributions::MPointDistribution...) + M = ProductManifold(map(d -> support(d).manifold, distributions)...) + return ProductPointDistribution(M, distributions...) end -function Random.rand!( - rng::AbstractRNG, - M::ProductManifold, - pX; - vector_at=nothing, - parts_kwargs=map(_ -> (;), M.manifolds), -) - if vector_at === nothing - map( - (N, q, kwargs) -> rand!(rng, N, q; kwargs...), - M.manifolds, - submanifold_components(M, pX), - parts_kwargs, - ) - else - map( - (N, X, p, kwargs) -> rand!(rng, N, X; vector_at=p, kwargs...), - M.manifolds, - submanifold_components(M, pX), - submanifold_components(M, vector_at), - parts_kwargs, - ) - end - return pX +function Random.rand(rng::AbstractRNG, d::ProductPointDistribution) + return ArrayPartition(map(d -> rand(rng, d), d.distributions)...) +end +function Random.rand(rng::AbstractRNG, d::ProductFVectorDistribution) + return ArrayPartition(map(d -> rand(rng, d), d.distributions)...) end function Distributions._rand!( @@ -1263,9 +157,13 @@ function Distributions._rand!( ) return copyto!(x, rand(rng, d)) end -function Distributions._rand!(rng::AbstractRNG, d::ProductPointDistribution, p::ProductRepr) +function Distributions._rand!( + rng::AbstractRNG, + d::ProductPointDistribution, + p::ArrayPartition, +) map( - t -> Distributions._rand!(rng, t[1], t[2]), + (t1, t2) -> Distributions._rand!(rng, t1, t2), d.distributions, submanifold_components(d.manifold, p), ) @@ -1281,7 +179,7 @@ end function Distributions._rand!( rng::AbstractRNG, d::ProductFVectorDistribution, - X::ProductRepr, + X::ArrayPartition, ) map( t -> Distributions._rand!(rng, t[1], t[2]), @@ -1291,56 +189,6 @@ function Distributions._rand!( return X end -@doc raw""" - retract(M::ProductManifold, p, X, m::ProductRetraction) - -Compute the retraction from `p` with tangent vector `X` on the [`ProductManifold`](@ref) `M` -using an [`ProductRetraction`](@ref), which by default encapsulates retractions of the -base manifolds. Then this method is performed elementwise, so the encapsulated retractions -method has to be one that is available on the manifolds. -""" -retract(::ProductManifold, ::Any...) - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function _retract( - M::ProductManifold, - p::$TP, - X::$TP, - t::Number, - method::ProductRetraction, - ) - return $TP( - map( - (N, pc, Xc, rm) -> retract(N, pc, Xc, t, rm), - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - method.retractions, - ), - ) - end - end, - ) -end - -function _retract!(M::ProductManifold, q, p, X, t::Number, method::ProductRetraction) - map( - (N, qc, pc, Xc, rm) -> retract!(N, qc, pc, Xc, t, rm), - M.manifolds, - submanifold_components(M, q), - submanifold_components(M, p), - submanifold_components(M, X), - method.retractions, - ) - return q -end - -function representation_size(M::ProductManifold) - return (mapreduce(m -> prod(representation_size(m)), +, M.manifolds),) -end - @doc raw""" Y = riemannian_Hessian(M::ProductManifold, p, G, H, X) riemannian_Hessian!(M::ProductManifold, Y, p, G, H, X) @@ -1366,176 +214,19 @@ function riemannian_Hessian!(M::ProductManifold, Y, p, G, H, X) return Y end -@doc raw""" - riemann_tensor(M::ProductManifold, p, X, Y, Z) - -Compute the Riemann tensor at point from `p` with tangent vectors `X`, `Y` and `Z` on -the [`ProductManifold`](@ref) `M`. -""" -riemann_tensor(M::ProductManifold, p, X, Y, X) - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function riemann_tensor(M::ProductManifold, p::$TP, X::$TP, Y::$TP, Z::$TP) - return $TP( - map( - riemann_tensor, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, Y), - submanifold_components(M, Z), - ), - ) - end - end, - ) -end - -function riemann_tensor!(M::ProductManifold, Xresult, p, X, Y, Z) - map( - riemann_tensor!, - M.manifolds, - submanifold_components(M, Xresult), - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, Y), - submanifold_components(M, Z), - ) - return Xresult -end - -""" - set_component!(M::ProductManifold, q, p, i) - -Set the `i`th component of a point `q` on a [`ProductManifold`](@ref) `M` to `p`, where `p` is a point on the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) this factor of the product manifold consists of. -""" -function set_component!(M::ProductManifold, q, p, i) - return copyto!(submanifold_component(M, q, i), p) -end - -""" - setindex!(q, p, M::ProductManifold, i::Union{Integer,Colon,AbstractVector}) - q[M::ProductManifold,i...] = p - -set the element `[i...]` of a point `q` on a [`ProductManifold`](@ref) by linear indexing to `q`. -See also [Array Indexing](https://docs.julialang.org/en/v1/manual/arrays/#man-array-indexing-1) in Julia. -""" -Base.@propagate_inbounds function Base.setindex!( - q::Union{ProductRepr,ArrayPartition}, - p, - M::ProductManifold, - i::Union{Integer,Colon,AbstractVector,Val}, -) - return set_component!(M, q, p, i) -end - @doc raw""" sharp(M::ProductManifold, p, ξ::FVector{CotangentSpaceType}) Use the musical isomorphism to transform the cotangent vector `ξ` from the tangent space at -`p` on the [`ProductManifold`](@ref) `M` to a tangent vector. -This can be done elementwise for every entry of `ξ` (and `p`) separately +`p` on the [`ProductManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.ProductManifold) +`M` to a tangent vector. This can be done elementwise for every entry of `ξ` (and `p`) +separately """ sharp(::ProductManifold, ::Any...) -function _show_submanifold(io::IO, M::AbstractManifold; pre="") - sx = sprint(show, "text/plain", M, context=io, sizehint=0) - if occursin('\n', sx) - sx = sprint(show, M, context=io, sizehint=0) - end - sx = replace(sx, '\n' => "\n$(pre)") - print(io, pre, sx) - return nothing -end - -function _show_submanifold_range(io::IO, Ms, range; pre="") - for i in range - M = Ms[i] - print(io, '\n') - _show_submanifold(io, M; pre=pre) - end - return nothing -end - -function _show_product_manifold_no_header(io::IO, M) - n = length(M.manifolds) - sz = displaysize(io) - screen_height, screen_width = sz[1] - 4, sz[2] - half_height = div(screen_height, 2) - inds = 1:n - pre = " " - if n > screen_height - inds = [1:half_height; (n - div(screen_height - 1, 2) + 1):n] - end - if n ≤ screen_height - _show_submanifold_range(io, M.manifolds, 1:n; pre=pre) - else - _show_submanifold_range(io, M.manifolds, 1:half_height; pre=pre) - print(io, "\n$(pre)⋮") - _show_submanifold_range( - io, - M.manifolds, - (n - div(screen_height - 1, 2) + 1):n; - pre=pre, - ) - end - return nothing -end - -function Base.show(io::IO, ::MIME"text/plain", M::ProductManifold) - n = length(M.manifolds) - print(io, "ProductManifold with $(n) submanifold$(n == 1 ? "" : "s"):") - return _show_product_manifold_no_header(io, M) -end - -function Base.show(io::IO, M::ProductManifold) - return print(io, "ProductManifold(", join(M.manifolds, ", "), ")") -end - -function Base.show( - io::IO, - mime::MIME"text/plain", - B::CachedBasis{𝔽,T,D}, -) where {𝔽,T<:AbstractBasis{𝔽},D<:ProductBasisData} - println(io, "$(T) for a product manifold") - for (i, cb) in enumerate(B.data.parts) - println(io, "Basis for component $i:") - show(io, mime, cb) - println(io) - end - return nothing -end - -""" - submanifold(M::ProductManifold, i::Integer) - -Extract the `i`th factor of the product manifold `M`. -""" -submanifold(M::ProductManifold, i::Integer) = M.manifolds[i] - -""" - submanifold(M::ProductManifold, i::Val) - submanifold(M::ProductManifold, i::AbstractVector) - -Extract the factor of the product manifold `M` indicated by indices in `i`. -For example, for `i` equal to `Val((1, 3))` the product manifold constructed -from the first and the third factor is returned. - -The version with `AbstractVector` is not type-stable, for better preformance use `Val`. -""" -function submanifold(M::ProductManifold, i::Val) - return ProductManifold(select_from_tuple(M.manifolds, i)...) -end -submanifold(M::ProductManifold, i::AbstractVector) = submanifold(M, Val(tuple(i...))) - Distributions.support(d::ProductPointDistribution) = MPointSupport(d.manifold) function Distributions.support(tvd::ProductFVectorDistribution) - return FVectorSupport( - tvd.type, - ProductRepr(map(d -> support(d).point, tvd.distributions)...), - ) + return FVectorSupport(tvd.type) end function uniform_distribution(M::ProductManifold) @@ -1548,135 +239,11 @@ function uniform_distribution(M::ProductManifold, p) ) end -function vector_bundle_transport(::VectorSpaceType, M::ProductManifold) - return ProductVectorTransport(map(_ -> ParallelTransport(), M.manifolds)) -end - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function vector_transport_direction( - M::ProductManifold, - p::$TP, - X::$TP, - d::$TP, - m::ProductVectorTransport, - ) - return $TP( - map( - vector_transport_direction, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, d), - m.methods, - ), - ) - end - end, - ) -end -function vector_transport_direction!( - M::ProductManifold, - Y, - p, - X, - d, - m::ProductVectorTransport, -) - map( - vector_transport_direction!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, d), - m.methods, - ) - return Y -end - -@doc raw""" - vector_transport_to(M::ProductManifold, p, X, q, m::ProductVectorTransport) - -Compute the vector transport the tangent vector `X`at `p` to `q` on the -[`ProductManifold`](@ref) `M` using an [`ProductVectorTransport`](@ref) `m`. -This method is performed elementwise, i.e. the method `m` has to be implemented on the -base manifold. -""" -vector_transport_to(::ProductManifold, ::Any, ::Any, ::Any, ::ProductVectorTransport) - -for TP in [ProductRepr, ArrayPartition] - eval( - quote - function vector_transport_to( - M::ProductManifold, - p::$TP, - X::$TP, - q::$TP, - m::ProductVectorTransport, - ) - return $TP( - map( - vector_transport_to, - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, q), - m.methods, - ), - ) - end - function vector_transport_to( - M::ProductManifold, - p::$TP, - X::$TP, - q::$TP, - m::ParallelTransport, - ) - return $TP( - map( - (iM, ip, iX, id) -> vector_transport_to(iM, ip, iX, id, m), - M.manifolds, - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, q), - ), - ) - end - end, - ) -end - -function vector_transport_to!(M::ProductManifold, Y, p, X, q, m::ProductVectorTransport) - map( - vector_transport_to!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, q), - m.methods, - ) - return Y -end -function vector_transport_to!(M::ProductManifold, Y, p, X, q, m::ParallelTransport) - map( - (iM, iY, ip, iX, id) -> vector_transport_to!(iM, iY, ip, iX, id, m), - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, q), - ), - return Y -end - @doc raw""" volume_density(M::ProductManifold, p, X) -Return volume density on the [`ProductManifold`](@ref) `M`, i.e. product of constituent -volume densities. +Return volume density on the [`ProductManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/metamanifolds/#ManifoldsBase.ProductManifold) +`M`, i.e. product of constituent volume densities. """ function volume_density(M::ProductManifold, p, X) dens = map( @@ -1687,34 +254,3 @@ function volume_density(M::ProductManifold, p, X) ) return prod(dens) end - -@doc raw""" - Y = Weingarten(M::ProductManifold, p, X, V) - Weingarten!(M::ProductManifold, Y, p, X, V) - -Since the metric decouples, also the computation of the Weingarten map -``\mathcal W_p`` can be computed elementwise on the single elements of the [`ProductManifold`](@ref) `M`. -""" -Weingarten(::ProductManifold, p, X, V) - -function Weingarten!(M::ProductManifold, Y, p, X, V) - map( - Weingarten!, - M.manifolds, - submanifold_components(M, Y), - submanifold_components(M, p), - submanifold_components(M, X), - submanifold_components(M, V), - ) - return Y -end - -function zero_vector!(M::ProductManifold, X, p) - map( - zero_vector!, - M.manifolds, - submanifold_components(M, X), - submanifold_components(M, p), - ) - return X -end diff --git a/src/manifolds/ProjectiveSpace.jl b/src/manifolds/ProjectiveSpace.jl index 7ff9180fb3..392215431b 100644 --- a/src/manifolds/ProjectiveSpace.jl +++ b/src/manifolds/ProjectiveSpace.jl @@ -37,8 +37,13 @@ Generate the projective space $𝔽ℙ^{n} ⊂ 𝔽^{n+1}$, defaulting to the re $ℝℙ^n$, where `field` can also be used to generate the complex- and right-quaternionic projective spaces. """ -struct ProjectiveSpace{N,𝔽} <: AbstractProjectiveSpace{𝔽} end -ProjectiveSpace(n::Int, field::AbstractNumbers=ℝ) = ProjectiveSpace{n,field}() +struct ProjectiveSpace{T,𝔽} <: AbstractProjectiveSpace{𝔽} + size::T +end +function ProjectiveSpace(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return ProjectiveSpace{typeof(size),field}(size) +end function active_traits(f, ::AbstractProjectiveSpace, args...) return merge_traits(IsIsometricEmbeddedManifold()) @@ -80,9 +85,16 @@ Generate the projective space $𝔽ℙ^{n_1, n_2, …, n_i}$, defaulting to the space, where `field` can also be used to generate the complex- and right-quaternionic projective spaces. """ -struct ArrayProjectiveSpace{N,𝔽} <: AbstractProjectiveSpace{𝔽} where {N<:Tuple} end -function ArrayProjectiveSpace(n::Vararg{Int,I}; field::AbstractNumbers=ℝ) where {I} - return ArrayProjectiveSpace{Tuple{n...},field}() +struct ArrayProjectiveSpace{T,𝔽} <: AbstractProjectiveSpace{𝔽} + size::T +end +function ArrayProjectiveSpace( + n::Vararg{Int,I}; + field::AbstractNumbers=ℝ, + parameter::Symbol=:type, +) where {I} + size = wrap_type_parameter(parameter, n) + return ArrayProjectiveSpace{typeof(size),field}(size) end function allocation_promotion_function(::AbstractProjectiveSpace{ℂ}, f, args::Tuple) @@ -128,6 +140,9 @@ end function decorated_manifold(M::AbstractProjectiveSpace{𝔽}) where {𝔽} return Euclidean(representation_size(M)...; field=𝔽) end +function decorated_manifold(M::ProjectiveSpace{<:Tuple,𝔽}) where {𝔽} + return Euclidean(representation_size(M)...; field=𝔽, parameter=:field) +end get_embedding(M::AbstractProjectiveSpace) = decorated_manifold(M) @@ -164,8 +179,9 @@ function exp!(M::AbstractProjectiveSpace, q, p, X) return q end -function get_basis(::ProjectiveSpace{n,ℝ}, p, B::DiagonalizingOrthonormalBasis{ℝ}) where {n} - return get_basis(Sphere{n,ℝ}(), p, B) +function get_basis(M::ProjectiveSpace{<:Any,ℝ}, p, B::DiagonalizingOrthonormalBasis{ℝ}) + n = get_parameter(M.size)[1] + return get_basis(Sphere(n), p, B) end @doc raw""" @@ -425,8 +441,13 @@ project!(::AbstractProjectiveSpace, Y, p, X) = (Y .= X .- p .* dot(p, X)) Return the size points on the [`AbstractProjectiveSpace`](@ref) `M` are represented as, i.e., the representation size of the embedding. """ -@generated representation_size(::ArrayProjectiveSpace{N}) where {N} = size_to_tuple(N) -@generated representation_size(::ProjectiveSpace{N}) where {N} = (N + 1,) +function representation_size(M::ArrayProjectiveSpace) + return get_parameter(M.size) +end +function representation_size(M::ProjectiveSpace) + n = get_parameter(M.size)[1] + return (n + 1,) +end @doc raw""" retract(M::AbstractProjectiveSpace, p, X, method::ProjectionRetraction) @@ -464,21 +485,29 @@ function retract_qr!(M::AbstractProjectiveSpace, q, p, X, t::Number) return project!(M, q, q) end -function Base.show(io::IO, ::ProjectiveSpace{n,𝔽}) where {n,𝔽} +function Base.show(io::IO, ::ProjectiveSpace{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} return print(io, "ProjectiveSpace($(n), $(𝔽))") end -function Base.show(io::IO, ::ArrayProjectiveSpace{N,𝔽}) where {N,𝔽} - return print(io, "ArrayProjectiveSpace($(join(N.parameters, ", ")); field = $(𝔽))") +function Base.show(io::IO, M::ProjectiveSpace{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return print(io, "ProjectiveSpace($(n), $(𝔽); parameter=:field)") +end +function Base.show(io::IO, ::ArrayProjectiveSpace{TypeParameter{tn},𝔽}) where {tn<:Tuple,𝔽} + return print(io, "ArrayProjectiveSpace($(join(tn.parameters, ", ")); field=$(𝔽))") +end +function Base.show(io::IO, M::ArrayProjectiveSpace{<:Tuple,𝔽}) where {𝔽} + n = M.size + return print(io, "ArrayProjectiveSpace($(join(n, ", ")); field=$(𝔽), parameter=:field)") end """ - uniform_distribution(M::ProjectiveSpace{n,ℝ}, p) where {n} + uniform_distribution(M::ProjectiveSpace{<:Any,ℝ}, p) Uniform distribution on given [`ProjectiveSpace`](@ref) `M`. Generated points will be of similar type as `p`. """ -function uniform_distribution(M::ProjectiveSpace{n,ℝ}, p) where {n} - d = Distributions.MvNormal(zero(p), 1.0) +function uniform_distribution(M::ProjectiveSpace{<:Any,ℝ}, p) + d = Distributions.MvNormal(zero(p), 1.0 * I) return ProjectedPointDistribution(M, d, project!, p) end diff --git a/src/manifolds/Rotations.jl b/src/manifolds/Rotations.jl index 956394822b..89f87f326b 100644 --- a/src/manifolds/Rotations.jl +++ b/src/manifolds/Rotations.jl @@ -1,18 +1,21 @@ @doc raw""" - Rotations{N} <: AbstractManifold{ℝ} + Rotations{T} <: AbstractManifold{ℝ} -The manifold of rotation matrices of sice ``n × n``, i.e. +The manifold of rotation matrices of size ``n × n``, i.e. real-valued orthogonal matrices with determinant ``+1``. # Constructor - Rotations(n) + Rotations(n::Int; parameter::Symbol=:type) Generate the manifold of ``n × n`` rotation matrices. """ -const Rotations{n} = GeneralUnitaryMatrices{n,ℝ,DeterminantOneMatrices} +const Rotations{T} = GeneralUnitaryMatrices{T,ℝ,DeterminantOneMatrices} -Rotations(n::Int) = Rotations{n}() +function Rotations(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return Rotations{typeof(size)}(size) +end """ NormalRotationDistribution(M::Rotations, d::Distribution, x::TResult) @@ -40,7 +43,7 @@ end @doc raw""" angles_4d_skew_sym_matrix(A) -The Lie algebra of [`Rotations(4)`](@ref) in $ℝ^{4 × 4}$, $𝔰𝔬(4)$, consists of $4 × 4$ +The Lie algebra of [`Rotations(4)`](@ref) in ``ℝ^{4 × 4}``, ``𝔰𝔬(4)``, consists of ``4 × 4`` skew-symmetric matrices. The unique imaginary components of their eigenvalues are the angles of the two plane rotations. This function computes these more efficiently than `eigvals`. @@ -121,11 +124,8 @@ function _ev_zero(tridiagonal_elements, unitary, evec, evals, fill_at; i) return (values=evals, vectors=evec) end -function get_basis_diagonalizing( - M::Rotations{N}, - p, - B::DiagonalizingOrthonormalBasis{ℝ}, -) where {N} +function get_basis_diagonalizing(M::Rotations, p, B::DiagonalizingOrthonormalBasis{ℝ}) + n = get_parameter(M.size)[1] decomp = schur(B.frame_direction) decomp = ordschur(decomp, map(v -> norm(v) > eps(eltype(p)), decomp.values)) @@ -135,7 +135,7 @@ function get_basis_diagonalizing( evals = Vector{eltype(B.frame_direction)}(undef, manifold_dimension(M)) i = 1 fill_at = Ref(1) - while i <= N + while i <= n if trian_elem[i] == 0 evs = _ev_zero(trian_elem, unitary, evec, evals, fill_at; i=i) i += 1 @@ -158,15 +158,18 @@ end injectivity_radius(M::Rotations, ::PolarRetraction) Return the radius of injectivity for the [`PolarRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.PolarRetraction) on the -[`Rotations`](@ref) `M` which is $\frac{π}{\sqrt{2}}$. +[`Rotations`](@ref) `M` which is ``\frac{π}{\sqrt{2}}``. """ injectivity_radius(::Rotations, ::PolarRetraction) -_injectivity_radius(::Rotations, ::PolarRetraction) = π / sqrt(2.0) +function _injectivity_radius(M::Rotations, ::PolarRetraction) + n = get_parameter(M.size)[1] + return n == 1 ? 0.0 : π / sqrt(2.0) +end @doc raw""" inverse_retract(M, p, q, ::PolarInverseRetraction) -Compute a vector from the tangent space $T_p\mathrm{SO}(n)$ +Compute a vector from the tangent space ``T_p\mathrm{SO}(n)`` of the point `p` on the [`Rotations`](@ref) manifold `M` with which the point `q` can be reached by the [`PolarRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.PolarRetraction) from the point `p` after time 1. @@ -177,22 +180,23 @@ The formula reads = -\frac{1}{2}(p^{\mathrm{T}}qs - (p^{\mathrm{T}}qs)^{\mathrm{T}}) ```` -where $s$ is the solution to the Sylvester equation +where ``s`` is the solution to the Sylvester equation -$p^{\mathrm{T}}qs + s(p^{\mathrm{T}}q)^{\mathrm{T}} + 2I_n = 0.$ +``p^{\mathrm{T}}qs + s(p^{\mathrm{T}}q)^{\mathrm{T}} + 2I_n = 0.`` """ inverse_retract(::Rotations, ::Any, ::Any, ::PolarInverseRetraction) @doc raw""" inverse_retract(M::Rotations, p, q, ::QRInverseRetraction) -Compute a vector from the tangent space $T_p\mathrm{SO}(n)$ of the point `p` on the +Compute a vector from the tangent space ``T_p\mathrm{SO}(n)`` of the point `p` on the [`Rotations`](@ref) manifold `M` with which the point `q` can be reached by the [`QRRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions.html#ManifoldsBase.QRRetraction) from the point `q` after time 1. """ inverse_retract(::Rotations, ::Any, ::Any, ::QRInverseRetraction) -function inverse_retract_polar!(M::Rotations{n}, X, p, q) where {n} +function inverse_retract_polar!(M::Rotations, X, p, q) + n = get_parameter(M.size)[1] A = transpose(p) * q Amat = A isa StaticMatrix ? A : convert(Matrix, A) H = copyto!(allocate(Amat), -2I) @@ -208,7 +212,8 @@ function inverse_retract_polar!(M::Rotations{n}, X, p, q) where {n} end return project!(SkewSymmetricMatrices(n), X, p, X) end -function inverse_retract_qr!(::Rotations{n}, X, p, q) where {n} +function inverse_retract_qr!(M::Rotations, X, p, q) + n = get_parameter(M.size)[1] A = transpose(p) * q R = zero(X) for i in 1:n @@ -225,26 +230,36 @@ end normal_rotation_distribution(M::Rotations, p, σ::Real) Return a random point on the manifold [`Rotations`](@ref) `M` -by generating a (Gaussian) random orthogonal matrix with determinant $+1$. Let +by generating a (Gaussian) random orthogonal matrix with determinant ``+1``. Let -$QR = A$ +```math +QR = A +``` -be the QR decomposition of a random matrix $A$, then the formula reads +be the QR decomposition of a random matrix ``A``, then the formula reads -$p = QD$ +```math +p = QD +``` -where $D$ is a diagonal matrix with the signs of the diagonal entries of $R$, +where ``D`` is a diagonal matrix with the signs of the diagonal entries of ``R``, i.e. -$D_{ij}=\begin{cases} \operatorname{sgn}(R_{ij}) & \text{if} \; i=j \\ 0 & \, \text{otherwise} \end{cases}.$ +```math +D_{ij}=\begin{cases} + \operatorname{sgn}(R_{ij}) & \text{if} \; i=j \\ + 0 & \, \text{otherwise} +\end{cases}. +``` It can happen that the matrix gets -1 as a determinant. In this case, the first and second columns are swapped. The argument `p` is used to determine the type of returned points. """ -function normal_rotation_distribution(M::Rotations{N}, p, σ::Real) where {N} - d = Distributions.MvNormal(zeros(N * N), σ) +function normal_rotation_distribution(M::Rotations, p, σ::Real) + n = get_parameter(M.size)[1] + d = Distributions.MvNormal(zeros(n * n), σ * I) return NormalRotationDistribution(M, d, p) end @@ -253,7 +268,7 @@ end Project `p` to the nearest point on manifold `M`. -Given the singular value decomposition $p = U Σ V^\mathrm{T}$, with the +Given the singular value decomposition ``p = U Σ V^\mathrm{T}``, with the singular values sorted in descending order, the projection is ````math @@ -261,19 +276,20 @@ singular values sorted in descending order, the projection is U\operatorname{diag}\left[1,1,…,\det(U V^\mathrm{T})\right] V^\mathrm{T} ```` -The diagonal matrix ensures that the determinant of the result is $+1$. +The diagonal matrix ensures that the determinant of the result is ``+1``. If `p` is expected to be almost special orthogonal, then you may avoid this check with `check_det = false`. """ project(::Rotations, ::Any) -function project!(::Rotations{N}, q, p; check_det=true) where {N} +function project!(M::Rotations, q, p; check_det::Bool=true) + n = get_parameter(M.size)[1] F = svd(p) mul!(q, F.U, F.Vt) if check_det && det(q) < 0 d = similar(F.S) - @inbounds fill!(view(d, 1:(N - 1)), 1) - @inbounds d[N] = -1 + @inbounds fill!(view(d, 1:(n - 1)), 1) + @inbounds d[n] = -1 copyto!(q, F.U * Diagonal(d) * F.Vt) end return q @@ -281,12 +297,13 @@ end function Random.rand( rng::AbstractRNG, - d::NormalRotationDistribution{TResult,Rotations{N}}, -) where {TResult,N} - return if N == 1 + d::NormalRotationDistribution{TResult,<:Rotations}, +) where {TResult} + n = get_parameter(d.manifold.size)[1] + return if n == 1 convert(TResult, ones(1, 1)) else - A = reshape(rand(rng, d.distr), (N, N)) + A = reshape(rand(rng, d.distr), (n, n)) convert(TResult, _fix_random_rotation(A)) end end @@ -320,9 +337,9 @@ end function Distributions._rand!( rng::AbstractRNG, - d::NormalRotationDistribution{TResult,Rotations{N}}, + d::NormalRotationDistribution, x::AbstractArray{<:Real}, -) where {TResult,N} +) return copyto!(x, rand(rng, d)) end @@ -356,7 +373,7 @@ function parallel_transport_direction!(M::Rotations, Y, p, X, d) q = exp(M, p, d) return copyto!(Y, transpose(q) * p * expdhalf * X * expdhalf) end -function parallel_transport_direction!(::Rotations{2}, Y, p, X, d) +function parallel_transport_direction!(::Rotations{TypeParameter{Tuple{2}}}, Y, p, X, d) return copyto!(Y, X) end function parallel_transport_direction(M::Rotations, p, X, d) @@ -364,14 +381,14 @@ function parallel_transport_direction(M::Rotations, p, X, d) q = exp(M, p, d) return transpose(q) * p * expdhalf * X * expdhalf end -parallel_transport_direction(::Rotations{2}, p, X, d) = X +parallel_transport_direction(::Rotations{TypeParameter{Tuple{2}}}, p, X, d) = X function parallel_transport_to!(M::Rotations, Y, p, X, q) d = log(M, p, q) expdhalf = exp(d / 2) return copyto!(Y, transpose(q) * p * expdhalf * X * expdhalf) end -function parallel_transport_to!(::Rotations{2}, Y, p, X, q) +function parallel_transport_to!(::Rotations{TypeParameter{Tuple{2}}}, Y, p, X, q) return copyto!(Y, X) end function parallel_transport_to(M::Rotations, p, X, q) @@ -379,11 +396,15 @@ function parallel_transport_to(M::Rotations, p, X, q) expdhalf = exp(d / 2) return transpose(q) * p * expdhalf * X * expdhalf end -parallel_transport_to(::Rotations{2}, p, X, q) = X +parallel_transport_to(::Rotations{TypeParameter{Tuple{2}}}, p, X, q) = X -function Base.show(io::IO, ::Rotations{n}) where {n} +function Base.show(io::IO, ::Rotations{TypeParameter{Tuple{n}}}) where {n} return print(io, "Rotations($(n))") end +function Base.show(io::IO, M::Rotations{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "Rotations($n; parameter=:field)") +end @doc raw""" riemannian_Hessian(M::Rotations, p, G, H, X) @@ -396,7 +417,8 @@ and that means the inverse has to be appliead to the (Euclidean) Hessian to map it into the Lie algebra. """ riemannian_Hessian(M::Rotations, p, G, H, X) -function riemannian_Hessian!(::Rotations{N}, Y, p, G, H, X) where {N} +function riemannian_Hessian!(M::Rotations, Y, p, G, H, X) + N = get_parameter(M.size)[1] symmetrize!(Y, G' * p) project!(SkewSymmetricMatrices(N), Y, p' * H - X * Y) return Y diff --git a/src/manifolds/SPDFixedDeterminant.jl b/src/manifolds/SPDFixedDeterminant.jl index 07b65b461c..5efae753c0 100644 --- a/src/manifolds/SPDFixedDeterminant.jl +++ b/src/manifolds/SPDFixedDeterminant.jl @@ -1,5 +1,5 @@ @doc raw""" - SPDFixedDeterminant{N,D} <: AbstractDecoratorManifold{ℝ} + SPDFixedDeterminant{T,D} <: AbstractDecoratorManifold{ℝ} The manifold of symmetric positive definite matrices of fixed determinant ``d > 0``, i.e. @@ -29,18 +29,23 @@ Additionally we store the tangent vectors as `X=p^{-1}Z`, i.e. symmetric matrice # Constructor - SPDFixedDeterminant(n::Int, d::Real=1.0) + SPDFixedDeterminant(n::Int, d::Real=1.0; parameter::Symbol=:type) -generates the manifold $\mathcal P_d(n) \subset \mathcal P(n)$ of determinant ``d``, +Generate the manifold $\mathcal P_d(n) \subset \mathcal P(n)$ of determinant ``d``, which defaults to 1. + +`parameter`: whether a type parameter should be used to store `n`. By default size +is stored in type. Value can either be `:field` or `:type`. """ -struct SPDFixedDeterminant{N,TD<:Real} <: AbstractDecoratorManifold{ℝ} +struct SPDFixedDeterminant{T,TD<:Real} <: AbstractDecoratorManifold{ℝ} + size::T d::TD end -function SPDFixedDeterminant(n::Int, d::F=1.0) where {F<:Real} +function SPDFixedDeterminant(n::Int, d::F=1.0; parameter::Symbol=:type) where {F<:Real} @assert d > 0 "The determinant has to be positive but was provided as $d." - return SPDFixedDeterminant{n,F}(d) + size = wrap_type_parameter(parameter, (n,)) + return SPDFixedDeterminant{typeof(size),F}(size, d) end function active_traits(f, ::SPDFixedDeterminant, args...) @@ -48,7 +53,7 @@ function active_traits(f, ::SPDFixedDeterminant, args...) end @doc raw""" - check_point(M::SPDFixedDeterminant{n}, p; kwargs...) + check_point(M::SPDFixedDeterminant, p; kwargs...) Check whether `p` is a valid manifold point on the [`SPDFixedDeterminant`](@ref)`(n,d)` `M`, i.e. whether `p` is a [`SymmetricPositiveDefinite`](@ref) matrix of size `(n, n)` @@ -57,7 +62,7 @@ with determinant ``\det(p) = ```M.d`. The tolerance for the determinant of `p` can be set using `kwargs...`. """ -function check_point(M::SPDFixedDeterminant{n}, p; kwargs...) where {n} +function check_point(M::SPDFixedDeterminant, p; kwargs...) if det(p) ≉ M.d return DomainError( det(p), @@ -92,9 +97,13 @@ embed(M::SPDFixedDeterminant, p, X) = copy(M, X) embed!(M::SPDFixedDeterminant, q, p) = copyto!(M, q, p) embed!(M::SPDFixedDeterminant, Y, p, X) = copyto!(M, Y, p, X) -function get_embedding(::SPDFixedDeterminant{n}) where {n} +function get_embedding(::SPDFixedDeterminant{TypeParameter{Tuple{n}}}) where {n} return SymmetricPositiveDefinite(n) end +function get_embedding(M::SPDFixedDeterminant{Tuple{Int}}) + n = get_parameter(M.size)[1] + return SymmetricPositiveDefinite(n; parameter=:field) +end @doc raw""" manifold_dimension(M::SPDFixedDeterminant) @@ -112,8 +121,8 @@ function manifold_dimension(M::SPDFixedDeterminant) end @doc raw""" - q = project(M::SPDFixedDeterminant{n}, p) - project!(M::SPDFixedDeterminant{n}, q, p) + q = project(M::SPDFixedDeterminant, p) + project!(M::SPDFixedDeterminant, q, p) Project the symmetric positive definite (s.p.d.) matrix `p` from the embedding onto the (sub-)manifold of s.p.d. matrices of determinant `M.d` (in place of `q`). @@ -126,14 +135,15 @@ q = \Bigl(\frac{d}{\det(p)}\Bigr)^{\frac{1}{n}}p """ project(M::SPDFixedDeterminant, p) -function project!(M::SPDFixedDeterminant{n}, q, p) where {n} +function project!(M::SPDFixedDeterminant, q, p) + n = get_parameter(M.size)[1] q .= (M.d / det(p))^(1 / n) .* p return end @doc raw""" - Y = project(M::SPDFixedDeterminant{n}, p, X) - project!(M::SPDFixedDeterminant{n}, Y, p, X) + Y = project(M::SPDFixedDeterminant, p, X) + project!(M::SPDFixedDeterminant, Y, p, X) Project the symmetric matrix `X` onto the tangent space at `p` of the (sub-)manifold of s.p.d. matrices of determinant `M.d` (in place of `Y`), @@ -148,6 +158,10 @@ function project!(M::SPDFixedDeterminant, Y, p, X) return Y end -function Base.show(io::IO, M::SPDFixedDeterminant{n}) where {n} +function Base.show(io::IO, M::SPDFixedDeterminant{TypeParameter{Tuple{n}}}) where {n} return print(io, "SPDFixedDeterminant($n, $(M.d))") end +function Base.show(io::IO, M::SPDFixedDeterminant{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "SPDFixedDeterminant($n, $(M.d); parameter=:field)") +end diff --git a/src/manifolds/SkewHermitian.jl b/src/manifolds/SkewHermitian.jl index 234e3f295d..0427cf8c8a 100644 --- a/src/manifolds/SkewHermitian.jl +++ b/src/manifolds/SkewHermitian.jl @@ -1,5 +1,5 @@ @doc raw""" - SkewHermitianMatrices{n,𝔽} <: AbstractDecoratorManifold{𝔽} + SkewHermitianMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} The [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) $ \operatorname{SkewHerm}(n)$ consisting of the real- or complex-valued skew-hermitian matrices of size ``n × n``, i.e. the set @@ -14,22 +14,25 @@ Though it is slightly redundant, usually the matrices are stored as ``n × n`` a Note that in this representation, the real-valued part of the diagonal must be zero, which is also reflected in the -[`manifold_dimension`](@ref manifold_dimension(::SkewHermitianMatrices{N,𝔽}) where {N,𝔽}). +[`manifold_dimension`](@ref manifold_dimension(::SkewHermitianMatrices)). # Constructor - SkewHermitianMatrices(n::Int, field::AbstractNumbers=ℝ) + SkewHermitianMatrices(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) Generate the manifold of ``n × n`` skew-hermitian matrices. """ -struct SkewHermitianMatrices{n,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct SkewHermitianMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -function SkewHermitianMatrices(n::Int, field::AbstractNumbers=ℝ) - return SkewHermitianMatrices{n,field}() +function SkewHermitianMatrices(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return SkewHermitianMatrices{typeof(size),field}(size) end @doc raw""" - SkewSymmetricMatrices{n} + SkewSymmetricMatrices{T} Generate the manifold of ``n × n`` real skew-symmetric matrices. This is equivalent to [`SkewHermitianMatrices(n, ℝ)`](@ref). @@ -38,10 +41,11 @@ This is equivalent to [`SkewHermitianMatrices(n, ℝ)`](@ref). SkewSymmetricMatrices(n::Int) """ -const SkewSymmetricMatrices{n} = SkewHermitianMatrices{n,ℝ} +const SkewSymmetricMatrices{T} = SkewHermitianMatrices{T,ℝ} -SkewSymmetricMatrices(n::Int) = SkewSymmetricMatrices{n}() -@deprecate SkewSymmetricMatrices(n::Int, 𝔽) SkewHermitianMatrices(n, 𝔽) +function SkewSymmetricMatrices(n::Int; parameter::Symbol=:type) + return SkewHermitianMatrices(n; parameter=parameter) +end function active_traits(f, ::SkewHermitianMatrices, args...) return merge_traits(IsEmbeddedSubmanifold()) @@ -56,7 +60,7 @@ function allocation_promotion_function( end @doc raw""" - check_point(M::SkewHermitianMatrices{n,𝔽}, p; kwargs...) + check_point(M::SkewHermitianMatrices, p; kwargs...) Check whether `p` is a valid manifold point on the [`SkewHermitianMatrices`](@ref) `M`, i.e. whether `p` is a skew-hermitian matrix of size `(n,n)` with values from the corresponding @@ -64,7 +68,7 @@ whether `p` is a skew-hermitian matrix of size `(n,n)` with values from the corr The tolerance for the skew-symmetry of `p` can be set using `kwargs...`. """ -function check_point(M::SkewHermitianMatrices{n,𝔽}, p; kwargs...) where {n,𝔽} +function check_point(M::SkewHermitianMatrices{<:Any,𝔽}, p; kwargs...) where {𝔽} if !isapprox(p, -p'; kwargs...) return DomainError( norm(p + p'), @@ -75,7 +79,7 @@ function check_point(M::SkewHermitianMatrices{n,𝔽}, p; kwargs...) where {n, end """ - check_vector(M::SkewHermitianMatrices{n}, p, X; kwargs... ) + check_vector(M::SkewHermitianMatrices, p, X; kwargs... ) Check whether `X` is a tangent vector to manifold point `p` on the [`SkewHermitianMatrices`](@ref) `M`, i.e. `X` must be a skew-hermitian matrix of size `(n,n)` @@ -92,13 +96,8 @@ function get_basis(M::SkewHermitianMatrices, p, B::DiagonalizingOrthonormalBasis return CachedBasis(B, κ, Ξ) end -function get_coordinates_orthonormal!( - M::SkewSymmetricMatrices{N}, - Y, - p, - X, - ::RealNumbers, -) where {N} +function get_coordinates_orthonormal!(M::SkewSymmetricMatrices, Y, p, X, ::RealNumbers) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(Y) == (dim,) @assert size(X) == (N, N) @@ -111,12 +110,13 @@ function get_coordinates_orthonormal!( return Y end function get_coordinates_orthonormal!( - M::SkewHermitianMatrices{N,ℂ}, + M::SkewHermitianMatrices{<:Any,ℂ}, Y, p, X, ::ComplexNumbers, -) where {N} +) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(Y) == (dim,) @assert size(X) == (N, N) @@ -135,15 +135,16 @@ function get_coordinates_orthonormal!( return Y end -get_embedding(::SkewHermitianMatrices{N,𝔽}) where {N,𝔽} = Euclidean(N, N; field=𝔽) +function get_embedding(::SkewHermitianMatrices{TypeParameter{Tuple{N}},𝔽}) where {N,𝔽} + return Euclidean(N, N; field=𝔽) +end +function get_embedding(M::SkewHermitianMatrices{Tuple{Int},𝔽}) where {𝔽} + N = get_parameter(M.size)[1] + return Euclidean(N, N; field=𝔽, parameter=:field) +end -function get_vector_orthonormal!( - M::SkewSymmetricMatrices{N}, - Y, - p, - X, - ::RealNumbers, -) where {N} +function get_vector_orthonormal!(M::SkewSymmetricMatrices, Y, p, X, ::RealNumbers) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(X) == (dim,) @assert size(Y) == (N, N) @@ -159,12 +160,13 @@ function get_vector_orthonormal!( return Y end function get_vector_orthonormal!( - M::SkewHermitianMatrices{N,ℂ}, + M::SkewHermitianMatrices{<:Any,ℂ}, Y, p, X, ::ComplexNumbers, -) where {N} +) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(X) == (dim,) @assert size(Y) == (N, N) @@ -190,7 +192,7 @@ Return true. [`SkewHermitianMatrices`](@ref) is a flat manifold. is_flat(M::SkewHermitianMatrices) = true @doc raw""" - manifold_dimension(M::SkewHermitianMatrices{n,𝔽}) + manifold_dimension(M::SkewHermitianMatrices) Return the dimension of the [`SkewHermitianMatrices`](@ref) matrix `M` over the number system `𝔽`, i.e. @@ -203,11 +205,12 @@ where ``\dim_ℝ 𝔽`` is the [`real_dimension`](https://juliamanifolds.github. only the upper triangular elements of the matrix being unique, and the second term corresponds to the constraint that the real part of the diagonal be zero. """ -function manifold_dimension(::SkewHermitianMatrices{N,𝔽}) where {N,𝔽} +function manifold_dimension(M::SkewHermitianMatrices{<:Any,𝔽}) where {𝔽} + N = get_parameter(M.size)[1] return div(N * (N + 1), 2) * real_dimension(𝔽) - N end -function number_of_coordinates(M::SkewHermitianMatrices{N,ℂ}, ::AbstractBasis{ℂ}) where {N} +function number_of_coordinates(M::SkewHermitianMatrices{<:Any,ℂ}, ::AbstractBasis{ℂ}) return manifold_dimension(M) end @@ -244,14 +247,25 @@ project(::SkewHermitianMatrices, ::Any, ::Any) project!(M::SkewHermitianMatrices, Y, p, X) = project!(M, Y, X) -representation_size(::SkewHermitianMatrices{N}) where {N} = (N, N) +function representation_size(M::SkewHermitianMatrices) + N = get_parameter(M.size)[1] + return (N, N) +end -function Base.show(io::IO, ::SkewHermitianMatrices{n,F}) where {n,F} +function Base.show(io::IO, ::SkewHermitianMatrices{TypeParameter{Tuple{n}},F}) where {n,F} return print(io, "SkewHermitianMatrices($(n), $(F))") end -function Base.show(io::IO, ::SkewSymmetricMatrices{n}) where {n} +function Base.show(io::IO, ::SkewSymmetricMatrices{TypeParameter{Tuple{n}}}) where {n} return print(io, "SkewSymmetricMatrices($(n))") end +function Base.show(io::IO, M::SkewHermitianMatrices{Tuple{Int},F}) where {F} + n = get_parameter(M.size)[1] + return print(io, "SkewHermitianMatrices($(n), $(F); parameter=:field)") +end +function Base.show(io::IO, M::SkewSymmetricMatrices{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "SkewSymmetricMatrices($(n); parameter=:field)") +end @doc raw""" Y = Weingarten(M::SkewSymmetricMatrices, p, X, V) diff --git a/src/manifolds/Spectrahedron.jl b/src/manifolds/Spectrahedron.jl index 28b883538f..ed7fb88479 100644 --- a/src/manifolds/Spectrahedron.jl +++ b/src/manifolds/Spectrahedron.jl @@ -1,5 +1,5 @@ @doc raw""" - Spectrahedron{N,K} <: AbstractDecoratorManifold{ℝ} + Spectrahedron{T} <: AbstractDecoratorManifold{ℝ} The Spectrahedron manifold, also known as the set of correlation matrices (symmetric positive semidefinite matrices) of rank $k$ with unit trace. @@ -38,13 +38,18 @@ investigated in [JourneeBachAbsilSepulchre:2010](@cite). # Constructor - Spectrahedron(n,k) + Spectrahedron(n::Int, k::Int; parameter::Symbol=:type) generates the manifold $\mathcal S(n,k) \subset ℝ^{n × n}$. """ -struct Spectrahedron{N,K} <: AbstractDecoratorManifold{ℝ} end +struct Spectrahedron{T} <: AbstractDecoratorManifold{ℝ} + size::T +end -Spectrahedron(n::Int, k::Int) = Spectrahedron{n,k}() +function Spectrahedron(n::Int, k::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, k)) + return Spectrahedron{typeof(size)}(size) +end active_traits(f, ::Spectrahedron, args...) = merge_traits(IsIsometricEmbeddedManifold()) @@ -59,7 +64,7 @@ Since by construction $p$ is symmetric, this is not explicitly checked. Since $p$ is by construction positive semidefinite, this is not checked. The tolerances for positive semidefiniteness and unit trace can be set using the `kwargs...`. """ -function check_point(M::Spectrahedron{N,K}, q; kwargs...) where {N,K} +function check_point(M::Spectrahedron, q; kwargs...) fro_n = norm(q) if !isapprox(fro_n, 1.0; kwargs...) return DomainError( @@ -80,7 +85,7 @@ and a $X$ has to be a symmetric matrix with trace. The tolerance for the base point check and zero diagonal can be set using the `kwargs...`. Note that symmetry of $X$ holds by construction and is not explicitly checked. """ -function check_vector(M::Spectrahedron{N,K}, q, Y; kwargs...) where {N,K} +function check_vector(M::Spectrahedron, q, Y; kwargs...) X = q * Y' + Y * q' n = tr(X) if !isapprox(n, 0.0; kwargs...) @@ -92,8 +97,12 @@ function check_vector(M::Spectrahedron{N,K}, q, Y; kwargs...) where {N,K} return nothing end -function get_embedding(M::Spectrahedron) - return Euclidean(representation_size(M)...; field=ℝ) +function get_embedding(::Spectrahedron{TypeParameter{Tuple{n,k}}}) where {n,k} + return Euclidean(n, k) +end +function get_embedding(M::Spectrahedron{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return Euclidean(n, k; parameter=:field) end """ @@ -112,7 +121,8 @@ returns the dimension of \dim \mathcal S(n,k) = nk - 1 - \frac{k(k-1)}{2}. ```` """ -@generated function manifold_dimension(::Spectrahedron{N,K}) where {N,K} +function manifold_dimension(M::Spectrahedron) + N, K = get_parameter(M.size) return N * K - 1 - div(K * (K - 1), 2) end @@ -155,10 +165,14 @@ Return the size of an array representing an element on the [`Spectrahedron`](@ref) manifold `M`, i.e. $n × k$, the size of such factor of $p=qq^{\mathrm{T}}$ on $\mathcal M = \mathcal S(n,k)$. """ -@generated representation_size(::Spectrahedron{N,K}) where {N,K} = (N, K) +representation_size(M::Spectrahedron) = get_parameter(M.size) -function Base.show(io::IO, ::Spectrahedron{N,K}) where {N,K} - return print(io, "Spectrahedron($(N), $(K))") +function Base.show(io::IO, M::Spectrahedron{TypeParameter{Tuple{n,k}}}) where {n,k} + return print(io, "Spectrahedron($n, $k)") +end +function Base.show(io::IO, M::Spectrahedron{Tuple{Int,Int}}) + n, k = get_parameter(M.size) + return print(io, "Spectrahedron($n, $k; parameter=:field)") end """ @@ -182,4 +196,4 @@ definite matrix `p` on the [`Spectrahedron`](@ref) manifold `M`. """ zero_vector(::Spectrahedron, ::Any...) -zero_vector!(::Spectrahedron{N,K}, v, ::Any) where {N,K} = fill!(v, 0) +zero_vector!(::Spectrahedron, X, ::Any) = fill!(X, 0) diff --git a/src/manifolds/Sphere.jl b/src/manifolds/Sphere.jl index 54e0fd38cb..e2fb758159 100644 --- a/src/manifolds/Sphere.jl +++ b/src/manifolds/Sphere.jl @@ -10,7 +10,7 @@ function active_traits(f, ::AbstractSphere, args...) end @doc raw""" - Sphere{n,𝔽} <: AbstractSphere{𝔽} + Sphere{T,𝔽} <: AbstractSphere{𝔽} The (unit) sphere manifold $𝕊^{n}$ is the set of all unit norm vectors in $𝔽^{n+1}$. The sphere is represented in the embedding, i.e. @@ -49,8 +49,13 @@ and the [`zero_vector`](@ref zero_vector(::Euclidean, ::Any...)) are inherited f Generate the (real-valued) sphere $𝕊^{n} ⊂ ℝ^{n+1}$, where `field` can also be used to generate the complex- and quaternionic-valued sphere. """ -struct Sphere{N,𝔽} <: AbstractSphere{𝔽} end -Sphere(n::Int, field::AbstractNumbers=ℝ) = Sphere{n,field}() +struct Sphere{T,𝔽} <: AbstractSphere{𝔽} + size::T +end +function Sphere(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return Sphere{typeof(size),field}(size) +end @doc raw""" ArraySphere{T<:Tuple,𝔽} <: AbstractSphere{𝔽} @@ -84,13 +89,20 @@ several functions like the [`inner`](@ref inner(::Euclidean, ::Any...)) product # Constructor - ArraySphere(n₁,n₂,...,nᵢ; field=ℝ) + ArraySphere(n₁,n₂,...,nᵢ; field=ℝ, parameter::Symbol=:type) Generate sphere in $𝔽^{n_1, n_2, …, n_i}$, where $𝔽$ defaults to the real-valued case $ℝ$. """ -struct ArraySphere{N,𝔽} <: AbstractSphere{𝔽} where {N<:Tuple} end -function ArraySphere(n::Vararg{Int,I}; field::AbstractNumbers=ℝ) where {I} - return ArraySphere{Tuple{n...},field}() +struct ArraySphere{T,𝔽} <: AbstractSphere{𝔽} + size::T +end +function ArraySphere( + n::Vararg{Int,I}; + field::AbstractNumbers=ℝ, + parameter::Symbol=:type, +) where {I} + size = wrap_type_parameter(parameter, n) + return ArraySphere{typeof(size),field}(size) end """ @@ -185,11 +197,8 @@ function exp!(M::AbstractSphere, q, p, X, t::Number) return q end -function get_basis_diagonalizing( - M::Sphere{n,ℝ}, - p, - B::DiagonalizingOrthonormalBasis{ℝ}, -) where {n} +function get_basis_diagonalizing(M::Sphere{<:Any,ℝ}, p, B::DiagonalizingOrthonormalBasis{ℝ}) + n = get_parameter(M.size)[1] A = zeros(n + 1, n + 1) A[1, :] = transpose(p) A[2, :] = transpose(B.frame_direction) @@ -234,6 +243,9 @@ end function get_embedding(M::AbstractSphere{𝔽}) where {𝔽} return Euclidean(representation_size(M)...; field=𝔽) end +function get_embedding(M::Sphere{<:Tuple,𝔽}) where {𝔽} + return Euclidean(representation_size(M)...; field=𝔽, parameter=:field) +end @doc raw""" get_vector(M::AbstractSphere{ℝ}, p, X, B::DefaultOrthonormalBasis) @@ -317,7 +329,15 @@ return the local representation of the metric in a [`DefaultOrthonormalBasis`](h the diagonal matrix of size ``n×n`` with ones on the diagonal, since the metric is obtained from the embedding by restriction to the tangent space ``T_p\mathcal M`` at ``p``. """ -function local_metric(::Sphere{n,ℝ}, p, B::DefaultOrthonormalBasis) where {n} +function local_metric(M::Sphere{Tuple{Int},ℝ}, p, ::DefaultOrthonormalBasis) + n = get_parameter(M.size)[1] + return Diagonal(ones(eltype(p), n)) +end +function local_metric( + ::Sphere{TypeParameter{Tuple{n}},ℝ}, + p, + B::DefaultOrthonormalBasis, +) where {n} return Diagonal(ones(SVector{n,eltype(p)})) end @@ -453,8 +473,13 @@ end Return the size points on the [`AbstractSphere`](@ref) `M` are represented as, i.e., the representation size of the embedding. """ -@generated representation_size(::ArraySphere{N}) where {N} = size_to_tuple(N) -@generated representation_size(::Sphere{N}) where {N} = (N + 1,) +function representation_size(M::ArraySphere) + return get_parameter(M.size) +end +function representation_size(M::Sphere) + n = get_parameter(M.size)[1] + return (n + 1,) +end @doc raw""" retract(M::AbstractSphere, p, X, ::ProjectionRetraction) @@ -472,9 +497,19 @@ function retract_project!(M::AbstractSphere, q, p, X, t::Number) return project!(M, q, q) end -Base.show(io::IO, ::Sphere{n,𝔽}) where {n,𝔽} = print(io, "Sphere($(n), $(𝔽))") -function Base.show(io::IO, ::ArraySphere{N,𝔽}) where {N,𝔽} - return print(io, "ArraySphere($(join(N.parameters, ", ")); field = $(𝔽))") +function Base.show(io::IO, ::Sphere{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return print(io, "Sphere($(n), $(𝔽))") +end +function Base.show(io::IO, M::Sphere{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return print(io, "Sphere($(n), $(𝔽); parameter=:field)") +end +function Base.show(io::IO, ::ArraySphere{TypeParameter{tn},𝔽}) where {tn,𝔽} + return print(io, "ArraySphere($(join(tn.parameters, ", ")); field=$(𝔽))") +end +function Base.show(io::IO, M::ArraySphere{<:Tuple,𝔽}) where {𝔽} + n = M.size + return print(io, "ArraySphere($(join(n, ", ")); field=$(𝔽), parameter=:field)") end """ @@ -483,8 +518,9 @@ end Uniform distribution on given [`Sphere`](@ref) `M`. Generated points will be of similar type as `p`. """ -function uniform_distribution(M::Sphere{n,ℝ}, p) where {n} - d = Distributions.MvNormal(zero(p), 1.0) +function uniform_distribution(M::Sphere{<:Any,ℝ}, p) + n = get_parameter(M.size)[1] + d = Distributions.MvNormal(zero(p), 1.0 * I) return ProjectedPointDistribution(M, d, project!, p) end @@ -571,7 +607,7 @@ point (1, 0, ..., 0) (called `:south`). """ struct StereographicAtlas <: AbstractAtlas{ℝ} end -function get_chart_index(::Sphere{n,ℝ}, ::StereographicAtlas, p) where {n} +function get_chart_index(::Sphere{<:Any,ℝ}, ::StereographicAtlas, p) if p[1] < 0 return :south else @@ -579,7 +615,7 @@ function get_chart_index(::Sphere{n,ℝ}, ::StereographicAtlas, p) where {n} end end -function get_parameters!(::Sphere{n,ℝ}, x, ::StereographicAtlas, i::Symbol, p) where {n} +function get_parameters!(::Sphere{<:Any,ℝ}, x, ::StereographicAtlas, i::Symbol, p) if i === :north return x .= p[2:end] ./ (1 + p[1]) else @@ -587,7 +623,7 @@ function get_parameters!(::Sphere{n,ℝ}, x, ::StereographicAtlas, i::Symbol, p) end end -function get_point!(::Sphere{n,ℝ}, p, ::StereographicAtlas, i::Symbol, x) where {n} +function get_point!(::Sphere{<:Any,ℝ}, p, ::StereographicAtlas, i::Symbol, x) xnorm2 = dot(x, x) if i === :north p[1] = (1 - xnorm2) / (xnorm2 + 1) @@ -599,12 +635,13 @@ function get_point!(::Sphere{n,ℝ}, p, ::StereographicAtlas, i::Symbol, x) wher end function get_coordinates_induced_basis!( - ::Sphere{n,ℝ}, + M::Sphere{<:Any,ℝ}, Y, p, X, B::InducedBasis{ℝ,TangentSpaceType,<:StereographicAtlas}, -) where {n} +) + n = get_parameter(M.size)[1] if B.i === :north for i in 1:n Y[i] = X[i + 1] / (1 + p[1]) - X[1] * p[i + 1] / (1 + p[1])^2 @@ -618,12 +655,13 @@ function get_coordinates_induced_basis!( end function get_vector_induced_basis!( - M::Sphere{n,ℝ}, + M::Sphere{<:Any,ℝ}, Y, p, X, B::InducedBasis{ℝ,TangentSpaceType,<:StereographicAtlas}, -) where {n} +) + n = get_parameter(M.size)[1] a = get_parameters(M, B.A, B.i, p) mult = inv(1 + dot(a, a))^2 @@ -648,10 +686,10 @@ function get_vector_induced_basis!( end function local_metric( - M::Sphere{n,ℝ}, + M::Sphere{<:Any,ℝ}, p, B::InducedBasis{ℝ,TangentSpaceType,StereographicAtlas,Symbol}, -) where {n} +) a = get_parameters(M, B.A, B.i, p) return (4 / (1 + dot(a, a))^2) * I end diff --git a/src/manifolds/SphereSymmetricMatrices.jl b/src/manifolds/SphereSymmetricMatrices.jl index dc4bf5465e..cdb97c05e5 100644 --- a/src/manifolds/SphereSymmetricMatrices.jl +++ b/src/manifolds/SphereSymmetricMatrices.jl @@ -1,5 +1,5 @@ @doc raw""" - SphereSymmetricMatrices{n,𝔽} <: AbstractEmbeddedManifold{ℝ,TransparentIsometricEmbedding} + SphereSymmetricMatrices{T,𝔽} <: AbstractEmbeddedManifold{ℝ,TransparentIsometricEmbedding} The [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) consisting of the $n × n$ symmetric matrices of unit Frobenius norm, i.e. @@ -14,10 +14,13 @@ and the field $𝔽 ∈ \{ ℝ, ℂ\}$. Generate the manifold of `n`-by-`n` symmetric matrices of unit Frobenius norm. """ -struct SphereSymmetricMatrices{N,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct SphereSymmetricMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -function SphereSymmetricMatrices(n::Int, field::AbstractNumbers=ℝ) - return SphereSymmetricMatrices{n,field}() +function SphereSymmetricMatrices(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return SphereSymmetricMatrices{typeof(size),field}(size) end function active_traits(f, ::SphereSymmetricMatrices, arge...) @@ -25,14 +28,14 @@ function active_traits(f, ::SphereSymmetricMatrices, arge...) end @doc raw""" - check_point(M::SphereSymmetricMatrices{n,𝔽}, p; kwargs...) + check_point(M::SphereSymmetricMatrices, p; kwargs...) Check whether the matrix is a valid point on the [`SphereSymmetricMatrices`](@ref) `M`, i.e. is an `n`-by-`n` symmetric matrix of unit Frobenius norm. The tolerance for the symmetry of `p` can be set using `kwargs...`. """ -function check_point(M::SphereSymmetricMatrices{n,𝔽}, p; kwargs...) where {n,𝔽} +function check_point(M::SphereSymmetricMatrices, p; kwargs...) if !isapprox(norm(p - p'), 0.0; kwargs...) return DomainError( norm(p - p'), @@ -43,7 +46,7 @@ function check_point(M::SphereSymmetricMatrices{n,𝔽}, p; kwargs...) where {n, end """ - check_vector(M::SphereSymmetricMatrices{n,𝔽}, p, X; kwargs... ) + check_vector(M::SphereSymmetricMatrices, p, X; kwargs... ) Check whether `X` is a tangent vector to manifold point `p` on the [`SphereSymmetricMatrices`](@ref) `M`, i.e. `X` has to be a symmetric matrix of size `(n,n)` @@ -51,7 +54,7 @@ of unit Frobenius norm. The tolerance for the symmetry of `p` and `X` can be set using `kwargs...`. """ -function check_vector(M::SphereSymmetricMatrices{n,𝔽}, p, X; kwargs...) where {n,𝔽} +function check_vector(M::SphereSymmetricMatrices, p, X; kwargs...) if !isapprox(norm(X - X'), 0.0; kwargs...) return DomainError( norm(X - X'), @@ -64,9 +67,13 @@ end embed(::SphereSymmetricMatrices, p) = p embed(::SphereSymmetricMatrices, p, X) = X -function get_embedding(::SphereSymmetricMatrices{n,𝔽}) where {n,𝔽} +function get_embedding(::SphereSymmetricMatrices{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} return ArraySphere(n, n; field=𝔽) end +function get_embedding(M::SphereSymmetricMatrices{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return ArraySphere(n, n; field=𝔽, parameter=:field) +end """ is_flat(::SphereSymmetricMatrices) @@ -76,7 +83,7 @@ Return false. [`SphereSymmetricMatrices`](@ref) is not a flat manifold. is_flat(M::SphereSymmetricMatrices) = false @doc raw""" - manifold_dimension(M::SphereSymmetricMatrices{n,𝔽}) + manifold_dimension(M::SphereSymmetricMatrices{<:Any,𝔽}) Return the manifold dimension of the [`SphereSymmetricMatrices`](@ref) `n`-by-`n` symmetric matrix `M` of unit Frobenius norm over the number system `𝔽`, i.e. @@ -88,7 +95,8 @@ Frobenius norm over the number system `𝔽`, i.e. \end{aligned} ```` """ -function manifold_dimension(::SphereSymmetricMatrices{n,𝔽}) where {n,𝔽} +function manifold_dimension(M::SphereSymmetricMatrices{<:Any,𝔽}) where {𝔽} + n = get_parameter(M.size)[1] return div(n * (n + 1), 2) * real_dimension(𝔽) - (𝔽 === ℂ ? n : 0) - 1 end @@ -124,8 +132,15 @@ function project!(M::SphereSymmetricMatrices, Y, p, X) return project!(get_embedding(M), Y, p, (X .+ X') ./ 2) end -@generated representation_size(::SphereSymmetricMatrices{n,𝔽}) where {n,𝔽} = (n, n) +function representation_size(M::SphereSymmetricMatrices) + n = get_parameter(M.size)[1] + return (n, n) +end -function Base.show(io::IO, ::SphereSymmetricMatrices{n,𝔽}) where {n,𝔽} +function Base.show(io::IO, ::SphereSymmetricMatrices{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} return print(io, "SphereSymmetricMatrices($(n), $(𝔽))") end +function Base.show(io::IO, M::SphereSymmetricMatrices{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return print(io, "SphereSymmetricMatrices($(n), $(𝔽); parameter=:field)") +end diff --git a/src/manifolds/Stiefel.jl b/src/manifolds/Stiefel.jl index 2cbee11ac0..e17da0546a 100644 --- a/src/manifolds/Stiefel.jl +++ b/src/manifolds/Stiefel.jl @@ -1,5 +1,5 @@ @doc raw""" - Stiefel{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} + Stiefel{T,𝔽} <: AbstractDecoratorManifold{𝔽} The Stiefel manifold consists of all $n × k$, $n ≥ k$ unitary matrices, i.e. @@ -27,19 +27,24 @@ The manifold is named after [Eduard L. Stiefel](https://en.wikipedia.org/wiki/Eduard_Stiefel) (1909–1978). # Constructor - Stiefel(n, k, field = ℝ) + Stiefel(n, k, field=ℝ; parameter::Symbol=:type) Generate the (real-valued) Stiefel manifold of $n × k$ dimensional orthonormal matrices. """ -struct Stiefel{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct Stiefel{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -Stiefel(n::Int, k::Int, field::AbstractNumbers=ℝ) = Stiefel{n,k,field}() +function Stiefel(n::Int, k::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n, k)) + return Stiefel{typeof(size),field}(size) +end function active_traits(f, ::Stiefel, args...) return merge_traits(IsIsometricEmbeddedManifold(), IsDefaultMetric(EuclideanMetric())) end -function allocation_promotion_function(::Stiefel{n,k,ℂ}, ::Any, ::Tuple) where {n,k} +function allocation_promotion_function(::Stiefel{<:Any,ℂ}, ::Any, ::Tuple) return complex end @@ -76,7 +81,7 @@ Check whether `p` is a valid point on the [`Stiefel`](@ref) `M`=$\operatorname{S [`AbstractNumbers`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#number-system) type and $p^{\mathrm{H}}p$ is (approximately) the identity, where $\cdot^{\mathrm{H}}$ is the complex conjugate transpose. The settings for approximately can be set with `kwargs...`. """ -function check_point(M::Stiefel{n,k,𝔽}, p; kwargs...) where {n,k,𝔽} +function check_point(M::Stiefel, p; kwargs...) cks = check_size(M, p) (cks === nothing) || return cks c = p' * p @@ -98,7 +103,8 @@ it (approximately) holds that $p^{\mathrm{H}}X + \overline{X^{\mathrm{H}}p} = 0$ where $\cdot^{\mathrm{H}}$ denotes the Hermitian and $\overline{\cdot}$ the (elementwise) complex conjugate. The settings for approximately can be set with `kwargs...`. """ -function check_vector(M::Stiefel{n,k,𝔽}, p, X; kwargs...) where {n,k,𝔽} +function check_vector(M::Stiefel, p, X; kwargs...) + n, k = get_parameter(M.size) cks = check_size(M, p, X) cks === nothing || return cks if !isapprox(p' * X, -conj(X' * p); kwargs...) @@ -138,8 +144,12 @@ end embed(::Stiefel, p) = p embed(::Stiefel, p, X) = X -function get_embedding(::Stiefel{N,K,𝔽}) where {N,K,𝔽} - return Euclidean(N, K; field=𝔽) +function get_embedding(::Stiefel{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return Euclidean(n, k; field=𝔽) +end +function get_embedding(M::Stiefel{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return Euclidean(n, k; field=𝔽, parameter=:field) end @doc raw""" @@ -176,7 +186,8 @@ in [KanekoFioriTanaka:2013](@cite). """ inverse_retract(::Stiefel, ::Any, ::Any, ::QRInverseRetraction) -function _stiefel_inv_retr_qr_mul_by_r_generic!(::Stiefel{n,k}, X, q, R, A) where {n,k} +function _stiefel_inv_retr_qr_mul_by_r_generic!(M::Stiefel, X, q, R, A) + n, k = get_parameter(M.size) @inbounds for i in 1:k b = zeros(eltype(R), i) b[i] = 1 @@ -188,12 +199,18 @@ function _stiefel_inv_retr_qr_mul_by_r_generic!(::Stiefel{n,k}, X, q, R, A) wher return mul!(X, q, R) end -function _stiefel_inv_retr_qr_mul_by_r!(::Stiefel{n,1}, X, q, A, ::Type) where {n} +function _stiefel_inv_retr_qr_mul_by_r!( + ::Stiefel{TypeParameter{Tuple{n,1}}}, + X, + q, + A, + ::Type, +) where {n} @inbounds R = SMatrix{1,1}(inv(A[1, 1])) return mul!(X, q, R) end function _stiefel_inv_retr_qr_mul_by_r!( - M::Stiefel{n,1}, + M::Stiefel{TypeParameter{Tuple{n,1}}}, X, q, A::StaticArray, @@ -201,7 +218,13 @@ function _stiefel_inv_retr_qr_mul_by_r!( ) where {n,ElT} return invoke( _stiefel_inv_retr_qr_mul_by_r!, - Tuple{Stiefel{n,1},typeof(X),typeof(q),AbstractArray,typeof(ElT)}, + Tuple{ + Stiefel{TypeParameter{Tuple{n,1}}}, + typeof(X), + typeof(q), + AbstractArray, + typeof(ElT), + }, M, X, q, @@ -209,7 +232,13 @@ function _stiefel_inv_retr_qr_mul_by_r!( ElT, ) end -function _stiefel_inv_retr_qr_mul_by_r!(::Stiefel{n,2}, X, q, A, ::Type{ElT}) where {n,ElT} +function _stiefel_inv_retr_qr_mul_by_r!( + ::Stiefel{TypeParameter{Tuple{n,2}}}, + X, + q, + A, + ::Type{ElT}, +) where {n,ElT} R11 = inv(A[1, 1]) @inbounds R = hcat(SA[R11, zero(ElT)], A[SOneTo(2), SOneTo(2)] \ SA[-R11 * A[2, 1], one(ElT)]) @@ -219,7 +248,7 @@ function _stiefel_inv_retr_qr_mul_by_r!(::Stiefel{n,2}, X, q, A, ::Type{ElT}) wh return mul!(X, q, R) end function _stiefel_inv_retr_qr_mul_by_r!( - M::Stiefel{n,2}, + M::Stiefel{TypeParameter{Tuple{n,2}}}, X, q, A::StaticArray, @@ -227,7 +256,13 @@ function _stiefel_inv_retr_qr_mul_by_r!( ) where {n,ElT} return invoke( _stiefel_inv_retr_qr_mul_by_r!, - Tuple{Stiefel{n,2},typeof(X),typeof(q),AbstractArray,typeof(ElT)}, + Tuple{ + Stiefel{TypeParameter{Tuple{n,2}}}, + typeof(X), + typeof(q), + AbstractArray, + typeof(ElT), + }, M, X, q, @@ -236,7 +271,7 @@ function _stiefel_inv_retr_qr_mul_by_r!( ) end function _stiefel_inv_retr_qr_mul_by_r!( - M::Stiefel{n,k}, + M::Stiefel{TypeParameter{Tuple{n,k}}}, X, q, A::StaticArray, @@ -245,13 +280,8 @@ function _stiefel_inv_retr_qr_mul_by_r!( R = zeros(MMatrix{k,k,ElT}) return _stiefel_inv_retr_qr_mul_by_r_generic!(M, X, q, R, A) end -function _stiefel_inv_retr_qr_mul_by_r!( - M::Stiefel{n,k}, - X, - q, - A, - ::Type{ElT}, -) where {n,k,ElT} +function _stiefel_inv_retr_qr_mul_by_r!(M::Stiefel, X, q, A, ::Type{ElT}) where {ElT} + n, k = get_parameter(M.size) R = zeros(ElT, k, k) return _stiefel_inv_retr_qr_mul_by_r_generic!(M, X, q, R, A) end @@ -264,7 +294,8 @@ function inverse_retract_polar!(::Stiefel, X, p, q) X .-= p return X end -function inverse_retract_qr!(M::Stiefel{n,k}, X, p, q) where {n,k} +function inverse_retract_qr!(M::Stiefel, X, p, q) + n, k = get_parameter(M.size) A = p' * q @boundscheck size(A) === (k, k) ElT = typeof(one(eltype(p)) * one(eltype(q))) @@ -298,9 +329,18 @@ The dimension is given by \end{aligned} ```` """ -manifold_dimension(::Stiefel{n,k,ℝ}) where {n,k} = n * k - div(k * (k + 1), 2) -manifold_dimension(::Stiefel{n,k,ℂ}) where {n,k} = 2 * n * k - k * k -manifold_dimension(::Stiefel{n,k,ℍ}) where {n,k} = 4 * n * k - k * (2k - 1) +function manifold_dimension(M::Stiefel{<:Any,ℝ}) + n, k = get_parameter(M.size) + return n * k - div(k * (k + 1), 2) +end +function manifold_dimension(M::Stiefel{<:Any,ℂ}) + n, k = get_parameter(M.size) + return 2 * n * k - k * k +end +function manifold_dimension(M::Stiefel{<:Any,ℍ}) + n, k = get_parameter(M.size) + return 4 * n * k - k * (2k - 1) +end @doc raw""" rand(::Stiefel; vector_at=nothing, σ::Real=1.0) @@ -318,11 +358,12 @@ rand(::Stiefel; σ::Real=1.0) function Random.rand!( rng::AbstractRNG, - M::Stiefel{n,k,𝔽}, + M::Stiefel{<:Any,𝔽}, pX; vector_at=nothing, σ::Real=one(real(eltype(pX))), -) where {n,k,𝔽} +) where {𝔽} + n, k = get_parameter(M.size) if vector_at === nothing A = σ * randn(rng, 𝔽 === ℝ ? Float64 : ComplexF64, n, k) pX .= Matrix(qr(A).Q) @@ -471,12 +512,18 @@ end Returns the representation size of the [`Stiefel`](@ref) `M`=$\operatorname{St}(n,k)$, i.e. `(n,k)`, which is the matrix dimensions. """ -@generated representation_size(::Stiefel{n,k}) where {n,k} = (n, k) +representation_size(M::Stiefel) = get_parameter(M.size) -Base.show(io::IO, ::Stiefel{n,k,F}) where {n,k,F} = print(io, "Stiefel($(n), $(k), $(F))") +function Base.show(io::IO, ::Stiefel{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return print(io, "Stiefel($(n), $(k), $(𝔽))") +end +function Base.show(io::IO, M::Stiefel{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return print(io, "Stiefel($(n), $(k), $(𝔽); parameter=:field)") +end """ - uniform_distribution(M::Stiefel{n,k,ℝ}, p) + uniform_distribution(M::Stiefel{<:Any,ℝ}, p) Uniform distribution on given (real-valued) [`Stiefel`](@ref) `M`. Specifically, this is the normalized Haar and Hausdorff measure on `M`. @@ -485,7 +532,8 @@ Generated points will be of similar type as `p`. The implementation is based on Section 2.5.1 in [Chikuse:2003](@cite); see also Theorem 2.2.1(iii) in [Chikuse:2003](@cite). """ -function uniform_distribution(M::Stiefel{n,k,ℝ}, p) where {n,k} +function uniform_distribution(M::Stiefel{<:Any,ℝ}, p) + n, k = get_parameter(M.size) μ = Distributions.Zeros(n, k) σ = one(eltype(p)) Σ1 = Distributions.PDMats.ScalMat(n, σ) diff --git a/src/manifolds/StiefelCanonicalMetric.jl b/src/manifolds/StiefelCanonicalMetric.jl index 68d57a6f28..7e750c3f9d 100644 --- a/src/manifolds/StiefelCanonicalMetric.jl +++ b/src/manifolds/StiefelCanonicalMetric.jl @@ -11,7 +11,7 @@ struct CanonicalMetric <: RiemannianMetric end ApproximateLogarithmicMap <: ApproximateInverseRetraction An approximate implementation of the logarithmic map, which is an [`inverse_retract`](@ref)ion. -See [`inverse_retract(::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, ::Any, ::Any, ::ApproximateLogarithmicMap) where {n,k}`](@ref) for a use case. +See [`inverse_retract(::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, ::Any, ::Any, ::ApproximateLogarithmicMap)`](@ref) for a use case. # Fields @@ -24,15 +24,15 @@ struct ApproximateLogarithmicMap{T} <: ApproximateInverseRetraction tolerance::T end -function distance(M::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, q, p) where {n,k} +function distance(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, q, p) return norm(M, p, log(M, p, q)) end @doc raw""" - q = exp(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, CanonicalMetric}, p, X) - exp!(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, q, CanonicalMetric}, p, X) + q = exp(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, p, X) + exp!(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, q, p, X) -Compute the exponential map on the [`Stiefel`](@ref)`(n,k)` manifold with respect to the [`CanonicalMetric`](@ref). +Compute the exponential map on the [`Stiefel`](@ref)`(n, k)` manifold with respect to the [`CanonicalMetric`](@ref). First, decompose The tangent vector ``X`` into its horizontal and vertical component with respect to ``p``, i.e. @@ -64,14 +64,15 @@ q = \exp_p X = pC + QB. ``` For more details, see [EdelmanAriasSmith:1998](@cite)[Zimmermann:2017](@cite). """ -exp(::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, ::Any...) where {n,k} +exp(::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, ::Any...) -function exp!(::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, q, p, X) where {n,k} +function exp!(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, q, p, X) + n, k = get_parameter(M.manifold.size) A = p' * X n == k && return mul!(q, p, exp(A)) QR = qr(X - p * A) BC_ext = exp([A -QR.R'; QR.R 0*I]) - @views begin + @views begin # COV_EXCL_LINE mul!(q, p, BC_ext[1:k, 1:k]) mul!(q, Matrix(QR.Q), BC_ext[(k + 1):(2 * k), 1:k], true, true) end @@ -79,7 +80,7 @@ function exp!(::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, q, p, X) w end @doc raw""" - inner(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, X, CanonicalMetric}, p, X, Y) + inner(M::MetricManifold{ℝ, Stiefel{<:Any,ℝ}, X, CanonicalMetric}, p, X, Y) Compute the inner product on the [`Stiefel`](@ref) manifold with respect to the [`CanonicalMetric`](@ref). The formula reads @@ -88,7 +89,8 @@ Compute the inner product on the [`Stiefel`](@ref) manifold with respect to the g_p(X,Y) = \operatorname{tr}\bigl( X^{\mathrm{T}}(I_n - \frac{1}{2}pp^{\mathrm{T}})Y \bigr). ``` """ -function inner(::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, p, X, Y) where {n,k} +function inner(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, p, X, Y) + n, k = get_parameter(M.manifold.size) T = Base.promote_eltype(p, X, Y) if n == k return T(dot(X, Y)) / 2 @@ -98,59 +100,60 @@ function inner(::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, p, X, Y) end @doc raw""" - X = inverse_retract(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, CanonicalMetric}, p, q, a::ApproximateLogarithmicMap) - inverse_retract!(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, X, CanonicalMetric}, p, q, a::ApproximateLogarithmicMap) + X = inverse_retract(M::MetricManifold{ℝ, Stiefel{<:Any,ℝ}, CanonicalMetric}, p, q, a::ApproximateLogarithmicMap) + inverse_retract!(M::MetricManifold{ℝ, Stiefel{<:Any,ℝ}, X, CanonicalMetric}, p, q, a::ApproximateLogarithmicMap) -Compute an approximation to the logarithmic map on the [`Stiefel`](@ref)`(n,k)` manifold with respect to the [`CanonicalMetric`](@ref) +Compute an approximation to the logarithmic map on the [`Stiefel`](@ref)`(n, k)` manifold with respect to the [`CanonicalMetric`](@ref) using a matrix-algebraic based approach to an iterative inversion of the formula of the -[`exp`](@ref exp(::MetricManifold{ℝ, Stiefel{n,k,ℝ}, CanonicalMetric}, ::Any...) where {n,k}). +[`exp`](@ref exp(::MetricManifold{ℝ, Stiefel{<:Any,ℝ}, CanonicalMetric}, ::Any...)). The algorithm is derived in [Zimmermann:2017](@cite) and it uses the `max_iterations` and the `tolerance` field from the [`ApproximateLogarithmicMap`](@ref). """ inverse_retract( - ::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, + ::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, ::Any, ::Any, ::ApproximateLogarithmicMap, -) where {n,k} +) function log( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, p, q; maxiter::Int=10000, tolerance=1e-9, -) where {n,k} +) X = allocate_result(M, log, p, q) inverse_retract!(M, X, p, q, ApproximateLogarithmicMap(maxiter, tolerance)) return X end function log!( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, X, p, q; maxiter::Int=10000, tolerance=1e-9, -) where {n,k} +) inverse_retract!(M, X, p, q, ApproximateLogarithmicMap(maxiter, tolerance)) return X end function inverse_retract!( - ::MetricManifold{ℝ,Stiefel{n,k,ℝ},CanonicalMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},CanonicalMetric}, X, p, q, a::ApproximateLogarithmicMap, -) where {n,k} +) + n, k = get_parameter(M.manifold.size) qfact = stiefel_factorization(p, q) V = allocate(qfact.Z, Size(2k, 2k)) LV = allocate(V) Zcompl = qr(qfact.Z).Q[1:(2k), (k + 1):(2k)] - @views begin + @views begin # COV_EXCL_LINE Vpcols = V[1:(2k), (k + 1):(2k)] #second half of the columns B = LV[(k + 1):(2k), 1:k] C = LV[(k + 1):(2k), (k + 1):(2k)] @@ -176,8 +179,8 @@ function inverse_retract!( end @doc raw""" - Y = riemannian_Hessian(M::MetricManifold{ℝ, Stiefel{n,k}, CanonicalMetric}, p, G, H, X) - riemannian_Hessian!(M::MetricManifold{ℝ, Stiefel{n,k}, CanonicalMetric}, Y, p, G, H, X) + Y = riemannian_Hessian(M::MetricManifold{ℝ, Stiefel, CanonicalMetric}, p, G, H, X) + riemannian_Hessian!(M::MetricManifold{ℝ, Stiefel, CanonicalMetric}, Y, p, G, H, X) Compute the Riemannian Hessian ``\operatorname{Hess} f(p)[X]`` given the Euclidean gradient ``∇ f(\tilde p)`` in `G` and the Euclidean Hessian ``∇^2 f(\tilde p)[\tilde X]`` in `H`, @@ -196,22 +199,16 @@ Here, we adopt Eq. (5.6) [Nguyen:2023](@cite), for the [`CanonicalMetric`](@ref) ``` where ``P = I-pp^{\mathrm{H}}``. """ -riemannian_Hessian( - M::MetricManifold{𝔽,Stiefel{n,k,𝔽},CanonicalMetric}, - p, - G, - H, - X, -) where {n,k,𝔽} +riemannian_Hessian(M::MetricManifold{𝔽,Stiefel,CanonicalMetric}, p, G, H, X) where {𝔽} function riemannian_Hessian!( - M::MetricManifold{𝔽,Stiefel{n,k,𝔽},CanonicalMetric}, + M::MetricManifold{𝔽,<:Stiefel{<:Any,𝔽},CanonicalMetric}, Y, p, G, H, X, -) where {n,k,𝔽} +) where {𝔽} Gp = symmetrize(G' * p) Z = symmetrize((I - p * p') * G * p') project!(M, Y, p, H - X * Gp - Z * X) diff --git a/src/manifolds/StiefelEuclideanMetric.jl b/src/manifolds/StiefelEuclideanMetric.jl index f897f4053b..fa2e64518c 100644 --- a/src/manifolds/StiefelEuclideanMetric.jl +++ b/src/manifolds/StiefelEuclideanMetric.jl @@ -23,10 +23,11 @@ $0_k$ are the identity matrix and the zero matrix of dimension $k × k$, respect """ exp(::Stiefel, ::Any...) -function exp!(::Stiefel{n,k}, q, p, X) where {n,k} +function exp!(M::Stiefel, q, p, X) + n, k = get_parameter(M.size) A = p' * X B = exp([A -X'*X; I A]) - @views begin + @views begin # COV_EXCL_LINE r = p * B[1:k, 1:k] mul!(r, X, B[(k + 1):(2 * k), 1:k], true, true) end @@ -35,7 +36,7 @@ function exp!(::Stiefel{n,k}, q, p, X) where {n,k} end @doc raw""" - get_basis(M::Stiefel{n,k,ℝ}, p, B::DefaultOrthonormalBasis) where {n,k} + get_basis(M::Stiefel{<:Any,ℝ}, p, B::DefaultOrthonormalBasis) Create the default basis using the parametrization for any $X ∈ T_p\mathcal M$. Set $p_\bot \in ℝ^{n\times(n-k)}$ the matrix such that the $n\times n$ matrix of the common @@ -58,30 +59,24 @@ trangular entries of $a$ is set to $1$ its symmetric entry to $-1$ and we normal the factor $\frac{1}{\sqrt{2}}$ and for $b$ one can just use unit vectors reshaped to a matrix to obtain orthonormal set of parameters. """ -get_basis(M::Stiefel{n,k,ℝ}, p, B::DefaultOrthonormalBasis{ℝ,TangentSpaceType}) where {n,k} +get_basis(M::Stiefel{<:Any,ℝ}, p, B::DefaultOrthonormalBasis{ℝ,TangentSpaceType}) function _get_basis( - M::Stiefel{n,k,ℝ}, + M::Stiefel{<:Any,ℝ}, p, B::DefaultOrthonormalBasis{ℝ,TangentSpaceType}; kwargs..., -) where {n,k} +) return CachedBasis(B, get_vectors(M, p, B)) end -function get_coordinates_orthonormal!( - M::Stiefel{n,k,ℝ}, - c, - p, - X, - N::RealNumbers, -) where {n,k} +function get_coordinates_orthonormal!(M::Stiefel{<:Any,ℝ}, c, p, X, N::RealNumbers) V = get_vectors(M, p, DefaultOrthonormalBasis(N)) c .= inner.(Ref(M), Ref(p), V, Ref(X)) return c end -function get_vector_orthonormal!(M::Stiefel{n,k,ℝ}, X, p, c, N::RealNumbers) where {n,k} +function get_vector_orthonormal!(M::Stiefel{<:Any,ℝ}, X, p, c, N::RealNumbers) V = get_vectors(M, p, DefaultOrthonormalBasis(N)) zero_vector!(M, X, p) length(c) < length(V) && error( @@ -93,11 +88,8 @@ function get_vector_orthonormal!(M::Stiefel{n,k,ℝ}, X, p, c, N::RealNumbers) w return X end -function get_vectors( - ::Stiefel{n,k,ℝ}, - p, - ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}, -) where {n,k} +function get_vectors(M::Stiefel{<:Any,ℝ}, p, ::DefaultOrthonormalBasis{ℝ,TangentSpaceType}) + n, k = get_parameter(M.size) p⊥ = nullspace([p zeros(n, n - k)]) an = div(k * (k - 1), 2) bn = (n - k) * k @@ -126,7 +118,7 @@ function inverse_retract_project!(M::Stiefel, X, p, q) return X end -function log!(M::Stiefel{n,k,ℝ}, X, p, q) where {n,k} +function log!(M::Stiefel{<:Any,ℝ}, X, p, q) MM = MetricManifold(M, StiefelSubmersionMetric(-1 // 2)) log!(MM, X, p, q) return X diff --git a/src/manifolds/StiefelSubmersionMetric.jl b/src/manifolds/StiefelSubmersionMetric.jl index 4c0fbbe00e..601db4d684 100644 --- a/src/manifolds/StiefelSubmersionMetric.jl +++ b/src/manifolds/StiefelSubmersionMetric.jl @@ -22,8 +22,8 @@ struct StiefelSubmersionMetric{T<:Real} <: RiemannianMetric end @doc raw""" - q = exp(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, <:StiefelSubmersionMetric}, p, X) - exp!(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, q, <:StiefelSubmersionMetric}, p, X) + q = exp(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, X) + exp!(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, q, p, X) Compute the exponential map on the [`Stiefel(n,k)`](@ref) manifold with respect to the [`StiefelSubmersionMetric`](@ref). @@ -40,14 +40,10 @@ This implementation is based on [ZimmermannHueper:2022](@cite). For ``k < \frac{n}{2}`` the exponential is computed more efficiently using [`StiefelFactorization`](@ref). """ -exp(::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, ::Any...) where {n,k} +exp(::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, ::Any...) -function exp!( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, - q, - p, - X, -) where {n,k} +function exp!(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, q, p, X) + n, k = get_parameter(M.manifold.size) α = metric(M).α T = Base.promote_eltype(q, p, X) if k ≤ div(n, 2) @@ -82,7 +78,7 @@ function exp!( end @doc raw""" - inner(M::MetricManifold{ℝ, Stiefel{n,k,ℝ}, X, <:StiefelSubmersionMetric}, p, X, Y) + inner(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, X, Y) Compute the inner product on the [`Stiefel`](@ref) manifold with respect to the [`StiefelSubmersionMetric`](@ref). The formula reads @@ -91,12 +87,8 @@ g_p(X,Y) = \operatorname{tr}\bigl( X^{\mathrm{T}}(I_n - \frac{2α+1}{2(α+1)}pp^ ``` where ``α`` is the parameter of the metric. """ -function inner( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, - p, - X, - Y, -) where {n,k} +function inner(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, X, Y) + n, k = get_parameter(M.manifold.size) α = metric(M).α T = typeof(one(Base.promote_eltype(p, X, Y, α))) if n == k @@ -119,7 +111,7 @@ end @doc doc""" inverse_retract( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, q, method::ShootingInverseRetraction, @@ -130,7 +122,7 @@ Compute the inverse retraction using [`ShootingInverseRetraction`](https://julia In general the retraction is computed using the generic shooting method. inverse_retract( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, q, method::ShootingInverseRetraction{ @@ -153,7 +145,7 @@ inverse_retract( ) function inverse_retract_shooting!( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, X::AbstractMatrix, p::AbstractMatrix, q::AbstractMatrix, @@ -162,13 +154,14 @@ function inverse_retract_shooting!( ProjectionInverseRetraction, <:Union{ProjectionTransport,ScaledVectorTransport{ProjectionTransport}}, }, -) where {n,k} +) + n, k = get_parameter(M.manifold.size) if k > div(n, 2) # fall back to default method invoke( inverse_retract_shooting!, Tuple{ - MetricManifold{ℝ,Stiefel{n,k,ℝ}}, + MetricManifold{ℝ,typeof(M.manifold)}, typeof(X), typeof(p), typeof(q), @@ -192,7 +185,7 @@ function inverse_retract_shooting!( end @doc raw""" - log(M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, p, q; kwargs...) + log(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, q; kwargs...) Compute the logarithmic map on the [`Stiefel(n,k)`](@ref) manifold with respect to the [`StiefelSubmersionMetric`](@ref). @@ -205,13 +198,13 @@ that documentation for details. Their defaults are: - `max_iterations=1_000` """ function log( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, q; tolerance=sqrt(eps(float(real(Base.promote_eltype(p, q))))), max_iterations=1_000, num_transport_points=4, -) where {n,k} +) X = allocate_result(M, log, p, q) log!( M, @@ -225,14 +218,14 @@ function log( return X end function log!( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, X, p, q; tolerance=sqrt(eps(float(real(eltype(X))))), max_iterations=1_000, num_transport_points=4, -) where {n,k} +) retraction = ExponentialRetraction() initial_inverse_retraction = ProjectionInverseRetraction() vector_transport = ScaledVectorTransport(ProjectionTransport()) @@ -248,8 +241,8 @@ function log!( end @doc raw""" - Y = riemannian_Hessian(M::MetricManifold{ℝ,Stiefel{n,k,ℝ}, StiefelSubmersionMetric},, p, G, H, X) - riemannian_Hessian!(MetricManifold{ℝ,Stiefel{n,k,ℝ}, StiefelSubmersionMetric},, Y, p, G, H, X) + Y = riemannian_Hessian(M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},StiefelSubmersionMetric}, p, G, H, X) + riemannian_Hessian!(MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},StiefelSubmersionMetric}, Y, p, G, H, X) Compute the Riemannian Hessian ``\operatorname{Hess} f(p)[X]`` given the Euclidean gradient ``∇ f(\tilde p)`` in `G` and the Euclidean Hessian ``∇^2 f(\tilde p)[\tilde X]`` in `H`, @@ -271,21 +264,21 @@ where ``P = I-pp^{\mathrm{H}}``. Compared to Eq. (5.6) we have that their ``α_0 = 1``and ``\alpha_1 = \frac{2α+1}{2(α+1)} + 1``. """ riemannian_Hessian( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p, G, H, X, -) where {n,k} +) function riemannian_Hessian!( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, Y, p, G, H, X, -) where {n,k} +) α = metric(M).α Gp = symmetrize(G' * p) Z = symmetrize((I - p * p') * G * p') @@ -353,7 +346,7 @@ function stiefel_factorization(p, x) U = allocate(p, T, Size(n, 2k)) Z = allocate(p, T, Size(2k, k)) xfact = StiefelFactorization(U, Z) - @views begin + @views begin # COV_EXCL_LINE U1 = U[1:n, 1:k] U2 = U[1:n, (k + 1):(2k)] Z1 = Z[1:k, 1:k] @@ -416,40 +409,40 @@ function Base.copyto!( broadcast!(bc.f, dest.Z, Zargs...) return dest end -function project!( - ::Stiefel{n,k,ℝ}, - q::StiefelFactorization, - p::StiefelFactorization, -) where {n,k} +function project!(M::Stiefel{<:Any,ℝ}, q::StiefelFactorization, p::StiefelFactorization) + n, k = get_parameter(M.size) project!(Stiefel(2k, k), q.Z, p.Z) return q end function project!( - ::Stiefel{n,k,ℝ}, + M::Stiefel{<:Any,ℝ}, Y::StiefelFactorization, p::StiefelFactorization, X::StiefelFactorization, -) where {n,k} +) + n, k = get_parameter(M.size) project!(Stiefel(2k, k), Y.Z, p.Z, X.Z) return Y end function inner( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, p::StiefelFactorization, X::StiefelFactorization, Y::StiefelFactorization, -) where {n,k} +) + n, k = get_parameter(M.manifold.size) Msub = MetricManifold(Stiefel(2k, k), metric(M)) return inner(Msub, p.Z, X.Z, Y.Z) end function exp!( - M::MetricManifold{ℝ,Stiefel{n,k,ℝ},<:StiefelSubmersionMetric}, + M::MetricManifold{ℝ,<:Stiefel{<:Any,ℝ},<:StiefelSubmersionMetric}, q::StiefelFactorization, p::StiefelFactorization, X::StiefelFactorization, -) where {n,k} +) + n, k = get_parameter(M.manifold.size) α = metric(M).α - @views begin + @views begin # COV_EXCL_LINE ZM = X.Z[1:k, 1:k] ZN = X.Z[(k + 1):(2k), 1:k] qM = q.Z[1:k, 1:k] @@ -457,7 +450,7 @@ function exp!( qM .= ZM .* (α / (α + 1)) D = exp(qM) C = allocate(D, Size(2k, 2k)) - @views begin + @views begin # COV_EXCL_LINE C[1:k, 1:k] .= ZM ./ (α + 1) C[1:k, (k + 1):(2k)] .= -ZN' C[(k + 1):(2k), 1:k] .= ZN diff --git a/src/manifolds/Symmetric.jl b/src/manifolds/Symmetric.jl index 6bad4bcb9c..3d5a7832bd 100644 --- a/src/manifolds/Symmetric.jl +++ b/src/manifolds/Symmetric.jl @@ -13,7 +13,7 @@ and the field $𝔽 ∈ \{ ℝ, ℂ\}$. Though it is slightly redundant, usually the matrices are stored as $n × n$ arrays. Note that in this representation, the complex valued case has to have a real-valued diagonal, -which is also reflected in the [`manifold_dimension`](@ref manifold_dimension(::SymmetricMatrices{N,𝔽}) where {N,𝔽}). +which is also reflected in the [`manifold_dimension`](@ref manifold_dimension(::SymmetricMatrices)). # Constructor @@ -21,10 +21,13 @@ which is also reflected in the [`manifold_dimension`](@ref manifold_dimension(:: Generate the manifold of $n × n$ symmetric matrices. """ -struct SymmetricMatrices{n,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct SymmetricMatrices{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -function SymmetricMatrices(n::Int, field::AbstractNumbers=ℝ) - return SymmetricMatrices{n,field}() +function SymmetricMatrices(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return SymmetricMatrices{typeof(size),field}(size) end function active_traits(f, ::SymmetricMatrices, args...) @@ -48,7 +51,7 @@ whether `p` is a symmetric matrix of size `(n,n)` with values from the correspon The tolerance for the symmetry of `p` can be set using `kwargs...`. """ -function check_point(M::SymmetricMatrices{n,𝔽}, p; kwargs...) where {n,𝔽} +function check_point(M::SymmetricMatrices{<:Any,𝔽}, p; kwargs...) where {𝔽} if !isapprox(norm(p - p'), 0.0; kwargs...) return DomainError( norm(p - p'), @@ -67,7 +70,7 @@ and its values have to be from the correct [`AbstractNumbers`](https://juliamani The tolerance for the symmetry of `X` can be set using `kwargs...`. """ -function check_vector(M::SymmetricMatrices{n,𝔽}, p, X; kwargs...) where {n,𝔽} +function check_vector(M::SymmetricMatrices{<:Any,𝔽}, p, X; kwargs...) where {𝔽} if !isapprox(norm(X - X'), 0.0; kwargs...) return DomainError( norm(X - X'), @@ -86,13 +89,8 @@ function get_basis(M::SymmetricMatrices, p, B::DiagonalizingOrthonormalBasis) return CachedBasis(B, κ, Ξ) end -function get_coordinates_orthonormal!( - M::SymmetricMatrices{N,ℝ}, - Y, - p, - X, - ::RealNumbers, -) where {N} +function get_coordinates_orthonormal!(M::SymmetricMatrices{<:Any,ℝ}, Y, p, X, ::RealNumbers) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(Y) == (dim,) @assert size(X) == (N, N) @@ -106,12 +104,13 @@ function get_coordinates_orthonormal!( return Y end function get_coordinates_orthonormal!( - M::SymmetricMatrices{N,ℂ}, + M::SymmetricMatrices{<:Any,ℂ}, Y, p, X, ::ComplexNumbers, -) where {N} +) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(Y) == (dim,) @assert size(X) == (N, N) @@ -129,15 +128,16 @@ function get_coordinates_orthonormal!( return Y end -get_embedding(::SymmetricMatrices{N,𝔽}) where {N,𝔽} = Euclidean(N, N; field=𝔽) +function get_embedding(::SymmetricMatrices{TypeParameter{Tuple{N}},𝔽}) where {N,𝔽} + return Euclidean(N, N; field=𝔽) +end +function get_embedding(M::SymmetricMatrices{Tuple{Int},𝔽}) where {𝔽} + N = get_parameter(M.size)[1] + return Euclidean(N, N; field=𝔽, parameter=:field) +end -function get_vector_orthonormal!( - M::SymmetricMatrices{N,ℝ}, - Y, - p, - X, - ::RealNumbers, -) where {N} +function get_vector_orthonormal!(M::SymmetricMatrices{<:Any,ℝ}, Y, p, X, ::RealNumbers) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(X) == (dim,) @assert size(Y) == (N, N) @@ -150,13 +150,8 @@ function get_vector_orthonormal!( end return Y end -function get_vector_orthonormal!( - M::SymmetricMatrices{N,ℂ}, - Y, - p, - X, - ::ComplexNumbers, -) where {N} +function get_vector_orthonormal!(M::SymmetricMatrices{<:Any,ℂ}, Y, p, X, ::ComplexNumbers) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(X) == (dim,) @assert size(Y) == (N, N) @@ -193,7 +188,8 @@ Return the dimension of the [`SymmetricMatrices`](@ref) matrix `M` over the numb where the last $-n$ is due to the zero imaginary part for Hermitian matrices """ -function manifold_dimension(::SymmetricMatrices{N,𝔽}) where {N,𝔽} +function manifold_dimension(M::SymmetricMatrices{<:Any,𝔽}) where {𝔽} + N = get_parameter(M.size)[1] return div(N * (N + 1), 2) * real_dimension(𝔽) - (𝔽 === ℂ ? N : 0) end @@ -230,9 +226,13 @@ project(::SymmetricMatrices, ::Any, ::Any) project!(M::SymmetricMatrices, Y, p, X) = (Y .= (X .+ transpose(X)) ./ 2) -function Base.show(io::IO, ::SymmetricMatrices{n,F}) where {n,F} +function Base.show(io::IO, ::SymmetricMatrices{TypeParameter{Tuple{n}},F}) where {n,F} return print(io, "SymmetricMatrices($(n), $(F))") end +function Base.show(io::IO, M::SymmetricMatrices{Tuple{Int},F}) where {F} + n = get_parameter(M.size)[1] + return print(io, "SymmetricMatrices($(n), $(F); parameter=:field)") +end @doc raw""" Y = Weingarten(M::SymmetricMatrices, p, X, V) diff --git a/src/manifolds/SymmetricPositiveDefinite.jl b/src/manifolds/SymmetricPositiveDefinite.jl index dd0beb3ebd..48d367cebd 100644 --- a/src/manifolds/SymmetricPositiveDefinite.jl +++ b/src/manifolds/SymmetricPositiveDefinite.jl @@ -1,5 +1,5 @@ @doc raw""" - SymmetricPositiveDefinite{N} <: AbstractDecoratorManifold{ℝ} + SymmetricPositiveDefinite{T} <: AbstractDecoratorManifold{ℝ} The manifold of symmetric positive definite matrices, i.e. @@ -22,13 +22,18 @@ i.e. the set of symmetric matrices, # Constructor - SymmetricPositiveDefinite(n) + SymmetricPositiveDefinite(n; parameter::Symbol=:type) generates the manifold $\mathcal P(n) \subset ℝ^{n × n}$ """ -struct SymmetricPositiveDefinite{N} <: AbstractDecoratorManifold{ℝ} end +struct SymmetricPositiveDefinite{T} <: AbstractDecoratorManifold{ℝ} + size::T +end -SymmetricPositiveDefinite(n::Int) = SymmetricPositiveDefinite{n}() +function SymmetricPositiveDefinite(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return SymmetricPositiveDefinite{typeof(size)}(size) +end @doc raw""" SPDPoint <: AbstractManifoldsPoint @@ -131,7 +136,7 @@ checks, whether `p` is a valid point on the [`SymmetricPositiveDefinite`](@ref) of size `(N,N)`, symmetric and positive definite. The tolerance for the second to last test can be set using the `kwargs...`. """ -function check_point(M::SymmetricPositiveDefinite{N}, p; kwargs...) where {N} +function check_point(M::SymmetricPositiveDefinite, p; kwargs...) if !isapprox(norm(p - transpose(p)), 0.0; kwargs...) return DomainError( norm(p - transpose(p)), @@ -159,7 +164,7 @@ and a symmetric matrix, i.e. this stores tangent vetors as elements of the corre Lie group. The tolerance for the last test can be set using the `kwargs...`. """ -function check_vector(M::SymmetricPositiveDefinite{N}, p, X; kwargs...) where {N} +function check_vector(M::SymmetricPositiveDefinite, p, X; kwargs...) if !isapprox(norm(X - transpose(X)), 0.0; kwargs...) return DomainError( X, @@ -223,8 +228,12 @@ embed(::SymmetricPositiveDefinite, p) = p embed(::SymmetricPositiveDefinite, p::SPDPoint) = convert(AbstractMatrix, p) embed(::SymmetricPositiveDefinite, p, X) = X -function get_embedding(M::SymmetricPositiveDefinite) - return Euclidean(representation_size(M)...; field=ℝ) +function get_embedding(::SymmetricPositiveDefinite{TypeParameter{Tuple{n}}}) where {n} + return Euclidean(n, n; field=ℝ) +end +function get_embedding(M::SymmetricPositiveDefinite{Tuple{Int}}) + n = get_parameter(M.size)[1] + return Euclidean(n, n; field=ℝ, parameter=:field) end @doc raw""" @@ -261,8 +270,9 @@ returns the dimension of \dim \mathcal P(n) = \frac{n(n+1)}{2}. ```` """ -@generated function manifold_dimension(::SymmetricPositiveDefinite{N}) where {N} - return div(N * (N + 1), 2) +function manifold_dimension(M::SymmetricPositiveDefinite) + n = get_parameter(M.size)[1] + return div(n * (n + 1), 2) end """ @@ -409,13 +419,14 @@ end function Random.rand!( rng::AbstractRNG, - M::SymmetricPositiveDefinite{N}, + M::SymmetricPositiveDefinite, pX; vector_at=nothing, σ::Real=one(eltype(pX)) / (vector_at === nothing ? 1 : norm(convert(AbstractMatrix, vector_at))), tangent_distr=:Gaussian, -) where {N} +) + N = get_parameter(M.size)[1] if vector_at === nothing D = Diagonal(1 .+ rand(rng, N)) # random diagonal matrix s = qr(σ * randn(rng, N, N)) # random q @@ -458,10 +469,17 @@ Return the size of an array representing an element on the [`SymmetricPositiveDefinite`](@ref) manifold `M`, i.e. $n × n$, the size of such a symmetric positive definite matrix on $\mathcal M = \mathcal P(n)$. """ -@generated representation_size(::SymmetricPositiveDefinite{N}) where {N} = (N, N) +function representation_size(M::SymmetricPositiveDefinite) + N = get_parameter(M.size)[1] + return (N, N) +end -function Base.show(io::IO, ::SymmetricPositiveDefinite{N}) where {N} - return print(io, "SymmetricPositiveDefinite($(N))") +function Base.show(io::IO, ::SymmetricPositiveDefinite{TypeParameter{Tuple{n}}}) where {n} + return print(io, "SymmetricPositiveDefinite($(n))") +end +function Base.show(io::IO, M::SymmetricPositiveDefinite{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "SymmetricPositiveDefinite($(n); parameter=:field)") end function Base.show(io::IO, ::MIME"text/plain", p::SPDPoint) @@ -493,4 +511,4 @@ function zero_vector(M::SymmetricPositiveDefinite, p::SPDPoint) return zero_vector(M, convert(AbstractMatrix, p)) end -zero_vector!(::SymmetricPositiveDefinite{N}, X, ::Any) where {N} = fill!(X, 0) +zero_vector!(::SymmetricPositiveDefinite, X, ::Any) = fill!(X, 0) diff --git a/src/manifolds/SymmetricPositiveDefiniteAffineInvariant.jl b/src/manifolds/SymmetricPositiveDefiniteAffineInvariant.jl index dde2356aef..126b40c1e2 100644 --- a/src/manifolds/SymmetricPositiveDefiniteAffineInvariant.jl +++ b/src/manifolds/SymmetricPositiveDefiniteAffineInvariant.jl @@ -31,7 +31,7 @@ function change_representer!(::SymmetricPositiveDefinite, Y, ::EuclideanMetric, end @doc raw""" - change_metric(M::SymmetricPositiveDefinite{n}, E::EuclideanMetric, p, X) + change_metric(M::SymmetricPositiveDefinite, E::EuclideanMetric, p, X) Given a tangent vector ``X ∈ T_p\mathcal P(n)`` with respect to the [`EuclideanMetric`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.EuclideanMetric) `g_E`, this function changes into the [`AffineInvariantMetric`](@ref) (default) metric on the @@ -67,7 +67,7 @@ d_{\mathcal P(n)}(p,q) where $\operatorname{Log}$ denotes the matrix logarithm and $\lVert\cdot\rVert_{\mathrm{F}}$ denotes the matrix Frobenius norm. """ -function distance(::SymmetricPositiveDefinite{N}, p, q) where {N} +function distance(::SymmetricPositiveDefinite, p, q) # avoid numerical instabilities in cholesky norm(p - q) < eps(eltype(p + q)) && return zero(eltype(p + q)) cq = cholesky(Symmetric(q)) # to avoid numerical inaccuracies @@ -80,7 +80,7 @@ end @doc raw""" exp(M::SymmetricPositiveDefinite, p, X) - exp(M::MetricManifold{SymmetricPositiveDefinite{N},AffineInvariantMetric}, p, X) + exp(M::MetricManifold{<:SymmetricPositiveDefinite,AffineInvariantMetric}, p, X) Compute the exponential map from `p` with tangent vector `X` on the [`SymmetricPositiveDefinite`](@ref) `M` with its default [`MetricManifold`](@ref) having the @@ -95,7 +95,7 @@ where $\operatorname{Exp}$ denotes to the matrix exponential. exp(::SymmetricPositiveDefinite, ::Any...) exp(M::SymmetricPositiveDefinite, p::SPDPoint, X, t::Number) = exp(M, p, t * X) -function exp(::SymmetricPositiveDefinite{N}, p::SPDPoint, X) where {N} +function exp(::SymmetricPositiveDefinite, p::SPDPoint, X) (p_sqrt, p_sqrt_inv) = spd_sqrt_and_sqrt_inv(p) T = Symmetric(p_sqrt_inv * X * p_sqrt_inv) eig1 = eigen(T) # numerical stabilization @@ -114,7 +114,7 @@ end function exp!(M::SymmetricPositiveDefinite, q, p, X, t::Number) return exp!(M, q, p, t * X) end -function exp!(::SymmetricPositiveDefinite{N}, q, p, X) where {N} +function exp!(::SymmetricPositiveDefinite, q, p, X) (p_sqrt, p_sqrt_inv) = spd_sqrt_and_sqrt_inv(p) T = Symmetric(p_sqrt_inv * X * p_sqrt_inv) eig1 = eigen(T) # numerical stabilization @@ -123,7 +123,7 @@ function exp!(::SymmetricPositiveDefinite{N}, q, p, X) where {N} pUe = p_sqrt * Ue return copyto!(q, pUe * Se * transpose(pUe)) end -function exp!(::SymmetricPositiveDefinite{N}, q::SPDPoint, p, X) where {N} +function exp!(::SymmetricPositiveDefinite, q::SPDPoint, p, X) (p_sqrt, p_sqrt_inv) = spd_sqrt_and_sqrt_inv(p) T = Symmetric(p_sqrt_inv * X * p_sqrt_inv) eig1 = eigen(T) # numerical stabilization @@ -146,7 +146,7 @@ end @doc raw""" [Ξ,κ] = get_basis_diagonalizing(M::SymmetricPositiveDefinite, p, B::DiagonalizingOrthonormalBasis) - [Ξ,κ] = get_basis_diagonalizing(M::MetricManifold{SymmetricPositiveDefinite{N},AffineInvariantMetric}, p, B::DiagonalizingOrthonormalBasis) + [Ξ,κ] = get_basis_diagonalizing(M::MetricManifold{<:SymmetricPositiveDefinite,AffineInvariantMetric}, p, B::DiagonalizingOrthonormalBasis) Return a orthonormal basis `Ξ` as a vector of tangent vectors (of length [`manifold_dimension`](@ref) of `M`) in the tangent space of `p` on the @@ -158,10 +158,11 @@ The construction is based on an ONB for the symmetric matrices similar to [`get_ just that the ONB here is build from the eigen vectors of ``p^{\frac{1}{2}}Vp^{\frac{1}{2}}``. """ function get_basis_diagonalizing( - ::SymmetricPositiveDefinite{N}, + M::SymmetricPositiveDefinite, p, B::DiagonalizingOrthonormalBasis, -) where {N} +) + N = get_parameter(M.size)[1] (p_sqrt, p_sqrt_inv) = spd_sqrt_and_sqrt_inv(p) eigv = eigen(Symmetric(p_sqrt_inv * B.frame_direction * p_sqrt_inv)) V = eigv.vectors @@ -178,7 +179,7 @@ end @doc raw""" [Ξ,κ] = get_basis(M::SymmetricPositiveDefinite, p, B::DefaultOrthonormalBasis) - [Ξ,κ] = get_basis(M::MetricManifold{SymmetricPositiveDefinite{N},AffineInvariantMetric}, p, B::DefaultOrthonormalBasis) + [Ξ,κ] = get_basis(M::MetricManifold{<:SymmetricPositiveDefinite,AffineInvariantMetric}, p, B::DefaultOrthonormalBasis) Return a default ONB for the tangent space ``T_p\mathcal P(n)`` of the [`SymmetricPositiveDefinite`](@ref) with respect to the [`AffineInvariantMetric`](@ref). @@ -208,11 +209,8 @@ We then form the ONB by """ get_basis(::SymmetricPositiveDefinite, p, B::DefaultOrthonormalBasis) -function get_basis_orthonormal( - M::SymmetricPositiveDefinite{N}, - p, - Ns::RealNumbers, -) where {N} +function get_basis_orthonormal(M::SymmetricPositiveDefinite, p, Ns::RealNumbers) + N = get_parameter(M.size)[1] p_sqrt = spd_sqrt(p) Ξ = [similar(convert(AbstractMatrix, p)) for _ in 1:manifold_dimension(M)] k = 1 @@ -240,13 +238,8 @@ where $k$ is trhe linearized index of the $i=1,\ldots,n, j=i,\ldots,n$. """ get_coordinates(::SymmetricPositiveDefinite, c, p, X, ::DefaultOrthonormalBasis) -function get_coordinates_orthonormal!( - M::SymmetricPositiveDefinite{N}, - c, - p, - X, - ::RealNumbers, -) where {N} +function get_coordinates_orthonormal!(M::SymmetricPositiveDefinite, c, p, X, ::RealNumbers) + N = get_parameter(M.size)[1] dim = manifold_dimension(M) @assert size(c) == (dim,) @assert size(X) == (N, N) @@ -283,13 +276,8 @@ where $k$ is the linearized index of the $i=1,\ldots,n, j=i,\ldots,n$. """ get_vector(::SymmetricPositiveDefinite, X, p, c, ::DefaultOrthonormalBasis) -function get_vector_orthonormal!( - ::SymmetricPositiveDefinite{N}, - X, - p, - c, - ::RealNumbers, -) where {N} +function get_vector_orthonormal!(M::SymmetricPositiveDefinite, X, p, c, ::RealNumbers) + N = get_parameter(M.size)[1] @assert size(c) == (div(N * (N + 1), 2),) @assert size(X) == (N, N) p_sqrt = spd_sqrt(p) @@ -359,7 +347,7 @@ function allocate_result( return allocate_result(M, log, convert(AbstractMatrix, q), convert(AbstractMatrix, p)) end -function log!(::SymmetricPositiveDefinite{N}, X, p, q) where {N} +function log!(::SymmetricPositiveDefinite, X, p, q) (p_sqrt, p_sqrt_inv) = spd_sqrt_and_sqrt_inv(p) T = Symmetric(p_sqrt_inv * convert(AbstractMatrix, q) * p_sqrt_inv) e2 = eigen(T) @@ -402,7 +390,7 @@ and `log` the logarithmic map on [`SymmetricPositiveDefinite`](@ref) """ parallel_transport_to(::SymmetricPositiveDefinite, ::Any, ::Any, ::Any) -function parallel_transport_to!(M::SymmetricPositiveDefinite{N}, Y, p, X, q) where {N} +function parallel_transport_to!(M::SymmetricPositiveDefinite, Y, p, X, q) distance(M, p, q) < 2 * eps(eltype(p)) && copyto!(Y, X) (p_sqrt, p_sqrt_inv) = spd_sqrt_and_sqrt_inv(p) tv = Symmetric(p_sqrt_inv * X * p_sqrt_inv) # p^(-1/2)Xp^{-1/2} @@ -467,13 +455,13 @@ function riemann_tensor!(::SymmetricPositiveDefinite, Xresult, p, X, Y, Z) end """ - volume_density(::SymmetricPositiveDefinite{n}, p, X) where {n} + volume_density(::SymmetricPositiveDefinite, p, X) Compute the volume density of the [`SymmetricPositiveDefinite`](@ref) manifold at `p` in direction `X`. See [ChevallierKalungaAngulo:2017](@cite), Section 6.2 for details. Note that metric in Manifolds.jl has a different scaling factor than the reference. """ -function volume_density(::SymmetricPositiveDefinite{n}, p, X) where {n} +function volume_density(::SymmetricPositiveDefinite, p, X) eig = eigvals(X) dens = 1.0 for i in 1:length(eig) diff --git a/src/manifolds/SymmetricPositiveDefiniteBuresWasserstein.jl b/src/manifolds/SymmetricPositiveDefiniteBuresWasserstein.jl index 95e06819bb..84732f071f 100644 --- a/src/manifolds/SymmetricPositiveDefiniteBuresWasserstein.jl +++ b/src/manifolds/SymmetricPositiveDefiniteBuresWasserstein.jl @@ -6,7 +6,7 @@ The Bures Wasserstein metric for symmetric positive definite matrices [MalagoMon struct BuresWassersteinMetric <: RiemannianMetric end @doc raw""" - change_representer(M::MetricManifold{ℝ,SymmetricPositiveDefinite,BuresWassersteinMetric}, E::EuclideanMetric, p, X) + change_representer(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,BuresWassersteinMetric}, E::EuclideanMetric, p, X) Given a tangent vector ``X ∈ T_p\mathcal M`` representing a linear function on the tangent space at `p` with respect to the [`EuclideanMetric`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.EuclideanMetric) @@ -23,7 +23,7 @@ for all ``Y`` and hence we get ``Z``= 2(A+A^{\mathrm{T}})`` with ``A=Xp``. """ change_representer( - ::MetricManifold{ℝ,SymmetricPositiveDefinite,BuresWassersteinMetric}, + ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,BuresWassersteinMetric}, ::EuclideanMetric, p, X, @@ -73,7 +73,7 @@ the [`BuresWassersteinMetric`](@ref) given by where ``q=L_p(X)`` denotes the Lyapunov operator, i.e. it solves ``pq + qp = X``. """ -exp(::MetricManifold{ℝ,SymmetricPositiveDefinite,BuresWassersteinMetric}, p, X) +exp(::MetricManifold{ℝ,<:SymmetricPositiveDefinite,BuresWassersteinMetric}, p, X) function exp!( ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,BuresWassersteinMetric}, @@ -127,7 +127,7 @@ the [`BuresWassersteinMetric`](@ref) given by where ``q=L_p(X)`` denotes the Lyapunov operator, i.e. it solves ``pq + qp = X``. """ -log(::MetricManifold{ℝ,SymmetricPositiveDefinite,BuresWassersteinMetric}, p, q) +log(::MetricManifold{ℝ,<:SymmetricPositiveDefinite,BuresWassersteinMetric}, p, q) function log!( ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,BuresWassersteinMetric}, diff --git a/src/manifolds/SymmetricPositiveDefiniteGeneralizedBuresWasserstein.jl b/src/manifolds/SymmetricPositiveDefiniteGeneralizedBuresWasserstein.jl index b710406fa3..262654a864 100644 --- a/src/manifolds/SymmetricPositiveDefiniteGeneralizedBuresWasserstein.jl +++ b/src/manifolds/SymmetricPositiveDefiniteGeneralizedBuresWasserstein.jl @@ -12,7 +12,7 @@ struct GeneralizedBuresWassersteinMetric{T<:AbstractMatrix} <: RiemannianMetric end @doc raw""" - change_representer(M::MetricManifold{ℝ,SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, E::EuclideanMetric, p, X) + change_representer(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, E::EuclideanMetric, p, X) Given a tangent vector ``X ∈ T_p\mathcal M`` representing a linear function on the tangent space at `p` with respect to the [`EuclideanMetric`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/manifolds.html#ManifoldsBase.EuclideanMetric) @@ -28,7 +28,7 @@ it holds for all ``Y`` and hence we get ``Z = 2pXM + 2MXp``. """ change_representer( - ::MetricManifold{ℝ,SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, + ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, ::EuclideanMetric, p, X, @@ -67,7 +67,7 @@ function distance( end @doc raw""" - exp(::MatricManifold{ℝ,SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, p, X) + exp(::MatricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, p, X) Compute the exponential map on [`SymmetricPositiveDefinite`](@ref) with respect to the [`GeneralizedBuresWassersteinMetric`](@ref) given by @@ -78,7 +78,11 @@ the [`GeneralizedBuresWassersteinMetric`](@ref) given by where ``q=L_{M,p}(X)`` denotes the generalized Lyapunov operator, i.e. it solves ``pqM + Mqp = X``. """ -exp(::MetricManifold{ℝ,SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, p, X) +exp( + ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, + p, + X, +) function exp!( M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, @@ -95,7 +99,7 @@ function exp!( end @doc raw""" - inner(::MetricManifold{ℝ,SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, p, X, Y) + inner(::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, p, X, Y) Compute the inner product [`SymmetricPositiveDefinite`](@ref) with respect to the [`GeneralizedBuresWassersteinMetric`](@ref) given by @@ -122,13 +126,13 @@ Return false. [`SymmetricPositiveDefinite`](@ref) with [`GeneralizedBuresWassers is not a flat manifold. """ function is_flat( - M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, + ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, ) return false end @doc raw""" - log(::MatricManifold{SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, p, q) + log(::MatricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, p, q) Compute the logarithmic map on [`SymmetricPositiveDefinite`](@ref) with respect to the [`BuresWassersteinMetric`](@ref) given by @@ -137,7 +141,11 @@ the [`BuresWassersteinMetric`](@ref) given by \log_p(q) = M(M^{-1}pM^{-1}q)^{\frac{1}{2}} + (qM^{-1}pM^{-1})^{\frac{1}{2}}M - 2 p. ``` """ -log(::MetricManifold{ℝ,SymmetricPositiveDefinite,GeneralizedBuresWassersteinMetric}, p, q) +log( + ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, + p, + q, +) function log!( M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,<:GeneralizedBuresWassersteinMetric}, diff --git a/src/manifolds/SymmetricPositiveDefiniteLogCholesky.jl b/src/manifolds/SymmetricPositiveDefiniteLogCholesky.jl index 703dc0b241..25c7d22d42 100644 --- a/src/manifolds/SymmetricPositiveDefiniteLogCholesky.jl +++ b/src/manifolds/SymmetricPositiveDefiniteLogCholesky.jl @@ -35,12 +35,9 @@ where $x$ and $y$ are the cholesky factors of $p$ and $q$, respectively, $⌊\cdot⌋$ denbotes the strictly lower triangular matrix of its argument, and $\lVert\cdot\rVert_{\mathrm{F}}$ the Frobenius norm. """ -function distance( - ::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogCholeskyMetric}, - p, - q, -) where {N} - return distance(CholeskySpace{N}(), cholesky(p).L, cholesky(q).L) +function distance(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, p, q) + N = get_parameter(M.manifold.size)[1] + return distance(CholeskySpace(N), cholesky(p).L, cholesky(q).L) end @doc raw""" @@ -60,28 +57,24 @@ denotes the lower triangular matrix with the diagonal multiplied by $\frac{1}{2} """ exp(::MetricManifold{ℝ,SymmetricPositiveDefinite,LogCholeskyMetric}, ::Any...) -function exp!( - ::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogCholeskyMetric}, - q, - p, - X, -) where {N} +function exp!(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, q, p, X) + N = get_parameter(M.manifold.size)[1] (y, W) = spd_to_cholesky(p, X) - z = exp(CholeskySpace{N}(), y, W) + z = exp(CholeskySpace(N), y, W) return copyto!(q, z * z') end function exp!( - M::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogCholeskyMetric}, + M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, q, p, X, t::Number, -) where {N} +) return exp!(M, q, p, t * X) end @doc raw""" - inner(M::MetricManifold{LogCholeskyMetric,ℝ,SymmetricPositiveDefinite}, p, X, Y) + inner(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, p, X, Y) Compute the inner product of two matrices `X`, `Y` in the tangent space of `p` on the [`SymmetricPositiveDefinite`](@ref) manifold `M`, as @@ -96,15 +89,11 @@ $z$ is the cholesky factor of $p$, $a_z(W) = z (z^{-1}Wz^{-\mathrm{T}})_{\frac{1}{2}}$, and $(\cdot)_\frac{1}{2}$ denotes the lower triangular matrix with the diagonal multiplied by $\frac{1}{2}$ """ -function inner( - ::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogCholeskyMetric}, - p, - X, - Y, -) where {N} +function inner(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, p, X, Y) + N = get_parameter(M.manifold.size)[1] (z, Xz) = spd_to_cholesky(p, X) (z, Yz) = spd_to_cholesky(p, z, Y) - return inner(CholeskySpace{N}(), z, Xz, Yz) + return inner(CholeskySpace(N), z, Xz, Yz) end """ @@ -116,7 +105,7 @@ is not a flat manifold. is_flat(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}) = false @doc raw""" - log(M::MetricManifold{SymmetricPositiveDefinite,LogCholeskyMetric}, p, q) + log(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, p, q) Compute the logarithmic map on [`SymmetricPositiveDefinite`](@ref) `M` with respect to the [`LogCholeskyMetric`](@ref) emanating from `p` to `q`. @@ -129,21 +118,17 @@ of $q$ and the just mentioned logarithmic map is the one on [`CholeskySpace`](@r """ log(::MetricManifold{ℝ,SymmetricPositiveDefinite,LogCholeskyMetric}, ::Any...) -function log!( - ::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogCholeskyMetric}, - X, - p, - q, -) where {N} +function log!(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, X, p, q) + N = get_parameter(M.manifold.size)[1] x = cholesky(p).L y = cholesky(q).L - log!(CholeskySpace{N}(), X, x, y) + log!(CholeskySpace(N), X, x, y) return tangent_cholesky_to_tangent_spd!(x, X) end @doc raw""" vector_transport_to( - M::MetricManifold{SymmetricPositiveDefinite,LogCholeskyMetric}, + M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, p, X, q, @@ -163,21 +148,22 @@ transport on [`CholeskySpace`](@ref) from $x$ to $y$. The formula hear reads ```` """ parallel_transport_to( - ::MetricManifold{ℝ,SymmetricPositiveDefinite,LogCholeskyMetric}, + ::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, ::Any, ::Any, ::Any, ) function parallel_transport_to!( - ::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogCholeskyMetric}, + M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogCholeskyMetric}, Y, p, X, q, -) where {N} +) + N = get_parameter(M.manifold.size)[1] y = cholesky(q).L (x, W) = spd_to_cholesky(p, X) - parallel_transport_to!(CholeskySpace{N}(), Y, x, W, y) + parallel_transport_to!(CholeskySpace(N), Y, x, W, y) return tangent_cholesky_to_tangent_spd!(y, Y) end diff --git a/src/manifolds/SymmetricPositiveDefiniteLogEuclidean.jl b/src/manifolds/SymmetricPositiveDefiniteLogEuclidean.jl index 579facd4cd..ad579dffc1 100644 --- a/src/manifolds/SymmetricPositiveDefiniteLogEuclidean.jl +++ b/src/manifolds/SymmetricPositiveDefiniteLogEuclidean.jl @@ -7,7 +7,7 @@ into the Lie Algebra, i.e. performing a matrix logarithm beforehand. struct LogEuclideanMetric <: RiemannianMetric end @doc raw""" - distance(M::MetricManifold{SymmetricPositiveDefinite{N},LogEuclideanMetric}, p, q) + distance(M::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogEuclideanMetric}, p, q) Compute the distance on the [`SymmetricPositiveDefinite`](@ref) manifold between `p` and `q` as a [`MetricManifold`](@ref) with [`LogEuclideanMetric`](@ref). @@ -20,11 +20,7 @@ The formula reads where $\operatorname{Log}$ denotes the matrix logarithm and $\lVert\cdot\rVert_{\mathrm{F}}$ denotes the matrix Frobenius norm. """ -function distance( - ::MetricManifold{ℝ,SymmetricPositiveDefinite{N},LogEuclideanMetric}, - p, - q, -) where {N} +function distance(::MetricManifold{ℝ,<:SymmetricPositiveDefinite,LogEuclideanMetric}, p, q) return norm(log(Symmetric(p)) - log(Symmetric(q))) end diff --git a/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl b/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl index c1232f4d5b..9be8593caf 100644 --- a/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl +++ b/src/manifolds/SymmetricPositiveSemidefiniteFixedRank.jl @@ -1,5 +1,5 @@ @doc raw""" - SymmetricPositiveSemidefiniteFixedRank{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} + SymmetricPositiveSemidefiniteFixedRank{T,𝔽} <: AbstractDecoratorManifold{𝔽} The [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) $ \operatorname{SPS}_k(n)$ consisting of the real- or complex-valued symmetric positive semidefinite matrices of size $n × n$ and rank $k$, i.e. the set @@ -35,15 +35,23 @@ The metric was used in [JourneeBachAbsilSepulchre:2010](@cite)[MassartAbsil:2020 # Constructor - SymmetricPositiveSemidefiniteFixedRank(n::Int, k::Int, field::AbstractNumbers=ℝ) + SymmetricPositiveSemidefiniteFixedRank(n::Int, k::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) Generate the manifold of $n × n$ symmetric positive semidefinite matrices of rank $k$ over the `field` of real numbers `ℝ` or complex numbers `ℂ`. """ -struct SymmetricPositiveSemidefiniteFixedRank{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct SymmetricPositiveSemidefiniteFixedRank{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -function SymmetricPositiveSemidefiniteFixedRank(n::Int, k::Int, field::AbstractNumbers=ℝ) - return SymmetricPositiveSemidefiniteFixedRank{n,k,field}() +function SymmetricPositiveSemidefiniteFixedRank( + n::Int, + k::Int, + field::AbstractNumbers=ℝ; + parameter::Symbol=:type, +) + size = wrap_type_parameter(parameter, (n, k)) + return SymmetricPositiveSemidefiniteFixedRank{typeof(size),field}(size) end function active_traits(f, ::SymmetricPositiveSemidefiniteFixedRank, args...) @@ -51,7 +59,7 @@ function active_traits(f, ::SymmetricPositiveSemidefiniteFixedRank, args...) end @doc raw""" - check_point(M::SymmetricPositiveSemidefiniteFixedRank{n,𝔽}, q; kwargs...) + check_point(M::SymmetricPositiveSemidefiniteFixedRank, q; kwargs...) Check whether `q` is a valid manifold point on the [`SymmetricPositiveSemidefiniteFixedRank`](@ref) `M`, i.e. whether `p=q*q'` is a symmetric matrix of size `(n,n)` with values from the corresponding @@ -59,11 +67,8 @@ whether `p=q*q'` is a symmetric matrix of size `(n,n)` with values from the corr The symmetry of `p` is not explicitly checked since by using `q` p is symmetric by construction. The tolerance for the symmetry of `p` can and the rank of `q*q'` be set using `kwargs...`. """ -function check_point( - M::SymmetricPositiveSemidefiniteFixedRank{n,k,𝔽}, - q; - kwargs..., -) where {n,k,𝔽} +function check_point(M::SymmetricPositiveSemidefiniteFixedRank, q; kwargs...) + n, k = get_parameter(M.size) p = q * q' r = rank(p * p'; kwargs...) if r < k @@ -76,7 +81,7 @@ function check_point( end """ - check_vector(M::SymmetricPositiveSemidefiniteFixedRank{n,k,𝔽}, p, X; kwargs... ) + check_vector(M::SymmetricPositiveSemidefiniteFixedRank, p, X; kwargs... ) Check whether `X` is a tangent vector to manifold point `p` on the [`SymmetricPositiveSemidefiniteFixedRank`](@ref) `M`, i.e. `X` has to be a symmetric matrix of size `(n,n)` @@ -86,8 +91,16 @@ Due to the reduced representation this is fulfilled as soon as the matrix is of """ check_vector(M::SymmetricPositiveSemidefiniteFixedRank, q, Y; kwargs...) -function get_embedding(::SymmetricPositiveSemidefiniteFixedRank{N,K,𝔽}) where {N,K,𝔽} - return Euclidean(N, K; field=𝔽) +function get_embedding( + ::SymmetricPositiveSemidefiniteFixedRank{TypeParameter{Tuple{n,k}},𝔽}, +) where {n,k,𝔽} + return Euclidean(n, k; field=𝔽) +end +function get_embedding( + M::SymmetricPositiveSemidefiniteFixedRank{Tuple{Int,Int},𝔽}, +) where {𝔽} + n, k = get_parameter(M.size) + return Euclidean(n, k; field=𝔽, parameter=:field) end @doc raw""" @@ -172,7 +185,7 @@ function log!(::SymmetricPositiveSemidefiniteFixedRank, Z, q, p) end @doc raw""" - manifold_dimension(M::SymmetricPositiveSemidefiniteFixedRank{n,k,𝔽}) + manifold_dimension(M::SymmetricPositiveSemidefiniteFixedRank) Return the dimension of the [`SymmetricPositiveSemidefiniteFixedRank`](@ref) matrix `M` over the number system `𝔽`, i.e. @@ -188,15 +201,13 @@ where the last $k^2$ is due to the zero imaginary part for Hermitian matrices di """ manifold_dimension(::SymmetricPositiveSemidefiniteFixedRank) -@generated function manifold_dimension( - ::SymmetricPositiveSemidefiniteFixedRank{N,K,ℝ}, -) where {N,K} - return K * N - div(K * (K - 1), 2) +function manifold_dimension(M::SymmetricPositiveSemidefiniteFixedRank{<:Any,ℝ}) + n, k = get_parameter(M.size) + return k * n - div(k * (k - 1), 2) end -@generated function manifold_dimension( - ::SymmetricPositiveSemidefiniteFixedRank{N,K,ℂ}, -) where {N,K} - return 2 * K * N - K * K +function manifold_dimension(M::SymmetricPositiveSemidefiniteFixedRank{<:Any,ℂ}) + n, k = get_parameter(M.size) + return 2 * k * n - k * k end function project!(::SymmetricPositiveSemidefiniteFixedRank, Z, q, Y) @@ -204,8 +215,21 @@ function project!(::SymmetricPositiveSemidefiniteFixedRank, Z, q, Y) return Z end -function Base.show(io::IO, ::SymmetricPositiveSemidefiniteFixedRank{n,k,F}) where {n,k,F} - return print(io, "SymmetricPositiveSemidefiniteFixedRank($(n), $(k), $(F))") +function Base.show( + io::IO, + ::SymmetricPositiveSemidefiniteFixedRank{TypeParameter{Tuple{n,k}},𝔽}, +) where {n,k,𝔽} + return print(io, "SymmetricPositiveSemidefiniteFixedRank($(n), $(k), $(𝔽))") +end +function Base.show( + io::IO, + M::SymmetricPositiveSemidefiniteFixedRank{Tuple{Int,Int},𝔽}, +) where {𝔽} + n, k = get_parameter(M.size) + return print( + io, + "SymmetricPositiveSemidefiniteFixedRank($(n), $(k), $(𝔽); parameter=:field)", + ) end """ diff --git a/src/manifolds/Symplectic.jl b/src/manifolds/Symplectic.jl index 7de358593d..23cfeca206 100644 --- a/src/manifolds/Symplectic.jl +++ b/src/manifolds/Symplectic.jl @@ -1,5 +1,5 @@ @doc raw""" - Symplectic{n, 𝔽} <: AbstractEmbeddedManifold{𝔽, DefaultIsometricEmbeddingType} + Symplectic{T, 𝔽} <: AbstractEmbeddedManifold{𝔽, DefaultIsometricEmbeddingType} The symplectic manifold consists of all ``2n \times 2n`` matrices which preserve the canonical symplectic form over ``𝔽^{2n × 2n} \times 𝔽^{2n × 2n}``, @@ -32,22 +32,26 @@ The tangent space at a point ``p`` is given by [BendokatZimmermann:2021](@cite) ```` # Constructor - Symplectic(2n, field=ℝ) -> Symplectic{div(2n, 2), field}() + + Symplectic(2n, field=ℝ; parameter::Symbol=:type) Generate the (real-valued) symplectic manifold of ``2n \times 2n`` symplectic matrices. The constructor for the [`Symplectic`](@ref) manifold accepts the even column/row embedding dimension ``2n`` for the real symplectic manifold, ``ℝ^{2n × 2n}``. """ -struct Symplectic{n,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct Symplectic{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end function active_traits(f, ::Symplectic, args...) return merge_traits(IsEmbeddedManifold(), IsDefaultMetric(RealSymplecticMetric())) end -function Symplectic(n::Int, field::AbstractNumbers=ℝ) +function Symplectic(n::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) n % 2 == 0 || throw(ArgumentError("The dimension of the symplectic manifold embedding space must be even. Was odd, n % 2 == $(n % 2).")) - return Symplectic{div(n, 2),field}() + size = wrap_type_parameter(parameter, (div(n, 2),)) + return Symplectic{typeof(size),field}(size) end @doc raw""" @@ -148,9 +152,9 @@ function change_representer!(::Symplectic, Y, ::EuclideanMetric, p, X) end @doc raw""" - change_representer(MetMan::MetricManifold{𝔽, Euclidean{Tuple{m, n}, 𝔽}, ExtendedSymplecticMetric}, + change_representer(MetMan::MetricManifold{<:Any, <:Euclidean, ExtendedSymplecticMetric}, EucMet::EuclideanMetric, p, X) - change_representer!(MetMan::MetricManifold{𝔽, Euclidean{Tuple{m, n}, 𝔽}, ExtendedSymplecticMetric}, + change_representer!(MetMan::MetricManifold{<:Any, <:Euclidean, ExtendedSymplecticMetric}, Y, EucMet::EuclideanMetric, p, X) Change the representation of a matrix ``ξ ∈ \mathbb{R}^{2n \times 2n}`` @@ -174,21 +178,21 @@ In this case, we compute the mapping ```` """ function change_representer( - ::MetricManifold{𝔽,Euclidean{Tuple{m,n},𝔽},ExtendedSymplecticMetric}, + ::MetricManifold{<:Any,<:Euclidean,ExtendedSymplecticMetric}, ::EuclideanMetric, p, X, -) where {𝔽,m,n} +) return p * p' * X end function change_representer!( - ::MetricManifold{𝔽,Euclidean{Tuple{m,n},𝔽},ExtendedSymplecticMetric}, + ::MetricManifold{<:Any,<:Euclidean,ExtendedSymplecticMetric}, Y, ::EuclideanMetric, p, X, -) where {𝔽,m,n} +) Y .= p * p' * X return Y end @@ -208,7 +212,7 @@ Q_{2n} = ```` The tolerance can be set with `kwargs...` (e.g. `atol = 1.0e-14`). """ -function check_point(M::Symplectic{n,ℝ}, p; kwargs...) where {n,ℝ} +function check_point(M::Symplectic, p; kwargs...) # Perform check that the matrix lives on the real symplectic manifold: expected_zero = norm(inv(M, p) * p - LinearAlgebra.I) if !isapprox(expected_zero, zero(eltype(p)); kwargs...) @@ -241,7 +245,7 @@ The tolerance can be set with `kwargs...` (e.g. `atol = 1.0e-14`). """ check_vector(::Symplectic, ::Any...) -function check_vector(M::Symplectic{n}, p, X; kwargs...) where {n} +function check_vector(M::Symplectic, p, X; kwargs...) Q = SymplecticMatrix(p, X) tangent_requirement_norm = norm(X' * Q * p + p' * Q * X, 2) if !isapprox(tangent_requirement_norm, 0.0; kwargs...) @@ -302,7 +306,7 @@ we see that \end{align*} ```` """ -function distance(M::Symplectic{n}, p, q) where {n} +function distance(M::Symplectic, p, q) return norm(log(symplectic_inverse_times(M, p, q))) end @@ -332,7 +336,13 @@ function exp!(M::Symplectic, q, p, X) return q end -get_embedding(::Symplectic{n,ℝ}) where {n} = Euclidean(2n, 2n; field=ℝ) +function get_embedding(::Symplectic{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return Euclidean(2 * n, 2 * n; field=𝔽) +end +function get_embedding(M::Symplectic{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return Euclidean(2 * n, 2 * n; field=𝔽, parameter=:field) +end @doc raw""" gradient(M::Symplectic, f, p, backend::RiemannianProjectionBackend; @@ -398,7 +408,7 @@ function ManifoldDiff.gradient!( end @doc raw""" - inner(::Symplectic{n, ℝ}, p, X, Y) + inner(::Symplectic{<:Any,ℝ}, p, X, Y) Compute the canonical Riemannian inner product [`RealSymplecticMetric`](@ref) ````math @@ -406,7 +416,7 @@ Compute the canonical Riemannian inner product [`RealSymplecticMetric`](@ref) ```` between the two tangent vectors ``X, Y \in T_p\operatorname{Sp}(2n)``. """ -function inner(M::Symplectic{n,ℝ}, p, X, Y) where {n} +function inner(M::Symplectic{<:Any,ℝ}, p, X, Y) p_star = inv(M, p) return dot((p_star * X), (p_star * Y)) end @@ -445,7 +455,8 @@ A^{+} = \end{bmatrix}. ```` """ -function Base.inv(::Symplectic{n,ℝ}, A) where {n} +function Base.inv(M::Symplectic{<:Any,ℝ}, A) + n = get_parameter(M.size)[1] Ai = similar(A) checkbounds(A, 1:(2n), 1:(2n)) @inbounds for i in 1:n, j in 1:n @@ -463,7 +474,8 @@ function Base.inv(::Symplectic{n,ℝ}, A) where {n} return Ai end -function inv!(::Symplectic{n,ℝ}, A) where {n} +function inv!(M::Symplectic{<:Any,ℝ}, A) + n = get_parameter(M.size)[1] checkbounds(A, 1:(2n), 1:(2n)) @inbounds for i in 1:n, j in 1:n A[i, j], A[j + n, i + n] = A[j + n, i + n], A[i, j] @@ -537,7 +549,7 @@ Return false. [`Symplectic`](@ref) is not a flat manifold. is_flat(M::Symplectic) = false @doc raw""" - manifold_dimension(::Symplectic{n}) + manifold_dimension(::Symplectic) Returns the dimension of the symplectic manifold embedded in ``ℝ^{2n \times 2n}``, i.e. @@ -545,7 +557,10 @@ embedded in ``ℝ^{2n \times 2n}``, i.e. \operatorname{dim}(\operatorname{Sp}(2n)) = (2n + 1)n. ```` """ -manifold_dimension(::Symplectic{n}) where {n} = (2n + 1) * n +function manifold_dimension(M::Symplectic) + n = get_parameter(M.size)[1] + return (2n + 1) * n +end @doc raw""" project(::Symplectic, p, A) @@ -586,7 +601,7 @@ function project!(::Symplectic, Y, p, A) end @doc raw""" - project!(::MetricManifold{𝔽,Euclidean,ExtendedSymplecticMetric}, Y, p, X) where {𝔽} + project!(::MetricManifold{𝔽,<:Euclidean,ExtendedSymplecticMetric}, Y, p, X) where {𝔽} Compute the projection of ``X ∈ R^{2n × 2n}`` onto ``T_p\operatorname{Sp}(2n, ℝ)`` w.r.t. the Riemannian metric ``g`` [`RealSymplecticMetric`](@ref). @@ -599,12 +614,7 @@ The closed form projection mapping is given by [GaoSonAbsilStykel:2021](@cite) where ``\operatorname{sym}(A) = \frac{1}{2}(A + A^{\mathrm{T}})``. This function is not exported. """ -function project!( - ::MetricManifold{𝔽,Euclidean{Tuple{m,n},𝔽},ExtendedSymplecticMetric}, - Y, - p, - X, -) where {m,n,𝔽} +function project!(::MetricManifold{<:Any,<:Euclidean,ExtendedSymplecticMetric}, Y, p, X) Q = SymplecticMatrix(p, X) pT_QT_X = p' * Q' * X @@ -615,7 +625,7 @@ function project!( end @doc raw""" - project_normal!(::MetricManifold{𝔽,Euclidean,ExtendedSymplecticMetric}, Y, p, X) + project_normal!(::MetricManifold{𝔽,<:Euclidean,ExtendedSymplecticMetric}, Y, p, X) Project onto the normal of the tangent space ``(T_p\operatorname{Sp}(2n))^{\perp_g}`` at a point ``p ∈ \operatorname{Sp}(2n)``, relative to the riemannian metric @@ -637,11 +647,11 @@ where ``\operatorname{skew}(A) = \frac{1}{2}(A - A^{\mathrm{T}})``. This function is not exported. """ function project_normal!( - ::MetricManifold{𝔽,Euclidean{Tuple{m,n},𝔽},ExtendedSymplecticMetric}, + M::MetricManifold{𝔽,<:Euclidean,ExtendedSymplecticMetric}, Y, p, X, -) where {m,n,𝔽} +) where {𝔽} Q = SymplecticMatrix(p, X) pT_QT_X = p' * Q' * X @@ -686,7 +696,8 @@ function Random.rand( end end -function random_vector(::Symplectic{n}, p::AbstractMatrix; symmetric_norm=1.0) where {n} +function random_vector(M::Symplectic, p::AbstractMatrix; symmetric_norm=1.0) + n = get_parameter(M.size)[1] # Generate random symmetric matrix: S = randn(2n, 2n) S .= (S + S') @@ -696,7 +707,8 @@ function random_vector(::Symplectic{n}, p::AbstractMatrix; symmetric_norm=1.0) w return p * S end -function rand_hamiltonian(::Symplectic{n}; frobenius_norm=1.0) where {n} +function rand_hamiltonian(M::Symplectic; frobenius_norm=1.0) + n = get_parameter(M.size)[1] A = randn(n, n) B = randn(n, n) C = randn(n, n) @@ -750,7 +762,13 @@ function retract_cayley!(M::Symplectic, q, p, X, t::Number) return q end -Base.show(io::IO, ::Symplectic{n,𝔽}) where {n,𝔽} = print(io, "Symplectic{$(2n), $(𝔽)}()") +function Base.show(io::IO, ::Symplectic{TypeParameter{Tuple{n}},𝔽}) where {n,𝔽} + return print(io, "Symplectic($(2n), $(𝔽))") +end +function Base.show(io::IO, M::Symplectic{Tuple{Int},𝔽}) where {𝔽} + n = get_parameter(M.size)[1] + return print(io, "Symplectic($(2n), $(𝔽); parameter=:field)") +end @doc raw""" symplectic_inverse_times(::Symplectic, p, q) @@ -763,12 +781,13 @@ That is, this function efficiently computes where ``Q_{2n}`` is the [`SymplecticMatrix`](@ref) of size ``2n \times 2n``. """ -function symplectic_inverse_times(M::Symplectic{n}, p, q) where {n} +function symplectic_inverse_times(M::Symplectic, p, q) A = similar(p) return symplectic_inverse_times!(M, A, p, q) end -function symplectic_inverse_times!(::Symplectic{n}, A, p, q) where {n} +function symplectic_inverse_times!(M::Symplectic, A, p, q) + n = get_parameter(M.size)[1] # we write p = [p1 p2; p3 p4] (and q, too), then p1 = @view(p[1:n, 1:n]) p2 = @view(p[1:n, (n + 1):(2n)]) diff --git a/src/manifolds/SymplecticStiefel.jl b/src/manifolds/SymplecticStiefel.jl index b9e47f487c..3a4c1c6ebb 100644 --- a/src/manifolds/SymplecticStiefel.jl +++ b/src/manifolds/SymplecticStiefel.jl @@ -1,5 +1,5 @@ @doc raw""" - SymplecticStiefel{n, k, 𝔽} <: AbstractEmbeddedManifold{𝔽, DefaultIsometricEmbeddingType} + SymplecticStiefel{T,𝔽} <: AbstractEmbeddedManifold{𝔽, DefaultIsometricEmbeddingType} The symplectic Stiefel manifold consists of all $2n × 2k, \; n \geq k$ matrices satisfying the requirement @@ -30,8 +30,7 @@ where ``Ω \in \mathfrak{sp}(2n,F)`` is Hamiltonian and ``p^s`` means the symplectic complement of ``p`` s.t. ``p^{+}p^{s} = 0``. # Constructor - SymplecticStiefel(2n::Int, 2k::Int, field::AbstractNumbers=ℝ) - -> SymplecticStiefel{div(2n, 2), div(2k, 2), field}() + SymplecticStiefel(2n::Int, 2k::Int, field::AbstractNumbers=ℝ; parameter::Symbol=:type) Generate the (real-valued) symplectic Stiefel manifold of ``2n \times 2k`` matrices which span a ``2k`` dimensional symplectic subspace of ``ℝ^{2n \times 2n}``. @@ -39,10 +38,18 @@ The constructor for the [`SymplecticStiefel`](@ref) manifold accepts the even co dimension ``2n`` and an even number of columns ``2k`` for the real symplectic Stiefel manifold with elements ``p \in ℝ^{2n × 2k}``. """ -struct SymplecticStiefel{n,k,𝔽} <: AbstractDecoratorManifold{𝔽} end +struct SymplecticStiefel{T,𝔽} <: AbstractDecoratorManifold{𝔽} + size::T +end -function SymplecticStiefel(two_n::Int, two_k::Int, field::AbstractNumbers=ℝ) - return SymplecticStiefel{div(two_n, 2),div(two_k, 2),field}() +function SymplecticStiefel( + two_n::Int, + two_k::Int, + field::AbstractNumbers=ℝ; + parameter::Symbol=:type, +) + size = wrap_type_parameter(parameter, (div(two_n, 2), div(two_k, 2))) + return SymplecticStiefel{typeof(size),field}(size) end function active_traits(f, ::SymplecticStiefel, args...) @@ -57,7 +64,7 @@ ManifoldsBase.default_retraction_method(::SymplecticStiefel) = CayleyRetraction( @doc raw""" canonical_project(::SymplecticStiefel, p_Sp) - canonical_project!(::SymplecticStiefel{n,k}, p, p_Sp) + canonical_project!(::SymplecticStiefel, p, p_Sp) Define the canonical projection from ``\operatorname{Sp}(2n, 2n)`` onto ``\operatorname{SpSt}(2n, 2k)``, by projecting onto the first ``k`` columns @@ -65,12 +72,14 @@ and the ``n + 1``'th onto the ``n + k``'th columns [BendokatZimmermann:2021](@ci It is assumed that the point ``p`` is on ``\operatorname{Sp}(2n, 2n)``. """ -function canonical_project(M::SymplecticStiefel{n,k}, p_Sp) where {n,k} +function canonical_project(M::SymplecticStiefel, p_Sp) + n, k = get_parameter(M.size) p_SpSt = similar(p_Sp, (2n, 2k)) return canonical_project!(M, p_SpSt, p_Sp) end -function canonical_project!(::SymplecticStiefel{n,k}, p, p_Sp) where {n,k} +function canonical_project!(M::SymplecticStiefel, p, p_Sp) + n, k = get_parameter(M.size) p[:, (1:k)] .= p_Sp[:, (1:k)] p[:, ((k + 1):(2k))] .= p_Sp[:, ((n + 1):(n + k))] return p @@ -94,7 +103,7 @@ Q_{2n} = ```` The tolerance can be set with `kwargs...` (e.g. `atol = 1.0e-14`). """ -function check_point(M::SymplecticStiefel{n,k}, p; kwargs...) where {n,k} +function check_point(M::SymplecticStiefel, p; kwargs...) # Perform check that the matrix lives on the real symplectic manifold: expected_zero = norm(inv(M, p) * p - I) if !isapprox(expected_zero, 0; kwargs...) @@ -133,7 +142,8 @@ The tolerance can be set with `kwargs...` (e.g. `atol = 1.0e-14`). """ check_vector(::SymplecticStiefel, ::Any...) -function check_vector(M::SymplecticStiefel{n,k,field}, p, X; kwargs...) where {n,k,field} +function check_vector(M::SymplecticStiefel{<:Any,field}, p, X; kwargs...) where {field} + n, k = get_parameter(M.size) # From Bendokat-Zimmermann: T_pSpSt(2n, 2k) = \{p*H | H^{+} = -H \} H = inv(M, p) * X # ∈ ℝ^{2k × 2k}, should be Hamiltonian. H_star = inv(Symplectic(2k, field), H) @@ -229,7 +239,8 @@ which only requires computing the matrix exponentials of """ exp(::SymplecticStiefel, p, X) -function exp!(M::SymplecticStiefel{n,k}, q, p, X) where {n,k} +function exp!(M::SymplecticStiefel, q, p, X) + n, k = get_parameter(M.size) Q = SymplecticMatrix(p, X) pT_p = lu(p' * p) # ∈ ℝ^{2k × 2k} @@ -276,17 +287,29 @@ function exp!(M::SymplecticStiefel{n,k}, q, p, X) where {n,k} return q end -get_embedding(::SymplecticStiefel{n,k,ℝ}) where {n,k} = Euclidean(2n, 2k; field=ℝ) +function get_embedding(::SymplecticStiefel{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return Euclidean(2 * n, 2 * k; field=𝔽) +end +function get_embedding(M::SymplecticStiefel{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return Euclidean(2 * n, 2 * k; field=𝔽, parameter=:field) +end @doc raw""" get_total_space(::SymplecticStiefel) Return the total space of the [`SymplecticStiefel`](@ref) manifold, which is the corresponding [`Symplectic`](@ref) manifold. """ -get_total_space(::SymplecticStiefel{n,k,𝔽}) where {n,k,𝔽} = Symplectic{n,𝔽}() +function get_total_space(::SymplecticStiefel{TypeParameter{Tuple{n,k}},ℝ}) where {n,k} + return Symplectic(2 * n) +end +function get_total_space(M::SymplecticStiefel{Tuple{Int,Int},ℝ}) + n, k = get_parameter(M.size) + return Symplectic(2 * n; parameter=:field) +end @doc raw""" - inner(M::SymplecticStiefel{n, k}, p, X. Y) + inner(M::SymplecticStiefel, p, X. Y) Compute the Riemannian inner product ``g^{\operatorname{SpSt}}`` at ``p \in \operatorname{SpSt}`` between tangent vectors ``X, X \in T_p\operatorname{SpSt}``. @@ -297,7 +320,7 @@ g^{\operatorname{SpSt}}_p(X, Y) \frac{1}{2}Q_{2n}^{\mathrm{T}}p(p^{\mathrm{T}}p)^{-1}p^{\mathrm{T}}Q_{2n}\right)Y(p^{\mathrm{T}}p)^{-1}\right). ```` """ -function inner(::SymplecticStiefel{n,k}, p, X, Y) where {n,k} +function inner(::SymplecticStiefel, p, X, Y) Q = SymplecticMatrix(p, X, Y) # Procompute lu(p'p) since we solve a^{-1}* 3 times a = lu(p' * p) # note that p'p is symmetric, thus so is its inverse c=a^{-1} @@ -311,8 +334,8 @@ function inner(::SymplecticStiefel{n,k}, p, X, Y) where {n,k} end @doc raw""" - inv(::SymplecticStiefel{n, k}, A) - inv!(::SymplecticStiefel{n, k}, q, p) + inv(::SymplecticStiefel, A) + inv!(::SymplecticStiefel, q, p) Compute the symplectic inverse ``A^+`` of matrix ``A ∈ ℝ^{2n × 2k}``. Given a matrix ````math @@ -346,12 +369,13 @@ A^{+} = \end{bmatrix}. ```` """ -function Base.inv(M::SymplecticStiefel{n,k}, p) where {n,k} +function Base.inv(M::SymplecticStiefel, p) q = similar(p') return inv!(M, q, p) end -function inv!(::SymplecticStiefel{n,k}, q, p) where {n,k} +function inv!(M::SymplecticStiefel, q, p) + n, k = get_parameter(M.size) checkbounds(q, 1:(2k), 1:(2n)) checkbounds(p, 1:(2n), 1:(2k)) @inbounds for i in 1:k, j in 1:n @@ -419,7 +443,7 @@ Return false. [`SymplecticStiefel`](@ref) is not a flat manifold. is_flat(M::SymplecticStiefel) = false @doc raw""" - manifold_dimension(::SymplecticStiefel{n, k}) + manifold_dimension(::SymplecticStiefel) Returns the dimension of the symplectic Stiefel manifold embedded in ``ℝ^{2n \times 2k}``, i.e. [BendokatZimmermann:2021](@cite) @@ -427,7 +451,10 @@ i.e. [BendokatZimmermann:2021](@cite) \operatorname{dim}(\operatorname{SpSt}(2n, 2k)) = (4n - 2k + 1)k. ```` """ -manifold_dimension(::SymplecticStiefel{n,k}) where {n,k} = (4n - 2k + 1) * k +function manifold_dimension(M::SymplecticStiefel) + n, k = get_parameter(M.size) + return (4n - 2k + 1) * k +end @doc raw""" project(::SymplecticStiefel, p, A) @@ -491,10 +518,11 @@ and generates a random Hamiltonian matrix ``Ω_X \in \mathfrak{sp}(2n,F)`` with Frobenius norm of `hamiltonian_norm` before returning ``X = pΩ_X``. """ function Random.rand( - M::SymplecticStiefel{n}; + M::SymplecticStiefel; vector_at=nothing, hamiltonian_norm=(vector_at === nothing ? 1 / 2 : 1.0), -) where {n} +) + n, k = get_parameter(M.size) if vector_at === nothing return canonical_project(M, rand(Symplectic(2n); hamiltonian_norm=hamiltonian_norm)) else @@ -502,11 +530,8 @@ function Random.rand( end end -function random_vector( - ::SymplecticStiefel{n,k}, - p::AbstractMatrix; - hamiltonian_norm=1.0, -) where {n,k} +function random_vector(M::SymplecticStiefel, p::AbstractMatrix; hamiltonian_norm=1.0) + n, k = get_parameter(M.size) Ω = rand_hamiltonian(Symplectic(2k); frobenius_norm=hamiltonian_norm) return p * Ω end @@ -597,8 +622,12 @@ function riemannian_gradient!( return X end -function Base.show(io::IO, ::SymplecticStiefel{n,k,𝔽}) where {n,k,𝔽} - return print(io, "SymplecticStiefel{$(2n), $(2k), $(𝔽)}()") +function Base.show(io::IO, ::SymplecticStiefel{TypeParameter{Tuple{n,k}},𝔽}) where {n,k,𝔽} + return print(io, "SymplecticStiefel($(2n), $(2k), $(𝔽))") +end +function Base.show(io::IO, M::SymplecticStiefel{Tuple{Int,Int},𝔽}) where {𝔽} + n, k = get_parameter(M.size) + return print(io, "SymplecticStiefel($(2n), $(2k), $(𝔽); parameter=:field)") end @doc raw""" @@ -616,12 +645,14 @@ This function performs this common operation without allocating more than a ``2k \times 2k`` matrix to store the result in, or in the case of the in-place function, without allocating memory at all. """ -function symplectic_inverse_times(M::SymplecticStiefel{n,k}, p, q) where {n,k} +function symplectic_inverse_times(M::SymplecticStiefel, p, q) + n, k = get_parameter(M.size) A = similar(p, (2k, 2k)) return symplectic_inverse_times!(M, A, p, q) end -function symplectic_inverse_times!(::SymplecticStiefel{n,k}, A, p, q) where {n,k} +function symplectic_inverse_times!(M::SymplecticStiefel, A, p, q) + n, k = get_parameter(M.size) # we write p = [p1 p2; p3 p4] (and q, too), then p1 = @view(p[1:n, 1:k]) p2 = @view(p[1:n, (k + 1):(2k)]) diff --git a/src/manifolds/Tucker.jl b/src/manifolds/Tucker.jl index 8ae708c871..414d00e62c 100644 --- a/src/manifolds/Tucker.jl +++ b/src/manifolds/Tucker.jl @@ -1,6 +1,6 @@ @doc raw""" - Tucker{N, R, D, 𝔽} <: AbstractManifold{𝔽} + Tucker{T, D, 𝔽} <: AbstractManifold{𝔽} The manifold of ``N_1 \times \dots \times N_D`` real-valued or complex-valued tensors of fixed multilinear rank ``(R_1, \dots, R_D)`` . If ``R_1 = \dots = R_D = 1``, this is the @@ -36,15 +36,24 @@ where ``\mathcal{C}^\prime`` is arbitrary, ``U_d^{\mathrm{H}}`` is the Hermitian ``U_d``, and ``U_d^{\mathrm{H}} U_d^\prime = 0`` for all ``d``. # Constructor - Tucker(N::NTuple{D, Int}, R::NTuple{D, Int}[, field = ℝ]) + + Tucker(N::NTuple{D, Int}, R::NTuple{D, Int}[, field=ℝ]; parameter::Symbol=:type) Generate the manifold of `field`-valued tensors of dimensions `N[1] × … × N[D]` and multilinear rank `R = (R[1], …, R[D])`. """ -struct Tucker{N,R,D,𝔽} <: AbstractManifold{𝔽} end -function Tucker(n⃗::NTuple{D,Int}, r⃗::NTuple{D,Int}, field::AbstractNumbers=ℝ) where {D} +struct Tucker{T,D,𝔽} <: AbstractManifold{𝔽} + size::T +end +function Tucker( + n⃗::NTuple{D,Int}, + r⃗::NTuple{D,Int}, + field::AbstractNumbers=ℝ; + parameter::Symbol=:type, +) where {D} @assert is_valid_mlrank(n⃗, r⃗) - return Tucker{n⃗,r⃗,D,field}() + size = wrap_type_parameter(parameter, (n⃗, r⃗)) + return Tucker{typeof(size),D,field}(size) end #= @@ -207,7 +216,7 @@ end #### @doc raw""" - check_point(M::Tucker{N,R,D}, p; kwargs...) where {N,R,D} + check_point(M::Tucker, p; kwargs...) Check whether the multidimensional array or [`TuckerPoint`](@ref) `p` is a point on the [`Tucker`](@ref) manifold, i.e. it is a `D`th order `N[1] × … × N[D]` tensor of multilinear @@ -215,7 +224,8 @@ rank `(R[1], …, R[D])`. The keyword arguments are passed to the matrix rank fu to the unfoldings. For a [`TuckerPoint`](@ref) it is checked that the point is in correct HOSVD form. """ -function check_point(M::Tucker{N,R,D}, x; kwargs...) where {N,R,D} +function check_point(M::Tucker, x; kwargs...) + N, R = get_parameter(M.size) s = "The point $(x) does not lie on $(M), " size(x) == N || return DomainError(size(x), s * "since its size is not $(N).") x_buffer = similar(x) @@ -225,7 +235,8 @@ function check_point(M::Tucker{N,R,D}, x; kwargs...) where {N,R,D} end return nothing end -function check_point(M::Tucker{N,R,D}, x::TuckerPoint; kwargs...) where {N,R,D} +function check_point(M::Tucker, x::TuckerPoint; kwargs...) + N, R = get_parameter(M.size) s = "The point $(x) does not lie on $(M), " U = x.hosvd.U ℭ = x.hosvd.core @@ -269,7 +280,7 @@ function check_point(M::Tucker{N,R,D}, x::TuckerPoint; kwargs...) where {N,R,D} end @doc raw""" - check_vector(M::Tucker{N,R,D}, p::TuckerPoint{T,D}, X::TuckerTVector) where {N,R,T,D} + check_vector(M::Tucker{<:Any,D}, p::TuckerPoint{T,D}, X::TuckerTVector) where {T,D} Check whether a [`TuckerTVector`](@ref) `X` is is in the tangent space to the `D`th order [`Tucker`](@ref) manifold `M` at the `D`th order [`TuckerPoint`](@ref) `p`. @@ -277,25 +288,21 @@ This is the case when the dimensions of the factors in `X` agree with those of `p` and the factor matrices of `X` are in the orthogonal complement of the HOSVD factors of `p`. """ -function check_vector( - M::Tucker{N,R,D}, - p::TuckerPoint{T,D}, - v::TuckerTVector, -) where {N,R,T,D} - s = "The tangent vector $(v) is not a tangent vector to $(p) on $(M), " - if size(p.hosvd.core) ≠ size(v.Ċ) || any(size.(v.U̇) .≠ size.(p.hosvd.U)) +function check_vector(M::Tucker{<:Any,D}, p::TuckerPoint{T,D}, X::TuckerTVector) where {T,D} + s = "The tangent vector $(X) is not a tangent vector to $(p) on $(M), " + if size(p.hosvd.core) ≠ size(X.Ċ) || any(size.(X.U̇) .≠ size.(p.hosvd.U)) return DomainError( - size(v.Ċ), - s * "since the array dimensons of $(p) and $(v)" * "do not agree.", + size(X.Ċ), + s * "since the array dimensons of $(p) and $(X)" * "do not agree.", ) end - for (U, U̇) in zip(p.hosvd.U, v.U̇) + for (U, U̇) in zip(p.hosvd.U, X.U̇) if norm(U' * U̇) ≥ √eps(eltype(U)) * √length(U) return DomainError( norm(U' * U̇), s * "since the columns of x.hosvd.U are not" * - "orthogonal to those of v.U̇.", + "orthogonal to those of X.U̇.", ) end end @@ -375,17 +382,19 @@ end end @doc raw""" - embed(::Tucker{N,R,D}, p::TuckerPoint) where {N,R,D} + embed(::Tucker, p::TuckerPoint) Convert a [`TuckerPoint`](@ref) `p` on the rank `R` [`Tucker`](@ref) manifold to a full `N[1] × … × N[D]`-array by evaluating the Tucker decomposition. - - embed(::Tucker{N,R,D}, p::TuckerPoint, X::TuckerTVector) where {N,R,D} +""" +embed(::Tucker, ::TuckerPoint) +@doc raw""" + embed(::Tucker, p::TuckerPoint, X::TuckerTVector) Convert a tangent vector `X` with base point `p` on the rank `R` [`Tucker`](@ref) manifold to a full tensor, represented as an `N[1] × … × N[D]`-array. """ -embed(::Tucker, ::Any, ::TuckerPoint) +embed(::Tucker, p::TuckerPoint, X::TuckerTVector) function embed!(::Tucker, q, p::TuckerPoint) return copyto!(q, reshape(⊗ᴿ(p.hosvd.U...) * vec(p.hosvd.core), size(p))) @@ -623,7 +632,7 @@ function is_valid_mlrank(n⃗, r⃗) end @doc raw""" - manifold_dimension(::Tucker{N,R,D}) where {N,R,D} + manifold_dimension(::Tucker) The dimension of the manifold of ``N_1 \times \dots \times N_D`` tensors of multilinear rank ``(R_1, \dots, R_D)``, i.e. @@ -631,7 +640,10 @@ rank ``(R_1, \dots, R_D)``, i.e. \mathrm{dim}(\mathcal{M}) = \prod_{d=1}^D R_d + \sum_{d=1}^D R_d (N_d - R_d). ```` """ -manifold_dimension(::Tucker{n⃗,r⃗}) where {n⃗,r⃗} = prod(r⃗) + sum(r⃗ .* (n⃗ .- r⃗)) +function manifold_dimension(M::Tucker) + n⃗, r⃗ = get_parameter(M.size) + return prod(r⃗) + sum(r⃗ .* (n⃗ .- r⃗)) +end @doc raw""" Base.ndims(p::TuckerPoint{T,D}) where {T,D} @@ -711,9 +723,18 @@ function retract_polar!( return q end -function Base.show(io::IO, ::MIME"text/plain", 𝒯::Tucker{N,R,D,𝔽}) where {N,R,D,𝔽} - return print(io, "Tucker(", N, ", ", R, ", ", 𝔽, ")") +function Base.show( + io::IO, + ::MIME"text/plain", + ::Tucker{TypeParameter{Tuple{n,r}},D,𝔽}, +) where {n,r,D,𝔽} + return print(io, "Tucker($(n), $(r), $(𝔽))") end +function Base.show(io::IO, ::MIME"text/plain", M::Tucker{<:Tuple,D,𝔽}) where {D,𝔽} + n, r = get_parameter(M.size) + return print(io, "Tucker($(n), $(r), $(𝔽); parameter=:field)") +end + function Base.show(io::IO, ::MIME"text/plain", 𝔄::TuckerPoint) pre = " " summary(io, 𝔄) @@ -858,7 +879,7 @@ for fun in [:get_vector, :inverse_retract, :project, :zero_vector] end end -function ManifoldsBase.allocate_result(::Tucker{N}, f::typeof(embed), p, args...) where {N} - dims = N +function ManifoldsBase.allocate_result(M::Tucker, f::typeof(embed), p, args...) + dims = get_parameter(M.size)[1] return Array{number_eltype(p),length(dims)}(undef, dims) end diff --git a/src/manifolds/Unitary.jl b/src/manifolds/Unitary.jl index 7345f90143..21e6fb90b3 100644 --- a/src/manifolds/Unitary.jl +++ b/src/manifolds/Unitary.jl @@ -27,26 +27,29 @@ the representation above. see also [`OrthogonalMatrices`](@ref) for the real valued case. """ -const UnitaryMatrices{n,𝔽} = GeneralUnitaryMatrices{n,𝔽,AbsoluteDeterminantOneMatrices} +const UnitaryMatrices{T,𝔽} = GeneralUnitaryMatrices{T,𝔽,AbsoluteDeterminantOneMatrices} -UnitaryMatrices(n::Int, 𝔽::AbstractNumbers=ℂ) = UnitaryMatrices{n,𝔽}() +function UnitaryMatrices(n::Int, 𝔽::AbstractNumbers=ℂ; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return UnitaryMatrices{typeof(size),𝔽}(size) +end -check_size(::UnitaryMatrices{1,ℍ}, p::Number) = nothing -check_size(::UnitaryMatrices{1,ℍ}, p, X::Number) = nothing +check_size(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p::Number) = nothing +check_size(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X::Number) = nothing -embed(::UnitaryMatrices{1,ℍ}, p::Number) = SMatrix{1,1}(p) +embed(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p::Number) = SMatrix{1,1}(p) -embed(::UnitaryMatrices{1,ℍ}, p, X::Number) = SMatrix{1,1}(X) +embed(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X::Number) = SMatrix{1,1}(X) -function exp(::UnitaryMatrices{1,ℍ}, p, X::Number) +function exp(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X::Number) return p * exp(X) end -function exp(::UnitaryMatrices{1,ℍ}, p, X::Number, t::Number) +function exp(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X::Number, t::Number) return p * exp(t * X) end function get_coordinates_orthonormal( - ::UnitaryMatrices{1,ℍ}, + ::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X::Quaternions.Quaternion, ::QuaternionNumbers, @@ -55,7 +58,7 @@ function get_coordinates_orthonormal( end function get_vector_orthonormal( - ::UnitaryMatrices{1,ℍ}, + ::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p::Quaternions.Quaternion, c, ::QuaternionNumbers, @@ -64,12 +67,16 @@ function get_vector_orthonormal( return Quaternions.quat(0, c[i], c[i + 1], c[i + 2]) end -injectivity_radius(::UnitaryMatrices{1,ℍ}) = π +injectivity_radius(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}) = π -_isapprox(::UnitaryMatrices{1,ℍ}, x, y; kwargs...) = isapprox(x[], y[]; kwargs...) -_isapprox(::UnitaryMatrices{1,ℍ}, p, X, Y; kwargs...) = isapprox(X[], Y[]; kwargs...) +function _isapprox(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, x, y; kwargs...) + return isapprox(x[], y[]; kwargs...) +end +function _isapprox(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X, Y; kwargs...) + return isapprox(X[], Y[]; kwargs...) +end -function log(::UnitaryMatrices{1,ℍ}, p::Number, q::Number) +function log(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p::Number, q::Number) return log(conj(p) * q) end @@ -81,31 +88,41 @@ Return the dimension of the manifold unitary matrices. \dim_{\mathrm{U}(n)} = n^2. ``` """ -manifold_dimension(::UnitaryMatrices{n,ℂ}) where {n} = n^2 +function manifold_dimension(M::UnitaryMatrices{<:Any,ℂ}) + n = get_parameter(M.size)[1] + return n^2 +end @doc raw""" - manifold_dimension(M::UnitaryMatrices{n,ℍ}) + manifold_dimension(M::UnitaryMatrices{<:Any,ℍ}) Return the dimension of the manifold unitary matrices. ```math \dim_{\mathrm{U}(n, ℍ)} = n(2n+1). ``` """ -manifold_dimension(::UnitaryMatrices{n,ℍ}) where {n} = n * (2n + 1) +function manifold_dimension(M::UnitaryMatrices{<:Any,ℍ}) + n = get_parameter(M.size)[1] + return n * (2n + 1) +end -Manifolds.number_of_coordinates(::UnitaryMatrices{1,ℍ}, ::AbstractBasis{ℍ}) = 3 +number_of_coordinates(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, ::AbstractBasis{ℍ}) = 3 -project(::UnitaryMatrices{1,ℍ}, p) = sign(p) +project(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p) = sign(p) -project(::UnitaryMatrices{1,ℍ}, p, X) = (X - conj(X)) / 2 +project(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p, X) = (X - conj(X)) / 2 -function Random.rand(M::UnitaryMatrices{1,ℍ}; vector_at=nothing) +function Random.rand(M::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}; vector_at=nothing) if vector_at === nothing return sign(rand(Quaternions.QuaternionF64)) else project(M, vector_at, rand(Quaternions.QuaternionF64)) end end -function Random.rand(rng::AbstractRNG, M::UnitaryMatrices{1,ℍ}; vector_at=nothing) +function Random.rand( + rng::AbstractRNG, + M::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}; + vector_at=nothing, +) if vector_at === nothing return sign(rand(rng, Quaternions.QuaternionF64)) else @@ -113,8 +130,20 @@ function Random.rand(rng::AbstractRNG, M::UnitaryMatrices{1,ℍ}; vector_at=noth end end -show(io::IO, ::UnitaryMatrices{n,ℂ}) where {n} = print(io, "UnitaryMatrices($(n))") -show(io::IO, ::UnitaryMatrices{n,ℍ}) where {n} = print(io, "UnitaryMatrices($(n), ℍ)") +function Base.show(io::IO, ::UnitaryMatrices{TypeParameter{Tuple{n}},ℂ}) where {n} + return print(io, "UnitaryMatrices($(n))") +end +function Base.show(io::IO, M::UnitaryMatrices{Tuple{Int},ℂ}) + n = get_parameter(M.size)[1] + return print(io, "UnitaryMatrices($n; parameter=:field)") +end +function Base.show(io::IO, ::UnitaryMatrices{TypeParameter{Tuple{n}},ℍ}) where {n} + return print(io, "UnitaryMatrices($(n), ℍ)") +end +function Base.show(io::IO, M::UnitaryMatrices{Tuple{Int},ℍ}) + n = get_parameter(M.size)[1] + return print(io, "UnitaryMatrices($n, ℍ; parameter=:field)") +end @doc raw""" riemannian_Hessian(M::UnitaryMatrices, p, G, H, X) @@ -153,4 +182,4 @@ function Weingarten!(::UnitaryMatrices, Y, p, X, V) return Y end -Manifolds.zero_vector(::UnitaryMatrices{1,ℍ}, p) = zero(p) +zero_vector(::UnitaryMatrices{TypeParameter{Tuple{1}},ℍ}, p) = zero(p) diff --git a/src/manifolds/VectorBundle.jl b/src/manifolds/VectorBundle.jl index 91a245c0ca..d030f6c2c5 100644 --- a/src/manifolds/VectorBundle.jl +++ b/src/manifolds/VectorBundle.jl @@ -1,607 +1,111 @@ """ - TensorProductType(spaces::VectorSpaceType...) + const VectorBundleVectorTransport = FiberBundleProductVectorTransport -Vector space type corresponding to the tensor product of given vector space -types. +Deprecated: an alias for `FiberBundleProductVectorTransport`. """ -struct TensorProductType{TS<:Tuple} <: VectorSpaceType - spaces::TS -end - -TensorProductType(spaces::VectorSpaceType...) = TensorProductType{typeof(spaces)}(spaces) - -""" - VectorBundleFibers(fiber::VectorSpaceType, M::AbstractManifold) - -Type representing a family of vector spaces (fibers) of a vector bundle over `M` -with vector spaces of type `fiber`. In contrast with `VectorBundle`, operations -on `VectorBundleFibers` expect point-like and vector-like parts to be -passed separately instead of being bundled together. It can be thought of -as a representation of vector spaces from a vector bundle but without -storing the point at which a vector space is attached (which is specified -separately in various functions). -""" -struct VectorBundleFibers{TVS<:VectorSpaceType,TM<:AbstractManifold} - fiber::TVS - manifold::TM -end - -const TangentBundleFibers{M} = - VectorBundleFibers{TangentSpaceType,M} where {M<:AbstractManifold} - -TangentBundleFibers(M::AbstractManifold) = VectorBundleFibers(TangentSpace, M) - -const CotangentBundleFibers{M} = - VectorBundleFibers{CotangentSpaceType,M} where {M<:AbstractManifold} - -CotangentBundleFibers(M::AbstractManifold) = VectorBundleFibers(CotangentSpace, M) - -""" - VectorSpaceAtPoint{ - 𝔽, - TFiber<:VectorBundleFibers{<:VectorSpaceType,<:AbstractManifold{𝔽}}, - TX, - } <: AbstractManifold{𝔽} - -A vector space at a point `p` on the manifold. -This is modelled using [`VectorBundleFibers`](@ref) with only a vector-like part -and fixing the point-like part to be just `p`. - -This vector space itself is also a `manifold`. Especially, it's flat and hence isometric -to the [`Euclidean`](@ref) manifold. - -# Constructor - VectorSpaceAtPoint(fiber::VectorBundleFibers, p) - -A vector space (fiber type `fiber` of a vector bundle) at point `p` from -the manifold `fiber.manifold`. -""" -struct VectorSpaceAtPoint{ - 𝔽, - TFiber<:VectorBundleFibers{<:VectorSpaceType,<:AbstractManifold{𝔽}}, - TX, -} <: AbstractManifold{𝔽} - fiber::TFiber - point::TX -end - -""" - TangentSpaceAtPoint{M} - -Alias for [`VectorSpaceAtPoint`](@ref) for the tangent space at a point. -""" -const TangentSpaceAtPoint{M} = - VectorSpaceAtPoint{𝔽,TangentBundleFibers{M}} where {𝔽,M<:AbstractManifold{𝔽}} - -""" - TangentSpaceAtPoint(M::AbstractManifold, p) - -Return an object of type [`VectorSpaceAtPoint`](@ref) representing tangent -space at `p` on the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) `M`. -""" -TangentSpaceAtPoint(M::AbstractManifold, p) = VectorSpaceAtPoint(TangentBundleFibers(M), p) - -""" - TangentSpace(M::AbstractManifold, p) - -Return a [`TangentSpaceAtPoint`](@ref) representing tangent space at `p` on the [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) `M`. -""" -TangentSpace(M::AbstractManifold, p) = VectorSpaceAtPoint(TangentBundleFibers(M), p) - -const CotangentSpaceAtPoint{M} = - VectorSpaceAtPoint{𝔽,CotangentBundleFibers{M}} where {𝔽,M<:AbstractManifold{𝔽}} - -""" - CotangentSpaceAtPoint(M::AbstractManifold, p) - -Return an object of type [`VectorSpaceAtPoint`](@ref) representing cotangent -space at `p`. -""" -function CotangentSpaceAtPoint(M::AbstractManifold, p) - return VectorSpaceAtPoint(CotangentBundleFibers(M), p) -end - -@doc raw""" - struct SasakiRetraction <: AbstractRetractionMethod end - -Exponential map on [`TangentBundle`](@ref) computed via Euler integration as described -in [MuralidharanFlecther:2012](@cite). The system of equations for $\gamma : ℝ \to T\mathcal M$ such that -$\gamma(1) = \exp_{p,X}(X_M, X_F)$ and $\gamma(0)=(p, X)$ reads - -```math -\dot{\gamma}(t) = (\dot{p}(t), \dot{X}(t)) = (R(X(t), \dot{X}(t))\dot{p}(t), 0) -``` - -where $R$ is the Riemann curvature tensor (see [`riemann_tensor`](@ref)). - -# Constructor - - SasakiRetraction(L::Int) - -In this constructor `L` is the number of integration steps. -""" -struct SasakiRetraction <: AbstractRetractionMethod - L::Int -end - -@doc raw""" - VectorBundleProductVectorTransport{ - TMP<:AbstractVectorTransportMethod, - TMV<:AbstractVectorTransportMethod, - } <: AbstractVectorTransportMethod - -Vector transport type on [`VectorBundle`](@ref). `method_point` is used for vector transport -of the point part and `method_vector` is used for transport of the vector part. - -The vector transport is derived as a product manifold-style vector transport. The considered -product manifold is the product between the manifold $\mathcal M$ and the topological vector -space isometric to the fiber. - -# Constructor - - VectorBundleProductVectorTransport( - method_point::AbstractVectorTransportMethod, - method_vector::AbstractVectorTransportMethod, - ) - VectorBundleProductVectorTransport() +const VectorBundleVectorTransport = FiberBundleProductVectorTransport -By default both methods are set to `ParallelTransport`. """ -struct VectorBundleProductVectorTransport{ - TMP<:AbstractVectorTransportMethod, - TMV<:AbstractVectorTransportMethod, -} <: AbstractVectorTransportMethod - method_point::TMP - method_vector::TMV -end - -function VectorBundleProductVectorTransport() - return VectorBundleProductVectorTransport(ParallelTransport(), ParallelTransport()) -end + fiber_bundle_transport(M::AbstractManifold, fiber::FiberType) +Determine the vector transport used for [`exp`](@ref exp(::FiberBundle, ::Any...)) and +[`log`](@ref log(::FiberBundle, ::Any...)) maps on a vector bundle with fiber type +`fiber` and manifold `M`. """ - const VectorBundleVectorTransport = VectorBundleProductVectorTransport - -Deprecated: an alias for `VectorBundleProductVectorTransport`. -""" -const VectorBundleVectorTransport = VectorBundleProductVectorTransport +fiber_bundle_transport(M::AbstractManifold, ::FiberType) = + default_vector_transport_method(M) """ - VectorBundle{𝔽,TVS<:VectorSpaceType,TM<:AbstractManifold{𝔽}} <: AbstractManifold{𝔽} + VectorBundle{𝔽,TVS,TM,VTV} = FiberBundle{𝔽,TVS,TM,TVT} where {TVS<:VectorSpaceType} -Vector bundle on a [`AbstractManifold`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/types.html#ManifoldsBase.AbstractManifold) `M` of type [`VectorSpaceType`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/bases.html#ManifoldsBase.VectorSpaceType). +Alias for [`FiberBundle`](@ref) when fiber type is a `TVS` of type +[`VectorSpaceType`]https://juliamanifolds.github.io/ManifoldsBase.jl/stable/bases/#ManifoldsBase.VectorSpaceType). -# Constructor - - VectorBundle(M::AbstractManifold, type::VectorSpaceType) +`VectorSpaceFiberType` is used to encode vector spaces as fiber types. """ -struct VectorBundle{ +const VectorBundle{𝔽,TVS,TM,TVT} = FiberBundle{ 𝔽, + TVS, + TM, + TVT, +} where { TVS<:VectorSpaceType, TM<:AbstractManifold{𝔽}, - TVT<:VectorBundleProductVectorTransport, -} <: AbstractManifold{𝔽} - type::TVS - manifold::TM - fiber::VectorBundleFibers{TVS,TM} - vector_transport::TVT -end - -function VectorBundle( - fiber::TVS, - M::TM, - vtm::VectorBundleProductVectorTransport, -) where {TVS<:VectorSpaceType,TM<:AbstractManifold{𝔽}} where {𝔽} - return VectorBundle{𝔽,TVS,TM,typeof(vtm)}(fiber, M, VectorBundleFibers(fiber, M), vtm) -end -function VectorBundle(fiber::VectorSpaceType, M::AbstractManifold) - vtmm = vector_bundle_transport(fiber, M) - vtbm = VectorBundleProductVectorTransport(vtmm, vtmm) - return VectorBundle(fiber, M, vtbm) -end + TVT<:FiberBundleProductVectorTransport, +} """ TangentBundle{𝔽,M} = VectorBundle{𝔽,TangentSpaceType,M} where {𝔽,M<:AbstractManifold{𝔽}} Tangent bundle for manifold of type `M`, as a manifold with the Sasaki metric [Sasaki:1958](@cite). -Exact retraction and inverse retraction can be approximated using [`VectorBundleProductRetraction`](@ref), -[`VectorBundleInverseProductRetraction`](@ref) and [`SasakiRetraction`](@ref). -[`VectorBundleProductVectorTransport`](@ref) can be used as a vector transport. +Exact retraction and inverse retraction can be approximated using [`FiberBundleProductRetraction`](@ref), +[`FiberBundleInverseProductRetraction`](@ref) and [`SasakiRetraction`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/retractions/#ManifoldsBase.SasakiRetraction). +[`FiberBundleProductVectorTransport`](@ref) can be used as a vector transport. # Constructors TangentBundle(M::AbstractManifold) - TangentBundle(M::AbstractManifold, vtm::VectorBundleProductVectorTransport) + TangentBundle(M::AbstractManifold, vtm::FiberBundleProductVectorTransport) """ const TangentBundle{𝔽,M} = VectorBundle{𝔽,TangentSpaceType,M} where {𝔽,M<:AbstractManifold{𝔽}} -TangentBundle(M::AbstractManifold) = VectorBundle(TangentSpace, M) -function TangentBundle(M::AbstractManifold, vtm::VectorBundleProductVectorTransport) - return VectorBundle(TangentSpace, M, vtm) +TangentBundle(M::AbstractManifold) = FiberBundle(TangentSpaceType(), M) +function TangentBundle(M::AbstractManifold, vtm::FiberBundleProductVectorTransport) + return FiberBundle(TangentSpaceType(), M, vtm) end const CotangentBundle{𝔽,M} = VectorBundle{𝔽,CotangentSpaceType,M} where {𝔽,M<:AbstractManifold{𝔽}} -CotangentBundle(M::AbstractManifold) = VectorBundle(CotangentSpace, M) -function CotangentBundle(M::AbstractManifold, vtm::VectorBundleProductVectorTransport) - return VectorBundle(CotangentSpace, M, vtm) +CotangentBundle(M::AbstractManifold) = FiberBundle(CotangentSpaceType(), M) +function CotangentBundle(M::AbstractManifold, vtm::FiberBundleProductVectorTransport) + return FiberBundle(CotangentSpaceType(), M, vtm) end -struct VectorBundleBasisData{BBasis<:CachedBasis,TBasis<:CachedBasis} - base_basis::BBasis - vec_basis::TBasis +function bundle_transport_to!(B::TangentBundle, Y, p, X, q) + return vector_transport_to!(B.manifold, Y, p, X, q, B.vector_transport.method_vertical) end -@doc raw""" - struct VectorBundleInverseProductRetraction <: AbstractInverseRetractionMethod end - -Inverse retraction of the point `y` at point `p` from vector bundle `B` over manifold -`B.fiber` (denoted $\mathcal M$). The inverse retraction is derived as a product manifold-style -approximation to the logarithmic map in the Sasaki metric. The considered product manifold -is the product between the manifold $\mathcal M$ and the topological vector space isometric -to the fiber. - -Notation: - * The point $p = (x_p, V_p)$ where $x_p ∈ \mathcal M$ and $V_p$ belongs to the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. - Similarly, $q = (x_q, V_q)$. - -The inverse retraction is calculated as - -$\operatorname{retr}^{-1}_p q = (\operatorname{retr}^{-1}_{x_p}(x_q), V_{\operatorname{retr}^{-1}} - V_p)$ - -where $V_{\operatorname{retr}^{-1}}$ is the result of vector transport of $V_q$ to the point $x_p$. -The difference $V_{\operatorname{retr}^{-1}} - V_p$ corresponds to the logarithmic map in -the vector space $F$. - -See also [`VectorBundleProductRetraction`](@ref). -""" -struct VectorBundleInverseProductRetraction <: AbstractInverseRetractionMethod end - -@doc raw""" - struct VectorBundleProductRetraction <: AbstractRetractionMethod end - -Product retraction map of tangent vector $X$ at point $p$ from vector bundle `B` over -manifold `B.fiber` (denoted $\mathcal M$). The retraction is derived as a product manifold-style -approximation to the exponential map in the Sasaki metric. The considered product manifold -is the product between the manifold $\mathcal M$ and the topological vector space isometric -to the fiber. - -Notation: - * The point $p = (x_p, V_p)$ where $x_p ∈ \mathcal M$ and $V_p$ belongs to the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. - * The tangent vector $X = (V_{X,M}, V_{X,F}) ∈ T_pB$ where - $V_{X,M}$ is a tangent vector from the tangent space $T_{x_p}\mathcal M$ and - $V_{X,F}$ is a tangent vector from the tangent space $T_{V_p}F$ (isomorphic to $F$). - -The retraction is calculated as - -$\operatorname{retr}_p(X) = (\exp_{x_p}(V_{X,M}), V_{\exp})$ - -where $V_{\exp}$ is the result of vector transport of $V_p + V_{X,F}$ -to the point $\exp_{x_p}(V_{X,M})$. -The sum $V_p + V_{X,F}$ corresponds to the exponential map in the vector space $F$. - -See also [`VectorBundleInverseProductRetraction`](@ref). -""" -struct VectorBundleProductRetraction <: AbstractRetractionMethod end - -function allocate_result(M::TangentSpaceAtPoint, ::typeof(rand)) - return zero_vector(M.fiber.manifold, M.point) -end - -base_manifold(B::VectorBundleFibers) = base_manifold(B.manifold) -base_manifold(B::VectorSpaceAtPoint) = base_manifold(B.fiber) -base_manifold(B::VectorBundle) = base_manifold(B.manifold) - -""" - bundle_projection(B::VectorBundle, p::ArrayPartition) - -Projection of point `p` from the bundle `M` to the base manifold. -Returns the point on the base manifold `B.manifold` at which the vector part -of `p` is attached. -""" -bundle_projection(B::VectorBundle, p) = submanifold_component(B.manifold, p, Val(1)) - -function default_inverse_retraction_method(::TangentBundle) - return VectorBundleInverseProductRetraction() -end - -function default_retraction_method(::TangentBundle) - return VectorBundleProductRetraction() -end - -function default_vector_transport_method(B::TangentBundle) - default_vt_m = default_vector_transport_method(B.manifold) - return VectorBundleProductVectorTransport(default_vt_m, default_vt_m) -end - -""" - distance(B::VectorBundleFibers, p, X, Y) - -Distance between vectors `X` and `Y` from the vector space at point `p` -from the manifold `B.manifold`, that is the base manifold of `M`. -""" -distance(B::VectorBundleFibers, p, X, Y) = norm(B, p, X - Y) -""" - distance(M::TangentSpaceAtPoint, p, q) - -Distance between vectors `p` and `q` from the vector space `M`. It is calculated as the norm -of their difference. -""" -function distance(M::TangentSpaceAtPoint, p, q) - return norm(M.fiber.manifold, M.point, q - p) -end - -function embed!(M::TangentSpaceAtPoint, q, p) - return embed!(M.fiber.manifold, q, M.point, p) -end -function embed!(M::TangentSpaceAtPoint, Y, p, X) - return embed!(M.fiber.manifold, Y, M.point, X) -end - -@doc raw""" - exp(M::TangentSpaceAtPoint, p, X) - -Exponential map of tangent vectors `X` and `p` from the tangent space `M`. It is -calculated as their sum. -""" -exp(::TangentSpaceAtPoint, ::Any, ::Any) - -function exp!(M::TangentSpaceAtPoint, q, p, X) - copyto!(M.fiber.manifold, q, p + X) - return q -end - -function get_basis(M::VectorBundle, p, B::AbstractBasis) - xp1 = submanifold_component(p, Val(1)) - base_basis = get_basis(M.manifold, xp1, B) - vec_basis = get_basis(M.fiber, xp1, B) - return CachedBasis(B, VectorBundleBasisData(base_basis, vec_basis)) -end -function get_basis(M::VectorBundle, p, B::CachedBasis) - return invoke(get_basis, Tuple{AbstractManifold,Any,CachedBasis}, M, p, B) -end -function get_basis(M::TangentSpaceAtPoint, p, B::CachedBasis) - return invoke( - get_basis, - Tuple{AbstractManifold,Any,CachedBasis}, - M.fiber.manifold, - M.point, - B, - ) -end - -function get_basis(M::VectorBundle, p, B::DiagonalizingOrthonormalBasis) - xp1 = submanifold_component(p, Val(1)) - bv1 = DiagonalizingOrthonormalBasis(submanifold_component(B.frame_direction, Val(1))) - b1 = get_basis(M.manifold, xp1, bv1) - bv2 = DiagonalizingOrthonormalBasis(submanifold_component(B.frame_direction, Val(2))) - b2 = get_basis(M.fiber, xp1, bv2) - return CachedBasis(B, VectorBundleBasisData(b1, b2)) -end - -function get_basis(M::TangentBundleFibers, p, B::AbstractBasis{<:Any,TangentSpaceType}) - return get_basis(M.manifold, p, B) -end -function get_basis(M::TangentSpaceAtPoint, p, B::AbstractBasis{<:Any,TangentSpaceType}) - return get_basis(M.fiber.manifold, M.point, B) -end - -function get_coordinates(M::TangentBundleFibers, p, X, B::AbstractBasis) - return get_coordinates(M.manifold, p, X, B) -end - -function get_coordinates!(M::TangentBundleFibers, Y, p, X, B::AbstractBasis) - return get_coordinates!(M.manifold, Y, p, X, B) -end - -function get_coordinates(M::TangentSpaceAtPoint, p, X, B::AbstractBasis) - return get_coordinates(M.fiber.manifold, M.point, X, B) -end - -function get_coordinates!(M::TangentSpaceAtPoint, Y, p, X, B::AbstractBasis) - return get_coordinates!(M.fiber.manifold, Y, M.point, X, B) -end - -function get_coordinates(M::VectorBundle, p, X, B::AbstractBasis) - px, Vx = submanifold_components(M.manifold, p) - VXM, VXF = submanifold_components(M.manifold, X) - n = manifold_dimension(M.manifold) - return vcat( - get_coordinates(M.manifold, px, VXM, B), - get_coordinates(M.fiber, px, VXF, B), - ) -end - -function get_coordinates!(M::VectorBundle, Y, p, X, B::AbstractBasis) - px, Vx = submanifold_components(M.manifold, p) - VXM, VXF = submanifold_components(M.manifold, X) - n = manifold_dimension(M.manifold) - get_coordinates!(M.manifold, view(Y, 1:n), px, VXM, B) - get_coordinates!(M.fiber, view(Y, (n + 1):length(Y)), px, VXF, B) - return Y -end - -function get_coordinates( - M::VectorBundle, - p, - X, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:VectorBundleBasisData}, -) where {𝔽} - px, Vx = submanifold_components(M.manifold, p) - VXM, VXF = submanifold_components(M.manifold, X) - return vcat( - get_coordinates(M.manifold, px, VXM, B.data.base_basis), - get_coordinates(M.fiber, px, VXF, B.data.vec_basis), - ) -end - -function get_coordinates!( - M::VectorBundle, +function bundle_transport_tangent_direction!( + B::TangentBundle, Y, p, + pf, X, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:VectorBundleBasisData}, -) where {𝔽} - px, Vx = submanifold_components(M.manifold, p) - VXM, VXF = submanifold_components(M.manifold, X) - n = manifold_dimension(M.manifold) - get_coordinates!(M.manifold, view(Y, 1:n), px, VXM, B.data.base_basis) - get_coordinates!(M.fiber, view(Y, (n + 1):length(Y)), px, VXF, B.data.vec_basis) - return Y -end - -function get_vector(M::VectorBundle, p, X, B::AbstractBasis) - n = manifold_dimension(M.manifold) - xp1 = submanifold_component(p, Val(1)) - return ArrayPartition( - get_vector(M.manifold, xp1, X[1:n], B), - get_vector(M.fiber, xp1, X[(n + 1):end], B), - ) -end - -function get_vector!(M::VectorBundle, Y, p, X, B::AbstractBasis) - n = manifold_dimension(M.manifold) - xp1 = submanifold_component(p, Val(1)) - get_vector!(M.manifold, submanifold_component(Y, Val(1)), xp1, X[1:n], B) - get_vector!(M.fiber, submanifold_component(Y, Val(2)), xp1, X[(n + 1):end], B) - return Y -end - -function get_vector( - M::VectorBundle, - p, - X, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:VectorBundleBasisData}, -) where {𝔽} - n = manifold_dimension(M.manifold) - xp1 = submanifold_component(p, Val(1)) - return ArrayPartition( - get_vector(M.manifold, xp1, X[1:n], B.data.base_basis), - get_vector(M.fiber, xp1, X[(n + 1):end], B.data.vec_basis), - ) + d, + m::AbstractVectorTransportMethod=default_vector_transport_method(B.manifold), +) + return vector_transport_direction!(B.manifold, Y, p, X, d, m) end -function get_vector!( - M::VectorBundle, +function bundle_transport_tangent_to!( + B::TangentBundle, Y, p, + pf, X, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:VectorBundleBasisData}, -) where {𝔽} - n = manifold_dimension(M.manifold) - xp1 = submanifold_component(p, Val(1)) - get_vector!( - M.manifold, - submanifold_component(Y, Val(1)), - xp1, - X[1:n], - B.data.base_basis, - ) - get_vector!( - M.fiber, - submanifold_component(Y, Val(2)), - xp1, - X[(n + 1):end], - B.data.vec_basis, - ) - return Y -end - -function get_vector(M::TangentBundleFibers, p, X, B::AbstractBasis) - return get_vector(M.manifold, p, X, B) -end -function get_vector(M::TangentSpaceAtPoint, p, X, B::AbstractBasis) - return get_vector(M.fiber.manifold, M.point, X, B) -end - -function get_vector!(M::TangentBundleFibers, Y, p, X, B::AbstractBasis) - return get_vector!(M.manifold, Y, p, X, B) -end -function get_vector!(M::TangentSpaceAtPoint, Y, p, X, B::AbstractBasis) - return get_vector!(M.fiber.manifold, Y, M.point, X, B) + q, + m::AbstractVectorTransportMethod=default_vector_transport_method(B.manifold), +) + return vector_transport_to!(B.manifold, Y, p, X, q, m) end -function get_vectors( - M::VectorBundle, - p::ProductRepr, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:VectorBundleBasisData}, -) where {𝔽} - xp1 = submanifold_component(p, Val(1)) - zero_m = zero_vector(M.manifold, xp1) - zero_f = zero_vector(M.fiber, xp1) - vs = typeof(ProductRepr(zero_m, zero_f))[] - for bv in get_vectors(M.manifold, xp1, B.data.base_basis) - push!(vs, ProductRepr(bv, zero_f)) - end - for bv in get_vectors(M.fiber, xp1, B.data.vec_basis) - push!(vs, ProductRepr(zero_m, bv)) - end - return vs -end -function get_vectors( - M::VectorBundle, - p::ArrayPartition, - B::CachedBasis{𝔽,<:AbstractBasis{𝔽},<:Manifolds.VectorBundleBasisData}, -) where {𝔽} - xp1 = submanifold_component(p, Val(1)) - zero_m = zero_vector(M.manifold, xp1) - zero_f = zero_vector(M.fiber, xp1) - vs = typeof(ArrayPartition(zero_m, zero_f))[] - for bv in get_vectors(M.manifold, xp1, B.data.base_basis) - push!(vs, ArrayPartition(bv, zero_f)) - end - for bv in get_vectors(M.fiber, xp1, B.data.vec_basis) - push!(vs, ArrayPartition(zero_m, bv)) - end - return vs -end -function get_vectors(M::VectorBundleFibers, p, B::CachedBasis) - return get_vectors(M.manifold, p, B) -end -function get_vectors(M::TangentSpaceAtPoint, p, B::CachedBasis) - return get_vectors(M.fiber.manifold, M.point, B) +function default_inverse_retraction_method(::TangentBundle) + return FiberBundleInverseProductRetraction() end -""" - getindex(p::ProductRepr, M::VectorBundle, s::Symbol) - p[M::VectorBundle, s] - -Access the element(s) at index `s` of a point `p` on a [`VectorBundle`](@ref) `M` by -using the symbols `:point` and `:vector` for the base and vector component, respectively. -""" -@inline function Base.getindex(p::ProductRepr, M::VectorBundle, s::Symbol) - (s === :point) && return submanifold_component(M, p, Val(1)) - (s === :vector) && return submanifold_component(M, p, Val(2)) - return throw(DomainError(s, "unknown component $s on $M.")) +function default_retraction_method(::TangentBundle) + return FiberBundleProductRetraction() end -""" - getindex(p::ArrayPartition, M::VectorBundle, s::Symbol) - p[M::VectorBundle, s] -Access the element(s) at index `s` of a point `p` on a [`VectorBundle`](@ref) `M` by -using the symbols `:point` and `:vector` for the base and vector component, respectively. -""" -@inline function Base.getindex(p::ArrayPartition, M::VectorBundle, s::Symbol) - (s === :point) && return p.x[1] - (s === :vector) && return p.x[2] - return throw(DomainError(s, "unknown component $s on $M.")) +function default_vector_transport_method(B::TangentBundle) + default_vt_m = default_vector_transport_method(B.manifold) + return FiberBundleProductVectorTransport(default_vt_m, default_vt_m) end -@doc raw""" - injectivity_radius(M::TangentSpaceAtPoint) - -Return the injectivity radius on the [`TangentSpaceAtPoint`](@ref) `M`, which is $∞$. -""" -injectivity_radius(::TangentSpaceAtPoint) = Inf - """ injectivity_radius(M::TangentBundle) @@ -617,75 +121,51 @@ function injectivity_radius(M::TangentBundle) end end -""" - inner(B::VectorBundleFibers, p, X, Y) - -Inner product of vectors `X` and `Y` from the vector space of type `B.fiber` -at point `p` from manifold `B.manifold`. -""" -function inner(B::VectorBundleFibers, p, X, Y) - return error( - "inner not defined for vector space family of type $(typeof(B)), " * - "point of type $(typeof(p)) and " * - "vectors of types $(typeof(X)) and $(typeof(Y)).", - ) -end -inner(B::TangentBundleFibers, p, X, Y) = inner(B.manifold, p, X, Y) -function inner(B::CotangentBundleFibers, p, X, Y) - return inner(B.manifold, p, sharp(B.manifold, p, X), sharp(B.manifold, p, Y)) -end @doc raw""" inner(B::VectorBundle, p, X, Y) Inner product of tangent vectors `X` and `Y` at point `p` from the -vector bundle `B` over manifold `B.fiber` (denoted $\mathcal M$). +vector bundle `B` over manifold `B.fiber` (denoted ``\mathcal M``). Notation: - * The point $p = (x_p, V_p)$ where $x_p ∈ \mathcal M$ and $V_p$ belongs to the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. - * The tangent vector $v = (V_{X,M}, V_{X,F}) ∈ T_{x}B$ where - $V_{X,M}$ is a tangent vector from the tangent space $T_{x_p}\mathcal M$ and - $V_{X,F}$ is a tangent vector from the tangent space $T_{V_p}F$ (isomorphic to $F$). - Similarly for the other tangent vector $w = (V_{Y,M}, V_{Y,F}) ∈ T_{x}B$. + * The point ``p = (x_p, V_p)`` where ``x_p ∈ \mathcal M`` and ``V_p`` belongs to the + fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the + canonical projection of that vector bundle ``B``. + * The tangent vector ``v = (V_{X,M}, V_{X,F}) ∈ T_{x}B`` where + ``V_{X,M}`` is a tangent vector from the tangent space ``T_{x_p}\mathcal M`` and + ``V_{X,F}`` is a tangent vector from the tangent space ``T_{V_p}F`` (isomorphic to ``F``). + Similarly for the other tangent vector ``w = (V_{Y,M}, V_{Y,F}) ∈ T_{x}B``. The inner product is calculated as -$⟨X, Y⟩_p = ⟨V_{X,M}, V_{Y,M}⟩_{x_p} + ⟨V_{X,F}, V_{Y,F}⟩_{V_p}.$ +``⟨X, Y⟩_p = ⟨V_{X,M}, V_{Y,M}⟩_{x_p} + ⟨V_{X,F}, V_{Y,F}⟩_{V_p}.`` """ -function inner(B::VectorBundle, p, X, Y) +function inner(B::FiberBundle, p, X, Y) px, Vx = submanifold_components(B.manifold, p) VXM, VXF = submanifold_components(B.manifold, X) VYM, VYF = submanifold_components(B.manifold, Y) - # for tangent bundle Vx is discarded by the method of inner for TangentSpaceAtPoint + F = Fiber(B.manifold, px, B.type) + # for tangent bundle Vx is discarded by the method of inner for TangentSpace # and px is actually used as the base point - return inner(B.manifold, px, VXM, VYM) + - inner(VectorSpaceAtPoint(B.fiber, px), Vx, VXF, VYF) + return inner(B.manifold, px, VXM, VYM) + inner(F, Vx, VXF, VYF) end -""" - inner(M::TangentSpaceAtPoint, p, X, Y) - -Inner product of vectors `X` and `Y` from the tangent space at `M`. -""" -function inner(M::TangentSpaceAtPoint, p, X, Y) - return inner(M.fiber.manifold, M.point, X, Y) -end - -function _inverse_retract(M::VectorBundle, p, q, ::VectorBundleInverseProductRetraction) +function _inverse_retract(M::FiberBundle, p, q, ::FiberBundleInverseProductRetraction) return inverse_retract_product(M, p, q) end -function _inverse_retract!(M::VectorBundle, X, p, q, ::VectorBundleInverseProductRetraction) +function _inverse_retract!(M::FiberBundle, X, p, q, ::FiberBundleInverseProductRetraction) return inverse_retract_product!(M, X, p, q) end """ - inverse_retract_product(M::VectorBundle, p, q) + inverse_retract(M::VectorBundle, p, q, ::FiberBundleInverseProductRetraction) -Compute the allocating variant of the [`VectorBundleInverseProductRetraction`](@ref), +Compute the allocating variant of the [`FiberBundleInverseProductRetraction`](@ref), which by default allocates and calls `inverse_retract_product!`. """ +inverse_retract(::VectorBundle, p, q, ::FiberBundleInverseProductRetraction) + function inverse_retract_product(M::VectorBundle, p, q) X = allocate_result(M, inverse_retract, p, q) return inverse_retract_product!(M, X, p, q) @@ -696,34 +176,11 @@ function inverse_retract_product!(B::VectorBundle, X, p, q) py, Vy = submanifold_components(B.manifold, q) VXM, VXF = submanifold_components(B.manifold, X) log!(B.manifold, VXM, px, py) - vector_transport_to!(B.fiber, VXF, py, Vy, px, B.vector_transport.method_vector) + bundle_transport_to!(B, VXF, py, Vy, px) copyto!(VXF, VXF - Vx) return X end -function _isapprox(B::VectorBundle, p, q; kwargs...) - xp, Vp = submanifold_components(B.manifold, p) - xq, Vq = submanifold_components(B.manifold, q) - return isapprox(B.manifold, xp, xq; kwargs...) && - isapprox(VectorSpaceAtPoint(B.fiber, xp), Vp, Vq; kwargs...) -end -function _isapprox(B::VectorBundle, p, X, Y; kwargs...) - px, Vx = submanifold_components(B.manifold, p) - VXM, VXF = submanifold_components(B.manifold, X) - VYM, VYF = submanifold_components(B.manifold, Y) - return isapprox(B.manifold, VXM, VYM; kwargs...) && - isapprox(VectorSpaceAtPoint(B.fiber, px), VXF, VYF; kwargs...) -end -function _isapprox(M::TangentSpaceAtPoint, X, Y; kwargs...) - return isapprox(M.fiber.manifold, M.point, X, Y; kwargs...) -end - -""" - is_flat(::TangentSpaceAtPoint) - -Return true. [`TangentSpaceAtPoint`](@ref) is a flat manifold. -""" -is_flat(::TangentSpaceAtPoint) = true """ is_flat(::VectorBundle) @@ -731,164 +188,78 @@ Return true if the underlying manifold of [`VectorBundle`](@ref) `M` is flat. """ is_flat(M::VectorBundle) = is_flat(M.manifold) -""" - log(M::TangentSpaceAtPoint, p, q) - -Logarithmic map on the tangent space manifold `M`, calculated as the difference of tangent -vectors `q` and `p` from `M`. -""" -log(::TangentSpaceAtPoint, ::Any...) -function log!(::TangentSpaceAtPoint, X, p, q) - copyto!(X, q - p) - return X -end - -function manifold_dimension(B::VectorBundle) - return manifold_dimension(B.manifold) + vector_space_dimension(B.fiber) -end -function manifold_dimension(M::VectorSpaceAtPoint) - return vector_space_dimension(M.fiber) -end - -""" - norm(B::VectorBundleFibers, p, q) - -Norm of the vector `X` from the vector space of type `B.fiber` -at point `p` from manifold `B.manifold`. -""" -LinearAlgebra.norm(B::VectorBundleFibers, p, X) = sqrt(inner(B, p, X, X)) -LinearAlgebra.norm(B::TangentBundleFibers, p, X) = norm(B.manifold, p, X) -LinearAlgebra.norm(M::VectorSpaceAtPoint, p, X) = norm(M.fiber.manifold, M.point, X) - -function parallel_transport_to!(M::TangentSpaceAtPoint, Y, p, X, q) - return copyto!(M.fiber.manifold, Y, p, X) -end - @doc raw""" project(B::VectorBundle, p) Project the point `p` from the ambient space of the vector bundle `B` -over manifold `B.fiber` (denoted $\mathcal M$) to the vector bundle. +over manifold `B.fiber` (denoted ``\mathcal M``) to the vector bundle. Notation: - * The point $p = (x_p, V_p)$ where $x_p$ belongs to the ambient space of $\mathcal M$ - and $V_p$ belongs to the ambient space of the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. + * The point ``p = (x_p, V_p)`` where ``x_p`` belongs to the ambient space of ``\mathcal M`` + and ``V_p`` belongs to the ambient space of the + fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the + canonical projection of that vector bundle ``B``. -The projection is calculated by projecting the point $x_p$ to the manifold $\mathcal M$ -and then projecting the vector $V_p$ to the tangent space $T_{x_p}\mathcal M$. +The projection is calculated by projecting the point ``x_p`` to the manifold ``\mathcal M`` +and then projecting the vector ``V_p`` to the tangent space ``T_{x_p}\mathcal M``. """ project(::VectorBundle, ::Any) -@doc raw""" - project(M::TangentSpaceAtPoint, p) - -Project the point `p` from the tangent space `M`, that is project the vector `p` -tangent at `M.point`. -""" -project(::TangentSpaceAtPoint, ::Any) - function project!(B::VectorBundle, q, p) px, pVx = submanifold_components(B.manifold, p) qx, qVx = submanifold_components(B.manifold, q) project!(B.manifold, qx, px) - project!(B.manifold, qVx, qx, pVx) + F = Fiber(B.manifold, qx, B.type) + project!(F, qVx, pVx) return q end -function project!(M::TangentSpaceAtPoint, q, p) - return project!(M.fiber.manifold, q, M.point, p) -end @doc raw""" project(B::VectorBundle, p, X) -Project the element `X` of the ambient space of the tangent space $T_p B$ -to the tangent space $T_p B$. +Project the element `X` of the ambient space of the tangent space ``T_p B`` +to the tangent space ``T_p B``. Notation: - * The point $p = (x_p, V_p)$ where $x_p ∈ \mathcal M$ and $V_p$ belongs to the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. - * The vector $x = (V_{X,M}, V_{X,F})$ where $x_p$ belongs to the ambient space of $T_{x_p}\mathcal M$ - and $V_{X,F}$ belongs to the ambient space of the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. - -The projection is calculated by projecting $V_{X,M}$ to tangent space $T_{x_p}\mathcal M$ -and then projecting the vector $V_{X,F}$ to the fiber $F$. -""" -project(::VectorBundle, ::Any, ::Any) -@doc raw""" - project(M::TangentSpaceAtPoint, p, X) + * The point ``p = (x_p, V_p)`` where ``x_p ∈ \mathcal M`` and ``V_p`` belongs to the + fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the + canonical projection of that vector bundle ``B``. + * The vector ``x = (V_{X,M}, V_{X,F})`` where ``x_p`` belongs to the ambient space of + ``T_{x_p}\mathcal M`` and ``V_{X,F}`` belongs to the ambient space of the + fiber ``F=π^{-1}(\{x_p\})`` of the vector bundle ``B`` where ``π`` is the + canonical projection of that vector bundle ``B``. -Project the vector `X` from the tangent space `M`, that is project the vector `X` -tangent at `M.point`. +The projection is calculated by projecting ``V_{X,M}`` to tangent space ``T_{x_p}\mathcal M`` +and then projecting the vector ``V_{X,F}`` to the fiber ``F``. """ -project(::TangentSpaceAtPoint, ::Any, ::Any) +project(::VectorBundle, ::Any, ::Any) function project!(B::VectorBundle, Y, p, X) px, Vx = submanifold_components(B.manifold, p) VXM, VXF = submanifold_components(B.manifold, X) VYM, VYF = submanifold_components(B.manifold, Y) + F = Fiber(B.manifold, px, B.type) project!(B.manifold, VYM, px, VXM) - project!(B.manifold, VYF, px, VXF) + project!(F, VYF, Vx, VXF) return Y end -function project!(M::TangentSpaceAtPoint, Y, p, X) - return project!(M.fiber.manifold, Y, M.point, X) + +function _retract!(M::VectorBundle, q, p, X, t::Number, ::FiberBundleProductRetraction) + return retract_product!(M, q, p, X, t) end """ - project(B::VectorBundleFibers, p, X) + retract(M::VectorBundle, p, q, t::Number, ::FiberBundleProductRetraction) -Project vector `X` from the vector space of type `B.fiber` at point `p`. +Compute the allocating variant of the [`FiberBundleProductRetraction`](@ref), +which by default allocates and calls `retract_product!`. """ -function project(B::VectorBundleFibers, p, X) - Y = allocate_result(B, project, p, X) - return project!(B, Y, p, X) -end - -function project!(B::TangentBundleFibers, 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)).", - ) -end - -function Random.rand!(rng::AbstractRNG, M::VectorBundle, pX; vector_at=nothing) - pXM, pXF = submanifold_components(M.manifold, pX) - if vector_at === nothing - rand!(rng, M.manifold, pXM) - rand!(rng, VectorSpaceAtPoint(M.fiber, pXM), pXF) - else - vector_atM, vector_atF = submanifold_components(M.manifold, vector_at) - rand!(rng, M.manifold, pXM; vector_at=vector_atM) - rand!(rng, VectorSpaceAtPoint(M.fiber, pXM), pXF; vector_at=vector_atF) - end - return pX -end -function Random.rand!(rng::AbstractRNG, M::TangentSpaceAtPoint, X; vector_at=nothing) - rand!(rng, M.fiber.manifold, X; vector_at=M.point) - return X -end +retract(::VectorBundle, p, q, t::Number, ::FiberBundleProductRetraction) -function _retract(M::VectorBundle, p, X, t::Number, ::VectorBundleProductRetraction) +function _retract(M::VectorBundle, p, X, t::Number, ::FiberBundleProductRetraction) return retract_product(M, p, X, t) end -function _retract!(M::VectorBundle, q, p, X, t::Number, ::VectorBundleProductRetraction) - return retract_product!(M, q, p, X, t) -end - -""" - retract_product(M::VectorBundle, p, q, t::Number) - -Compute the allocating variant of the [`VectorBundleProductRetraction`](@ref), -which by default allocates and calls `retract_product!`. -""" function retract_product(M::VectorBundle, p, X, t::Number) q = allocate_result(M, retract, p, X) return retract_product!(M, q, p, X, t) @@ -907,31 +278,12 @@ function retract_product!(B::VectorBundle, q, p, X, t::Number) xp, Xp + VXF, VXM, - B.vector_transport.method_point, + B.vector_transport.method_horizontal, ) copyto!(B.manifold, xq, xqt) return q end -function _retract(M::AbstractManifold, p, X, t::Number, m::SasakiRetraction) - return retract_sasaki(M, p, X, t, m) -end - -function _retract!(M::AbstractManifold, q, p, X, t::Number, m::SasakiRetraction) - return retract_sasaki!(M, q, p, X, t, m) -end - -""" - retract_sasaki(M::AbstractManifold, p, X, t::Number, m::SasakiRetraction) - -Compute the allocating variant of the [`SasakiRetraction`](@ref), -which by default allocates and calls `retract_sasaki!`. -""" -function retract_sasaki(M::AbstractManifold, p, X, t::Number, m::SasakiRetraction) - q = allocate_result(M, retract, p, X) - return retract_sasaki!(M, q, p, X, t, m) -end - function retract_sasaki!(B::TangentBundle, q, p, X, t::Number, m::SasakiRetraction) tX = t * X xp, Xp = submanifold_components(B.manifold, p) @@ -966,172 +318,11 @@ function retract_sasaki!(B::TangentBundle, q, p, X, t::Number, m::SasakiRetracti return q end -""" - setindex!(p::ProductRepr, val, M::VectorBundle, s::Symbol) - p[M::VectorBundle, s] = val - -Set the element(s) at index `s` of a point `p` on a [`VectorBundle`](@ref) `M` to `val` by -using the symbols `:point` and `:vector` for the base and vector component, respectively. - -!!! note - - The *content* of element of `p` is replaced, not the element itself. -""" -@inline function Base.setindex!(x::ProductRepr, val, M::VectorBundle, s::Symbol) - if s === :point - return copyto!(submanifold_component(M, x, Val(1)), val) - elseif s === :vector - return copyto!(submanifold_component(M, x, Val(2)), val) - else - throw(DomainError(s, "unknown component $s on $M.")) - end -end -""" - setindex!(p::ArrayPartition, val, M::VectorBundle, s::Symbol) - p[M::VectorBundle, s] = val - -Set the element(s) at index `s` of a point `p` on a [`VectorBundle`](@ref) `M` to `val` by -using the symbols `:point` and `:vector` for the base and vector component, respectively. - -!!! note - - The *content* of element of `p` is replaced, not the element itself. -""" -@inline function Base.setindex!(x::ArrayPartition, val, M::VectorBundle, s::Symbol) - if s === :point - return copyto!(x.x[1], val) - elseif s === :vector - return copyto!(x.x[2], val) - else - throw(DomainError(s, "unknown component $s on $M.")) - end -end - -function representation_size(B::VectorBundleFibers{<:TCoTSpaceType}) - return representation_size(B.manifold) -end -function representation_size(B::TangentSpaceAtPoint) - return representation_size(B.fiber.manifold) -end - -function Base.show(io::IO, tpt::TensorProductType) - return print(io, "TensorProductType(", join(tpt.spaces, ", "), ")") -end -function Base.show(io::IO, fiber::VectorBundleFibers) - return print(io, "VectorBundleFibers($(fiber.fiber), $(fiber.manifold))") -end -function Base.show(io::IO, ::MIME"text/plain", vs::VectorSpaceAtPoint) - summary(io, vs) - println(io, "\nFiber:") - pre = " " - sf = sprint(show, "text/plain", vs.fiber; context=io, sizehint=0) - sf = replace(sf, '\n' => "\n$(pre)") - println(io, pre, sf) - println(io, "Base point:") - sp = sprint(show, "text/plain", vs.point; context=io, sizehint=0) - sp = replace(sp, '\n' => "\n$(pre)") - return print(io, pre, sp) -end -function Base.show(io::IO, ::MIME"text/plain", TpM::TangentSpaceAtPoint) - println(io, "Tangent space to the manifold $(base_manifold(TpM)) at point:") - pre = " " - sp = sprint(show, "text/plain", TpM.point; context=io, sizehint=0) - sp = replace(sp, '\n' => "\n$(pre)") - return print(io, pre, sp) -end -Base.show(io::IO, vb::VectorBundle) = print(io, "VectorBundle($(vb.type), $(vb.manifold))") Base.show(io::IO, vb::TangentBundle) = print(io, "TangentBundle($(vb.manifold))") -Base.show(io::IO, vb::CotangentBundle) = print(io, "CotangentBundle($(vb.manifold))") - -""" - allocate_result(B::VectorBundleFibers, f, x...) - -Allocates an array for the result of function `f` that is -an element of the vector space of type `B.fiber` on manifold `B.manifold` -and arguments `x...` for implementing the non-modifying operation -using the modifying operation. -""" -@inline function allocate_result(B::VectorBundleFibers, f::TF, x...) where {TF} - if length(x) == 0 - # TODO: this may be incorrect when point and tangent vector representation are - # different for the manifold but there is no easy and generic way around that - return allocate_result(B.manifold, f) - else - T = allocate_result_type(B, f, x) - return allocate(x[1], T) - end -end -@inline function allocate_result(B::TangentBundleFibers, f::typeof(zero_vector), x...) - return allocate_result(B.manifold, f, x...) -end -@inline function allocate_result(M::VectorBundle, f::TF) where {TF} - return ArrayPartition(allocate_result(M.manifold, f), allocate_result(M.fiber, f)) -end - -""" - allocate_result_type(B::VectorBundleFibers, f, args::NTuple{N,Any}) where N - -Return type of element of the array that will represent the result of -function `f` for representing an operation with result in the vector space `fiber` -for manifold `M` on given arguments (passed at a tuple). -""" -@inline function allocate_result_type( - ::VectorBundleFibers, - f::TF, - args::NTuple{N,Any}, -) where {TF,N} - return typeof(mapreduce(eti -> one(number_eltype(eti)), +, args)) -end - -""" - vector_bundle_transport(fiber::VectorSpaceType, M::AbstractManifold) - -Determine the vector tranport used for [`exp`](@ref exp(::VectorBundle, ::Any...)) and -[`log`](@ref log(::VectorBundle, ::Any...)) maps on a vector bundle with vector space type -`fiber` and manifold `M`. -""" -vector_bundle_transport(::VectorSpaceType, ::AbstractManifold) = ParallelTransport() - -function vector_space_dimension(B::VectorBundleFibers) - return vector_space_dimension(B.manifold, B.fiber) -end - -function vector_space_dimension(M::AbstractManifold, V::TensorProductType) - dim = 1 - for space in V.spaces - dim *= vector_space_dimension(M, space) - end - return dim -end function vector_transport_direction(M::VectorBundle, p, X, d) return vector_transport_direction(M, p, X, d, M.vector_transport) end -function vector_transport_direction( - M::TangentBundleFibers, - p, - X, - d, - m::AbstractVectorTransportMethod, -) - return vector_transport_direction(M.manifold, p, X, d, m) -end - -function _vector_transport_direction( - M::VectorBundle, - p, - X, - d, - m::VectorBundleProductVectorTransport, -) - px, pVx = submanifold_components(M.manifold, p) - VXM, VXF = submanifold_components(M.manifold, X) - dx, dVx = submanifold_components(M.manifold, d) - return ArrayPartition( - vector_transport_direction(M.manifold, px, VXM, dx, m.method_point), - vector_transport_direction(M.fiber, px, VXF, dx, m.method_vector), - ) -end function vector_transport_direction!(M::VectorBundle, Y, p, X, d) return vector_transport_direction!(M, Y, p, X, d, M.vector_transport) @@ -1143,46 +334,30 @@ function _vector_transport_direction!( p, X, d, - m::VectorBundleProductVectorTransport, + m::FiberBundleProductVectorTransport, ) VYM, VYF = submanifold_components(M.manifold, Y) px, pVx = submanifold_components(M.manifold, p) VXM, VXF = submanifold_components(M.manifold, X) dx, dVx = submanifold_components(M.manifold, d) - vector_transport_direction!(M.manifold, VYM, px, VXM, dx, m.method_point), - vector_transport_direction!(M.manifold, VYF, px, VXF, dx, m.method_vector), + vector_transport_direction!(M.manifold, VYM, px, VXM, dx, m.method_horizontal), + vector_transport_direction!(M.manifold, VYF, px, VXF, dx, m.method_vertical), return Y end @doc raw""" - vector_transport_to(M::VectorBundle, p, X, q, m::VectorBundleProductVectorTransport) + vector_transport_to(M::VectorBundle, p, X, q, m::FiberBundleProductVectorTransport) Compute the vector transport the tangent vector `X`at `p` to `q` on the -[`VectorBundle`](@ref) `M` using the [`VectorBundleProductVectorTransport`](@ref) `m`. +[`VectorBundle`](@ref) `M` using the [`FiberBundleProductVectorTransport`](@ref) `m`. """ vector_transport_to( ::VectorBundle, ::Any, ::Any, ::Any, - ::VectorBundleProductVectorTransport, -) - -function _vector_transport_to( - M::VectorBundle, - p, - X, - q, - m::VectorBundleProductVectorTransport, + ::FiberBundleProductVectorTransport, ) - px, pVx = submanifold_components(M.manifold, p) - VXM, VXF = submanifold_components(M.manifold, X) - qx, qVx = submanifold_components(M.manifold, q) - return ArrayPartition( - vector_transport_to(M.manifold, px, VXM, qx, m.method_point), - vector_transport_to(M.manifold, px, VXF, qx, m.method_vector), - ) -end function vector_transport_to(M::VectorBundle, p, X, q) return vector_transport_to(M, p, X, q, M.vector_transport) @@ -1197,118 +372,47 @@ function vector_transport_to!( p, X, q, - m::VectorBundleProductVectorTransport, + m::FiberBundleProductVectorTransport, ) px, pVx = submanifold_components(M.manifold, p) VXM, VXF = submanifold_components(M.manifold, X) VYM, VYF = submanifold_components(M.manifold, Y) qx, qVx = submanifold_components(M.manifold, q) - vector_transport_to!(M.manifold, VYM, px, VXM, qx, m.method_point) - vector_transport_to!(M.manifold, VYF, px, VXF, qx, m.method_vector) + vector_transport_to!(M.manifold, VYM, px, VXM, qx, m.method_horizontal) + bundle_transport_tangent_to!(M, VYF, px, pVx, VXF, qx, m.method_vertical) return Y end -function vector_transport_to!( - M::TangentSpaceAtPoint, - Y, + +function _vector_transport_direction( + M::VectorBundle, p, X, - q, - m::AbstractVectorTransportMethod, + d, + m::FiberBundleProductVectorTransport, ) - return copyto!(M.fiber.manifold, Y, p, X) + px, pVx = submanifold_components(M.manifold, p) + VXM, VXF = submanifold_components(M.manifold, X) + dx, dVx = submanifold_components(M.manifold, d) + return ArrayPartition( + vector_transport_direction(M.manifold, px, VXM, dx, m.method_horizontal), + bundle_transport_tangent_direction(M, px, pVx, VXF, dx, m.method_vertical), + ) end -function vector_transport_to!( - M::TangentBundleFibers, - Y, + +function _vector_transport_to( + M::VectorBundle, p, X, q, - m::AbstractVectorTransportMethod, + m::FiberBundleProductVectorTransport, ) - vector_transport_to!(M.manifold, Y, p, X, q, m) - return Y -end - -@inline function Base.view(x::ArrayPartition, M::VectorBundle, s::Symbol) - (s === :point) && return x.x[1] - (s === :vector) && return x.x[2] - throw(DomainError(s, "unknown component $s on $M.")) -end - -""" - zero_vector(B::VectorBundleFibers, p) - -Compute the zero vector from the vector space of type `B.fiber` at point `p` -from manifold `B.manifold`. -""" -function zero_vector(B::VectorBundleFibers, p) - X = allocate_result(B, zero_vector, p) - return zero_vector!(B, X, p) -end - -""" - zero_vector!(B::VectorBundleFibers, X, p) - -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)).", + px, pVx = submanifold_components(M.manifold, p) + VXM, VXF = submanifold_components(M.manifold, X) + qx, qVx = submanifold_components(M.manifold, q) + return ArrayPartition( + vector_transport_to(M.manifold, px, VXM, qx, m.method_horizontal), + bundle_transport_tangent_to(M, px, pVx, VXF, qx, m.method_vertical), ) end -function zero_vector!(B::TangentBundleFibers, X, p) - return zero_vector!(B.manifold, X, p) -end - -@doc raw""" - Y = Weingarten(M::VectorSpaceAtPoint, p, X, V) - Weingarten!(M::VectorSpaceAtPoint, Y, p, X, V) - -Compute the Weingarten map ``\mathcal W_p`` at `p` on the [`VectorSpaceAtPoint`](@ref) `M` with respect to the -tangent vector ``X \in T_p\mathcal M`` and the normal vector ``V \in N_p\mathcal M``. - -Since this a flat space by itself, the result is always the zero tangent vector. -""" -Weingarten(::VectorSpaceAtPoint, p, X, V) - -Weingarten!(::VectorSpaceAtPoint, Y, p, X, V) = fill!(Y, 0) - -@doc raw""" - zero_vector(B::VectorBundle, p) - -Zero tangent vector at point `p` from the vector bundle `B` -over manifold `B.fiber` (denoted $\mathcal M$). The zero vector belongs to the space $T_{p}B$ - -Notation: - * The point $p = (x_p, V_p)$ where $x_p ∈ \mathcal M$ and $V_p$ belongs to the - fiber $F=π^{-1}(\{x_p\})$ of the vector bundle $B$ where $π$ is the - canonical projection of that vector bundle $B$. -The zero vector is calculated as - -$\mathbf{0}_{p} = (\mathbf{0}_{x_p}, \mathbf{0}_F)$ - -where $\mathbf{0}_{x_p}$ is the zero tangent vector from $T_{x_p}\mathcal M$ and -$\mathbf{0}_F$ is the zero element of the vector space $F$. -""" -zero_vector(::VectorBundle, ::Any...) - -@doc raw""" - zero_vector(M::TangentSpaceAtPoint, p) - -Zero tangent vector at point `p` from the tangent space `M`, that is the zero tangent vector -at point `M.point`. -""" -zero_vector(::TangentSpaceAtPoint, ::Any...) - -function zero_vector!(B::VectorBundle, X, p) - xp, Vp = submanifold_components(B.manifold, p) - VXM, VXF = submanifold_components(B.manifold, X) - zero_vector!(B.manifold, VXM, xp) - zero_vector!(B.fiber, VXF, Vp) - return X -end -function zero_vector!(M::TangentSpaceAtPoint, X, p) - return zero_vector!(M.fiber.manifold, X, M.point) -end +Base.show(io::IO, vb::CotangentBundle) = print(io, "CotangentBundle($(vb.manifold))") diff --git a/src/manifolds/VectorFiber.jl b/src/manifolds/VectorFiber.jl new file mode 100644 index 0000000000..96bb07ae2a --- /dev/null +++ b/src/manifolds/VectorFiber.jl @@ -0,0 +1,33 @@ + +""" + TensorProductType(spaces::VectorSpaceType...) + +Vector space type corresponding to the tensor product of given vector space +types. +""" +struct TensorProductType{TS<:Tuple} <: VectorSpaceType + spaces::TS +end + +TensorProductType(spaces::VectorSpaceType...) = TensorProductType{typeof(spaces)}(spaces) + +function inner(B::CotangentSpace, p, X, Y) + return inner( + B.manifold, + B.point, + sharp(B.manifold, B.point, X), + sharp(B.manifold, B.point, Y), + ) +end + +function Base.show(io::IO, tpt::TensorProductType) + return print(io, "TensorProductType(", join(tpt.spaces, ", "), ")") +end + +function vector_space_dimension(M::AbstractManifold, V::TensorProductType) + dim = 1 + for space in V.spaces + dim *= fiber_dimension(M, space) + end + return dim +end diff --git a/src/product_representations.jl b/src/product_representations.jl index 3930c9060d..8b13789179 100644 --- a/src/product_representations.jl +++ b/src/product_representations.jl @@ -1,208 +1 @@ -@doc raw""" - submanifold_component(M::AbstractManifold, p, i::Integer) - submanifold_component(M::AbstractManifold, p, ::Val(i)) where {i} - submanifold_component(p, i::Integer) - submanifold_component(p, ::Val(i)) where {i} - -Project the product array `p` on `M` to its `i`th component. A new array is returned. -""" -submanifold_component(::Any...) -@inline function submanifold_component(M::AbstractManifold, p, i::Integer) - return submanifold_component(M, p, Val(i)) -end -@inline submanifold_component(M::AbstractManifold, p, i::Val) = submanifold_component(p, i) -@inline submanifold_component(p, ::Val{I}) where {I} = p.parts[I] -@inline submanifold_component(p::ArrayPartition, ::Val{I}) where {I} = p.x[I] -@inline submanifold_component(p, i::Integer) = submanifold_component(p, Val(i)) - -@doc raw""" - submanifold_components(M::AbstractManifold, p) - submanifold_components(p) - -Get the projected components of `p` on the submanifolds of `M`. The components are returned in a Tuple. -""" -submanifold_components(::Any...) -@inline submanifold_components(::AbstractManifold, p) = submanifold_components(p) -@inline submanifold_components(p) = p.parts -@inline submanifold_components(p::ArrayPartition) = p.x - -function _show_component(io::IO, v; pre="", head="") - sx = sprint(show, "text/plain", v, context=io, sizehint=0) - sx = replace(sx, '\n' => "\n$(pre)") - return print(io, head, pre, sx) -end - -function _show_component_range(io::IO, vs, range; pre="", sym="Component ") - for i in range - _show_component(io, vs[i]; pre=pre, head="\n$(sym)$(i) =\n") - end - return nothing -end - -function _show_product_repr(io::IO, x; name="Product representation", nmax=4) - n = length(x.parts) - print(io, "$(name) with $(n) submanifold component$(n == 1 ? "" : "s"):") - half_nmax = div(nmax, 2) - pre = " " - sym = " Component " - if n ≤ nmax - _show_component_range(io, x.parts, 1:n; pre=pre, sym=sym) - else - _show_component_range(io, x.parts, 1:half_nmax; pre=pre, sym=sym) - print(io, "\n ⋮") - _show_component_range(io, x.parts, (n - half_nmax + 1):n; pre=pre, sym=sym) - end - return nothing -end - -""" - ProductRepr(parts) - -A more general but slower representation of points and tangent vectors on -a product manifold. - -# Example: - -A product point on a product manifold `Sphere(2) × Euclidean(2)` might be -created as - - ProductRepr([1.0, 0.0, 0.0], [2.0, 3.0]) - -where `[1.0, 0.0, 0.0]` is the part corresponding to the sphere factor -and `[2.0, 3.0]` is the part corresponding to the euclidean manifold. - - -!!! warning - - `ProductRepr` is deprecated and will be removed in a future release. - Please use `ArrayPartition` instead. -""" -struct ProductRepr{TM<:Tuple} - parts::TM -end - -function ProductRepr(points...) - Base.depwarn( - "`ProductRepr` will be deprecated in a future release. " * - "Please use `ArrayPartition` instead of `ProductRepr`.", - :ProductRepr, - ) - return ProductRepr{typeof(points)}(points) -end - -Base.:(==)(x::ProductRepr, y::ProductRepr) = x.parts == y.parts - -@inline function number_eltype(x::ProductRepr) - @inline eti_to_one(eti) = one(number_eltype(eti)) - return typeof(sum(map(eti_to_one, x.parts))) -end - -allocate(x::ProductRepr) = ProductRepr(map(allocate, submanifold_components(x))...) -function allocate(x::ProductRepr, T::Type) - return ProductRepr(map(t -> allocate(t, T), submanifold_components(x))...) -end -allocate(::ProductRepr, ::Type{T}, s::Size{S}) where {S,T} = Vector{T}(undef, S) -allocate(::ProductRepr, ::Type{T}, s::Integer) where {T} = Vector{T}(undef, s) -allocate(a::AbstractArray{<:ProductRepr}) = map(allocate, a) - -Base.copy(x::ProductRepr) = ProductRepr(map(copy, x.parts)) - -function Base.copyto!(x::ProductRepr, y::Union{ProductRepr,ArrayPartition}) - map(copyto!, submanifold_components(x), submanifold_components(y)) - return x -end -function Base.copyto!(x::ArrayPartition, y::ProductRepr) - map(copyto!, submanifold_components(x), submanifold_components(y)) - return x -end - -function Base.:+(v1::ProductRepr, v2::ProductRepr) - return ProductRepr(map(+, submanifold_components(v1), submanifold_components(v2))...) -end - -function Base.:-(v1::ProductRepr, v2::ProductRepr) - return ProductRepr(map(-, submanifold_components(v1), submanifold_components(v2))...) -end -Base.:-(v::ProductRepr) = ProductRepr(map(-, submanifold_components(v))) - -Base.:*(a::Number, v::ProductRepr) = ProductRepr(map(t -> a * t, submanifold_components(v))) -Base.:*(v::ProductRepr, a::Number) = ProductRepr(map(t -> t * a, submanifold_components(v))) - -Base.:/(v::ProductRepr, a::Number) = ProductRepr(map(t -> t / a, submanifold_components(v))) - -function Base.convert(::Type{ProductRepr{TPR}}, x::ProductRepr) where {TPR<:Tuple} - if @isdefined TPR - return ProductRepr(convert(TPR, submanifold_components(x))) - else - return x - end -end - -function Base.show(io::IO, ::MIME"text/plain", x::ProductRepr) - return _show_product_repr(io, x; name="ProductRepr") -end - -ManifoldsBase._get_vector_cache_broadcast(::ProductRepr) = Val(false) - -# Tuple-like broadcasting of ProductRepr - -function Broadcast.BroadcastStyle(::Type{<:ProductRepr}) - return Broadcast.Style{ProductRepr}() -end -function Broadcast.BroadcastStyle( - ::Broadcast.AbstractArrayStyle{0}, - b::Broadcast.Style{ProductRepr}, -) - return b -end - -Broadcast.instantiate(bc::Broadcast.Broadcasted{Broadcast.Style{ProductRepr},Nothing}) = bc -function Broadcast.instantiate(bc::Broadcast.Broadcasted{Broadcast.Style{ProductRepr}}) - Broadcast.check_broadcast_axes(bc.axes, bc.args...) - return bc -end - -Broadcast.broadcastable(v::ProductRepr) = v - -@inline function Base.copy(bc::Broadcast.Broadcasted{Broadcast.Style{ProductRepr}}) - dim = axes(bc) - length(dim) == 1 || throw(DimensionMismatch("ProductRepr only supports one dimension")) - N = length(dim[1]) - return ProductRepr(ntuple(k -> @inbounds(Broadcast._broadcast_getindex(bc, k)), Val(N))) -end - -Base.@propagate_inbounds Broadcast._broadcast_getindex(v::ProductRepr, I) = v.parts[I[1]] - -Base.axes(v::ProductRepr) = axes(v.parts) - -@inline function Base.copyto!( - dest::ProductRepr, - bc::Broadcast.Broadcasted{Broadcast.Style{ProductRepr}}, -) - axes(dest) == axes(bc) || Broadcast.throwdm(axes(dest), axes(bc)) - # Performance optimization: broadcast!(identity, dest, A) is equivalent to copyto!(dest, A) if indices match - if bc.f === identity && bc.args isa Tuple{ProductRepr} # only a single input argument to broadcast! - A = bc.args[1] - if axes(dest) == axes(A) - return copyto!(dest, A) - end - end - bc′ = Broadcast.preprocess(dest, bc) - # Performance may vary depending on whether `@inbounds` is placed outside the - # for loop or not. (cf. https://github.com/JuliaLang/julia/issues/38086) - @inbounds @simd for I in eachindex(bc′) - copyto!(dest.parts[I], bc′[I]) - end - return dest -end - -## ArrayPartition - -ManifoldsBase._get_vector_cache_broadcast(::ArrayPartition) = Val(false) - -allocate(a::AbstractArray{<:ArrayPartition}) = map(allocate, a) -allocate(x::ArrayPartition) = ArrayPartition(map(allocate, x.x)...) -function allocate(x::ArrayPartition, T::Type) - return ArrayPartition(map(t -> allocate(t, T), submanifold_components(x))...) -end diff --git a/src/projected_distribution.jl b/src/projected_distribution.jl index 28dc84a457..1d707e417a 100644 --- a/src/projected_distribution.jl +++ b/src/projected_distribution.jl @@ -65,7 +65,7 @@ end Distributions.support(d::ProjectedPointDistribution) = MPointSupport(d.manifold) """ - ProjectedFVectorDistribution(type::VectorBundleFibers, p, d, project!) + ProjectedFVectorDistribution(type::VectorSpaceFiber, p, d, project!) Generates a random vector from ambient space of manifold `type.manifold` at point `p` and projects it to vector space of type `type` using function @@ -74,33 +74,23 @@ Generated arrays are of type `TResult`. """ struct ProjectedFVectorDistribution{ TResult, - TSpace<:VectorBundleFibers, - ManifoldPoint, + TSpace<:VectorSpaceFiber, TD<:Distribution, TProj, -} <: FVectorDistribution{TSpace,ManifoldPoint} +} <: FVectorDistribution{TSpace} type::TSpace - point::ManifoldPoint distribution::TD project!::TProj end function ProjectedFVectorDistribution( - type::VectorBundleFibers, - p, + type::VectorSpaceFiber, d::Distribution, project!, ::TResult, ) where {TResult} - return ProjectedFVectorDistribution{ - TResult, - typeof(type), - typeof(p), - typeof(d), - typeof(project!), - }( + return ProjectedFVectorDistribution{TResult,typeof(type),typeof(d),typeof(project!)}( type, - p, d, project!, ) @@ -110,8 +100,8 @@ function Random.rand( rng::AbstractRNG, d::ProjectedFVectorDistribution{TResult}, ) where {TResult} - X = convert(TResult, reshape(rand(rng, d.distribution), size(d.point))) - return d.project!(d.type, X, d.point, X) + X = convert(TResult, reshape(rand(rng, d.distribution), size(d.type.point))) + return d.project!(d.type.manifold, X, d.type.point, X) end function Distributions._rand!( @@ -130,10 +120,10 @@ Normal distribution in ambient space with standard deviation `σ` projected to tangent space at `p`. """ function normal_tvector_distribution(M::AbstractManifold, p, σ) - d = Distributions.MvNormal(zero(vec(p)), σ) - return ProjectedFVectorDistribution(TangentBundleFibers(M), p, d, project!, p) + d = Distributions.MvNormal(zero(vec(p)), σ * I) + return ProjectedFVectorDistribution(TangentSpace(M, p), d, project!, p) end function Distributions.support(tvd::ProjectedFVectorDistribution) - return FVectorSupport(tvd.type, tvd.point) + return FVectorSupport(tvd.type) end diff --git a/src/trait_recursion_breaking.jl b/src/trait_recursion_breaking.jl index 611f01ae90..451417ea2c 100644 --- a/src/trait_recursion_breaking.jl +++ b/src/trait_recursion_breaking.jl @@ -1,5 +1,6 @@ # An unfortunate consequence of Julia's method recursion limitations +# (the code below makes some calls of `isapprox` faster) for trait_type in [ TraitList{<:IsDefaultMetric}, diff --git a/src/utils.jl b/src/utils.jl index d934ce8c9e..d12937d426 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -207,20 +207,6 @@ unrealify!(Y, X, ::typeof(ℝ), args...) = copyto!(Y, X) @generated maybesize(s::Size{S}) where {S} = prod(S) > 100 ? S : :(s) -""" - select_from_tuple(t::NTuple{N, Any}, positions::Val{P}) - -Selects elements of tuple `t` at positions specified by the second argument. -For example `select_from_tuple(("a", "b", "c"), Val((3, 1, 1)))` returns -`("c", "a", "a")`. -""" -@generated function select_from_tuple(t::NTuple{N,Any}, positions::Val{P}) where {N,P} - for k in P - (k < 0 || k > N) && error("positions must be between 1 and $N") - end - return Expr(:tuple, [Expr(:ref, :t, k) for k in P]...) -end - @doc raw""" symmetrize!(Y, X) @@ -286,56 +272,6 @@ function isnormal(x; kwargs...) end isnormal(::LinearAlgebra.RealHermSymComplexHerm; kwargs...) = true -""" - ziptuples(a, b[, c[, d[, e]]]) - -Zips tuples `a`, `b`, and remaining in a fast, type-stable way. If they have different -lengths, the result is trimmed to the length of the shorter tuple. -""" -@generated function ziptuples(a::NTuple{N,Any}, b::NTuple{M,Any}) where {N,M} - ex = Expr(:tuple) - for i in 1:min(N, M) - push!(ex.args, :((a[$i], b[$i]))) - end - return ex -end -@generated function ziptuples( - a::NTuple{N,Any}, - b::NTuple{M,Any}, - c::NTuple{L,Any}, -) where {N,M,L} - ex = Expr(:tuple) - for i in 1:min(N, M, L) - push!(ex.args, :((a[$i], b[$i], c[$i]))) - end - return ex -end -@generated function ziptuples( - a::NTuple{N,Any}, - b::NTuple{M,Any}, - c::NTuple{L,Any}, - d::NTuple{K,Any}, -) where {N,M,L,K} - ex = Expr(:tuple) - for i in 1:min(N, M, L, K) - push!(ex.args, :((a[$i], b[$i], c[$i], d[$i]))) - end - return ex -end -@generated function ziptuples( - a::NTuple{N,Any}, - b::NTuple{M,Any}, - c::NTuple{L,Any}, - d::NTuple{K,Any}, - e::NTuple{J,Any}, -) where {N,M,L,K,J} - ex = Expr(:tuple) - for i in 1:min(N, M, L, K, J) - push!(ex.args, :((a[$i], b[$i], c[$i], d[$i], e[$i]))) - end - return ex -end - _eps_safe(::Type{T}) where {T<:Integer} = zero(T) _eps_safe(::Type{T}) where {T<:Real} = eps(T) _eps_safe(::Type{T}) where {T<:Number} = eps(real(T)) diff --git a/test/ambiguities.jl b/test/ambiguities.jl index 4c94b54232..b46f568017 100644 --- a/test/ambiguities.jl +++ b/test/ambiguities.jl @@ -1,5 +1,5 @@ @testset "Ambiguities" begin - if VERSION.prerelease == () && !Sys.iswindows() && VERSION < v"1.9.0" + if VERSION.prerelease == () && !Sys.iswindows() && VERSION < v"1.10.0" mbs = Test.detect_ambiguities(ManifoldsBase) # Interims solution until we follow what was proposed in # https://discourse.julialang.org/t/avoid-ambiguities-with-individual-number-element-identity/62465/2 @@ -17,7 +17,7 @@ # Interims solution until we follow what was proposed in # https://discourse.julialang.org/t/avoid-ambiguities-with-individual-number-element-identity/62465/2 fms = filter(x -> !any(has_type_in_signature.(x, Identity)), ms) - FMS_LIMIT = 39 + FMS_LIMIT = 47 println("Number of Manifolds.jl ambiguities: $(length(fms))") if length(fms) > FMS_LIMIT for amb in fms diff --git a/test/differentiation.jl b/test/differentiation.jl index df9835e4c5..35a9dca2d7 100644 --- a/test/differentiation.jl +++ b/test/differentiation.jl @@ -180,7 +180,7 @@ end q2 = [1.0, 0.0, 0.0] f2(X) = [0.0 0.0 0.0; 0.0 2.0 -1.0; 0.0 -3.0 1.0] * X - Tq2s2 = TangentSpaceAtPoint(s2, q2) + Tq2s2 = TangentSpace(s2, q2) @test isapprox( Manifolds.jacobian(Tq2s2, Tq2s2, f2, zero_vector(s2, q2), rb_onb_default), [2.0 -1.0; -3.0 1.0], @@ -188,7 +188,7 @@ end q3 = [0.0, 1.0, 0.0] f3(X) = [0.0 2.0 1.0; 0.0 0.0 0.0; 0.0 5.0 1.0] * X - Tq3s2 = TangentSpaceAtPoint(s2, q3) + Tq3s2 = TangentSpace(s2, q3) @test isapprox( Manifolds.jacobian(Tq2s2, Tq3s2, f3, zero_vector(s2, q2), rb_onb_default), [-2.0 -1.0; 5.0 1.0], diff --git a/test/groups/circle_group.jl b/test/groups/circle_group.jl index 48b6435d88..b11672ab6f 100644 --- a/test/groups/circle_group.jl +++ b/test/groups/circle_group.jl @@ -1,9 +1,8 @@ include("../utils.jl") include("group_utils.jl") -# TODO: remove after bug in StaticArray is fixed -@inline Base.copy(a::SizedArray) = __copy(a) -@inline __copy(a::SizedArray{S,T}) where {S,T} = SizedArray{S,T}(copy(a.data)) +using Manifolds: + LeftForwardAction, LeftBackwardAction, RightForwardAction, RightBackwardAction @testset "Circle group" begin G = CircleGroup() @@ -28,8 +27,8 @@ include("group_utils.jl") @test identity_element(G, fill(1.0f0)) == fill(1.0f0) @test !is_point(G, Identity(AdditionOperation())) ef = Identity(AdditionOperation()) - @test_throws DomainError is_point(G, ef, true) - @test_throws DomainError is_vector(G, ef, X, true; check_base_point=true) + @test_throws DomainError is_point(G, ef; error=:error) + @test_throws DomainError is_vector(G, ef, X, true; error=:error) end @testset "scalar points" begin @@ -167,4 +166,7 @@ end # issue #489 @test vee(G, [0.0], [1.5]) isa Vector + + # basis + @test number_of_coordinates(G, DefaultOrthonormalBasis()) == 1 end diff --git a/test/groups/general_linear.jl b/test/groups/general_linear.jl index b41ca1abd4..b3e76768fb 100644 --- a/test/groups/general_linear.jl +++ b/test/groups/general_linear.jl @@ -64,20 +64,20 @@ using NLsolve @testset "Real" begin G = GeneralLinear(3) - @test_throws ManifoldDomainError is_point(G, randn(2, 3), true) - @test_throws ManifoldDomainError is_point(G, randn(2, 2), true) - @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 3, 3), true) - @test_throws DomainError is_point(G, zeros(3, 3), true) - @test_throws DomainError is_point(G, Float64[0 0 0; 0 1 1; 1 1 1], true) - @test is_point(G, Float64[0 0 1; 0 1 1; 1 1 1], true) - @test is_point(G, Identity(G), true) - @test_throws ManifoldDomainError is_vector( + @test_throws ManifoldDomainError is_point(G, randn(2, 3); error=:error) + @test_throws ManifoldDomainError is_point(G, randn(2, 2); error=:error) + @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 3, 3); error=:error) + @test_throws DomainError is_point(G, zeros(3, 3); error=:error) + @test_throws DomainError is_point(G, Float64[0 0 0; 0 1 1; 1 1 1]; error=:error) + @test is_point(G, Float64[0 0 1; 0 1 1; 1 1 1]; error=:error) + @test is_point(G, Identity(G); error=:error) + @test_throws DomainError is_vector( G, Float64[0 1 1; 0 1 1; 1 0 0], - randn(3, 3), - true, + randn(3, 3); + error=:error, ) - @test is_vector(G, Float64[0 0 1; 0 1 1; 1 1 1], randn(3, 3), true) + @test is_vector(G, Float64[0 0 1; 0 1 1; 1 1 1], randn(3, 3); error=:error) types = [Matrix{Float64}] pts = [ @@ -140,20 +140,24 @@ using NLsolve @testset "Complex" begin G = GeneralLinear(2, ℂ) - @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 2, 3), true) - @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 3, 3), true) - @test_throws DomainError is_point(G, zeros(2, 2), true) - @test_throws DomainError is_point(G, ComplexF64[1 im; 1 im], true) - @test is_point(G, ComplexF64[1 1; im 1], true) - @test is_point(G, Identity(G), true) - @test_throws ManifoldDomainError is_point(G, Float64[0 0 0; 0 1 1; 1 1 1], true) - @test_throws ManifoldDomainError is_vector( + @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 2, 3); error=:error) + @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 3, 3); error=:error) + @test_throws DomainError is_point(G, zeros(2, 2); error=:error) + @test_throws DomainError is_point(G, ComplexF64[1 im; 1 im]; error=:error) + @test is_point(G, ComplexF64[1 1; im 1]; error=:error) + @test is_point(G, Identity(G); error=:error) + @test_throws ManifoldDomainError is_point( + G, + Float64[0 0 0; 0 1 1; 1 1 1]; + error=:error, + ) + @test_throws DomainError is_vector( G, ComplexF64[im im; im im], - randn(ComplexF64, 2, 2), - true, + randn(ComplexF64, 2, 2); + error=:error, ) - @test is_vector(G, ComplexF64[1 im; im im], randn(ComplexF64, 2, 2), true) + @test is_vector(G, ComplexF64[1 im; im im], randn(ComplexF64, 2, 2); error=:error) types = [Matrix{ComplexF64}] pts = [ @@ -197,4 +201,9 @@ using NLsolve ) end end + @testset "field parameter" begin + G = GeneralLinear(3; parameter=:field) + @test typeof(get_embedding(G)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(G) == "GeneralLinear(3, ℝ; parameter=:field)" + end end diff --git a/test/groups/general_unitary_groups.jl b/test/groups/general_unitary_groups.jl index 18c3463067..a3829e2990 100644 --- a/test/groups/general_unitary_groups.jl +++ b/test/groups/general_unitary_groups.jl @@ -3,7 +3,7 @@ include("group_utils.jl") @testset "General Unitary Groups" begin # SpecialUnitary -> injectivity us also π√2 - SU3 = Manifolds.GeneralUnitaryMatrices{3,ℂ,Manifolds.DeterminantOneMatrices}() + SU3 = SpecialUnitary(3) @test injectivity_radius(SU3) == π * √2 @testset "Orthogonal Group" begin O2 = Orthogonal(2) @@ -147,30 +147,30 @@ include("group_utils.jl") p = ones(2, 2) q = project(SU2, p) - @test is_point(SU2, q, true) + @test is_point(SU2, q; error=:error) q2 = allocate(q) project!(SU2, q2, p) @test q == q2 p2 = copy(p) p2[1, 1] = -1 q2 = project(SU2, p2) - @test is_point(SU2, q2, true) + @test is_point(SU2, q2; error=:error) p3 = [2.0 0; 0.0 2.0] #real pos determinant @test project(SU2, p3) == p3 ./ 2 Xe = ones(2, 2) X = project(SU2, q, Xe) @test is_vector(SU2, q, X) - @test_throws ManifoldDomainError is_vector(SU2, p, X, true, true) # base point wrong - @test_throws DomainError is_vector(SU2, q, Xe, true, true) # Xe not skew hermitian + @test_throws DomainError is_vector(SU2, p, X, true; error=:error) # base point wrong + @test_throws DomainError is_vector(SU2, q, Xe, true; error=:error) # Xe not skew hermitian @test_throws DomainError is_vector( SU2, Identity(AdditionOperation()), Xe, - true, - true, + true; + error=:error, ) # base point wrong e = Identity(MultiplicationOperation()) - @test_throws DomainError is_vector(SU2, e, Xe, true, true) # Xe not skew hermitian + @test_throws DomainError is_vector(SU2, e, Xe, true; error=:error) # Xe not skew hermitian @test manifold_volume(SpecialUnitary(1)) ≈ 1 @test manifold_volume(SpecialUnitary(2)) ≈ 2 * π^2 @@ -230,4 +230,18 @@ include("group_utils.jl") @test is_vector(Rotations(4), E, X3b) @test X3a[2, 3] ≈ π end + + @testset "field parameter" begin + G = Orthogonal(2; parameter=:field) + @test repr(G) == "Orthogonal(2; parameter=:field)" + + SU3 = SpecialUnitary(3; parameter=:field) + @test repr(SU3) == "SpecialUnitary(3; parameter=:field)" + + G = Unitary(3, ℂ; parameter=:field) + @test repr(G) == "Unitary(3; parameter=:field)" + + G = Unitary(3, ℍ; parameter=:field) + @test repr(G) == "Unitary(3, ℍ; parameter=:field)" + end end diff --git a/test/groups/group_operation_action.jl b/test/groups/group_operation_action.jl index d32bddd68c..3992a90c5c 100644 --- a/test/groups/group_operation_action.jl +++ b/test/groups/group_operation_action.jl @@ -2,6 +2,9 @@ include("../utils.jl") include("group_utils.jl") +using Manifolds: + LeftForwardAction, LeftBackwardAction, RightForwardAction, RightBackwardAction + @testset "Group operation action" begin G = GroupManifold(NotImplementedManifold(), Manifolds.MultiplicationOperation()) A_left_fwd = GroupOperationAction(G) @@ -13,11 +16,9 @@ include("group_utils.jl") @test group_manifold(A_left_fwd) === G @test base_group(A_left_fwd) == G - @test repr(A_left_fwd) == "GroupOperationAction($(repr(G)), LeftForwardAction())" - @test repr(A_right_back) == "GroupOperationAction($(repr(G)), RightBackwardAction())" - - @test switch_direction(LeftForwardAction()) === RightBackwardAction() - @test switch_direction(RightBackwardAction()) === LeftForwardAction() + @test repr(A_left_fwd) == "GroupOperationAction($(repr(G)), (LeftAction(), LeftSide()))" + @test repr(A_right_back) == + "GroupOperationAction($(repr(G)), (RightAction(), RightSide()))" for type in types a_pts = convert.(type, [reshape(i:(i + 3), 2, 2) for i in 1:3]) @@ -32,7 +33,7 @@ include("group_utils.jl") test_optimal_alignment=false, test_diff=false, atol=atol, - test_switch_direction=Manifolds.SimultaneousSwitch(), + test_switch_direction=true, ) test_action( @@ -42,7 +43,7 @@ include("group_utils.jl") test_optimal_alignment=false, test_diff=false, atol=atol, - test_switch_direction=Manifolds.SimultaneousSwitch(), + test_switch_direction=true, ) end @@ -66,8 +67,9 @@ include("group_utils.jl") @test group_manifold(A_left_fwd) === G @test base_group(A_left_fwd) == G - @test repr(A_left_fwd) == "GroupOperationAction($(repr(G)), LeftForwardAction())" - @test repr(A_right_back) == "GroupOperationAction($(repr(G)), RightBackwardAction())" + @test repr(A_left_fwd) == "GroupOperationAction($(repr(G)), (LeftAction(), LeftSide()))" + @test repr(A_right_back) == + "GroupOperationAction($(repr(G)), (RightAction(), RightSide()))" test_action( A_left_fwd, @@ -76,7 +78,7 @@ include("group_utils.jl") X_pts; test_optimal_alignment=true, test_diff=true, - test_switch_direction=Manifolds.SimultaneousSwitch(), + test_switch_direction=true, ) test_action( @@ -86,7 +88,7 @@ include("group_utils.jl") X_pts; test_optimal_alignment=true, test_diff=true, - test_switch_direction=Manifolds.SimultaneousSwitch(), + test_switch_direction=true, ) test_action( @@ -96,7 +98,7 @@ include("group_utils.jl") X_pts; test_optimal_alignment=true, test_diff=true, - test_switch_direction=Manifolds.SimultaneousSwitch(), + test_switch_direction=true, ) test_action( @@ -106,7 +108,7 @@ include("group_utils.jl") X_pts; test_optimal_alignment=true, test_diff=true, - test_switch_direction=Manifolds.SimultaneousSwitch(), + test_switch_direction=true, ) @testset "apply_diff_group" begin diff --git a/test/groups/groups_general.jl b/test/groups/groups_general.jl index 5ca867fa9f..39a445722c 100644 --- a/test/groups/groups_general.jl +++ b/test/groups/groups_general.jl @@ -4,6 +4,9 @@ using Base: decode_overlong include("../utils.jl") include("group_utils.jl") +using Manifolds: + LeftForwardAction, LeftBackwardAction, RightForwardAction, RightBackwardAction + @testset "General group tests" begin @testset "Not implemented operation" begin G = GroupManifold(NotImplementedManifold(), NotImplementedOperation()) @@ -26,7 +29,7 @@ include("group_utils.jl") Identity(AdditionOperation()), Identity(MultiplicationOperation()), ) - @test_throws DomainError is_point(G, Identity(AdditionOperation()), true) + @test_throws DomainError is_point(G, Identity(AdditionOperation()); error=:error) @test is_point(G, eg) @test_throws MethodError is_identity(G, 1) # same error as before i.e. dispatch isapprox works @test Manifolds.check_size(G, eg) === nothing @@ -37,9 +40,14 @@ include("group_utils.jl") ) isa DomainError @test !is_vector(G, Identity(AdditionOperation()), X) # wrong identity - @test_throws DomainError is_vector(G, Identity(AdditionOperation()), X, true) + @test_throws DomainError is_vector( + G, + Identity(AdditionOperation()), + X; + error=:error, + ) # identity_element for G not implemented - @test_throws MethodError is_vector(G, eg, X, true) + @test_throws MethodError is_vector(G, eg, X; error=:error) @test Identity(NotImplementedOperation()) === eg @test Identity(NotImplementedOperation) === eg @test !is_point(G, Identity(AdditionOperation())) @@ -126,34 +134,19 @@ include("group_utils.jl") end @testset "Action direction" begin - @test switch_direction(LeftBackwardAction()) === RightForwardAction() - @test switch_direction(LeftForwardAction()) === RightBackwardAction() - @test switch_direction(RightBackwardAction()) === LeftForwardAction() - @test switch_direction(RightForwardAction()) === LeftBackwardAction() - - @test switch_direction(LeftBackwardAction(), Manifolds.LeftRightSwitch()) === - RightBackwardAction() - @test switch_direction(LeftForwardAction(), Manifolds.LeftRightSwitch()) === - RightForwardAction() - @test switch_direction(RightBackwardAction(), Manifolds.LeftRightSwitch()) === - LeftBackwardAction() - @test switch_direction(RightForwardAction(), Manifolds.LeftRightSwitch()) === - LeftForwardAction() - - @test switch_direction(LeftBackwardAction(), Manifolds.ForwardBackwardSwitch()) === - LeftForwardAction() - @test switch_direction(LeftForwardAction(), Manifolds.ForwardBackwardSwitch()) === - LeftBackwardAction() - @test switch_direction(RightBackwardAction(), Manifolds.ForwardBackwardSwitch()) === - RightForwardAction() - @test switch_direction(RightForwardAction(), Manifolds.ForwardBackwardSwitch()) === - RightBackwardAction() + @test switch_direction(LeftAction()) === RightAction() + @test switch_direction(RightAction()) === LeftAction() G = GroupManifold(NotImplementedManifold(), NotImplementedOperation()) @test Manifolds._action_order(G, 1, 2, LeftForwardAction()) === (1, 2) @test Manifolds._action_order(G, 1, 2, RightBackwardAction()) === (2, 1) end + @testset "Action side" begin + @test switch_side(LeftSide()) === RightSide() + @test switch_side(RightSide()) === LeftSide() + end + @testset "Addition operation" begin G = GroupManifold(NotImplementedManifold(), Manifolds.AdditionOperation()) test_group( @@ -296,7 +289,7 @@ include("group_utils.jl") end end -struct NotImplementedAction <: AbstractGroupAction{LeftForwardAction} end +struct NotImplementedAction <: AbstractGroupAction{LeftAction} end @testset "General group action tests" begin @testset "Not implemented operations" begin @@ -305,17 +298,17 @@ struct NotImplementedAction <: AbstractGroupAction{LeftForwardAction} end a = [1.0, 2.0] X = [1.0, 2.0] - @test_throws ErrorException apply(A, a, p) - @test_throws ErrorException apply!(A, p, a, p) - @test_throws ErrorException inverse_apply(A, a, p) - @test_throws ErrorException inverse_apply!(A, p, a, p) - @test_throws ErrorException apply_diff(A, a, p, X) - @test_throws ErrorException apply_diff!(A, X, p, a, X) - @test_throws ErrorException inverse_apply_diff(A, a, p, X) - @test_throws ErrorException inverse_apply_diff!(A, X, p, a, X) - @test_throws ErrorException compose(A, a, a) - @test_throws ErrorException compose!(A, a, a, a) - @test_throws ErrorException optimal_alignment(A, p, p) - @test_throws ErrorException optimal_alignment!(A, a, p, p) + @test_throws MethodError apply(A, a, p) + @test_throws MethodError apply!(A, p, a, p) + @test_throws MethodError inverse_apply(A, a, p) + @test_throws MethodError inverse_apply!(A, p, a, p) + @test_throws MethodError apply_diff(A, a, p, X) + @test_throws MethodError apply_diff!(A, X, p, a, X) + @test_throws MethodError inverse_apply_diff(A, a, p, X) + @test_throws MethodError inverse_apply_diff!(A, X, p, a, X) + @test_throws MethodError compose(A, a, a) + @test_throws MethodError compose!(A, a, a, a) + @test_throws MethodError optimal_alignment(A, p, p) + @test_throws MethodError optimal_alignment!(A, a, p, p) end end diff --git a/test/groups/heisenberg.jl b/test/groups/heisenberg.jl index 120bd43ab1..1e13d24387 100644 --- a/test/groups/heisenberg.jl +++ b/test/groups/heisenberg.jl @@ -55,4 +55,9 @@ include("group_utils.jl") test_rand_point=true, test_rand_tvector=true, ) + @testset "field parameter" begin + G = HeisenbergGroup(1; parameter=:field) + @test typeof(get_embedding(G)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(G) == "HeisenbergGroup(1; parameter=:field)" + end end diff --git a/test/groups/metric.jl b/test/groups/metric.jl index 793c7333cb..f759d20426 100644 --- a/test/groups/metric.jl +++ b/test/groups/metric.jl @@ -6,6 +6,9 @@ import Manifolds: local_metric using Manifolds: LeftInvariantMetric, RightInvariantMetric +using Manifolds: + LeftForwardAction, LeftBackwardAction, RightForwardAction, RightBackwardAction + struct TestInvariantMetricBase <: AbstractMetric end function active_traits( @@ -126,10 +129,10 @@ end end @testset "invariant metric direction" begin - @test direction(HasRightInvariantMetric()) === RightBackwardAction() - @test direction(HasLeftInvariantMetric()) === LeftForwardAction() - @test direction(HasRightInvariantMetric) === RightBackwardAction() - @test direction(HasLeftInvariantMetric) === LeftForwardAction() + @test direction_and_side(HasRightInvariantMetric()) === RightBackwardAction() + @test direction_and_side(HasLeftInvariantMetric()) === LeftForwardAction() + @test direction_and_side(HasRightInvariantMetric) === RightBackwardAction() + @test direction_and_side(HasLeftInvariantMetric) === LeftForwardAction() end @testset "invariant metrics on SE(3)" begin @@ -137,6 +140,8 @@ end for inv_metric in [LeftInvariantMetric(), RightInvariantMetric()] G = MetricManifold(SpecialEuclidean(3), inv_metric) + @test direction(G) == direction(inv_metric) + M = base_manifold(G) Rn = Rotations(3) p = Matrix(I, 3, 3) @@ -149,37 +154,35 @@ end ([-2.0, 1.0, 0.5], hat(Rn, p, [-1.0, -0.5, 1.1])), ] - for prod_type in [ProductRepr, ArrayPartition] - pts = [prod_type(tp...) for tp in tuple_pts] - X_pts = [prod_type(tX...) for tX in tuple_X] - - g1, g2 = pts[1:2] - t1, R1 = submanifold_components(g1) - t2, R2 = submanifold_components(g2) - g1g2 = prod_type(R1 * t2 + t1, R1 * R2) - @test isapprox(G, compose(G, g1, g2), g1g2) - - test_group( - G, - pts, - X_pts, - X_pts; - test_diff=true, - test_lie_bracket=true, - test_adjoint_action=true, - diff_convs=[(), (LeftForwardAction(),), (RightBackwardAction(),)], - ) - test_manifold( - G, - pts; - #basis_types_vecs=basis_types, - basis_types_to_from=basis_types, - is_mutating=true, - #test_inplace=true, - test_vee_hat=false, - exp_log_atol_multiplier=50, - ) - end + pts = [ArrayPartition(tp...) for tp in tuple_pts] + X_pts = [ArrayPartition(tX...) for tX in tuple_X] + + g1, g2 = pts[1:2] + t1, R1 = submanifold_components(g1) + t2, R2 = submanifold_components(g2) + g1g2 = ArrayPartition(R1 * t2 + t1, R1 * R2) + @test isapprox(G, compose(G, g1, g2), g1g2) + + test_group( + G, + pts, + X_pts, + X_pts; + test_diff=true, + test_lie_bracket=true, + test_adjoint_action=true, + diff_convs=[(), (LeftForwardAction(),), (RightBackwardAction(),)], + ) + test_manifold( + G, + pts; + #basis_types_vecs=basis_types, + basis_types_to_from=basis_types, + is_mutating=true, + #test_inplace=true, + test_vee_hat=false, + exp_log_atol_multiplier=50, + ) end end end diff --git a/test/groups/power_group.jl b/test/groups/power_group.jl index 164196e39b..30525c8466 100644 --- a/test/groups/power_group.jl +++ b/test/groups/power_group.jl @@ -30,7 +30,7 @@ include("group_utils.jl") pts, X_pts, X_pts; - atol=1e-9, + atol=2e-9, test_diff=true, test_log_from_identity=true, test_exp_from_identity=true, diff --git a/test/groups/product_group.jl b/test/groups/product_group.jl index 9b84f87978..9dc00ba148 100644 --- a/test/groups/product_group.jl +++ b/test/groups/product_group.jl @@ -29,93 +29,89 @@ using RecursiveArrayTools @test submanifold_component(G, i, 2) == Identity(Tn) end - for TRepr in (ProductRepr, ArrayPartition) - @testset "$TRepr" begin - pts = [TRepr(tp...) for tp in tuple_pts] - X_pts = [TRepr(tuple_v...)] + pts = [ArrayPartition(tp...) for tp in tuple_pts] + X_pts = [ArrayPartition(tuple_v...)] - @testset "setindex! and getindex" begin - p1 = pts[1] - p2 = allocate(p1) - @test p1[G, 1] === p1[M, 1] - p2[G, 1] = p1[M, 1] - @test p2[G, 1] == p1[M, 1] - end + @testset "setindex! and getindex" begin + p1 = pts[1] + p2 = allocate(p1) + @test p1[G, 1] === p1[M, 1] + p2[G, 1] = p1[M, 1] + @test p2[G, 1] == p1[M, 1] + end - @test compose(G, pts[1], Identity(G)) == pts[1] - @test compose(G, Identity(G), pts[1]) == pts[1] - test_group( - G, - pts, - X_pts, - X_pts; - test_diff=true, - test_exp_from_identity=true, - test_log_from_identity=true, - test_vee_hat_from_identity=true, - ) - @test isapprox( - G, - Identity(G), - exp_lie(G, X_pts[1]), - TRepr( - exp_lie(SOn, submanifold_component(X_pts[1], 1)), - exp_lie(Tn, submanifold_component(X_pts[1], 2)), - ), - ) - @test isapprox( - G, - Identity(G), - log_lie(G, pts[1]), - TRepr( - log_lie(SOn, submanifold_component(pts[1], 1)), - log_lie(Tn, submanifold_component(pts[1], 2)), - ), - ) - X = log_lie(G, pts[1]) - Z = zero_vector(G, pts[1]) - log_lie!(G, Z, pts[1]) - @test isapprox(G, pts[1], X, Z) - p = exp_lie(G, X) - q = identity_element(G) - @test is_identity(G, q) - @test isapprox(G, q, Identity(G)) - @test isapprox(G, Identity(G), q) - exp_lie!(G, q, X) - @test isapprox(G, p, q) - log_lie!(G, Z, Identity(G)) - @test isapprox(G, Identity(G), Z, zero_vector(G, identity_element(G))) - @test isapprox( - G, - Identity(G), - log_lie(G, Identity(G)), - zero_vector(G, identity_element(G)), - ) + @test compose(G, pts[1], Identity(G)) == pts[1] + @test compose(G, Identity(G), pts[1]) == pts[1] + test_group( + G, + pts, + X_pts, + X_pts; + test_diff=true, + test_exp_from_identity=true, + test_log_from_identity=true, + test_vee_hat_from_identity=true, + ) + @test isapprox( + G, + Identity(G), + exp_lie(G, X_pts[1]), + ArrayPartition( + exp_lie(SOn, submanifold_component(X_pts[1], 1)), + exp_lie(Tn, submanifold_component(X_pts[1], 2)), + ), + ) + @test isapprox( + G, + Identity(G), + log_lie(G, pts[1]), + ArrayPartition( + log_lie(SOn, submanifold_component(pts[1], 1)), + log_lie(Tn, submanifold_component(pts[1], 2)), + ), + ) + X = log_lie(G, pts[1]) + Z = zero_vector(G, pts[1]) + log_lie!(G, Z, pts[1]) + @test isapprox(G, pts[1], X, Z) + p = exp_lie(G, X) + q = identity_element(G) + @test is_identity(G, q) + @test isapprox(G, q, Identity(G)) + @test isapprox(G, Identity(G), q) + exp_lie!(G, q, X) + @test isapprox(G, p, q) + log_lie!(G, Z, Identity(G)) + @test isapprox(G, Identity(G), Z, zero_vector(G, identity_element(G))) + @test isapprox( + G, + Identity(G), + log_lie(G, Identity(G)), + zero_vector(G, identity_element(G)), + ) - @test compose(G, pts[1], Identity(G)) == pts[1] - @test compose(G, Identity(G), pts[1]) == pts[1] - test_group(G, pts, X_pts, X_pts; test_diff=true, test_mutating=false) - test_manifold(G, pts; is_mutating=false) - @test isapprox( - G, - exp_lie(G, X_pts[1]), - TRepr( - exp_lie(SOn, submanifold_component(X_pts[1], 1)), - exp_lie(Tn, submanifold_component(X_pts[1], 2)), - ), - ) - @test isapprox( - G, - log_lie(G, pts[1]), - TRepr( - log_lie(SOn, submanifold_component(pts[1], 1)), - log_lie(Tn, submanifold_component(pts[1], 2)), - ), - ) - end - end + @test compose(G, pts[1], Identity(G)) == pts[1] + @test compose(G, Identity(G), pts[1]) == pts[1] + test_group(G, pts, X_pts, X_pts; test_diff=true, test_mutating=false) + test_manifold(G, pts; is_mutating=false) + @test isapprox( + G, + exp_lie(G, X_pts[1]), + ArrayPartition( + exp_lie(SOn, submanifold_component(X_pts[1], 1)), + exp_lie(Tn, submanifold_component(X_pts[1], 2)), + ), + ) + @test isapprox( + G, + log_lie(G, pts[1]), + ArrayPartition( + log_lie(SOn, submanifold_component(pts[1], 1)), + log_lie(Tn, submanifold_component(pts[1], 2)), + ), + ) @test sprint(show, "text/plain", G) === """ ProductGroup with 2 subgroups: SpecialOrthogonal(3) - TranslationGroup(2; field = ℝ)""" + TranslationGroup(2; field=ℝ)""" end diff --git a/test/groups/rotation_action.jl b/test/groups/rotation_action.jl index 725b4a0f26..71be2acd73 100644 --- a/test/groups/rotation_action.jl +++ b/test/groups/rotation_action.jl @@ -6,12 +6,11 @@ include("group_utils.jl") M = Rotations(2) G = SpecialOrthogonal(2) A_left = RotationAction(Euclidean(2), G) - A_right = RotationAction(Euclidean(2), G, RightForwardAction()) + A_right = RotationAction(Euclidean(2), G, RightAction()) - @test repr(A_left) == - "RotationAction($(repr(Euclidean(2))), $(repr(G)), LeftForwardAction())" + @test repr(A_left) == "RotationAction($(repr(Euclidean(2))), $(repr(G)), LeftAction())" @test repr(A_right) == - "RotationAction($(repr(Euclidean(2))), $(repr(G)), RightForwardAction())" + "RotationAction($(repr(Euclidean(2))), $(repr(G)), RightAction())" types_a = [Matrix{Float64}] @@ -19,7 +18,7 @@ include("group_utils.jl") @test group_manifold(A_left) == Euclidean(2) @test base_group(A_left) == G - @test isa(A_left, AbstractGroupAction{<:LeftForwardAction}) + @test isa(A_left, AbstractGroupAction{LeftAction}) @test base_manifold(G) == M for (i, T_A, T_M) in zip(1:length(types_a), types_a, types_m) @@ -76,7 +75,7 @@ end @test group_manifold(A) == Euclidean(3) @test base_group(A) == G - @test isa(A, AbstractGroupAction{LeftForwardAction}) + @test isa(A, AbstractGroupAction{LeftAction}) @test base_manifold(G) == M for (i, T_A, T_M) in zip(1:length(types_a), types_a, types_m) diff --git a/test/groups/rotation_translation_action.jl b/test/groups/rotation_translation_action.jl new file mode 100644 index 0000000000..7c2c8fb6a3 --- /dev/null +++ b/test/groups/rotation_translation_action.jl @@ -0,0 +1,132 @@ + +include("../utils.jl") +include("group_utils.jl") + +@testset "Rotation-translation action" begin + G = SpecialEuclidean(2) + M = base_manifold(G) + A_left = RotationTranslationAction(Euclidean(2), G) + A_right = RotationTranslationAction(Euclidean(2), G, RightAction()) + + @test repr(A_left) == + "RotationTranslationAction($(repr(Euclidean(2))), $(repr(G)), LeftAction())" + @test repr(A_right) == + "RotationTranslationAction($(repr(Euclidean(2))), $(repr(G)), RightAction())" + + types_a = [ArrayPartition{Float64,Tuple{Vector{Float64},Matrix{Float64}}}] + + types_m = [Vector{Float64}] + + @test group_manifold(A_left) == Euclidean(2) + @test base_group(A_left) == G + @test isa(A_left, AbstractGroupAction{LeftAction}) + @test base_manifold(G) == M + + for (i, T_A, T_M) in zip(1:length(types_a), types_a, types_m) + angles = (0.0, π / 2, 2π / 3, π / 4) + translations = [[1.0, 0.0], [0.0, -2.0], [-1.0, 2.0]] + a_pts = + convert.( + T_A, + [ + ArrayPartition(t, [cos(ϕ) -sin(ϕ); sin(ϕ) cos(ϕ)]) for + (t, ϕ) in zip(translations, angles) + ], + ) + a_X_pts = map(a -> log_lie(G, a), a_pts) + m_pts = convert.(T_M, [[0.0, 1.0], [-1.0, 0.0], [1.0, 1.0]]) + X_pts = convert.(T_M, [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) + + atol = if eltype(T_M) == Float32 + 2e-7 + else + 1e-15 + end + test_action( + A_left, + a_pts, + m_pts, + X_pts; + test_optimal_alignment=false, + test_diff=true, + atol=atol, + ) + + test_action( + A_right, + a_pts, + m_pts, + X_pts; + test_optimal_alignment=false, + test_diff=true, + atol=atol, + ) + + @testset "apply_diff_group" begin + @test apply_diff_group(A_left, Identity(G), a_X_pts[1], m_pts[1]) ≈ + a_X_pts[1].x[2] * m_pts[1] + Y = similar(m_pts[1]) + apply_diff_group!(A_left, Y, Identity(G), a_X_pts[1], m_pts[1]) + @test Y ≈ a_X_pts[1].x[2] * m_pts[1] + end + end +end + +@testset "Matrix columnwise multiplication action" begin + M = Euclidean(2, 3) + G = SpecialEuclidean(2) + p1 = [ + 0.4385117672460505 -0.6877826444042382 0.24927087715818771 + -0.3830259932279294 0.35347460720654283 0.029551386021386548 + ] + p2 = [ + -0.42693314765896473 -0.3268567431952937 0.7537898908542584 + 0.3054740561061169 -0.18962848284149897 -0.11584557326461796 + ] + A = Manifolds.ColumnwiseSpecialEuclideanAction(M, G) + + @test group_manifold(A) === M + @test base_group(A) === SpecialEuclidean(2) + + a1 = ArrayPartition( + [1.0, 2.0], + [0.5851302132737501 -0.8109393525500014; 0.8109393525500014 0.5851302132737504], + ) + a2 = ArrayPartition( + [2.0, -1.0], + [0.903025374532402 -0.4295872122754759; 0.4295872122754759 0.9030253745324022], + ) + a3 = ArrayPartition( + [2.0, 0.0], + [0.5851302132737501 -0.8109393525500014; 0.8109393525500014 0.5851302132737504], + ) + @test isapprox( + apply(A, a1, p1), + [ + 1.567197334849809 0.3109111254828243 1.1218915396673668 + 2.1314863675092206 1.6490786599533187 2.2194349725374605 + ], + ) + @test isapprox( + inverse_apply(A, a1, p1), + [ + -2.2610332854401007 -2.3228048546690494 -2.0371886150121097 + -0.9390476037204165 0.40525761065242144 -0.5441732289245019 + ], + ) + @test apply(A, Identity(G), p1) === p1 + q = similar(p1) + apply!(A, q, a1, p1) + @test isapprox(q, apply(A, a1, p1)) + apply!(A, q, Identity(G), p1) + @test isapprox(q, p1) + test_action( + A, + [a1, a2, a3], + [p1, p2]; + test_optimal_alignment=true, + test_diff=false, + test_switch_direction=false, + atol=1e-14, + ) +end diff --git a/test/groups/semidirect_product_group.jl b/test/groups/semidirect_product_group.jl index 7a31bf49e7..49c24acdd3 100644 --- a/test/groups/semidirect_product_group.jl +++ b/test/groups/semidirect_product_group.jl @@ -18,42 +18,40 @@ include("group_utils.jl") ts2 = Vector{Float64}.([1:2, 2:3, 3:4]) .* 10 tuple_pts = [zip(ts1, ts2)...] - for prod_type in [ProductRepr, ArrayPartition] - pts = [prod_type(tp...) for tp in tuple_pts] - - @testset "setindex! and getindex" begin - p1 = pts[1] - p2 = allocate(p1) - @test p1[G, 1] === p1[M, 1] - p2[G, 1] = p1[M, 1] - @test p2[G, 1] == p1[M, 1] - end - - X = log(G, pts[1], pts[1]) - Y = zero_vector(G, pts[1]) - Z = Manifolds.allocate_result(G, zero_vector, pts[1]) - Z = zero_vector!(M, Z, pts[1]) - @test norm(G, pts[1], X) ≈ 0 - @test norm(G, pts[1], Y) ≈ 0 - @test norm(G, pts[1], Z) ≈ 0 - - e = Identity(G) - @test inv(G, e) === e - - @test compose(G, e, pts[1]) == pts[1] - @test compose(G, pts[1], e) == pts[1] - @test compose(G, e, e) === e - - # test in-place composition - o1 = copy(pts[1]) - compose!(G, o1, o1, pts[2]) - @test isapprox(G, o1, compose(G, pts[1], pts[2])) - - eA = identity_element(G) - @test isapprox(G, eA, e) - @test isapprox(G, e, eA) - W = log(G, eA, pts[1]) - Z = log(G, eA, pts[1]) - @test isapprox(G, e, W, Z) + pts = [ArrayPartition(tp...) for tp in tuple_pts] + + @testset "setindex! and getindex" begin + p1 = pts[1] + p2 = allocate(p1) + @test p1[G, 1] === p1[M, 1] + p2[G, 1] = p1[M, 1] + @test p2[G, 1] == p1[M, 1] end + + X = log(G, pts[1], pts[1]) + Y = zero_vector(G, pts[1]) + Z = Manifolds.allocate_result(G, zero_vector, pts[1]) + Z = zero_vector!(M, Z, pts[1]) + @test norm(G, pts[1], X) ≈ 0 + @test norm(G, pts[1], Y) ≈ 0 + @test norm(G, pts[1], Z) ≈ 0 + + e = Identity(G) + @test inv(G, e) === e + + @test compose(G, e, pts[1]) == pts[1] + @test compose(G, pts[1], e) == pts[1] + @test compose(G, e, e) === e + + # test in-place composition + o1 = copy(pts[1]) + compose!(G, o1, o1, pts[2]) + @test isapprox(G, o1, compose(G, pts[1], pts[2])) + + eA = identity_element(G) + @test isapprox(G, eA, e) + @test isapprox(G, e, eA) + W = log(G, eA, pts[1]) + Z = log(G, eA, pts[1]) + @test isapprox(G, e, W, Z) end diff --git a/test/groups/special_euclidean.jl b/test/groups/special_euclidean.jl index 2200de7c6b..c4cd336dc0 100644 --- a/test/groups/special_euclidean.jl +++ b/test/groups/special_euclidean.jl @@ -5,174 +5,180 @@ using ManifoldsBase: VeeOrthogonalBasis Random.seed!(10) +using Manifolds: + LeftForwardAction, LeftBackwardAction, RightForwardAction, RightBackwardAction + @testset "Special Euclidean group" begin - @testset "SpecialEuclidean($n)" for n in (2, 3, 4) - G = SpecialEuclidean(n) - @test isa(G, SpecialEuclidean{n}) - @test repr(G) == "SpecialEuclidean($n)" - M = base_manifold(G) - @test M === TranslationGroup(n) × SpecialOrthogonal(n) - @test submanifold(G, 1) === TranslationGroup(n) - @test submanifold(G, 2) === SpecialOrthogonal(n) - Rn = Rotations(n) - p = Matrix(I, n, n) - - if n == 2 - t = Vector{Float64}.([1:2, 2:3, 3:4]) - ω = [[1.0], [2.0], [1.0]] - tuple_pts = [(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] - tuple_X = [([-1.0, 2.0], hat(Rn, p, [1.0])), ([1.0, -2.0], hat(Rn, p, [0.5]))] - elseif n == 3 - t = Vector{Float64}.([1:3, 2:4, 4:6]) - ω = [[1.0, 2.0, 3.0], [3.0, 2.0, 1.0], [1.0, 3.0, 2.0]] - tuple_pts = [(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] - tuple_X = [ - ([-1.0, 2.0, 1.0], hat(Rn, p, [1.0, 0.5, -0.5])), - ([-2.0, 1.0, 0.5], hat(Rn, p, [-1.0, -0.5, 1.1])), - ] - else # n == 4 - t = Vector{Float64}.([1:4, 2:5, 3:6]) - ω = [ - [1.0, 2.0, 3.0, 1.0, 2.0, 3.0], - [3.0, 2.0, 1.0, 1.0, 2.0, 3.0], - [1.0, 3.0, 2.0, 1.0, 2.0, 3.0], - ] - tuple_pts = [(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] - tuple_X = [ - ([-1.0, 2.0, 1.0, 3.0], hat(Rn, p, [1.0, 0.5, -0.5, 0.0, 2.0, 1.0])), - ([-2.0, 1.5, -1.0, 2.0], hat(Rn, p, [1.0, -0.5, 0.5, 1.0, 0.0, 1.0])), - ] - end + for se_parameter in [:field, :type] + @testset "SpecialEuclidean($n)" for n in (2, 3, 4) + G = SpecialEuclidean(n; parameter=se_parameter) + if se_parameter === :field + @test isa(G, SpecialEuclidean{Tuple{Int}}) + else + @test isa(G, SpecialEuclidean{TypeParameter{Tuple{n}}}) + end - basis_types = (DefaultOrthonormalBasis(),) + if se_parameter === :field + @test repr(G) == "SpecialEuclidean($n; parameter=:field)" + else + @test repr(G) == "SpecialEuclidean($n)" + end + M = base_manifold(G) + @test M === + TranslationGroup(n; parameter=se_parameter) × + SpecialOrthogonal(n; parameter=se_parameter) + @test submanifold(G, 1) === TranslationGroup(n; parameter=se_parameter) + @test submanifold(G, 2) === SpecialOrthogonal(n; parameter=se_parameter) + Rn = Rotations(n) + p = Matrix(I, n, n) - @testset "isapprox with identity" begin - @test isapprox(G, Identity(G), identity_element(G)) - @test isapprox(G, identity_element(G), Identity(G)) - end + if n == 2 + t = Vector{Float64}.([1:2, 2:3, 3:4]) + ω = [[1.0], [2.0], [1.0]] + tuple_pts = [(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] + tuple_X = + [([-1.0, 2.0], hat(Rn, p, [1.0])), ([1.0, -2.0], hat(Rn, p, [0.5]))] + elseif n == 3 + t = Vector{Float64}.([1:3, 2:4, 4:6]) + ω = [[1.0, 2.0, 3.0], [3.0, 2.0, 1.0], [1.0, 3.0, 2.0]] + tuple_pts = [(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] + tuple_X = [ + ([-1.0, 2.0, 1.0], hat(Rn, p, [1.0, 0.5, -0.5])), + ([-2.0, 1.0, 0.5], hat(Rn, p, [-1.0, -0.5, 1.1])), + ] + else # n == 4 + t = Vector{Float64}.([1:4, 2:5, 3:6]) + ω = [ + [1.0, 2.0, 3.0, 1.0, 2.0, 3.0], + [3.0, 2.0, 1.0, 1.0, 2.0, 3.0], + [1.0, 3.0, 2.0, 1.0, 2.0, 3.0], + ] + tuple_pts = [(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] + tuple_X = [ + ([-1.0, 2.0, 1.0, 3.0], hat(Rn, p, [1.0, 0.5, -0.5, 0.0, 2.0, 1.0])), + ([-2.0, 1.5, -1.0, 2.0], hat(Rn, p, [1.0, -0.5, 0.5, 1.0, 0.0, 1.0])), + ] + end - for prod_type in [ProductRepr, ArrayPartition] - @testset "product repr" begin - pts = [prod_type(tp...) for tp in tuple_pts] - X_pts = [prod_type(tX...) for tX in tuple_X] + basis_types = (DefaultOrthonormalBasis(),) - @testset "setindex! and getindex" begin - p1 = pts[1] - p2 = allocate(p1) - @test p1[G, 1] === p1[M, 1] - p2[G, 1] = p1[M, 1] - @test p2[G, 1] == p1[M, 1] - end + @testset "isapprox with identity" begin + @test isapprox(G, Identity(G), identity_element(G)) + @test isapprox(G, identity_element(G), Identity(G)) + end - g1, g2 = pts[1:2] - t1, R1 = submanifold_components(g1) - t2, R2 = submanifold_components(g2) - g1g2 = prod_type(R1 * t2 + t1, R1 * R2) - @test isapprox(G, compose(G, g1, g2), g1g2) - g1g2mat = affine_matrix(G, g1g2) - @test g1g2mat ≈ affine_matrix(G, g1) * affine_matrix(G, g2) - @test affine_matrix(G, g1g2mat) === g1g2mat - @test affine_matrix(G, Identity(G)) isa SDiagonal{n,Float64} - @test affine_matrix(G, Identity(G)) == SDiagonal{n,Float64}(I) + pts = [ArrayPartition(tp...) for tp in tuple_pts] + X_pts = [ArrayPartition(tX...) for tX in tuple_X] - w = translate_diff(G, pts[1], Identity(G), X_pts[1]) - w2 = allocate(w) - submanifold_component(w2, 1) .= submanifold_component(w, 1) - submanifold_component(w2, 2) .= - submanifold_component(pts[1], 2) * submanifold_component(w, 2) - w2mat = screw_matrix(G, w2) - @test w2mat ≈ affine_matrix(G, pts[1]) * screw_matrix(G, X_pts[1]) - @test screw_matrix(G, w2mat) === w2mat + @testset "setindex! and getindex" begin + p1 = pts[1] + p2 = allocate(p1) + @test p1[G, 1] === p1[M, 1] + p2[G, 1] = p1[M, 1] + @test p2[G, 1] == p1[M, 1] + end - test_group( - G, - pts, - X_pts, - X_pts; - test_diff=true, - test_lie_bracket=true, - test_adjoint_action=true, - test_exp_from_identity=true, - test_log_from_identity=true, - test_vee_hat_from_identity=true, - diff_convs=[(), (LeftForwardAction(),), (RightBackwardAction(),)], - ) - test_manifold( - G, - pts; - basis_types_vecs=basis_types, - basis_types_to_from=basis_types, - is_mutating=true, - #test_inplace=true, - test_vee_hat=true, - exp_log_atol_multiplier=50, - ) + g1, g2 = pts[1:2] + t1, R1 = submanifold_components(g1) + t2, R2 = submanifold_components(g2) + g1g2 = ArrayPartition(R1 * t2 + t1, R1 * R2) + @test isapprox(G, compose(G, g1, g2), g1g2) + g1g2mat = affine_matrix(G, g1g2) + @test g1g2mat ≈ affine_matrix(G, g1) * affine_matrix(G, g2) + @test affine_matrix(G, g1g2mat) === g1g2mat + if se_parameter === :type + @test affine_matrix(G, Identity(G)) isa SDiagonal{n,Float64} + end + @test affine_matrix(G, Identity(G)) == SDiagonal{n,Float64}(I) + + w = translate_diff(G, pts[1], Identity(G), X_pts[1]) + w2 = allocate(w) + submanifold_component(w2, 1) .= submanifold_component(w, 1) + submanifold_component(w2, 2) .= + submanifold_component(pts[1], 2) * submanifold_component(w, 2) + w2mat = screw_matrix(G, w2) + @test w2mat ≈ affine_matrix(G, pts[1]) * screw_matrix(G, X_pts[1]) + @test screw_matrix(G, w2mat) === w2mat + + test_group( + G, + pts, + X_pts, + X_pts; + test_diff=true, + test_lie_bracket=true, + test_adjoint_action=true, + test_exp_from_identity=true, + test_log_from_identity=true, + test_vee_hat_from_identity=true, + diff_convs=[(), (LeftForwardAction(),), (RightBackwardAction(),)], + ) + test_manifold( + G, + pts; + basis_types_vecs=basis_types, + basis_types_to_from=basis_types, + is_mutating=true, + #test_inplace=true, + test_vee_hat=true, + exp_log_atol_multiplier=50, + ) + + for CS in [CartanSchoutenMinus(), CartanSchoutenPlus(), CartanSchoutenZero()] + @testset "$CS" begin + G_TR = ConnectionManifold(G, CS) + + test_group( + G_TR, + pts, + X_pts, + X_pts; + test_diff=true, + test_lie_bracket=true, + test_adjoint_action=true, + diff_convs=[(), (LeftForwardAction(),), (RightBackwardAction(),)], + ) - for CS in - [CartanSchoutenMinus(), CartanSchoutenPlus(), CartanSchoutenZero()] - @testset "$CS" begin - G_TR = ConnectionManifold(G, CS) - - test_group( - G_TR, - pts, - X_pts, - X_pts; - test_diff=true, - test_lie_bracket=true, - test_adjoint_action=true, - diff_convs=[ - (), - (LeftForwardAction(),), - (RightBackwardAction(),), - ], - ) - - test_manifold( - G_TR, - pts; - is_mutating=true, - exp_log_atol_multiplier=50, - test_inner=false, - test_norm=false, - ) - end + test_manifold( + G_TR, + pts; + is_mutating=true, + exp_log_atol_multiplier=50, + test_inner=false, + test_norm=false, + ) end - for MM in [LeftInvariantMetric()] - @testset "$MM" begin - G_TR = MetricManifold(G, MM) - @test base_group(G_TR) === G - - test_group( - G_TR, - pts, - X_pts, - X_pts; - test_diff=true, - test_lie_bracket=true, - test_adjoint_action=true, - diff_convs=[ - (), - (LeftForwardAction(),), - (RightBackwardAction(),), - ], - ) - - test_manifold( - G_TR, - pts; - basis_types_vecs=basis_types, - basis_types_to_from=basis_types, - is_mutating=true, - exp_log_atol_multiplier=50, - ) - end + end + for MM in [LeftInvariantMetric()] + @testset "$MM" begin + G_TR = MetricManifold(G, MM) + @test base_group(G_TR) === G + + test_group( + G_TR, + pts, + X_pts, + X_pts; + test_diff=true, + test_lie_bracket=true, + test_adjoint_action=true, + diff_convs=[(), (LeftForwardAction(),), (RightBackwardAction(),)], + ) + + test_manifold( + G_TR, + pts; + basis_types_vecs=basis_types, + basis_types_to_from=basis_types, + is_mutating=true, + exp_log_atol_multiplier=50, + ) end end @testset "affine matrix" begin - pts = [affine_matrix(G, prod_type(tp...)) for tp in tuple_pts] - X_pts = [screw_matrix(G, prod_type(tX...)) for tX in tuple_X] + pts = [affine_matrix(G, ArrayPartition(tp...)) for tp in tuple_pts] + X_pts = [screw_matrix(G, ArrayPartition(tX...)) for tX in tuple_X] @testset "setindex! and getindex" begin p1 = pts[1] @@ -204,21 +210,21 @@ Random.seed!(10) p = copy(G, pts[1]) X = copy(G, p, X_pts[1]) X[n + 1, n + 1] = 0.1 - @test_throws DomainError is_vector(G, p, X, true) + @test_throws DomainError is_vector(G, p, X; error=:error) X2 = zeros(n + 2, n + 2) # nearly correct just too large (and the error from before) X2[1:n, 1:n] .= X[1:n, 1:n] X2[1:n, end] .= X[1:n, end] X2[end, end] = X[end, end] - @test_throws DomainError is_vector(G, p, X2, true) + @test_throws DomainError is_vector(G, p, X2; error=:error) p[n + 1, n + 1] = 0.1 - @test_throws DomainError is_point(G, p, true) + @test_throws DomainError is_point(G, p; error=:error) p2 = zeros(n + 2, n + 2) # nearly correct just too large (and the error from before) p2[1:n, 1:n] .= p[1:n, 1:n] p2[1:n, end] .= p[1:n, end] p2[end, end] = p[end, end] - @test_throws DomainError is_point(G, p2, true) + @test_throws DomainError is_point(G, p2; error=:error) # exp/log_lie for ProductGroup on arrays X = copy(G, p, X_pts[1]) p3 = exp_lie(G, X) @@ -227,8 +233,8 @@ Random.seed!(10) end @testset "hat/vee" begin - p = prod_type(tuple_pts[1]...) - X = prod_type(tuple_X[1]...) + p = ArrayPartition(tuple_pts[1]...) + X = ArrayPartition(tuple_X[1]...) Xexp = [ submanifold_component(X, 1) vee(Rn, submanifold_component(p, 2), submanifold_component(X, 2)) @@ -266,22 +272,22 @@ Random.seed!(10) hat!(G, Ye2, identity_element(G), Xc) @test isapprox(G, e, Ye, Ye2) end - end - G = SpecialEuclidean(11) - @test affine_matrix(G, Identity(G)) isa Diagonal{Float64,Vector{Float64}} - @test affine_matrix(G, Identity(G)) == Diagonal(ones(11)) - - @testset "Explicit embedding in GL(n+1)" begin - G = SpecialEuclidean(3) - t = Vector{Float64}.([1:3, 2:4, 4:6]) - ω = [[1.0, 2.0, 3.0], [3.0, 2.0, 1.0], [1.0, 3.0, 2.0]] - p = Matrix(I, 3, 3) - Rn = Rotations(3) - for prod_type in [ProductRepr, ArrayPartition] - pts = [prod_type(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] - X = prod_type([-1.0, 2.0, 1.0], hat(Rn, p, [1.0, 0.5, -0.5])) - q = prod_type([0.0, 0.0, 0.0], p) + G = SpecialEuclidean(11) + @test affine_matrix(G, Identity(G)) isa Diagonal{Float64,Vector{Float64}} + @test affine_matrix(G, Identity(G)) == Diagonal(ones(11)) + + @testset "Explicit embedding in GL(n+1)" begin + G = SpecialEuclidean(3) + t = Vector{Float64}.([1:3, 2:4, 4:6]) + ω = [[1.0, 2.0, 3.0], [3.0, 2.0, 1.0], [1.0, 3.0, 2.0]] + p = Matrix(I, 3, 3) + Rn = Rotations(3) + pts = [ + ArrayPartition(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω) + ] + X = ArrayPartition([-1.0, 2.0, 1.0], hat(Rn, p, [1.0, 0.5, -0.5])) + q = ArrayPartition([0.0, 0.0, 0.0], p) GL = GeneralLinear(4) SEGL = EmbeddedManifold(G, GL) @@ -329,22 +335,20 @@ Random.seed!(10) end @testset "Adjoint action on 𝔰𝔢(3)" begin - G = SpecialEuclidean(3) + G = SpecialEuclidean(3; parameter=:type) t = Vector{Float64}.([1:3, 2:4, 4:6]) ω = [[1.0, 2.0, 3.0], [3.0, 2.0, 1.0], [1.0, 3.0, 2.0]] p = Matrix(I, 3, 3) Rn = Rotations(3) - for prod_type in [ProductRepr, ArrayPartition] - pts = [prod_type(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] - X = prod_type([-1.0, 2.0, 1.0], hat(Rn, p, [1.0, 0.5, -0.5])) - q = prod_type([0.0, 0.0, 0.0], p) - - # adjoint action of SE(3) - fX = TFVector(vee(G, q, X), VeeOrthogonalBasis()) - fXp = adjoint_action(G, pts[1], fX) - fXp2 = adjoint_action(G, pts[1], X) - @test isapprox(G, pts[1], hat(G, pts[1], fXp.data), fXp2) - end + pts = [ArrayPartition(ti, exp(Rn, p, hat(Rn, p, ωi))) for (ti, ωi) in zip(t, ω)] + X = ArrayPartition([-1.0, 2.0, 1.0], hat(Rn, p, [1.0, 0.5, -0.5])) + q = ArrayPartition([0.0, 0.0, 0.0], p) + + # adjoint action of SE(3) + fX = TFVector(vee(G, q, X), VeeOrthogonalBasis()) + fXp = adjoint_action(G, pts[1], fX) + fXp2 = adjoint_action(G, pts[1], X) + @test isapprox(G, pts[1], hat(G, pts[1], fXp.data), fXp2) end @testset "performance of selected operations" begin diff --git a/test/groups/special_linear.jl b/test/groups/special_linear.jl index dc1651081f..107ee1f9cf 100644 --- a/test/groups/special_linear.jl +++ b/test/groups/special_linear.jl @@ -30,32 +30,32 @@ using NLsolve @testset "Real" begin G = SpecialLinear(3) - @test_throws ManifoldDomainError is_point(G, randn(2, 3), true) - @test_throws ManifoldDomainError is_point(G, Float64[2 1; 1 1], true) - @test_throws ManifoldDomainError is_point(G, [1 0 im; im 0 0; 0 -1 0], true) - @test_throws ManifoldDomainError is_point(G, zeros(3, 3), true) - @test_throws DomainError is_point(G, Float64[1 3 3; 1 1 2; 1 2 3], true) - @test is_point(G, Float64[1 1 1; 2 2 1; 2 3 3], true) - @test is_point(G, Identity(G), true) - @test_throws ManifoldDomainError is_vector( + @test_throws ManifoldDomainError is_point(G, randn(2, 3); error=:error) + @test_throws ManifoldDomainError is_point(G, Float64[2 1; 1 1]; error=:error) + @test_throws ManifoldDomainError is_point(G, [1 0 im; im 0 0; 0 -1 0]; error=:error) + @test_throws ManifoldDomainError is_point(G, zeros(3, 3); error=:error) + @test_throws DomainError is_point(G, Float64[1 3 3; 1 1 2; 1 2 3]; error=:error) + @test is_point(G, Float64[1 1 1; 2 2 1; 2 3 3]; error=:error) + @test is_point(G, Identity(G); error=:error) + @test_throws DomainError is_vector( G, Float64[2 3 2; 3 1 2; 1 1 1], - randn(3, 3), - true; + randn(3, 3); + error=:error, atol=1e-6, ) @test_throws DomainError is_vector( G, Float64[2 1 2; 3 2 2; 2 2 1], - Float64[2 1 -1; 2 2 1; 1 1 -1], - true; + Float64[2 1 -1; 2 2 1; 1 1 -1]; + error=:error, atol=1e-6, ) @test is_vector( G, Float64[2 1 2; 3 2 2; 2 2 1], - Float64[-1 -1 -1; 1 -1 2; -1 -1 2], - true; + Float64[-1 -1 -1; 1 -1 2; -1 -1 2]; + error=:error, atol=1e-6, ) @@ -126,35 +126,35 @@ using NLsolve @testset "Complex" begin G = SpecialLinear(2, ℂ) - @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 2, 3), true) - @test_throws DomainError is_point(G, randn(2, 2), true) + @test_throws ManifoldDomainError is_point(G, randn(ComplexF64, 2, 3); error=:error) + @test_throws DomainError is_point(G, randn(2, 2); error=:error) @test_throws ManifoldDomainError is_point( G, - ComplexF64[1 0 im; im 0 0; 0 -1 0], - true, + ComplexF64[1 0 im; im 0 0; 0 -1 0]; + error=:error, ) - @test_throws DomainError is_point(G, ComplexF64[1 im; im 1], true) - @test is_point(G, ComplexF64[im 1; -2 im], true) - @test is_point(G, Identity(G), true) - @test_throws ManifoldDomainError is_vector( + @test_throws DomainError is_point(G, ComplexF64[1 im; im 1]; error=:error) + @test is_point(G, ComplexF64[im 1; -2 im]; error=:error) + @test is_point(G, Identity(G); error=:error) + @test_throws DomainError is_vector( G, ComplexF64[-1+im -1; -im 1], - ComplexF64[1-im 1+im; 1 -1+im], - true; + ComplexF64[1-im 1+im; 1 -1+im]; + error=:error, atol=1e-6, ) @test_throws DomainError is_vector( G, ComplexF64[1 1+im; -1+im -1], - ComplexF64[1-im -1-im; -im im], - true; + ComplexF64[1-im -1-im; -im im]; + error=:error, atol=1e-6, ) @test is_vector( G, ComplexF64[1 1+im; -1+im -1], - ComplexF64[1-im 1+im; 1 -1+im], - true; + ComplexF64[1-im 1+im; 1 -1+im]; + error=:error, atol=1e-6, ) @@ -215,4 +215,9 @@ using NLsolve @test project(G, q, Y) ≈ Y end end + @testset "field parameter" begin + G = SpecialLinear(3; parameter=:field) + @test typeof(get_embedding(G)) === GeneralLinear{Tuple{Int64},ℝ} + @test repr(G) == "SpecialLinear(3, ℝ; parameter=:field)" + end end diff --git a/test/groups/special_orthogonal.jl b/test/groups/special_orthogonal.jl index 9f3f0faa06..1359cbf9bd 100644 --- a/test/groups/special_orthogonal.jl +++ b/test/groups/special_orthogonal.jl @@ -1,6 +1,8 @@ include("../utils.jl") include("group_utils.jl") +using Manifolds: LeftForwardAction, RightBackwardAction + @testset "Special Orthogonal group" begin for n in [2, 3] G = SpecialOrthogonal(n) diff --git a/test/groups/translation_action.jl b/test/groups/translation_action.jl index a278f323e4..1348759b93 100644 --- a/test/groups/translation_action.jl +++ b/test/groups/translation_action.jl @@ -7,9 +7,9 @@ include("group_utils.jl") G = TranslationGroup(2, 3) A = TranslationAction(Euclidean(2, 3), G) - @test repr(A) == "TranslationAction($(repr(M)), $(repr(G)), LeftForwardAction())" + @test repr(A) == "TranslationAction($(repr(M)), $(repr(G)), LeftAction())" @test repr(switch_direction(A)) == - "TranslationAction($(repr(M)), $(repr(G)), RightForwardAction())" + "TranslationAction($(repr(M)), $(repr(G)), RightAction())" types_a = [Matrix{Float64}] diff --git a/test/groups/translation_group.jl b/test/groups/translation_group.jl index 07fac5d3b2..5c1b43d75c 100644 --- a/test/groups/translation_group.jl +++ b/test/groups/translation_group.jl @@ -4,8 +4,8 @@ include("group_utils.jl") @testset "Translation group" begin @testset "real" begin G = TranslationGroup(2, 3) - @test repr(G) == "TranslationGroup(2, 3; field = ℝ)" - @test repr(TranslationGroup(2, 3; field=ℂ)) == "TranslationGroup(2, 3; field = ℂ)" + @test repr(G) == "TranslationGroup(2, 3; field=ℝ)" + @test repr(TranslationGroup(2, 3; field=ℂ)) == "TranslationGroup(2, 3; field=ℂ)" @test has_invariant_metric(G, LeftForwardAction()) @test has_invariant_metric(G, RightBackwardAction()) @@ -46,7 +46,7 @@ include("group_utils.jl") @testset "complex" begin G = TranslationGroup(2, 3; field=ℂ) - @test repr(G) == "TranslationGroup(2, 3; field = ℂ)" + @test repr(G) == "TranslationGroup(2, 3; field=ℂ)" types = [Matrix{ComplexF64}] @test base_manifold(G) === Euclidean(2, 3; field=ℂ) @@ -70,4 +70,8 @@ include("group_utils.jl") ) end end + @testset "field parameter" begin + @test repr(TranslationGroup(2, 3; field=ℂ, parameter=:field)) == + "TranslationGroup(2, 3; field=ℂ, parameter=:field)" + end end diff --git a/test/manifolds/centered_matrices.jl b/test/manifolds/centered_matrices.jl index cf77276232..ae63d1f5f9 100644 --- a/test/manifolds/centered_matrices.jl +++ b/test/manifolds/centered_matrices.jl @@ -11,16 +11,16 @@ include("../utils.jl") @testset "Real Centered Matrices Basics" begin @test repr(M) == "CenteredMatrices(3, 2, ℝ)" @test representation_size(M) == (3, 2) - @test typeof(get_embedding(M)) === Euclidean{Tuple{3,2},ℝ} + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{3,2}},ℝ} @test is_flat(M) @test check_point(M, A) === nothing - @test_throws ManifoldDomainError is_point(M, B, true) - @test_throws ManifoldDomainError is_point(M, C, true) - @test_throws DomainError is_point(M, D, true) + @test_throws ManifoldDomainError is_point(M, B; error=:error) + @test_throws ManifoldDomainError is_point(M, C; error=:error) + @test_throws DomainError is_point(M, D; error=:error) @test check_vector(M, A, A) === nothing - @test_throws DomainError is_vector(M, A, D, true) - @test_throws ManifoldDomainError is_vector(M, D, A, true) - @test_throws ManifoldDomainError is_vector(M, A, B, true) + @test_throws DomainError is_vector(M, A, D; error=:error) + @test_throws DomainError is_vector(M, D, A; error=:error) + @test_throws ManifoldDomainError is_vector(M, A, B; error=:error) @test manifold_dimension(M) == 4 @test A == project!(M, A, A) @test A == project(M, A, A) @@ -59,4 +59,9 @@ include("../utils.jl") test_inplace=true, ) end + @testset "field parameter" begin + M = CenteredMatrices(3, 2; parameter=:field) + @test repr(M) == "CenteredMatrices(3, 2, ℝ; parameter=:field)" + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + end end diff --git a/test/manifolds/cholesky_space.jl b/test/manifolds/cholesky_space.jl index 8a51258e41..2cbd2e11db 100644 --- a/test/manifolds/cholesky_space.jl +++ b/test/manifolds/cholesky_space.jl @@ -41,18 +41,22 @@ include("../utils.jl") pt3f = [2.0 0.0 1.0; 0.0 1.0 0.0; 0.0 0.0 4.0] # no lower and nonsym pt4 = [2.0 0.0 0.0; 1.0 2.0 0.0; 0.0 0.0 4.0] @test !is_point(M, pt1f) - @test_throws DomainError is_point(M, pt1f, true) + @test_throws DomainError is_point(M, pt1f; error=:error) @test !is_point(M, pt2f) - @test_throws DomainError is_point(M, pt2f, true) + @test_throws DomainError is_point(M, pt2f; error=:error) @test !is_point(M, pt3f) - @test_throws DomainError is_point(M, pt3f, true) + @test_throws DomainError is_point(M, pt3f; error=:error) @test is_point(M, pt4) @test !is_vector(M, pt3f, pt1f) - @test_throws DomainError is_vector(M, pt3f, pt1f, true) + @test_throws DomainError is_vector(M, pt3f, pt1f; error=:error) @test !is_vector(M, pt4, pt1f) - @test_throws DomainError is_vector(M, pt4, pt1f, true) + @test_throws DomainError is_vector(M, pt4, pt1f; error=:error) @test !is_vector(M, pt4, pt3f) - @test_throws DomainError is_vector(M, pt4, pt3f, true) + @test_throws DomainError is_vector(M, pt4, pt3f; error=:error) @test is_vector(M, pt4, pt2f) end + @testset "field parameter" begin + M = CholeskySpace(3; parameter=:field) + @test repr(M) == "CholeskySpace(3; parameter=:field)" + end end diff --git a/test/manifolds/circle.jl b/test/manifolds/circle.jl index 0187eb732b..07d3eddb6d 100644 --- a/test/manifolds/circle.jl +++ b/test/manifolds/circle.jl @@ -2,10 +2,6 @@ include("../utils.jl") using Manifolds: TFVector, CoTFVector -# TODO: remove after bug in StaticArray is fixed -@inline Base.copy(a::SizedArray) = __copy(a) -@inline __copy(a::SizedArray{S,T}) where {S,T} = SizedArray{S,T}(copy(a.data)) - @testset "Circle" begin M = Circle() @testset "Real Circle Basics" begin @@ -17,13 +13,13 @@ using Manifolds: TFVector, CoTFVector @test !is_point(M, zeros(3, 3)) @test Manifolds.check_size(M, [9.0]) === nothing @test Manifolds.check_size(M, [1.0], [-2.0]) === nothing - @test_throws DomainError is_point(M, 9.0, true) - @test_throws DomainError is_point(M, zeros(3, 3), true) + @test_throws DomainError is_point(M, 9.0; error=:error) + @test_throws DomainError is_point(M, zeros(3, 3); error=:error) @test !is_vector(M, 9.0, 0.0) @test !is_vector(M, zeros(3, 3), zeros(3, 3)) - @test_throws DomainError is_vector(M, 9.0, 0.0, true) - @test_throws DomainError is_vector(M, zeros(3, 3), zeros(3, 3), true) - @test_throws DomainError is_vector(M, 0.0, zeros(3, 3), true) + @test_throws DomainError is_vector(M, 9.0, 0.0; error=:error) + @test_throws DomainError is_vector(M, zeros(3, 3), zeros(3, 3); error=:error) + @test_throws DomainError is_vector(M, 0.0, zeros(3, 3); error=:error) @test is_vector(M, 0.0, 0.0) @test get_coordinates(M, Ref(0.0), Ref(2.0), DefaultOrthonormalBasis())[] ≈ 2.0 @test get_coordinates( @@ -210,11 +206,11 @@ using Manifolds: TFVector, CoTFVector @test is_vector(Mc, 1im, 0.0) @test is_point(Mc, 1im) @test !is_point(Mc, 1 + 1im) - @test_throws DomainError is_point(Mc, 1 + 1im, true) + @test_throws DomainError is_point(Mc, 1 + 1im; error=:error) @test !is_vector(Mc, 1 + 1im, 0.0) - @test_throws DomainError is_vector(Mc, 1 + 1im, 0.0, true) + @test_throws DomainError is_vector(Mc, 1 + 1im, 0.0; error=:error) @test !is_vector(Mc, 1im, 2im) - @test_throws DomainError is_vector(Mc, 1im, 2im, true) + @test_throws DomainError is_vector(Mc, 1im, 2im; error=:error) rrcv = Manifolds.RieszRepresenterCotangentVector(Mc, 0.0 + 0.0im, 1.0im) @test flat(Mc, 0.0 + 0.0im, 1.0im) == rrcv @test sharp(Mc, 0.0 + 0.0im, rrcv) == 1.0im diff --git a/test/manifolds/direct_sum_bundle.jl b/test/manifolds/direct_sum_bundle.jl new file mode 100644 index 0000000000..c37f54af68 --- /dev/null +++ b/test/manifolds/direct_sum_bundle.jl @@ -0,0 +1,119 @@ +using Test +using Manifolds +using RecursiveArrayTools + +@testset "Multitangent bundle" begin + M = Sphere(2) + m_prod_retr = Manifolds.FiberBundleProductRetraction() + m_prod_invretr = Manifolds.FiberBundleInverseProductRetraction() + m_sasaki = SasakiRetraction(5) + + @testset "Nice access to vector bundle components" begin + TB = Manifolds.MultitangentBundle{2}(M) + p = ArrayPartition( + [1.0, 0.0, 0.0], + ArrayPartition([0.0, 2.0, 4.0], [0.0, -1.0, 0.0]), + ) + @test p[TB, :point] === p.x[1] + p[TB, :point] = [0.0, 1.0, 0.0] + @test p.x[1] == [0.0, 1.0, 0.0] + @test_throws DomainError p[TB, :error] + @test_throws DomainError p[TB, :error] = [1, 2, 3] + + @test view(p, TB, :point) === p.x[1] + view(p, TB, :point) .= [2.0, 3.0, 5.0] + @test p.x[1] == [2.0, 3.0, 5.0] + @test_throws DomainError view(p, TB, :error) + end + + p = [1.0, 0.0, 0.0] + TB = Manifolds.MultitangentBundle{2}(M) + # @test sprint(show, TB) == "TangentBundle(Sphere(2, ℝ))" + @test base_manifold(TB) == M + @test manifold_dimension(TB) == 3 * manifold_dimension(M) + @test representation_size(TB) === nothing + @test default_inverse_retraction_method(TB) === m_prod_invretr + @test default_retraction_method(TB) == m_prod_retr + @test default_vector_transport_method(TB) isa + Manifolds.FiberBundleProductVectorTransport + + @testset "Type" begin + pts_tb = [ + ArrayPartition( + [1.0, 0.0, 0.0], + ArrayPartition([0.0, -1.0, -1.0], [0.0, -1.0, -1.0]), + ), + ArrayPartition( + [0.0, 1.0, 0.0], + ArrayPartition([2.0, 0.0, 1.0], [-1.0, 0.0, -2.0]), + ), + ArrayPartition( + [1.0, 0.0, 0.0], + ArrayPartition([0.0, 2.0, -1.0], [0.0, -2.0, -1.0]), + ), + ] + + for pt in pts_tb + @test bundle_projection(TB, pt) ≈ pt.x[1] + end + X12_prod = inverse_retract(TB, pts_tb[1], pts_tb[2], m_prod_invretr) + X13_prod = inverse_retract(TB, pts_tb[1], pts_tb[3], m_prod_invretr) + basis_types = + (DefaultOrthonormalBasis(), get_basis(TB, pts_tb[1], DefaultOrthonormalBasis())) + test_manifold( + TB, + pts_tb, + default_inverse_retraction_method=m_prod_invretr, + default_retraction_method=m_prod_retr, + inverse_retraction_methods=[m_prod_invretr], + retraction_methods=[m_prod_retr], + test_exp_log=false, + test_injectivity_radius=false, + test_tangent_vector_broadcasting=false, + test_default_vector_transport=true, + vector_transport_methods=[], + basis_types_vecs=basis_types, + projection_atol_multiplier=4, + test_inplace=true, + test_representation_size=false, + test_rand_point=true, + test_rand_tvector=true, + ) + + Xir = allocate(pts_tb[1]) + inverse_retract!(TB, Xir, pts_tb[1], pts_tb[2], m_prod_invretr) + @test isapprox(TB, pts_tb[1], Xir, X12_prod) + @test isapprox( + norm(TB.fiber, pts_tb[1][TB, :point], pts_tb[1][TB, :vector]), + sqrt( + inner( + TB.fiber, + pts_tb[1][TB, :point], + pts_tb[1][TB, :vector], + pts_tb[1][TB, :vector], + ), + ), + ) + @test isapprox( + distance( + TB.fiber, + pts_tb[1][TB, :point], + pts_tb[1][TB, :vector], + ArrayPartition([0.0, 2.0, 3.0], [0.0, 2.0, 2.0]), + ), + 9.273618495495704, + ) + Xir2 = allocate(pts_tb[1]) + vector_transport_to!( + TB, + Xir2, + pts_tb[1], + Xir, + pts_tb[2], + Manifolds.FiberBundleProductVectorTransport(), + ) + @test is_vector(TB, pts_tb[2], Xir2) + end + + @test base_manifold(TangentBundle(M)) == M +end diff --git a/test/manifolds/elliptope.jl b/test/manifolds/elliptope.jl index 0498a1b108..e36fae782f 100644 --- a/test/manifolds/elliptope.jl +++ b/test/manifolds/elliptope.jl @@ -11,11 +11,11 @@ include("../utils.jl") @test is_point(M, q, true; atol=10^-15) @test base_manifold(M) === M qN = [2.0 0.0; 0.0 1.0; 1/sqrt(2) -1/sqrt(2); 1/sqrt(2) 1/sqrt(2)] - @test_throws DomainError is_point(M, qN, true) + @test_throws DomainError is_point(M, qN; error=:error) Y = [0.0 1.0; 1.0 0.0; 0.0 0.0; 0.0 0.0] - @test is_vector(M, q, Y, true) + @test is_vector(M, q, Y; error=:error) YN = [0.1 1.0; 1.0 0.1; 0.0 0.0; 0.0 0.0] - @test_throws DomainError is_vector(M, q, YN, true) + @test_throws DomainError is_vector(M, q, YN; error=:error) qE = similar(q) embed!(M, qE, q) qE2 = embed(M, q) @@ -51,4 +51,9 @@ include("../utils.jl") ) end end + @testset "field parameter" begin + M = Elliptope(4, 2; parameter=:field) + @test repr(M) == "Elliptope(4, 2; parameter=:field)" + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + end end diff --git a/test/manifolds/essential_manifold.jl b/test/manifolds/essential_manifold.jl index a466998f82..a05e3edd98 100644 --- a/test/manifolds/essential_manifold.jl +++ b/test/manifolds/essential_manifold.jl @@ -22,17 +22,17 @@ include("../utils.jl") np3 = [r1, r2, r3] @test !is_point(M, r1) # first two components of r1 are not rotations - @test_throws DomainError is_point(M, r1, true) - @test_throws DomainError is_point(M, np3, true) + @test_throws DomainError is_point(M, r1; error=:error) + @test_throws DomainError is_point(M, np3; error=:error) @test is_point(M, p1) - @test_throws ComponentManifoldError is_point(M, np1, true) - @test_throws CompositeManifoldError is_point(M, np2, true) + @test_throws ComponentManifoldError is_point(M, np1; error=:error) + @test_throws CompositeManifoldError is_point(M, np2; error=:error) @test !is_vector(M, p1, 0.0) @test_throws DomainError is_vector( M, p1, - [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0], - true, + [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0]; + error=:error, ) @test !is_vector(M, np1, [0.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0]) @test !is_vector(M, p1, p2) diff --git a/test/manifolds/euclidean.jl b/test/manifolds/euclidean.jl index c4024d6850..c92fe16ce4 100644 --- a/test/manifolds/euclidean.jl +++ b/test/manifolds/euclidean.jl @@ -1,154 +1,177 @@ include("../utils.jl") using Manifolds: induced_basis +using FiniteDifferences @testset "Euclidean" begin - E = Euclidean(3) - Ec = Euclidean(3; field=ℂ) - EM = Manifolds.MetricManifold(E, Manifolds.EuclideanMetric()) - @test repr(E) == "Euclidean(3; field = ℝ)" - @test repr(Ec) == "Euclidean(3; field = ℂ)" - @test repr(Euclidean(2, 3; field=ℍ)) == "Euclidean(2, 3; field = ℍ)" - @test Manifolds.allocation_promotion_function(Ec, get_vector, ()) === complex - @test is_flat(E) - @test is_flat(Ec) - p = zeros(3) - A = Manifolds.RetractionAtlas() - B = induced_basis(EM, A, p, TangentSpace) - @test det_local_metric(EM, p, B) == one(eltype(p)) - @test log_local_metric_density(EM, p, B) == zero(eltype(p)) - @test project!(E, p, p) == p - @test embed!(E, p, p) == p - @test manifold_dimension(Ec) == 2 * manifold_dimension(E) - X = zeros(3) - X[1] = 1.0 - Y = similar(X) - project!(E, Y, p, X) - @test Y == X - @test embed(E, p, X) == X - - # temp: explicit test for induced basis - B = induced_basis(E, RetractionAtlas(), 0, ManifoldsBase.TangentSpaceType()) - @test get_coordinates(E, p, X, B) == X - get_coordinates!(E, Y, p, X, B) - @test Y == X - @test get_vector(E, p, Y, B) == X - Y2 = similar(X) - get_vector!(E, Y2, p, Y, B) - @test Y2 == X - - Y = parallel_transport_along(E, p, X, [p]) - @test Y == X - parallel_transport_along!(E, Y, p, X, [p]) - @test Y == X - - Y = vector_transport_along(E, p, X, [p]) - @test Y == X - vector_transport_along!(E, Y, p, X, [p]) - @test Y == X - - # real manifold does not allow complex values - @test_throws DomainError is_point(Ec, [:a, :b, :b], true) - @test_throws DomainError is_point(E, [1.0, 1.0im, 0.0], true) - @test_throws DomainError is_point(E, [1], true) - @test_throws DomainError is_vector(Ec, [:a, :b, :b], [1.0, 1.0, 0.0], true) - @test_throws DomainError is_vector(E, [1.0, 1.0im, 0.0], [1.0, 1.0, 0.0], true) # real manifold does not allow complex values - @test_throws DomainError is_vector(E, [1], [1.0, 1.0, 0.0], true) - @test_throws DomainError is_vector(E, [0.0, 0.0, 0.0], [1.0], true) - @test_throws DomainError is_vector(E, [0.0, 0.0, 0.0], [1.0, 0.0, 1.0im], true) - @test_throws DomainError is_vector(Ec, [0.0, 0.0, 0.0], [:a, :b, :c], true) - - @test E^2 === Euclidean(3, 2) - @test ^(E, 2) === Euclidean(3, 2) - @test E^(2,) === Euclidean(3, 2) - @test Ec^(4, 5) === Euclidean(3, 4, 5; field=ℂ) - - manifolds = [E, EM, Ec] - types = [Vector{Float64}] - TEST_FLOAT32 && push!(types, Vector{Float32}) - TEST_DOUBLE64 && push!(types, Vector{Double64}) - TEST_STATIC_SIZED && push!(types, MVector{3,Float64}) - - types_complex = [Vector{ComplexF64}] - TEST_FLOAT32 && push!(types_complex, Vector{ComplexF32}) - TEST_DOUBLE64 && push!(types_complex, Vector{ComplexDF64}) - TEST_STATIC_SIZED && push!(types_complex, MVector{3,ComplexF64}) - - for M in manifolds - basis_types = if M == E - ( - DefaultOrthonormalBasis(), - ProjectedOrthonormalBasis(:svd), - DiagonalizingOrthonormalBasis([1.0, 2.0, 3.0]), - ) - elseif M == Ec - ( - DefaultOrthonormalBasis(), - DefaultOrthonormalBasis(ℂ), - DiagonalizingOrthonormalBasis([1.0, 2.0, 3.0]), - DiagonalizingOrthonormalBasis([1.0, 2.0, 3.0], ℂ), - ) + for param in [:field, :type] + E = Euclidean(3, parameter=param) + Ec = Euclidean(3; field=ℂ, parameter=param) + EM = Manifolds.MetricManifold(E, Manifolds.EuclideanMetric()) + EH = Euclidean(2, 3; field=ℍ, parameter=param) + if param === :type + @test repr(E) == "Euclidean(3; field=ℝ)" + @test repr(Ec) == "Euclidean(3; field=ℂ)" + @test repr(EH) == "Euclidean(2, 3; field=ℍ)" else - () + @test repr(E) == "Euclidean(3; field=ℝ, parameter=:field)" + @test repr(Ec) == "Euclidean(3; field=ℂ, parameter=:field)" + @test repr(EH) == "Euclidean(2, 3; field=ℍ, parameter=:field)" end - for T in types - @testset "$M Type $T" begin + + @test Manifolds.allocation_promotion_function(Ec, get_vector, ()) === complex + @test is_flat(E) + @test is_flat(Ec) + p = zeros(3) + A = Manifolds.RetractionAtlas() + B = induced_basis(EM, A, p, TangentSpaceType()) + @test det_local_metric(EM, p, B) == one(eltype(p)) + @test log_local_metric_density(EM, p, B) == zero(eltype(p)) + @test project!(E, p, p) == p + @test embed!(E, p, p) == p + @test manifold_dimension(Ec) == 2 * manifold_dimension(E) + X = zeros(3) + X[1] = 1.0 + Y = similar(X) + project!(E, Y, p, X) + @test Y == X + @test embed(E, p, X) == X + + # temp: explicit test for induced basis + B = induced_basis(E, RetractionAtlas(), 0, ManifoldsBase.TangentSpaceType()) + @test get_coordinates(E, p, X, B) == X + get_coordinates!(E, Y, p, X, B) + @test Y == X + @test get_vector(E, p, Y, B) == X + Y2 = similar(X) + get_vector!(E, Y2, p, Y, B) + @test Y2 == X + + Y = parallel_transport_along(E, p, X, [p]) + @test Y == X + parallel_transport_along!(E, Y, p, X, [p]) + @test Y == X + + Y = vector_transport_along(E, p, X, [p]) + @test Y == X + vector_transport_along!(E, Y, p, X, [p]) + @test Y == X + + # real manifold does not allow complex values + @test_throws DomainError is_point(Ec, [:a, :b, :b]; error=:error) + @test_throws DomainError is_point(E, [1.0, 1.0im, 0.0], error=:error) + @test_throws DomainError is_point(E, [1]; error=:error) + @test_throws DomainError is_vector(Ec, [:a, :b, :b], [1.0, 1.0, 0.0]; error=:error) + @test_throws DomainError is_vector( + E, + [1.0, 1.0im, 0.0], + [1.0, 1.0, 0.0]; + error=:error, + ) # real manifold does not allow complex values + @test_throws DomainError is_vector(E, [1], [1.0, 1.0, 0.0]; error=:error) + @test_throws DomainError is_vector(E, [0.0, 0.0, 0.0], [1.0]; error=:error) + @test_throws DomainError is_vector( + E, + [0.0, 0.0, 0.0], + [1.0, 0.0, 1.0im]; + error=:error, + ) + @test_throws DomainError is_vector(Ec, [0.0, 0.0, 0.0], [:a, :b, :c]; error=:error) + + @test E^2 === Euclidean(3, 2, parameter=param) + @test ^(E, 2) === Euclidean(3, 2, parameter=param) + @test E^(2,) === Euclidean(3, 2, parameter=param) + @test Ec^(4, 5) === Euclidean(3, 4, 5; field=ℂ, parameter=param) + + manifolds = [E, EM, Ec] + types = [Vector{Float64}] + TEST_FLOAT32 && push!(types, Vector{Float32}) + TEST_DOUBLE64 && push!(types, Vector{Double64}) + TEST_STATIC_SIZED && push!(types, MVector{3,Float64}) + + types_complex = [Vector{ComplexF64}] + TEST_FLOAT32 && push!(types_complex, Vector{ComplexF32}) + TEST_DOUBLE64 && push!(types_complex, Vector{ComplexDF64}) + TEST_STATIC_SIZED && push!(types_complex, MVector{3,ComplexF64}) + + for M in manifolds + basis_types = if M == E + ( + DefaultOrthonormalBasis(), + ProjectedOrthonormalBasis(:svd), + DiagonalizingOrthonormalBasis([1.0, 2.0, 3.0]), + ) + elseif M == Ec + ( + DefaultOrthonormalBasis(), + DefaultOrthonormalBasis(ℂ), + DiagonalizingOrthonormalBasis([1.0, 2.0, 3.0]), + DiagonalizingOrthonormalBasis([1.0, 2.0, 3.0], ℂ), + ) + else + () + end + for T in types + @testset "$M Type $T" begin + pts = [ + convert(T, [1.0, 0.0, 0.0]), + convert(T, [0.0, 1.0, 0.0]), + convert(T, [0.0, 0.0, 1.0]), + ] + test_manifold( + M, + pts, + test_project_point=true, + test_project_tangent=true, + test_musical_isomorphisms=true, + test_default_vector_transport=true, + vector_transport_methods=[ + ParallelTransport(), + SchildsLadderTransport(), + PoleLadderTransport(), + ], + test_mutating_rand=isa(T, Vector), + point_distributions=[ + Manifolds.projected_distribution( + M, + Distributions.MvNormal(zero(pts[1]), 1.0 * I), + ), + ], + tvector_distributions=[ + Manifolds.normal_tvector_distribution(M, pts[1], 1.0 * I), + ], + basis_types_vecs=basis_types, + basis_types_to_from=basis_types, + basis_has_specialized_diagonalizing_get=true, + test_vee_hat=isa(M, Euclidean), + test_inplace=true, + test_rand_point=M === E, + test_rand_tvector=M === E, + ) + end + end + end + for T in types_complex + @testset "Complex Euclidean, type $T" begin pts = [ - convert(T, [1.0, 0.0, 0.0]), - convert(T, [0.0, 1.0, 0.0]), + convert(T, [1.0im, -1.0im, 1.0]), + convert(T, [0.0, 1.0, 1.0im]), convert(T, [0.0, 0.0, 1.0]), ] test_manifold( - M, + Ec, pts, - test_project_point=true, test_project_tangent=true, test_musical_isomorphisms=true, test_default_vector_transport=true, - vector_transport_methods=[ - ParallelTransport(), - SchildsLadderTransport(), - PoleLadderTransport(), - ], - test_mutating_rand=isa(T, Vector), - point_distributions=[ - Manifolds.projected_distribution( - M, - Distributions.MvNormal(zero(pts[1]), 1.0 * I), - ), - ], - tvector_distributions=[ - Manifolds.normal_tvector_distribution(M, pts[1], 1.0 * I), - ], - basis_types_vecs=basis_types, - basis_types_to_from=basis_types, - basis_has_specialized_diagonalizing_get=true, - test_vee_hat=isa(M, Euclidean), - test_inplace=true, - test_rand_point=M === E, - test_rand_tvector=M === E, + test_vee_hat=false, + parallel_transport=true, ) end end end - for T in types_complex - @testset "Complex Euclidean, type $T" begin - pts = [ - convert(T, [1.0im, -1.0im, 1.0]), - convert(T, [0.0, 1.0, 1.0im]), - convert(T, [0.0, 0.0, 1.0]), - ] - test_manifold( - Ec, - pts, - test_project_tangent=true, - test_musical_isomorphisms=true, - test_default_vector_transport=true, - test_vee_hat=false, - parallel_transport=true, - ) - end - end + E = Euclidean(3) + Ec = Euclidean(3; field=ℂ) number_types = [Float64, ComplexF64] TEST_FLOAT32 && push!(number_types, Float32) @@ -251,7 +274,7 @@ using Manifolds: induced_basis p = zeros(2) A = Manifolds.get_default_atlas(M) i = Manifolds.get_chart_index(M, A, p) - B = Manifolds.induced_basis(M, A, i, TangentSpace) + B = Manifolds.induced_basis(M, A, i, TangentSpaceType()) C1 = christoffel_symbols_first(M, p, B) @test size(C1) == (2, 2, 2) @test norm(C1) ≈ 0.0 atol = 1e-13 @@ -281,7 +304,7 @@ using Manifolds: induced_basis p = zeros(3) M = DefaultManifold() TpM = TangentSpace(M, p) - B = induced_basis(M, Manifolds.get_default_atlas(M), p, TangentSpace) + B = induced_basis(M, Manifolds.get_default_atlas(M), p, TangentSpaceType()) MM = MetricManifold(M, EuclideanMetric()) @test local_metric(MM, p, B) == Diagonal(ones(3)) @test inverse_local_metric(MM, p, B) == Diagonal(ones(3)) @@ -289,7 +312,7 @@ using Manifolds: induced_basis DB1 = dual_basis(MM, p, B) @test DB1 isa InducedBasis @test DB1.vs isa ManifoldsBase.CotangentSpaceType - DB2 = induced_basis(M, Manifolds.get_default_atlas(M), p, CotangentSpace) + DB2 = induced_basis(M, Manifolds.get_default_atlas(M), p, CotangentSpaceType()) @test DB2 isa InducedBasis @test DB2.vs isa ManifoldsBase.CotangentSpaceType DDB = dual_basis(MM, p, DB2) @@ -331,6 +354,14 @@ using Manifolds: induced_basis SizedMatrix{2,2}([-1.0, -2.0, -3.0, -4.0]), DefaultOrthonormalBasis(), ) == SA[-1.0 -3.0; -2.0 -4.0] + + M1c = Euclidean(3, field=ℂ) + get_vector( + M1c, + SizedVector{3}([1.0im, 2.0, 4.0im]), + SizedVector{3}([-1.0, -3.0, -4.0im]), + DefaultOrthonormalBasis(), + ) == SA[-1.0, -3.0, -4.0im] end @testset "Euclidean(1)" begin @@ -376,7 +407,43 @@ using Manifolds: induced_basis @test rH == H end @testset "Volume" begin - @test manifold_volume(Euclidean(2)) == Inf + E = Euclidean(3) + @test manifold_volume(E) == Inf + p = zeros(3) + X = zeros(3) @test volume_density(E, p, X) == 1.0 end + + @testset "field parameter" begin + Ms = Euclidean(1; parameter=:field) + M0s = Euclidean(; parameter=:field) + + @test distance(Ms, 2.0, 4.0) == 2.0 + @test distance(M0s, 2.0, 4.0) == 2.0 + @test log(M0s, 2.0, 4.0) == 2.0 + @test manifold_dimension(M0s) == 1 + @test project(M0s, 4.0) == 4.0 + @test project(M0s, 2.0, 4.0) == 4.0 + @test retract(M0s, 2.0, 4.0) == 6.0 + @test retract(M0s, 2.0, 4.0, ExponentialRetraction()) == 6.0 + + @test ManifoldDiff.adjoint_Jacobi_field( + M0s, + 0.0, + 1.0, + 0.5, + 2.0, + ManifoldDiff.βdifferential_shortest_geodesic_startpoint, + ) === 2.0 + @test ManifoldDiff.diagonalizing_projectors(M0s, 0.0, 2.0) == + ((0.0, ManifoldDiff.IdentityProjector()),) + @test ManifoldDiff.jacobi_field( + M0s, + 0.0, + 1.0, + 0.5, + 2.0, + ManifoldDiff.βdifferential_shortest_geodesic_startpoint, + ) === 2.0 + end end diff --git a/test/manifolds/fiber.jl b/test/manifolds/fiber.jl new file mode 100644 index 0000000000..8bf9253d09 --- /dev/null +++ b/test/manifolds/fiber.jl @@ -0,0 +1,32 @@ +include("../utils.jl") + +using RecursiveArrayTools + +struct TestVectorSpaceType <: VectorSpaceType end + +@testset "spaces at point" begin + M = Sphere(2) + @testset "tangent and cotangent space" begin + p = [1.0, 0.0, 0.0] + t_p = TangentSpace(M, p) + ct_p = CotangentSpace(M, p) + t_ps = sprint(show, "text/plain", t_p) + sp = sprint(show, "text/plain", p) + sp = replace(sp, '\n' => "\n ") + t_ps_test = "Tangent space to the manifold $(M) at point:\n $(sp)" + @test t_ps == t_ps_test + @test base_manifold(t_p) == M + @test base_manifold(ct_p) == M + @test t_p.manifold == M + @test ct_p.manifold == M + @test t_p.fiber_type == TangentSpaceType() + @test ct_p.fiber_type == CotangentSpaceType() + @test t_p.point == p + @test ct_p.point == p + @test injectivity_radius(t_p) == Inf + @test representation_size(t_p) == representation_size(M) + X = [0.0, 0.0, 1.0] + @test embed(t_p, X) == X + @test embed(t_p, X, X) == X + end +end diff --git a/test/manifolds/fiber_bundle.jl b/test/manifolds/fiber_bundle.jl new file mode 100644 index 0000000000..9950c1ac67 --- /dev/null +++ b/test/manifolds/fiber_bundle.jl @@ -0,0 +1,10 @@ +include("../utils.jl") + +using RecursiveArrayTools + +@testset "fiber bundle" begin + M = Stiefel(3, 2) + vm = default_vector_transport_method(M) + @test Manifolds.FiberBundleProductVectorTransport(M) == + Manifolds.FiberBundleProductVectorTransport(vm, vm) +end diff --git a/test/manifolds/fixed_rank.jl b/test/manifolds/fixed_rank.jl index e381bb4da8..f779c06444 100644 --- a/test/manifolds/fixed_rank.jl +++ b/test/manifolds/fixed_rank.jl @@ -53,9 +53,9 @@ include("../utils.jl") @test !is_flat(M) @test !is_flat(Mc) @test !is_point(M, SVDMPoint([1.0 0.0; 0.0 0.0], 2)) - @test_throws DomainError is_point(M, SVDMPoint([1.0 0.0; 0.0 0.0], 2), true) + @test_throws DomainError is_point(M, SVDMPoint([1.0 0.0; 0.0 0.0], 2); error=:error) @test is_point(M2, p2) - @test_throws DomainError is_point(M2, [1.0 0.0; 0.0 1.0; 0.0 0.0], true) + @test_throws DomainError is_point(M2, [1.0 0.0; 0.0 1.0; 0.0 0.0]; error=:error) @test Manifolds.check_point(M2, [1.0 0.0; 0.0 1.0; 0.0 0.0]) isa DomainError @test default_retraction_method(M) === PolarRetraction() @@ -68,16 +68,26 @@ include("../utils.jl") UMVTVector(zeros(2, 1), zeros(1, 2), zeros(2, 2)), ) @test !is_vector(M, SVDMPoint([1.0 0.0; 0.0 0.0], 2), X) - @test_throws ManifoldDomainError is_vector( + @test_throws DomainError is_vector( M, SVDMPoint([1.0 0.0; 0.0 0.0], 2), - X, - true, + X; + error=:error, ) @test !is_vector(M, p, UMVTVector(p.U, X.M, p.Vt, 2)) - @test_throws DomainError is_vector(M, p, UMVTVector(p.U, X.M, p.Vt, 2), true) + @test_throws DomainError is_vector( + M, + p, + UMVTVector(p.U, X.M, p.Vt, 2); + error=:error, + ) @test !is_vector(M, p, UMVTVector(X.U, X.M, p.Vt, 2)) - @test_throws DomainError is_vector(M, p, UMVTVector(X.U, X.M, p.Vt, 2), true) + @test_throws DomainError is_vector( + M, + p, + UMVTVector(X.U, X.M, p.Vt, 2); + error=:error, + ) @test is_point(M, p) @test is_vector(M, p, X) @@ -134,15 +144,23 @@ include("../utils.jl") xM = embed(M, p) @test is_point(M, xM) @test !is_point(M, xM[1:2, :]) - @test_throws DomainError is_point(M, xM[1:2, :], true) - @test_throws DomainError is_point(FixedRankMatrices(3, 2, 1), p, true) - @test_throws DomainError is_point(FixedRankMatrices(3, 2, 1), xM, true) + @test_throws DomainError is_point(M, xM[1:2, :]; error=:error) + @test_throws DomainError is_point( + FixedRankMatrices(3, 2, 1), + p; + error=:error, + ) + @test_throws DomainError is_point( + FixedRankMatrices(3, 2, 1), + xM; + error=:error, + ) xF1 = SVDMPoint(2 * p.U, p.S, p.Vt) @test !is_point(M, xF1) - @test_throws DomainError is_point(M, xF1, true) + @test_throws DomainError is_point(M, xF1; error=:error) xF2 = SVDMPoint(p.U, p.S, 2 * p.Vt) @test !is_point(M, xF2) - @test_throws DomainError is_point(M, xF2, true) + @test_throws DomainError is_point(M, xF2; error=:error) # copyto yC = allocate(y) copyto!(M, yC, y) @@ -239,4 +257,9 @@ include("../utils.jl") H = [0.0 3.0; 0.0 4.0; 0.0 1.0] @test is_vector(M, p, riemannian_Hessian(M, p, G, H, X)) end + @testset "field parameter" begin + M = FixedRankMatrices(3, 2, 2; parameter=:field) + @test repr(M) == "FixedRankMatrices(3, 2, 2, ℝ; parameter=:field)" + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + end end diff --git a/test/manifolds/flag.jl b/test/manifolds/flag.jl index 53e4f6c09d..fb4533c7e2 100644 --- a/test/manifolds/flag.jl +++ b/test/manifolds/flag.jl @@ -267,6 +267,18 @@ using Random @test isapprox(Y_tmp, X1_ortho.value) @test retract(M, p1_ortho, X1_ortho, QRRetraction()).value ≈ - retract(Orthogonal(5), p1_ortho.value, X1_ortho.value, QRRetraction()) + retract(OrthogonalMatrices(5), p1_ortho.value, X1_ortho.value, QRRetraction()) + + @testset "field parameters" begin + M = Flag(5, 1, 2; parameter=:field) + @test get_embedding(M, p1_ortho) == OrthogonalMatrices(5; parameter=:field) + end + end + + @testset "field parameters" begin + M = Flag(5, 1, 2; parameter=:field) + @test Manifolds.get_parameter(M.size)[1] == 5 + @test get_embedding(M) == Stiefel(5, 2; parameter=:field) + @test repr(M) == "Flag(5, 1, 2; parameter=:field)" end end diff --git a/test/manifolds/generalized_grassmann.jl b/test/manifolds/generalized_grassmann.jl index d7790f8b8d..243b3af019 100644 --- a/test/manifolds/generalized_grassmann.jl +++ b/test/manifolds/generalized_grassmann.jl @@ -14,22 +14,27 @@ include("../utils.jl") @test manifold_dimension(M) == 2 @test base_manifold(M) === M @test !is_flat(M) - @test_throws ManifoldDomainError is_point(M, [1.0, 0.0, 0.0, 0.0], true) + @test_throws ManifoldDomainError is_point(M, [1.0, 0.0, 0.0, 0.0]; error=:error) @test_throws ManifoldDomainError is_point( M, - 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0], - true, + 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0]; + error=:error, ) - @test_throws ManifoldDomainError is_point(M, 2 * p, true) + @test_throws ManifoldDomainError is_point(M, 2 * p; error=:error) @test !is_vector(M, p, [0.0, 0.0, 1.0, 0.0]) - @test_throws ManifoldDomainError is_vector(M, p, [0.0, 0.0, 1.0, 0.0], true) @test_throws ManifoldDomainError is_vector( M, p, - 1 * im * zero_vector(M, p), - true, + [0.0, 0.0, 1.0, 0.0]; + error=:error, ) - @test_throws ManifoldDomainError is_vector(M, p, X, true) + @test_throws ManifoldDomainError is_vector( + M, + p, + 1 * im * zero_vector(M, p); + error=:error, + ) + @test_throws ManifoldDomainError is_vector(M, p, X; error=:error) @test injectivity_radius(M) == π / 2 @test injectivity_radius(M, ExponentialRetraction()) == π / 2 @test injectivity_radius(M, p) == π / 2 @@ -90,9 +95,9 @@ include("../utils.jl") @testset "Type $T" for T in types pts = convert.(T, [p, q, r]) @test !is_point(M, 2 * p) - @test_throws ManifoldDomainError !is_point(M, 2 * r, true) + @test_throws ManifoldDomainError !is_point(M, 2 * r; error=:error) @test !is_vector(M, p, q) - @test_throws ManifoldDomainError is_vector(M, p, q, true) + @test_throws ManifoldDomainError is_vector(M, p, q; error=:error) test_manifold( M, pts, @@ -164,4 +169,12 @@ include("../utils.jl") end end end + @testset "field parameter" begin + B = [1.0 0.0 0.0; 0.0 4.0 0.0; 0.0 0.0 1.0] + M = GeneralizedGrassmann(3, 2, B; parameter=:field) + @test repr(M) == + "GeneralizedGrassmann(3, 2, [1.0 0.0 0.0; 0.0 4.0 0.0; 0.0 0.0 1.0], ℝ; parameter=:field)" + @test typeof(get_embedding(M)) === + GeneralizedStiefel{Tuple{Int64,Int64},ℝ,Matrix{Float64}} + end end diff --git a/test/manifolds/generalized_stiefel.jl b/test/manifolds/generalized_stiefel.jl index a1e6141ebd..9dc195e0e7 100644 --- a/test/manifolds/generalized_stiefel.jl +++ b/test/manifolds/generalized_stiefel.jl @@ -14,22 +14,22 @@ include("../utils.jl") @test manifold_dimension(M) == 3 @test base_manifold(M) === M @test !is_flat(M) - @test_throws DomainError is_point(M, [1.0, 0.0, 0.0, 0.0], true) + @test_throws DomainError is_point(M, [1.0, 0.0, 0.0, 0.0]; error=:error) @test_throws ManifoldDomainError is_point( M, - 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0], - true, + 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0]; + error=:error, ) - @test_throws DomainError is_point(M, 2 * p, true) + @test_throws DomainError is_point(M, 2 * p; error=:error) @test !is_vector(M, p, [0.0, 0.0, 1.0, 0.0]) - @test_throws DomainError is_vector(M, p, [0.0, 0.0, 1.0, 0.0], true) + @test_throws DomainError is_vector(M, p, [0.0, 0.0, 1.0, 0.0]; error=:error) @test_throws ManifoldDomainError is_vector( M, p, - 1 * im * zero_vector(M, p), - true, + 1 * im * zero_vector(M, p); + error=:error, ) - @test_throws DomainError is_vector(M, p, X, true) + @test_throws DomainError is_vector(M, p, X; error=:error) @test default_retraction_method(M) == ProjectionRetraction() @test is_point(M, rand(M)) @test is_vector(M, p, rand(M; vector_at=p)) @@ -79,9 +79,9 @@ include("../utils.jl") @testset "Type $T" for T in types pts = convert.(T, [p, y, z]) @test !is_point(M, 2 * p) - @test_throws DomainError !is_point(M, 2 * p, true) + @test_throws DomainError !is_point(M, 2 * p; error=:error) @test !is_vector(M, p, y) - @test_throws DomainError is_vector(M, p, y, true) + @test_throws DomainError is_vector(M, p, y; error=:error) test_manifold( M, pts, @@ -134,4 +134,12 @@ include("../utils.jl") @test !is_flat(M) end end + + @testset "field parameter" begin + B = [1.0 0.0 0.0; 0.0 4.0 0.0; 0.0 0.0 1.0] + M = GeneralizedStiefel(3, 2, B; parameter=:field) + @test repr(M) == + "GeneralizedStiefel(3, 2, [1.0 0.0 0.0; 0.0 4.0 0.0; 0.0 0.0 1.0], ℝ; parameter=:field)" + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int64,Int64},ℝ} + end end diff --git a/test/manifolds/graph.jl b/test/manifolds/graph.jl index d0db4ca5f7..73f0cd5c12 100644 --- a/test/manifolds/graph.jl +++ b/test/manifolds/graph.jl @@ -15,12 +15,12 @@ include("../utils.jl") manifold_dimension(M) * ne(G) @test is_point(N, x) @test !is_point(N, [x..., [0.0, 0.0]]) # an entry too much - @test_throws DomainError is_point(N, [x..., [0.0, 0.0]], true) + @test_throws DomainError is_point(N, [x..., [0.0, 0.0]]; error=:error) @test is_vector(N, x, log(N, x, y)) @test !is_vector(N, x[1:2], log(N, x, y)) - @test_throws DomainError is_vector(N, x[1:2], log(N, x, y), true) + @test_throws DomainError is_vector(N, x[1:2], log(N, x, y); error=:error) @test !is_vector(N, x[1:2], log(N, x, y)[1:2]) - @test_throws DomainError is_vector(N, x, log(N, x, y)[1:2], true) + @test_throws DomainError is_vector(N, x, log(N, x, y)[1:2]; error=:error) @test incident_log(N, x) == [x[2] - x[1], x[1] - x[2] + x[3] - x[2], x[2] - x[3]] pts = [x, y, z] @@ -30,17 +30,17 @@ include("../utils.jl") Graph: {3, 2} undirected simple Int64 graph AbstractManifold on vertices: - Euclidean(2; field = ℝ)""" + Euclidean(2; field=ℝ)""" NE = GraphManifold(G, M, EdgeManifold()) @test is_point(NE, x[1:2]) @test !is_point(NE, x) # an entry too much - @test_throws DomainError is_point(NE, x, true) + @test_throws DomainError is_point(NE, x; error=:error) @test is_vector(NE, x[1:2], log(N, x, y)[1:2]) @test !is_vector(NE, x, log(N, x, y)) - @test_throws DomainError is_vector(NE, x, log(N, x, y), true) + @test_throws DomainError is_vector(NE, x, log(N, x, y); error=:error) @test !is_vector(N, x[1:2], log(N, x, y)) - @test_throws DomainError is_vector(NE, x[1:2], log(N, x, y), true) + @test_throws DomainError is_vector(NE, x[1:2], log(N, x, y); error=:error) test_manifold( NE, @@ -53,7 +53,7 @@ include("../utils.jl") Graph: {3, 2} undirected simple Int64 graph AbstractManifold on edges: - Euclidean(2; field = ℝ)""" + Euclidean(2; field=ℝ)""" G2 = SimpleDiGraph(3) add_edge!(G2, 1, 2) diff --git a/test/manifolds/grassmann.jl b/test/manifolds/grassmann.jl index bbab887d7b..500ad62f5a 100644 --- a/test/manifolds/grassmann.jl +++ b/test/manifolds/grassmann.jl @@ -19,36 +19,40 @@ include("../utils.jl") Manifolds.RowwiseMultiplicationAction(M, Orthogonal(2)) @test !is_point(M, [1.0, 0.0, 0.0, 0.0]) @test !is_vector(M, [1.0 0.0; 0.0 1.0; 0.0 0.0], [0.0, 0.0, 1.0, 0.0]) - @test_throws ManifoldDomainError is_point(M, [2.0 0.0; 0.0 1.0; 0.0 0.0], true) + @test_throws ManifoldDomainError is_point( + M, + [2.0 0.0; 0.0 1.0; 0.0 0.0]; + error=:error, + ) @test_throws ManifoldDomainError is_vector( M, [2.0 0.0; 0.0 1.0; 0.0 0.0], - zeros(3, 2), - true, + zeros(3, 2); + error=:error, ) @test_throws ManifoldDomainError is_vector( M, [1.0 0.0; 0.0 1.0; 0.0 0.0], - ones(3, 2), - true, + ones(3, 2); + error=:error, ) - @test is_point(M, [1.0 0.0; 0.0 1.0; 0.0 0.0], true) + @test is_point(M, [1.0 0.0; 0.0 1.0; 0.0 0.0]; error=:error) @test_throws ManifoldDomainError is_point( M, - 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0], - true, + 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0]; + error=:error, ) @test is_vector( M, [1.0 0.0; 0.0 1.0; 0.0 0.0], - zero_vector(M, [1.0 0.0; 0.0 1.0; 0.0 0.0]), - true, + zero_vector(M, [1.0 0.0; 0.0 1.0; 0.0 0.0]); + error=:error, ) @test_throws ManifoldDomainError is_vector( M, [1.0 0.0; 0.0 1.0; 0.0 0.0], - 1im * zero_vector(M, [1.0 0.0; 0.0 1.0; 0.0 0.0]), - true, + 1im * zero_vector(M, [1.0 0.0; 0.0 1.0; 0.0 0.0]); + error=:error, ) @test injectivity_radius(M) == π / 2 @test injectivity_radius(M, ExponentialRetraction()) == π / 2 @@ -135,8 +139,8 @@ include("../utils.jl") @test is_vector( M, p2, - vector_transport_to(M, p1, X, p2, ProjectionTransport()), - true; + vector_transport_to(M, p1, X, p2, ProjectionTransport()); + error=:error, atol=10^-15, ) end @@ -159,18 +163,22 @@ include("../utils.jl") @test !is_point(M, [1.0, 0.0, 0.0, 0.0]) @test !is_vector(M, [1.0 0.0; 0.0 1.0; 0.0 0.0], [0.0, 0.0, 1.0, 0.0]) @test Manifolds.allocation_promotion_function(M, exp!, (1,)) == complex - @test_throws ManifoldDomainError is_point(M, [2.0 0.0; 0.0 1.0; 0.0 0.0], true) + @test_throws ManifoldDomainError is_point( + M, + [2.0 0.0; 0.0 1.0; 0.0 0.0]; + error=:error, + ) @test_throws ManifoldDomainError is_vector( M, [2.0 0.0; 0.0 1.0; 0.0 0.0], - zeros(3, 2), - true, + zeros(3, 2); + error=:error, ) @test_throws ManifoldDomainError is_vector( M, [1.0 0.0; 0.0 1.0; 0.0 0.0], - ones(3, 2), - true, + ones(3, 2); + error=:error, ) @test is_vector( M, @@ -230,7 +238,7 @@ include("../utils.jl") p = reshape([im, 0.0, 0.0], 3, 1) @test is_point(G, p) X = reshape([-0.5; 0.5; 0], 3, 1) - @test_throws ManifoldDomainError is_vector(G, p, X, true) + @test_throws ManifoldDomainError is_vector(G, p, X; error=:error) Y = project(G, p, X) @test is_vector(G, p, Y) end @@ -307,16 +315,16 @@ include("../utils.jl") M = Grassmann(3, 2) p = StiefelPoint([1.0 0.0; 0.0 1.0; 0.0 0.0]) X = StiefelTVector([0.0 1.0; -1.0 0.0; 0.0 0.0]) - @test is_point(M, p, true) - @test is_vector(M, p, X, true) + @test is_point(M, p; error=:error) + @test is_vector(M, p, X; error=:error) @test repr(p) == "StiefelPoint($(p.value))" @test repr(X) == "StiefelTVector($(X.value))" M2 = Stiefel(3, 2) - @test is_point(M2, p, true) - @test is_vector(M2, p, X, true) + @test is_point(M2, p; error=:error) + @test is_vector(M2, p, X; error=:error) p2 = convert(ProjectorPoint, p) - @test is_point(M, p2, true) + @test is_point(M, p2; error=:error) p3 = convert(ProjectorPoint, p.value) @test p2.value == p3.value X2 = ProjectorTVector([0.0 0.0 1.0; 0.0 0.0 1.0; 1.0 1.0 0.0]) @@ -326,20 +334,20 @@ include("../utils.jl") # rank just 1 pF1 = ProjectorPoint([1.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0]) - @test_throws DomainError is_point(M, pF1, true) + @test_throws DomainError is_point(M, pF1; error=:error) # not equal to its square pF2 = ProjectorPoint([1.0 0.0 0.0; 0.0 0.0 0.0; 0.0 1.0 0.0]) - @test_throws DomainError is_point(M, pF2, true) + @test_throws DomainError is_point(M, pF2; error=:error) # not symmetric pF3 = ProjectorPoint([0.0 1.0 0.0; 0.0 1.0 0.0; 0.0 0.0 0.0]) - @test_throws DomainError is_point(M, pF3, true) + @test_throws DomainError is_point(M, pF3; error=:error) # not symmetric XF1 = ProjectorTVector([0.0 0.0 1.0; 0.0 0.0 1.0; 1.0 0.0 0.0]) - @test_throws DomainError is_vector(M, p2, XF1, true) + @test_throws DomainError is_vector(M, p2, XF1; error=:error) # XF2 is not p2*XF2 + XF2*p2 XF2 = ProjectorTVector(ones(3, 3)) - @test_throws DomainError is_vector(M, p2, XF2, true) + @test_throws DomainError is_vector(M, p2, XF2; error=:error) # embed for Stiefel with its point M2 = Stiefel(3, 2) @@ -388,4 +396,14 @@ include("../utils.jl") Z = [0.0 -1.0; 1.0 0.0; 1.0 0.0] @test riemannian_Hessian(M, p, Y, Z, X) == [0.0 0.0; 0.0 0.0; 2.0 0.0] end + @testset "field parameter" begin + M = Grassmann(3, 2; parameter=:field) + @test repr(M) == "Grassmann(3, 2, ℝ; parameter=:field)" + @test get_total_space(M) == Stiefel(3, 2; parameter=:field) + @test typeof(get_embedding(M)) === Stiefel{Tuple{Int64,Int64},ℝ} + + p = StiefelPoint([1.0 0.0; 0.0 1.0; 0.0 0.0]) + p2 = convert(ProjectorPoint, p) + @test get_embedding(M, p2) == Euclidean(3, 3; parameter=:field) + end end diff --git a/test/manifolds/hyperbolic.jl b/test/manifolds/hyperbolic.jl index 97a25da6a5..887627371c 100644 --- a/test/manifolds/hyperbolic.jl +++ b/test/manifolds/hyperbolic.jl @@ -7,7 +7,7 @@ include("../utils.jl") @test base_manifold(M) == M @test manifold_dimension(M) == 2 @test typeof(get_embedding(M)) == - MetricManifold{ℝ,Euclidean{Tuple{3},ℝ},MinkowskiMetric} + MetricManifold{ℝ,Euclidean{TypeParameter{Tuple{3}},ℝ},MinkowskiMetric} @test representation_size(M) == (3,) @test !is_flat(M) @test isinf(injectivity_radius(M)) @@ -16,17 +16,22 @@ include("../utils.jl") @test isinf(injectivity_radius(M, [0.0, 0.0, 1.0], ExponentialRetraction())) @test !is_point(M, [1.0, 0.0, 0.0, 0.0]) @test !is_vector(M, [0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0]) - @test_throws DomainError is_point(M, [2.0, 0.0, 0.0], true) + @test_throws DomainError is_point(M, [2.0, 0.0, 0.0]; error=:error) @test !is_point(M, [2.0, 0.0, 0.0]) @test !is_vector(M, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - @test_throws ManifoldDomainError is_vector( + @test_throws DomainError is_vector( M, [1.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - true, + [1.0, 0.0, 0.0]; + error=:error, ) @test !is_vector(M, [0.0, 0.0, 1.0], [1.0, 0.0, 1.0]) - @test_throws DomainError is_vector(M, [0.0, 0.0, 1.0], [1.0, 0.0, 1.0], true) + @test_throws DomainError is_vector( + M, + [0.0, 0.0, 1.0], + [1.0, 0.0, 1.0]; + error=:error, + ) @test is_default_metric(M, MinkowskiMetric()) @test manifold_dimension(M) == 2 @@ -71,22 +76,34 @@ include("../utils.jl") @test is_point(M, pB) @test convert(AbstractVector, pB) == p # convert back yields again p @test convert(HyperboloidPoint, pB).value == pH.value - @test_throws DomainError is_point(M, PoincareBallPoint([0.9, 0.0, 0.0]), true) - @test_throws DomainError is_point(M, PoincareBallPoint([1.1, 0.0]), true) + @test_throws DomainError is_point( + M, + PoincareBallPoint([0.9, 0.0, 0.0]); + error=:error, + ) + @test_throws DomainError is_point(M, PoincareBallPoint([1.1, 0.0]); error=:error) @test is_vector(M, pB, PoincareBallTVector([2.0, 2.0])) @test_throws DomainError is_vector( M, pB, - PoincareBallTVector([2.0, 2.0, 3.0]), - true, + PoincareBallTVector([2.0, 2.0, 3.0]); + error=:error, ) pS = convert(PoincareHalfSpacePoint, p) pS2 = convert(PoincareHalfSpacePoint, pB) pS3 = convert(PoincareHalfSpacePoint, pH) - @test_throws DomainError is_point(M, PoincareHalfSpacePoint([0.0, 0.0, 1.0]), true) - @test_throws DomainError is_point(M, PoincareHalfSpacePoint([0.0, -1.0]), true) + @test_throws DomainError is_point( + M, + PoincareHalfSpacePoint([0.0, 0.0, 1.0]); + error=:error, + ) + @test_throws DomainError is_point( + M, + PoincareHalfSpacePoint([0.0, -1.0]); + error=:error, + ) @test pS.value == pS2.value @test pS.value == pS3.value @@ -255,7 +272,7 @@ include("../utils.jl") B = get_basis(M, p, DefaultOrthonormalBasis()) V = get_vectors(M, p, B) for v in V - @test is_vector(M, p, v, true) + @test is_vector(M, p, v; error=:error) for b in [DefaultOrthonormalBasis(), DiagonalizingOrthonormalBasis(V[1])] @test isapprox(M, p, v, get_vector(M, p, get_coordinates(M, p, v, b), b)) end @@ -377,4 +394,14 @@ include("../utils.jl") @test volume_density(M, p, X) ≈ 2.980406103535168 @test volume_density(M, p, [0.0, 0.0, 0.0]) ≈ 1.0 end + @testset "field parameter" begin + M = Hyperbolic(2; parameter=:field) + @test repr(M) == "Hyperbolic(2; parameter=:field)" + @test typeof(get_embedding(M)) === Lorentz{Tuple{Int64},MinkowskiMetric} + + for Tp in [PoincareBallPoint, PoincareHalfSpacePoint] + p = convert(Tp, [1.0, 0.0, sqrt(2.0)]) + @test get_embedding(M, p) === Euclidean(2; parameter=:field) + end + end end diff --git a/test/manifolds/multinomial_doubly_stochastic.jl b/test/manifolds/multinomial_doubly_stochastic.jl index db7f65e6ec..dd48083f72 100644 --- a/test/manifolds/multinomial_doubly_stochastic.jl +++ b/test/manifolds/multinomial_doubly_stochastic.jl @@ -9,15 +9,15 @@ include("../utils.jl") @test is_point(M, p) @test is_vector(M, p, X) pf1 = [0.1 0.9 0.1; 0.1 0.9 0.1; 0.1 0.1 0.9] #not sum 1 - @test_throws ManifoldDomainError is_point(M, pf1, true) + @test_throws ManifoldDomainError is_point(M, pf1; error=:error) pf2r = [0.1 0.9 0.1; 0.8 0.05 0.15; 0.1 0.05 0.75] - @test_throws DomainError is_point(M, pf2r, true) - @test_throws ManifoldDomainError is_point(M, pf2r', true) + @test_throws DomainError is_point(M, pf2r; error=:error) + @test_throws ManifoldDomainError is_point(M, pf2r'; error=:error) pf3 = [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0] # contains nonpositive entries - @test_throws ManifoldDomainError is_point(M, pf3, true) + @test_throws ManifoldDomainError is_point(M, pf3; error=:error) Xf2c = [-0.1 0.0 0.1; -0.2 0.1 0.1; 0.2 -0.1 -0.1] #nonzero columns - @test_throws ManifoldDomainError is_vector(M, p, Xf2c, true) - @test_throws DomainError is_vector(M, p, Xf2c', true) + @test_throws ManifoldDomainError is_vector(M, p, Xf2c; error=:error) + @test_throws DomainError is_vector(M, p, Xf2c'; error=:error) @test representation_size(M) == (3, 3) @test !is_flat(M) pE = similar(p) @@ -63,4 +63,9 @@ include("../utils.jl") ) end end + @testset "field parameter" begin + M = MultinomialDoubleStochastic(3; parameter=:field) + @test repr(M) == "MultinomialDoubleStochastic(3; parameter=:field)" + @test get_embedding(M) === MultinomialMatrices(3, 3; parameter=:field) + end end diff --git a/test/manifolds/multinomial_matrices.jl b/test/manifolds/multinomial_matrices.jl index 8ab55db73f..c04d07410f 100644 --- a/test/manifolds/multinomial_matrices.jl +++ b/test/manifolds/multinomial_matrices.jl @@ -6,8 +6,11 @@ include("../utils.jl") @test ProbabilitySimplex(2)^3 === MultinomialMatrices(3, 3) @test ProbabilitySimplex(2)^(3,) === PowerManifold(ProbabilitySimplex(2), 3) @test ^(ProbabilitySimplex(2), 2) === MultinomialMatrices(3, 2) - @test typeof(^(ProbabilitySimplex(2), 2)) == MultinomialMatrices{3,2,2} - @test repr(M) == "MultinomialMatrices(3,2)" + @test typeof(^(ProbabilitySimplex(2), 2)) == MultinomialMatrices{ + TypeParameter{Tuple{3,2}}, + ProbabilitySimplex{TypeParameter{Tuple{2}},:open}, + } + @test repr(M) == "MultinomialMatrices(3, 2)" @test representation_size(M) == (3, 2) @test manifold_dimension(M) == 4 @test !is_flat(M) @@ -15,21 +18,21 @@ include("../utils.jl") p = [2, 0, 0] p2 = [p p] @test !is_point(M, p) - @test_throws DomainError is_point(M, p, true) + @test_throws DomainError is_point(M, p; error=:error) @test !is_point(M, p2) - @test_throws CompositeManifoldError is_point(M, p2, true) + @test_throws CompositeManifoldError is_point(M, p2; error=:error) @test !is_vector(M, p2, 0.0) @test_throws CompositeManifoldError{ComponentManifoldError{Int64,DomainError}} is_vector( M, p2, - [-1.0, 0.0, 0.0], - true, + [-1.0, 0.0, 0.0]; + error=:error, ) @test !is_vector(M, p2, [-1.0, 0.0, 0.0]) - @test_throws DomainError is_vector(M, p, [-1.0, 0.0, 0.0], true) + @test_throws DomainError is_vector(M, p, [-1.0, 0.0, 0.0]; error=:error) @test injectivity_radius(M) ≈ 0 x = [0.5 0.4 0.1; 0.5 0.4 0.1]' - @test_throws DomainError is_vector(M, x, [0.0, 0.0, 0.0], true) # tangent wrong + @test_throws DomainError is_vector(M, x, [0.0, 0.0, 0.0]; error=:error) # tangent wrong y = [0.6 0.3 0.1; 0.4 0.5 0.1]' z = [0.3 0.6 0.1; 0.6 0.3 0.1]' test_manifold( @@ -44,4 +47,12 @@ include("../utils.jl") test_inplace=true, ) end + @testset "field parameter" begin + M = ProbabilitySimplex(2; parameter=:field) + @test typeof(^(M, 2)) == + MultinomialMatrices{Tuple{Int64,Int64},ProbabilitySimplex{Tuple{Int64},:open}} + + M = MultinomialMatrices(3, 2; parameter=:field) + @test repr(M) == "MultinomialMatrices(3, 2; parameter=:field)" + end end diff --git a/test/manifolds/multinomial_symmetric.jl b/test/manifolds/multinomial_symmetric.jl index 6a89c4302f..58450b5bac 100644 --- a/test/manifolds/multinomial_symmetric.jl +++ b/test/manifolds/multinomial_symmetric.jl @@ -9,15 +9,15 @@ include("../utils.jl") @test is_point(M, p) @test is_vector(M, p, X) pf1 = [0.1 0.9 0.1; 0.1 0.9 0.1; 0.1 0.1 0.9] #not symmetric - @test_throws ManifoldDomainError is_point(M, pf1, true) + @test_throws ManifoldDomainError is_point(M, pf1; error=:error) pf2 = [0.8 0.1 0.1; 0.1 0.8 0.1; 0.1 0.1 0.9] # cols do not sum to 1 - @test_throws ManifoldDomainError is_point(M, pf2, true) + @test_throws ManifoldDomainError is_point(M, pf2; error=:error) pf3 = [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0] # contains nonpositive entries - @test_throws ManifoldDomainError is_point(M, pf3, true) + @test_throws ManifoldDomainError is_point(M, pf3; error=:error) Xf1 = [0.0 1.0 -1.0; 0.0 0.0 0.0; 0.0 0.0 0.0] # not symmetric - @test_throws ManifoldDomainError is_vector(M, p, Xf1, true) + @test_throws ManifoldDomainError is_vector(M, p, Xf1; error=:error) Xf2 = [0.0 -1.0 0.0; -1.0 0.0 0.0; 0.0 0.0 0.0] # nonzero sums - @test_throws ManifoldDomainError is_vector(M, p, Xf2, true) + @test_throws ManifoldDomainError is_vector(M, p, Xf2; error=:error) @test representation_size(M) == (3, 3) @test !is_flat(M) pE = similar(p) @@ -68,4 +68,9 @@ include("../utils.jl") ) end end + @testset "field parameter" begin + M = MultinomialSymmetric(3; parameter=:field) + @test repr(M) == "MultinomialSymmetric(3; parameter=:field)" + @test get_embedding(M) === MultinomialMatrices(3, 3; parameter=:field) + end end diff --git a/test/manifolds/oblique.jl b/test/manifolds/oblique.jl index cfd46da61b..4f51907aea 100644 --- a/test/manifolds/oblique.jl +++ b/test/manifolds/oblique.jl @@ -6,29 +6,30 @@ include("../utils.jl") @test Sphere(2)^3 === Oblique(3, 3) @test Sphere(2)^(3,) === PowerManifold(Sphere(2), 3) @test ^(Sphere(2), 2) === Oblique(3, 2) - @test typeof(^(Sphere(2), 2)) == Oblique{3,2,ℝ,2} - @test repr(M) == "Oblique(3,2; field = ℝ)" + @test typeof(^(Sphere(2), 2)) == + Oblique{TypeParameter{Tuple{3,2}},ℝ,TypeParameter{Tuple{2}}} + @test repr(M) == "Oblique(3, 2; field=ℝ)" @test representation_size(M) == (3, 2) @test manifold_dimension(M) == 4 @test !is_flat(M) p = [2, 0, 0] p2 = [p p] @test !is_point(M, p) - @test_throws DomainError is_point(M, p, true) + @test_throws DomainError is_point(M, p; error=:error) @test !is_point(M, p2) - @test_throws CompositeManifoldError is_point(M, p2, true) + @test_throws CompositeManifoldError is_point(M, p2; error=:error) @test !is_vector(M, p2, 0.0) @test_throws CompositeManifoldError{ComponentManifoldError{Int64,DomainError}} is_vector( M, p2, - [0.0, 0.0, 0.0], - true, + [0.0, 0.0, 0.0]; + error=:error, ) @test !is_vector(M, p2, [0.0, 0.0, 0.0]) - @test_throws DomainError is_vector(M, p, [0.0, 0.0, 0.0], true) # p wrong + @test_throws DomainError is_vector(M, p, [0.0, 0.0, 0.0]; error=:error) # p wrong @test injectivity_radius(M) ≈ π x = [1.0 0.0 0.0; 1.0 0.0 0.0]' - @test_throws DomainError is_vector(M, x, [0.0, 0.0, 0.0], true) # tangent wrong + @test_throws DomainError is_vector(M, x, [0.0, 0.0, 0.0]; error=:error) # tangent wrong y = [1.0 0.0 0.0; 1/sqrt(2) 1/sqrt(2) 0.0]' z = [1/sqrt(2) 1/sqrt(2) 0.0; 1.0 0.0 0.0]' basis_types = (DefaultOrthonormalBasis(),) @@ -46,4 +47,11 @@ include("../utils.jl") test_inplace=true, ) end + @testset "field parameter" begin + @test typeof(^(Sphere(2; parameter=:field), 2)) == + Oblique{Tuple{Int,Int},ℝ,Tuple{Int}} + + M = Oblique(3, 2; parameter=:field) + @test repr(M) == "Oblique(3, 2; field=ℝ, parameter=:field)" + end end diff --git a/test/manifolds/positive_numbers.jl b/test/manifolds/positive_numbers.jl index 3aec6d5868..c914ba0940 100644 --- a/test/manifolds/positive_numbers.jl +++ b/test/manifolds/positive_numbers.jl @@ -11,7 +11,7 @@ include("../utils.jl") @test manifold_dimension(M) == 1 @test !is_flat(M) @test !is_point(M, -1.0) - @test_throws DomainError is_point(M, -1.0, true) + @test_throws DomainError is_point(M, -1.0; error=:error) @test is_vector(M, 1.0, 0.0) @test vector_transport_to(M, 1.0, 3.0, 2.0, ParallelTransport()) == 6.0 @test retract(M, 1.0, 1.0) == exp(M, 1.0, 1.0) @@ -102,4 +102,13 @@ include("../utils.jl") rand!(M, X; vector_at=p) @test is_vector(M, p, X) end + + @testset "field parameter" begin + M1 = PositiveVectors(3; parameter=:field) + @test repr(M1) == "PositiveVectors(3; parameter=:field)" + M2 = PositiveMatrices(3, 4; parameter=:field) + @test repr(M2) == "PositiveMatrices(3, 4; parameter=:field)" + M3 = PositiveArrays(3, 4, 5; parameter=:field) + @test repr(M3) == "PositiveArrays(3, 4, 5; parameter=:field)" + end end diff --git a/test/manifolds/power_manifold.jl b/test/manifolds/power_manifold.jl index dbce456621..bace6ba9b7 100644 --- a/test/manifolds/power_manifold.jl +++ b/test/manifolds/power_manifold.jl @@ -29,7 +29,7 @@ end @test power_dimensions(Ms2n) == (7,) @test manifold_dimension(Ms2n) == 70 - Mr = Manifolds.Rotations(3) + Mr = Rotations(3; parameter=:type) Mr1 = PowerManifold(Mr, 5) Mrn1 = PowerManifold(Mr, Manifolds.NestedPowerRepresentation(), 5) @test PowerManifold(PowerManifold(Mr, 2), 3) == PowerManifold(Mr, 2, 3) @@ -55,11 +55,8 @@ end @test Ms^(5,) === Ms1 @test Mr^(5, 7) === Mr2 - types_s1 = [Array{Float64,2}, HybridArray{Tuple{3,Dynamic()},Float64,2}] types_s2 = [Array{Float64,3}, HybridArray{Tuple{3,Dynamic(),Dynamic()},Float64,3}] - types_r1 = [Array{Float64,3}, HybridArray{Tuple{3,3,Dynamic()},Float64,3}] - types_rn1 = [Vector{Matrix{Float64}}] TEST_STATIC_SIZED && push!(types_rn1, Vector{MMatrix{3,3,Float64,9}}) @@ -77,13 +74,11 @@ end sphere_tv_dist = Manifolds.normal_tvector_distribution(Ms, (@MVector [1.0, 0.0, 0.0]), 1.0) power_s1_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Ms1), - rand(power_s1_pt_dist), + TangentSpace(Ms1, rand(power_s1_pt_dist)), sphere_tv_dist, ) power_s2_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Ms2), - rand(power_s2_pt_dist), + TangentSpace(Ms2, rand(power_s2_pt_dist)), sphere_tv_dist, ) @@ -105,23 +100,19 @@ end ) rotations_tv_dist = Manifolds.normal_tvector_distribution(Mr, MMatrix(id_rot), 1.0) power_r1_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Mr1), - rand(power_r1_pt_dist), + TangentSpace(Mr1, rand(power_r1_pt_dist)), rotations_tv_dist, ) power_rn1_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Mrn1), - rand(power_rn1_pt_dist), + TangentSpace(Mrn1, rand(power_rn1_pt_dist)), rotations_tv_dist, ) power_r2_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Mr2), - rand(power_r2_pt_dist), + TangentSpace(Mr2, rand(power_r2_pt_dist)), rotations_tv_dist, ) power_rn2_tv_dist = Manifolds.PowerFVectorDistribution( - TangentBundleFibers(Mrn2), - rand(power_rn2_pt_dist), + TangentSpace(Mrn2, rand(power_rn2_pt_dist)), rotations_tv_dist, ) @@ -163,8 +154,8 @@ end M = PowerManifold(Sphere(2), NestedPowerRepresentation(), 2) p = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]] X = [[0.0, 0.0, 0.0], [0.0, 1.0, 0.0]] - @test_throws ComponentManifoldError is_point(M, X, true) - @test_throws ComponentManifoldError is_vector(M, p, X, true) + @test_throws ComponentManifoldError is_point(M, X; error=:error) + @test_throws ComponentManifoldError is_vector(M, p, X; error=:error) end @testset "power vector transport" begin @@ -179,45 +170,6 @@ end trim(s::String) = s[1:min(length(s), 20)] basis_types = (DefaultOrthonormalBasis(), ProjectedOrthonormalBasis(:svd)) - for T in types_s1 - @testset "Type $(trim(string(T)))..." begin - pts1 = [convert(T, rand(power_s1_pt_dist)) for _ in 1:3] - @test injectivity_radius(Ms1, pts1[1]) == π - basis_diag = get_basis( - Ms1, - pts1[1], - DiagonalizingOrthonormalBasis(log(Ms1, pts1[1], pts1[2])), - ) - basis_arb = get_basis(Ms1, pts1[1], DefaultOrthonormalBasis()) - test_manifold( - Ms1, - pts1; - test_musical_isomorphisms=true, - test_injectivity_radius=false, - test_default_vector_transport=true, - test_project_point=true, - test_project_tangent=true, - vector_transport_methods=[ - ParallelTransport(), - SchildsLadderTransport(), - PoleLadderTransport(), - ], - test_vee_hat=true, - retraction_methods=retraction_methods, - inverse_retraction_methods=inverse_retraction_methods, - point_distributions=[power_s1_pt_dist], - tvector_distributions=[power_s1_tv_dist], - basis_types_to_from=(basis_diag, basis_arb, basis_types...), - rand_tvector_atol_multiplier=600.0, - retraction_atol_multiplier=12.0, - is_tangent_atol_multiplier=500.0, - exp_log_atol_multiplier=20 * prod(power_dimensions(Ms1)), - test_inplace=true, - test_rand_point=true, - test_rand_tvector=true, - ) - end - end for T in types_s2 @testset "Type $(trim(string(T)))..." begin pts2 = [convert(T, rand(power_s2_pt_dist)) for _ in 1:3] @@ -240,29 +192,6 @@ end end end - for T in types_r1 - @testset "Type $(trim(string(T)))..." begin - pts1 = [convert(T, rand(power_r1_pt_dist)) for _ in 1:3] - test_manifold( - Mr1, - pts1; - test_injectivity_radius=false, - test_musical_isomorphisms=true, - test_vee_hat=true, - retraction_methods=retraction_methods, - inverse_retraction_methods=inverse_retraction_methods, - point_distributions=[power_r1_pt_dist], - tvector_distributions=[power_r1_tv_dist], - basis_types_to_from=basis_types, - rand_tvector_atol_multiplier=8.0, - retraction_atol_multiplier=12, - is_tangent_atol_multiplier=12.0, - exp_log_atol_multiplier=2e2 * prod(power_dimensions(Mr2)), - test_inplace=true, - ) - end - end - for T in types_rn1 @testset "Type $(trim(string(T)))..." begin pts1 = [convert(T, rand(power_rn1_pt_dist)) for _ in 1:3] @@ -334,6 +263,7 @@ end pts_t = [[0.0, 1.0, 2.0], [1.0, 1.0, 2.4], [0.0, 2.0, 1.0]] MT = PowerManifold(Circle(), 3) @test representation_size(MT) == (3,) + @test pts_t[2][MT, 3] == 2.4 test_manifold( MT, pts_t; @@ -350,6 +280,9 @@ end test_rand_point=true, test_rand_tvector=true, ) + + ph = HybridVector{3}(pts_t[1]) + @test Manifolds._read(MT, (), ph, 3) == 2.0 end @testset "Basis printing" begin @@ -370,26 +303,6 @@ end """ end - @testset "Power manifold of Circle" begin - pts_t = [[0.0, 1.0, 2.0], [1.0, 1.0, 2.4], [0.0, 2.0, 1.0]] - MT = PowerManifold(Circle(), 3) - @test representation_size(MT) == (3,) - @test pts_t[1][MT, 2] == 1.0 - @test HybridVector{3}(pts_t[1])[MT, 2] == 1.0 - test_manifold( - MT, - pts_t; - test_injectivity_radius=false, - test_musical_isomorphisms=true, - retraction_methods=retraction_methods, - inverse_retraction_methods=inverse_retraction_methods, - rand_tvector_atol_multiplier=5.0, - retraction_atol_multiplier=12, - is_tangent_atol_multiplier=12.0, - test_inplace=true, - ) - end - @testset "Atlas & Induced Basis" begin M = PowerManifold(Euclidean(2), NestedPowerRepresentation(), 2) p = [zeros(2), ones(2)] @@ -464,39 +377,29 @@ end SE2 = SpecialEuclidean(2) PSE2 = PowerManifold(SE2, NestedReplacingPowerRepresentation(), 2) - for prod_type in [ProductRepr, ArrayPartition] - pse = prod_type( - SA[1.0, 2.0], - SA[ - 0.5403023058681398 -0.8414709848078965 - 0.8414709848078965 0.5403023058681398 - ], - ) - p2 = [pse, pse] - if prod_type === ProductRepr - @test allocate(PSE2, p2) isa - Vector{ProductRepr{Tuple{SVector{2,Float64},SMatrix{2,2,Float64,4}}}} - else - @test allocate(PSE2, p2) isa Vector{ - ArrayPartition{ - Float64, - Tuple{SVector{2,Float64},SMatrix{2,2,Float64,4}}, - }, - } - end - - pse_ap = ArrayPartition( - SA[1.0, 2.0], - SA[ - 0.5403023058681398 -0.8414709848078965 - 0.8414709848078965 0.5403023058681398 - ], - ) - p2_ap = [pse_ap, pse_ap] - @test allocate(PSE2, p2_ap) isa Vector{ - ArrayPartition{Float64,Tuple{SVector{2,Float64},SMatrix{2,2,Float64,4}}}, - } - end + pse = ArrayPartition( + SA[1.0, 2.0], + SA[ + 0.5403023058681398 -0.8414709848078965 + 0.8414709848078965 0.5403023058681398 + ], + ) + p2 = [pse, pse] + @test allocate(PSE2, p2) isa Vector{ + ArrayPartition{Float64,Tuple{SVector{2,Float64},SMatrix{2,2,Float64,4}}}, + } + + pse_ap = ArrayPartition( + SA[1.0, 2.0], + SA[ + 0.5403023058681398 -0.8414709848078965 + 0.8414709848078965 0.5403023058681398 + ], + ) + p2_ap = [pse_ap, pse_ap] + @test allocate(PSE2, p2_ap) isa Vector{ + ArrayPartition{Float64,Tuple{SVector{2,Float64},SMatrix{2,2,Float64,4}}}, + } end @testset "Manifold volume" begin @@ -505,4 +408,10 @@ end X = repeat([0.0, 1.0, 0.0], 1, 5) @test volume_density(Ms1, p, X) ≈ volume_density(Ms, p[:, 1], X[:, 1])^5 end + + @testset "Static type parameter" begin + Ms1s = PowerManifold(Ms, 5; parameter=:type) + @test sprint(show, "text/plain", Ms1s) == + "PowerManifold(Sphere(2, ℝ), 5, parameter=:type)" + end end diff --git a/test/manifolds/probability_simplex.jl b/test/manifolds/probability_simplex.jl index e34c24127a..d2b6a5445d 100644 --- a/test/manifolds/probability_simplex.jl +++ b/test/manifolds/probability_simplex.jl @@ -11,16 +11,16 @@ include("../utils.jl") Y = [-0.1, 0.05, 0.05] @test repr(M) == "ProbabilitySimplex(2; boundary=:open)" @test is_point(M, p) - @test_throws DomainError is_point(M, p .+ 1, true) - @test_throws ManifoldDomainError is_point(M, [0], true) - @test_throws DomainError is_point(M, -ones(3), true) + @test_throws DomainError is_point(M, p .+ 1; error=:error) + @test_throws ManifoldDomainError is_point(M, [0]; error=:error) + @test_throws DomainError is_point(M, -ones(3); error=:error) @test manifold_dimension(M) == 2 @test !is_flat(M) @test is_vector(M, p, X) @test is_vector(M, p, Y) - @test_throws ManifoldDomainError is_vector(M, p .+ 1, X, true) - @test_throws ManifoldDomainError is_vector(M, p, zeros(4), true) - @test_throws DomainError is_vector(M, p, Y .+ 1, true) + @test_throws DomainError is_vector(M, p .+ 1, X; error=:error) + @test_throws ManifoldDomainError is_vector(M, p, zeros(4); error=:error) + @test_throws DomainError is_vector(M, p, Y .+ 1; error=:error) @test injectivity_radius(M, p) == injectivity_radius(M, p, ExponentialRetraction()) @test injectivity_radius(M, p, SoftmaxRetraction()) == injectivity_radius(M, p) @@ -66,6 +66,8 @@ include("../utils.jl") test_rand_tvector=true, rand_tvector_atol_multiplier=20.0, ) + X = similar(pts[1]) + @test exp!(M_euc, X, pts[1], [0.0, 0.1, -0.1]) ≈ [0.5, 0.4, 0.1] test_manifold( M_euc, pts, @@ -129,7 +131,7 @@ include("../utils.jl") X = [0, 1, -1] Y = [0, 2, -2] @test is_point(Mb, p) - @test_throws DomainError is_point(Mb, p .- 1, true) + @test_throws DomainError is_point(Mb, p .- 1; error=:error) @test inner(Mb, p, X, Y) == 8 @test_throws ArgumentError ProbabilitySimplex(2; boundary=:tomato) @@ -165,4 +167,10 @@ include("../utils.jl") @test manifold_volume(M_euc) ≈ sqrt(3) / 2 @test volume_density(M_euc, p, Y) ≈ 1.0 end + + @testset "field parameter" begin + M = ProbabilitySimplex(2; parameter=:field) + @test repr(M) == "ProbabilitySimplex(2; boundary=:open, parameter=:field)" + @test get_embedding(M) === Euclidean(3; parameter=:field) + end end diff --git a/test/manifolds/product_manifold.jl b/test/manifolds/product_manifold.jl index de0e96ebf7..f9233a984e 100644 --- a/test/manifolds/product_manifold.jl +++ b/test/manifolds/product_manifold.jl @@ -23,21 +23,21 @@ using RecursiveArrayTools: ArrayPartition @test injectivity_radius(Mse, ExponentialRetraction()) ≈ π @test injectivity_radius( Mse, - ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0]), + ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0]), ProductRetraction(ExponentialRetraction(), ExponentialRetraction()), ) ≈ π @test injectivity_radius( Mse, - ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0]), + ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0]), ExponentialRetraction(), ) ≈ π @test is_default_metric(Mse, ProductMetric()) @test Manifolds.number_of_components(Mse) == 2 # test that arrays are not points - @test_throws DomainError is_point(Mse, [1, 2], true) + @test_throws DomainError is_point(Mse, [1, 2]; error=:error) @test check_point(Mse, [1, 2]) isa DomainError - @test_throws DomainError is_vector(Mse, 1, [1, 2], true; check_base_point=false) + @test_throws DomainError is_vector(Mse, 1, [1, 2]; error=:error, check_base_point=false) @test check_vector(Mse, 1, [1, 2]; check_base_point=false) isa DomainError #default fallbacks for check_size, Product not working with Arrays @test Manifolds.check_size(Mse, zeros(2)) isa DomainError @@ -47,239 +47,31 @@ using RecursiveArrayTools: ArrayPartition TEST_STATIC_SIZED && push!(types, MVector{5,Float64}) retraction_methods = [ - Manifolds.ProductRetraction( - ManifoldsBase.ExponentialRetraction(), - ManifoldsBase.ExponentialRetraction(), - ), + ProductRetraction(ExponentialRetraction(), ExponentialRetraction()), + ExponentialRetraction(), ] inverse_retraction_methods = [ - Manifolds.InverseProductRetraction( - ManifoldsBase.LogarithmicInverseRetraction(), - ManifoldsBase.LogarithmicInverseRetraction(), + InverseProductRetraction( + LogarithmicInverseRetraction(), + LogarithmicInverseRetraction(), ), + LogarithmicInverseRetraction(), ] - @testset "get_component, set_component!, getindex and setindex!" begin - p1 = ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0]) - @test get_component(Mse, p1, 1) == p1.parts[1] - @test get_component(Mse, p1, Val(1)) == p1.parts[1] - @test p1[Mse, 1] == p1.parts[1] - @test p1[Mse, Val(1)] == p1.parts[1] - @test p1[Mse, 1] isa Vector - @test p1[Mse, Val(1)] isa Vector - p2 = [10.0, 12.0] - set_component!(Mse, p1, p2, 2) - @test get_component(Mse, p1, 2) == p2 - p1[Mse, 2] = 2 * p2 - @test p1[Mse, 2] == 2 * p2 - p3 = [11.0, 15.0] - set_component!(Mse, p1, p3, Val(2)) - @test get_component(Mse, p1, Val(2)) == p3 - p1[Mse, Val(2)] = 2 * p3 - @test p1[Mse, Val(2)] == 2 * p3 - - p1ap = ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0]) - @test get_component(Mse, p1ap, 1) == p1ap.x[1] - @test get_component(Mse, p1ap, Val(1)) == p1ap.x[1] - @test p1ap[Mse, 1] == p1ap.x[1] - @test p1ap[Mse, Val(1)] == p1ap.x[1] - @test p1ap[Mse, 1] isa Vector - @test p1ap[Mse, Val(1)] isa Vector - set_component!(Mse, p1ap, p2, 2) - @test get_component(Mse, p1ap, 2) == p2 - p1ap[Mse, 2] = 2 * p2 - @test p1ap[Mse, 2] == 2 * p2 - p3 = [11.0, 15.0] - set_component!(Mse, p1ap, p3, Val(2)) - @test get_component(Mse, p1ap, Val(2)) == p3 - p1ap[Mse, Val(2)] = 2 * p3 - @test p1ap[Mse, Val(2)] == 2 * p3 - - p1c = copy(p1) - p1c.parts[1][1] = -123.0 - @test p1c.parts[1][1] == -123.0 - @test p1.parts[1][1] == 0.0 - copyto!(p1c, p1) - @test p1c.parts[1][1] == 0.0 - - p1c.parts[1][1] = -123.0 - copyto!(p1ap, p1c) - @test p1ap.x[1][1] == -123.0 - end - - @testset "some ArrayPartition functions" begin - p = ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0]) - q = allocate(p) - @test q.x[1] isa Vector - p = ArrayPartition([[0.0, 1.0, 0.0]], [0.0, 0.0]) - q = allocate(p, Int) - @test q.x[1] isa Vector{Vector{Int}} - end - - @testset "allocate on PowerManifold of ProductManifold" begin - p = ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0]) - q = allocate([p]) - @test q[1] isa ArrayPartition - @test q[1].x[1] isa Vector - - p = ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0]) - q = allocate([p]) - @test q[1] isa ProductRepr - @test q[1].parts[1] isa Vector - end - - @testset "copyto!" begin - p = ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0]) - X = ProductRepr([1.0, 0.0, 0.0], [1.0, 0.0]) - q = allocate(p) - copyto!(Mse, q, p) - @test p.parts == q.parts - Y = allocate(X) - copyto!(Mse, Y, p, X) - @test Y.parts == X.parts - end - - @testset "Broadcasting" begin - p1 = ProductRepr([0.0, 1.0, 0.0], [0.0, 1.0]) - p2 = ProductRepr([3.0, 4.0, 5.0], [2.0, 5.0]) - br_result = p1 .+ 2.0 .* p2 - @test br_result isa ProductRepr - @test br_result.parts[1] ≈ [6.0, 9.0, 10.0] - @test br_result.parts[2] ≈ [4.0, 11.0] - - br_result .= 2.0 .* p1 .+ p2 - @test br_result.parts[1] ≈ [3.0, 6.0, 5.0] - @test br_result.parts[2] ≈ [2.0, 7.0] - - br_result .= p1 - @test br_result.parts[1] ≈ [0.0, 1.0, 0.0] - @test br_result.parts[2] ≈ [0.0, 1.0] - - @test axes(p1) == (Base.OneTo(2),) - - # errors - p3 = ProductRepr([3.0, 4.0, 5.0], [2.0, 5.0], [3.0, 2.0]) - @test_throws DimensionMismatch p1 .+ p3 - @test_throws DimensionMismatch p1 .= p3 - end - - @testset "CompositeManifoldError" begin - Mpr = Sphere(2) × Sphere(2) - p1 = [1.0, 0.0, 0.0] - p2 = [0.0, 1.0, 0.0] - X1 = [0.0, 1.0, 0.2] - X2 = [1.0, 0.0, 0.2] - p = ProductRepr(p1, p2) - X = ProductRepr(X1, X2) - pf = ProductRepr(p1, X1) - Xf = ProductRepr(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) - end - @testset "arithmetic" begin Mee = ProductManifold(Euclidean(3), Euclidean(2)) - p1 = ProductRepr([0.0, 1.0, 0.0], [0.0, 1.0]) - p2 = ProductRepr([1.0, 2.0, 0.0], [2.0, 3.0]) - - @test isapprox(Mee, p1 + p2, ProductRepr([1.0, 3.0, 0.0], [2.0, 4.0])) - @test isapprox(Mee, p1 - p2, ProductRepr([-1.0, -1.0, 0.0], [-2.0, -2.0])) - @test isapprox(Mee, -p1, ProductRepr([0.0, -1.0, 0.0], [0.0, -1.0])) - @test isapprox(Mee, p1 * 2, ProductRepr([0.0, 2.0, 0.0], [0.0, 2.0])) - @test isapprox(Mee, 2 * p1, ProductRepr([0.0, 2.0, 0.0], [0.0, 2.0])) - @test isapprox(Mee, p1 / 2, ProductRepr([0.0, 0.5, 0.0], [0.0, 0.5])) - end - - @testset "Show methods" begin - Mse2 = ProductManifold(M1, M1, M2, M2) - @test sprint(show, Mse2) == "ProductManifold($(M1), $(M1), $(M2), $(M2))" - withenv("LINES" => 10, "COLUMNS" => 100) do - @test sprint(show, "text/plain", ProductManifold(M1)) == - "ProductManifold with 1 submanifold:\n $(M1)" - @test sprint(show, "text/plain", Mse2) == - "ProductManifold with 4 submanifolds:\n $(M1)\n $(M1)\n $(M2)\n $(M2)" - return nothing - end - withenv("LINES" => 7, "COLUMNS" => 100) do - @test sprint(show, "text/plain", Mse2) == - "ProductManifold with 4 submanifolds:\n $(M1)\n ⋮\n $(M2)" - return nothing - end - - @test sprint(show, "text/plain", ProductManifold(Mse, Mse)) == """ - ProductManifold with 2 submanifolds: - ProductManifold(Sphere(2, ℝ), Euclidean(2; field = ℝ)) - ProductManifold(Sphere(2, ℝ), Euclidean(2; field = ℝ))""" - - p = Manifolds.ProductRepr(Float64[1, 0, 0]) - @test sprint(show, "text/plain", p) == """ - ProductRepr with 1 submanifold component: - Component 1 = - 3-element $(sprint(show, Vector{Float64})): - 1.0 - 0.0 - 0.0""" - - p = Manifolds.ProductRepr( - Float64[1, 0, 0], - Float64[0, 1, 0], - Float64[1, 2], - Float64[3, 4], - ) - @test sprint(show, "text/plain", p) == """ - ProductRepr with 4 submanifold components: - Component 1 = - 3-element $(sprint(show, Vector{Float64})): - 1.0 - 0.0 - 0.0 - Component 2 = - 3-element $(sprint(show, Vector{Float64})): - 0.0 - 1.0 - 0.0 - Component 3 = - 2-element $(sprint(show, Vector{Float64})): - 1.0 - 2.0 - Component 4 = - 2-element $(sprint(show, Vector{Float64})): - 3.0 - 4.0""" - - p = Manifolds.ProductRepr( - Float64[1, 0, 0], - Float64[0, 1, 0], - Float64[1, 2], - Float64[3, 4], - Float64[5, 6], - ) - @test sprint(show, "text/plain", p) == """ - ProductRepr with 5 submanifold components: - Component 1 = - 3-element $(sprint(show, Vector{Float64})): - 1.0 - 0.0 - 0.0 - Component 2 = - 3-element $(sprint(show, Vector{Float64})): - 0.0 - 1.0 - 0.0 - ⋮ - Component 4 = - 2-element $(sprint(show, Vector{Float64})): - 3.0 - 4.0 - Component 5 = - 2-element $(sprint(show, Vector{Float64})): - 5.0 - 6.0""" + p1 = ArrayPartition([0.0, 1.0, 0.0], [0.0, 1.0]) + p2 = ArrayPartition([1.0, 2.0, 0.0], [2.0, 3.0]) + + @test isapprox(Mee, p1 + p2, ArrayPartition([1.0, 3.0, 0.0], [2.0, 4.0])) + @test isapprox(Mee, p1 - p2, ArrayPartition([-1.0, -1.0, 0.0], [-2.0, -2.0])) + @test isapprox(Mee, -p1, ArrayPartition([0.0, -1.0, 0.0], [0.0, -1.0])) + @test isapprox(Mee, p1 * 2, ArrayPartition([0.0, 2.0, 0.0], [0.0, 2.0])) + @test isapprox(Mee, 2 * p1, ArrayPartition([0.0, 2.0, 0.0], [0.0, 2.0])) + @test isapprox(Mee, p1 / 2, ArrayPartition([0.0, 0.5, 0.0], [0.0, 0.5])) end - M3 = Manifolds.Rotations(2) + M3 = Rotations(2) Mser = ProductManifold(M1, M2, M3) @test submanifold(Mser, 2) == M2 @@ -291,231 +83,99 @@ using RecursiveArrayTools: ArrayPartition pts_r2 = [[0.0, 0.0], [1.0, 0.0], [0.0, 0.1]] angles = (0.0, π / 2, 2π / 3) pts_rot = [[cos(ϕ) sin(ϕ); -sin(ϕ) cos(ϕ)] for ϕ in angles] - for prod_type in [ProductRepr, ArrayPartition] - pts = [prod_type(p[1], p[2], p[3]) for p in zip(pts_sphere, pts_r2, pts_rot)] + pts = [ArrayPartition(p[1], p[2], p[3]) for p in zip(pts_sphere, pts_r2, pts_rot)] + test_manifold( + Mser, + pts, + test_injectivity_radius=false, + is_tangent_atol_multiplier=1, + exp_log_atol_multiplier=1, + test_inplace=true, + test_rand_point=true, + test_rand_tvector=true, + ) + + @testset "manifold tests (static size)" begin + Ts = SizedVector{3,Float64} + Tr2 = SizedVector{2,Float64} + pts_sphere = [ + convert(Ts, [1.0, 0.0, 0.0]), + convert(Ts, [0.0, 1.0, 0.0]), + convert(Ts, [0.0, 0.0, 1.0]), + ] + pts_r2 = + [convert(Tr2, [0.0, 0.0]), convert(Tr2, [1.0, 0.0]), convert(Tr2, [0.0, 0.1])] + + pts = [ArrayPartition(p[1], p[2]) for p in zip(pts_sphere, pts_r2)] + basis_types = ( + DefaultOrthonormalBasis(), + ProjectedOrthonormalBasis(:svd), + get_basis(Mse, pts[1], DefaultOrthonormalBasis()), + DiagonalizingOrthonormalBasis( + ArrayPartition(SizedVector{3}([0.0, 1.0, 0.0]), SizedVector{2}([1.0, 0.0])), + ), + ) + distr_M1 = Manifolds.uniform_distribution(M1, pts_sphere[1]) + distr_M2 = Manifolds.projected_distribution( + M2, + Distributions.MvNormal(zero(pts_r2[1]), 1.0 * I), + ) + distr_tv_M1 = Manifolds.normal_tvector_distribution(M1, pts_sphere[1], 1.0) + distr_tv_M2 = Manifolds.normal_tvector_distribution(M2, pts_r2[1], 1.0) + @test injectivity_radius(Mse, pts[1]) ≈ π + @test injectivity_radius(Mse) ≈ π + @test injectivity_radius(Mse, pts[1], ExponentialRetraction()) ≈ π + @test injectivity_radius(Mse, ExponentialRetraction()) ≈ π + + @test ManifoldsBase.allocate_coordinates( + Mse, + pts[1], + Float64, + number_of_coordinates(Mse, DefaultOrthogonalBasis()), + ) isa Vector{Float64} + + Y = allocate(pts[1]) + inverse_retract!(Mse, Y, pts[1], pts[2], default_inverse_retraction_method(Mse)) + @test isapprox( + Mse, + pts[1], + Y, + inverse_retract(Mse, pts[1], pts[2], default_inverse_retraction_method(Mse)), + ) + test_manifold( - Mser, - pts, - test_injectivity_radius=false, - is_tangent_atol_multiplier=1, - exp_log_atol_multiplier=1, - test_inplace=true, + Mse, + pts; + point_distributions=[Manifolds.ProductPointDistribution(distr_M1, distr_M2)], + tvector_distributions=[ + Manifolds.ProductFVectorDistribution(distr_tv_M1, distr_tv_M2), + ], + test_injectivity_radius=true, + test_musical_isomorphisms=true, + musical_isomorphism_bases=[DefaultOrthonormalBasis()], + test_tangent_vector_broadcasting=true, + test_project_tangent=true, + test_project_point=true, + test_mutating_rand=true, + retraction_methods=retraction_methods, + inverse_retraction_methods=inverse_retraction_methods, + test_riesz_representer=true, + test_default_vector_transport=true, test_rand_point=true, test_rand_tvector=true, + vector_transport_methods=[ + ProductVectorTransport(ParallelTransport(), ParallelTransport()), + ProductVectorTransport(SchildsLadderTransport(), SchildsLadderTransport()), + ProductVectorTransport(PoleLadderTransport(), PoleLadderTransport()), + ], + basis_types_vecs=(basis_types[1], basis_types[3], basis_types[4]), + basis_types_to_from=basis_types, + is_tangent_atol_multiplier=1, + exp_log_atol_multiplier=1, ) + @test number_eltype(pts[1]) === Float64 - @testset "product vector transport" begin - p = prod_type([1.0, 0.0, 0.0], [0.0, 0.0]) - q = prod_type([0.0, 1.0, 0.0], [2.0, 0.0]) - X = log(Mse, p, q) - m = ProductVectorTransport(ParallelTransport(), ParallelTransport()) - Y = vector_transport_to(Mse, p, X, q, m) - Z = -log(Mse, q, p) - @test isapprox(Mse, q, Y, Z) - end - - @testset "Implicit product vector transport" begin - p = prod_type([1.0, 0.0, 0.0], [0.0, 0.0]) - q = prod_type([0.0, 1.0, 0.0], [2.0, 0.0]) - X = log(Mse, p, q) - for m in [ParallelTransport(), SchildsLadderTransport(), PoleLadderTransport()] - Y = vector_transport_to(Mse, p, X, q, m) - Z1 = vector_transport_to( - Mse.manifolds[1], - submanifold_component.([p, X, q], Ref(1))..., - m, - ) - Z2 = vector_transport_to( - Mse.manifolds[2], - submanifold_component.([p, X, q], Ref(2))..., - m, - ) - Z = prod_type(Z1, Z2) - @test isapprox(Mse, q, Y, Z) - Y2 = allocate(Mse, Y) - vector_transport_to!(Mse, Y2, p, X, q, m) - @test isapprox(Mse, q, Y2, Z) - end - for m in [ParallelTransport(), SchildsLadderTransport(), PoleLadderTransport()] - Y = vector_transport_direction(Mse, p, X, X, m) - Z1 = vector_transport_direction( - Mse.manifolds[1], - submanifold_component.([p, X, X], Ref(1))..., - m, - ) - Z2 = vector_transport_direction( - Mse.manifolds[2], - submanifold_component.([p, X, X], Ref(2))..., - m, - ) - Z = prod_type(Z1, Z2) - @test isapprox(Mse, q, Y, Z) - end - end - @testset "Parallel transport" begin - p = prod_type([1.0, 0.0, 0.0], [0.0, 0.0]) - q = prod_type([0.0, 1.0, 0.0], [2.0, 0.0]) - X = log(Mse, p, q) - # to - Y = parallel_transport_to(Mse, p, X, q) - Z1 = parallel_transport_to( - Mse.manifolds[1], - submanifold_component.([p, X, q], Ref(1))..., - ) - Z2 = parallel_transport_to( - Mse.manifolds[2], - submanifold_component.([p, X, q], Ref(2))..., - ) - Z = prod_type(Z1, Z2) - @test isapprox(Mse, q, Y, Z) - Ym = allocate(Y) - parallel_transport_to!(Mse, Ym, p, X, q) - @test isapprox(Mse, q, Y, Z) - - # direction - Y = parallel_transport_direction(Mse, p, X, X) - Z1 = parallel_transport_direction( - Mse.manifolds[1], - submanifold_component.([p, X, X], Ref(1))..., - ) - Z2 = parallel_transport_direction( - Mse.manifolds[2], - submanifold_component.([p, X, X], Ref(2))..., - ) - Z = prod_type(Z1, Z2) - @test isapprox(Mse, q, Y, Z) - Ym = allocate(Y) - parallel_transport_direction!(Mse, Ym, p, X, X) - @test isapprox(Mse, q, Ym, Z) - end - end - - @testset "ProductRepr" begin - @test (@inferred convert( - ProductRepr{Tuple{T,Float64,T} where T}, - ProductRepr(9, 10, 11), - )) == ProductRepr(9, 10.0, 11) - p = ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0]) - @test (@inferred convert(ProductRepr, p)) === p - - @test p == ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0]) - @test submanifold_component(Mse, p, 1) === p.parts[1] - @test submanifold_component(Mse, p, Val(1)) === p.parts[1] - @test submanifold_component(p, 1) === p.parts[1] - @test submanifold_component(p, Val(1)) === p.parts[1] - @test submanifold_components(Mse, p) === p.parts - @test submanifold_components(p) === p.parts - @test allocate(p, Int, 10) isa Vector{Int} - @test length(allocate(p, Int, 10)) == 10 - end - - @testset "ArrayPartition" begin - p = ArrayPartition([1.0, 0.0, 0.0], [0.0, 0.0]) - @test submanifold_component(Mse, p, 1) === p.x[1] - @test submanifold_component(Mse, p, Val(1)) === p.x[1] - @test submanifold_component(p, 1) === p.x[1] - @test submanifold_component(p, Val(1)) === p.x[1] - @test submanifold_components(Mse, p) === p.x - @test submanifold_components(p) === p.x - end - - for TP in [ProductRepr, ArrayPartition] - @testset "TP=$TP" begin - Ts = SizedVector{3,Float64} - Tr2 = SizedVector{2,Float64} - pts_sphere = [ - convert(Ts, [1.0, 0.0, 0.0]), - convert(Ts, [0.0, 1.0, 0.0]), - convert(Ts, [0.0, 0.0, 1.0]), - ] - pts_r2 = [ - convert(Tr2, [0.0, 0.0]), - convert(Tr2, [1.0, 0.0]), - convert(Tr2, [0.0, 0.1]), - ] - - pts = [TP(p[1], p[2]) for p in zip(pts_sphere, pts_r2)] - basis_types = ( - DefaultOrthonormalBasis(), - ProjectedOrthonormalBasis(:svd), - get_basis(Mse, pts[1], DefaultOrthonormalBasis()), - DiagonalizingOrthonormalBasis( - TP(SizedVector{3}([0.0, 1.0, 0.0]), SizedVector{2}([1.0, 0.0])), - ), - ) - distr_M1 = Manifolds.uniform_distribution(M1, pts_sphere[1]) - distr_M2 = Manifolds.projected_distribution( - M2, - Distributions.MvNormal(zero(pts_r2[1]), 1.0), - ) - distr_tv_M1 = Manifolds.normal_tvector_distribution(M1, pts_sphere[1], 1.0) - distr_tv_M2 = Manifolds.normal_tvector_distribution(M2, pts_r2[1], 1.0) - @test injectivity_radius(Mse, pts[1]) ≈ π - @test injectivity_radius(Mse) ≈ π - @test injectivity_radius(Mse, pts[1], ExponentialRetraction()) ≈ π - @test injectivity_radius(Mse, ExponentialRetraction()) ≈ π - - @test ManifoldsBase.allocate_coordinates( - Mse, - pts[1], - Float64, - number_of_coordinates(Mse, DefaultOrthogonalBasis()), - ) isa Vector{Float64} - - Y = allocate(pts[1]) - inverse_retract!(Mse, Y, pts[1], pts[2], default_inverse_retraction_method(Mse)) - @test isapprox( - Mse, - pts[1], - Y, - inverse_retract( - Mse, - pts[1], - pts[2], - default_inverse_retraction_method(Mse), - ), - ) - - test_manifold( - Mse, - pts; - point_distributions=[ - Manifolds.ProductPointDistribution(distr_M1, distr_M2), - ], - tvector_distributions=[ - Manifolds.ProductFVectorDistribution(distr_tv_M1, distr_tv_M2), - ], - test_injectivity_radius=true, - test_musical_isomorphisms=true, - musical_isomorphism_bases=[DefaultOrthonormalBasis()], - test_tangent_vector_broadcasting=true, - test_project_tangent=true, - test_project_point=true, - test_mutating_rand=false, - retraction_methods=retraction_methods, - inverse_retraction_methods=inverse_retraction_methods, - test_riesz_representer=true, - test_default_vector_transport=true, - test_rand_point=true, - test_rand_tvector=true, - vector_transport_methods=[ - ProductVectorTransport(ParallelTransport(), ParallelTransport()), - ProductVectorTransport( - SchildsLadderTransport(), - SchildsLadderTransport(), - ), - ProductVectorTransport(PoleLadderTransport(), PoleLadderTransport()), - ], - basis_types_vecs=(basis_types[1], basis_types[3], basis_types[4]), - basis_types_to_from=basis_types, - is_tangent_atol_multiplier=1, - exp_log_atol_multiplier=1, - ) - @test number_eltype(pts[1]) === Float64 - - @test (@inferred ManifoldsBase._get_vector_cache_broadcast(pts[1])) === - Val(false) - end + @test (@inferred ManifoldsBase._get_vector_cache_broadcast(pts[1])) === Val(false) end @testset "vee/hat" begin @@ -524,7 +184,7 @@ using RecursiveArrayTools: ArrayPartition M = M1 × M2 e = Matrix{Float64}(I, 3, 3) - p = ProductRepr(exp(M1, e, hat(M1, e, [1.0, 2.0, 3.0])), [1.0, 2.0, 3.0]) + p = ArrayPartition(exp(M1, e, hat(M1, e, [1.0, 2.0, 3.0])), [1.0, 2.0, 3.0]) X = [0.1, 0.2, 0.3, -1.0, 2.0, -3.0] Xc = hat(M, p, X) @@ -532,61 +192,6 @@ using RecursiveArrayTools: ArrayPartition @test isapprox(X, X2) end - @testset "get_coordinates" begin - # make sure `get_coordinates` does not return an `ArrayPartition` - p1 = ProductRepr([0.0, 1.0, 0.0], [0.0, 0.0]) - X1 = ProductRepr([1.0, 0.0, -1.0], [1.0, 0.0]) - Tp1Mse = TangentSpaceAtPoint(Mse, p1) - c = get_coordinates(Tp1Mse, p1, X1, DefaultOrthonormalBasis()) - @test c isa Vector - - p1ap = ArrayPartition([0.0, 1.0, 0.0], [0.0, 0.0]) - X1ap = ArrayPartition([1.0, 0.0, -1.0], [1.0, 0.0]) - Tp1apMse = TangentSpaceAtPoint(Mse, p1ap) - cap = get_coordinates(Tp1apMse, p1ap, X1ap, DefaultOrthonormalBasis()) - @test cap isa Vector - end - - @testset "Basis printing" begin - p = ProductRepr([1.0, 0.0, 0.0], [1.0, 0.0]) - B = DefaultOrthonormalBasis() - Bc = get_basis(Mse, p, B) - Bc_components_s = sprint.(show, "text/plain", Bc.data.parts) - @test sprint(show, "text/plain", Bc) == """ - $(typeof(B)) for a product manifold - Basis for component 1: - $(Bc_components_s[1]) - Basis for component 2: - $(Bc_components_s[2]) - """ - end - - @testset "Basis-related errors" begin - a = ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0]) - B = CachedBasis(DefaultOrthonormalBasis(), ProductBasisData(([],))) - @test_throws AssertionError get_vector!( - Mse, - a, - ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0]), - [1.0, 2.0, 3.0, 4.0, 5.0], # this is one element too long, hence assertionerror - B, - ) - @test_throws MethodError get_vector!( - Mse, - a, - ProductRepr([1.0, 0.0, 0.0], [0.0, 0.0]), - [1.0, 2.0, 3.0, 4.0], - B, # empty elements yield a submanifold MethodError - ) - end - - @testset "allocation promotion" begin - M2c = Euclidean(2; field=ℂ) - Msec = ProductManifold(M1, M2c) - @test Manifolds.allocation_promotion_function(Msec, get_vector, ()) === complex - @test Manifolds.allocation_promotion_function(Mse, get_vector, ()) === identity - end - @testset "empty allocation" begin p = allocate_result(Mse, uniform_distribution) @test isa(p, ArrayPartition) @@ -601,115 +206,92 @@ using RecursiveArrayTools: ArrayPartition @test is_point(Mss, rand(uniform_distribution(Mss, p))) end - for TP in [ProductRepr, ArrayPartition] - @testset "Atlas & Induced Basis" begin - M = ProductManifold(Euclidean(2), Euclidean(2)) - p = TP(zeros(2), ones(2)) - X = TP(ones(2), 2 .* ones(2)) - A = RetractionAtlas() - a = get_parameters(M, A, p, p) - p2 = get_point(M, A, p, a) - @test all(submanifold_components(p2) .== submanifold_components(p)) - end + @testset "Atlas & Induced Basis" begin + M = ProductManifold(Euclidean(2), Euclidean(2)) + p = ArrayPartition(zeros(2), ones(2)) + X = ArrayPartition(ones(2), 2 .* ones(2)) + A = RetractionAtlas() + a = get_parameters(M, A, p, p) + p2 = get_point(M, A, p, a) + @test all(submanifold_components(p2) .== submanifold_components(p)) + end - @testset "metric conversion" begin - M = SymmetricPositiveDefinite(3) - N = ProductManifold(M, M) - e = EuclideanMetric() - p = [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1] - q = [2.0 0.0 0.0; 0.0 2.0 0.0; 0.0 0.0 1] - P = TP(p, q) - X = TP(log(M, p, q), log(M, q, p)) - Y = change_metric(N, e, P, X) - Yc = TP( - change_metric(M, e, p, log(M, p, q)), - change_metric(M, e, q, log(M, q, p)), - ) - @test norm(N, P, Y - Yc) ≈ 0 - Z = change_representer(N, e, P, X) - Zc = TP( - change_representer(M, e, p, log(M, p, q)), - change_representer(M, e, q, log(M, q, p)), - ) - @test norm(N, P, Z - Zc) ≈ 0 - end + @testset "metric conversion" begin + M = SymmetricPositiveDefinite(3) + N = ProductManifold(M, M) + e = EuclideanMetric() + p = [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1] + q = [2.0 0.0 0.0; 0.0 2.0 0.0; 0.0 0.0 1] + P = ArrayPartition(p, q) + X = ArrayPartition(log(M, p, q), log(M, q, p)) + Y = change_metric(N, e, P, X) + Yc = ArrayPartition( + change_metric(M, e, p, log(M, p, q)), + change_metric(M, e, q, log(M, q, p)), + ) + @test norm(N, P, Y - Yc) ≈ 0 + Z = change_representer(N, e, P, X) + Zc = ArrayPartition( + change_representer(M, e, p, log(M, p, q)), + change_representer(M, e, q, log(M, q, p)), + ) + @test norm(N, P, Z - Zc) ≈ 0 end @testset "default retraction, inverse retraction and VT" begin Mstb = ProductManifold(M1, TangentBundle(M1)) T_p_ap = ArrayPartition{ Float64, - Tuple{Matrix{Float64},ProductRepr{Tuple{Matrix{Float64},Matrix{Float64}}}}, - } - T_p_pr = ProductRepr{ - Tuple{Matrix{Float64},ProductRepr{Tuple{Matrix{Float64},Matrix{Float64}}}}, + Tuple{ + Matrix{Float64}, + ArrayPartition{Float64,Tuple{Matrix{Float64},Matrix{Float64}}}, + }, } @test Manifolds.default_retraction_method(Mstb) === ProductRetraction( ExponentialRetraction(), - Manifolds.VectorBundleProductRetraction(), + Manifolds.FiberBundleProductRetraction(), ) @test Manifolds.default_retraction_method(Mstb, T_p_ap) === ProductRetraction( ExponentialRetraction(), - Manifolds.VectorBundleProductRetraction(), - ) - @test Manifolds.default_retraction_method(Mstb, T_p_pr) === ProductRetraction( - ExponentialRetraction(), - Manifolds.VectorBundleProductRetraction(), + Manifolds.FiberBundleProductRetraction(), ) @test Manifolds.default_inverse_retraction_method(Mstb) === Manifolds.InverseProductRetraction( LogarithmicInverseRetraction(), - Manifolds.VectorBundleInverseProductRetraction(), + Manifolds.FiberBundleInverseProductRetraction(), ) @test Manifolds.default_inverse_retraction_method(Mstb, T_p_ap) === Manifolds.InverseProductRetraction( LogarithmicInverseRetraction(), - Manifolds.VectorBundleInverseProductRetraction(), - ) - @test Manifolds.default_inverse_retraction_method(Mstb, T_p_pr) === - Manifolds.InverseProductRetraction( - LogarithmicInverseRetraction(), - Manifolds.VectorBundleInverseProductRetraction(), + Manifolds.FiberBundleInverseProductRetraction(), ) @test Manifolds.default_vector_transport_method(Mstb) === ProductVectorTransport( ParallelTransport(), - Manifolds.VectorBundleProductVectorTransport( + Manifolds.FiberBundleProductVectorTransport( ParallelTransport(), ParallelTransport(), ), ) - @test Manifolds.default_vector_transport_method(Mstb, T_p_pr) === + @test Manifolds.default_vector_transport_method(Mstb, T_p_ap) === ProductVectorTransport( ParallelTransport(), - Manifolds.VectorBundleProductVectorTransport( + Manifolds.FiberBundleProductVectorTransport( ParallelTransport(), ParallelTransport(), ), ) - @test Manifolds.default_vector_transport_method(Mstb, T_p_pr) === + @test Manifolds.default_vector_transport_method(Mstb, T_p_ap) === ProductVectorTransport( ParallelTransport(), - Manifolds.VectorBundleProductVectorTransport( + Manifolds.FiberBundleProductVectorTransport( ParallelTransport(), ParallelTransport(), ), ) end - @testset "Riemann tensor" begin - p = ArrayPartition([0.0, 1.0, 0.0], [2.0, 3.0]) - X = ArrayPartition([1.0, 0.0, 0.0], [2.0, 3.0]) - Y = ArrayPartition([0.0, 0.0, 3.0], [-2.0, 3.0]) - Z = ArrayPartition([-1.0, 0.0, 2.0], [2.0, -3.0]) - Xresult = ArrayPartition([6.0, 0.0, 3.0], [0.0, 0.0]) - @test isapprox(riemann_tensor(Mse, p, X, Y, Z), Xresult) - Xresult2 = allocate(Xresult) - riemann_tensor!(Mse, Xresult2, p, X, Y, Z) - @test isapprox(Xresult2, Xresult) - end - @testset "ManifoldDiff" begin p = ArrayPartition([0.0, 1.0, 0.0], [2.0, 3.0]) q = ArrayPartition([1.0, 0.0, 0.0], [-2.0, 3.0]) diff --git a/test/manifolds/projective_space.jl b/test/manifolds/projective_space.jl index 412922419a..140da634cc 100644 --- a/test/manifolds/projective_space.jl +++ b/test/manifolds/projective_space.jl @@ -11,10 +11,15 @@ include("../utils.jl") @test is_flat(ProjectiveSpace(1)) @test !is_point(M, [1.0, 0.0, 0.0, 0.0]) @test !is_vector(M, [1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0]) - @test_throws DomainError is_point(M, [2.0, 0.0, 0.0], true) + @test_throws DomainError is_point(M, [2.0, 0.0, 0.0]; error=:error) @test !is_point(M, [2.0, 0.0, 0.0]) @test !is_vector(M, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - @test_throws DomainError is_vector(M, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0], true) + @test_throws DomainError is_vector( + M, + [1.0, 0.0, 0.0], + [1.0, 0.0, 0.0]; + error=:error, + ) @test injectivity_radius(M) == π / 2 @test injectivity_radius(M, ExponentialRetraction()) == π / 2 @test injectivity_radius(M, [1.0, 0.0, 0.0]) == π / 2 @@ -108,21 +113,21 @@ include("../utils.jl") @test Manifolds.allocation_promotion_function(M, exp!, (1,)) == complex @test !is_point(M, [1.0 + 0im, 0.0, 0.0, 0.0]) @test !is_vector(M, [1.0 + 0im, 0.0, 0.0, 0.0], [0.0 + 0im, 1.0, 0.0]) - @test_throws DomainError is_point(M, [1.0, im, 0.0], true) + @test_throws DomainError is_point(M, [1.0, im, 0.0]; error=:error) @test !is_point(M, [1.0, im, 0.0]) @test !is_vector(M, [1.0 + 0im, 0.0, 0.0], [1.0 + 0im, 0.0, 0.0]) @test !is_vector(M, [1.0 + 0im, 0.0, 0.0], [-0.5im, 0.0, 0.0]) @test_throws DomainError is_vector( M, [1.0 + 0im, 0.0, 0.0], - [1.0 + 0im, 0.0, 0.0], - true, + [1.0 + 0im, 0.0, 0.0]; + error=:error, ) @test_throws DomainError is_vector( M, [1.0 + 0im, 0.0, 0.0], - [-0.5im, 0.0, 0.0], - true, + [-0.5im, 0.0, 0.0]; + error=:error, ) @test injectivity_radius(M) == π / 2 @test injectivity_radius(M, ExponentialRetraction()) == π / 2 @@ -198,21 +203,21 @@ include("../utils.jl") @test !is_flat(M) @test !is_point(M, quat([1.0, 0.0, 0.0, 0.0])) @test !is_vector(M, quat([1.0, 0.0, 0.0, 0.0]), quat([0.0, 1.0, 0.0])) - @test_throws DomainError is_point(M, [1.0, quat(0, 1, 0, 0), 0.0], true) + @test_throws DomainError is_point(M, [1.0, quat(0, 1, 0, 0), 0.0]; error=:error) @test !is_point(M, [1.0, quat(0, 1, 0, 0), 0.0]) @test !is_vector(M, quat([1.0, 0.0, 0.0]), quat([1.0, 0.0, 0.0])) @test !is_vector(M, quat([1.0, 0.0, 0.0]), [quat(0, -0.5, 0, 0), 0.0, 0.0]) @test_throws DomainError is_vector( M, Quaternion[1.0, 0.0, 0.0], - Quaternion[1.0, 0.0, 0.0], - true, + Quaternion[1.0, 0.0, 0.0]; + error=:error, ) @test_throws DomainError is_vector( M, quat([1.0, 0.0, 0.0]), - quat([-0.5, 0.0, 0.0]), - true, + quat([-0.5, 0.0, 0.0]); + error=:error, ) @test injectivity_radius(M) == π / 2 @test injectivity_radius(M, ExponentialRetraction()) == π / 2 @@ -289,8 +294,8 @@ include("../utils.jl") @testset "ArrayProjectiveSpace" begin M = ArrayProjectiveSpace(2, 2; field=ℝ) @test manifold_dimension(M) == 3 - @test repr(M) == "ArrayProjectiveSpace(2, 2; field = ℝ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{2,2},ℝ} + @test repr(M) == "ArrayProjectiveSpace(2, 2; field=ℝ)" + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{2,2}},ℝ} @test representation_size(M) == (2, 2) p = ones(2, 2) q = project(M, p) @@ -301,8 +306,8 @@ include("../utils.jl") M = ArrayProjectiveSpace(2, 2; field=ℂ) @test manifold_dimension(M) == 6 - @test repr(M) == "ArrayProjectiveSpace(2, 2; field = ℂ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{2,2},ℂ} + @test repr(M) == "ArrayProjectiveSpace(2, 2; field=ℂ)" + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{2,2}},ℂ} @test representation_size(M) == (2, 2) end @@ -331,4 +336,12 @@ include("../utils.jl") @test manifold_volume(ProjectiveSpace(2)) ≈ 2 * π @test manifold_volume(ProjectiveSpace(3)) ≈ π * π end + + @testset "field parameter" begin + M = ProjectiveSpace(2; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int},ℝ} + @test repr(M) == "ProjectiveSpace(2, ℝ; parameter=:field)" + @test repr(ArrayProjectiveSpace(2, 3; parameter=:field)) == + "ArrayProjectiveSpace(2, 3; field=ℝ, parameter=:field)" + end end diff --git a/test/manifolds/rotations.jl b/test/manifolds/rotations.jl index a729cfa62a..3fa56e491b 100644 --- a/test/manifolds/rotations.jl +++ b/test/manifolds/rotations.jl @@ -26,15 +26,15 @@ include("../utils.jl") M = Rotations(2) Xf = [1.23] p = Matrix{Float64}(I, 2, 2) - X = Manifolds.hat(M, p, Xf) + X = hat(M, p, Xf) @test isa(X, AbstractMatrix) @test norm(M, p, X) / sqrt(2) ≈ norm(Xf) - @test Manifolds.vee(M, p, X) == Xf + @test vee(M, p, X) == Xf X = project(M, p, randn(2, 2)) - Xf = Manifolds.vee(M, p, X) + Xf = vee(M, p, X) @test isa(Xf, AbstractVector) - @test Manifolds.hat(M, p, Xf) == X + @test hat(M, p, Xf) == X end for T in types @@ -163,46 +163,46 @@ include("../utils.jl") ) p = exp(SOn, pts[1], X) X2 = log(SOn, pts[1], p) - @test p ≈ exp(SOn, pts[1], X2) + @test distance(SOn, p, exp(SOn, pts[1], X2)) < 25 * eps() end end @testset "Test AbstractManifold Point and Tangent Vector checks" begin M = Rotations(2) for p in [1, [2.0 0.0; 0.0 1.0], [1.0 0.5; 0.0 1.0]] - @test_throws DomainError is_point(M, p, true) + @test_throws DomainError is_point(M, p; error=:error) @test !is_point(M, p) end p = one(zeros(2, 2)) @test is_point(M, p) - @test is_point(M, p, true) + @test is_point(M, p; error=:error) for X in [1, [0.0 1.0; 0.0 0.0]] - @test_throws DomainError is_vector(M, p, X, true) + @test_throws DomainError is_vector(M, p, X; error=:error) @test !is_vector(M, p, X) end X = [0.0 1.0; -1.0 0.0] @test is_vector(M, p, X) - @test is_vector(M, p, X, true) + @test is_vector(M, p, X; error=:error) end @testset "Project point" begin M = Rotations(2) p = Matrix{Float64}(I, 2, 2) p1 = project(M, p) - @test is_point(M, p1, true) + @test is_point(M, p1; error=:error) M = Rotations(3) p = collect(reshape(1.0:9.0, (3, 3))) p2 = project(M, p) - @test is_point(M, p2, true) + @test is_point(M, p2; error=:error) rng = MersenneTwister(44) x3 = project(M, randn(rng, 3, 3)) - @test is_point(M, x3, true) + @test is_point(M, x3; error=:error) end @testset "Convert from Lie algebra representation of tangents to Riemannian submanifold representation" begin M = Rotations(3) p = project(M, collect(reshape(1.0:9.0, (3, 3)))) x = [[0, -1, 3] [1, 0, 2] [-3, -2, 0]] - @test is_vector(M, p, x, true) + @test is_vector(M, p, x; error=:error) @test embed(M, p, x) == p * x Y = zeros((3, 3)) embed!(M, Y, p, x) @@ -274,4 +274,16 @@ include("../utils.jl") 0.050996416671166 -0.024666891276861697 0.0 ] end + + @testset "field parameter" begin + M = Rotations(2; parameter=:field) + @test is_flat(M) + @test repr(M) == "Rotations(2; parameter=:field)" + + M = Rotations(1; parameter=:field) + p = fill(1.0, 1, 1) + X = get_vector(M, p, Float64[], DefaultOrthonormalBasis()) + @test X isa Matrix{Float64} + @test X == fill(0.0, 1, 1) + end end diff --git a/test/manifolds/shape_space.jl b/test/manifolds/shape_space.jl index ea1bbcc876..777b298914 100644 --- a/test/manifolds/shape_space.jl +++ b/test/manifolds/shape_space.jl @@ -2,6 +2,7 @@ include("../utils.jl") @testset "KendallsPreShapeSpace" begin M = KendallsPreShapeSpace(2, 3) + @test repr(M) == "KendallsPreShapeSpace(2, 3)" @test representation_size(M) === (2, 3) @test manifold_dimension(M) == 3 @test injectivity_radius(M) == pi @@ -18,12 +19,12 @@ include("../utils.jl") 0.3248027612629014 0.440253011955812 -0.7650557732187135 0.26502337825226757 -0.06175142812400016 -0.20327195012826738 ] - @test_throws DomainError is_point(M, [1 0 1; 1 -1 0] / 2, true) + @test_throws DomainError is_point(M, [1 0 1; 1 -1 0] / 2; error=:error) @test_throws DomainError is_vector( M, [-1 0 1.0; 0 0 0] / sqrt(2), - [1.0 0 1; 1 -1 0], - true, + [1.0 0 1; 1 -1 0]; + error=:error, ) test_manifold( M, @@ -37,10 +38,16 @@ include("../utils.jl") test_rand_tvector=true, rand_tvector_atol_multiplier=5, ) + @testset "field parameter" begin + M = KendallsPreShapeSpace(2, 3; parameter=:field) + @test repr(M) == "KendallsPreShapeSpace(2, 3; parameter=:field)" + @test get_embedding(M) === ArraySphere(2, 3; field=ℝ, parameter=:field) + end end @testset "KendallsShapeSpace" begin M = KendallsShapeSpace(2, 3) + @test repr(M) == "KendallsShapeSpace(2, 3)" @test manifold_dimension(M) == 2 @test !is_flat(M) @test get_total_space(M) === KendallsPreShapeSpace(2, 3) @@ -78,8 +85,8 @@ end @test abs(norm(M, p1, X1) - norm(M, p1, X1h)) < 1e-16 end - @test_throws ManifoldDomainError is_point(M, [1 0 1; 1 -1 0], true) - @test_throws ManifoldDomainError is_vector(M, p1, [1 0 1; 1 -1 0], true) + @test_throws ManifoldDomainError is_point(M, [1 0 1; 1 -1 0]; error=:error) + @test_throws ManifoldDomainError is_vector(M, p1, [1 0 1; 1 -1 0]; error=:error) @testset "exp/distance/norm" begin q1 = exp(M, p1, X1) @@ -105,4 +112,14 @@ end @test manifold_dimension(Md3_2) == 0 @test manifold_dimension(Md2_1) == 0 end + @testset "field parameter" begin + M = KendallsShapeSpace(2, 3; parameter=:field) + @test repr(M) == "KendallsShapeSpace(2, 3; parameter=:field)" + @test get_embedding(M) === KendallsPreShapeSpace(2, 3; parameter=:field) + @test get_total_space(M) === KendallsPreShapeSpace(2, 3; parameter=:field) + @test get_orbit_action(M) === Manifolds.ColumnwiseMultiplicationAction( + M, + SpecialOrthogonal(2; parameter=:field), + ) + end end diff --git a/test/manifolds/skewhermitian.jl b/test/manifolds/skewhermitian.jl index 76369454ad..7fd5a7600e 100644 --- a/test/manifolds/skewhermitian.jl +++ b/test/manifolds/skewhermitian.jl @@ -2,7 +2,6 @@ include("../utils.jl") @testset "SkewSymmetricMatrices" begin @test SkewSymmetricMatrices(3) === SkewHermitianMatrices(3) - @test SkewSymmetricMatrices(3, ℂ) === SkewHermitianMatrices(3, ℂ) end @testset "SkewHermitianMatrices" begin @@ -23,20 +22,20 @@ end @test representation_size(M) == (3, 3) @test base_manifold(M) === M @test is_flat(M) - @test typeof(get_embedding(M)) === Euclidean{Tuple{3,3},ℝ} + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{3,3}},ℝ} @test check_point(M, B_skewsym) === nothing - @test_throws DomainError is_point(M, A, true) - @test_throws ManifoldDomainError is_point(M, C, true) - @test_throws DomainError is_point(M, D, true) + @test_throws DomainError is_point(M, A; error=:error) + @test_throws ManifoldDomainError is_point(M, C; error=:error) + @test_throws DomainError is_point(M, D; error=:error) @test check_vector(M, B_skewsym, B_skewsym) === nothing - @test_throws DomainError is_vector(M, B_skewsym, A, true) - @test_throws ManifoldDomainError is_vector(M, A, B_skewsym, true) - @test_throws DomainError is_vector(M, B_skewsym, D, true) + @test_throws DomainError is_vector(M, B_skewsym, A; error=:error) + @test_throws DomainError is_vector(M, A, B_skewsym; error=:error) + @test_throws DomainError is_vector(M, B_skewsym, D; error=:error) @test_throws ManifoldDomainError is_vector( M, B_skewsym, - 1 * im * zero_vector(M, B_skewsym), - true, + 1 * im * zero_vector(M, B_skewsym); + error=:error, ) @test manifold_dimension(M) == 3 @test manifold_dimension(M_complex) == 9 @@ -102,4 +101,11 @@ end ) end # testset type $T end # for + @testset "field parameter" begin + M = SkewHermitianMatrices(3, ℝ; parameter=:field) + Mc = SkewHermitianMatrices(3, ℂ; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(M) == "SkewSymmetricMatrices(3; parameter=:field)" + @test repr(Mc) == "SkewHermitianMatrices(3, ℂ; parameter=:field)" + end end # test SymmetricMatrices diff --git a/test/manifolds/spd_fixed_determinant.jl b/test/manifolds/spd_fixed_determinant.jl index 4e66ecd7b2..21f01506fa 100644 --- a/test/manifolds/spd_fixed_determinant.jl +++ b/test/manifolds/spd_fixed_determinant.jl @@ -7,13 +7,13 @@ include("../utils.jl") @test is_point(M, p) # Determinant is 4 @test !is_point(M, 2.0 .* p) - @test_throws DomainError is_point(M, 2.0 .* p, true) + @test_throws DomainError is_point(M, 2.0 .* p; error=:error) # X = [0.0 0.1; 0.1 0.0] @test is_vector(M, p, X) Y = [1.0 0.1; 0.1 1.0] @test !is_vector(M, p, Y) - @test_throws DomainError is_vector(M, p, Y, true) + @test_throws DomainError is_vector(M, p, Y; error=:error) @test project(M, 2.0 .* p) == p @test project(M, p, Y) == X @@ -34,4 +34,10 @@ include("../utils.jl") @test distance(M, q, exp(get_embedding(M), p, X)) ≈ 0 atol = 6e-16 @test norm(M, p, log(M, p, q) - X) ≈ 0 atol = 3e-16 @test norm(M, p, log(get_embedding(M), p, q) - X) ≈ 0 atol = 3e-16 + + @testset "field parameter" begin + M = SPDFixedDeterminant(2, 1.0; parameter=:field) + @test repr(M) == "SPDFixedDeterminant(2, 1.0; parameter=:field)" + @test get_embedding(M) == SymmetricPositiveDefinite(2; parameter=:field) + end end diff --git a/test/manifolds/spectrahedron.jl b/test/manifolds/spectrahedron.jl index 912750a738..ffbf6290f4 100644 --- a/test/manifolds/spectrahedron.jl +++ b/test/manifolds/spectrahedron.jl @@ -9,14 +9,14 @@ include("../utils.jl") @test !is_flat(M) q = [1.0 0.0; 0.0 1.0; 1.0 1.0; -1.0 1.0] q = q / norm(q) - @test is_point(M, q, true) + @test is_point(M, q; error=:error) @test base_manifold(M) === M qN = [2.0 0.0; 0.0 1.0; 1/sqrt(2) -1/sqrt(2); 1/sqrt(2) 1/sqrt(2)] - @test_throws DomainError is_point(M, qN, true) + @test_throws DomainError is_point(M, qN; error=:error) Y = [0.0 1.0; 1.0 0.0; 0.0 0.0; 0.0 0.0] - @test is_vector(M, q, Y, true) + @test is_vector(M, q, Y; error=:error) YN = [0.1 1.0; 1.0 0.1; 0.0 0.0; 0.0 0.0] - @test_throws DomainError is_vector(M, q, YN, true) + @test_throws DomainError is_vector(M, q, YN; error=:error) qE = similar(q) embed!(M, qE, q) qE2 = embed(M, q) @@ -54,4 +54,9 @@ include("../utils.jl") ) end end + @testset "field parameter" begin + M = Spectrahedron(4, 2; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(M) == "Spectrahedron(4, 2; parameter=:field)" + end end diff --git a/test/manifolds/sphere.jl b/test/manifolds/sphere.jl index 98e2b955a8..3c04cb7be2 100644 --- a/test/manifolds/sphere.jl +++ b/test/manifolds/sphere.jl @@ -7,7 +7,7 @@ using ManifoldsBase: TFVector M = Sphere(2) @testset "Sphere Basics" begin @test repr(M) == "Sphere(2, ℝ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{3},ℝ} + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{3}},ℝ} @test representation_size(M) == (3,) @test !is_flat(M) @test is_flat(Sphere(1)) @@ -19,10 +19,15 @@ using ManifoldsBase: TFVector @test !is_default_metric(M, AffineInvariantMetric()) @test !is_point(M, [1.0, 0.0, 0.0, 0.0]) @test !is_vector(M, [1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]) - @test_throws DomainError is_point(M, [2.0, 0.0, 0.0], true) + @test_throws DomainError is_point(M, [2.0, 0.0, 0.0]; error=:error) @test !is_point(M, [2.0, 0.0, 0.0]) @test !is_vector(M, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]) - @test_throws DomainError is_vector(M, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0], true) + @test_throws DomainError is_vector( + M, + [1.0, 0.0, 0.0], + [1.0, 0.0, 0.0]; + error=:error, + ) @test injectivity_radius(M, [1.0, 0.0, 0.0], ProjectionRetraction()) == π / 2 end types = [Vector{Float64}] @@ -139,7 +144,7 @@ using ManifoldsBase: TFVector @testset "Complex Sphere" begin M = Sphere(2, ℂ) @test repr(M) == "Sphere(2, ℂ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{3},ℂ} + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{3}},ℂ} @test representation_size(M) == (3,) p = [1.0, 1.0im, 1.0] q = project(M, p) @@ -156,7 +161,7 @@ using ManifoldsBase: TFVector @testset "Quaternion Sphere" begin M = Sphere(2, ℍ) @test repr(M) == "Sphere(2, ℍ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{3},ℍ} + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{3}},ℍ} @test representation_size(M) == (3,) p = [Quaternion(1.0), Quaternion(0, 1.0, 0, 0), Quaternion(0.0, 0.0, -1.0, 0.0)] q = project(M, p) @@ -168,8 +173,8 @@ using ManifoldsBase: TFVector @testset "Array Sphere" begin M = ArraySphere(2, 2; field=ℝ) - @test repr(M) == "ArraySphere(2, 2; field = ℝ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{2,2},ℝ} + @test repr(M) == "ArraySphere(2, 2; field=ℝ)" + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{2,2}},ℝ} @test representation_size(M) == (2, 2) p = ones(2, 2) q = project(M, p) @@ -179,8 +184,8 @@ using ManifoldsBase: TFVector @test is_vector(M, q, X) M = ArraySphere(2, 2; field=ℂ) - @test repr(M) == "ArraySphere(2, 2; field = ℂ)" - @test typeof(get_embedding(M)) === Euclidean{Tuple{2,2},ℂ} + @test repr(M) == "ArraySphere(2, 2; field=ℂ)" + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{2,2}},ℂ} @test representation_size(M) == (2, 2) end @@ -203,7 +208,7 @@ using ManifoldsBase: TFVector p3 ./= norm(p3) X2 = log(M, p, p2) X3 = log(M, p, p3) - B = induced_basis(M, A, i, TangentSpace) + B = induced_basis(M, A, i, TangentSpaceType()) X2B = get_coordinates(M, p, X2, B) X3B = get_coordinates(M, p, X3, B) @@ -282,4 +287,14 @@ using ManifoldsBase: TFVector @test manifold_volume(Sphere(3)) ≈ 2 * π * π @test volume_density(M, p, [0.0, 0.5, 0.5]) ≈ 0.9187253698655684 end + + @testset "field parameter" begin + M = Sphere(2; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int},ℝ} + @test repr(M) == "Sphere(2, ℝ; parameter=:field)" + @test repr(ArraySphere(2, 3; parameter=:field)) == + "ArraySphere(2, 3; field=ℝ, parameter=:field)" + p = [1.0, 0.0, 0.0] + @test local_metric(M, p, DefaultOrthonormalBasis()) == Diagonal([1.0, 1.0]) + end end diff --git a/test/manifolds/sphere_symmetric_matrices.jl b/test/manifolds/sphere_symmetric_matrices.jl index 9ddada770b..255a35e0bc 100644 --- a/test/manifolds/sphere_symmetric_matrices.jl +++ b/test/manifolds/sphere_symmetric_matrices.jl @@ -15,19 +15,19 @@ include("../utils.jl") @test representation_size(M) == (3, 3) @test base_manifold(M) === M @test !is_flat(M) - @test typeof(get_embedding(M)) === ArraySphere{Tuple{3,3},ℝ} + @test typeof(get_embedding(M)) === ArraySphere{TypeParameter{Tuple{3,3}},ℝ} @test check_point(M, A) === nothing - @test_throws ManifoldDomainError is_point(M, B, true) - @test_throws ManifoldDomainError is_point(M, C, true) - @test_throws DomainError is_point(M, D, true) - @test_throws ManifoldDomainError is_point(M, E, true) + @test_throws ManifoldDomainError is_point(M, B; error=:error) + @test_throws ManifoldDomainError is_point(M, C; error=:error) + @test_throws DomainError is_point(M, D; error=:error) + @test_throws ManifoldDomainError is_point(M, E; error=:error) @test check_vector(M, A, zeros(3, 3)) === nothing - @test_throws ManifoldDomainError is_vector(M, A, B, true) - @test_throws ManifoldDomainError is_vector(M, A, C, true) - @test_throws ManifoldDomainError is_vector(M, A, D, true) - @test_throws ManifoldDomainError is_vector(M, D, A, true) - @test_throws ManifoldDomainError is_vector(M, A, E, true) - @test_throws DomainError is_vector(M, J, K, true) + @test_throws ManifoldDomainError is_vector(M, A, B; error=:error) + @test_throws ManifoldDomainError is_vector(M, A, C; error=:error) + @test_throws ManifoldDomainError is_vector(M, A, D; error=:error) + @test_throws DomainError is_vector(M, D, A; error=:error) + @test_throws ManifoldDomainError is_vector(M, A, E; error=:error) + @test_throws DomainError is_vector(M, J, K; error=:error) @test manifold_dimension(M) == 5 A2 = similar(A) @test A == project!(M, A2, A) @@ -74,4 +74,9 @@ include("../utils.jl") test_inplace=true, ) end + @testset "field parameter" begin + M = SphereSymmetricMatrices(3; parameter=:field) + @test repr(M) == "SphereSymmetricMatrices(3, ℝ; parameter=:field)" + @test typeof(get_embedding(M)) === ArraySphere{Tuple{Int,Int},ℝ} + end end diff --git a/test/manifolds/stiefel.jl b/test/manifolds/stiefel.jl index 2daa966b0b..6154ce2a28 100644 --- a/test/manifolds/stiefel.jl +++ b/test/manifolds/stiefel.jl @@ -14,18 +14,18 @@ include("../utils.jl") @test !is_flat(M2) @test is_flat(Stiefel(2, 1)) base_manifold(M) === M - @test_throws ManifoldDomainError is_point(M, [1.0, 0.0, 0.0, 0.0], true) + @test_throws ManifoldDomainError is_point(M, [1.0, 0.0, 0.0, 0.0]; error=:error) @test_throws ManifoldDomainError is_point( M, - 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0], - true, + 1im * [1.0 0.0; 0.0 1.0; 0.0 0.0]; + error=:error, ) @test !is_vector(M, p, [0.0, 0.0, 1.0, 0.0]) @test_throws ManifoldDomainError is_vector( M, p, - 1 * im * zero_vector(M, p), - true, + 1 * im * zero_vector(M, p); + error=:error, ) @test default_retraction_method(M) === PolarRetraction() @test default_inverse_retraction_method(M) === PolarInverseRetraction() @@ -126,11 +126,11 @@ include("../utils.jl") pts = convert.(T, [x, y, z]) v = inverse_retract(M, x, y, PolarInverseRetraction()) @test !is_point(M, 2 * x) - @test_throws DomainError !is_point(M, 2 * x, true) + @test_throws DomainError !is_point(M, 2 * x; error=:error) @test !is_vector(M, 2 * x, v) - @test_throws ManifoldDomainError !is_vector(M, 2 * x, v, true) + @test_throws DomainError !is_vector(M, 2 * x, v; error=:error) @test !is_vector(M, x, y) - @test_throws DomainError is_vector(M, x, y, true) + @test_throws DomainError is_vector(M, x, y; error=:error) test_manifold( M, pts, @@ -225,11 +225,11 @@ include("../utils.jl") pts = convert.(T, [x, y, z]) v = inverse_retract(M, x, y, PolarInverseRetraction()) @test !is_point(M, 2 * x) - @test_throws DomainError !is_point(M, 2 * x, true) + @test_throws DomainError !is_point(M, 2 * x; error=:error) @test !is_vector(M, 2 * x, v) - @test_throws ManifoldDomainError !is_vector(M, 2 * x, v, true) + @test_throws DomainError !is_vector(M, 2 * x, v; error=:error) @test !is_vector(M, x, y) - @test_throws DomainError is_vector(M, x, y, true) + @test_throws DomainError is_vector(M, x, y; error=:error) test_manifold( M, pts, @@ -541,4 +541,9 @@ include("../utils.jl") @test W == Wb end end + @testset "field parameter" begin + M = Stiefel(3, 2; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(M) == "Stiefel(3, 2, ℝ; parameter=:field)" + end end diff --git a/test/manifolds/symmetric.jl b/test/manifolds/symmetric.jl index f0921080b1..5e0ce75a1e 100644 --- a/test/manifolds/symmetric.jl +++ b/test/manifolds/symmetric.jl @@ -17,20 +17,20 @@ include("../utils.jl") @test representation_size(M) == (3, 3) @test base_manifold(M) === M @test is_flat(M) - @test typeof(get_embedding(M)) === Euclidean{Tuple{3,3},ℝ} + @test typeof(get_embedding(M)) === Euclidean{TypeParameter{Tuple{3,3}},ℝ} @test check_point(M, B_sym) === nothing - @test_throws DomainError is_point(M, A, true) - @test_throws ManifoldDomainError is_point(M, C, true) - @test_throws ManifoldDomainError is_point(M, D, true) #embedding changes type + @test_throws DomainError is_point(M, A; error=:error) + @test_throws ManifoldDomainError is_point(M, C; error=:error) + @test_throws ManifoldDomainError is_point(M, D; error=:error) #embedding changes type @test check_vector(M, B_sym, B_sym) === nothing - @test_throws DomainError is_vector(M, B_sym, A, true) - @test_throws ManifoldDomainError is_vector(M, A, B_sym, true) - @test_throws ManifoldDomainError is_vector(M, B_sym, D, true) + @test_throws DomainError is_vector(M, B_sym, A; error=:error) + @test_throws DomainError is_vector(M, A, B_sym; error=:error) + @test_throws ManifoldDomainError is_vector(M, B_sym, D; error=:error) @test_throws ManifoldDomainError is_vector( M, B_sym, - 1 * im * zero_vector(M, B_sym), - true, + 1 * im * zero_vector(M, B_sym); + error=:error, ) @test manifold_dimension(M) == 6 @test manifold_dimension(M_complex) == 9 @@ -85,4 +85,9 @@ include("../utils.jl") @test isapprox(-pts[1], exp(M, pts[1], log(M, pts[1], -pts[1]))) end # testset type $T end # for + @testset "field parameter" begin + M = SymmetricMatrices(3, ℝ; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(M) == "SymmetricMatrices(3, ℝ; parameter=:field)" + end end # test SymmetricMatrices diff --git a/test/manifolds/symmetric_positive_definite.jl b/test/manifolds/symmetric_positive_definite.jl index 0dc0a1854f..254f187f69 100644 --- a/test/manifolds/symmetric_positive_definite.jl +++ b/test/manifolds/symmetric_positive_definite.jl @@ -266,6 +266,7 @@ include("../utils.jl") @test isapprox(exp!(M, pS, p, zero_vector(M, p)), p) @test ismissing(pS.sqrt) @test ismissing(pS.sqrt_inv) + @test allocate_result(M1, zero_vector, p) isa Matrix end @testset "test BigFloat" begin @@ -303,4 +304,9 @@ include("../utils.jl") ] @test volume_density(M, p, X) ≈ 5.141867280770719 end + @testset "field parameter" begin + M = SymmetricPositiveDefinite(3; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(M) == "SymmetricPositiveDefinite(3; parameter=:field)" + end end diff --git a/test/manifolds/symmetric_positive_semidefinite_fixed_rank.jl b/test/manifolds/symmetric_positive_semidefinite_fixed_rank.jl index ca7cc13552..83eae0b079 100644 --- a/test/manifolds/symmetric_positive_semidefinite_fixed_rank.jl +++ b/test/manifolds/symmetric_positive_semidefinite_fixed_rank.jl @@ -9,7 +9,7 @@ include("../utils.jl") q = [1.0 0.0; 0.0 1.0; 0.0 0.0; 0.0 0.0] @test is_point(M, q) Y = [1.0 0.0; 0.0 0.0; 0.0 0.0; 0.0 0.0] - @test_throws DomainError is_point(M, Y, true) + @test_throws DomainError is_point(M, Y; error=:error) @test is_vector(M, q, Y) q2 = [2.0 1.0; 0.0 0.0; 0.0 1.0; 0.0 0.0] q3 = [0.0 0.0; 1.0 0.0; 0.0 1.0; 0.0 0.0] @@ -41,4 +41,9 @@ include("../utils.jl") @test repr(M) == "SymmetricPositiveSemidefiniteFixedRank(4, 2, ℂ)" @test manifold_dimension(M) == 12 end + @testset "field parameter" begin + M = SymmetricPositiveSemidefiniteFixedRank(4, 2; parameter=:field) + @test typeof(get_embedding(M)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(M) == "SymmetricPositiveSemidefiniteFixedRank(4, 2, ℝ; parameter=:field)" + end end diff --git a/test/manifolds/symplectic.jl b/test/manifolds/symplectic.jl index 894d622084..e618d11968 100644 --- a/test/manifolds/symplectic.jl +++ b/test/manifolds/symplectic.jl @@ -74,18 +74,23 @@ using ManifoldDiff ] @testset "Basics" begin - @test repr(Sp_2) == "Symplectic{$(2), ℝ}()" + @test repr(Sp_2) == "Symplectic($(2), ℝ)" @test representation_size(Sp_2) == (2, 2) @test base_manifold(Sp_2) === Sp_2 @test !is_flat(Sp_2) @test is_point(Sp_2, p_2) - @test_throws DomainError is_point(Sp_2, p_2 + I, true) + @test_throws DomainError is_point(Sp_2, p_2 + I; error=:error) @test is_vector(Sp_2, p_2, X1; atol=1.0e-6) @test is_vector(Sp_2, p_2, X2; atol=1.0e-12) @test is_vector(Sp_2, p_2, X1 + X2; atol=1.0e-6) - @test_throws DomainError is_vector(Sp_2, p_2, X1 + [0.1 0.1; -0.1 0.1], true) + @test_throws DomainError is_vector( + Sp_2, + p_2, + X1 + [0.1 0.1; -0.1 0.1]; + error=:error, + ) end @testset "Symplectic Inverse" begin I_2n = Array(I, 2, 2) @@ -154,7 +159,7 @@ using ManifoldDiff # Project Project matrix A ∈ ℝ^{2 × 2} onto (T_pSp): A_2 = [5.0 -21.5; 3.14 14.9] A_2_proj = similar(A_2) - Manifolds.project!(Extended_Sp_2, A_2_proj, p_2, A_2) + project!(Extended_Sp_2, A_2_proj, p_2, A_2) @test is_vector(Sp_2, p_2, A_2_proj; atol=1.0e-16) # Change representer of A onto T_pSp: @@ -185,9 +190,9 @@ using ManifoldDiff @testset "Generate random points/tangent vectors" begin M_big = Symplectic(20) p_big = rand(M_big) - @test is_point(M_big, p_big, true; atol=1.0e-12) + @test is_point(M_big, p_big; error=:error, atol=1.0e-12) X_big = rand(M_big; vector_at=p_big) - @test is_vector(M_big, p_big, X_big, true; atol=1.0e-12) + @test is_vector(M_big, p_big, X_big; error=:error, atol=1.0e-12) end @testset "test_manifold(Symplectic(6), ...)" begin @testset "Type $(Matrix{Float64})" begin @@ -416,4 +421,9 @@ using ManifoldDiff @test ((Q' * pQ_1' * Q) * pQ_1 - I) == zeros(eltype(pQ_1), size(pQ_1)...) end end + @testset "field parameter" begin + Sp_2 = Symplectic(2; parameter=:field) + @test typeof(get_embedding(Sp_2)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(Sp_2) == "Symplectic(2, ℝ; parameter=:field)" + end end diff --git a/test/manifolds/symplecticstiefel.jl b/test/manifolds/symplecticstiefel.jl index ff4563a914..db18123d23 100644 --- a/test/manifolds/symplecticstiefel.jl +++ b/test/manifolds/symplecticstiefel.jl @@ -137,20 +137,26 @@ end ] @testset "Basics" begin - @test repr(SpSt_6_4) == "SymplecticStiefel{6, 4, ℝ}()" + @test repr(SpSt_6_4) == "SymplecticStiefel(6, 4, ℝ)" @test representation_size(SpSt_6_4) == (6, 4) @test base_manifold(SpSt_6_4) === SpSt_6_4 @test get_total_space(SpSt_6_4) == Symplectic(6) @test !is_flat(SpSt_6_4) @test is_point(SpSt_6_4, p_6_4) - @test_throws DomainError is_point(SpSt_6_4, 2 * p_6_4, true) + @test_throws DomainError is_point(SpSt_6_4, 2 * p_6_4; error=:error) @test is_vector(SpSt_6_4, p_6_4, X1; atol=1.0e-12) @test is_vector(SpSt_6_4, p_6_4, X2; atol=1.0e-6) - @test_throws DomainError is_vector(SpSt_6_4, p_6_4, X2, true; atol=1.0e-12) + @test_throws DomainError is_vector( + SpSt_6_4, + p_6_4, + X2; + error=:error, + atol=1.0e-12, + ) @test is_vector(SpSt_6_4, p_6_4, X1 + X2; atol=1.0e-6) - @test_throws DomainError is_vector(SpSt_6_4, p_6_4, X1 + p_6_4, true) + @test_throws DomainError is_vector(SpSt_6_4, p_6_4, X1 + p_6_4; error=:error) end @testset "Symplectic Inverse" begin I_2k = Array(I, 4, 4) @@ -223,14 +229,14 @@ end ]) A_6_4_proj = similar(A_6_4) Manifolds.project!(SpSt_6_4, A_6_4_proj, p_6_4, A_6_4) - @test is_vector(SpSt_6_4, p_6_4, A_6_4_proj, true; atol=2.0e-12) + @test is_vector(SpSt_6_4, p_6_4, A_6_4_proj; error=:error, atol=2.0e-12) end @testset "Generate random points/tangent vectors" begin M_big = SymplecticStiefel(20, 10) p_big = rand(M_big) - @test is_point(M_big, p_big, true; atol=1.0e-14) + @test is_point(M_big, p_big; error=:error, atol=1.0e-14) X_big = rand(M_big; vector_at=p_big, hamiltonian_norm=1.0) - @test is_vector(M_big, p_big, X_big, true; atol=1.0e-14) + @test is_vector(M_big, p_big, X_big; error=:error, atol=1.0e-14) end @testset "test_manifold(Symplectic(6), ...)" begin types = [Matrix{Float64}] @@ -302,4 +308,10 @@ end @test isapprox(grad_f_p, analytical_grad_f(p_grad); atol=1.0e-9) end end + @testset "field parameter" begin + SpSt_6_4 = SymplecticStiefel(2 * 3, 2 * 2; parameter=:field) + @test typeof(get_embedding(SpSt_6_4)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(SpSt_6_4) == "SymplecticStiefel(6, 4, ℝ; parameter=:field)" + @test get_total_space(SpSt_6_4) == Symplectic(6; parameter=:field) + end end diff --git a/test/manifolds/torus.jl b/test/manifolds/torus.jl index 27bfc94932..ddab72ceef 100644 --- a/test/manifolds/torus.jl +++ b/test/manifolds/torus.jl @@ -12,14 +12,14 @@ include("../utils.jl") @test manifold_dimension(M) == 2 @test is_flat(M) @test !is_point(M, 9.0) - @test_throws DomainError is_point(M, 9.0, true) + @test_throws DomainError is_point(M, 9.0; error=:error) @test !is_point(M, [9.0; 9.0]) - @test_throws CompositeManifoldError is_point(M, [9.0 9.0], true) - @test_throws CompositeManifoldError is_point(M, [9.0, 9.0], true) + @test_throws CompositeManifoldError is_point(M, [9.0 9.0]; error=:error) + @test_throws CompositeManifoldError is_point(M, [9.0, 9.0]; error=:error) @test !is_vector(M, [9.0; 9.0], 0.0) - @test_throws DomainError is_vector(M, 9.0, 0.0, true) # point false and checked + @test_throws DomainError is_vector(M, 9.0, 0.0; error=:error) # point false and checked @test !is_vector(M, [9.0; 9.0], [0.0; 0.0]) - @test_throws DomainError is_vector(M, [0.0, 0.0], 0.0, true) + @test_throws DomainError is_vector(M, [0.0, 0.0], 0.0; error=:error) @test injectivity_radius(M) ≈ π x = [1.0, 2.0] y = [-1.0, 2.0] diff --git a/test/manifolds/tucker.jl b/test/manifolds/tucker.jl index 1dbd39722e..762662cbcb 100644 --- a/test/manifolds/tucker.jl +++ b/test/manifolds/tucker.jl @@ -184,4 +184,9 @@ include("../utils.jl") ) end end + @testset "field parameter" begin + M = Tucker(n⃗, r⃗; parameter=:field) + @test sprint(show, "text/plain", M) == + "Tucker((4, 5, 6), (2, 3, 4), ℝ; parameter=:field)" + end end diff --git a/test/manifolds/unitary_matrices.jl b/test/manifolds/unitary_matrices.jl index 3dee3b3d3e..6584e8b48e 100644 --- a/test/manifolds/unitary_matrices.jl +++ b/test/manifolds/unitary_matrices.jl @@ -5,16 +5,18 @@ using Quaternions @testset "Orthogonal Matrices" begin M = OrthogonalMatrices(3) @test repr(M) == "OrthogonalMatrices(3)" + @test repr(OrthogonalMatrices(3; parameter=:field)) == + "OrthogonalMatrices(3; parameter=:field)" @test injectivity_radius(M, PolarRetraction()) == π / sqrt(2.0) @test manifold_dimension(M) == 3 @test injectivity_radius(M) == π * sqrt(2.0) @test !is_flat(M) p = project(M, ones(3, 3)) - @test is_point(M, p, true) - @test is_point(M, rand(M), true) + @test is_point(M, p; error=:error) + @test is_point(M, rand(M); error=:error) @test abs(rand(OrthogonalMatrices(1))[]) == 1 @test is_vector(M, p, rand(M; vector_at=p)) - @test is_point(M, rand(MersenneTwister(), M), true) + @test is_point(M, rand(MersenneTwister(), M); error=:error) @test abs(rand(MersenneTwister(), OrthogonalMatrices(1))[]) == 1 @test is_vector(M, p, rand(MersenneTwister(), M; vector_at=p)) end @@ -22,32 +24,34 @@ end @testset "Unitary Matrices" begin M = UnitaryMatrices(2) @test repr(M) == "UnitaryMatrices(2)" + @test repr(UnitaryMatrices(2; parameter=:field)) == + "UnitaryMatrices(2; parameter=:field)" @test manifold_dimension(M) == 4 @test !is_flat(M) @test injectivity_radius(M) == π # wrong length of size - @test_throws DomainError is_point(M, zeros(1), true) - @test_throws DomainError is_point(M, zeros(3, 3), true) + @test_throws DomainError is_point(M, zeros(1); error=:error) + @test_throws DomainError is_point(M, zeros(3, 3); error=:error) pF = 1 / 2 .* [1im 1im; -1im 1im] - @test_throws DomainError is_point(M, pF, true) + @test_throws DomainError is_point(M, pF; error=:error) # Determinant not one pF2 = [1im 1.0; 0.0 -1im] - @test_throws DomainError is_point(M, pF2, true) + @test_throws DomainError is_point(M, pF2; error=:error) p = [1im 0.0; 0.0 1im] - @test is_point(M, p, true) + @test is_point(M, p; error=:error) - @test_throws DomainError is_vector(M, p, zeros(1), true) - @test_throws DomainError is_vector(M, p, zeros(3, 3), true) + @test_throws DomainError is_vector(M, p, zeros(1); error=:error) + @test_throws DomainError is_vector(M, p, zeros(3, 3); error=:error) # not skew - @test_throws DomainError is_vector(M, p, ones(2, 2), true) + @test_throws DomainError is_vector(M, p, ones(2, 2); error=:error) X = [0.0 1.0; -1.0 0.0] - @test is_vector(M, p, X, true) + @test is_vector(M, p, X; error=:error) q = project(M, ones(2, 2)) - @test is_point(M, q, true) + @test is_point(M, q; error=:error) q2 = project(M, 1im * ones(2, 2)) - @test is_point(M, q2, true) + @test is_point(M, q2; error=:error) r = exp(M, p, X) X2 = log(M, p, r) @@ -67,7 +71,7 @@ end end @testset "Special unitary matrices" begin - M = Manifolds.GeneralUnitaryMatrices{2,ℂ,Manifolds.DeterminantOneMatrices}() + M = SpecialUnitary(2) @test manifold_dimension(M) == 3 @test injectivity_radius(M) ≈ π * sqrt(2.0) end @@ -75,6 +79,8 @@ end @testset "Quaternionic Unitary Matrices" begin M = UnitaryMatrices(1, ℍ) @test repr(M) == "UnitaryMatrices(1, ℍ)" + @test repr(UnitaryMatrices(1, ℍ; parameter=:field)) == + "UnitaryMatrices(1, ℍ; parameter=:field)" @test manifold_dimension(M) == 3 @test injectivity_radius(M) == π @test !is_flat(M) @@ -90,11 +96,11 @@ end end # wrong length of size - @test_throws DomainError is_point(M, zeros(2, 2), true) + @test_throws DomainError is_point(M, zeros(2, 2); error=:error) # Determinant not one pF2 = [quat(0, 1, 0, 0) 1.0; 0.0 -quat(0, 1, 0, 0)] - @test_throws DomainError is_point(M, pF2, true) + @test_throws DomainError is_point(M, pF2; error=:error) p = QuaternionF64( 0.4815296357756736, 0.6041613272484806, @@ -105,9 +111,9 @@ end @test is_point(M, fill(p, 1, 1)) @test is_point(M, p) - @test_throws DomainError is_vector(M, p, zeros(2, 2), true) + @test_throws DomainError is_vector(M, p, zeros(2, 2); error=:error) # not skew - @test_throws DomainError is_vector(M, p, Quaternion(1, 0, 0, 0), true) + @test_throws DomainError is_vector(M, p, Quaternion(1, 0, 0, 0); error=:error) X = Quaternion(0.0, 0, 0, 1) @test is_vector(M, p, X) @@ -134,7 +140,6 @@ end end @testset "Flatness edge cases" begin - @test is_flat( - Manifolds.GeneralUnitaryMatrices{1,ℂ,Manifolds.AbsoluteDeterminantOneMatrices}(), - ) + @test is_flat(SpecialUnitary(1)) + @test is_flat(SpecialUnitary(1; parameter=:field)) end diff --git a/test/manifolds/vector_bundle.jl b/test/manifolds/vector_bundle.jl index 89472ee238..370aacbafa 100644 --- a/test/manifolds/vector_bundle.jl +++ b/test/manifolds/vector_bundle.jl @@ -4,25 +4,14 @@ using RecursiveArrayTools struct TestVectorSpaceType <: VectorSpaceType end -@testset "Tangent bundle" begin +@testset "Vector bundle" begin M = Sphere(2) - m_prod_retr = Manifolds.VectorBundleProductRetraction() - m_prod_invretr = Manifolds.VectorBundleInverseProductRetraction() + TB = TangentBundle(M) + m_prod_retr = Manifolds.FiberBundleProductRetraction() + m_prod_invretr = Manifolds.FiberBundleInverseProductRetraction() m_sasaki = SasakiRetraction(5) @testset "Nice access to vector bundle components" begin - TB = TangentBundle(M) - @testset "ProductRepr" begin - p = ProductRepr([1.0, 0.0, 0.0], [0.0, 2.0, 4.0]) - @test p[TB, :point] === p.parts[1] - @test p[TB, :vector] === p.parts[2] - p[TB, :vector] = [0.0, 3.0, 1.0] - @test p.parts[2] == [0.0, 3.0, 1.0] - p[TB, :point] = [0.0, 1.0, 0.0] - @test p.parts[1] == [0.0, 1.0, 0.0] - @test_throws DomainError p[TB, :error] - @test_throws DomainError p[TB, :error] = [1, 2, 3] - end @testset "ArrayPartition" begin p = ArrayPartition([1.0, 0.0, 0.0], [0.0, 2.0, 4.0]) @test p[TB, :point] === p.x[1] @@ -44,53 +33,49 @@ struct TestVectorSpaceType <: VectorSpaceType end end end - types = [Vector{Float64}] - TEST_FLOAT32 && push!(types, Vector{Float32}) - TEST_STATIC_SIZED && push!(types, MVector{3,Float64}) - - for T in types, prepr in [ProductRepr, ArrayPartition] - p = convert(T, [1.0, 0.0, 0.0]) - TB = TangentBundle(M) + @testset "basic tests" begin @test injectivity_radius(TB) == 0 - TpM = TangentSpaceAtPoint(M, p) @test sprint(show, TB) == "TangentBundle(Sphere(2, ℝ))" @test base_manifold(TB) == M @test manifold_dimension(TB) == 2 * manifold_dimension(M) @test !is_flat(TB) - @test is_flat(TpM) @test representation_size(TB) === nothing @test default_inverse_retraction_method(TB) === m_prod_invretr @test default_retraction_method(TB) == m_prod_retr @test default_vector_transport_method(TB) isa - Manifolds.VectorBundleProductVectorTransport + Manifolds.FiberBundleProductVectorTransport CTB = CotangentBundle(M) @test sprint(show, CTB) == "CotangentBundle(Sphere(2, ℝ))" - @test sprint(show, VectorBundle(TestVectorSpaceType(), M)) == - "VectorBundle(TestVectorSpaceType(), Sphere(2, ℝ))" + @test sprint(show, FiberBundle(TestVectorSpaceType(), M)) == + "FiberBundle(TestVectorSpaceType(), Sphere(2, ℝ), Manifolds.FiberBundleProductVectorTransport{ParallelTransport, ParallelTransport}(ParallelTransport(), ParallelTransport()))" + + @test Manifolds.fiber_dimension(M, ManifoldsBase.CotangentSpaceType()) == 2 + @test base_manifold(TangentBundle(M)) == M + end + + types = [Vector{Float64}] + TEST_FLOAT32 && push!(types, Vector{Float32}) + TEST_STATIC_SIZED && push!(types, MVector{3,Float64}) + + for T in types + p = convert(T, [1.0, 0.0, 0.0]) + TpM = TangentSpace(M, p) + @test is_flat(TpM) + @testset "Type $T" begin pts_tb = [ - prepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, -1.0, -1.0])), - prepr(convert(T, [0.0, 1.0, 0.0]), convert(T, [2.0, 0.0, 1.0])), - prepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, 2.0, -1.0])), + ArrayPartition(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, -1.0, -1.0])), + ArrayPartition(convert(T, [0.0, 1.0, 0.0]), convert(T, [2.0, 0.0, 1.0])), + ArrayPartition(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, 2.0, -1.0])), ] - @inferred prepr(convert(T, [1.0, 0.0, 0.0]), convert(T, [0.0, -1.0, -1.0])) - if prepr === ProductRepr - for pt in pts_tb - @test bundle_projection(TB, pt) ≈ pt.parts[1] - end - else - for pt in pts_tb - @test bundle_projection(TB, pt) ≈ pt.x[1] - end + for pt in pts_tb + @test bundle_projection(TB, pt) ≈ pt.x[1] end X12_prod = inverse_retract(TB, pts_tb[1], pts_tb[2], m_prod_invretr) X13_prod = inverse_retract(TB, pts_tb[1], pts_tb[3], m_prod_invretr) - diag_basis = DiagonalizingOrthonormalBasis(X12_prod) basis_types = ( DefaultOrthonormalBasis(), get_basis(TB, pts_tb[1], DefaultOrthonormalBasis()), - diag_basis, - get_basis(TB, pts_tb[1], diag_basis), ) test_manifold( TB, @@ -101,7 +86,7 @@ struct TestVectorSpaceType <: VectorSpaceType end retraction_methods=[m_prod_retr, m_sasaki], test_exp_log=false, test_injectivity_radius=false, - test_tangent_vector_broadcasting=false, + test_tangent_vector_broadcasting=true, test_vee_hat=true, test_project_tangent=true, test_project_point=true, @@ -118,26 +103,8 @@ struct TestVectorSpaceType <: VectorSpaceType end Xir = allocate(pts_tb[1]) inverse_retract!(TB, Xir, pts_tb[1], pts_tb[2], m_prod_invretr) @test isapprox(TB, pts_tb[1], Xir, X12_prod) - @test isapprox( - norm(TB.fiber, pts_tb[1][TB, :point], pts_tb[1][TB, :vector]), - sqrt( - inner( - TB.fiber, - pts_tb[1][TB, :point], - pts_tb[1][TB, :vector], - pts_tb[1][TB, :vector], - ), - ), - ) - @test isapprox( - distance( - TB.fiber, - pts_tb[1][TB, :point], - pts_tb[1][TB, :vector], - [0.0, 2.0, 3.0], - ), - 5.0, - ) + F = Fiber(M, pts_tb[1][TB, :point], TangentSpaceType()) + @test isapprox(distance(F, pts_tb[1][TB, :vector], [0.0, 2.0, 3.0]), 5.0) Xir2 = allocate(pts_tb[1]) vector_transport_to!( TB, @@ -145,7 +112,7 @@ struct TestVectorSpaceType <: VectorSpaceType end pts_tb[1], Xir, pts_tb[2], - Manifolds.VectorBundleProductVectorTransport(), + Manifolds.FiberBundleProductVectorTransport(), ) @test is_vector(TB, pts_tb[2], Xir2) @@ -174,6 +141,13 @@ struct TestVectorSpaceType <: VectorSpaceType end test_rand_point=true, test_rand_tvector=true, ) + + @test Manifolds.bundle_transport_to( + TB, + p, + convert(T, [0.0, -1.0, -1.0]), + convert(T, [0.0, 1.0, 0.0]), + ) ≈ convert(T, [1.0, 0.0, -1.0]) end end @@ -182,76 +156,43 @@ struct TestVectorSpaceType <: VectorSpaceType end @test CotangentBundle{ℝ,Sphere{2,ℝ}} == VectorBundle{ℝ,Manifolds.CotangentSpaceType,Sphere{2,ℝ}} - @test base_manifold(TangentBundle(M)) == M - @testset "spaces at point" begin - p = [1.0, 0.0, 0.0] - t_p = TangentSpaceAtPoint(M, p) - t_p2 = TangentSpace(M, p) - @test t_p == t_p2 - ct_p = CotangentSpaceAtPoint(M, p) - t_ps = sprint(show, "text/plain", t_p) - sp = sprint(show, "text/plain", p) - sp = replace(sp, '\n' => "\n ") - t_ps_test = "Tangent space to the manifold $(M) at point:\n $(sp)" - @test t_ps == t_ps_test - @test base_manifold(t_p) == M - @test base_manifold(ct_p) == M - @test t_p.fiber.manifold == M - @test ct_p.fiber.manifold == M - @test t_p.fiber.fiber == TangentSpace - @test ct_p.fiber.fiber == CotangentSpace - @test t_p.point == p - @test ct_p.point == p - @test injectivity_radius(t_p) == Inf - @test representation_size(t_p) == representation_size(M) - X = [0.0, 0.0, 1.0] - @test embed(t_p, X) == X - @test embed(t_p, X, X) == X - # generic vector space at - fiber = VectorBundleFibers(TestVectorSpaceType(), M) - X_p = VectorSpaceAtPoint(fiber, p) - X_ps = sprint(show, "text/plain", X_p) - fiber_s = sprint(show, "text/plain", fiber) - X_ps_test = "$(typeof(X_p))\nFiber:\n $(fiber_s)\nBase point:\n $(sp)" - @test X_ps == X_ps_test - @test_throws ErrorException project(fiber, p, X) - @test_throws ErrorException norm(fiber, p, X) - @test_throws ErrorException distance(fiber, p, X, X) - end - @testset "tensor product" begin - TT = Manifolds.TensorProductType(TangentSpace, TangentSpace) - @test sprint(show, TT) == "TensorProductType(TangentSpace, TangentSpace)" - @test vector_space_dimension(VectorBundleFibers(TT, Sphere(2))) == 4 - @test vector_space_dimension(VectorBundleFibers(TT, Sphere(3))) == 9 - @test base_manifold(VectorBundleFibers(TT, Sphere(2))) == M - @test sprint(show, VectorBundleFibers(TT, Sphere(2))) == - "VectorBundleFibers(TensorProductType(TangentSpace, TangentSpace), Sphere(2, ℝ))" + TT = Manifolds.TensorProductType(TangentSpaceType(), TangentSpaceType()) + @test sprint(show, TT) == + "TensorProductType(TangentSpaceType(), TangentSpaceType())" + @test vector_space_dimension(Sphere(2), TT) == 4 + @test vector_space_dimension(Sphere(3), TT) == 9 + @test base_manifold(Fiber(Sphere(2), [1.0, 0.0, 0.0], TT)) == M + @test sprint(show, Fiber(Sphere(2), [1.0, 0.0, 0.0], TT)) == + "VectorSpaceFiber{ℝ, Sphere{TypeParameter{Tuple{2}}, ℝ}, Manifolds.TensorProductType{Tuple{TangentSpaceType, TangentSpaceType}}, Vector{Float64}}(Sphere(2, ℝ), [1.0, 0.0, 0.0], TensorProductType(TangentSpaceType(), TangentSpaceType()))" end @testset "Error messages" begin - vbf = VectorBundleFibers(TestVectorSpaceType(), Euclidean(3)) - @test_throws ErrorException inner(vbf, [1, 2, 3], [1, 2, 3], [1, 2, 3]) - @test_throws ErrorException Manifolds.project!(vbf, [1, 2, 3], [1, 2, 3], [1, 2, 3]) - @test_throws ErrorException zero_vector!(vbf, [1, 2, 3], [1, 2, 3]) + vbf = Fiber(Euclidean(3), [1.0, 0.0, 0.0], TestVectorSpaceType()) + @test_throws MethodError inner(vbf, [1, 2, 3], [1, 2, 3], [1, 2, 3]) + @test_throws MethodError project!(vbf, [1, 2, 3], [1, 2, 3], [1, 2, 3]) + @test_throws MethodError zero_vector!(vbf, [1, 2, 3], [1, 2, 3]) @test_throws MethodError vector_space_dimension(vbf) end @testset "product retraction and inverse retraction on tangent bundle for power and product manifolds" begin M = PowerManifold(Circle(ℝ), 2) N = TangentBundle(M) - p1 = ProductRepr([0.0, 0.0], [0.0, 0.0]) - p2 = ProductRepr([-1.047, -1.047], [0.0, 0.0]) + p1 = ArrayPartition([0.0, 0.0], [0.0, 0.0]) + p2 = ArrayPartition([-1.047, -1.047], [0.0, 0.0]) X1 = inverse_retract(N, p1, p2, m_prod_invretr) @test isapprox(N, p2, retract(N, p1, X1, m_prod_retr)) @test is_vector(N, p2, vector_transport_to(N, p1, X1, p2)) M2 = ProductManifold(Circle(ℝ), Euclidean(2)) N2 = TangentBundle(M2) - p1_2 = ProductRepr(ProductRepr([0.0], [0.0, 0.0]), ProductRepr([0.0], [0.0, 0.0])) - p2_2 = ProductRepr( - ProductRepr([-1.047], [1.0, 0.0]), - ProductRepr([-1.047], [0.0, 1.0]), + p1_2 = ArrayPartition( + ArrayPartition([0.0], [0.0, 0.0]), + ArrayPartition([0.0], [0.0, 0.0]), + ) + p2_2 = ArrayPartition( + ArrayPartition([-1.047], [1.0, 0.0]), + ArrayPartition([-1.047], [0.0, 1.0]), ) @test isapprox( N2, @@ -260,10 +201,10 @@ struct TestVectorSpaceType <: VectorSpaceType end ) ppt = ParallelTransport() - tbvt = Manifolds.VectorBundleProductVectorTransport(ppt, ppt) + tbvt = Manifolds.FiberBundleProductVectorTransport(ppt, ppt) @test TangentBundle(M, tbvt).vector_transport === tbvt @test CotangentBundle(M, tbvt).vector_transport === tbvt - @test VectorBundle(TangentSpace, M, tbvt).vector_transport === tbvt + @test TangentBundle(M, tbvt).vector_transport === tbvt end @testset "Extended flatness tests" begin @@ -271,13 +212,4 @@ struct TestVectorSpaceType <: VectorSpaceType end @test is_flat(M) @test injectivity_radius(M) == Inf end - - @testset "Weingarten Map" begin - p0 = [1.0, 0.0, 0.0] - M = TangentSpaceAtPoint(Sphere(2), p0) - p = [0.0, 1.0, 1.0] - X = [0.0, 1.0, 0.0] - V = [1.0, 0.0, 0.0] - @test Weingarten(M, p, X, V) == zero_vector(M, p) - end end diff --git a/test/metric.jl b/test/metric.jl index 2ea9879806..b9fd75b17a 100644 --- a/test/metric.jl +++ b/test/metric.jl @@ -248,7 +248,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, Random.seed!(42) @testset "Metric Basics" begin @test repr(MetricManifold(Euclidean(3), EuclideanMetric())) === - "MetricManifold(Euclidean(3; field = ℝ), EuclideanMetric())" + "MetricManifold(Euclidean(3; field=ℝ), EuclideanMetric())" @test repr(IsDefaultMetric(EuclideanMetric())) === "IsDefaultMetric(EuclideanMetric())" end @@ -295,7 +295,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, p = [3, 4] i = get_chart_index(M, A, p) - B = induced_basis(M, A, i, TangentSpace) + B = induced_basis(M, A, i, TangentSpaceType()) @test_throws MethodError local_metric(M, p, B) end @testset "scaled Euclidean metric" begin @@ -317,7 +317,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, @test metric(M) === g i_zeros = get_chart_index(M, A, zeros(3)) - B_i_zeros = induced_basis(M, A, i_zeros, TangentSpace) + B_i_zeros = induced_basis(M, A, i_zeros, TangentSpaceType()) @test_throws MethodError local_metric_jacobian(E, zeros(3), B_i_zeros) @test_throws MethodError christoffel_symbols_second_jacobian(E, zeros(3), B_i_zeros) @@ -325,7 +325,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, p, X, Y = vtype(randn(n)), vtype(randn(n)), vtype(randn(n)) chart_p = get_chart_index(M, A, p) - B_chart_p = induced_basis(M, A, chart_p, TangentSpace) + B_chart_p = induced_basis(M, A, chart_p, TangentSpaceType()) @test check_point(M, p) == check_point(E, p) @test check_vector(M, p, X) == check_vector(E, p, X) @@ -404,7 +404,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, for vtype in (Vector, MVector{n}) p = vtype([θ, ϕ]) chart_p = get_chart_index(M, A, p) - B_p = induced_basis(M, A, chart_p, TangentSpace) + B_p = induced_basis(M, A, chart_p, TangentSpaceType()) G = Diagonal(vtype([1, sin(θ)^2])) .* r^2 invG = Diagonal(vtype([1, 1 / sin(θ)^2])) ./ r^2 X, Y = normalize(randn(n)), normalize(randn(n)) @@ -511,7 +511,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, X = [0.5, 0.7, 0.11] chart_p = get_chart_index(M, A, p) - B_p = induced_basis(M, A, chart_p, TangentSpace) + B_p = induced_basis(M, A, chart_p, TangentSpaceType()) fX = ManifoldsBase.TFVector(X, B_p) fY = ManifoldsBase.TFVector(Y, B_p) @@ -550,7 +550,7 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, A = Manifolds.get_default_atlas(MM2) chart_p = get_chart_index(MM2, A, p) - B_p = induced_basis(MM2, A, chart_p, TangentSpace) + B_p = induced_basis(MM2, A, chart_p, TangentSpaceType()) @test_throws MethodError local_metric(MM2, p, B_p) @test_throws MethodError local_metric_jacobian(MM2, p, B_p) @test_throws MethodError christoffel_symbols_second_jacobian(MM2, p, B_p) @@ -595,10 +595,13 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, @test is_point(MM2, p) === is_point(M, p) @test is_vector(MM2, p, X) === is_vector(M, p, X) - a = Manifolds.projected_distribution(M, Distributions.MvNormal(zero(zeros(3)), 1.0)) + a = Manifolds.projected_distribution( + M, + Distributions.MvNormal(zero(zeros(3)), 1.0 * I), + ) b = Manifolds.projected_distribution( MM2, - Distributions.MvNormal(zero(zeros(3)), 1.0), + Distributions.MvNormal(zero(zeros(3)), 1.0 * I), ) @test isapprox(Matrix(a.distribution.Σ), Matrix(b.distribution.Σ)) @test isapprox(a.distribution.μ, b.distribution.μ) @@ -614,18 +617,19 @@ Manifolds.inner(::MetricManifold{ℝ,<:AbstractManifold{ℝ},Issue539Metric}, p, cofY = flat(M, p, fY) @test coX(X) ≈ norm(M, p, X)^2 @test coY(X) ≈ inner(M, p, X, Y) - cotspace = CotangentBundleFibers(M) - cotspace2 = CotangentBundleFibers(MM) + cotspace = CotangentSpace(M, p) + cotspace2 = CotangentSpace(MM, p) @test coX.X ≈ X - @test inner(M, p, X, Y) ≈ inner(cotspace, p, coX, coY) - @test inner(MM, p, fX, fY) ≈ inner(cotspace, p, coX, coY) + X0p = zero_vector(MM, p) + @test inner(M, p, X, Y) ≈ inner(cotspace, X0p, coX, coY) + @test inner(MM, p, fX, fY) ≈ inner(cotspace, X0p, coX, coY) - @test inner(MM, p, fX, fY) ≈ inner(cotspace2, p, cofX, cofY) + @test inner(MM, p, fX, fY) ≈ inner(cotspace2, X0p, cofX, cofY) @test sharp(M, p, coX) ≈ X coMMfX = flat(MM, p, fX) coMMfY = flat(MM, p, fY) - @test inner(MM, p, fX, fY) ≈ inner(cotspace2, p, coMMfX, coMMfY) + @test inner(MM, p, fX, fY) ≈ inner(cotspace2, X0p, coMMfX, coMMfY) @test isapprox(sharp(MM, p, coMMfX).data, fX.data) @testset "Mutating flat/sharp" begin diff --git a/test/notation.jl b/test/notation.jl index ea6df530f5..294cc39a7c 100644 --- a/test/notation.jl +++ b/test/notation.jl @@ -7,7 +7,7 @@ include("utils.jl") @test (2 * p1 ∈ M) == is_point(M, 2 * p1) X1 = [0.0, 1.0, 0.0] X2 = [1.0, 0.0, 0.0] - TpM = TangentSpaceAtPoint(M, p1) + TpM = TangentSpace(M, p1) @test (X1 ∈ TpM) == is_vector(M, p1, X1) @test (X2 ∈ TpM) == is_vector(M, p1, X2) end diff --git a/test/runtests.jl b/test/runtests.jl index 372bf55221..afb113cc85 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -176,6 +176,7 @@ include("utils.jl") include_test("manifolds/product_manifold.jl") include_test("manifolds/power_manifold.jl") include_test("manifolds/quotient_manifold.jl") + include_test("manifolds/fiber_bundle.jl") include_test("manifolds/vector_bundle.jl") include_test("manifolds/graph.jl") @@ -208,6 +209,7 @@ include("utils.jl") include_test("groups/group_operation_action.jl") include_test("groups/rotation_action.jl") include_test("groups/translation_action.jl") + include_test("groups/rotation_translation_action.jl") include_test("groups/connections.jl") include_test("groups/metric.jl") end diff --git a/test/statistics.jl b/test/statistics.jl index 1c25de3282..c4356d3788 100644 --- a/test/statistics.jl +++ b/test/statistics.jl @@ -515,6 +515,7 @@ end x = [1.0, 2.0, 3.0, 4.0] w = pweights(ones(length(x)) / length(x)) @test mean(M, x) ≈ mean(x) + @test mean(Euclidean(; parameter=:field), x) ≈ mean(x) @test mean(M, x, w) ≈ mean(x, w) @test median(M, x; rng=MersenneTwister(1212), atol=10^-12) ≈ median(x) @test median(M, x, w; rng=MersenneTwister(1212), atol=10^-12) ≈ median(x, w) diff --git a/test/test_deprecated.jl b/test/test_deprecated.jl index 1947014240..5791392758 100644 --- a/test/test_deprecated.jl +++ b/test/test_deprecated.jl @@ -1,6 +1,3 @@ using Manifolds, ManifoldsBase, Test -@testset "Deprecation tests" begin - # Let's just test that for now it still works - @test LinearAffineMetric() === AffineInvariantMetric() -end +@testset "Deprecation tests" begin end diff --git a/test/utils.jl b/test/utils.jl index fb1efc5d2c..6e53f06d56 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -5,7 +5,7 @@ TEST_GROUP = get(ENV, "MANIFOLDS_TEST_GROUP", "all") using Manifolds using ManifoldsBase -using ManifoldsBase: number_of_coordinates +using ManifoldsBase: number_of_coordinates, TypeParameter import ManifoldsBase: active_traits, merge_traits using ManifoldDiff diff --git a/tutorials/Project.toml b/tutorials/Project.toml index 51df6bcbef..20f5eaf955 100644 --- a/tutorials/Project.toml +++ b/tutorials/Project.toml @@ -13,12 +13,15 @@ OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" [compat] +BoundaryValueDiffEq = "4" CSV = "0.10" DataFrames = "1" Distributions = "0.22.6, 0.23, 0.24, 0.25" IJulia = "1" -Manifolds = "0.8.71" -ManifoldsBase = "0.14.10" +Manifolds = "0.8.81, 0.9.0" +ManifoldsBase = "0.14, 0.15.0" MultivariateStats = "0.10" +StaticArrays = "1" diff --git a/tutorials/getstarted.qmd b/tutorials/getstarted.qmd index 2e70ea10a1..9f47a8e470 100644 --- a/tutorials/getstarted.qmd +++ b/tutorials/getstarted.qmd @@ -9,6 +9,7 @@ title: 🚀 Get Started with `Manifolds.jl` using Pkg; cd(@__DIR__) Pkg.activate("."); # for reproducibility use the local tutorial environment. +Pkg.develop(path="../") # a trick to work on the local dev version using Markdown ``` @@ -107,7 +108,7 @@ Note that the `LoadError:` is due to quarto, on `REPL` you would just get the `D ```{julia} #| error: true -is_point(M₂, [0, 0, 1.001], true) +is_point(M₂, [0, 0, 1.001]; error=:error) ``` #### 3. ``[The sphere](@ref SphereSection)``{=commonmark} @@ -152,7 +153,7 @@ and for these we again get informative error messages ```{julia} #| error: true -@expect_error is_vector(M₃, [1, 0, 0], [0.1, 1, 1], true) DomainError +@expect_error is_vector(M₃, [1, 0, 0], [0.1, 1, 1]; error=:error) DomainError ``` To learn about how to define a manifold youself check out the [🔗 How to define your own manifold](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/example.html) tutorial of [🔗 `ManifoldsBase.jl`](https://juliamanifolds.github.io/ManifoldsBase.jl/stable/)." @@ -229,7 +230,7 @@ p₃ = Manifolds.ArrayPartition([0, 0, 1], [0, 1, 0]) Here `ArrayPartition` taken from [🔗 `RecursiveArrayTools.jl`](https://github.com/SciML/RecursiveArrayTools.jl) to store the point on the product manifold efficiently in one array, still allowing efficient access to the product elements. ```{julia} -is_point(M₆, p₃, true) +is_point(M₆, p₃; error=:error) ``` But accessing single components still works the same."