Skip to content

Commit df3b615

Browse files
committed
speed up ManifoldPreintegration update Jacobian computation
1 parent 2c84e0c commit df3b615

File tree

7 files changed

+208
-37
lines changed

7 files changed

+208
-37
lines changed

gtsam/navigation/ImuFactor.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,6 @@ void PreintegratedImuMeasurementsT<PreintegrationType>::integrateMeasurement(
6969
Matrix93 B, C; // Jacobian of state wrpt accel bias and omega bias respectively.
7070
PreintegrationType::update(measuredAcc, measuredOmega, dt, &A, &B, &C);
7171

72-
// For TangentPreintegration, the perturbation on deltaXij is dX2, the 3D perturbation of the rotation tangent space +
73-
// 3D perturbation of the translation part + 3D perturbation of the velocity part,
74-
// as validated by the matrix A_k in the ImuFactor.pdf and the unit test on preintegrated_H_biasAcc()
75-
76-
// For ManifoldPreintegration, for covariance propagation, the perturbation on deltaXij is dX1,
77-
// the right perturbation of the NavState manifold, as validated by the unit tests on NavState::retract);
78-
// for jacobian computation, the perturbation on deltaXij is dX2 defined above, as validated by the unit test on delPdelBiasAcc().
79-
80-
// For ImuFactor or CombinedImuFactor, the residual is defined by PreintegrationBase::computeError and
81-
// its perturbation is coincident to dX2 (up to a right Jacobian of a small angle ~ identity).
82-
// So for TangentPreintegration, the covariance of the residual equals to the preintMeasCov of TangentPreintegration.
83-
// But for ManifoldPreintegration, we need to rotate the position and velocity blocks of the preintMeasCov to get the residual cov.
84-
// Otherwise, we can update the cov propagation of manifold preintegration to make it use dX2.
85-
8672
// first order covariance propagation:
8773
// as in [2] we consider a first order propagation that can be seen as a
8874
// prediction phase in EKF

gtsam/navigation/ImuFactor.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,19 @@ class GTSAM_EXPORT PreintegratedImuMeasurementsT: public PreintegrationType {
173173
// controls which class PreintegratedImuMeasurements uses):
174174
using PreintegratedImuMeasurements = PreintegratedImuMeasurementsT<DefaultPreintegrationType>;
175175

176+
// For TangentPreintegration, the perturbation on deltaXij is dXt, the 3D perturbation of the rotation tangent space +
177+
// 3D perturbation of the translation part + 3D perturbation of the velocity part,
178+
// as validated by the matrix A_k in the ImuFactor.pdf and the unit test on preintegrated_H_biasAcc()
179+
180+
// For ManifoldPreintegration, for covariance propagation, the perturbation on deltaXij is dXn,
181+
// the right perturbation of the NavState manifold, as validated by the unit tests on NavState::retract);
182+
// for jacobian computation, the perturbation on deltaXij is dXt defined above, as validated by the unit test on delPdelBiasAcc().
183+
184+
// For ImuFactor or CombinedImuFactor, the residual is defined by PreintegrationBase::computeError and
185+
// its perturbation is coincident to dXt (up to a right Jacobian of a small angle ~ identity).
186+
// So for TangentPreintegration, the covariance of the residual equals to the preintMeasCov of TangentPreintegration.
187+
// But for ManifoldPreintegration, we need to rotate the position and velocity blocks of the preintMeasCov to get the residual cov.
188+
// Otherwise, we can update the cov propagation of manifold preintegration to make it use dXt.
176189
template <class PIM>
177190
inline gtsam::Matrix9 imuFactorResidualCov(const PIM& pim) {
178191
return pim.preintMeasCov();

gtsam/navigation/ManifoldPreintegration.cpp

Lines changed: 115 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,120 @@ bool ManifoldPreintegration::equals(const ManifoldPreintegration& other,
5656
&& equal_with_abs_tol(delVdelBiasOmega_, other.delVdelBiasOmega_, tol);
5757
}
5858

59+
NavState ManifoldPreintegration::UpdatePreintegrated(
60+
const Eigen::Vector3d& a_body,
61+
const Eigen::Vector3d& w_body,
62+
double dt,
63+
const NavState& X, // (R,p,v)
64+
gtsam::OptionalJacobian<9,9> A_t, // dXt_{k+1} / dXt_k
65+
gtsam::OptionalJacobian<9,3> B_t, // dXt_{k+1} / d a_body
66+
gtsam::OptionalJacobian<9,3> C_t) { // dXt_{k+1} / d w_body
67+
68+
// Let's denote the right perturbation dXn on the NavState X_ij's manifold, i.e.,
69+
// dXn = [\delta \phi_ij, \delta p_ij, \delta v_ij] where R_ij = \hat{R}_ij Exp(\delta \phi_ij)
70+
// p_ij = \hat{p}_ij + R_ij \delta p_{ij} and v_ij = \hat{v}_ij + R_ij \delta v_{ij}.
71+
// The error state dXt of the preint X_ij is actually defined as
72+
// dXt = [\delta \phi_ij, \delta p_ij, \delta v_ij] where R_ij = \hat{R}_ij Exp(\delta \phi_ij)
73+
// p_ij = \hat{p}_ij + \delta p_{ij} and v_ij = \hat{v}_ij + \delta v_{ij}.
74+
// So to propagate the X_ij's covariance, we need to transform the transition matrix A.
75+
// from NavState.update(), An = \frac{\delta X^n_j}{\delta X^n_{j-1}}, and transform it to
76+
// At = \frac{\delta X^t_j}{\delta X^t_{j-1}} = \frac{\delta X^t_j}{\delta X^n_j} * An * \frac{\delta X^n_{j-1}}{\delta X^t_{j-1}}
77+
78+
const double dt2 = dt * dt;
79+
const double dt22 = 0.5 * dt2;
80+
81+
const gtsam::Rot3& R = X.rotation();
82+
const Eigen::Matrix3d Rm = R.matrix();
83+
84+
// Rotation increment
85+
const Eigen::Vector3d phi = w_body * dt;
86+
87+
Eigen::Matrix3d Dexp; // not used directly here, but cheap to compute
88+
const gtsam::Rot3 dR = gtsam::Rot3::Expmap(phi, (A_t || C_t) ? &Dexp : nullptr);
89+
const gtsam::Rot3 Rn = R.compose(dR);
90+
91+
// a_nav uses OLD R (matching typical preintegration / NavState.update structure)
92+
const Eigen::Vector3d a_nav = R.rotate(a_body);
93+
94+
const gtsam::Point3 pn = X.position() + X.v() * dt + a_nav * dt22;
95+
const Eigen::Vector3d vn = X.v() + a_nav * dt;
96+
97+
NavState Xn(Rn, pn, vn);
98+
99+
if (A_t) {
100+
A_t->setZero();
101+
102+
// dphi+ = dR^T * dphi (right-perturbation transport)
103+
A_t->block<3,3>(0,0) = dR.transpose().matrix();
104+
105+
// dp+ = dp + dt dv + (d/dphi)(0.5*R*a*dt^2)*dphi
106+
// dv+ = dv + (d/dphi)(R*a*dt)*dphi
107+
//
108+
// Right perturbation: R Exp(dphi) a ≈ R (I + [dphi]x) a
109+
// so δ(R a) = R (dphi x a) = - R [a]x dphi
110+
const Eigen::Matrix3d a_skew = skewSymmetric(a_body);
111+
A_t->block<3,3>(3,0) = -Rm * a_skew * dt22;
112+
A_t->block<3,3>(6,0) = -Rm * a_skew * dt;
113+
114+
// world-additive p,v parts
115+
A_t->block<3,3>(3,3) = Eigen::Matrix3d::Identity();
116+
A_t->block<3,3>(3,6) = Eigen::Matrix3d::Identity() * dt;
117+
A_t->block<3,3>(6,6) = Eigen::Matrix3d::Identity();
118+
}
119+
120+
if (B_t) {
121+
B_t->setZero();
122+
// dp+ / da = 0.5 * R * dt^2, dv+ / da = R * dt
123+
B_t->block<3,3>(3,0) = Rm * dt22;
124+
B_t->block<3,3>(6,0) = Rm * dt;
125+
}
126+
127+
if (C_t) {
128+
C_t->setZero();
129+
// dphi+ / dw = invJr(phi) * dt
130+
so3::DexpFunctor local(phi);
131+
const Eigen::Matrix3d invJr = local.InvJacobian().right();
132+
C_t->block<3,3>(0,0) = invJr * dt;
133+
}
134+
135+
return Xn;
136+
}
137+
138+
NavState ManifoldPreintegration::UpdatePreintegratedSlow(
139+
const Eigen::Vector3d& a_body,
140+
const Eigen::Vector3d& w_body,
141+
double dt,
142+
const NavState& X,
143+
gtsam::OptionalJacobian<9,9> A,
144+
gtsam::OptionalJacobian<9,3> B,
145+
gtsam::OptionalJacobian<9,3> C) {
146+
const Eigen::Matrix3d Rm = X.rotation().matrix();
147+
148+
Matrix9 D_dXn_dXt_jm1 = Matrix9::Identity();
149+
D_dXn_dXt_jm1.block<3,3>(3,3) = Rm.transpose();
150+
D_dXn_dXt_jm1.block<3,3>(6,6) = Rm.transpose();
151+
152+
Matrix9 An;
153+
Matrix93 Bn, Cn;
154+
155+
NavState Xn = X.update(a_body, w_body, dt,
156+
A ? &An : nullptr,
157+
B ? &Bn : nullptr,
158+
C ? &Cn : nullptr);
159+
160+
const Eigen::Matrix3d Rnm = Xn.rotation().matrix();
161+
162+
Matrix9 D_dXt_dXn_j = Matrix9::Identity();
163+
D_dXt_dXn_j.block<3,3>(3,3) = Rnm;
164+
D_dXt_dXn_j.block<3,3>(6,6) = Rnm;
165+
166+
if (A) *A = D_dXt_dXn_j * An * D_dXn_dXt_jm1;
167+
if (B) *B = D_dXt_dXn_j * Bn;
168+
if (C) *C = D_dXt_dXn_j * Cn;
169+
170+
return Xn;
171+
}
172+
59173
//------------------------------------------------------------------------------
60174
void ManifoldPreintegration::update(const Vector3& measuredAcc,
61175
const Vector3& measuredOmega, const double dt, Matrix9* A, Matrix93* B,
@@ -78,27 +192,7 @@ void ManifoldPreintegration::update(const Vector3& measuredAcc,
78192

79193
// Do update
80194
deltaTij_ += dt;
81-
82-
// Let's denote the right perturbation dXn on the NavState X_ij's manifold, i.e.,
83-
// dXn = [\delta \phi_ij, \delta p_ij, \delta v_ij] where R_ij = \hat{R}_ij Exp(\delta \phi_ij)
84-
// p_ij = \hat{p}_ij + R_ij \delta p_{ij} and v_ij = \hat{v}_ij + R_ij \delta v_{ij}.
85-
// The error state dXt of the preint X_ij is actually defined as
86-
// dXt = [\delta \phi_ij, \delta p_ij, \delta v_ij] where R_ij = \hat{R}_ij Exp(\delta \phi_ij)
87-
// p_ij = \hat{p}_ij + \delta p_{ij} and v_ij = \hat{v}_ij + \delta v_{ij}.
88-
// So to propagate the X_ij's covariance, we need to transform the transition matrix A.
89-
// from NavState.update(), An = \frac{\delta X^n_j}{\delta X^n_{j-1}}, and transform it to
90-
// At = \frac{\delta X^t_j}{\delta X^t_{j-1}} = \frac{\delta X^t_j}{\delta X^n_j} * An * \frac{\delta X^n_{j-1}}{\delta X^t_{j-1}}
91-
Matrix9 D_dXn_dXt_jm1 = Matrix9::Identity();
92-
D_dXn_dXt_jm1.block<3, 3>(3, 3).noalias() = deltaXij_.rotation().matrix().transpose();
93-
D_dXn_dXt_jm1.block<3, 3>(6, 6) = D_dXn_dXt_jm1.block<3, 3>(3, 3);
94-
Matrix9 An;
95-
deltaXij_ = deltaXij_.update(acc, omega, dt, &An, B, C); // functional
96-
Matrix9 D_dXt_dXn_j = Matrix9::Identity();
97-
D_dXt_dXn_j.block<3, 3>(3, 3).noalias() = deltaXij_.rotation().matrix();
98-
D_dXt_dXn_j.block<3, 3>(6, 6) = D_dXt_dXn_j.block<3, 3>(3, 3);
99-
*A = D_dXt_dXn_j * An * D_dXn_dXt_jm1;
100-
*B = D_dXt_dXn_j * *B;
101-
*C = D_dXt_dXn_j * *C;
195+
deltaXij_ = UpdatePreintegrated(acc, omega, dt, deltaXij_, A, B, C);
102196

103197
if (p().body_P_sensor) {
104198
// More complicated derivatives in case of non-trivial sensor pose

gtsam/navigation/ManifoldPreintegration.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,28 @@ class GTSAM_EXPORT ManifoldPreintegration : public PreintegrationBase {
9292
/// @name Main functionality
9393
/// @{
9494

95+
// Error chart dXt:
96+
// R = Rhat * Exp(dphi) (right perturbation)
97+
// p = phat + dp (world additive)
98+
// v = vhat + dv (world additive)
99+
static NavState UpdatePreintegrated(
100+
const Eigen::Vector3d& a_body,
101+
const Eigen::Vector3d& w_body,
102+
double dt,
103+
const NavState& X, // (R,p,v)
104+
gtsam::OptionalJacobian<9,9> A = {}, // dXt_{k+1} / dXt_k
105+
gtsam::OptionalJacobian<9,3> B = {}, // dXt_{k+1} / d a_body
106+
gtsam::OptionalJacobian<9,3> C = {}); // dXt_{k+1} / d w_body
107+
108+
static NavState UpdatePreintegratedSlow(
109+
const Eigen::Vector3d& a_body,
110+
const Eigen::Vector3d& w_body,
111+
double dt,
112+
const NavState& X, // (R,p,v)
113+
gtsam::OptionalJacobian<9,9> A = {}, // dXt_{k+1} / dXt_k
114+
gtsam::OptionalJacobian<9,3> B = {}, // dXt_{k+1} / d a_body
115+
gtsam::OptionalJacobian<9,3> C = {}); // dXt_{k+1} / d w_body
116+
95117
/// Update preintegrated measurements and get derivatives
96118
/// It takes measured quantities in the j frame
97119
/// Modifies preintegrated quantities in place after correcting for bias and possibly sensor pose

gtsam/navigation/tests/testCombinedImuFactor.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,6 @@ MakeParamsU_Combined(const ImuSimConfig& cfg) {
334334
// Turn off bias random walk so we can compare to 9D EKF (no bias state)
335335
p->biasOmegaCovariance = gtsam::Matrix3::Zero();
336336
p->biasAccCovariance = gtsam::Matrix3::Zero();
337-
p->biasAccOmegaInt.setZero();
338337

339338
p->use2ndOrderCoriolis = false;
340339
return p;

gtsam/navigation/tests/testImuFactor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ static inline Maps9 BuildMaps9_Tangent(
948948
// phi = Log(dR)
949949
const gtsam::Vector3 phi = gtsam::Rot3::Logmap(gtsam::Rot3(dR));
950950
const Eigen::Matrix3d Jr = gtsam::so3::DexpFunctor(phi).rightJacobian();
951-
const Eigen::Matrix3d Jr_inv = gtsam::so3::DexpFunctor(phi).rightJacobianInverse();
951+
const Eigen::Matrix3d Jr_inv = gtsam::so3::DexpFunctor(phi).InvJacobian().right();
952952

953953
m.F.block<3,3>(0,0) = A;
954954
m.F.block<3,3>(3,0) = -A * gtsam::skewSymmetric(dP);

gtsam/navigation/tests/testManifoldPreintegration.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,63 @@ TEST(ManifoldPreintegration, CompareWithPreintegratedRotation) {
155155
H2.rightCols<3>()));
156156
}
157157

158+
TEST(ManifoldPreintegration, UpdatePreintegratedFastMatchesSlow) {
159+
using namespace gtsam;
160+
161+
const unsigned int seed_cfg = 0;
162+
const unsigned int seed_meas = 1;
163+
164+
ImuSimConfig cfg(seed_cfg);
165+
auto params = MakeParamsU(cfg);
166+
167+
const double T = 5.0; // seconds
168+
const auto meas = MakeRandomImuMeasurements(cfg, T, seed_meas);
169+
170+
NavState X_fast;
171+
NavState X_slow;
172+
173+
Matrix9 P_fast = Matrix9::Zero();
174+
Matrix9 P_slow = Matrix9::Zero();
175+
176+
const Matrix3 aCov = params->accelerometerCovariance;
177+
const Matrix3 wCov = params->gyroscopeCovariance;
178+
const Matrix3 iCov = params->integrationCovariance;
179+
180+
ManifoldPreintegration mip(params, cfg.bias);
181+
182+
for (const auto& s : meas) {
183+
Matrix9 A_f;
184+
Matrix93 B_f, C_f;
185+
NavState Xn_fast = mip.UpdatePreintegrated(
186+
s.measuredAcc, s.measuredOmega, s.dt, X_fast, &A_f, &B_f, &C_f);
187+
188+
Matrix9 A_s;
189+
Matrix93 B_s, C_s;
190+
NavState Xn_slow = mip.UpdatePreintegratedSlow(
191+
s.measuredAcc, s.measuredOmega, s.dt, X_slow, &A_s, &B_s, &C_s);
192+
193+
EXPECT(assert_equal(Xn_fast, Xn_slow, 1e-9));
194+
195+
EXPECT_MAT_NEAR(A_f, A_s, 1e-9, 1e-6);
196+
EXPECT_MAT_NEAR(B_f, B_s, 1e-9, 1e-6);
197+
EXPECT_MAT_NEAR(C_f, C_s, 1e-6, 1e-4);
198+
199+
P_fast = A_f * P_fast * A_f.transpose();
200+
P_fast.noalias() += B_f * (aCov / s.dt) * B_f.transpose();
201+
P_fast.noalias() += C_f * (wCov / s.dt) * C_f.transpose();
202+
P_fast.block<3,3>(3,3).noalias() += iCov * s.dt;
203+
204+
P_slow = A_s * P_slow * A_s.transpose();
205+
P_slow.noalias() += B_s * (aCov / s.dt) * B_s.transpose();
206+
P_slow.noalias() += C_s * (wCov / s.dt) * C_s.transpose();
207+
P_slow.block<3,3>(3,3).noalias() += iCov * s.dt;
208+
209+
EXPECT_MAT_NEAR(P_fast, P_slow, 1e-5, 5e-3);
210+
X_fast = Xn_fast;
211+
X_slow = Xn_slow;
212+
}
213+
}
214+
158215
/* ************************************************************************* */
159216
int main() {
160217
TestResult tr;

0 commit comments

Comments
 (0)