Skip to content

Fix IMU covariance whitening with nonzero Coriolis - #2806

Merged
dellaert merged 3 commits into
developfrom
codex/galilean-rotating-covariance
Sep 18, 2026
Merged

dellaert merged 3 commits into
developfrom
codex/galilean-rotating-covariance

Conversation

@dellaert

Copy link
Copy Markdown
Member

With nonzero omegaCoriolis, IMU prediction already converts transported velocity back to physical velocity, but the residual covariance omitted the corresponding conversion into the body-frame error coordinates. This PR applies that conversion to ordinary and Combined IMU-factor whitening across all four preintegration backends.

Provenance and behavior

This omission was found while auditing the rotating-Earth covariance/NEES path following #2774. The rotating predictor uses v_bar = v + omega × p, then projects back to physical v. Covariance previously stopped before that projection. The missing Jacobian block is H(v,p) = -Rᵀ[omega]×R: it both expresses the navigation-frame rotation rate in the endpoint body frame and carries position uncertainty into physical velocity uncertainty. This is more than a rotation of the covariance axes.

Apply H P Hᵀ after the existing backend-chart conversion, retaining the Combined factor's bias-sign convention and navigation–bias cross-covariances. Raw preintegration covariance, mean prediction, and nonlinear residuals are unchanged. When omegaCoriolis is unset or zero, the additional conversion is identity.

The new residualCovarianceAt(predictedAttitude) accessor and factor constructor overloads let callers freeze whitening at their nominal endpoint attitude. With nonzero omegaCoriolis, the recommended argument is pim.predict(nominalState_i, nominalBias_i).attitude(). Existing constructors remain available and use prediction from identity at biasHat(); this fallback can give different whitening for a differently oriented initial state. Whitening stays fixed during optimization. The shared interface exposes the accessor and constructors in Python and MATLAB, and the NEES helper calls the wrapped C++ accessor directly.

Evidence

RotatingMeasurementSensitivity independently differentiates the complete integrate/predict/Logmap path with respect to each accelerometer and gyroscope sample, then propagates the sample-noise covariance through those numerical derivatives. Its reference does not use the covariance conversion or its analytic Jacobian. It covers all four backends, zero/Earth/exaggerated frame rates, and identity/rotated initial states, and explicitly checks that the former covariance fails the same nonzero-rate comparison.

An additional before/after probe uses only the existing residualCovariance() API. The same source was compiled against the original headers at d56360ce6 and the fixed headers, holding integration and prediction code constant. Relative Frobenius covariance error against the numerical reference was:

Backend Before: Earth rate Before: stress rate After: maximum over all three rates
Manifold 1.389e-5 8.148e-2 5.220e-10
Tangent 1.388e-5 8.146e-2 7.490e-10
Lie group 1.389e-5 8.148e-2 5.220e-10
Galilean 1.131e-5 7.151e-2 9.851e-10

The rates have norms 0, 7.295e-5, and 0.539 rad/s; the last deliberately amplifies the defect. Zero-rate results are identical before and after. These are deterministic covariance discrepancies, not Monte Carlo NEES improvements. They establish the omitted first-order transformation directly.

The Galilean regression additionally checks the inverse-lift Jacobian numerically, full 15D cross-covariances, covariance symmetry/positive semidefiniteness, fixed whitening, and factor Jacobians at finite residuals.

Experiment context: NEES notebook and Logmap results report.

Validation

  • testGalileanImuFactor, testImuFactorCovariance, testImuFactor, and testCombinedImuFactor pass on the updated develop base.
  • Python source-tree suite against the rebuilt wrapper: 553 passed, 20 skipped; focused IMU wrapper tests: 12 passed, 33 subtests passed. The build-directory python-test target encountered one stale, deleted-source MatrixWeightedLocalizationExample test referring to an absent dataset; running the current source tests passes.
  • MATLAB wrapper rebuilt; testRotatingImuCovariance passes actual accessor and constructor/whitening calls, including unset, zero, and nonzero omegaCoriolis.
  • Reexecuted the 3,000-trial rotating-Earth notebook section and refreshed its table/plot and Markdown report. Physical RMS errors are unchanged; specified-rate mean NEES rounds from 14.012 to 14.011 for Manifold/Tangent/Lie group, while Galilean remains 9.090. Other experiments were not rerun.
  • git diff --check passes.

@dellaert
dellaert requested a review from scottiyio September 18, 2026 00:06
Only predict the nominal endpoint attitude when nonzero Coriolis requires it, and guard parameter access in both ordinary and Combined covariance conversions. Preserve gravity-factor constructor validation for parameterless PIMs.

Add covariance regression coverage across all four backends and null-parameter checks through the Python wrapper.
@dellaert
dellaert merged commit 4a749ef into develop Sep 18, 2026
11 of 14 checks passed
@dellaert
dellaert deleted the codex/galilean-rotating-covariance branch September 18, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant