@@ -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
0 commit comments