work with groups

This is a short overview of group support in Manifolds.jl and how to get started working with them.

Groups currently available in Manifolds.jl are listed in group section.

You can read more about the theory of Lie groups for example in [Chi12]. An example application of Lie groups in robotics is described in [SDA21].

First, let’s load libraries we will use. RecursiveArrayTools.jl is necessary because its ArrayPartition is used as one of the possible representations of elements of product and semidirect product groups. StaticArrays.jl can be used to speed up some operations.

using Manifolds, RecursiveArrayTools, StaticArrays

Introduction: group of rotations on a plane

Let’s first consider an example of the group of rotations of a plane, $\operatorname{SO}(2)$. They can be represented in several ways, for example as angles of rotation (which corresponds to RealCircleGroup), unit complex numbers (CircleGroup), or rotation matrices (SpecialOrthogonal). Let’s consider the last representation since it is the most nontrivial one and can be more easily generalized to other groups.

The associated manifolds and groups are defined by:

G = SpecialOrthogonal(2)
M = base_manifold(G)
@assert M === Rotations(2)

This duality (Lie group and the underlying manifold being separate) is a common pattern in Manifolds.jl. The group G can be used for both Lie group-specific operations and metric-specific operation, while the manifold M only allows using manifold and metric operations. This way groups can be specialized in ways not relevant to plain manifolds, and if someone doesn’t use the groups structure, they don’t have to consider it by just using the manifold.

Some basic definitions

# default basis
B = DefaultOrthogonalBasis()
# Identity rotation
p0 = @SMatrix [1.0 0; 0 1]

# Group identity element of a special type
IG = Identity(G)
Identity(MultiplicationOperation)

Let’s say we want to define a manifold point p_i some rotation θ from the identity_element reference rotation p0 (another point on the manifold that we will use as reference)

# + radians rotation from x-axis on plane to point i
xθi = π/6
0.5235987755982988

From Coordinates

To get our first Lie algebra element we can use the hat function which is commonly used in robotics, or equivalently a more generalized get_vector, function:

X_ = hat(G, IG, xθi)              # specific definition to Lie algebras
xXi = get_vector(G, p0, xθi, B)   # generalized definition beyond Lie algebras
println(xXi)
@assert isapprox(X_, xXi)
[0.0 -0.5235987755982988; 0.5235987755982988 0.0]

Note that hat here assumes a default (orthogonal) basis for the more general get_vector.

Note

In this case, the same would work given the base manifold Rotations(2):

_X_ = hat(M, p0, xθi)             # Lie groups definition
_X = get_vector(M, p0, xθi, B)   # generalized definition
@assert _X_ == xXi; @assert _X == xXi

One more caveat here is that for the Rotation matrices, the tangent vectors are always stored as elements from the Lie algebra.

Now, we can transform this algebra element to a point on the manifold using the exponential map exp:

xRi = exp(G, p0, xXi)
# similarly for known underlying manifold
xRi_ = exp(M, p0, xXi)

@assert isapprox(xRi, xRi_)

To Coordinates

The logarithmic map transforms elements of the group back to its Lie algebra:

xXi_ = log(G, p0, xRi)
xXi__ = log(M, p0, xRi)
@assert xXi ≈ xXi__

Similarly, the coordinate values can be extracted from the algebra elements using vee, or using the more generalized get_coordinates:

# extracting coordinates using vee
xθi__ = vee(G, p0, xXi_)[1]
_xθi__ = vee(M, p0, xXi_)[1]

# OR, the preferred generalized get_coordinate function
xθi_ = get_coordinates(G, p0, xXi_, B)[1]
_xθi_ = get_coordinates(M, p0, xXi_, B)[1]

# confirm all versions are correct
@assert isapprox(xθi, xθi_); @assert isapprox(xθi, _xθi_)
@assert isapprox(xθi, xθi__); @assert isapprox(xθi, _xθi__)

Actions and Operations

With the basics in hand on how to move between the coordinate, algebra, and group representations, let’s briefly look at composition and application of points on the manifold. For example, a Rotations manifold is the mathematical representation, but the points have an application purpose in retaining information regarding a specific rotation.

Points from a Lie group may have an associated action (i.e. a rotation) which we apply. Consider rotating through θ = π/6 three vectors V from their native domain Euclidean(2), from the reference point a to a new point b. Engineering disciplines sometimes refer to the action of a manifold point a or b as reference frames. More generally, by taking the tangent space at point p, we are defining a local coordinate frame with basis B, and should not be confused with “reference frame” a or b.

Keeping with our two-dimensional example above:

aV1 = [1; 0]
aV2 = [0; 1]
aV3 = [10; 10]

A_left = RotationAction(Euclidean(2), G)

bθa = π/6
bXa = get_vector(base_manifold(G), p0, bθa, B)

bRa = exp(G, p0, bXa)

for aV in [aV1; aV2; aV3]
    bV = apply(A_left, bRa, aV)
    # test we are getting the rotated vectors in Euclidean(2) as expected
    @assert isapprox(bV[1], norm(aV) * cos(bθa))
    @assert isapprox(bV[2], norm(aV) * sin(bθa))
end

Note

In general, actions are usually non-commutative and the user must therefore be aware whether they want to use LeftAction or RightAction. In this case, the default LeftAction() is used.

Finally, the actions (i.e. points from a manifold) can be composed together. Consider putting together two rotations aRb and bRc such that a single composite rotation aRc is found. The next bit of code composes five rotations of π/4 increments:

A_left = RotationAction(M, G)
aRi = copy(p0)

iθi_ = π/4
x_θ = get_vector(M, p0, iθi_, B) #hat(Rn, R0, θ)
iRi_ = exp(M, p0, x_θ)

# do 5 times over:
# Ri_ = Ri*iRi_
for i in 1:5
    aRi = compose(A_left, aRi, iRi_)
end

# drop back to a algebra, then coordinate
aXi = log(G, p0, aRi)
aθi = get_coordinates(G, p0, aXi, B)

# should wrap around to 3rd quadrant of xy-plane
@assert isapprox(-3π/4, aθi[1])

Warning

compose or apply must be done with group (not algebra) elements. This example shows how these two element types can easily be confused, since both the manifold group and algebra elements can have exactly the same data storage type – i.e. a 2x2 matrix.

As a last note, other rotation representations, including quaternions, Pauli matrices, etc., have similar features. A contrasting example in rotations, however, are Euler angles which can also store rotation information but quickly becomes problematic with familiar problems such as “gimbal-lock”.

Relationship between groups, metrics and connections

Group structure provides a canonical way to define 📖 exponential and logarithmic maps from the Lie algebra. They can be calculated in Manifolds.jl using the exp_lie and log_lie functions. Such exponential and logarithmic maps can be extended invariantly to tangent spaces at any point of the Lie group. This extension is implemented using functions exp_inv and log_inv.

Finally, there are log and exp functions which are metric (or connection)-related functions in Manifolds.jl. For groups which can be equipped with a bi-invariant metric, log and log_inv return the same result, similarly exp and exp_inv. However, only compact groups and their products with Euclidean spaces can have a bi-invariant metric (see for example Theorem 21.9 in [GQ20]). A prominent example of a Lie group without a bi-invariant metric is the special Euclidean group (in two or more dimensions). Then we have a choice between a metric but non-invariant exponential map (which is generally the default choice for exp) or a non-metric, invariant exponential map (exp_inv). Which one should be used depends on whether being metric or being invariant is more important in a particular application.

G = SpecialEuclidean(2)
p = ArrayPartition([1.0, -1.0], xRi)
X = ArrayPartition([2.0, -3.0], aXi)
q_m = exp(G, p, X)
println(q_m)
q_i = exp_inv(G, p, X)
println(q_i)
ArrayPartition{Float64, Tuple{Vector{Float64}, SMatrix{2, 2, Float64, 4}}}(([3.0, -4.0], [-0.25881904510252124 0.9659258262890682; -0.9659258262890682 -0.25881904510252124]))
ArrayPartition{Float64, Tuple{Vector{Float64}, MMatrix{2, 2, Float64, 4}}}(([0.8121200537878321, -3.8212723543456155], [-0.25881904510252124 0.9659258262890682; -0.9659258262890682 -0.25881904510252124]))

As we can see, the results differ. We can observe the invariance as follows:

p2 = ArrayPartition([2.0, -1.0], xRi)
q1_m = exp(G, translate(G, p2, p), translate_diff(G, p2, p, X))
q2_m = translate(G, p2, exp(G, p, X))
println(isapprox(q1_m, q2_m))

q1_i = exp_inv(G, translate(G, p2, p), translate_diff(G, p2, p, X))
q2_i = translate(G, p2, exp_inv(G, p, X))
println(isapprox(q1_i, q2_i))
false
true

Now, q1_m and q2_m are different due to non-invariance of the metric connection but q1_i and q2_i are equal due to invarianced of exp_inv.

The following table outlines invariance of exp and log of various groups.

GroupZero torsion connectionInvariant
ProductGroupProduct of connections in each submanifold🟡[1]
SemidirectProductGroupSame as underlying product
TranslationGroupCartanSchoutenZero
CircleGroupCartanSchoutenZero
GeneralLinearGroupMetric connection from the left invariant metric induced from the standard basis on the Lie algebra
GeneralUnitaryMultiplicationGroupCartanSchoutenZero (explicitly)
HeisenbergGroupCartanSchoutenZero
SpecialLinearGroupSame as GeneralLinear

Literature

[Chi12]
G. S. Chirikjian. Stochastic Models, Information Theory, and Lie Groups, Volume 2. 1 Edition, Vol. 2 of Applied and Numerical Harmonic Analysis (Birkhäuser Boston, MA, 2012).
[GQ20]
[SDA21]
J. Solà, J. Deray and D. Atchuthan. A micro Lie theory for state estimation in robotics (Dec 2021), arXiv:1812.01537 [cs.RO], arXiv: 1812.01537.
  • 1Yes if all component connections are invariant separately, otherwise no