386 lines
14 KiB
C++
386 lines
14 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* 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 testSO3.cpp
|
|
* @brief Unit tests for SO3, as a GTSAM-adapted Lie Group
|
|
* @author Frank Dellaert
|
|
**/
|
|
|
|
#include <CppUnitLite/TestHarness.h>
|
|
#include <gtsam/base/Testable.h>
|
|
#include <gtsam/base/testLie.h>
|
|
#include <gtsam/geometry/SO3.h>
|
|
|
|
using namespace std::placeholders;
|
|
using namespace std;
|
|
using namespace gtsam;
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Identity) {
|
|
const SO3 R;
|
|
EXPECT_LONGS_EQUAL(3, R.rows());
|
|
EXPECT_LONGS_EQUAL(3, SO3::dimension);
|
|
EXPECT_LONGS_EQUAL(3, SO3::Dim());
|
|
EXPECT_LONGS_EQUAL(3, R.dim());
|
|
EXPECT_LONGS_EQUAL(3, traits<SO3>::GetDimension(R));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Concept) {
|
|
BOOST_CONCEPT_ASSERT((IsGroup<SO3>));
|
|
BOOST_CONCEPT_ASSERT((IsManifold<SO3>));
|
|
BOOST_CONCEPT_ASSERT((IsLieGroup<SO3>));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Constructors) {
|
|
const Vector3 axis(0, 0, 1);
|
|
const double angle = 2.5;
|
|
SO3 Q(Eigen::AngleAxisd(angle, axis));
|
|
SO3 R = SO3::AxisAngle(axis, angle);
|
|
EXPECT(assert_equal(Q, R));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(SO3, ClosestTo) {
|
|
Matrix3 M;
|
|
M << 0.79067393, 0.6051136, -0.0930814, //
|
|
0.4155925, -0.64214347, -0.64324489, //
|
|
-0.44948549, 0.47046326, -0.75917576;
|
|
|
|
Matrix expected(3, 3);
|
|
expected << 0.790687, 0.605096, -0.0931312, //
|
|
0.415746, -0.642355, -0.643844, //
|
|
-0.449411, 0.47036, -0.759468;
|
|
|
|
auto actual = SO3::ClosestTo(3 * M);
|
|
EXPECT(assert_equal(expected, actual.matrix(), 1e-6));
|
|
}
|
|
|
|
//******************************************************************************
|
|
namespace {
|
|
SO3 id;
|
|
Vector3 z_axis(0, 0, 1), v2(1, 2, 0), v3(1, 2, 3);
|
|
SO3 R1(Eigen::AngleAxisd(0.1, z_axis));
|
|
SO3 R2(Eigen::AngleAxisd(0.2, z_axis));
|
|
} // namespace
|
|
|
|
/* ************************************************************************* */
|
|
TEST(SO3, ChordalMean) {
|
|
std::vector<SO3> rotations = {R1, R1.inverse()};
|
|
EXPECT(assert_equal(SO3(), SO3::ChordalMean(rotations)));
|
|
}
|
|
|
|
//******************************************************************************
|
|
// Check that Hat specialization is equal to dynamic version
|
|
TEST(SO3, Hat) {
|
|
EXPECT(assert_equal(SO3::Hat(z_axis), SOn::Hat(z_axis)));
|
|
EXPECT(assert_equal(SO3::Hat(v2), SOn::Hat(v2)));
|
|
EXPECT(assert_equal(SO3::Hat(v3), SOn::Hat(v3)));
|
|
}
|
|
|
|
//******************************************************************************
|
|
// Check that Hat specialization is equal to dynamic version
|
|
TEST(SO3, Vee) {
|
|
auto X1 = SOn::Hat(z_axis), X2 = SOn::Hat(v2), X3 = SOn::Hat(v3);
|
|
EXPECT(assert_equal(SO3::Vee(X1), SOn::Vee(X1)));
|
|
EXPECT(assert_equal(SO3::Vee(X2), SOn::Vee(X2)));
|
|
EXPECT(assert_equal(SO3::Vee(X3), SOn::Vee(X3)));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Local) {
|
|
Vector3 expected(0, 0, 0.1);
|
|
Vector3 actual = traits<SO3>::Local(R1, R2);
|
|
EXPECT(assert_equal((Vector)expected, actual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Retract) {
|
|
Vector3 v(0, 0, 0.1);
|
|
SO3 actual = traits<SO3>::Retract(R1, v);
|
|
EXPECT(assert_equal(R2, actual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Logmap) {
|
|
Vector3 expected(0, 0, 0.1);
|
|
Vector3 actual = SO3::Logmap(R1.between(R2));
|
|
EXPECT(assert_equal((Vector)expected, actual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Expmap) {
|
|
Vector3 v(0, 0, 0.1);
|
|
SO3 actual = R1 * SO3::Expmap(v);
|
|
EXPECT(assert_equal(R2, actual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, Invariants) {
|
|
EXPECT(check_group_invariants(id, id));
|
|
EXPECT(check_group_invariants(id, R1));
|
|
EXPECT(check_group_invariants(R2, id));
|
|
EXPECT(check_group_invariants(R2, R1));
|
|
|
|
EXPECT(check_manifold_invariants(id, id));
|
|
EXPECT(check_manifold_invariants(id, R1));
|
|
EXPECT(check_manifold_invariants(R2, id));
|
|
EXPECT(check_manifold_invariants(R2, R1));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, LieGroupDerivatives) {
|
|
CHECK_LIE_GROUP_DERIVATIVES(id, id);
|
|
CHECK_LIE_GROUP_DERIVATIVES(id, R2);
|
|
CHECK_LIE_GROUP_DERIVATIVES(R2, id);
|
|
CHECK_LIE_GROUP_DERIVATIVES(R2, R1);
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ChartDerivatives) {
|
|
CHECK_CHART_DERIVATIVES(id, id);
|
|
CHECK_CHART_DERIVATIVES(id, R2);
|
|
CHECK_CHART_DERIVATIVES(R2, id);
|
|
CHECK_CHART_DERIVATIVES(R2, R1);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(SO3, ExpmapFunctor) {
|
|
Vector axis = Vector3(0., 1., 0.); // rotation around Y
|
|
double angle = 3.14 / 4.0;
|
|
Matrix expected(3,3);
|
|
expected << 0.707388, 0, 0.706825, 0, 1, 0, -0.706825, 0, 0.707388;
|
|
|
|
// axis angle version
|
|
so3::ExpmapFunctor f1(axis, angle);
|
|
SO3 actual1 = f1.expmap();
|
|
CHECK(assert_equal(expected, actual1.matrix(), 1e-5));
|
|
|
|
// axis angle version, negative angle
|
|
so3::ExpmapFunctor f2(axis, angle - 2*M_PI);
|
|
SO3 actual2 = f2.expmap();
|
|
CHECK(assert_equal(expected, actual2.matrix(), 1e-5));
|
|
|
|
// omega version
|
|
so3::ExpmapFunctor f3(axis * angle);
|
|
SO3 actual3 = f3.expmap();
|
|
CHECK(assert_equal(expected, actual3.matrix(), 1e-5));
|
|
|
|
// omega version, negative angle
|
|
so3::ExpmapFunctor f4(axis * (angle - 2*M_PI));
|
|
SO3 actual4 = f4.expmap();
|
|
CHECK(assert_equal(expected, actual4.matrix(), 1e-5));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
namespace exmap_derivative {
|
|
static const Vector3 w(0.1, 0.27, -0.2);
|
|
}
|
|
// Left trivialized Derivative of exp(w) wrpt w:
|
|
// How does exp(w) change when w changes?
|
|
// We find a y such that: exp(w) exp(y) = exp(w + dw) for dw --> 0
|
|
// => y = log (exp(-w) * exp(w+dw))
|
|
Vector3 testDexpL(const Vector3& dw) {
|
|
using exmap_derivative::w;
|
|
return SO3::Logmap(SO3::Expmap(-w) * SO3::Expmap(w + dw));
|
|
}
|
|
|
|
TEST(SO3, ExpmapDerivative) {
|
|
using exmap_derivative::w;
|
|
const Matrix actualDexpL = SO3::ExpmapDerivative(w);
|
|
const Matrix expectedDexpL =
|
|
numericalDerivative11<Vector3, Vector3>(testDexpL, Vector3::Zero(), 1e-2);
|
|
EXPECT(assert_equal(expectedDexpL, actualDexpL, 1e-7));
|
|
|
|
const Matrix actualDexpInvL = SO3::LogmapDerivative(w);
|
|
EXPECT(assert_equal(expectedDexpL.inverse(), actualDexpInvL, 1e-7));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ExpmapDerivative2) {
|
|
const Vector3 theta(0.1, 0, 0.1);
|
|
const Matrix Jexpected = numericalDerivative11<SO3, Vector3>(
|
|
std::bind(&SO3::Expmap, std::placeholders::_1, nullptr), theta);
|
|
|
|
CHECK(assert_equal(Jexpected, SO3::ExpmapDerivative(theta)));
|
|
CHECK(assert_equal(Matrix3(Jexpected.transpose()),
|
|
SO3::ExpmapDerivative(-theta)));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ExpmapDerivative3) {
|
|
const Vector3 theta(10, 20, 30);
|
|
const Matrix Jexpected = numericalDerivative11<SO3, Vector3>(
|
|
std::bind(&SO3::Expmap, std::placeholders::_1, nullptr), theta);
|
|
|
|
CHECK(assert_equal(Jexpected, SO3::ExpmapDerivative(theta)));
|
|
CHECK(assert_equal(Matrix3(Jexpected.transpose()),
|
|
SO3::ExpmapDerivative(-theta)));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ExpmapDerivative4) {
|
|
// Iserles05an (Lie-group Methods) says:
|
|
// scalar is easy: d exp(a(t)) / dt = exp(a(t)) a'(t)
|
|
// matrix is hard: d exp(A(t)) / dt = exp(A(t)) dexp[-A(t)] A'(t)
|
|
// where A(t): R -> so(3) is a trajectory in the tangent space of SO(3)
|
|
// and dexp[A] is a linear map from 3*3 to 3*3 derivatives of se(3)
|
|
// Hence, the above matrix equation is typed: 3*3 = SO(3) * linear_map(3*3)
|
|
|
|
// In GTSAM, we don't work with the skew-symmetric matrices A directly, but
|
|
// with 3-vectors.
|
|
// omega is easy: d Expmap(w(t)) / dt = ExmapDerivative[w(t)] * w'(t)
|
|
|
|
// Let's verify the above formula.
|
|
|
|
auto w = [](double t) { return Vector3(2 * t, sin(t), 4 * t * t); };
|
|
auto w_dot = [](double t) { return Vector3(2, cos(t), 8 * t); };
|
|
|
|
// We define a function R
|
|
auto R = [w](double t) { return SO3::Expmap(w(t)); };
|
|
|
|
for (double t = -2.0; t < 2.0; t += 0.3) {
|
|
const Matrix expected = numericalDerivative11<SO3, double>(R, t);
|
|
const Matrix actual = SO3::ExpmapDerivative(w(t)) * w_dot(t);
|
|
CHECK(assert_equal(expected, actual, 1e-7));
|
|
}
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ExpmapDerivative5) {
|
|
auto w = [](double t) { return Vector3(2 * t, sin(t), 4 * t * t); };
|
|
auto w_dot = [](double t) { return Vector3(2, cos(t), 8 * t); };
|
|
|
|
// Now define R as mapping local coordinates to neighborhood around R0.
|
|
const SO3 R0 = SO3::Expmap(Vector3(0.1, 0.4, 0.2));
|
|
auto R = [R0, w](double t) { return R0.expmap(w(t)); };
|
|
|
|
for (double t = -2.0; t < 2.0; t += 0.3) {
|
|
const Matrix expected = numericalDerivative11<SO3, double>(R, t);
|
|
const Matrix actual = SO3::ExpmapDerivative(w(t)) * w_dot(t);
|
|
CHECK(assert_equal(expected, actual, 1e-7));
|
|
}
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ExpmapDerivative6) {
|
|
const Vector3 thetahat(0.1, 0, 0.1);
|
|
const Matrix Jexpected = numericalDerivative11<SO3, Vector3>(
|
|
std::bind(&SO3::Expmap, std::placeholders::_1, nullptr), thetahat);
|
|
Matrix3 Jactual;
|
|
SO3::Expmap(thetahat, Jactual);
|
|
EXPECT(assert_equal(Jexpected, Jactual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, LogmapDerivative) {
|
|
const Vector3 thetahat(0.1, 0, 0.1);
|
|
const SO3 R = SO3::Expmap(thetahat); // some rotation
|
|
const Matrix Jexpected = numericalDerivative11<Vector, SO3>(
|
|
std::bind(&SO3::Logmap, std::placeholders::_1, nullptr), R);
|
|
const Matrix3 Jactual = SO3::LogmapDerivative(thetahat);
|
|
EXPECT(assert_equal(Jexpected, Jactual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, JacobianLogmap) {
|
|
const Vector3 thetahat(0.1, 0, 0.1);
|
|
const SO3 R = SO3::Expmap(thetahat); // some rotation
|
|
const Matrix Jexpected = numericalDerivative11<Vector, SO3>(
|
|
std::bind(&SO3::Logmap, std::placeholders::_1, nullptr), R);
|
|
Matrix3 Jactual;
|
|
SO3::Logmap(R, Jactual);
|
|
EXPECT(assert_equal(Jexpected, Jactual));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ApplyDexp) {
|
|
Matrix aH1, aH2;
|
|
for (bool nearZeroApprox : {true, false}) {
|
|
std::function<Vector3(const Vector3&, const Vector3&)> f =
|
|
[=](const Vector3& omega, const Vector3& v) {
|
|
return so3::DexpFunctor(omega, nearZeroApprox).applyDexp(v);
|
|
};
|
|
for (Vector3 omega : {Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 1, 0),
|
|
Vector3(0, 0, 1), Vector3(0.1, 0.2, 0.3)}) {
|
|
so3::DexpFunctor local(omega, nearZeroApprox);
|
|
for (Vector3 v : {Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1),
|
|
Vector3(0.4, 0.3, 0.2)}) {
|
|
EXPECT(assert_equal(Vector3(local.dexp() * v),
|
|
local.applyDexp(v, aH1, aH2)));
|
|
EXPECT(assert_equal(numericalDerivative21(f, omega, v), aH1));
|
|
EXPECT(assert_equal(numericalDerivative22(f, omega, v), aH2));
|
|
EXPECT(assert_equal(local.dexp(), aH2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, ApplyInvDexp) {
|
|
Matrix aH1, aH2;
|
|
for (bool nearZeroApprox : {true, false}) {
|
|
std::function<Vector3(const Vector3&, const Vector3&)> f =
|
|
[=](const Vector3& omega, const Vector3& v) {
|
|
return so3::DexpFunctor(omega, nearZeroApprox).applyInvDexp(v);
|
|
};
|
|
for (Vector3 omega : {Vector3(0, 0, 0), Vector3(1, 0, 0), Vector3(0, 1, 0),
|
|
Vector3(0, 0, 1), Vector3(0.1, 0.2, 0.3)}) {
|
|
so3::DexpFunctor local(omega, nearZeroApprox);
|
|
Matrix invDexp = local.dexp().inverse();
|
|
for (Vector3 v : {Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1),
|
|
Vector3(0.4, 0.3, 0.2)}) {
|
|
EXPECT(assert_equal(Vector3(invDexp * v),
|
|
local.applyInvDexp(v, aH1, aH2)));
|
|
EXPECT(assert_equal(numericalDerivative21(f, omega, v), aH1));
|
|
EXPECT(assert_equal(numericalDerivative22(f, omega, v), aH2));
|
|
EXPECT(assert_equal(invDexp, aH2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(SO3, vec) {
|
|
const Vector9 expected = Eigen::Map<const Vector9>(R2.matrix().data());
|
|
Matrix actualH;
|
|
const Vector9 actual = R2.vec(actualH);
|
|
CHECK(assert_equal(expected, actual));
|
|
std::function<Vector9(const SO3&)> f = [](const SO3& Q) { return Q.vec(); };
|
|
const Matrix numericalH = numericalDerivative11(f, R2, 1e-5);
|
|
CHECK(assert_equal(numericalH, actualH));
|
|
}
|
|
|
|
//******************************************************************************
|
|
TEST(Matrix, compose) {
|
|
Matrix3 M;
|
|
M << 1, 2, 3, 4, 5, 6, 7, 8, 9;
|
|
SO3 R = SO3::Expmap(Vector3(1, 2, 3));
|
|
const Matrix3 expected = M * R.matrix();
|
|
Matrix actualH;
|
|
const Matrix3 actual = so3::compose(M, R, actualH);
|
|
CHECK(assert_equal(expected, actual));
|
|
std::function<Matrix3(const Matrix3&)> f = [R](const Matrix3& M) {
|
|
return so3::compose(M, R);
|
|
};
|
|
Matrix numericalH = numericalDerivative11(f, M, 1e-2);
|
|
CHECK(assert_equal(numericalH, actualH));
|
|
}
|
|
|
|
//******************************************************************************
|
|
int main() {
|
|
TestResult tr;
|
|
return TestRegistry::runAllTests(tr);
|
|
}
|
|
//******************************************************************************
|