Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable Changes to the Julia package `LieGroups.jl` will be documented in th
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.1.12] unreleased

### Added

* `jacobian_exp` for the `TranslationGroup` (identity), the `SpecialEuclideanGroup` in both variants and dimensions 2 and 3, and the `SpecialGalileanGroup(3)` (closed form from Kelly, arXiv:2312.07555, section 8). All closed forms fall back to numerically robust expansions near zero rotation angles.
* `jacobian_exp` for a (direct) `ProductLieGroup`, assembled block-diagonally from the `jacobian_exp` of its factors.
* Fix ambiguities with `ZeroVector`and `Identity{AdditionGroupOperation}` in addition operations. `ZeroVector` was introduced in ManifoldsBase v2.5.

### Changed

* `jacobian_exp(G, g, X, b)` and `jacobian_exp!(G, J, g, X, b)` no longer take the base point `g`, since the Jacobian of the group exponential map does not depend on it: use `jacobian_exp(G, X, b)` and `jacobian_exp!(G, J, X, b)` instead. The old signatures are deprecated and still work.

## [0.1.11] 2026-05-05

### Added
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ LieGroupsTestExt = "Test"
[compat]
LinearAlgebra = "1.10"
Manifolds = "0.11"
ManifoldsBase = "2"
ManifoldsBase = "2.5"
Quaternions = "0.7.6"
Random = "1.10"
RecursiveArrayTools = "2, 3, 4.0"
Expand Down
2 changes: 1 addition & 1 deletion docs/src/tutorials/transition.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ The list lists first types, then functions. Within both blocks, the order is alp
| `inverse_translate(G, g, h, c)` | [`inv_left_compose`](@ref)`(G, g, h)`, [`inv_right_compose`](@ref)`(G, g, h)` | compute ``g^{-1}∘h`` and ``g∘h^{-1}``, resp. |
| `inverse_translate_diff(G, g, h, X, LeftForwardAction())` | - | discontinued, use `diff_left_compose(G, inv(G,g), h)` |
| `inverse_translate_diff(G, g, h, X, RightBackwardAction())` | - | discontinued, use `diff_left_compose(G, h, inv(G,g))` |
| `jacobian_exp_argument(G, g, X, b)` | [`jacobian_exp`](@ref)`(G, g, X, b)` | the Jacobian of the exponential map w.r.t. an [`AbstractBasis`](@extref `ManifoldsBase.AbstractBasis`) of the [`LieAlgebra`](@ref). The old name is resevered for the Riemannian exponential map. |
| `jacobian_exp_argument(G, g, X, b)` | [`jacobian_exp`](@ref)`(G, X, b)` | the Jacobian of the exponential map w.r.t. an [`AbstractBasis`](@extref `ManifoldsBase.AbstractBasis`) of the [`LieAlgebra`](@ref). The old name is resevered for the Riemannian exponential map. |
| `log(G, g, h)` | `log(`[`base_manifold`](@ref base_manifold(G::LieGroup))`(G), g, h)` | you can now access the previous defaults on the internal manifold whenever they do not agree with the invariant one |
| `log_inv(G, g, h)` | [`log`](@ref log(G::LieGroup, g, h))`(G, g, h)` | the logarithmic map invariant to the group operation is the default on Lie groups here |
| `log_lie(G, g)` | [`log`](@ref log(G::LieGroup, g))`(G, g)` | the (matrix/Lie group) logarithm |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,33 @@ function ManifoldsBase.log!(
return X
end

function LieGroups.jacobian_exp!(
G::SpecialEuclideanGroup{<:ManifoldsBase.TypeParameter{Tuple{2}}},
J::AbstractMatrix,
X::ArrayPartition,
::DefaultLieAlgebraOrthogonalBasis,
)
return LieGroups._jacobian_exp_SE2!(G, J, X)
end

function LieGroups.jacobian_exp!(
G::SpecialEuclideanGroup{<:ManifoldsBase.TypeParameter{Tuple{3}}},
J::AbstractMatrix,
X::ArrayPartition,
::DefaultLieAlgebraOrthogonalBasis,
)
return LieGroups._jacobian_exp_SE3!(G, J, X)
end

function LieGroups.jacobian_exp!(
G::SpecialEuclideanGroup,
J::AbstractMatrix,
X::SpecialEuclideanProductTangentVector,
B::DefaultLieAlgebraOrthogonalBasis,
)
return LieGroups.jacobian_exp!(G, J, ManifoldsBase.internal_value(X), B)
end

function LinearAlgebra.norm(
𝔤::LieAlgebra{
ℝ, <:LieGroups.SpecialEuclideanGroupOperation, <:LieGroups.SpecialEuclideanGroup,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
using LieGroups: SpecialGalileanGroup
using LieGroups: SpecialGalileanGroup, _skew
using StaticArrays
using LinearAlgebra

# Internal function to compute the skew-symmetric matrix as an SMatrix used for performance.
# Can be replaced with hat(SO(3), v) once that works without allocations.
function _skew(v::AbstractVector{T}) where {T <: Real}
return SMatrix{3, 3, T}(0, v[3], -v[2], -v[3], 0, v[1], v[2], -v[1], 0)
end

# Internal function to compute the matrix Q used in the exponential and logarithm maps for the Special Galilean group. (D matrix in Kelly:2025)
function _Q(θ⃗)
T = eltype(θ⃗)
Expand Down Expand Up @@ -200,6 +194,47 @@ function LieGroups.compose(::SpecialGalileanGroup, g::ArrayPartition, h::ArrayPa
)
end

# Lie bracket [X, Y] on 𝔰𝔤𝔞𝔩(3): the matrix commutator of the 5×5 screw representation
# (see the `hat` docstring), expressed in the ((Ω, ν), (ρ, ι)) blocks
# [Kelly:2025; eq. (14) basis](@cite):
# [X, Y]_Ω = Ω_X Ω_Y - Ω_Y Ω_X (equivalently ω_X × ω_Y)
# [X, Y]_ν = Ω_X ν_Y - Ω_Y ν_X
# [X, Y]_ρ = Ω_X ρ_Y - Ω_Y ρ_X + ι_Y ν_X - ι_X ν_Y
# [X, Y]_ι = 0
function LieGroups.lie_bracket(
Comment thread
kellertuer marked this conversation as resolved.
::typeof(LieAlgebra(SpecialGalileanGroup(3))),
Comment thread
kellertuer marked this conversation as resolved.
X::ArrayPartition,
Y::ArrayPartition,
)
ΩX, νX, ρX, ιX = X.x[1].x[1], X.x[1].x[2], X.x[2].x[1], X.x[2].x[2][1]
ΩY, νY, ρY, ιY = Y.x[1].x[1], Y.x[1].x[2], Y.x[2].x[1], Y.x[2].x[2][1]
return ArrayPartition(
ArrayPartition(
ΩX * ΩY - ΩY * ΩX, # Ω
ΩX * νY - ΩY * νX, # ν
),
ArrayPartition(
ΩX * ρY - ΩY * ρX + ιY * νX - ιX * νY, # ρ
zero(X.x[2].x[2]), # ι
),
)
end

function LieGroups.lie_bracket!(
::typeof(LieAlgebra(SpecialGalileanGroup(3))),
Comment thread
kellertuer marked this conversation as resolved.
Z::ArrayPartition,
X::ArrayPartition,
Y::ArrayPartition,
)
ΩX, νX, ρX, ιX = X.x[1].x[1], X.x[1].x[2], X.x[2].x[1], X.x[2].x[2][1]
ΩY, νY, ρY, ιY = Y.x[1].x[1], Y.x[1].x[2], Y.x[2].x[1], Y.x[2].x[2][1]
Z.x[1].x[1] .= ΩX * ΩY .- ΩY * ΩX
Z.x[1].x[2] .= ΩX * νY .- ΩY * νX
Z.x[2].x[1] .= ΩX * ρY .- ΩY * ρX .+ ιY .* νX .- ιX .* νY
Z.x[2].x[2] .= 0
return Z
end

# Dev NOTE: hat and vee use a different bases order than that of the underlining semidirect + direct product groups,
# therefore, get_vector_lie and get_coordinates_lie are implemented explicitly. see hat/vee docstrings for details.
function LieGroups.get_vector_lie(
Expand Down Expand Up @@ -285,3 +320,19 @@ function LieGroups.get_coordinates_lie!(
c[10] = X.x[2].x[2][] # Δt
return c
end

function LieGroups.jacobian_exp!(
::LieGroups.SpecialGalileanGroup{ManifoldsBase.TypeParameter{Tuple{3}}},
J::AbstractMatrix,
X::ArrayPartition,
::DefaultLieAlgebraOrthogonalBasis,
)
Ω = X.x[1].x[1]
ν = X.x[1].x[2]
ρ = X.x[2].x[1]
ι = X.x[2].x[2][]
ω = [Ω[3, 2], Ω[1, 3], Ω[2, 1]]
# jacobian_exp is the left-trivialized differential of exp (the right Jacobian),
Comment thread
kellertuer marked this conversation as resolved.
# obtained from the left Jacobian of [Kelly:2025, eq. (31)] as J_r(ξ) = J_ℓ(-ξ)
return LieGroups._jacobian_exp_left_SGal3!(J, -ρ, -ν, -ω, -ι)
end
10 changes: 5 additions & 5 deletions ext/LieGroupsTestExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ end

"""
LieGroups.Test.test_jacobian_exp(
G::AbstractLieGroup, g, X;
G::AbstractLieGroup, X;
basis = DefaultLieAlgebraOrthogonalBasis(),
expected = missing,
test_mutating = true,
Expand All @@ -903,19 +903,19 @@ test `jacobian_exp`.

"""
function LieGroups.Test.test_jacobian_exp(
G::AbstractLieGroup, g, X;
G::AbstractLieGroup, X;
basis = DefaultLieAlgebraOrthogonalBasis(),
expected = missing,
test_mutating::Bool = true,
kwargs...,
)
@testset "Jacobian of the exponential map with respect to its argument" begin
J = jacobian_exp(G, g, X, basis)
J = jacobian_exp(G, X, basis)
n = manifold_dimension(base_manifold(G))
@test size(J) == (n, n)
if test_mutating
J2 = copy(J)
jacobian_exp!(G, J2, g, X, basis)
jacobian_exp!(G, J2, X, basis)
@test isapprox(J, J2; kwargs...)
end
!ismissing(expected) && (@test isapprox(J, expected; kwargs...))
Expand Down Expand Up @@ -1343,7 +1343,7 @@ function LieGroups.Test.test_lie_group(G::AbstractLieGroup, properties::Dict, ex
end
if (jacobian_exp in functions)
expected = get(expectations, :jacobian_exp, missing)
LieGroups.Test.test_jacobian_exp(G, points[1], vectors[1]; expected = expected)
LieGroups.Test.test_jacobian_exp(G, vectors[1]; expected = expected)
end
#
#
Expand Down
28 changes: 28 additions & 0 deletions src/group_operations/addition_operation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Base.:-(e::Identity{AdditionGroupOperation}, ::Identity{AdditionGroupOperation})
Base.:-(::Identity{AdditionGroupOperation}, g) = -g
Base.:-(g, ::Identity{AdditionGroupOperation}) = g

# Identity{AdditionGroupOperation} is a valid point on a Lie algebra and the same as the zero vector.
Base.:+(e::Identity{AdditionGroupOperation}, ::ManifoldsBase.ZeroVector) = e
Base.:+(::ManifoldsBase.ZeroVector, e::Identity{AdditionGroupOperation}) = e
Base.:-(e::Identity{AdditionGroupOperation}, ::ManifoldsBase.ZeroVector) = e
Base.:-(::ManifoldsBase.ZeroVector, e::Identity{AdditionGroupOperation}) = e

_doc_compose_add = """
compose(G::LieGroup{𝔽,AdditionGroupOperation}, g, h)
compose!(G::LieGroup{𝔽,AdditionGroupOperation}, k, g, h)
Expand Down Expand Up @@ -142,6 +148,28 @@ end
return convert(T, c)
end

_doc_jacobian_exp_add = """
jacobian_exp(G::LieGroup{𝔽,AdditionGroupOperation}, X, ::DefaultLieAlgebraOrthogonalBasis)
jacobian_exp!(G::LieGroup{𝔽,AdditionGroupOperation}, J, X, ::DefaultLieAlgebraOrthogonalBasis)

Compute the Jacobian of the Lie group exponential in a basis of the Lie algebra on a
[`LieGroup`](@ref) with an [`AdditionGroupOperation`](@ref).

Since such a group is Abelian and flat, the differential of the exponential map is the
identity, so ``J = I_n`` for every ``X``.
Comment thread
kellertuer marked this conversation as resolved.
"""

@doc "$(_doc_jacobian_exp_add)"
jacobian_exp(::LieGroup{𝔽, AdditionGroupOperation}, X, basis = DefaultLieAlgebraOrthogonalBasis()) where {𝔽}

@doc "$(_doc_jacobian_exp_add)"
function jacobian_exp!(
::LieGroup{𝔽, AdditionGroupOperation}, J::AbstractMatrix, X, ::DefaultLieAlgebraOrthogonalBasis
) where {𝔽}
copyto!(J, LinearAlgebra.I)
return J
end

_doc_identity_element_add = """
identity_element(G::LieGroup{𝔽,AdditionGroupOperation})
identity_element!(G::LieGroup{𝔽,AdditionGroupOperation}, e)
Expand Down
40 changes: 40 additions & 0 deletions src/groups/product_group.jl
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,46 @@ function ManifoldsBase.exp!(
return h
end

_doc_jacobian_exp_product = raw"""
jacobian_exp(G::ProductLieGroup, X, ::DefaultLieAlgebraOrthogonalBasis)
jacobian_exp!(G::ProductLieGroup, J, X, ::DefaultLieAlgebraOrthogonalBasis)

Compute the Jacobian of the Lie group exponential in a basis of the Lie algebra on a
[`ProductLieGroup`](@ref).

Since on a (direct) product ``G = G_1 × ⋯ × G_n`` the exponential map acts componentwise
and the basis of the [`LieAlgebra`](@ref) is the concatenation of the bases of the factors,
the Jacobian is block-diagonal with the [`jacobian_exp`](@ref) of the factors on its
diagonal,

````math
J = \begin{pmatrix} J_1 & & \\ & \ddots & \\ & & J_n \end{pmatrix},
\qquad J_i = \text{jacobian\_exp}(G_i, X_i).
````
"""

@doc "$(_doc_jacobian_exp_product)"
jacobian_exp(
::LieGroup{𝔽, <:ProductGroupOperation, <:ProductManifold}, X, ::AbstractBasis
) where {𝔽}

@doc "$(_doc_jacobian_exp_product)"
function jacobian_exp!(
PrG::LieGroup{𝔽, Op, M}, J, X, B::DefaultLieAlgebraOrthogonalBasis
) where {𝔽, Op <: ProductGroupOperation, M <: ProductManifold}
PrM = PrG.manifold
dims = map(manifold_dimension, PrM.manifolds)
dim_ranges = ManifoldsBase._get_dim_ranges(dims)
fill!(J, 0)
foreach(
(Gi, Xi, dr) -> jacobian_exp!(Gi, view(J, dr, dr), Xi, B),
map(LieGroup, PrM.manifolds, PrG.op.operations),
submanifold_components(PrM, X),
dim_ranges,
)
return J
end

function get_vector_lie!(
Pr𝔤::LieAlgebra{𝔽, Op, LieGroup{𝔽, Op, M}}, X, c, B::DefaultLieAlgebraOrthogonalBasis
) where {𝔽, Op <: AbstractProductGroupOperation, M <: ProductManifold}
Expand Down
Loading
Loading