Skip to content

Commit 505f12c

Browse files
authored
Merge pull request #2261 from borglab/feature/exactIMU
Exact zero-order hold IMU integration
2 parents d41badf + 9d3464a commit 505f12c

File tree

7 files changed

+1898
-52312
lines changed

7 files changed

+1898
-52312
lines changed

gtsam/navigation/NavStateImuEKF.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,16 @@ NavStateImuEKF::NavStateImuEKF(const NavState& X0, const Covariance& P0,
3333
Q_.template block<3, 3>(6, 6) = p->accelerometerCovariance;
3434
}
3535

36-
NavState NavStateImuEKF::Dynamics(const Vector3& n_gravity, const NavState& X,
36+
NavState NavStateImuEKF::IMU(const Vector3& omega_b, const Vector3& f_b,
37+
double dt) {
38+
const Vector3 phi_b = omega_b * dt;
39+
const so3::DexpFunctor local(phi_b);
40+
const Matrix3 J_l = local.Jacobian().left();
41+
const Matrix3 N_l = local.Gamma().left();
42+
return {Rot3::Expmap(phi_b), N_l * f_b * dt * dt, J_l * f_b * dt};
43+
}
44+
45+
NavState NavStateImuEKF::Dynamics(const Vector3& g_n, const NavState& X,
3746
const Vector3& omega_b, const Vector3& f_b,
3847
double dt, OptionalJacobian<9, 9> A) {
3948
if (dt <= 0.0) {
@@ -42,7 +51,7 @@ NavState NavStateImuEKF::Dynamics(const Vector3& n_gravity, const NavState& X,
4251
}
4352

4453
// Calculate W, phi, and U
45-
const NavState W = Gravity(n_gravity, dt);
54+
const NavState W = Gravity(g_n, dt);
4655
NavState::AutonomousFlow phi{dt}; // Φ: velocity acts on position
4756
const NavState U = IMU(omega_b, f_b, dt);
4857

gtsam/navigation/NavStateImuEKF.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,13 @@ class GTSAM_EXPORT NavStateImuEKF : public LeftLinearEKF<NavState> {
4444
const std::shared_ptr<PreintegrationParams>& params);
4545

4646
/// Calculate W (gravity-only left composition, world-frame increments)
47-
static NavState Gravity(const Vector3& n_gravity, double dt) {
48-
return {Rot3(), n_gravity * (0.5 * dt * dt), n_gravity * dt};
47+
static NavState Gravity(const Vector3& g_n, double dt) {
48+
return {Rot3(), g_n * (0.5 * dt * dt), g_n * dt};
4949
}
5050

5151
/// Calculate U from raw IMU (no gravity): body-frame increments
52-
static NavState IMU(const Vector3& omega_b, const Vector3& f_b, double dt) {
53-
return {Rot3::Expmap(omega_b * dt), f_b * (0.5 * dt * dt), f_b * dt};
54-
}
52+
/// We do an explicit closed-form integration based on SO3(3) kernels
53+
static NavState IMU(const Vector3& omega_b, const Vector3& f_b, double dt);
5554

5655
/**
5756
* @brief Compute the dynamics of the system.
@@ -64,15 +63,15 @@ class GTSAM_EXPORT NavStateImuEKF : public LeftLinearEKF<NavState> {
6463
* where W, \phi, and U are the gravity, (autonomous) position update, and
6564
* IMU increment functions, respectively.
6665
*
67-
* @param n_gravity Gravity vector in the navigation frame.
66+
* @param g_n Gravity vector in the navigation frame.
6867
* @param X Current NavState.
6968
* @param omega_b Body angular velocity measurement (rad/s).
7069
* @param f_b Body specific force measurement (m/s^2).
7170
* @param dt Time step in seconds.
7271
* @param A Optional Jacobian of the dynamics with respect to the state.
7372
* @return The next NavState after applying the dynamics.
7473
*/
75-
static NavState Dynamics(const Vector3& n_gravity, const NavState& X,
74+
static NavState Dynamics(const Vector3& g_n, const NavState& X,
7675
const Vector3& omega_b, const Vector3& f_b,
7776
double dt, OptionalJacobian<9, 9> A = {});
7877

gtsam/navigation/doc/NavStateImuEKF.ipynb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
{
2626
"cell_type": "code",
27-
"execution_count": 1,
27+
"execution_count": null,
2828
"id": "9258793f",
2929
"metadata": {
3030
"tags": [
@@ -33,11 +33,12 @@
3333
},
3434
"outputs": [],
3535
"source": [
36-
"# Install gtsam-develop if not installed\n",
36+
"# Install GTSAM and Plotly from pip if running in Google Colab\n",
3737
"try:\n",
38-
" import gtsam\n",
38+
" import google.colab\n",
39+
" %pip install --quiet gtsam-develop \n",
3940
"except ImportError:\n",
40-
" %pip install --quiet gtsam-develop"
41+
" pass # Not in Colab"
4142
]
4243
},
4344
{
@@ -256,7 +257,7 @@
256257
"\n",
257258
"- [NavStateImuEKF.cpp](https://github.com/borglab/gtsam/blob/develop/gtsam/navigation/NavStateImuEKF.cpp)\n",
258259
"\n",
259-
"- [LieGroupEKF.h](https://github.com/borglab/gtsam/blob/develop/gtsam/navigation/LieGroupEKF.h)\n",
260+
"- [LeftLinearEKF.h](https://github.com/borglab/gtsam/blob/develop/gtsam/navigation/LeftLinearEKF.h)\n",
260261
"\n",
261262
"- [ManifoldEKF.h](https://github.com/borglab/gtsam/blob/develop/gtsam/navigation/ManifoldEKF.h)"
262263
]

gtsam/navigation/navigation.i

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ class ScenarioRunner {
594594

595595
// ---------------------------------------------------------------------------
596596
// EKF classes
597-
597+
#include <gtsam/geometry/Gal3.h>
598598
#include <gtsam/navigation/ManifoldEKF.h>
599599
template <M = {gtsam::Unit3, gtsam::Rot3, gtsam::Pose2, gtsam::Pose3, gtsam::NavState, gtsam::Gal3}>
600600
virtual class ManifoldEKF {
@@ -615,7 +615,6 @@ virtual class ManifoldEKF {
615615
};
616616

617617
#include <gtsam/navigation/LieGroupEKF.h>
618-
#include <gtsam/geometry/Gal3.h>
619618
template <G = {gtsam::Rot3, gtsam::Pose2, gtsam::Pose3, gtsam::NavState, gtsam::Gal3}>
620619
virtual class LieGroupEKF : gtsam::ManifoldEKF<G> {
621620
// Constructors
@@ -650,20 +649,23 @@ class NavStateImuEKF : gtsam::LeftLinearEKF<gtsam::NavState> {
650649
NavStateImuEKF(const gtsam::NavState& X0, gtsam::Matrix P0,
651650
const gtsam::PreintegrationParams* params);
652651

653-
// Predict using IMU measurements
654-
void predict(const gtsam::Vector& omega_b, const gtsam::Vector& f_b, double dt);
655-
656652
// Accessors
657653
gtsam::Matrix processNoise() const;
658654
gtsam::Vector gravity() const;
659655
const gtsam::PreintegrationParams* params() const;
660656

661657
// Static methods
662-
gtsam::NavState Gravity(const gtsam::Vector& n_gravity, double dt);
663-
gtsam::NavState IMU(const gtsam::Vector& omega_b, const gtsam::Vector& f_b, double dt);
664-
gtsam::NavState Dynamics(const gtsam::Vector& n_gravity, const gtsam::NavState& X,
665-
const gtsam::Vector& omega_b, const gtsam::Vector& f_b,
666-
double dt);
658+
static gtsam::NavState Gravity(const gtsam::Vector& n_gravity, double dt);
659+
static gtsam::NavState IMU(const gtsam::Vector& omega_b, const gtsam::Vector& f_b, double dt);
660+
static gtsam::NavState Dynamics(const gtsam::Vector& n_gravity, const gtsam::NavState& X,
661+
const gtsam::Vector& omega_b, const gtsam::Vector& f_b,
662+
double dt);
663+
664+
// Predict using IMU measurements
665+
void predict(const gtsam::Vector& omega_b, const gtsam::Vector& f_b, double dt);
667666
};
668667

669668
}
669+
670+
671+

gtsam/navigation/tests/testNavStateImuEKF.cpp

Lines changed: 34 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <CppUnitLite/TestHarness.h>
1818
#include <gtsam/base/Testable.h>
1919
#include <gtsam/base/numericalDerivative.h>
20+
#include <gtsam/geometry/Gal3.h>
2021
#include <gtsam/inference/Symbol.h>
2122
#include <gtsam/linear/GaussianFactorGraph.h>
2223
#include <gtsam/linear/HessianFactor.h>
@@ -86,25 +87,44 @@ TEST(NavStateImuEKF, DynamicsJacobian) {
8687
/* ************************************************************************* */
8788
TEST(NavStateImuEKF, PredictMatchesExplicitIntegration) {
8889
using namespace nontrivial_navstate_example;
89-
double dt = 0.02;
90-
91-
// Explicit integration as per Derek's code (from raw inputs)
92-
Rot3 dR = Rot3::Expmap(omega_b * dt);
93-
Rot3 R_new = R0.compose(dR); // R_new = R * dR
94-
Vector3 a_world = R0 * f_b + params->n_gravity;
95-
Vector3 v_new = v0 + a_world * dt;
96-
Point3 p_new = p0 + v0 * dt + a_world * (0.5 * dt * dt);
97-
NavState X_explicit(R_new, p_new, v_new);
90+
double dt = 10;
91+
92+
// Explicit integration from paper
93+
const Vector3 phi_b = omega_b * dt;
94+
const so3::DexpFunctor local(phi_b);
95+
const Matrix3 J_l = local.Jacobian().left();
96+
const Matrix3 N_l = local.Gamma().left();
97+
98+
const NavState U{Rot3::Expmap(phi_b), // R_new
99+
N_l * f_b * dt * dt, // p_new
100+
J_l * f_b * dt}; // v_new
101+
102+
// Check against static IMU function:
103+
const NavState U_static = NavStateImuEKF::IMU(omega_b, f_b, dt);
104+
EXPECT(assert_equal(U, U_static, 1e-9));
105+
106+
// Check against Gal3::Expmap
107+
Vector10 xi;
108+
xi << omega_b, f_b, Vector3::Zero(), 1.0;
109+
const Gal3 T = Gal3::Expmap(xi * dt);
110+
const NavState U_gal3{T.rotation(), T.position(), T.velocity()};
111+
EXPECT(assert_equal(U_gal3, U, 1e-9));
112+
113+
// Explicit integration, combined with gravity and v0 * dt boost
114+
const Vector3& g_n = params->n_gravity;
115+
const Rot3 R_new = R0 * U.attitude();
116+
const Vector3 v_new = v0 + g_n * dt + R0 * U.velocity();
117+
const Point3 p_new = p0 + g_n * (0.5 * dt * dt) + v0 * dt + R0 * U.position();
118+
const NavState X_explicit(R_new, p_new, v_new);
98119

99120
// Increment-based integration should match exactly
100121
auto params = PreintegrationParams::MakeSharedU(9.81);
101122
NavStateImuEKF ekf(X0, I_9x9 * 1e-3, params);
102-
NavState X_inc =
103-
NavStateImuEKF::Dynamics(params->n_gravity, X0, omega_b, f_b, dt);
104-
EXPECT(assert_equal(X_explicit, X_inc, 1e-12));
123+
NavState X_next = NavStateImuEKF::Dynamics(g_n, X0, omega_b, f_b, dt);
124+
EXPECT(assert_equal(X_explicit, X_next, 1e-12));
105125
}
106126

107-
/* ************************************************************************* */
127+
/* ***************************************************************************/
108128
// Check Jacobian for world-position measurement h(X)=position(X).
109129
TEST(NavStateImuEKF, PositionMeasurementJacobian) {
110130
using namespace nontrivial_navstate_example;
@@ -215,62 +235,8 @@ TEST(NavStateImuEKF, PositionUpdateSanity) {
215235
}
216236

217237
/* ************************************************************************* */
218-
NavState oldNavStateImuDynamics(const NavState& X, const Vector3& omega_b,
219-
const Vector3& f_b, double dt,
220-
const Vector3& n_gravity,
221-
OptionalJacobian<9, 9> H = {}) {
222-
// Rotation and velocity
223-
const Rot3& R = X.attitude();
224-
Matrix3 D_vb_R, D_vb_v, D_gb_R;
225-
const Vector3& v_n = X.velocity(); // Has D_v_v = R !
226-
const Vector3 v_body =
227-
R.unrotate(v_n, H ? &D_vb_R : nullptr, H ? &D_vb_v : nullptr);
228-
const Vector3 g_body = R.unrotate(n_gravity, H ? &D_gb_R : nullptr);
229-
const Vector3 a_b_total = f_b + g_body;
230-
231-
// Construct increment directly as group element with body-frame p/v
232-
// increments
233-
const Rot3 dR = Rot3::Expmap(omega_b * dt);
234-
const double dt2 = 0.5 * dt * dt;
235-
const Vector3 dp_body = v_body * dt + a_b_total * dt2;
236-
const Vector3 dv_body = a_b_total * dt;
237-
NavState U(dR, dp_body, dv_body);
238-
239-
if (H) {
240-
Matrix3 dRt = dR.transpose(); // Jacobian of NavState::Create
241-
H->setZero();
242-
// position:
243-
H->template block<3, 3>(3, 0) = dRt * (D_vb_R * dt + D_gb_R * dt2);
244-
const Matrix3 D_v_v = R.matrix(); // Jacobian of velocity()
245-
H->template block<3, 3>(3, 6) = dRt * D_vb_v * D_v_v * dt;
246-
// velocity:
247-
H->template block<3, 3>(6, 0) = dRt * D_gb_R * dt;
248-
}
249-
250-
return U;
251-
}
252-
253-
TEST(NavStateImuEKF, WPhiU_matches_navStateImuDynamics) {
254-
using namespace nontrivial_navstate_example;
255-
256-
const double dt = 1e-2; // 10 ms
257-
const Vector3 n_gravity(0, 0, -9.81);
258-
259-
// Expected state from oldNavStateImuDynamics
260-
Matrix9 D_U_X0, D_X_X0, D_X_U;
261-
NavState U = oldNavStateImuDynamics(X0, omega_b, f_b, dt, n_gravity, D_U_X0);
262-
NavState X_old = X0.compose(U, D_X_X0, D_X_U);
263-
264-
// Check New dynamics function
265-
Matrix9 A_ekf;
266-
NavState X_ekf =
267-
NavStateImuEKF::Dynamics(n_gravity, X0, omega_b, f_b, dt, A_ekf);
268-
269-
CHECK(assert_equal(X_old, X_ekf));
270-
CHECK(assert_equal<Matrix>(D_X_X0 + D_X_U * D_U_X0, A_ekf));
271-
}
272-
273238
int main() {
274239
TestResult tr;
275240
return TestRegistry::runAllTests(tr);
276241
}
242+
/* ************************************************************************* */

python/gtsam/examples/NavStateImuASVExample.ipynb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51932,7 +51932,11 @@
5193251932
"cell_type": "code",
5193351933
"execution_count": null,
5193451934
"id": "2ea60e82",
51935-
"metadata": {},
51935+
"metadata": {
51936+
"tags": [
51937+
"hide-input"
51938+
]
51939+
},
5193651940
"outputs": [
5193751941
{
5193851942
"data": {

0 commit comments

Comments
 (0)