-
Notifications
You must be signed in to change notification settings - Fork 921
Invariant EKF #2115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Invariant EKF #2115
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
c403e4a
Add files via upload
scottiyio aafeabe
Add files via upload
scottiyio 693a089
Add files via upload
scottiyio c521dbc
Add files via upload
scottiyio 136a7cd
Delete examples/SE2LIEKF_NoFactors.cpp
scottiyio 12d4223
clang-formatted, changed getFoobar() to foobar()
scottiyio 8aed731
Added documentation and format changes.
scottiyio 6ff0439
Added authors
scottiyio f857001
Added documentation & clang formatting
scottiyio 95794f7
Added authors
scottiyio 0d832a6
no_boost
dellaert b1c32cb
Templated predict/update
dellaert 9352465
More symmetric templating
dellaert f0e35ae
Comments with o4-mini
dellaert f63255b
Move to nav
dellaert 885ab38
Docs with o4-mini
dellaert 0ee0a8b
Test WIP
dellaert 5af25dc
Use nav version
dellaert 062bdf6
Added Rot3 example to show state-dependent control
dellaert 1ced4d0
Review comments
dellaert a0c6902
SFINAE to restrict template matching
dellaert 4f2a62a
Update testLIEKF.cpp
dellaert 104341f
well-tested predictMean
dellaert 87c4445
predictMean for state=control f
dellaert 5bc0108
Correct jacobian for h_gps
dellaert ab60577
Rename
dellaert 51e89d2
Fix compilation
dellaert 4cc2e22
Add value to concepts
dellaert 3bcc556
EKF hierarchy
dellaert ec8c762
Renamed GroupEKF -> LieGroupEKF, moved invariant predicts
dellaert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /* ---------------------------------------------------------------------------- | ||
|
|
||
| * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||
| * Atlanta, Georgia 30332-0415 | ||
| * All Rights Reserved | ||
| * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||
|
|
||
| * See LICENSE for the license information | ||
|
|
||
| * -------------------------------------------------------------------------- */ | ||
|
|
||
| /** | ||
| * @file IEKF_Rot3Example.cpp | ||
| * @brief Left‐Invariant EKF on SO(3) with state‐dependent pitch/roll control | ||
| * and a single magnetometer update. | ||
| * @date April 25, 2025 | ||
| * @authors Scott Baker, Matt Kielo, Frank Dellaert | ||
| */ | ||
|
|
||
| #include <gtsam/base/Matrix.h> | ||
| #include <gtsam/base/OptionalJacobian.h> | ||
| #include <gtsam/geometry/Rot3.h> | ||
| #include <gtsam/navigation/LieGroupEKF.h> | ||
|
|
||
| #include <iostream> | ||
|
|
||
| using namespace std; | ||
| using namespace gtsam; | ||
|
|
||
| // --- 1) Closed‐loop dynamics f(X): xi = –k·[φx,φy,0], H = ∂xi/∂φ·Dφ --- | ||
| static constexpr double k = 0.5; | ||
| Vector3 dynamicsSO3(const Rot3& X, OptionalJacobian<3, 3> H = {}) { | ||
| // φ = Logmap(R), Dφ = ∂φ/∂δR | ||
| Matrix3 D_phi; | ||
| Vector3 phi = Rot3::Logmap(X, D_phi); | ||
| // zero out yaw | ||
| phi[2] = 0.0; | ||
| D_phi.row(2).setZero(); | ||
|
|
||
| if (H) *H = -k * D_phi; // ∂(–kφ)/∂δR | ||
| return -k * phi; // xi ∈ 𝔰𝔬(3) | ||
| } | ||
|
|
||
| // --- 2) Magnetometer model: z = R⁻¹ m, H = –[z]_× --- | ||
| static const Vector3 m_world(0, 0, -1); | ||
| Vector3 h_mag(const Rot3& X, OptionalJacobian<3, 3> H = {}) { | ||
| Vector3 z = X.inverse().rotate(m_world); | ||
| if (H) *H = -skewSymmetric(z); | ||
| return z; | ||
| } | ||
|
|
||
| int main() { | ||
| // Initial estimate (identity) and covariance | ||
| const Rot3 R0 = Rot3::RzRyRx(0.1, -0.2, 0.3); | ||
| const Matrix3 P0 = Matrix3::Identity() * 0.1; | ||
| LieGroupEKF<Rot3> ekf(R0, P0); | ||
|
|
||
| // Timestep, process noise, measurement noise | ||
| double dt = 0.1; | ||
| Matrix3 Q = Matrix3::Identity() * 0.01; | ||
| Matrix3 Rm = Matrix3::Identity() * 0.05; | ||
|
|
||
| cout << "=== Init ===\nR:\n" | ||
| << ekf.state().matrix() << "\nP:\n" | ||
| << ekf.covariance() << "\n\n"; | ||
|
|
||
| // Predict using state‐dependent f | ||
| ekf.predict(dynamicsSO3, dt, Q); | ||
| cout << "--- After predict ---\nR:\n" << ekf.state().matrix() << "\n\n"; | ||
|
|
||
| // Magnetometer measurement = body‐frame reading of m_world | ||
| Vector3 z = h_mag(R0); | ||
| ekf.update(h_mag, z, Rm); | ||
| cout << "--- After update ---\nR:\n" << ekf.state().matrix() << "\n"; | ||
|
|
||
| return 0; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /* ---------------------------------------------------------------------------- | ||
|
|
||
| * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||
| * Atlanta, Georgia 30332-0415 | ||
| * All Rights Reserved | ||
| * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||
|
|
||
| * See LICENSE for the license information | ||
|
|
||
| * -------------------------------------------------------------------------- */ | ||
|
|
||
| /** | ||
| * @file IEKF_NavstateExample.cpp | ||
| * @brief InvariantEKF on NavState (SE_2(3)) with IMU (predict) and GPS (update) | ||
| * @date April 25, 2025 | ||
| * @authors Scott Baker, Matt Kielo, Frank Dellaert | ||
| */ | ||
|
|
||
| #include <gtsam/base/Matrix.h> | ||
| #include <gtsam/base/OptionalJacobian.h> | ||
| #include <gtsam/navigation/InvariantEKF.h> | ||
| #include <gtsam/navigation/NavState.h> | ||
|
|
||
| #include <iostream> | ||
|
|
||
| using namespace std; | ||
| using namespace gtsam; | ||
|
|
||
| /** | ||
| * @brief Left-invariant dynamics for NavState. | ||
| * @param imu 6×1 vector [a; ω]: linear acceleration and angular velocity. | ||
| * @return 9×1 tangent: [ω; 0₃; a]. | ||
| */ | ||
| Vector9 dynamics(const Vector6& imu) { | ||
| auto a = imu.head<3>(); | ||
| auto w = imu.tail<3>(); | ||
| Vector9 xi; | ||
| xi << w, Vector3::Zero(), a; | ||
| return xi; | ||
| } | ||
|
|
||
| /** | ||
| * @brief GPS measurement model: returns position and its Jacobian. | ||
| * @param X Current state. | ||
| * @param H Optional 3×9 Jacobian w.r.t. X. | ||
| * @return 3×1 position vector. | ||
| */ | ||
| Vector3 h_gps(const NavState& X, OptionalJacobian<3, 9> H = {}) { | ||
| return X.position(H); | ||
| } | ||
|
|
||
| int main() { | ||
| // Initial state & covariances | ||
| NavState X0; // R=I, v=0, t=0 | ||
| Matrix9 P0 = Matrix9::Identity() * 0.1; | ||
| InvariantEKF<NavState> ekf(X0, P0); | ||
|
|
||
| // Noise & timestep | ||
| double dt = 1.0; | ||
| Matrix9 Q = Matrix9::Identity() * 0.01; | ||
| Matrix3 R = Matrix3::Identity() * 0.5; | ||
|
|
||
| // Two IMU samples [a; ω] | ||
| Vector6 imu1; | ||
| imu1 << 0.1, 0, 0, 0, 0.2, 0; | ||
| Vector6 imu2; | ||
| imu2 << 0, 0.3, 0, 0.4, 0, 0; | ||
|
|
||
| // Two GPS fixes | ||
| Vector3 z1; | ||
| z1 << 0.3, 0, 0; | ||
| Vector3 z2; | ||
| z2 << 0.6, 0, 0; | ||
|
|
||
| cout << "=== Init ===\nX: " << ekf.state() << "\nP: " << ekf.covariance() | ||
| << "\n\n"; | ||
|
|
||
| // --- first predict/update --- | ||
| ekf.predict(dynamics(imu1), dt, Q); | ||
| cout << "--- After predict 1 ---\nX: " << ekf.state() | ||
| << "\nP: " << ekf.covariance() << "\n\n"; | ||
| ekf.update(h_gps, z1, R); | ||
| cout << "--- After update 1 ---\nX: " << ekf.state() | ||
| << "\nP: " << ekf.covariance() << "\n\n"; | ||
|
|
||
| // --- second predict/update --- | ||
| ekf.predict(dynamics(imu2), dt, Q); | ||
| cout << "--- After predict 2 ---\nX: " << ekf.state() | ||
| << "\nP: " << ekf.covariance() << "\n\n"; | ||
| ekf.update(h_gps, z2, R); | ||
| cout << "--- After update 2 ---\nX: " << ekf.state() | ||
| << "\nP: " << ekf.covariance() << "\n"; | ||
|
|
||
| return 0; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| /* ---------------------------------------------------------------------------- | ||
|
|
||
| * GTSAM Copyright 2010, Georgia Tech Research Corporation, | ||
| * Atlanta, Georgia 30332-0415 | ||
| * All Rights Reserved | ||
| * Authors: Frank Dellaert, et al. (see THANKS for the full author list) | ||
|
|
||
| * See LICENSE for the license information | ||
|
|
||
| * -------------------------------------------------------------------------- */ | ||
|
|
||
| /** | ||
| * @file IEKF_SE2Example.cpp | ||
| * @brief A left invariant Extended Kalman Filter example using a Lie group | ||
| * odometry as the prediction stage on SE(2) and | ||
| * | ||
| * This example uses the templated InvariantEKF class to estimate the state of | ||
| * an object using odometry / GPS measurements The prediction stage of the | ||
| * InvariantEKF uses a Lie Group element to propagate the stage in a discrete | ||
| * InvariantEKF. For most cases, U = exp(u^ * dt) if u is a control vector that | ||
| * is constant over the interval dt. However, if u is not constant over dt, | ||
| * other approaches are needed to find the value of U. This approach simply | ||
| * takes a Lie group element U, which can be found in various different ways. | ||
| * | ||
| * This data was compared to a left invariant EKF on SE(2) using identical | ||
| * measurements and noise from the source of the InEKF plugin | ||
| * https://inekf.readthedocs.io/en/latest/ Based on the paper "An Introduction | ||
| * to the Invariant Extended Kalman Filter" by Easton R. Potokar, Randal W. | ||
| * Beard, and Joshua G. Mangelson | ||
| * | ||
| * @date Apr 25, 2025 | ||
| * @authors Scott Baker, Matt Kielo, Frank Dellaert | ||
| */ | ||
| #include <gtsam/geometry/Pose2.h> | ||
| #include <gtsam/navigation/InvariantEKF.h> | ||
|
|
||
| #include <iostream> | ||
|
|
||
| using namespace std; | ||
| using namespace gtsam; | ||
|
|
||
| // Create a 2D GPS measurement function that returns the predicted measurement h | ||
| // and Jacobian H. The predicted measurement h is the translation of the state | ||
| // X. | ||
| Vector2 h_gps(const Pose2& X, OptionalJacobian<2, 3> H = {}) { | ||
| return X.translation(H); | ||
| } | ||
|
|
||
| // Define a InvariantEKF class that uses the Pose2 Lie group as the state and | ||
| // the Vector2 measurement type. | ||
| int main() { | ||
| // // Initialize the filter's state, covariance, and time interval values. | ||
| Pose2 X0(0.0, 0.0, 0.0); | ||
| Matrix3 P0 = Matrix3::Identity() * 0.1; | ||
|
|
||
| // Create the filter with the initial state, covariance, and measurement | ||
| // function. | ||
| InvariantEKF<Pose2> ekf(X0, P0); | ||
|
|
||
| // Define the process covariance and measurement covariance matrices Q and R. | ||
| Matrix3 Q = (Vector3(0.05, 0.05, 0.001)).asDiagonal(); | ||
| Matrix2 R = I_2x2 * 0.01; | ||
|
|
||
| // Define odometry movements. | ||
| // U1: Move 1 unit in X, 1 unit in Y, 0.5 radians in theta. | ||
| // U2: Move 1 unit in X, 1 unit in Y, 0 radians in theta. | ||
| Pose2 U1(1.0, 1.0, 0.5), U2(1.0, 1.0, 0.0); | ||
|
|
||
| // Create GPS measurements. | ||
| // z1: Measure robot at (1, 0) | ||
| // z2: Measure robot at (1, 1) | ||
| Vector2 z1, z2; | ||
| z1 << 1.0, 0.0; | ||
| z2 << 1.0, 1.0; | ||
|
|
||
| // Define a transformation matrix to convert the covariance into (theta, x, y) | ||
| // form. The paper and data mentioned above uses a (theta, x, y) notation, | ||
| // whereas GTSAM uses (x, y, theta). The transformation matrix is used to | ||
| // directly compare results of the covariance matrix. | ||
| Matrix3 TransformP; | ||
| TransformP << 0, 0, 1, 1, 0, 0, 0, 1, 0; | ||
|
|
||
| // Propagating/updating the filter | ||
| // Initial state and covariance | ||
| cout << "\nInitialization:\n"; | ||
| cout << "X0: " << ekf.state() << endl; | ||
| cout << "P0: " << TransformP * ekf.covariance() * TransformP.transpose() | ||
| << endl; | ||
|
|
||
| // First prediction stage | ||
| ekf.predict(U1, Q); | ||
| cout << "\nFirst Prediction:\n"; | ||
| cout << "X: " << ekf.state() << endl; | ||
| cout << "P: " << TransformP * ekf.covariance() * TransformP.transpose() | ||
| << endl; | ||
|
|
||
| // First update stage | ||
| ekf.update(h_gps, z1, R); | ||
| cout << "\nFirst Update:\n"; | ||
| cout << "X: " << ekf.state() << endl; | ||
| cout << "P: " << TransformP * ekf.covariance() * TransformP.transpose() | ||
| << endl; | ||
|
|
||
| // Second prediction stage | ||
| ekf.predict(U2, Q); | ||
| cout << "\nSecond Prediction:\n"; | ||
| cout << "X: " << ekf.state() << endl; | ||
| cout << "P: " << TransformP * ekf.covariance() * TransformP.transpose() | ||
| << endl; | ||
|
|
||
| // Second update stage | ||
| ekf.update(h_gps, z2, R); | ||
| cout << "\nSecond Update:\n"; | ||
| cout << "X: " << ekf.state() << endl; | ||
| cout << "P: " << TransformP * ekf.covariance() * TransformP.transpose() | ||
| << endl; | ||
|
|
||
| return 0; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.