From c1409181d7c72a1529b5d34133f4b231f41066c1 Mon Sep 17 00:00:00 2001 From: Mateusz Baran Date: Mon, 16 Dec 2024 14:33:40 +0100 Subject: [PATCH] Heisenberg matrices (#775) * Heisenberg matrices * add docs page * more testing and addressing review --- NEWS.md | 4 + docs/make.jl | 1 + docs/src/manifolds/heisenberg.md | 7 ++ src/Manifolds.jl | 2 + src/manifolds/HeisenbergMatrices.jl | 142 ++++++++++++++++++++++++++ test/manifolds/heisenberg_matrices.jl | 47 +++++++++ test/runtests.jl | 1 + 7 files changed, 204 insertions(+) create mode 100644 docs/src/manifolds/heisenberg.md create mode 100644 src/manifolds/HeisenbergMatrices.jl create mode 100644 test/manifolds/heisenberg_matrices.jl diff --git a/NEWS.md b/NEWS.md index fe14490fc5..97c697b15c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.10.9] - unreleased +### Added + +* The manifold `HeisenbergMatrices` as the underlying manifold of `HeisenbergGroup`. + ### Changed * `about.md` now also lists contributors of manifolds and a very short history of the package. diff --git a/docs/make.jl b/docs/make.jl index 23942d8e4c..3150bfad52 100755 --- a/docs/make.jl +++ b/docs/make.jl @@ -186,6 +186,7 @@ makedocs(; "Generalized Grassmann" => "manifolds/generalizedgrassmann.md", "Grassmann" => "manifolds/grassmann.md", "Hamiltonian" => "manifolds/hamiltonian.md", + "Heisenberg matrices" => "manifolds/heisenberg.md", "Hyperbolic space" => "manifolds/hyperbolic.md", "Hyperrectangle" => "manifolds/hyperrectangle.md", "Invertible matrices" => "manifolds/invertible.md", diff --git a/docs/src/manifolds/heisenberg.md b/docs/src/manifolds/heisenberg.md new file mode 100644 index 0000000000..039e6d7be3 --- /dev/null +++ b/docs/src/manifolds/heisenberg.md @@ -0,0 +1,7 @@ +# Heisenberg matrices + +```@autodocs +Modules = [Manifolds] +Pages = ["manifolds/HeisenbergMatrices.jl"] +Order = [:type, :function] +``` diff --git a/src/Manifolds.jl b/src/Manifolds.jl index e43e215606..b74815e7a8 100644 --- a/src/Manifolds.jl +++ b/src/Manifolds.jl @@ -444,6 +444,7 @@ include("manifolds/FlagOrthogonal.jl") include("manifolds/FlagStiefel.jl") include("manifolds/GeneralizedGrassmann.jl") include("manifolds/GeneralizedStiefel.jl") +include("manifolds/HeisenbergMatrices.jl") include("manifolds/Hyperbolic.jl") include("manifolds/Hyperrectangle.jl") include("manifolds/InvertibleMatrices.jl") @@ -639,6 +640,7 @@ export Euclidean, Grassmann, HamiltonianMatrices, HeisenbergGroup, + HeisenbergMatrices, Hyperbolic, Hyperrectangle, InvertibleMatrices, diff --git a/src/manifolds/HeisenbergMatrices.jl b/src/manifolds/HeisenbergMatrices.jl new file mode 100644 index 0000000000..705b20fefb --- /dev/null +++ b/src/manifolds/HeisenbergMatrices.jl @@ -0,0 +1,142 @@ +@doc raw""" + HeisenbergMatrices{T} <: AbstractDecoratorManifold{𝔽} + +Heisenberg matrices `HeisenbergMatrices(n)` is the manifold of ``(n+2)×(n+2)`` matrices [BinzPods:2008](@cite) + +```math +\begin{bmatrix} 1 & \mathbf{a} & c \\ +\mathbf{0}_n & I_n & \mathbf{b} \\ +0 & \mathbf{0}_n^\mathrm{T} & 1 \end{bmatrix} +``` + +where ``I_n`` is the ``n×n`` unit matrix, ``\mathbf{a}`` is a row vector of length ``n``, +``\mathbf{b}`` is a column vector of length ``n``, ``\mathbf{0}_n`` is the column zero vector +of length ``n``, and ``c`` is a real number. + +It is a submanifold of [`Euclidean`](@ref)`(n+2, n+2)` and the manifold of the +[`HeisenbergGroup`](@ref). + +# Constructor + + HeisenbergMatrices(n::Int; parameter::Symbol=:type) + +Generate the manifold of ``(n+2)×(n+2)`` Heisenberg matrices. +""" +struct HeisenbergMatrices{T} <: AbstractDecoratorManifold{ℝ} + size::T +end + +function HeisenbergMatrices(n::Int; parameter::Symbol=:type) + size = wrap_type_parameter(parameter, (n,)) + return HeisenbergMatrices{typeof(size)}(size) +end + +function active_traits(f, ::HeisenbergMatrices, args...) + return merge_traits(IsEmbeddedSubmanifold()) +end + +function check_point(M::HeisenbergMatrices, p; kwargs...) + n = get_parameter(M.size)[1] + if !isone(p[1, 1]) + return DomainError( + p[1, 1], + "The matrix $(p) does not lie on $(M), since p[1, 1] is not equal to 1.", + ) + end + if !isone(p[n + 2, n + 2]) + return DomainError( + p[n + 2, n + 2], + "The matrix $(p) does not lie on $(M), since p[n+2, n+2] is not equal to 1.", + ) + end + if !iszero(p[2:(n + 2), 1]) + return DomainError( + norm(iszero(p[2:(n + 2), 1])), + "The matrix $(p) does not lie on $(M), since p[2:(n + 2), 1] is not equal to 0.", + ) + end + if !iszero(p[n + 2, 1:(n + 1)]) + return DomainError( + norm(iszero(p[n + 2, 1:(n + 1)])), + "The matrix $(p) does not lie on $(M), since p[n + 2, 1:(n+1)] is not equal to 0.", + ) + end + if !isapprox(I, p[2:(n + 1), 2:(n + 1)]) + return DomainError( + det(p[2:(n + 1), 2:(n + 1)] - I), + "The matrix $(p) does not lie on $(M), since p[2:(n+1), 2:(n+1)] is not an identity matrix.", + ) + end + + return nothing +end + +function check_vector(M::HeisenbergMatrices, p, X; kwargs...) + n = get_parameter(M.size)[1] + if !iszero(X[1, 1]) + return DomainError( + X[1, 1], + "The matrix $(X) does not lie in the tangent space of $(M), since X[1, 1] is not equal to 0.", + ) + end + if !iszero(X[n + 2, n + 2]) + return DomainError( + X[n + 2, n + 2], + "The matrix $(X) does not lie in the tangent space of $(M), since X[n+2, n+2] is not equal to 0.", + ) + end + if !iszero(X[2:(n + 2), 1:(n + 1)]) + return DomainError( + norm(X[2:(n + 2), 1:(n + 1)]), + "The matrix $(X) does not lie in the tangent space of $(M), since X[2:(n + 2), 1:(n + 1)] is not a zero matrix.", + ) + end + return nothing +end + +embed(::HeisenbergMatrices, p) = p +embed(::HeisenbergMatrices, p, X) = X + +function get_embedding(::HeisenbergMatrices{TypeParameter{Tuple{n}}}) where {n} + return Euclidean(n + 2, n + 2) +end +function get_embedding(M::HeisenbergMatrices{Tuple{Int}}) + n = get_parameter(M.size)[1] + return Euclidean(n + 2, n + 2; parameter=:field) +end + +""" + is_flat(::HeisenbergMatrices) + +Return true. [`HeisenbergMatrices`](@ref) is a flat manifold. +""" +is_flat(M::HeisenbergMatrices) = true + +""" + manifold_dimension(M::HeisenbergMatrices) + +Return the dimension of [`HeisenbergMatrices`](@ref)`(n)`, which is equal to ``2n+1``. +""" +manifold_dimension(M::HeisenbergMatrices) = 2 * get_parameter(M.size)[1] + 1 + +function Base.show(io::IO, ::HeisenbergMatrices{TypeParameter{Tuple{n}}}) where {n} + return print(io, "HeisenbergMatrices($(n))") +end +function Base.show(io::IO, M::HeisenbergMatrices{Tuple{Int}}) + n = get_parameter(M.size)[1] + return print(io, "HeisenbergMatrices($(n); parameter=:field)") +end + +@doc raw""" + Y = Weingarten(M::HeisenbergMatrices, p, X, V) + Weingarten!(M::HeisenbergMatrices, Y, p, X, V) + +Compute the Weingarten map ``\mathcal W_p`` at `p` on the [`HeisenbergMatrices`](@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(::HeisenbergMatrices, p, X, V) + +Weingarten!(::HeisenbergMatrices, Y, p, X, V) = fill!(Y, 0) diff --git a/test/manifolds/heisenberg_matrices.jl b/test/manifolds/heisenberg_matrices.jl new file mode 100644 index 0000000000..2f5bca0e18 --- /dev/null +++ b/test/manifolds/heisenberg_matrices.jl @@ -0,0 +1,47 @@ +using LinearAlgebra, Manifolds, ManifoldsBase, Test + +@testset "Heisenberg matrices" begin + M = HeisenbergMatrices(1) + @test repr(M) == "HeisenbergMatrices(1)" + @test is_flat(M) + + pts = [ + [1.0 2.0 3.0; 0.0 1.0 -1.0; 0.0 0.0 1.0], + [1.0 4.0 -3.0; 0.0 1.0 3.0; 0.0 0.0 1.0], + [1.0 -2.0 1.0; 0.0 1.0 1.1; 0.0 0.0 1.0], + ] + Xpts = [ + [0.0 2.0 3.0; 0.0 0.0 -1.0; 0.0 0.0 0.0], + [0.0 4.0 -3.0; 0.0 0.0 3.0; 0.0 0.0 0.0], + [0.0 -2.0 1.0; 0.0 0.0 1.1; 0.0 0.0 0.0], + ] + + @test check_point(M, [0.0 2.0 3.0; 0.0 1.0 -1.0; 0.0 0.0 1.0]) isa DomainError + @test check_point(M, [1.0 2.0 3.0; 0.0 1.0 -1.0; 1.0 0.0 1.0]) isa DomainError + @test check_point(M, [1.0 2.0 3.0; 0.0 2.0 -1.0; 0.0 0.0 1.0]) isa DomainError + @test check_point(M, [1.0 2.0 3.0; 0.0 1.0 -1.0; 0.0 0.0 2.0]) isa DomainError + @test check_point(M, [1.0 2.0 3.0; 0.0 1.0 -1.0; 0.0 1.0 1.0]) isa DomainError + @test check_point(M, [1.0 2.0 3.0; 1.0 1.0 -1.0; 0.0 0.0 1.0]) isa DomainError + + @test check_vector(M, pts[1], [1.0 2.0 3.0; 0.0 0.0 -1.0; 0.0 0.0 0.0]) isa DomainError + @test check_vector(M, pts[1], [0.0 2.0 3.0; 1.0 0.0 -1.0; 0.0 0.0 0.0]) isa DomainError + @test check_vector(M, pts[1], [0.0 2.0 3.0; 0.0 0.0 -1.0; 0.0 0.0 2.0]) isa DomainError + + test_manifold( + M, + pts; + parallel_transport=true, + test_injectivity_radius=true, + test_musical_isomorphisms=false, + ) + + @test all( + iszero, + Weingarten(M, pts[1], Xpts[1], [1.0 0.0 0.0; 0.0 0.0 0.0; 0.0 0.0 0.0]), + ) + @testset "field parameter" begin + G = HeisenbergMatrices(1; parameter=:field) + @test typeof(get_embedding(G)) === Euclidean{Tuple{Int,Int},ℝ} + @test repr(G) == "HeisenbergMatrices(1; parameter=:field)" + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 49b82cea64..799c5cbc74 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -150,6 +150,7 @@ end include_test("manifolds/generalized_stiefel.jl") include_test("manifolds/grassmann.jl") include_test("manifolds/hamiltonian.jl") + include_test("manifolds/heisenberg_matrices.jl") include_test("manifolds/hyperbolic.jl") include_test("manifolds/hyperrectangle.jl") include_test("manifolds/invertible_matrices.jl")