Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
- name: Install Boost
if: contains(matrix.name, 'boost') && matrix.build_type == 'Release'
run: |
brew update
brew upgrade boost --quiet || brew install boost --quiet
echo "GTSAM_USE_BOOST_FEATURES=ON" >> $GITHUB_ENV
echo "GTSAM_ENABLE_BOOST_SERIALIZATION=ON" >> $GITHUB_ENV
Expand Down
25 changes: 7 additions & 18 deletions gtsam/geometry/SL4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,6 @@ struct LogDeterminantResult {
double logAbsDet;
};

// Threshold on |log(det)| below which we treat a negative sign as numerical.
// We allow a generous tolerance to account for overflow when exponentiating
// large trace-zero matrices.
constexpr double kSignFlipLogTolerance = 1e3;

LogDeterminantResult logDeterminantWithSign(const gtsam::Matrix4& pose) {
const Eigen::FullPivLU<gtsam::Matrix4> lu(pose);
double sign =
Expand Down Expand Up @@ -166,19 +161,13 @@ SL4 SL4::Expmap(const Vector& xi, SL4Jacobian H) {
Matrix44 expA = A.exp();
LogDeterminantResult logDet = logDeterminantWithSign(expA);

// The exponential of a trace-zero matrix should have det=1. If we see a
// negative sign but the magnitude is reasonable (within tolerance), treat it
// as a numerical sign flip and continue; otherwise reject.
if (logDet.sign < 0.0) {
if (std::abs(logDet.logAbsDet) < kSignFlipLogTolerance) {
logDet.sign = 1.0; // ignore spurious sign
} else {
throw std::runtime_error("SL4::Expmap: Negative determinant from exp.");
}
}

if (logDet.sign == 0.0 || !std::isfinite(logDet.logAbsDet)) {
throw std::runtime_error("SL4::Expmap: Singular result from matrix exp.");
// The exponential of a trace-zero matrix should have det=1. A non-positive
// determinant indicates severe numerical instability that cannot be corrected
// by simple renormalization.
if (logDet.sign <= 0.0 || !std::isfinite(logDet.logAbsDet)) {
throw std::runtime_error(
"SL4::Expmap: Matrix exponential has non-positive or infinite "
"determinant, cannot project to SL(4).");
}

SL4 result;
Expand Down
49 changes: 45 additions & 4 deletions gtsam/geometry/tests/testSL4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,30 @@ TEST(SL4, Identity) {
TEST(SL4, Expmap) {
SL4 expected(SL4::Expmap(xi0));
EXPECT(assert_equal(expected, SL4::Expmap(xi0), 1e-8));

// Verify that the result has determinant 1
Matrix4 T = expected.matrix();
double det = T.determinant();
EXPECT_DOUBLES_EQUAL(1.0, det, 1e-8);
}

// Make sure larger xi still works
(void)SL4::Expmap(xi_large);
/* ************************************************************************* */
TEST(SL4, ExpmapLargeTangent) {
// Test with large tangent vector - this may fail due to numerical limitations
// of the matrix exponential computation. For very large tangent vectors,
// the exponential map can produce matrices with numerically unstable determinants.
try {
SL4 result = SL4::Expmap(xi_large);
// If it succeeds, verify determinant is 1
Matrix4 T = result.matrix();
double det = T.determinant();
EXPECT_DOUBLES_EQUAL(1.0, det, 1e-6); // Looser tolerance for large inputs
} catch (const std::runtime_error& e) {
// If it fails due to numerical issues, that's acceptable for very large inputs
// The error message should indicate a determinant issue
std::string msg(e.what());
EXPECT(msg.find("determinant") != std::string::npos);
}
}

/* ************************************************************************* */
Expand All @@ -84,9 +105,29 @@ TEST(SL4, Retract) {
SL4 actual = SL4::Retract(xi);
SL4 expected(I_4x4 + SL4::Hat(xi));
EXPECT(assert_equal(expected, actual, 1e-8));

// Verify that the result has determinant 1
Matrix4 T = actual.matrix();
double det = T.determinant();
EXPECT_DOUBLES_EQUAL(1.0, det, 1e-8);
}

// Make sure larger xi still works
(void)SL4::Retract(xi_large);
/* ************************************************************************* */
TEST(SL4, RetractLargeTangent) {
// Test with large tangent vector - this may fail due to numerical limitations.
// The retraction will fall back to Expmap for large inputs that produce
// invalid first-order approximations.
try {
SL4 result = SL4::Retract(xi_large);
// If it succeeds, verify determinant is 1
Matrix4 T = result.matrix();
double det = T.determinant();
EXPECT_DOUBLES_EQUAL(1.0, det, 1e-6); // Looser tolerance for large inputs
} catch (const std::runtime_error& e) {
// If it fails due to numerical issues in Expmap fallback, that's acceptable
std::string msg(e.what());
EXPECT(msg.find("determinant") != std::string::npos);
}
}

/* ************************************************************************* */
Expand Down
Loading