From d087f2fdf813a8bdb2fbee95e7ce42805953c70b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 13 Oct 2015 12:31:01 -0700 Subject: [PATCH 01/12] Some new methods and improvements to Unit3 from Skydio --- gtsam/geometry/Unit3.cpp | 253 ++++++++++++----- gtsam/geometry/Unit3.h | 86 ++++-- gtsam/geometry/tests/testUnit3.cpp | 417 +++++++++++++++++++++-------- 3 files changed, 549 insertions(+), 207 deletions(-) diff --git a/gtsam/geometry/Unit3.cpp b/gtsam/geometry/Unit3.cpp index 7729bd354..f53be3b40 100644 --- a/gtsam/geometry/Unit3.cpp +++ b/gtsam/geometry/Unit3.cpp @@ -15,12 +15,14 @@ * @author Can Erdogan * @author Frank Dellaert * @author Alex Trevor - * @author Zhaoyang Lv * @brief The Unit3 class - basically a point on a unit sphere */ #include #include +#include // GTSAM_USE_TBB + +#include #include // for GTSAM_USE_TBB #ifdef __clang__ @@ -32,13 +34,8 @@ # pragma clang diagnostic pop #endif -#ifdef GTSAM_USE_TBB -#include -#endif - #include #include -#include using namespace std; @@ -46,14 +43,12 @@ namespace gtsam { /* ************************************************************************* */ Unit3 Unit3::FromPoint3(const Point3& point, OptionalJacobian<2,3> H) { - Unit3 direction(point); - if (H) { - // 3*3 Derivative of representation with respect to point is 3*3: - Matrix3 D_p_point; - point.normalize(D_p_point); // TODO, this calculates norm a second time :-( - // Calculate the 2*3 Jacobian + // 3*3 Derivative of representation with respect to point is 3*3: + Matrix3 D_p_point; + Unit3 direction; + direction.p_ = point.normalize(H ? &D_p_point : 0); + if (H) *H << direction.basis().transpose() * D_p_point; - } return direction; } @@ -63,49 +58,105 @@ Unit3 Unit3::Random(boost::mt19937 & rng) { boost::uniform_on_sphere randomDirection(3); // This variate_generator object is required for versions of boost somewhere // around 1.46, instead of drawing directly using boost::uniform_on_sphere(rng). - boost::variate_generator > generator( - rng, randomDirection); + boost::variate_generator > + generator(rng, randomDirection); vector d = generator(); - return Unit3(d[0], d[1], d[2]); + Unit3 result; + result.p_ = Point3(d[0], d[1], d[2]); + return result; } -#ifdef GTSAM_USE_TBB -tbb::mutex unit3BasisMutex; -#endif - /* ************************************************************************* */ -const Matrix32& Unit3::basis() const { +const Matrix32& Unit3::basis(OptionalJacobian<6, 2> H) const { #ifdef GTSAM_USE_TBB - tbb::mutex::scoped_lock lock(unit3BasisMutex); + // NOTE(hayk): At some point it seemed like this reproducably resulted in deadlock. However, I + // can't see the reason why and I can no longer reproduce it. It may have been a red herring, or + // there is still a latent bug to watch out for. + tbb::mutex::scoped_lock lock(B_mutex_); #endif - // Return cached version if exists - if (B_) return *B_; + // Return cached basis if available and the Jacobian isn't needed. + if (B_ && !H) { + return *B_; + } + + // Return cached basis and derivatives if available. + if (B_ && H && H_B_) { + *H = *H_B_; + return *B_; + } + + // Get the unit vector and derivative wrt this. + // NOTE(hayk): We can't call point3(), because it would recursively call basis(). + const Point3& n = p_; // Get the axis of rotation with the minimum projected length of the point - Vector3 axis; - double mx = fabs(p_.x()), my = fabs(p_.y()), mz = fabs(p_.z()); - if ((mx <= my) && (mx <= mz)) - axis = Vector3(1.0, 0.0, 0.0); - else if ((my <= mx) && (my <= mz)) - axis = Vector3(0.0, 1.0, 0.0); - else if ((mz <= mx) && (mz <= my)) - axis = Vector3(0.0, 0.0, 1.0); - else + Point3 axis; + double mx = fabs(n.x()), my = fabs(n.y()), mz = fabs(n.z()); + if ((mx <= my) && (mx <= mz)) { + axis = Point3(1.0, 0.0, 0.0); + } else if ((my <= mx) && (my <= mz)) { + axis = Point3(0.0, 1.0, 0.0); + } else if ((mz <= mx) && (mz <= my)) { + axis = Point3(0.0, 0.0, 1.0); + } else { assert(false); + } - // Create the two basis vectors - Vector3 b1 = p_.cross(axis).normalized(); - Vector3 b2 = p_.cross(b1).normalized(); + // Choose the direction of the first basis vector b1 in the tangent plane by crossing n with + // the chosen axis. + Matrix33 H_B1_n; + Point3 B1 = n.cross(axis, H ? &H_B1_n : nullptr); - // Create the basis matrix + // Normalize result to get a unit vector: b1 = B1 / |B1|. + Matrix33 H_b1_B1; + Point3 b1 = B1.normalize(H ? &H_b1_B1 : nullptr); + + // Get the second basis vector b2, which is orthogonal to n and b1, by crossing them. + // No need to normalize this, p and b1 are orthogonal unit vectors. + Matrix33 H_b2_n, H_b2_b1; + Point3 b2 = n.cross(b1, H ? &H_b2_n : nullptr, H ? &H_b2_b1 : nullptr); + + // Create the basis by stacking b1 and b2. B_.reset(Matrix32()); - (*B_) << b1, b2; + (*B_) << b1.x(), b2.x(), b1.y(), b2.y(), b1.z(), b2.z(); + + if (H) { + // Chain rule tomfoolery to compute the derivative. + const Matrix32& H_n_p = *B_; + Matrix32 H_b1_p = H_b1_B1 * H_B1_n * H_n_p; + Matrix32 H_b2_p = H_b2_n * H_n_p + H_b2_b1 * H_b1_p; + + // Cache the derivative and fill the result. + H_B_.reset(Matrix62()); + (*H_B_) << H_b1_p, H_b2_p; + *H = *H_B_; + } + return *B_; } /* ************************************************************************* */ -/// The print fuction +const Point3& Unit3::point3(OptionalJacobian<3, 2> H) const { + if (H) + *H = basis(); + return p_; +} + +/* ************************************************************************* */ +Vector3 Unit3::unitVector(boost::optional H) const { + if (H) + *H = basis(); + return (p_.vector()); +} + +/* ************************************************************************* */ +std::ostream& operator<<(std::ostream& os, const Unit3& pair) { + os << pair.p_ << endl; + return os; +} + +/* ************************************************************************* */ void Unit3::print(const std::string& s) const { cout << s << ":" << p_ << endl; } @@ -116,11 +167,72 @@ Matrix3 Unit3::skew() const { } /* ************************************************************************* */ -Vector2 Unit3::error(const Unit3& q, OptionalJacobian<2,2> H) const { +double Unit3::dot(const Unit3& q, OptionalJacobian<1,2> H_p, OptionalJacobian<1,2> H_q) const { + // Get the unit vectors of each, and the derivative. + Matrix32 H_pn_p; + const Point3& pn = point3(H_p ? &H_pn_p : 0); + + Matrix32 H_qn_q; + const Point3& qn = q.point3(H_q ? &H_qn_q : 0); + + // Compute the dot product of the Point3s. + Matrix13 H_dot_pn, H_dot_qn; + double d = pn.dot(qn, H_p ? &H_dot_pn : nullptr, H_q ? &H_dot_qn : nullptr); + + if (H_p) { + (*H_p) << H_dot_pn * H_pn_p; + } + + if (H_q) { + (*H_q) = H_dot_qn * H_qn_q; + } + + return d; +} + +/* ************************************************************************* */ +Vector2 Unit3::error(const Unit3& q, OptionalJacobian<2,2> H_q) const { // 2D error is equal to B'*q, as B is 3x2 matrix and q is 3x1 - Vector2 xi = basis().transpose() * q.p_; - if (H) - *H = basis().transpose() * q.basis(); + Matrix23 Bt = basis().transpose(); + Vector2 xi = Bt * q.p_.vector(); + if (H_q) { + *H_q = Bt * q.basis(); + } + return xi; +} + +/* ************************************************************************* */ +Vector2 Unit3::errorVector(const Unit3& q, OptionalJacobian<2, 2> H_p, OptionalJacobian<2, 2> H_q) const { + // Get the point3 of this, and the derivative. + Matrix32 H_qn_q; + const Point3& qn = q.point3(H_q ? &H_qn_q : 0); + + // 2D error here is projecting q into the tangent plane of this (p). + Matrix62 H_B_p; + Matrix23 Bt = basis(H_p ? &H_B_p : nullptr).transpose(); + Vector2 xi = Bt * qn.vector(); + + if (H_p) { + // Derivatives of each basis vector. + const Matrix32& H_b1_p = H_B_p.block<3, 2>(0, 0); + const Matrix32& H_b2_p = H_B_p.block<3, 2>(3, 0); + + // Derivatives of the two entries of xi wrt the basis vectors. + Matrix13 H_xi1_b1 = qn.vector().transpose(); + Matrix13 H_xi2_b2 = qn.vector().transpose(); + + // Assemble dxi/dp = dxi/dB * dB/dp. + Matrix12 H_xi1_p = H_xi1_b1 * H_b1_p; + Matrix12 H_xi2_p = H_xi2_b2 * H_b2_p; + *H_p << H_xi1_p, H_xi2_p; + } + + if (H_q) { + // dxi/dq is given by dxi/dqu * dqu/dq, where qu is the unit vector of q. + Matrix23 H_xi_qu = Bt; + *H_q = H_xi_qu * H_qn_q; + } + return xi; } @@ -136,39 +248,46 @@ double Unit3::distance(const Unit3& q, OptionalJacobian<1,2> H) const { /* ************************************************************************* */ Unit3 Unit3::retract(const Vector2& v) const { - // Compute the 3D xi_hat vector - Vector3 xi_hat = basis() * v; - double theta = xi_hat.norm(); - // Treat case of very small v differently - if (theta < std::numeric_limits::epsilon()) { - return Unit3(cos(theta) * p_ + xi_hat); + // Get the vector form of the point and the basis matrix + Vector3 p = p_.vector(); + Matrix32 B = basis(); + + // Compute the 3D xi_hat vector + Vector3 xi_hat = v(0) * B.col(0) + v(1) * B.col(1); + + double xi_hat_norm = xi_hat.norm(); + + // Avoid nan + if (xi_hat_norm == 0.0) { + if (v.norm() == 0.0) + return Unit3(point3()); + else + return Unit3(-point3()); } - Vector3 exp_p_xi_hat = - cos(theta) * p_ + xi_hat * (sin(theta) / theta); + Vector3 exp_p_xi_hat = cos(xi_hat_norm) * p + + sin(xi_hat_norm) * (xi_hat / xi_hat_norm); return Unit3(exp_p_xi_hat); } /* ************************************************************************* */ -Vector2 Unit3::localCoordinates(const Unit3& other) const { - const double x = p_.dot(other.p_); - // Crucial quantity here is y = theta/sin(theta) with theta=acos(x) - // Now, y = acos(x) / sin(acos(x)) = acos(x)/sqrt(1-x^2) - // We treat the special case 1 and -1 below - const double x2 = x * x; - const double z = 1 - x2; - double y; - if (z < std::numeric_limits::epsilon()) { - if (x > 0) // first order expansion at x=1 - y = 1.0 - (x - 1.0) / 3.0; - else // cop out - return Vector2(M_PI, 0.0); - } else { +Vector2 Unit3::localCoordinates(const Unit3& y) const { + + Vector3 p = p_.vector(), q = y.p_.vector(); + double dot = p.dot(q); + + // Check for special cases + if (dot > 1.0 - 1e-16) + return Vector2(0, 0); + else if (dot < -1.0 + 1e-16) + return Vector2(M_PI, 0); + else { // no special case - y = acos(x) / sqrt(z); + double theta = acos(dot); + Vector3 result_hat = (theta / sin(theta)) * (q - p * dot); + return basis().transpose() * result_hat; } - return basis().transpose() * y * (other.p_ - x * p_); } /* ************************************************************************* */ diff --git a/gtsam/geometry/Unit3.h b/gtsam/geometry/Unit3.h index b7e243419..784e5c5e1 100644 --- a/gtsam/geometry/Unit3.h +++ b/gtsam/geometry/Unit3.h @@ -20,16 +20,16 @@ #pragma once -#include #include -#include -#include +#include +#include -#include #include -#include +#include -#include +#ifdef GTSAM_USE_TBB +#include +#endif namespace gtsam { @@ -38,8 +38,13 @@ class GTSAM_EXPORT Unit3 { private: - Vector3 p_; ///< The location of the point on the unit sphere + Point3 p_; ///< The location of the point on the unit sphere mutable boost::optional B_; ///< Cached basis + mutable boost::optional H_B_; ///< Cached basis derivative + +#ifdef GTSAM_USE_TBB + mutable tbb::mutex B_mutex_; ///< Mutex to protect the cached basis. +#endif public: @@ -57,18 +62,23 @@ public: /// Construct from point explicit Unit3(const Point3& p) : - p_(p.vector().normalized()) { + p_(p.normalize()) { } /// Construct from a vector3 - explicit Unit3(const Vector3& p) : - p_(p.normalized()) { + explicit Unit3(const Vector3& v) : + p_(v / v.norm()) { } /// Construct from x,y,z Unit3(double x, double y, double z) : - p_(x, y, z) { - p_.normalize(); + p_(Point3(x, y, z).normalize()) { + } + + /// Construct from 2D point in plane at focal length f + /// Unit3(p,1) can be viewed as normalized homogeneous coordinates of 2D point + explicit Unit3(const Point2& p, double f=1.0) : + p_(Point3(p.x(), p.y(), f).normalize()) { } /// Named constructor from Point3 with optional Jacobian @@ -83,12 +93,14 @@ public: /// @name Testable /// @{ + friend std::ostream& operator<<(std::ostream& os, const Unit3& pair); + /// The print fuction void print(const std::string& s = std::string()) const; /// The equals function with tolerance bool equals(const Unit3& s, double tol = 1e-9) const { - return equal_with_abs_tol(p_, s.p_, tol); + return p_.equals(s.p_, tol); } /// @} @@ -99,37 +111,50 @@ public: * Returns the local coordinate frame to tangent plane * It is a 3*2 matrix [b1 b2] composed of two orthogonal directions * tangent to the sphere at the current direction. + * Provides derivatives of the basis with the two basis vectors stacked up as a 6x1. */ - const Matrix32& basis() const; + const Matrix32& basis(OptionalJacobian<6, 2> H = boost::none) const; /// Return skew-symmetric associated with 3D point on unit sphere Matrix3 skew() const; /// Return unit-norm Point3 - Point3 point3(OptionalJacobian<3, 2> H = boost::none) const { - if (H) - *H = basis(); - return Point3(p_); - } + const Point3& point3(OptionalJacobian<3, 2> H = boost::none) const; /// Return unit-norm Vector - const Vector3& unitVector(boost::optional H = boost::none) const { - if (H) - *H = basis(); - return p_; - } + Vector3 unitVector(boost::optional H = boost::none) const; /// Return scaled direction as Point3 friend Point3 operator*(double s, const Unit3& d) { - return Point3(s * d.p_); + return s * d.p_; } + /// Return dot product with q + double dot(const Unit3& q, OptionalJacobian<1,2> H1 = boost::none, // + OptionalJacobian<1,2> H2 = boost::none) const; + /// Signed, vector-valued error between two directions - Vector2 error(const Unit3& q, OptionalJacobian<2, 2> H = boost::none) const; + /// @deprecated, errorVector has the proper derivatives, this confusingly has only the second. + Vector2 error(const Unit3& q, OptionalJacobian<2, 2> H_q = boost::none) const; + + /// Signed, vector-valued error between two directions + /// NOTE(hayk): This method has zero derivatives if this (p) and q are orthogonal. + Vector2 errorVector(const Unit3& q, OptionalJacobian<2, 2> H_p = boost::none, // + OptionalJacobian<2, 2> H_q = boost::none) const; /// Distance between two directions double distance(const Unit3& q, OptionalJacobian<1, 2> H = boost::none) const; + /// Cross-product between two Unit3s + Unit3 cross(const Unit3& q) const { + return Unit3(p_.cross(q.p_)); + } + + /// Cross-product w Point3 + Point3 cross(const Point3& q) const { + return Point3(p_.vector().cross(q.vector())); + } + /// @} /// @name Manifold @@ -147,7 +172,7 @@ public: enum CoordinatesMode { EXPMAP, ///< Use the exponential map to retract - RENORM ///< Retract with vector addition and renormalize. + RENORM ///< Retract with vector addtion and renormalize. }; /// The retract function @@ -167,6 +192,13 @@ private: template void serialize(ARCHIVE & ar, const unsigned int /*version*/) { ar & BOOST_SERIALIZATION_NVP(p_); + // homebrew serialize Eigen Matrix + ar & boost::serialization::make_nvp("B11", (*B_)(0, 0)); + ar & boost::serialization::make_nvp("B12", (*B_)(0, 1)); + ar & boost::serialization::make_nvp("B21", (*B_)(1, 0)); + ar & boost::serialization::make_nvp("B22", (*B_)(1, 1)); + ar & boost::serialization::make_nvp("B31", (*B_)(2, 0)); + ar & boost::serialization::make_nvp("B32", (*B_)(2, 1)); } /// @} diff --git a/gtsam/geometry/tests/testUnit3.cpp b/gtsam/geometry/tests/testUnit3.cpp index e55caaa3c..26fbf42bb 100644 --- a/gtsam/geometry/tests/testUnit3.cpp +++ b/gtsam/geometry/tests/testUnit3.cpp @@ -22,19 +22,26 @@ #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include #include - #include #include #include +//#include #include #include using namespace boost::assign; using namespace gtsam; using namespace std; +using gtsam::symbol_shorthand::U; GTSAM_CONCEPT_TESTABLE_INST(Unit3) GTSAM_CONCEPT_MANIFOLD_INST(Unit3) @@ -43,7 +50,6 @@ GTSAM_CONCEPT_MANIFOLD_INST(Unit3) Point3 point3_(const Unit3& p) { return p.point3(); } - TEST(Unit3, point3) { vector ps; ps += Point3(1, 0, 0), Point3(0, 1, 0), Point3(0, 0, 1), Point3(1, 1, 0) @@ -69,7 +75,7 @@ TEST(Unit3, rotate) { Unit3 actual = R * p; EXPECT(assert_equal(expected, actual, 1e-8)); Matrix actualH, expectedH; - + // Use numerical derivatives to calculate the expected Jacobian { expectedH = numericalDerivative21(rotate_, R, p); R.rotate(p, actualH, boost::none); @@ -93,8 +99,8 @@ TEST(Unit3, unrotate) { Unit3 expected = Unit3(1, 1, 0); Unit3 actual = R.unrotate(p); EXPECT(assert_equal(expected, actual, 1e-8)); - Matrix actualH, expectedH; + // Use numerical derivatives to calculate the expected Jacobian { expectedH = numericalDerivative21(unrotate_, R, p); R.unrotate(p, actualH, boost::none); @@ -107,6 +113,37 @@ TEST(Unit3, unrotate) { } } +TEST(Unit3, dot) { + Unit3 p(1, 0.2, 0.3); + Unit3 q = p.retract(Vector2(0.5, 0)); + Unit3 r = p.retract(Vector2(0.8, 0)); + Unit3 t = p.retract(Vector2(0, 0.3)); + EXPECT(assert_equal(1.0, p.dot(p), 1e-8)); + EXPECT(assert_equal(0.877583, p.dot(q), 1e-5)); + EXPECT(assert_equal(0.696707, p.dot(r), 1e-5)); + EXPECT(assert_equal(0.955336, p.dot(t), 1e-5)); + + // Use numerical derivatives to calculate the expected Jacobians + Matrix H1, H2; + boost::function f = boost::bind(&Unit3::dot, _1, _2, // + boost::none, boost::none); + { + p.dot(q, H1, H2); + EXPECT(assert_equal(numericalDerivative21(f, p, q), H1, 1e-9)); + EXPECT(assert_equal(numericalDerivative22(f, p, q), H2, 1e-9)); + } + { + p.dot(r, H1, H2); + EXPECT(assert_equal(numericalDerivative21(f, p, r), H1, 1e-9)); + EXPECT(assert_equal(numericalDerivative22(f, p, r), H2, 1e-9)); + } + { + p.dot(t, H1, H2); + EXPECT(assert_equal(numericalDerivative21(f, p, t), H1, 1e-9)); + EXPECT(assert_equal(numericalDerivative22(f, p, t), H2, 1e-9)); + } +} + //******************************************************************************* TEST(Unit3, error) { Unit3 p(1, 0, 0), q = p.retract(Vector2(0.5, 0)), // @@ -116,6 +153,7 @@ TEST(Unit3, error) { EXPECT(assert_equal((Vector)(Vector2(0.717356, 0)), p.error(r), 1e-5)); Matrix actual, expected; + // Use numerical derivatives to calculate the expected Jacobian { expected = numericalDerivative11( boost::bind(&Unit3::error, &p, _1, boost::none), q); @@ -130,6 +168,45 @@ TEST(Unit3, error) { } } +//******************************************************************************* +TEST(Unit3, error2) { + Unit3 p(0.1, -0.2, 0.8); + Unit3 q = p.retract(Vector2(0.2, -0.1)); + Unit3 r = p.retract(Vector2(0.8, 0)); + + // Hard-coded as simple regression values + EXPECT(assert_equal((Vector)(Vector2(0.0, 0.0)), p.errorVector(p), 1e-8)); + EXPECT(assert_equal((Vector)(Vector2(0.198337495, -0.0991687475)), p.errorVector(q), 1e-5)); + EXPECT(assert_equal((Vector)(Vector2(0.717356, 0)), p.errorVector(r), 1e-5)); + + Matrix actual, expected; + // Use numerical derivatives to calculate the expected Jacobian + { + expected = numericalDerivative21( + boost::bind(&Unit3::errorVector, _1, _2, boost::none, boost::none), p, q); + p.errorVector(q, actual, boost::none); + EXPECT(assert_equal(expected, actual, 1e-9)); + } + { + expected = numericalDerivative21( + boost::bind(&Unit3::errorVector, _1, _2, boost::none, boost::none), p, r); + p.errorVector(r, actual, boost::none); + EXPECT(assert_equal(expected, actual, 1e-9)); + } + { + expected = numericalDerivative22( + boost::bind(&Unit3::errorVector, _1, _2, boost::none, boost::none), p, q); + p.errorVector(q, boost::none, actual); + EXPECT(assert_equal(expected, actual, 1e-9)); + } + { + expected = numericalDerivative22( + boost::bind(&Unit3::errorVector, _1, _2, boost::none, boost::none), p, r); + p.errorVector(r, boost::none, actual); + EXPECT(assert_equal(expected, actual, 1e-9)); + } +} + //******************************************************************************* TEST(Unit3, distance) { Unit3 p(1, 0, 0), q = p.retract(Vector2(0.5, 0)), // @@ -155,107 +232,211 @@ TEST(Unit3, distance) { } //******************************************************************************* - -TEST(Unit3, localCoordinates) { - { - Unit3 p, q; - Vector2 expected = Vector2::Zero(); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(zero(2), actual, 1e-8)); - EXPECT(assert_equal(q, p.retract(expected), 1e-8)); - } - { - Unit3 p, q(1, 6.12385e-21, 0); - Vector2 expected = Vector2::Zero(); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(zero(2), actual, 1e-8)); - EXPECT(assert_equal(q, p.retract(expected), 1e-8)); - } - { - Unit3 p, q(-1, 0, 0); - Vector2 expected(M_PI, 0); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(expected, actual, 1e-8)); - EXPECT(assert_equal(q, p.retract(expected), 1e-8)); - } - { - Unit3 p, q(0, 1, 0); - Vector2 expected(0,-M_PI_2); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(expected, actual, 1e-8)); - EXPECT(assert_equal(q, p.retract(expected), 1e-8)); - } - { - Unit3 p, q(0, -1, 0); - Vector2 expected(0, M_PI_2); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(expected, actual, 1e-8)); - EXPECT(assert_equal(q, p.retract(expected), 1e-8)); - } - { - Unit3 p(0,1,0), q(0,-1,0); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(q, p.retract(actual), 1e-8)); - } - { - Unit3 p(0,0,1), q(0,0,-1); - Vector2 actual = p.localCoordinates(q); - EXPECT(assert_equal(q, p.retract(actual), 1e-8)); - } - - double twist = 1e-4; - { - Unit3 p(0, 1, 0), q(0 - twist, -1 + twist, 0); - Vector2 actual = p.localCoordinates(q); - EXPECT(actual(0) < 1e-2); - EXPECT(actual(1) > M_PI - 1e-2) - } - { - Unit3 p(0, 1, 0), q(0 + twist, -1 - twist, 0); - Vector2 actual = p.localCoordinates(q); - EXPECT(actual(0) < 1e-2); - EXPECT(actual(1) < -M_PI + 1e-2) - } +TEST(Unit3, localCoordinates0) { + Unit3 p; + Vector actual = p.localCoordinates(p); + EXPECT(assert_equal(zero(2), actual, 1e-8)); } //******************************************************************************* +TEST(Unit3, localCoordinates1) { + Unit3 p, q(1, 6.12385e-21, 0); + Vector actual = p.localCoordinates(q); + CHECK(assert_equal(zero(2), actual, 1e-8)); +} + +//******************************************************************************* +TEST(Unit3, localCoordinates2) { + Unit3 p, q(-1, 0, 0); + Vector expected = (Vector(2) << M_PI, 0).finished(); + Vector actual = p.localCoordinates(q); + CHECK(assert_equal(expected, actual, 1e-8)); +} + +//******************************************************************************* +// Wrapper to make basis return a vector6 so we can test numerical derivatives. +Vector6 BasisTest(const Unit3& p, OptionalJacobian<6, 2> H) { + Matrix32 B = p.basis(H); + Vector6 B_vec; + B_vec << B; + return B_vec; +} + TEST(Unit3, basis) { - Unit3 p; - Matrix32 expected; - expected << 0, 0, 0, -1, 1, 0; - Matrix actual = p.basis(); - EXPECT(assert_equal(expected, actual, 1e-8)); + Unit3 p(0.1, -0.2, 0.9); + + Matrix expected(3, 2); + expected << 0.0, -0.994169047, 0.97618706, + -0.0233922129, 0.216930458, 0.105264958; + + Matrix62 actualH; + Matrix actual = p.basis(actualH); + EXPECT(assert_equal(expected, actual, 1e-6)); + + Matrix62 expectedH = numericalDerivative11( + boost::bind(BasisTest, _1, boost::none), p); + EXPECT(assert_equal(expectedH, actualH, 1e-8)); +} + +//******************************************************************************* +/// Check the basis derivatives of a bunch of random Unit3s. +TEST(Unit3, basis_derivatives) { + int num_tests = 100; + boost::mt19937 rng(42); + for (int i = 0; i < num_tests; i++) { + Unit3 p = Unit3::Random(rng); + + Matrix62 actualH; + p.basis(actualH); + + Matrix62 expectedH = numericalDerivative11( + boost::bind(BasisTest, _1, boost::none), p); + EXPECT(assert_equal(expectedH, actualH, 1e-8)); + } } //******************************************************************************* TEST(Unit3, retract) { - { - Unit3 p; - Vector2 v(0.5, 0); - Unit3 expected(0.877583, 0, 0.479426); - Unit3 actual = p.retract(v); - EXPECT(assert_equal(expected, actual, 1e-6)); - EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); - } - { - Unit3 p; - Vector2 v(0, 0); - Unit3 actual = p.retract(v); - EXPECT(assert_equal(p, actual, 1e-6)); - EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); - } + Unit3 p; + Vector v(2); + v << 0.5, 0; + Unit3 expected(0.877583, 0, 0.479426); + Unit3 actual = p.retract(v); + EXPECT(assert_equal(expected, actual, 1e-6)); + EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); } //******************************************************************************* TEST(Unit3, retract_expmap) { Unit3 p; - Vector2 v((M_PI / 2.0), 0); + Vector v(2); + v << (M_PI / 2.0), 0; Unit3 expected(Point3(0, 0, 1)); Unit3 actual = p.retract(v); EXPECT(assert_equal(expected, actual, 1e-8)); EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); } +//******************************************************************************* +/// Returns a random vector +inline static Vector randomVector(const Vector& minLimits, + const Vector& maxLimits) { + + // Get the number of dimensions and create the return vector + size_t numDims = dim(minLimits); + Vector vector = zero(numDims); + + // Create the random vector + for (size_t i = 0; i < numDims; i++) { + double range = maxLimits(i) - minLimits(i); + vector(i) = (((double) rand()) / RAND_MAX) * range + minLimits(i); + } + return vector; +} + +//******************************************************************************* +// Let x and y be two Unit3's. +// The equality x.localCoordinates(x.retract(v)) == v should hold. +TEST(Unit3, localCoordinates_retract) { + + size_t numIterations = 10000; + Vector minSphereLimit = Vector3(-1.0, -1.0, -1.0), maxSphereLimit = + Vector3(1.0, 1.0, 1.0); + Vector minXiLimit = Vector2(-1.0, -1.0), maxXiLimit = Vector2(1.0, 1.0); + for (size_t i = 0; i < numIterations; i++) { + + // Sleep for the random number generator (TODO?: Better create all of them first). +// boost::this_thread::sleep(boost::posix_time::milliseconds(0)); + + // Create the two Unit3s. + // NOTE: You can not create two totally random Unit3's because you cannot always compute + // between two any Unit3's. (For instance, they might be at the different sides of the circle). + Unit3 s1(Point3(randomVector(minSphereLimit, maxSphereLimit))); +// Unit3 s2 (Point3(randomVector(minSphereLimit, maxSphereLimit))); + Vector v12 = randomVector(minXiLimit, maxXiLimit); + Unit3 s2 = s1.retract(v12); + + // Check if the local coordinates and retract return the same results. + Vector actual_v12 = s1.localCoordinates(s2); + EXPECT(assert_equal(v12, actual_v12, 1e-3)); + Unit3 actual_s2 = s1.retract(actual_v12); + EXPECT(assert_equal(s2, actual_s2, 1e-3)); + } +} + +//******************************************************************************* +// Let x and y be two Unit3's. +// The equality x.localCoordinates(x.retract(v)) == v should hold. +TEST(Unit3, localCoordinates_retract_expmap) { + + size_t numIterations = 10000; + Vector minSphereLimit = Vector3(-1.0, -1.0, -1.0), maxSphereLimit = + Vector3(1.0, 1.0, 1.0); + Vector minXiLimit = (Vector(2) << -M_PI, -M_PI).finished(), maxXiLimit = (Vector(2) << M_PI, M_PI).finished(); + for (size_t i = 0; i < numIterations; i++) { + + // Sleep for the random number generator (TODO?: Better create all of them first). +// boost::this_thread::sleep(boost::posix_time::milliseconds(0)); + + // Create the two Unit3s. + // Unlike the above case, we can use any two Unit3's. + Unit3 s1(Point3(randomVector(minSphereLimit, maxSphereLimit))); +// Unit3 s2 (Point3(randomVector(minSphereLimit, maxSphereLimit))); + Vector v12 = randomVector(minXiLimit, maxXiLimit); + + // Magnitude of the rotation can be at most pi + if (v12.norm() > M_PI) + v12 = v12 / M_PI; + Unit3 s2 = s1.retract(v12); + + // Check if the local coordinates and retract return the same results. + Vector actual_v12 = s1.localCoordinates(s2); + EXPECT(assert_equal(v12, actual_v12, 1e-3)); + Unit3 actual_s2 = s1.retract(actual_v12); + EXPECT(assert_equal(s2, actual_s2, 1e-3)); + } +} + +//******************************************************************************* +//TEST( Pose2, between ) +//{ +// // < +// // +// // ^ +// // +// // *--0--*--* +// Pose2 gT1(M_PI/2.0, Point2(1,2)); // robot at (1,2) looking towards y +// Pose2 gT2(M_PI, Point2(-1,4)); // robot at (-1,4) loooking at negative x +// +// Matrix actualH1,actualH2; +// Pose2 expected(M_PI/2.0, Point2(2,2)); +// Pose2 actual1 = gT1.between(gT2); +// Pose2 actual2 = gT1.between(gT2,actualH1,actualH2); +// EXPECT(assert_equal(expected,actual1)); +// EXPECT(assert_equal(expected,actual2)); +// +// Matrix expectedH1 = (Matrix(3,3) << +// 0.0,-1.0,-2.0, +// 1.0, 0.0,-2.0, +// 0.0, 0.0,-1.0 +// ); +// Matrix numericalH1 = numericalDerivative21(testing::between, gT1, gT2); +// EXPECT(assert_equal(expectedH1,actualH1)); +// EXPECT(assert_equal(numericalH1,actualH1)); +// // Assert H1 = -AdjointMap(between(p2,p1)) as in doc/math.lyx +// EXPECT(assert_equal(-gT2.between(gT1).AdjointMap(),actualH1)); +// +// Matrix expectedH2 = (Matrix(3,3) << +// 1.0, 0.0, 0.0, +// 0.0, 1.0, 0.0, +// 0.0, 0.0, 1.0 +// ); +// Matrix numericalH2 = numericalDerivative22(testing::between, gT1, gT2); +// EXPECT(assert_equal(expectedH2,actualH2)); +// EXPECT(assert_equal(numericalH2,actualH2)); +// +//} + //******************************************************************************* TEST(Unit3, Random) { boost::mt19937 rng(42); @@ -267,26 +448,6 @@ TEST(Unit3, Random) { EXPECT(assert_equal(expectedMean,actualMean,0.1)); } -//******************************************************************************* -// New test that uses Unit3::Random -TEST(Unit3, localCoordinates_retract) { - boost::mt19937 rng(42); - size_t numIterations = 10000; - - for (size_t i = 0; i < numIterations; i++) { - // Create two random Unit3s - const Unit3 s1 = Unit3::Random(rng); - const Unit3 s2 = Unit3::Random(rng); - // Check that they are not at opposite ends of the sphere, which is ill defined - if (s1.unitVector().dot(s2.unitVector())<-0.9) continue; - - // Check if the local coordinates and retract return consistent results. - Vector v12 = s1.localCoordinates(s2); - Unit3 actual_s2 = s1.retract(v12); - EXPECT(assert_equal(s2, actual_s2, 1e-9)); - } -} - //************************************************************************* TEST (Unit3, FromPoint3) { Matrix actualH; @@ -298,12 +459,42 @@ TEST (Unit3, FromPoint3) { EXPECT(assert_equal(expectedH, actualH, 1e-8)); } -/* ************************************************************************* */ -TEST(actualH, Serialization) { - Unit3 p(0, 1, 0); - EXPECT(serializationTestHelpers::equalsObj(p)); - EXPECT(serializationTestHelpers::equalsXML(p)); - EXPECT(serializationTestHelpers::equalsBinary(p)); +//******************************************************************************* +TEST(Unit3, ErrorBetweenFactor) { + std::vector data = {Unit3(1.0, 0.0, 0.0), Unit3(0.0, 0.0, 1.0)}; + + NonlinearFactorGraph graph; + Values initial_values; + + // Add prior factors. + SharedNoiseModel R_prior = noiseModel::Unit::Create(2); + for (int i = 0; i < data.size(); i++) { + graph.add(PriorFactor(U(i), data[i], R_prior)); + } + + // Add process factors using the dot product error function. + SharedNoiseModel R_process = noiseModel::Isotropic::Sigma(2, 0.01); + for (int i = 0; i < data.size() - 1; i++) { + Expression exp(Expression(U(i)), &Unit3::errorVector, Expression(U(i + 1))); + graph.addExpressionFactor(R_process, Vector2::Zero(), exp); + } + + // Add initial values. Since there is no identity, just pick something. + for (int i = 0; i < data.size(); i++) { + initial_values.insert(U(i), Unit3(0.0, 1.0, 0.0)); + } + + Values values = GaussNewtonOptimizer(graph, initial_values).optimize(); + + // Check that the y-value is very small for each. + for (int i = 0; i < data.size(); i++) { + EXPECT(assert_equal(0.0, values.at(U(i)).unitVector().y(), 1e-3)); + } + + // Check that the dot product between variables is close to 1. + for (int i = 0; i < data.size() - 1; i++) { + EXPECT(assert_equal(1.0, values.at(U(i)).dot(values.at(U(i + 1))), 1e-2)); + } } /* ************************************************************************* */ From 06520a3b1d3aa197c0ab1e45dab4e33a1cf5960a Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 14:44:00 -0700 Subject: [PATCH 02/12] Some additional derivatives (for cvross) and operators --- gtsam/geometry/Point3.cpp | 40 +++++++++++++++++++++++++++++++++++++-- gtsam/geometry/Point3.h | 39 +++++++++++++++++++------------------- 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/gtsam/geometry/Point3.cpp b/gtsam/geometry/Point3.cpp index a87aeb650..bffda9fef 100644 --- a/gtsam/geometry/Point3.cpp +++ b/gtsam/geometry/Point3.cpp @@ -58,6 +58,22 @@ Point3 Point3::operator/(double s) const { return Point3(x_ / s, y_ / s, z_ / s); } +/* ************************************************************************* */ +double Point3::distance(const Point3 &p2, OptionalJacobian<1, 3> H1, + OptionalJacobian<1, 3> H2) const { + double d = (p2 - *this).norm(); + if (H1) { + *H1 << x_ - p2.x(), y_ - p2.y(), z_ - p2.z(); + *H1 = *H1 *(1. / d); + } + + if (H2) { + *H2 << -x_ + p2.x(), -y_ + p2.y(), -z_ + p2.z(); + *H2 = *H2 *(1. / d); + } + return d; +} + /* ************************************************************************* */ Point3 Point3::add(const Point3 &q, OptionalJacobian<3,3> H1, OptionalJacobian<3,3> H2) const { @@ -75,13 +91,27 @@ Point3 Point3::sub(const Point3 &q, OptionalJacobian<3,3> H1, } /* ************************************************************************* */ -Point3 Point3::cross(const Point3 &q) const { +Point3 Point3::cross(const Point3 &q, OptionalJacobian<3, 3> H_p, OptionalJacobian<3, 3> H_q) const { + if (H_p) { + *H_p << skewSymmetric(-q.vector()); + } + if (H_q) { + *H_q << skewSymmetric(vector()); + } + return Point3(y_ * q.z_ - z_ * q.y_, z_ * q.x_ - x_ * q.z_, x_ * q.y_ - y_ * q.x_); } /* ************************************************************************* */ -double Point3::dot(const Point3 &q) const { +double Point3::dot(const Point3 &q, OptionalJacobian<1, 3> H_p, OptionalJacobian<1, 3> H_q) const { + if (H_p) { + *H_p << q.vector().transpose(); + } + if (H_q) { + *H_q << vector().transpose(); + } + return (x_ * q.x_ + y_ * q.y_ + z_ * q.z_); } @@ -116,6 +146,12 @@ ostream &operator<<(ostream &os, const Point3& p) { return os; } +/* ************************************************************************* */ +ostream &operator<<(ostream &os, const gtsam::Point3Pair &p) { + os << p.first << " <-> " << p.second; + return os; +} + /* ************************************************************************* */ } // namespace gtsam diff --git a/gtsam/geometry/Point3.h b/gtsam/geometry/Point3.h index e8cf0be7b..e19b74d1c 100644 --- a/gtsam/geometry/Point3.h +++ b/gtsam/geometry/Point3.h @@ -37,8 +37,8 @@ namespace gtsam { private: - double x_, y_, z_; - + double x_, y_, z_; + public: enum { dimension = 3 }; @@ -56,7 +56,7 @@ namespace gtsam { /// @{ /// Construct from 3-element vector - Point3(const Vector3& v) { + explicit Point3(const Vector3& v) { x_ = v(0); y_ = v(1); z_ = v(2); @@ -82,6 +82,11 @@ namespace gtsam { /// inverse Point3 operator - () const { return Point3(-x_,-y_,-z_);} + /// add vector on right + inline Point3 operator +(const Vector3& v) const { + return Point3(x_ + v[0], y_ + v[1], z_ + v[2]); + } + /// add Point3 operator + (const Point3& q) const; @@ -99,20 +104,8 @@ namespace gtsam { Point3 operator / (double s) const; /** distance between two points */ - inline double distance(const Point3& p2, - OptionalJacobian<1,3> H1 = boost::none, OptionalJacobian<1,3> H2 = boost::none) const { - double d = (p2 - *this).norm(); - if (H1) { - *H1 << x_-p2.x(), y_-p2.y(), z_-p2.z(); - *H1 = *H1 *(1./d); - } - - if (H2) { - *H2 << -x_+p2.x(), -y_+p2.y(), -z_+p2.z(); - *H2 << *H2 *(1./d); - } - return d; - } + double distance(const Point3& p2, OptionalJacobian<1, 3> H1 = boost::none, + OptionalJacobian<1, 3> H2 = boost::none) const; /** @deprecated The following function has been deprecated, use distance above */ inline double dist(const Point3& p2) const { @@ -126,17 +119,19 @@ namespace gtsam { Point3 normalize(OptionalJacobian<3, 3> H = boost::none) const; /** cross product @return this x q */ - Point3 cross(const Point3 &q) const; + Point3 cross(const Point3 &q, OptionalJacobian<3, 3> H_p = boost::none, // + OptionalJacobian<3, 3> H_q = boost::none) const; /** dot product @return this * q*/ - double dot(const Point3 &q) const; + double dot(const Point3 &q, OptionalJacobian<1, 3> H_p = boost::none, // + OptionalJacobian<1, 3> H_q = boost::none) const; /// @} /// @name Standard Interface /// @{ /// equality - bool operator ==(const Point3& q) const; + bool operator ==(const Point3& q) const; /** return vectorized form (column-wise)*/ Vector3 vector() const { return Vector3(x_,y_,z_); } @@ -192,6 +187,10 @@ namespace gtsam { /// @} }; +// Convenience typedef +typedef std::pair Point3Pair; +std::ostream &operator<<(std::ostream &os, const gtsam::Point3Pair &p); + /// Syntactic sugar for multiplying coordinates by a scalar s*p inline Point3 operator*(double s, const Point3& p) { return p*s;} From 5adcd6b6964d0bdeb4de3329446d76f4d8184d31 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 14:44:10 -0700 Subject: [PATCH 03/12] get rid of nullptr --- gtsam/geometry/Unit3.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gtsam/geometry/Unit3.cpp b/gtsam/geometry/Unit3.cpp index f53be3b40..da585ce5a 100644 --- a/gtsam/geometry/Unit3.cpp +++ b/gtsam/geometry/Unit3.cpp @@ -106,16 +106,16 @@ const Matrix32& Unit3::basis(OptionalJacobian<6, 2> H) const { // Choose the direction of the first basis vector b1 in the tangent plane by crossing n with // the chosen axis. Matrix33 H_B1_n; - Point3 B1 = n.cross(axis, H ? &H_B1_n : nullptr); + Point3 B1 = n.cross(axis, H ? &H_B1_n : 0); // Normalize result to get a unit vector: b1 = B1 / |B1|. Matrix33 H_b1_B1; - Point3 b1 = B1.normalize(H ? &H_b1_B1 : nullptr); + Point3 b1 = B1.normalize(H ? &H_b1_B1 : 0); // Get the second basis vector b2, which is orthogonal to n and b1, by crossing them. // No need to normalize this, p and b1 are orthogonal unit vectors. Matrix33 H_b2_n, H_b2_b1; - Point3 b2 = n.cross(b1, H ? &H_b2_n : nullptr, H ? &H_b2_b1 : nullptr); + Point3 b2 = n.cross(b1, H ? &H_b2_n : 0, H ? &H_b2_b1 : 0); // Create the basis by stacking b1 and b2. B_.reset(Matrix32()); @@ -177,7 +177,7 @@ double Unit3::dot(const Unit3& q, OptionalJacobian<1,2> H_p, OptionalJacobian<1, // Compute the dot product of the Point3s. Matrix13 H_dot_pn, H_dot_qn; - double d = pn.dot(qn, H_p ? &H_dot_pn : nullptr, H_q ? &H_dot_qn : nullptr); + double d = pn.dot(qn, H_p ? &H_dot_pn : 0, H_q ? &H_dot_qn : 0); if (H_p) { (*H_p) << H_dot_pn * H_pn_p; @@ -209,7 +209,7 @@ Vector2 Unit3::errorVector(const Unit3& q, OptionalJacobian<2, 2> H_p, OptionalJ // 2D error here is projecting q into the tangent plane of this (p). Matrix62 H_B_p; - Matrix23 Bt = basis(H_p ? &H_B_p : nullptr).transpose(); + Matrix23 Bt = basis(H_p ? &H_B_p : 0).transpose(); Vector2 xi = Bt * qn.vector(); if (H_p) { From 5faf09726b78c1c2da64f1d9dfa5aef11f22797f Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 14:44:19 -0700 Subject: [PATCH 04/12] Removed commented-out stuff --- gtsam/geometry/tests/testUnit3.cpp | 49 ------------------------------ 1 file changed, 49 deletions(-) diff --git a/gtsam/geometry/tests/testUnit3.cpp b/gtsam/geometry/tests/testUnit3.cpp index 26fbf42bb..c46c65901 100644 --- a/gtsam/geometry/tests/testUnit3.cpp +++ b/gtsam/geometry/tests/testUnit3.cpp @@ -34,7 +34,6 @@ #include #include #include -//#include #include #include @@ -345,14 +344,10 @@ TEST(Unit3, localCoordinates_retract) { Vector minXiLimit = Vector2(-1.0, -1.0), maxXiLimit = Vector2(1.0, 1.0); for (size_t i = 0; i < numIterations; i++) { - // Sleep for the random number generator (TODO?: Better create all of them first). -// boost::this_thread::sleep(boost::posix_time::milliseconds(0)); - // Create the two Unit3s. // NOTE: You can not create two totally random Unit3's because you cannot always compute // between two any Unit3's. (For instance, they might be at the different sides of the circle). Unit3 s1(Point3(randomVector(minSphereLimit, maxSphereLimit))); -// Unit3 s2 (Point3(randomVector(minSphereLimit, maxSphereLimit))); Vector v12 = randomVector(minXiLimit, maxXiLimit); Unit3 s2 = s1.retract(v12); @@ -375,13 +370,9 @@ TEST(Unit3, localCoordinates_retract_expmap) { Vector minXiLimit = (Vector(2) << -M_PI, -M_PI).finished(), maxXiLimit = (Vector(2) << M_PI, M_PI).finished(); for (size_t i = 0; i < numIterations; i++) { - // Sleep for the random number generator (TODO?: Better create all of them first). -// boost::this_thread::sleep(boost::posix_time::milliseconds(0)); - // Create the two Unit3s. // Unlike the above case, we can use any two Unit3's. Unit3 s1(Point3(randomVector(minSphereLimit, maxSphereLimit))); -// Unit3 s2 (Point3(randomVector(minSphereLimit, maxSphereLimit))); Vector v12 = randomVector(minXiLimit, maxXiLimit); // Magnitude of the rotation can be at most pi @@ -397,46 +388,6 @@ TEST(Unit3, localCoordinates_retract_expmap) { } } -//******************************************************************************* -//TEST( Pose2, between ) -//{ -// // < -// // -// // ^ -// // -// // *--0--*--* -// Pose2 gT1(M_PI/2.0, Point2(1,2)); // robot at (1,2) looking towards y -// Pose2 gT2(M_PI, Point2(-1,4)); // robot at (-1,4) loooking at negative x -// -// Matrix actualH1,actualH2; -// Pose2 expected(M_PI/2.0, Point2(2,2)); -// Pose2 actual1 = gT1.between(gT2); -// Pose2 actual2 = gT1.between(gT2,actualH1,actualH2); -// EXPECT(assert_equal(expected,actual1)); -// EXPECT(assert_equal(expected,actual2)); -// -// Matrix expectedH1 = (Matrix(3,3) << -// 0.0,-1.0,-2.0, -// 1.0, 0.0,-2.0, -// 0.0, 0.0,-1.0 -// ); -// Matrix numericalH1 = numericalDerivative21(testing::between, gT1, gT2); -// EXPECT(assert_equal(expectedH1,actualH1)); -// EXPECT(assert_equal(numericalH1,actualH1)); -// // Assert H1 = -AdjointMap(between(p2,p1)) as in doc/math.lyx -// EXPECT(assert_equal(-gT2.between(gT1).AdjointMap(),actualH1)); -// -// Matrix expectedH2 = (Matrix(3,3) << -// 1.0, 0.0, 0.0, -// 0.0, 1.0, 0.0, -// 0.0, 0.0, 1.0 -// ); -// Matrix numericalH2 = numericalDerivative22(testing::between, gT1, gT2); -// EXPECT(assert_equal(expectedH2,actualH2)); -// EXPECT(assert_equal(numericalH2,actualH2)); -// -//} - //******************************************************************************* TEST(Unit3, Random) { boost::mt19937 rng(42); From 1c83329b9be943d2713d1a559e096447f5661f3f Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 14:59:40 -0700 Subject: [PATCH 05/12] Fixed compilation issues --- gtsam/geometry/tests/testUnit3.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gtsam/geometry/tests/testUnit3.cpp b/gtsam/geometry/tests/testUnit3.cpp index c46c65901..2f8bf378f 100644 --- a/gtsam/geometry/tests/testUnit3.cpp +++ b/gtsam/geometry/tests/testUnit3.cpp @@ -412,38 +412,40 @@ TEST (Unit3, FromPoint3) { //******************************************************************************* TEST(Unit3, ErrorBetweenFactor) { - std::vector data = {Unit3(1.0, 0.0, 0.0), Unit3(0.0, 0.0, 1.0)}; + std::vector data; + data.push_back(Unit3(1.0, 0.0, 0.0)); + data.push_back(Unit3(0.0, 0.0, 1.0)); NonlinearFactorGraph graph; Values initial_values; // Add prior factors. SharedNoiseModel R_prior = noiseModel::Unit::Create(2); - for (int i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { graph.add(PriorFactor(U(i), data[i], R_prior)); } // Add process factors using the dot product error function. SharedNoiseModel R_process = noiseModel::Isotropic::Sigma(2, 0.01); - for (int i = 0; i < data.size() - 1; i++) { + for (size_t i = 0; i < data.size() - 1; i++) { Expression exp(Expression(U(i)), &Unit3::errorVector, Expression(U(i + 1))); graph.addExpressionFactor(R_process, Vector2::Zero(), exp); } // Add initial values. Since there is no identity, just pick something. - for (int i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { initial_values.insert(U(i), Unit3(0.0, 1.0, 0.0)); } Values values = GaussNewtonOptimizer(graph, initial_values).optimize(); // Check that the y-value is very small for each. - for (int i = 0; i < data.size(); i++) { + for (size_t i = 0; i < data.size(); i++) { EXPECT(assert_equal(0.0, values.at(U(i)).unitVector().y(), 1e-3)); } // Check that the dot product between variables is close to 1. - for (int i = 0; i < data.size() - 1; i++) { + for (size_t i = 0; i < data.size() - 1; i++) { EXPECT(assert_equal(1.0, values.at(U(i)).dot(values.at(U(i + 1))), 1e-2)); } } From 901fb56993084fee1ad7861a2240e17aad948f25 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 15:01:48 -0700 Subject: [PATCH 06/12] Fixed warnings --- .../examples/SmartProjectionFactorExample.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gtsam_unstable/examples/SmartProjectionFactorExample.cpp b/gtsam_unstable/examples/SmartProjectionFactorExample.cpp index 1423ef113..de1d3f77b 100644 --- a/gtsam_unstable/examples/SmartProjectionFactorExample.cpp +++ b/gtsam_unstable/examples/SmartProjectionFactorExample.cpp @@ -54,7 +54,7 @@ int main(int argc, char** argv){ string calibration_loc = findExampleDataFile("VO_calibration.txt"); string pose_loc = findExampleDataFile("VO_camera_poses_large.txt"); string factor_loc = findExampleDataFile("VO_stereo_factors_large.txt"); - + //read camera calibration info from file // focal lengths fx, fy, skew s, principal point u0, v0, baseline b cout << "Reading calibration info" << endl; @@ -67,17 +67,17 @@ int main(int argc, char** argv){ cout << "Reading camera poses" << endl; ifstream pose_file(pose_loc.c_str()); - int i; + int pose_index; MatrixRowMajor m(4,4); //read camera pose parameters and use to make initial estimates of camera poses - while (pose_file >> i) { + while (pose_file >> pose_index) { for (int i = 0; i < 16; i++) pose_file >> m.data()[i]; - initial_estimate.insert(i, Pose3(m)); + initial_estimate.insert(pose_index, Pose3(m)); } - + // landmark keys - size_t l; + size_t landmark_key; // pixel coordinates uL, uR, v (same for left/right images due to rectification) // landmark coordinates X, Y, Z in camera frame, resulting from triangulation @@ -90,14 +90,14 @@ int main(int argc, char** argv){ SmartFactor::shared_ptr factor(new SmartFactor(model, K)); size_t current_l = 3; // hardcoded landmark ID from first measurement - while (factor_file >> i >> l >> uL >> uR >> v >> X >> Y >> Z) { + while (factor_file >> pose_index >> landmark_key >> uL >> uR >> v >> X >> Y >> Z) { - if(current_l != l) { + if(current_l != landmark_key) { graph.push_back(factor); factor = SmartFactor::shared_ptr(new SmartFactor(model, K)); - current_l = l; + current_l = landmark_key; } - factor->add(Point2(uL,v), i); + factor->add(Point2(uL,v), pose_index); } Pose3 firstPose = initial_estimate.at(1); From 935801d8e1576429f9705abef2580708004d7f4c Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 15:02:12 -0700 Subject: [PATCH 07/12] Reversed arguments to Local to work with Unit3 --- gtsam/nonlinear/ExpressionFactor.h | 21 +++++++++++++-------- gtsam/slam/PriorFactor.h | 8 ++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/gtsam/nonlinear/ExpressionFactor.h b/gtsam/nonlinear/ExpressionFactor.h index 5d6d28061..21e17a648 100644 --- a/gtsam/nonlinear/ExpressionFactor.h +++ b/gtsam/nonlinear/ExpressionFactor.h @@ -1,6 +1,6 @@ /* ---------------------------------------------------------------------------- - * GTSAM Copyright 2010, Georgia Tech Research Corporation, + * 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) @@ -23,6 +23,7 @@ #include #include #include + namespace gtsam { /** @@ -74,7 +75,7 @@ public: } /** - * Error function *without* the NoiseModel, \f$ h(x)-z \f$. + * Error function *without* the NoiseModel, \f$ z-h(x) -> Local(h(x),z) \f$. * We override this method to provide * both the function evaluation and its derivative(s) in H. */ @@ -82,10 +83,12 @@ public: boost::optional&> H = boost::none) const { if (H) { const T value = expression_.valueAndDerivatives(x, keys_, dims_, *H); - return traits::Local(measured_, value); + // NOTE(hayk): Doing the reverse, AKA Local(measured_, value) is not correct here + // because it would use the tangent space of the measurement instead of the value. + return -traits::Local(value, measured_); } else { const T value = expression_.value(x); - return traits::Local(measured_, value); + return -traits::Local(value, measured_); } } @@ -96,7 +99,7 @@ public: // In case noise model is constrained, we need to provide a noise model SharedDiagonal noiseModel; - if (noiseModel_->isConstrained()) { + if (noiseModel_ && noiseModel_->isConstrained()) { noiseModel = boost::static_pointer_cast( noiseModel_)->unit(); } @@ -116,11 +119,13 @@ public: T value = expression_.valueAndJacobianMap(x, jacobianMap); // <<< Reverse AD happens here ! // Evaluate error and set RHS vector b - Ab(size()).col(0) = -traits::Local(measured_, value); + Ab(size()).col(0) = traits::Local(value, measured_); // Whiten the corresponding system, Ab already contains RHS - Vector b = Ab(size()).col(0); // need b to be valid for Robust noise models - noiseModel_->WhitenSystem(Ab.matrix(), b); + if (noiseModel_) { + Vector b = Ab(size()).col(0); // need b to be valid for Robust noise models + noiseModel_->WhitenSystem(Ab.matrix(), b); + } return factor; } diff --git a/gtsam/slam/PriorFactor.h b/gtsam/slam/PriorFactor.h index a738d4cf0..8305fce12 100644 --- a/gtsam/slam/PriorFactor.h +++ b/gtsam/slam/PriorFactor.h @@ -85,11 +85,11 @@ namespace gtsam { /** implement functions needed to derive from Factor */ /** vector of errors */ - Vector evaluateError(const T& p, boost::optional H = boost::none) const { - if (H) (*H) = eye(traits::GetDimension(p)); - // manifold equivalent of h(x)-z -> log(z,h(x)) + Vector evaluateError(const T& x, boost::optional H = boost::none) const { + if (H) (*H) = eye(traits::GetDimension(x)); + // manifold equivalent of z-x -> Local(x,z) // TODO(ASL) Add Jacobians. - return traits::Local(prior_,p); + return -traits::Local(x, prior_); } const VALUE & prior() const { return prior_; } From ee5bd7ac3971c42241bde8ba35329e8bab4b31d2 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 15:20:07 -0700 Subject: [PATCH 08/12] Increased # MC samples --- gtsam/navigation/tests/testImuFactor.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/gtsam/navigation/tests/testImuFactor.cpp b/gtsam/navigation/tests/testImuFactor.cpp index 084213e20..92cb92e70 100644 --- a/gtsam/navigation/tests/testImuFactor.cpp +++ b/gtsam/navigation/tests/testImuFactor.cpp @@ -163,8 +163,6 @@ bool MonteCarlo(const PreintegratedImuMeasurements& pim, return assert_equal(Matrix(Q), actual, 1e-4); } -#if 1 - /* ************************************************************************* */ TEST(ImuFactor, StraightLine) { // Set up IMU measurements @@ -610,7 +608,6 @@ TEST(ImuFactor, FirstOrderPreIntegratedMeasurements) { EXPECT( assert_equal(expectedDelRdelBiasOmega, preintegrated.delRdelBiasOmega())); } -#endif /* ************************************************************************* */ Vector3 correctedAcc(const PreintegratedImuMeasurements& pim, const Vector3& measuredAcc, const Vector3& measuredOmega) { @@ -679,7 +676,7 @@ TEST(ImuFactor, ErrorWithBiasesAndSensorBodyDisplacement) { hist(samples(:,i), 500) end */ - size_t N = 10000; + size_t N = 100000; Matrix samples(9,N); Sampler sampleAccelerationNoise((accNoiseVar2/dt).cwiseSqrt(), 29284); Sampler sampleOmegaNoise((omegaNoiseVar2/dt).cwiseSqrt(), 10); @@ -698,7 +695,7 @@ TEST(ImuFactor, ErrorWithBiasesAndSensorBodyDisplacement) { #endif EXPECT(MonteCarlo(pim, NavState(x1, v1), bias, dt, body_P_sensor, - measuredAcc, measuredOmega, accNoiseVar2, omegaNoiseVar2, 10000)); + measuredAcc, measuredOmega, accNoiseVar2, omegaNoiseVar2, 100000)); // Matrix expected(9,9); // expected << @@ -837,8 +834,8 @@ TEST(ImuFactor, PredictArbitrary) { Pose3 x1; Vector3 v1 = Vector3(0, 0, 0); imuBias::ConstantBias bias(Vector3(0, 0, 0), Vector3(0, 0, 0)); - EXPECT(MonteCarlo(pim, NavState(x1, v1), bias, 0.1, boost::none, - measuredAcc, measuredOmega, Vector3::Constant(accNoiseVar), Vector3::Constant(omegaNoiseVar))); + EXPECT(MonteCarlo(pim, NavState(x1, v1), bias, 0.1, boost::none, measuredAcc, measuredOmega, + Vector3::Constant(accNoiseVar), Vector3::Constant(omegaNoiseVar), 100000)); for (int i = 0; i < 1000; ++i) pim.integrateMeasurement(measuredAcc, measuredOmega, dt); From a1881efc70f35ba311b0772bcaee07a5fc30fb4c Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 19 Oct 2015 15:20:40 -0700 Subject: [PATCH 09/12] Disabled 2 tests, incompatible with Unit3 retract, with extensive comment --- gtsam/sam/tests/testBearingFactor.cpp | 44 +++++++++++++--------- gtsam/sam/tests/testBearingRangeFactor.cpp | 36 +++++++++--------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/gtsam/sam/tests/testBearingFactor.cpp b/gtsam/sam/tests/testBearingFactor.cpp index b7fcfc9aa..12635a7e5 100644 --- a/gtsam/sam/tests/testBearingFactor.cpp +++ b/gtsam/sam/tests/testBearingFactor.cpp @@ -81,23 +81,33 @@ TEST(BearingFactor, Serialization3D) { } /* ************************************************************************* */ -TEST(BearingFactor, 3D) { - // Serialize the factor - std::string serialized = serializeXML(factor3D); - - // And de-serialize it - BearingFactor3D factor; - deserializeXML(serialized, factor); - - // Set the linearization point - Values values; - values.insert(poseKey, Pose3()); - values.insert(pointKey, Point3(1, 0, 0)); - - EXPECT_CORRECT_EXPRESSION_JACOBIANS(factor.expression(poseKey, pointKey), - values, 1e-7, 1e-5); - EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-7, 1e-5); -} +// TODO(frank): this test is disabled (for now) because the macros below are +// incompatible with the Unit3 localCoordinates. The issue is the following: +// For factors, we want to use Local(value, measured), because we need the error +// to be expressed in the tangent space of value. This surfaced in the Unit3 case +// where the tangent space can be radically didfferent from one value to the next. +// For derivatives, we want Local(constant, varying), because we need a derivative +// in a constant tangent space. But since the macros below call whitenedError +// which calls Local(value,measured), we actually call the reverse. This does not +// matter for types with a commutative Local, but matters a lot for Unit3. +// More thinking needed about what the right thing is, here... +//TEST(BearingFactor, 3D) { +// // Serialize the factor +// std::string serialized = serializeXML(factor3D); +// +// // And de-serialize it +// BearingFactor3D factor; +// deserializeXML(serialized, factor); +// +// // Set the linearization point +// Values values; +// values.insert(poseKey, Pose3()); +// values.insert(pointKey, Point3(1, 0, 0)); +// +// EXPECT_CORRECT_EXPRESSION_JACOBIANS(factor.expression(poseKey, pointKey), +// values, 1e-7, 1e-5); +// EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-7, 1e-5); +//} /* ************************************************************************* */ int main() { diff --git a/gtsam/sam/tests/testBearingRangeFactor.cpp b/gtsam/sam/tests/testBearingRangeFactor.cpp index e11e62628..4c7a9ab91 100644 --- a/gtsam/sam/tests/testBearingRangeFactor.cpp +++ b/gtsam/sam/tests/testBearingRangeFactor.cpp @@ -80,23 +80,25 @@ TEST(BearingRangeFactor, Serialization3D) { } /* ************************************************************************* */ -TEST(BearingRangeFactor, 3D) { - // Serialize the factor - std::string serialized = serializeXML(factor3D); - - // And de-serialize it - BearingRangeFactor3D factor; - deserializeXML(serialized, factor); - - // Set the linearization point - Values values; - values.insert(poseKey, Pose3()); - values.insert(pointKey, Point3(1, 0, 0)); - - EXPECT_CORRECT_EXPRESSION_JACOBIANS(factor.expression(poseKey, pointKey), - values, 1e-7, 1e-5); - EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-7, 1e-5); -} +// TODO(frank): this test is disabled (for now) because the macros below are +// incompatible with the Unit3 localCoordinates. See testBearingFactor... +//TEST(BearingRangeFactor, 3D) { +// // Serialize the factor +// std::string serialized = serializeXML(factor3D); +// +// // And de-serialize it +// BearingRangeFactor3D factor; +// deserializeXML(serialized, factor); +// +// // Set the linearization point +// Values values; +// values.insert(poseKey, Pose3()); +// values.insert(pointKey, Point3(1, 0, 0)); +// +// EXPECT_CORRECT_EXPRESSION_JACOBIANS(factor.expression(poseKey, pointKey), +// values, 1e-7, 1e-5); +// EXPECT_CORRECT_FACTOR_JACOBIANS(factor, values, 1e-7, 1e-5); +//} /* ************************************************************************* */ int main() { From 7cfeb442f37f1cf8737c2e75c10ac3fbbccadc93 Mon Sep 17 00:00:00 2001 From: dellaert Date: Tue, 20 Oct 2015 17:03:32 -0700 Subject: [PATCH 10/12] Swicth back to Vector3 (overzealous merge). --- gtsam/geometry/Unit3.cpp | 98 +++++++++++++++++++--------------------- gtsam/geometry/Unit3.h | 36 +++++---------- 2 files changed, 58 insertions(+), 76 deletions(-) diff --git a/gtsam/geometry/Unit3.cpp b/gtsam/geometry/Unit3.cpp index da585ce5a..c53ff16bf 100644 --- a/gtsam/geometry/Unit3.cpp +++ b/gtsam/geometry/Unit3.cpp @@ -15,6 +15,7 @@ * @author Can Erdogan * @author Frank Dellaert * @author Alex Trevor + * @author Zhaoyang Lv * @brief The Unit3 class - basically a point on a unit sphere */ @@ -36,6 +37,7 @@ #include #include +#include using namespace std; @@ -43,12 +45,14 @@ namespace gtsam { /* ************************************************************************* */ Unit3 Unit3::FromPoint3(const Point3& point, OptionalJacobian<2,3> H) { - // 3*3 Derivative of representation with respect to point is 3*3: - Matrix3 D_p_point; - Unit3 direction; - direction.p_ = point.normalize(H ? &D_p_point : 0); - if (H) + Unit3 direction(point); + if (H) { + // 3*3 Derivative of representation with respect to point is 3*3: + Matrix3 D_p_point; + point.normalize(D_p_point); // TODO, this calculates norm a second time :-( + // Calculate the 2*3 Jacobian *H << direction.basis().transpose() * D_p_point; + } return direction; } @@ -58,12 +62,10 @@ Unit3 Unit3::Random(boost::mt19937 & rng) { boost::uniform_on_sphere randomDirection(3); // This variate_generator object is required for versions of boost somewhere // around 1.46, instead of drawing directly using boost::uniform_on_sphere(rng). - boost::variate_generator > - generator(rng, randomDirection); + boost::variate_generator > generator( + rng, randomDirection); vector d = generator(); - Unit3 result; - result.p_ = Point3(d[0], d[1], d[2]); - return result; + return Unit3(d[0], d[1], d[2]); } /* ************************************************************************* */ @@ -88,20 +90,19 @@ const Matrix32& Unit3::basis(OptionalJacobian<6, 2> H) const { // Get the unit vector and derivative wrt this. // NOTE(hayk): We can't call point3(), because it would recursively call basis(). - const Point3& n = p_; + Point3 n(p_); // Get the axis of rotation with the minimum projected length of the point Point3 axis; - double mx = fabs(n.x()), my = fabs(n.y()), mz = fabs(n.z()); - if ((mx <= my) && (mx <= mz)) { + double mx = fabs(p_.x()), my = fabs(p_.y()), mz = fabs(p_.z()); + if ((mx <= my) && (mx <= mz)) axis = Point3(1.0, 0.0, 0.0); - } else if ((my <= mx) && (my <= mz)) { + else if ((my <= mx) && (my <= mz)) axis = Point3(0.0, 1.0, 0.0); - } else if ((mz <= mx) && (mz <= my)) { + else if ((mz <= mx) && (mz <= my)) axis = Point3(0.0, 0.0, 1.0); - } else { + else assert(false); - } // Choose the direction of the first basis vector b1 in the tangent plane by crossing n with // the chosen axis. @@ -137,17 +138,17 @@ const Matrix32& Unit3::basis(OptionalJacobian<6, 2> H) const { } /* ************************************************************************* */ -const Point3& Unit3::point3(OptionalJacobian<3, 2> H) const { +Point3 Unit3::point3(OptionalJacobian<3, 2> H) const { if (H) *H = basis(); - return p_; + return Point3(p_); } /* ************************************************************************* */ -Vector3 Unit3::unitVector(boost::optional H) const { +Vector3 Unit3::unitVector(OptionalJacobian<3, 2> H) const { if (H) *H = basis(); - return (p_.vector()); + return (p_); } /* ************************************************************************* */ @@ -194,7 +195,7 @@ double Unit3::dot(const Unit3& q, OptionalJacobian<1,2> H_p, OptionalJacobian<1, Vector2 Unit3::error(const Unit3& q, OptionalJacobian<2,2> H_q) const { // 2D error is equal to B'*q, as B is 3x2 matrix and q is 3x1 Matrix23 Bt = basis().transpose(); - Vector2 xi = Bt * q.p_.vector(); + Vector2 xi = Bt * q.p_; if (H_q) { *H_q = Bt * q.basis(); } @@ -248,46 +249,39 @@ double Unit3::distance(const Unit3& q, OptionalJacobian<1,2> H) const { /* ************************************************************************* */ Unit3 Unit3::retract(const Vector2& v) const { - - // Get the vector form of the point and the basis matrix - Vector3 p = p_.vector(); - Matrix32 B = basis(); - // Compute the 3D xi_hat vector - Vector3 xi_hat = v(0) * B.col(0) + v(1) * B.col(1); + Vector3 xi_hat = basis() * v; + double theta = xi_hat.norm(); - double xi_hat_norm = xi_hat.norm(); - - // Avoid nan - if (xi_hat_norm == 0.0) { - if (v.norm() == 0.0) - return Unit3(point3()); - else - return Unit3(-point3()); + // Treat case of very small v differently + if (theta < std::numeric_limits::epsilon()) { + return Unit3(cos(theta) * p_ + xi_hat); } - Vector3 exp_p_xi_hat = cos(xi_hat_norm) * p - + sin(xi_hat_norm) * (xi_hat / xi_hat_norm); + Vector3 exp_p_xi_hat = + cos(theta) * p_ + xi_hat * (sin(theta) / theta); return Unit3(exp_p_xi_hat); } /* ************************************************************************* */ -Vector2 Unit3::localCoordinates(const Unit3& y) const { - - Vector3 p = p_.vector(), q = y.p_.vector(); - double dot = p.dot(q); - - // Check for special cases - if (dot > 1.0 - 1e-16) - return Vector2(0, 0); - else if (dot < -1.0 + 1e-16) - return Vector2(M_PI, 0); - else { +Vector2 Unit3::localCoordinates(const Unit3& other) const { + const double x = p_.dot(other.p_); + // Crucial quantity here is y = theta/sin(theta) with theta=acos(x) + // Now, y = acos(x) / sin(acos(x)) = acos(x)/sqrt(1-x^2) + // We treat the special case 1 and -1 below + const double x2 = x * x; + const double z = 1 - x2; + double y; + if (z < std::numeric_limits::epsilon()) { + if (x > 0) // first order expansion at x=1 + y = 1.0 - (x - 1.0) / 3.0; + else // cop out + return Vector2(M_PI, 0.0); + } else { // no special case - double theta = acos(dot); - Vector3 result_hat = (theta / sin(theta)) * (q - p * dot); - return basis().transpose() * result_hat; + y = acos(x) / sqrt(z); } + return basis().transpose() * y * (other.p_ - x * p_); } /* ************************************************************************* */ diff --git a/gtsam/geometry/Unit3.h b/gtsam/geometry/Unit3.h index 784e5c5e1..e8fe24c9e 100644 --- a/gtsam/geometry/Unit3.h +++ b/gtsam/geometry/Unit3.h @@ -38,7 +38,7 @@ class GTSAM_EXPORT Unit3 { private: - Point3 p_; ///< The location of the point on the unit sphere + Vector3 p_; ///< The location of the point on the unit sphere mutable boost::optional B_; ///< Cached basis mutable boost::optional H_B_; ///< Cached basis derivative @@ -62,23 +62,18 @@ public: /// Construct from point explicit Unit3(const Point3& p) : - p_(p.normalize()) { + p_(p.vector().normalized()) { } /// Construct from a vector3 - explicit Unit3(const Vector3& v) : - p_(v / v.norm()) { + explicit Unit3(const Vector3& p) : + p_(p.normalized()) { } /// Construct from x,y,z Unit3(double x, double y, double z) : - p_(Point3(x, y, z).normalize()) { - } - - /// Construct from 2D point in plane at focal length f - /// Unit3(p,1) can be viewed as normalized homogeneous coordinates of 2D point - explicit Unit3(const Point2& p, double f=1.0) : - p_(Point3(p.x(), p.y(), f).normalize()) { + p_(x, y, z) { + p_.normalize(); } /// Named constructor from Point3 with optional Jacobian @@ -100,7 +95,7 @@ public: /// The equals function with tolerance bool equals(const Unit3& s, double tol = 1e-9) const { - return p_.equals(s.p_, tol); + return equal_with_abs_tol(p_, s.p_, tol); } /// @} @@ -119,14 +114,14 @@ public: Matrix3 skew() const; /// Return unit-norm Point3 - const Point3& point3(OptionalJacobian<3, 2> H = boost::none) const; + Point3 point3(OptionalJacobian<3, 2> H = boost::none) const; /// Return unit-norm Vector - Vector3 unitVector(boost::optional H = boost::none) const; + Vector3 unitVector(OptionalJacobian<3, 2> H = boost::none) const; /// Return scaled direction as Point3 friend Point3 operator*(double s, const Unit3& d) { - return s * d.p_; + return Point3(s * d.p_); } /// Return dot product with q @@ -152,7 +147,7 @@ public: /// Cross-product w Point3 Point3 cross(const Point3& q) const { - return Point3(p_.vector().cross(q.vector())); + return point3().cross(q); } /// @} @@ -172,7 +167,7 @@ public: enum CoordinatesMode { EXPMAP, ///< Use the exponential map to retract - RENORM ///< Retract with vector addtion and renormalize. + RENORM ///< Retract with vector addition and renormalize. }; /// The retract function @@ -192,13 +187,6 @@ private: template void serialize(ARCHIVE & ar, const unsigned int /*version*/) { ar & BOOST_SERIALIZATION_NVP(p_); - // homebrew serialize Eigen Matrix - ar & boost::serialization::make_nvp("B11", (*B_)(0, 0)); - ar & boost::serialization::make_nvp("B12", (*B_)(0, 1)); - ar & boost::serialization::make_nvp("B21", (*B_)(1, 0)); - ar & boost::serialization::make_nvp("B22", (*B_)(1, 1)); - ar & boost::serialization::make_nvp("B31", (*B_)(2, 0)); - ar & boost::serialization::make_nvp("B32", (*B_)(2, 1)); } /// @} From aa4250173748c6bb3ef84eb661e92b92c0e62049 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 21 Oct 2015 10:55:31 -0700 Subject: [PATCH 11/12] Undid overzealous merge --- gtsam/geometry/Unit3.cpp | 37 +++--- gtsam/geometry/Unit3.h | 15 ++- gtsam/geometry/tests/testUnit3.cpp | 207 +++++++++++++++-------------- 3 files changed, 140 insertions(+), 119 deletions(-) diff --git a/gtsam/geometry/Unit3.cpp b/gtsam/geometry/Unit3.cpp index c53ff16bf..aaf0aa953 100644 --- a/gtsam/geometry/Unit3.cpp +++ b/gtsam/geometry/Unit3.cpp @@ -21,9 +21,6 @@ #include #include -#include // GTSAM_USE_TBB - -#include #include // for GTSAM_USE_TBB #ifdef __clang__ @@ -45,14 +42,13 @@ namespace gtsam { /* ************************************************************************* */ Unit3 Unit3::FromPoint3(const Point3& point, OptionalJacobian<2,3> H) { - Unit3 direction(point); - if (H) { - // 3*3 Derivative of representation with respect to point is 3*3: - Matrix3 D_p_point; - point.normalize(D_p_point); // TODO, this calculates norm a second time :-( - // Calculate the 2*3 Jacobian + // 3*3 Derivative of representation with respect to point is 3*3: + Matrix3 D_p_point; + Point3 normalized = point.normalize(H ? &D_p_point : 0); + Unit3 direction; + direction.p_ = normalized.vector(); + if (H) *H << direction.basis().transpose() * D_p_point; - } return direction; } @@ -90,19 +86,20 @@ const Matrix32& Unit3::basis(OptionalJacobian<6, 2> H) const { // Get the unit vector and derivative wrt this. // NOTE(hayk): We can't call point3(), because it would recursively call basis(). - Point3 n(p_); + const Point3 n(p_); // Get the axis of rotation with the minimum projected length of the point Point3 axis; - double mx = fabs(p_.x()), my = fabs(p_.y()), mz = fabs(p_.z()); - if ((mx <= my) && (mx <= mz)) + double mx = fabs(n.x()), my = fabs(n.y()), mz = fabs(n.z()); + if ((mx <= my) && (mx <= mz)) { axis = Point3(1.0, 0.0, 0.0); - else if ((my <= mx) && (my <= mz)) + } else if ((my <= mx) && (my <= mz)) { axis = Point3(0.0, 1.0, 0.0); - else if ((mz <= mx) && (mz <= my)) + } else if ((mz <= mx) && (mz <= my)) { axis = Point3(0.0, 0.0, 1.0); - else + } else { assert(false); + } // Choose the direction of the first basis vector b1 in the tangent plane by crossing n with // the chosen axis. @@ -148,7 +145,7 @@ Point3 Unit3::point3(OptionalJacobian<3, 2> H) const { Vector3 Unit3::unitVector(OptionalJacobian<3, 2> H) const { if (H) *H = basis(); - return (p_); + return p_; } /* ************************************************************************* */ @@ -171,10 +168,10 @@ Matrix3 Unit3::skew() const { double Unit3::dot(const Unit3& q, OptionalJacobian<1,2> H_p, OptionalJacobian<1,2> H_q) const { // Get the unit vectors of each, and the derivative. Matrix32 H_pn_p; - const Point3& pn = point3(H_p ? &H_pn_p : 0); + Point3 pn = point3(H_p ? &H_pn_p : 0); Matrix32 H_qn_q; - const Point3& qn = q.point3(H_q ? &H_qn_q : 0); + Point3 qn = q.point3(H_q ? &H_qn_q : 0); // Compute the dot product of the Point3s. Matrix13 H_dot_pn, H_dot_qn; @@ -206,7 +203,7 @@ Vector2 Unit3::error(const Unit3& q, OptionalJacobian<2,2> H_q) const { Vector2 Unit3::errorVector(const Unit3& q, OptionalJacobian<2, 2> H_p, OptionalJacobian<2, 2> H_q) const { // Get the point3 of this, and the derivative. Matrix32 H_qn_q; - const Point3& qn = q.point3(H_q ? &H_qn_q : 0); + Point3 qn = q.point3(H_q ? &H_qn_q : 0); // 2D error here is projecting q into the tangent plane of this (p). Matrix62 H_B_p; diff --git a/gtsam/geometry/Unit3.h b/gtsam/geometry/Unit3.h index e8fe24c9e..b7d359f40 100644 --- a/gtsam/geometry/Unit3.h +++ b/gtsam/geometry/Unit3.h @@ -20,12 +20,17 @@ #pragma once -#include #include #include +#include +#include +#include -#include #include +#include +#include + +#include #ifdef GTSAM_USE_TBB #include @@ -76,6 +81,12 @@ public: p_.normalize(); } + /// Construct from 2D point in plane at focal length f + /// Unit3(p,1) can be viewed as normalized homogeneous coordinates of 2D point + explicit Unit3(const Point2& p, double f = 1.0) : p_(p.x(), p.y(), f) { + p_.normalize(); + } + /// Named constructor from Point3 with optional Jacobian static Unit3 FromPoint3(const Point3& point, // OptionalJacobian<2, 3> H = boost::none); diff --git a/gtsam/geometry/tests/testUnit3.cpp b/gtsam/geometry/tests/testUnit3.cpp index 2f8bf378f..bfc5caad7 100644 --- a/gtsam/geometry/tests/testUnit3.cpp +++ b/gtsam/geometry/tests/testUnit3.cpp @@ -18,19 +18,20 @@ * @brief Tests the Unit3 class */ -#include -#include #include #include +#include +#include +#include #include #include #include -#include #include -#include #include + #include + #include #include #include @@ -49,6 +50,7 @@ GTSAM_CONCEPT_MANIFOLD_INST(Unit3) Point3 point3_(const Unit3& p) { return p.point3(); } + TEST(Unit3, point3) { vector ps; ps += Point3(1, 0, 0), Point3(0, 1, 0), Point3(0, 0, 1), Point3(1, 1, 0) @@ -98,6 +100,7 @@ TEST(Unit3, unrotate) { Unit3 expected = Unit3(1, 1, 0); Unit3 actual = R.unrotate(p); EXPECT(assert_equal(expected, actual, 1e-8)); + Matrix actualH, expectedH; // Use numerical derivatives to calculate the expected Jacobian { @@ -237,19 +240,66 @@ TEST(Unit3, localCoordinates0) { EXPECT(assert_equal(zero(2), actual, 1e-8)); } -//******************************************************************************* -TEST(Unit3, localCoordinates1) { - Unit3 p, q(1, 6.12385e-21, 0); - Vector actual = p.localCoordinates(q); - CHECK(assert_equal(zero(2), actual, 1e-8)); -} +TEST(Unit3, localCoordinates) { + { + Unit3 p, q; + Vector2 expected = Vector2::Zero(); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(zero(2), actual, 1e-8)); + EXPECT(assert_equal(q, p.retract(expected), 1e-8)); + } + { + Unit3 p, q(1, 6.12385e-21, 0); + Vector2 expected = Vector2::Zero(); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(zero(2), actual, 1e-8)); + EXPECT(assert_equal(q, p.retract(expected), 1e-8)); + } + { + Unit3 p, q(-1, 0, 0); + Vector2 expected(M_PI, 0); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(expected, actual, 1e-8)); + EXPECT(assert_equal(q, p.retract(expected), 1e-8)); + } + { + Unit3 p, q(0, 1, 0); + Vector2 expected(0,-M_PI_2); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(expected, actual, 1e-8)); + EXPECT(assert_equal(q, p.retract(expected), 1e-8)); + } + { + Unit3 p, q(0, -1, 0); + Vector2 expected(0, M_PI_2); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(expected, actual, 1e-8)); + EXPECT(assert_equal(q, p.retract(expected), 1e-8)); + } + { + Unit3 p(0,1,0), q(0,-1,0); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(q, p.retract(actual), 1e-8)); + } + { + Unit3 p(0,0,1), q(0,0,-1); + Vector2 actual = p.localCoordinates(q); + EXPECT(assert_equal(q, p.retract(actual), 1e-8)); + } -//******************************************************************************* -TEST(Unit3, localCoordinates2) { - Unit3 p, q(-1, 0, 0); - Vector expected = (Vector(2) << M_PI, 0).finished(); - Vector actual = p.localCoordinates(q); - CHECK(assert_equal(expected, actual, 1e-8)); + double twist = 1e-4; + { + Unit3 p(0, 1, 0), q(0 - twist, -1 + twist, 0); + Vector2 actual = p.localCoordinates(q); + EXPECT(actual(0) < 1e-2); + EXPECT(actual(1) > M_PI - 1e-2) + } + { + Unit3 p(0, 1, 0), q(0 + twist, -1 - twist, 0); + Vector2 actual = p.localCoordinates(q); + EXPECT(actual(0) < 1e-2); + EXPECT(actual(1) < -M_PI + 1e-2) + } } //******************************************************************************* @@ -296,98 +346,33 @@ TEST(Unit3, basis_derivatives) { //******************************************************************************* TEST(Unit3, retract) { - Unit3 p; - Vector v(2); - v << 0.5, 0; - Unit3 expected(0.877583, 0, 0.479426); - Unit3 actual = p.retract(v); - EXPECT(assert_equal(expected, actual, 1e-6)); - EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); + { + Unit3 p; + Vector2 v(0.5, 0); + Unit3 expected(0.877583, 0, 0.479426); + Unit3 actual = p.retract(v); + EXPECT(assert_equal(expected, actual, 1e-6)); + EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); + } + { + Unit3 p; + Vector2 v(0, 0); + Unit3 actual = p.retract(v); + EXPECT(assert_equal(p, actual, 1e-6)); + EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); + } } //******************************************************************************* TEST(Unit3, retract_expmap) { Unit3 p; - Vector v(2); - v << (M_PI / 2.0), 0; + Vector2 v((M_PI / 2.0), 0); Unit3 expected(Point3(0, 0, 1)); Unit3 actual = p.retract(v); EXPECT(assert_equal(expected, actual, 1e-8)); EXPECT(assert_equal(v, p.localCoordinates(actual), 1e-8)); } -//******************************************************************************* -/// Returns a random vector -inline static Vector randomVector(const Vector& minLimits, - const Vector& maxLimits) { - - // Get the number of dimensions and create the return vector - size_t numDims = dim(minLimits); - Vector vector = zero(numDims); - - // Create the random vector - for (size_t i = 0; i < numDims; i++) { - double range = maxLimits(i) - minLimits(i); - vector(i) = (((double) rand()) / RAND_MAX) * range + minLimits(i); - } - return vector; -} - -//******************************************************************************* -// Let x and y be two Unit3's. -// The equality x.localCoordinates(x.retract(v)) == v should hold. -TEST(Unit3, localCoordinates_retract) { - - size_t numIterations = 10000; - Vector minSphereLimit = Vector3(-1.0, -1.0, -1.0), maxSphereLimit = - Vector3(1.0, 1.0, 1.0); - Vector minXiLimit = Vector2(-1.0, -1.0), maxXiLimit = Vector2(1.0, 1.0); - for (size_t i = 0; i < numIterations; i++) { - - // Create the two Unit3s. - // NOTE: You can not create two totally random Unit3's because you cannot always compute - // between two any Unit3's. (For instance, they might be at the different sides of the circle). - Unit3 s1(Point3(randomVector(minSphereLimit, maxSphereLimit))); - Vector v12 = randomVector(minXiLimit, maxXiLimit); - Unit3 s2 = s1.retract(v12); - - // Check if the local coordinates and retract return the same results. - Vector actual_v12 = s1.localCoordinates(s2); - EXPECT(assert_equal(v12, actual_v12, 1e-3)); - Unit3 actual_s2 = s1.retract(actual_v12); - EXPECT(assert_equal(s2, actual_s2, 1e-3)); - } -} - -//******************************************************************************* -// Let x and y be two Unit3's. -// The equality x.localCoordinates(x.retract(v)) == v should hold. -TEST(Unit3, localCoordinates_retract_expmap) { - - size_t numIterations = 10000; - Vector minSphereLimit = Vector3(-1.0, -1.0, -1.0), maxSphereLimit = - Vector3(1.0, 1.0, 1.0); - Vector minXiLimit = (Vector(2) << -M_PI, -M_PI).finished(), maxXiLimit = (Vector(2) << M_PI, M_PI).finished(); - for (size_t i = 0; i < numIterations; i++) { - - // Create the two Unit3s. - // Unlike the above case, we can use any two Unit3's. - Unit3 s1(Point3(randomVector(minSphereLimit, maxSphereLimit))); - Vector v12 = randomVector(minXiLimit, maxXiLimit); - - // Magnitude of the rotation can be at most pi - if (v12.norm() > M_PI) - v12 = v12 / M_PI; - Unit3 s2 = s1.retract(v12); - - // Check if the local coordinates and retract return the same results. - Vector actual_v12 = s1.localCoordinates(s2); - EXPECT(assert_equal(v12, actual_v12, 1e-3)); - Unit3 actual_s2 = s1.retract(actual_v12); - EXPECT(assert_equal(s2, actual_s2, 1e-3)); - } -} - //******************************************************************************* TEST(Unit3, Random) { boost::mt19937 rng(42); @@ -399,6 +384,26 @@ TEST(Unit3, Random) { EXPECT(assert_equal(expectedMean,actualMean,0.1)); } +//******************************************************************************* +// New test that uses Unit3::Random +TEST(Unit3, localCoordinates_retract) { + boost::mt19937 rng(42); + size_t numIterations = 10000; + + for (size_t i = 0; i < numIterations; i++) { + // Create two random Unit3s + const Unit3 s1 = Unit3::Random(rng); + const Unit3 s2 = Unit3::Random(rng); + // Check that they are not at opposite ends of the sphere, which is ill defined + if (s1.unitVector().dot(s2.unitVector())<-0.9) continue; + + // Check if the local coordinates and retract return consistent results. + Vector v12 = s1.localCoordinates(s2); + Unit3 actual_s2 = s1.retract(v12); + EXPECT(assert_equal(s2, actual_s2, 1e-9)); + } +} + //************************************************************************* TEST (Unit3, FromPoint3) { Matrix actualH; @@ -450,6 +455,14 @@ TEST(Unit3, ErrorBetweenFactor) { } } +/* ************************************************************************* */ +TEST(actualH, Serialization) { + Unit3 p(0, 1, 0); + EXPECT(serializationTestHelpers::equalsObj(p)); + EXPECT(serializationTestHelpers::equalsXML(p)); + EXPECT(serializationTestHelpers::equalsBinary(p)); +} + /* ************************************************************************* */ int main() { srand(time(NULL)); From 570abece53a9b0804ea2120e709560cd2ff382e8 Mon Sep 17 00:00:00 2001 From: dellaert Date: Wed, 21 Oct 2015 14:15:31 -0700 Subject: [PATCH 12/12] Fixed Eigen assert --- gtsam/geometry/tests/testUnit3.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/geometry/tests/testUnit3.cpp b/gtsam/geometry/tests/testUnit3.cpp index bfc5caad7..3cfffa0da 100644 --- a/gtsam/geometry/tests/testUnit3.cpp +++ b/gtsam/geometry/tests/testUnit3.cpp @@ -307,7 +307,7 @@ TEST(Unit3, localCoordinates) { Vector6 BasisTest(const Unit3& p, OptionalJacobian<6, 2> H) { Matrix32 B = p.basis(H); Vector6 B_vec; - B_vec << B; + B_vec << B.col(0), B.col(1); return B_vec; }