Skip to content

Commit 5f3e184

Browse files
committed
expose unit3/rot3 symmetry, abc python bindings for EqF
1 parent 12c9db4 commit 5f3e184

File tree

6 files changed

+493
-0
lines changed

6 files changed

+493
-0
lines changed

gtsam/navigation/EquivariantFilter.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,34 @@ class EquivariantFilter : public ManifoldEKF<M> {
222222
Base::predict(xi_next, Phi, Q_manifold);
223223
}
224224

225+
/**
226+
* @brief Propagate the filter state with explicit Jacobian and lift vector.
227+
*
228+
* This overload is wrapper-friendly: takes a lifted tangent vector
229+
* directly, avoiding templated Lift functors. It uses first-order Euler
230+
* discretization (K = 1).
231+
*
232+
* @param Lambda Lifted tangent vector in the group tangent space.
233+
* @param A Error dynamics matrix (DimM x DimM).
234+
* @param Qc Process noise covariance on the manifold (continuous-time).
235+
* @param dt Time step.
236+
*/
237+
void predictWithJacobianEuler(const Vector& Lambda, const MatrixM& A,
238+
const MatrixM& Qc, double dt) {
239+
TangentG lambda_g;
240+
if constexpr (TangentG::RowsAtCompileTime == Eigen::Dynamic) {
241+
lambda_g.resize(DimG);
242+
}
243+
lambda_g = Lambda;
244+
245+
g_ = traits<G>::Compose(g_, traits<G>::Expmap(lambda_g * dt));
246+
M xi_next = act_on_ref_(g_);
247+
248+
MatrixM Phi = transitionMatrix<1>(A, dt);
249+
CovarianceM Q_manifold = Qc * dt;
250+
Base::predict(xi_next, Phi, Q_manifold);
251+
}
252+
225253
/**
226254
* Measurement update: Corrects the state and covariance using a
227255
* pre-calculated predicted measurement and its Jacobian.

gtsam/navigation/navigation.i

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,51 @@ virtual class InvariantEKF : gtsam::LeftLinearEKF<G> {
651651
void predict(const gtsam::Vector& u, double dt, gtsam::Matrix Q);
652652
};
653653

654+
// ---------------------------------------------------------------------------
655+
// Equivariant Filter (attitude example wrapper)
656+
#include <gtsam/navigation/EquivariantFilter.h>
657+
#include <gtsam_unstable/geometry/EquivariantFilterAttitude.h>
658+
template <M = {gtsam::Unit3}, Symmetry = {gtsam::attitude_example::Symmetry}>
659+
virtual class EquivariantFilter : gtsam::ManifoldEKF<M> {
660+
// Constructors
661+
EquivariantFilter(const M& xi_ref, gtsam::Matrix Sigma);
662+
EquivariantFilter(const M& xi_ref, gtsam::Matrix Sigma,
663+
const Symmetry::Group& X0);
664+
665+
// Accessors
666+
gtsam::Matrix errorCovariance() const;
667+
gtsam::Matrix covariance() const;
668+
Symmetry::Group groupEstimate() const;
669+
670+
// Wrapper-friendly predict
671+
void predictWithJacobianEuler(const gtsam::Vector& Lambda, gtsam::Matrix A,
672+
gtsam::Matrix Qc, double dt);
673+
674+
// Only vector-based measurements are supported in wrapper
675+
void updateWithVector(const gtsam::Vector& prediction, const gtsam::Matrix& H,
676+
const gtsam::Vector& z, const gtsam::Matrix& R);
677+
};
678+
679+
// ---------------------------------------------------------------------------
680+
// ABC Equivariant Filter wrapper (N=1)
681+
#include <gtsam_unstable/geometry/ABCEqFWrapper.h>
682+
namespace abc {
683+
class AbcEquivariantFilter1 {
684+
AbcEquivariantFilter1();
685+
AbcEquivariantFilter1(const gtsam::Matrix& Sigma0);
686+
687+
void predict(const gtsam::Vector& omega, const gtsam::Matrix& inputCovariance,
688+
double dt);
689+
void update(const gtsam::Unit3& y, const gtsam::Unit3& d,
690+
const gtsam::Matrix& R, int cal_idx);
691+
692+
gtsam::Rot3 attitude() const;
693+
gtsam::Vector bias() const;
694+
gtsam::Rot3 calibration(size_t i) const;
695+
gtsam::Matrix errorCovariance() const;
696+
};
697+
} // namespace abc
698+
654699
// Specialized NavState IMU EKF
655700
#include <gtsam/navigation/NavStateImuEKF.h>
656701
class NavStateImuEKF : gtsam::LeftLinearEKF<gtsam::NavState> {
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/**
2+
* @file ABCEqFWrapper.h
3+
* @brief Wrapper-friendly ABC EqF helper class for Python bindings.
4+
*
5+
* This class mirrors the logic in examples/AbcEquivariantFilterExample.cpp
6+
* but exposes a simple, non-templated API for wrapping.
7+
*/
8+
9+
#pragma once
10+
11+
#include <gtsam/base/Matrix.h>
12+
#include <gtsam/base/Vector.h>
13+
#include <gtsam/geometry/Rot3.h>
14+
#include <gtsam/geometry/Unit3.h>
15+
#include <gtsam/navigation/EquivariantFilter.h>
16+
#include <gtsam_unstable/geometry/ABC.h>
17+
18+
namespace gtsam {
19+
namespace abc {
20+
21+
class AbcEquivariantFilter1 {
22+
public:
23+
static constexpr size_t N = 1;
24+
using State1 = State<N>;
25+
using Group1 = Group<N>;
26+
using Symmetry1 = Symmetry<N>;
27+
using EqFilter1 = gtsam::EquivariantFilter<State1, Symmetry1>;
28+
29+
AbcEquivariantFilter1() : AbcEquivariantFilter1(Matrix::Identity(6 + 3 * N, 6 + 3 * N)) {}
30+
31+
explicit AbcEquivariantFilter1(const Matrix& Sigma0)
32+
: filter_(State1::identity(), Sigma0) {}
33+
34+
/// Propagate filter using the explicit matrices API from ABC.
35+
void predict(const Vector3& omega, const Matrix6& inputCovariance, double dt) {
36+
const Matrix Q = inputProcessNoise<N>(inputCovariance);
37+
const Vector6 u = toInputVector(omega);
38+
const Lift<N> lift_u(u);
39+
const typename InputAction<N>::Orbit psi_u(u);
40+
41+
const Group1 X_hat = filter_.groupEstimate();
42+
const Matrix A = stateMatrixA<N>(psi_u, X_hat);
43+
const Matrix B = inputMatrixB<N>(X_hat);
44+
const Matrix Qc = B * Q * B.transpose();
45+
46+
filter_.predictWithJacobian<2>(lift_u, A, Qc, dt);
47+
}
48+
49+
/// Update filter with a direction measurement.
50+
void update(const Unit3& y, const Unit3& d, const Matrix3& R, int cal_idx) {
51+
const Innovation<N> innovation(y, d, cal_idx);
52+
const Group1 X_hat = filter_.groupEstimate();
53+
const Matrix3 D = outputMatrixD<N>(X_hat, cal_idx);
54+
const Matrix3 R_adjusted = D * R * D.transpose();
55+
filter_.update<Vector3>(innovation, Z_3x1, R_adjusted);
56+
}
57+
58+
/// Accessors for current estimate.
59+
Rot3 attitude() const { return filter_.state().R; }
60+
Vector3 bias() const { return filter_.state().b; }
61+
Rot3 calibration(size_t i) const { return filter_.state().S[i]; }
62+
Matrix errorCovariance() const { return filter_.errorCovariance(); }
63+
64+
private:
65+
EqFilter1 filter_;
66+
};
67+
68+
} // namespace abc
69+
} // namespace gtsam
70+
71+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @file EquivariantFilterAttitude.h
3+
* @brief Attitude-only EqF helper types for Python bindings.
4+
*
5+
* Provides the symmetry action used by the Unit3/Rot3 attitude example in
6+
* tests/testEquivariantFilter.cpp so it can be instantiated in wrappers.
7+
*/
8+
9+
#pragma once
10+
11+
#include <gtsam/base/GroupAction.h>
12+
#include <gtsam/base/OptionalJacobian.h>
13+
#include <gtsam/geometry/Rot3.h>
14+
#include <gtsam/geometry/Unit3.h>
15+
#include <gtsam/navigation/EquivariantFilter.h>
16+
17+
namespace gtsam {
18+
namespace attitude_example {
19+
20+
using M = Unit3;
21+
using G = Rot3;
22+
23+
/**
24+
* Symmetry: right group action on Unit3.
25+
* φ_η(Q) = Q^T η.
26+
*/
27+
struct Symmetry : public GroupAction<Symmetry, G, M> {
28+
static constexpr ActionType type = ActionType::Right;
29+
30+
M operator()(const M& eta, const G& Q,
31+
OptionalJacobian<2, 2> H_eta = {},
32+
OptionalJacobian<2, 3> H_Q = {}) const {
33+
return Q.unrotate(eta, H_Q, H_eta);
34+
}
35+
};
36+
37+
} // namespace attitude_example
38+
} // namespace gtsam
39+
40+

0 commit comments

Comments
 (0)