Skip to content

Commit e155fe6

Browse files
authored
Merge pull request #2373 from borglab/feature/reset
EKF Reset
2 parents 3f81b4a + c624518 commit e155fe6

File tree

12 files changed

+2949
-72678
lines changed

12 files changed

+2949
-72678
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ doc/#*.lyx#
3131
.venv/
3232
conductor/
3333
.ccache/
34+
gtsam_unstable/timing/data/

gtsam/geometry/tests/testGal3.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ TEST(Gal3, ChartDerivatives) {
8383
CHECK_CHART_DERIVATIVES(kTestGal3_Lie1, kTestGal3_Lie2);
8484
}
8585

86+
/* ************************************************************************* */
87+
TEST(Gal3, RetractJacobians) {
88+
const Gal3 g = kTestGal3_Lie1;
89+
const Vector10 v = (Vector10() << 0.01, -0.02, 0.03, 0.1, -0.05, 0.2, 0.4,
90+
-0.3, 0.2, 0.01)
91+
.finished();
92+
93+
Matrix actualH1, actualH2;
94+
traits<Gal3>::Retract(g, v, &actualH1, &actualH2);
95+
96+
std::function<Gal3(const Gal3&, const Vector10&)> retract_proxy =
97+
[](const Gal3& g_, const Vector10& v_) { return g_.retract(v_); };
98+
Matrix expectedH1 =
99+
numericalDerivative21<Gal3, Gal3, Vector10>(retract_proxy, g, v);
100+
Matrix expectedH2 =
101+
numericalDerivative22<Gal3, Gal3, Vector10>(retract_proxy, g, v);
102+
103+
EXPECT(assert_equal(expectedH1, actualH1, 1e-5));
104+
EXPECT(assert_equal(expectedH2, actualH2, 1e-5));
105+
}
106+
86107

87108
/* ************************************************************************* */
88109
TEST(Gal3, StaticConstructorsValue) {

gtsam/geometry/tests/testPose2.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,20 @@ TEST(Pose2, retract) {
7575
EXPECT(assert_equal(expected, actual, 1e-5));
7676
}
7777

78+
/* ************************************************************************* */
79+
TEST(Pose2, retractJacobian) {
80+
Pose2 pose(M_PI / 2.0, Point2(1, 2));
81+
Vector3 v(0.01, -0.015, 0.99);
82+
83+
Matrix3 actualH;
84+
traits<Pose2>::Retract(pose, v, {}, &actualH);
85+
86+
auto retract_from_pose = [&](const Vector3& delta) { return pose.retract(delta); };
87+
Matrix3 expectedH = numericalDerivative11<Pose2, Vector3, 3>(retract_from_pose, v, 1e-6);
88+
89+
EXPECT(assert_equal(expectedH, actualH, 1e-5));
90+
}
91+
7892
/* ************************************************************************* */
7993
TEST(Pose2, expmap) {
8094
Pose2 pose(M_PI/2.0, Point2(1, 2));
@@ -997,4 +1011,3 @@ int main() {
9971011
return TestRegistry::runAllTests(tr);
9981012
}
9991013
/* ************************************************************************* */
1000-

gtsam/navigation/ManifoldEKF.h

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ class ManifoldEKF {
135135
return P_ * H.transpose() * S.inverse();
136136
}
137137

138-
/// Joseph-form covariance update using a precomputed gain.
138+
/// Joseph-form covariance update in the current tangent space using a
139+
/// precomputed gain.
139140
template <typename GainMatrix, typename HMatrix, typename RMatrix>
140141
void JosephUpdate(const GainMatrix& K, const HMatrix& H, const RMatrix& R) {
141142
Jacobian I_KH = I_ - K * H;
@@ -151,14 +152,17 @@ class ManifoldEKF {
151152
* @param H Jacobian of the measurement function h.
152153
* @param z Observed measurement.
153154
* @param R Measurement noise covariance.
155+
* @param performReset If true (default), performs a reset (transport) after
156+
* update; otherwise, just retracts the state.
154157
*/
155158
template <typename Measurement>
156159
void update(
157160
const Measurement& prediction,
158161
const Eigen::Matrix<double, traits<Measurement>::dimension, Dim>& H,
159162
const Measurement& z,
160163
const Eigen::Matrix<double, traits<Measurement>::dimension,
161-
traits<Measurement>::dimension>& R) {
164+
traits<Measurement>::dimension>& R,
165+
bool performReset = true) {
162166
static constexpr int MeasDim = traits<Measurement>::dimension;
163167

164168
// Innovation: y = h(x_pred) - z. In tangent space: local(z, h(x_pred))
@@ -174,11 +178,14 @@ class ManifoldEKF {
174178
const TangentVector delta_xi =
175179
-K * innovation; // delta_xi is Dim x 1 (or n_ x 1 if dynamic)
176180

177-
// Update state using retract: X_new = retract(X_old, delta_xi)
178-
X_ = traits<M>::Retract(X_, delta_xi);
179-
180-
// Update covariance using Joseph form
181+
// --- Update covariance in the tangent space at the current state
181182
this->JosephUpdate(K, H, R);
183+
184+
// Update state using retract/ transport or just retract
185+
if (performReset)
186+
reset(delta_xi);
187+
else
188+
X_ = traits<M>::Retract(X_, delta_xi);
182189
}
183190

184191
/**
@@ -190,11 +197,13 @@ class ManifoldEKF {
190197
* @param h Measurement model function.
191198
* @param z Observed measurement.
192199
* @param R Measurement noise covariance.
200+
* @param performReset If true (default), transport covariance after retract.
193201
*/
194202
template <typename Measurement, typename MeasurementFunction>
195203
void update(MeasurementFunction&& h, const Measurement& z,
196204
const Eigen::Matrix<double, traits<Measurement>::dimension,
197-
traits<Measurement>::dimension>& R) {
205+
traits<Measurement>::dimension>& R,
206+
bool performReset = true) {
198207
static_assert(IsManifold<Measurement>::value,
199208
"Template parameter Measurement must be a GTSAM Manifold.");
200209

@@ -203,17 +212,45 @@ class ManifoldEKF {
203212
Measurement prediction = h(X_, H);
204213

205214
// Call the other update function
206-
update<Measurement>(prediction, H, z, R);
215+
update<Measurement>(prediction, H, z, R, performReset);
207216
}
208217

209-
/// Convenience bridge for wrappers: vector measurement update calling
210-
/// update<Vector>. This overload exists to avoid templates in wrappers. It
211-
/// validates sizes and forwards to the templated update with Measurement =
212-
/// gtsam::Vector (dynamic size).
218+
/**
219+
* Convenience bridge for wrappers: vector measurement update calling
220+
* update<Vector>. This overload exists to avoid templates in wrappers. It
221+
* validates sizes and forwards to the templated update with Measurement =
222+
* gtsam::Vector (dynamic size).
223+
* @param prediction Predicted measurement vector.
224+
* @param H Measurement Jacobian matrix.
225+
* @param z Observed measurement vector.
226+
* @param R Measurement noise covariance matrix.
227+
* @param performReset If true (default), transport covariance after retract.
228+
*/
213229
void updateWithVector(const gtsam::Vector& prediction, const Matrix& H,
214-
const gtsam::Vector& z, const Matrix& R) {
230+
const gtsam::Vector& z, const Matrix& R,
231+
bool performReset = true) {
215232
validateInputs(prediction, H, z, R);
216-
update<Vector>(prediction, H, z, R);
233+
update<Vector>(prediction, H, z, R, performReset);
234+
}
235+
236+
/**
237+
* Reset step: retract the state by a tangent perturbation and, if available,
238+
* transport the covariance from the old tangent space to the new tangent
239+
* space.
240+
*
241+
* If the retract supports a Jacobian argument, we compute B and update
242+
* P <- B P B^T. Otherwise, we leave the covariance unchanged.
243+
*/
244+
void reset(const TangentVector& eta) {
245+
if constexpr (HasRetractJacobian<M>::value) {
246+
Jacobian B;
247+
if constexpr (Dim == Eigen::Dynamic) B.resize(n_, n_);
248+
X_ = traits<M>::Retract(X_, eta, &B);
249+
P_ = B * P_ * B.transpose();
250+
} else {
251+
X_ = traits<M>::Retract(X_, eta);
252+
// Covariance unchanged when Jacobian is not available.
253+
}
217254
}
218255

219256
protected:
@@ -243,6 +280,17 @@ class ManifoldEKF {
243280
Covariance P_; ///< Covariance (Eigen::Matrix<double, Dim, Dim>).
244281
Jacobian I_; ///< Identity matrix sized to the state dimension.
245282
int n_; ///< Runtime tangent space dimension of M.
283+
284+
private:
285+
// Detection helper: check if traits<T>::Retract(x, v, Jacobian*) is valid.
286+
template <typename T, typename = void>
287+
struct HasRetractJacobian : std::false_type {};
288+
template <typename T>
289+
struct HasRetractJacobian<
290+
T, std::void_t<decltype(traits<T>::Retract(
291+
std::declval<const T&>(),
292+
std::declval<const typename traits<T>::TangentVector&>(),
293+
(Jacobian*)nullptr))>> : std::true_type {};
246294
};
247295

248296
} // namespace gtsam

gtsam/navigation/navigation.i

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ virtual class Scenario {
551551
gtsam::Vector acceleration_n(double t) const;
552552
gtsam::Rot3 rotation(double t) const;
553553
gtsam::NavState navState(double t) const;
554+
gtsam::Gal3 gal3(double t) const;
554555
gtsam::Vector velocity_b(double t) const;
555556
gtsam::Vector acceleration_b(double t) const;
556557
};
@@ -619,7 +620,7 @@ virtual class ManifoldEKF {
619620

620621
// Only vector-based measurements are supported in wrapper
621622
void updateWithVector(const gtsam::Vector& prediction, const gtsam::Matrix& H,
622-
const gtsam::Vector& z, const gtsam::Matrix& R);
623+
const gtsam::Vector& z, const gtsam::Matrix& R, bool performReset = true);
623624
};
624625

625626
#include <gtsam/navigation/LieGroupEKF.h>

gtsam/navigation/tests/testInvariantEKF.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ TEST(IEKF_Pose2, PredictUpdateSequence) {
6565
EXPECT(assert_equal(P1_expected, ekf.covariance(), 1e-9));
6666

6767
// --- First Update ---
68-
ekf.update(h_gps, z1, R);
68+
ekf.update(h_gps, z1, R, false);
6969

7070
// Calculate expected state and covariance (manual Kalman steps)
7171
Matrix H1; // H = dh/dlocal(X) -> 2x3
@@ -97,7 +97,7 @@ TEST(IEKF_Pose2, PredictUpdateSequence) {
9797
EXPECT(assert_equal(P2_expected, ekf.covariance(), 1e-9));
9898

9999
// --- Second Update ---
100-
ekf.update(h_gps, z2, R);
100+
ekf.update(h_gps, z2, R, false);
101101

102102
// Calculate expected state and covariance (manual Kalman steps)
103103
Matrix H2; // 2x3

0 commit comments

Comments
 (0)