Skip to content

Commit 0eaa797

Browse files
authored
Merge pull request #2416 from borglab/feature/ExtendedPose
Extended Pose SE_k(3)
2 parents 1df996f + cc0141e commit 0eaa797

26 files changed

+2313
-865
lines changed

.github/copilot-instructions.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,16 @@ For reviewing PRs:
44
* Use meaningful variable names, e.g. `measurement` not `msm`, avoid abbreviations.
55
* Flag overly complex or long/functions: break up in smaller functions
66
* On Windows it is necessary to explicitly export all functions from the library which should be externally accessible. To do this, include the macro `GTSAM_EXPORT` in your class or function definition.
7+
* When adding or modifying public classes/methods, always review and follow `Using-GTSAM-EXPORT.md` before finalizing changes, including template specialization/export rules.
78
* If we add a C++ function to a `.i` file to expose it to the wrapper, we must ensure that the parameter names match exactly between the declaration in the header file and the declaration in the `.i`. Similarly, if we change any parameter names in a wrapped function in a header file, or change any parameter names in a `.i` file, we must change the corresponding function in the other file to reflect those changes.
89
* Classes are Uppercase, methods and functions lowerMixedCase.
910
* Public fields in structs keep plain names (no trailing underscore).
1011
* Apart from those naming conventions, we adopt Google C++ style.
12+
* Notebooks in `*/doc/*.ipynb` and `*/examples/*.ipynb` should follow the standard preamble:
13+
1) title/introduction markdown cell,
14+
2) copyright markdown cell tagged `remove-cell`,
15+
3) Colab badge markdown cell,
16+
4) Colab install code cell tagged `remove-cell`,
17+
5) imports/setup code cell.
18+
Use the same `remove-cell` tagging convention as existing notebooks so docs build and Colab behavior stay consistent.
1119
* After any code change, always run relevant tests via `make -j6 testXXX.run` in the build folder $WORKSPACE/build. If in VS code, ask for escalated permissions if needed.

gtsam/base/Lie.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ struct LieGroupTraits : public GetDimensionImpl<Class, Class::dimension> {
209209
// GetDimensionImpl handles resolving this to a static value or providing GetDimension(obj).
210210
inline constexpr static auto dimension = Class::dimension;
211211
using TangentVector = Eigen::Matrix<double, dimension, 1>;
212+
using Jacobian = Eigen::Matrix<double, dimension, dimension>;
212213
using ChartJacobian = OptionalJacobian<dimension, dimension>;
213214

214215
static TangentVector Local(const Class& origin, const Class& other,

gtsam/base/MatrixLieGroup.h

Lines changed: 180 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#pragma once
2020

2121
#include <gtsam/base/Lie.h>
22+
#include <array>
2223
#include <type_traits>
2324

2425
namespace gtsam {
@@ -56,6 +57,9 @@ namespace gtsam {
5657
using Jacobian = typename Base::Jacobian;
5758
using TangentVector = typename Base::TangentVector;
5859

60+
/// @name Matrix Lie Group
61+
/// @{
62+
5963
/**
6064
* Vectorize the matrix representation of a Lie group element.
6165
* The derivative `H` is the `(N*N) x D` Jacobian of this vectorization map.
@@ -130,7 +134,149 @@ namespace gtsam {
130134
return adj;
131135
}
132136

137+
/**
138+
* Adjoint action on a tangent vector.
139+
*
140+
* Returns Ad_g * xi with optional Jacobians with respect to g and xi.
141+
*/
142+
TangentVector Adjoint(const TangentVector& xi,
143+
ChartJacobian H_this = {},
144+
ChartJacobian H_xi = {}) const {
145+
const auto& m = static_cast<const Class&>(*this);
146+
const Jacobian Ad = m.AdjointMap();
147+
if (H_this) *H_this = -Ad * Class::adjointMap(xi);
148+
if (H_xi) *H_xi = Ad;
149+
return Ad * xi;
150+
}
151+
152+
/**
153+
* Dual Adjoint action on a tangent covector.
154+
*
155+
* Returns Ad_g^T * x with optional Jacobians with respect to g and x.
156+
*/
157+
TangentVector AdjointTranspose(const TangentVector& x,
158+
ChartJacobian H_this = {},
159+
ChartJacobian H_x = {}) const {
160+
const auto& m = static_cast<const Class&>(*this);
161+
const Jacobian Ad = m.AdjointMap();
162+
const TangentVector AdTx = Ad.transpose() * x;
163+
164+
if (H_this) {
165+
const Eigen::Index d = tangentDim(&m, nullptr);
166+
setZeroJacobian(H_this, d);
167+
if constexpr (D == Eigen::Dynamic) {
168+
for (Eigen::Index i = 0; i < d; ++i) {
169+
H_this->col(i) =
170+
Class::adjointMap(TangentVector::Unit(d, i)).transpose() * AdTx;
171+
}
172+
} else {
173+
const auto& basis = adjointBasis();
174+
for (Eigen::Index i = 0; i < d; ++i) {
175+
H_this->col(i) = basis[static_cast<size_t>(i)].transpose() * AdTx;
176+
}
177+
}
178+
}
179+
180+
if (H_x) *H_x = Ad.transpose();
181+
return AdTx;
182+
}
183+
184+
/**
185+
* Lie algebra adjoint map ad_xi, with optional specialization in derived
186+
* classes.
187+
*/
188+
static Jacobian adjointMap(const TangentVector& xi) {
189+
const Eigen::Index d = tangentDim(nullptr, &xi);
190+
Jacobian ad;
191+
if constexpr (D == Eigen::Dynamic) {
192+
ad.setZero(d, d);
193+
} else {
194+
ad.setZero();
195+
}
196+
const auto Xi = Class::Hat(xi);
197+
for (Eigen::Index i = 0; i < d; ++i) {
198+
const auto Ei = Class::Hat(TangentVector::Unit(d, i));
199+
ad.col(i) = Class::Vee(Xi * Ei - Ei * Xi);
200+
}
201+
return ad;
202+
}
203+
204+
/**
205+
* Lie algebra action ad_xi(y), with optional Jacobians.
206+
*/
207+
static TangentVector adjoint(const TangentVector& xi,
208+
const TangentVector& y, ChartJacobian Hxi = {},
209+
ChartJacobian H_y = {}) {
210+
const Jacobian ad_xi = Class::adjointMap(xi);
211+
if (Hxi) *Hxi = -Class::adjointMap(y);
212+
if (H_y) *H_y = ad_xi;
213+
return ad_xi * y;
214+
}
215+
216+
/**
217+
* Dual Lie algebra action ad_xi^T(y), with optional Jacobians.
218+
*/
219+
static TangentVector adjointTranspose(const TangentVector& xi,
220+
const TangentVector& y,
221+
ChartJacobian Hxi = {},
222+
ChartJacobian H_y = {}) {
223+
const Jacobian adT_xi = Class::adjointMap(xi).transpose();
224+
if (Hxi) {
225+
const Eigen::Index d = tangentDim(nullptr, &xi);
226+
setZeroJacobian(Hxi, d);
227+
if constexpr (D == Eigen::Dynamic) {
228+
for (Eigen::Index i = 0; i < d; ++i) {
229+
Hxi->col(i) =
230+
Class::adjointMap(TangentVector::Unit(d, i)).transpose() * y;
231+
}
232+
} else {
233+
const auto& basis = adjointBasis();
234+
for (Eigen::Index i = 0; i < d; ++i) {
235+
Hxi->col(i) = basis[static_cast<size_t>(i)].transpose() * y;
236+
}
237+
}
238+
}
239+
if (H_y) *H_y = adT_xi;
240+
return adT_xi * y;
241+
}
242+
243+
/// @}
244+
133245
private:
246+
static Eigen::Index tangentDim(const Class* m, const TangentVector* xi) {
247+
if constexpr (D == Eigen::Dynamic) {
248+
return m ? static_cast<Eigen::Index>(traits<Class>::GetDimension(*m))
249+
: static_cast<Eigen::Index>(xi->size());
250+
} else {
251+
(void)m;
252+
(void)xi;
253+
return D;
254+
}
255+
}
256+
257+
static void setZeroJacobian(ChartJacobian H, Eigen::Index d) {
258+
if constexpr (D == Eigen::Dynamic) {
259+
H->setZero(d, d);
260+
} else {
261+
(void)d;
262+
H->setZero();
263+
}
264+
}
265+
266+
/// Basis maps ad_{e_i}, cached for fixed-size groups.
267+
template <int DD = D, typename std::enable_if_t<DD != Eigen::Dynamic, int> = 0>
268+
static const std::array<Jacobian, DD>& adjointBasis() {
269+
static const std::array<Jacobian, DD> basis = []() {
270+
std::array<Jacobian, DD> B{};
271+
for (int i = 0; i < DD; ++i) {
272+
B[static_cast<size_t>(i)] =
273+
Class::adjointMap(TangentVector::Unit(DD, i));
274+
}
275+
return B;
276+
}();
277+
return basis;
278+
}
279+
134280
/// Pre-compute and store vectorized generators for fixed-size groups.
135281
inline static const Eigen::Matrix<double, internal::product(N, N), D>&
136282
VectorizedGenerators() {
@@ -142,10 +288,12 @@ namespace gtsam {
142288

143289
namespace internal {
144290

145-
/// Adds LieAlgebra, Hat, Vee, and Vec to LieGroupTraits
291+
/// Adds MatrixLieGroup methods to LieGroupTraits
146292
template <class Class, int N> struct MatrixLieGroupTraits : LieGroupTraits<Class> {
147293
using LieAlgebra = typename Class::LieAlgebra;
148294
using TangentVector = typename LieGroupTraits<Class>::TangentVector;
295+
using Jacobian = typename LieGroupTraits<Class>::Jacobian;
296+
using ChartJacobian = typename LieGroupTraits<Class>::ChartJacobian;
149297

150298
static LieAlgebra Hat(const TangentVector& v) {
151299
return Class::Hat(v);
@@ -162,6 +310,37 @@ namespace gtsam {
162310
LieGroupTraits<Class>::dimension> H = {}) {
163311
return m.vec(H);
164312
}
313+
314+
static TangentVector AdjointTranspose(const Class& m,
315+
const TangentVector& x,
316+
ChartJacobian Hm = {},
317+
ChartJacobian Hx = {}) {
318+
return m.AdjointTranspose(x, Hm, Hx);
319+
}
320+
321+
static TangentVector Adjoint(const Class& m, const TangentVector& x,
322+
ChartJacobian Hm = {},
323+
ChartJacobian Hx = {}) {
324+
return m.Adjoint(x, Hm, Hx);
325+
}
326+
327+
static Jacobian adjointMap(const TangentVector& xi) {
328+
return Class::adjointMap(xi);
329+
}
330+
331+
static TangentVector adjoint(const TangentVector& xi,
332+
const TangentVector& y,
333+
ChartJacobian Hxi = {},
334+
ChartJacobian H_y = {}) {
335+
return Class::adjoint(xi, y, Hxi, H_y);
336+
}
337+
338+
static TangentVector adjointTranspose(const TangentVector& xi,
339+
const TangentVector& y,
340+
ChartJacobian Hxi = {},
341+
ChartJacobian H_y = {}) {
342+
return Class::adjointTranspose(xi, y, Hxi, H_y);
343+
}
165344
};
166345

167346
/// Both LieGroupTraits and Testable

gtsam/base/doc/MatrixLieGroup.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@ You must implement everything from the `LieGroup.md` guide, plus the following:
2929

3030
By inheriting from `gtsam::MatrixLieGroup`, you get:
3131

32-
* **A default `AdjointMap()` implementation**: This generic version works by repeatedly calling your `Hat` and `Vee` methods. While it is correct, it is often slow. For performance-critical applications, you should still provide a faster, closed-form `AdjointMap()` implementation in your class, which will override the default.
32+
* **Default group adjoint methods**: `AdjointMap()`, `Adjoint(xi)`, and `AdjointTranspose(x)` are provided generically, including Jacobians for the latter two.
33+
* **Default Lie algebra methods**: `adjointMap(xi)`, `adjoint(xi, y)`, and `adjointTranspose(xi, y)` are provided generically, including Jacobians for the latter two.
3334
* **A `vec()` method**: This vectorizes the `N x N` matrix representation of your group element into an `(N*N) x 1` vector.
3435

36+
**Performance Note:** The generic implementation of `AdjointMap()` is correct but may be slower because it is derived via the `Hat`/`Vee` mappings. If a closed-form expression for `AdjointMap()` is available for your group, consider overriding the default implementation for better performance.
37+
3538
### 4. Traits and Concept Checking
3639

3740
Finally, the traits specialization in your header file must be updated to reflect that your class is now a `MatrixLieGroup`.

0 commit comments

Comments
 (0)