123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587 |
- /* ----------------------------------------------------------------------------
- * 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 testNonlinearEquality.cpp
- * @author Alex Cunningham
- */
- #include <tests/simulated2DConstraints.h>
- #include <gtsam/nonlinear/PriorFactor.h>
- #include <gtsam/slam/ProjectionFactor.h>
- #include <gtsam/nonlinear/NonlinearEquality.h>
- #include <gtsam/nonlinear/NonlinearFactorGraph.h>
- #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
- #include <gtsam/linear/GaussianBayesNet.h>
- #include <gtsam/inference/Symbol.h>
- #include <gtsam/geometry/Point2.h>
- #include <gtsam/geometry/Pose2.h>
- #include <gtsam/geometry/Point3.h>
- #include <gtsam/geometry/Pose3.h>
- #include <gtsam/geometry/Cal3_S2.h>
- #include <gtsam/geometry/PinholeCamera.h>
- #include <CppUnitLite/TestHarness.h>
- using namespace std;
- using namespace gtsam;
- namespace eq2D = simulated2D::equality_constraints;
- static const double tol = 1e-5;
- typedef PriorFactor<Pose2> PosePrior;
- typedef NonlinearEquality<Pose2> PoseNLE;
- typedef boost::shared_ptr<PoseNLE> shared_poseNLE;
- static Symbol key('x', 1);
- //******************************************************************************
- TEST ( NonlinearEquality, linearization ) {
- Pose2 value = Pose2(2.1, 1.0, 2.0);
- Values linearize;
- linearize.insert(key, value);
- // create a nonlinear equality constraint
- shared_poseNLE nle(new PoseNLE(key, value));
- // check linearize
- SharedDiagonal constraintModel = noiseModel::Constrained::All(3);
- JacobianFactor expLF(key, I_3x3, Z_3x1, constraintModel);
- GaussianFactor::shared_ptr actualLF = nle->linearize(linearize);
- EXPECT(assert_equal((const GaussianFactor&)expLF, *actualLF));
- }
- //******************************************************************************
- TEST ( NonlinearEquality, linearization_pose ) {
- Symbol key('x', 1);
- Pose2 value;
- Values config;
- config.insert(key, value);
- // create a nonlinear equality constraint
- shared_poseNLE nle(new PoseNLE(key, value));
- GaussianFactor::shared_ptr actualLF = nle->linearize(config);
- EXPECT(true);
- }
- //******************************************************************************
- TEST ( NonlinearEquality, linearization_fail ) {
- Pose2 value = Pose2(2.1, 1.0, 2.0);
- Pose2 wrong = Pose2(2.1, 3.0, 4.0);
- Values bad_linearize;
- bad_linearize.insert(key, wrong);
- // create a nonlinear equality constraint
- shared_poseNLE nle(new PoseNLE(key, value));
- // check linearize to ensure that it fails for bad linearization points
- CHECK_EXCEPTION(nle->linearize(bad_linearize), std::invalid_argument);
- }
- //******************************************************************************
- TEST ( NonlinearEquality, linearization_fail_pose ) {
- Symbol key('x', 1);
- Pose2 value(2.0, 1.0, 2.0), wrong(2.0, 3.0, 4.0);
- Values bad_linearize;
- bad_linearize.insert(key, wrong);
- // create a nonlinear equality constraint
- shared_poseNLE nle(new PoseNLE(key, value));
- // check linearize to ensure that it fails for bad linearization points
- CHECK_EXCEPTION(nle->linearize(bad_linearize), std::invalid_argument);
- }
- //******************************************************************************
- TEST ( NonlinearEquality, linearization_fail_pose_origin ) {
- Symbol key('x', 1);
- Pose2 value, wrong(2.0, 3.0, 4.0);
- Values bad_linearize;
- bad_linearize.insert(key, wrong);
- // create a nonlinear equality constraint
- shared_poseNLE nle(new PoseNLE(key, value));
- // check linearize to ensure that it fails for bad linearization points
- CHECK_EXCEPTION(nle->linearize(bad_linearize), std::invalid_argument);
- }
- //******************************************************************************
- TEST ( NonlinearEquality, error ) {
- Pose2 value = Pose2(2.1, 1.0, 2.0);
- Pose2 wrong = Pose2(2.1, 3.0, 4.0);
- Values feasible, bad_linearize;
- feasible.insert(key, value);
- bad_linearize.insert(key, wrong);
- // create a nonlinear equality constraint
- shared_poseNLE nle(new PoseNLE(key, value));
- // check error function outputs
- Vector actual = nle->unwhitenedError(feasible);
- EXPECT(assert_equal(actual, Z_3x1));
- actual = nle->unwhitenedError(bad_linearize);
- EXPECT(
- assert_equal(actual, Vector::Constant(3, std::numeric_limits<double>::infinity())));
- }
- //******************************************************************************
- TEST ( NonlinearEquality, equals ) {
- Pose2 value1 = Pose2(2.1, 1.0, 2.0);
- Pose2 value2 = Pose2(2.1, 3.0, 4.0);
- // create some constraints to compare
- shared_poseNLE nle1(new PoseNLE(key, value1));
- shared_poseNLE nle2(new PoseNLE(key, value1));
- shared_poseNLE nle3(new PoseNLE(key, value2));
- // verify
- EXPECT(nle1->equals(*nle2));
- // basic equality = true
- EXPECT(nle2->equals(*nle1));
- // test symmetry of equals()
- EXPECT(!nle1->equals(*nle3));
- // test config
- }
- //******************************************************************************
- TEST ( NonlinearEquality, allow_error_pose ) {
- Symbol key1('x', 1);
- Pose2 feasible1(1.0, 2.0, 3.0);
- double error_gain = 500.0;
- PoseNLE nle(key1, feasible1, error_gain);
- // the unwhitened error should provide logmap to the feasible state
- Pose2 badPoint1(0.0, 2.0, 3.0);
- Vector actVec = nle.evaluateError(badPoint1);
- Vector expVec = Vector3(-0.989992, -0.14112, 0.0);
- EXPECT(assert_equal(expVec, actVec, 1e-5));
- // the actual error should have a gain on it
- Values config;
- config.insert(key1, badPoint1);
- double actError = nle.error(config);
- DOUBLES_EQUAL(500.0, actError, 1e-9);
- // check linearization
- GaussianFactor::shared_ptr actLinFactor = nle.linearize(config);
- Matrix A1 = I_3x3;
- Vector b = expVec;
- SharedDiagonal model = noiseModel::Constrained::All(3);
- GaussianFactor::shared_ptr expLinFactor(
- new JacobianFactor(key1, A1, b, model));
- EXPECT(assert_equal(*expLinFactor, *actLinFactor, 1e-5));
- }
- //******************************************************************************
- TEST ( NonlinearEquality, allow_error_optimize ) {
- Symbol key1('x', 1);
- Pose2 feasible1(1.0, 2.0, 3.0);
- double error_gain = 500.0;
- PoseNLE nle(key1, feasible1, error_gain);
- // add to a graph
- NonlinearFactorGraph graph;
- graph += nle;
- // initialize away from the ideal
- Pose2 initPose(0.0, 2.0, 3.0);
- Values init;
- init.insert(key1, initPose);
- // optimize
- Ordering ordering;
- ordering.push_back(key1);
- Values result = LevenbergMarquardtOptimizer(graph, init, ordering).optimize();
- // verify
- Values expected;
- expected.insert(key1, feasible1);
- EXPECT(assert_equal(expected, result));
- }
- //******************************************************************************
- TEST ( NonlinearEquality, allow_error_optimize_with_factors ) {
- // create a hard constraint
- Symbol key1('x', 1);
- Pose2 feasible1(1.0, 2.0, 3.0);
- // initialize away from the ideal
- Values init;
- Pose2 initPose(0.0, 2.0, 3.0);
- init.insert(key1, initPose);
- double error_gain = 500.0;
- PoseNLE nle(key1, feasible1, error_gain);
- // create a soft prior that conflicts
- PosePrior prior(key1, initPose, noiseModel::Isotropic::Sigma(3, 0.1));
- // add to a graph
- NonlinearFactorGraph graph;
- graph += nle;
- graph += prior;
- // optimize
- Ordering ordering;
- ordering.push_back(key1);
- Values actual = LevenbergMarquardtOptimizer(graph, init, ordering).optimize();
- // verify
- Values expected;
- expected.insert(key1, feasible1);
- EXPECT(assert_equal(expected, actual));
- }
- //******************************************************************************
- static SharedDiagonal hard_model = noiseModel::Constrained::All(2);
- static SharedDiagonal soft_model = noiseModel::Isotropic::Sigma(2, 1.0);
- //******************************************************************************
- TEST( testNonlinearEqualityConstraint, unary_basics ) {
- Point2 pt(1.0, 2.0);
- Symbol key1('x', 1);
- double mu = 1000.0;
- eq2D::UnaryEqualityConstraint constraint(pt, key, mu);
- Values config1;
- config1.insert(key, pt);
- EXPECT(constraint.active(config1));
- EXPECT(assert_equal(Z_2x1, constraint.evaluateError(pt), tol));
- EXPECT(assert_equal(Z_2x1, constraint.unwhitenedError(config1), tol));
- EXPECT_DOUBLES_EQUAL(0.0, constraint.error(config1), tol);
- Values config2;
- Point2 ptBad1(2.0, 2.0);
- config2.insert(key, ptBad1);
- EXPECT(constraint.active(config2));
- EXPECT(
- assert_equal(Vector2(1.0, 0.0), constraint.evaluateError(ptBad1), tol));
- EXPECT(
- assert_equal(Vector2(1.0, 0.0), constraint.unwhitenedError(config2), tol));
- EXPECT_DOUBLES_EQUAL(500.0, constraint.error(config2), tol);
- }
- //******************************************************************************
- TEST( testNonlinearEqualityConstraint, unary_linearization ) {
- Point2 pt(1.0, 2.0);
- Symbol key1('x', 1);
- double mu = 1000.0;
- eq2D::UnaryEqualityConstraint constraint(pt, key, mu);
- Values config1;
- config1.insert(key, pt);
- GaussianFactor::shared_ptr actual1 = constraint.linearize(config1);
- GaussianFactor::shared_ptr expected1(
- new JacobianFactor(key, I_2x2, Z_2x1, hard_model));
- EXPECT(assert_equal(*expected1, *actual1, tol));
- Values config2;
- Point2 ptBad(2.0, 2.0);
- config2.insert(key, ptBad);
- GaussianFactor::shared_ptr actual2 = constraint.linearize(config2);
- GaussianFactor::shared_ptr expected2(
- new JacobianFactor(key, I_2x2, Vector2(-1.0, 0.0), hard_model));
- EXPECT(assert_equal(*expected2, *actual2, tol));
- }
- //******************************************************************************
- TEST( testNonlinearEqualityConstraint, unary_simple_optimization ) {
- // create a single-node graph with a soft and hard constraint to
- // ensure that the hard constraint overrides the soft constraint
- Point2 truth_pt(1.0, 2.0);
- Symbol key('x', 1);
- double mu = 10.0;
- eq2D::UnaryEqualityConstraint::shared_ptr constraint(
- new eq2D::UnaryEqualityConstraint(truth_pt, key, mu));
- Point2 badPt(100.0, -200.0);
- simulated2D::Prior::shared_ptr factor(
- new simulated2D::Prior(badPt, soft_model, key));
- NonlinearFactorGraph graph;
- graph.push_back(constraint);
- graph.push_back(factor);
- Values initValues;
- initValues.insert(key, badPt);
- // verify error values
- EXPECT(constraint->active(initValues));
- Values expected;
- expected.insert(key, truth_pt);
- EXPECT(constraint->active(expected));
- EXPECT_DOUBLES_EQUAL(0.0, constraint->error(expected), tol);
- Values actual = LevenbergMarquardtOptimizer(graph, initValues).optimize();
- EXPECT(assert_equal(expected, actual, tol));
- }
- //******************************************************************************
- TEST( testNonlinearEqualityConstraint, odo_basics ) {
- Point2 x1(1.0, 2.0), x2(2.0, 3.0), odom(1.0, 1.0);
- Symbol key1('x', 1), key2('x', 2);
- double mu = 1000.0;
- eq2D::OdoEqualityConstraint constraint(odom, key1, key2, mu);
- Values config1;
- config1.insert(key1, x1);
- config1.insert(key2, x2);
- EXPECT(constraint.active(config1));
- EXPECT(assert_equal(Z_2x1, constraint.evaluateError(x1, x2), tol));
- EXPECT(assert_equal(Z_2x1, constraint.unwhitenedError(config1), tol));
- EXPECT_DOUBLES_EQUAL(0.0, constraint.error(config1), tol);
- Values config2;
- Point2 x1bad(2.0, 2.0);
- Point2 x2bad(2.0, 2.0);
- config2.insert(key1, x1bad);
- config2.insert(key2, x2bad);
- EXPECT(constraint.active(config2));
- EXPECT(
- assert_equal(Vector2(-1.0, -1.0), constraint.evaluateError(x1bad, x2bad), tol));
- EXPECT(
- assert_equal(Vector2(-1.0, -1.0), constraint.unwhitenedError(config2), tol));
- EXPECT_DOUBLES_EQUAL(1000.0, constraint.error(config2), tol);
- }
- //******************************************************************************
- TEST( testNonlinearEqualityConstraint, odo_linearization ) {
- Point2 x1(1.0, 2.0), x2(2.0, 3.0), odom(1.0, 1.0);
- Symbol key1('x', 1), key2('x', 2);
- double mu = 1000.0;
- eq2D::OdoEqualityConstraint constraint(odom, key1, key2, mu);
- Values config1;
- config1.insert(key1, x1);
- config1.insert(key2, x2);
- GaussianFactor::shared_ptr actual1 = constraint.linearize(config1);
- GaussianFactor::shared_ptr expected1(
- new JacobianFactor(key1, -I_2x2, key2, I_2x2, Z_2x1,
- hard_model));
- EXPECT(assert_equal(*expected1, *actual1, tol));
- Values config2;
- Point2 x1bad(2.0, 2.0);
- Point2 x2bad(2.0, 2.0);
- config2.insert(key1, x1bad);
- config2.insert(key2, x2bad);
- GaussianFactor::shared_ptr actual2 = constraint.linearize(config2);
- GaussianFactor::shared_ptr expected2(
- new JacobianFactor(key1, -I_2x2, key2, I_2x2, Vector2(1.0, 1.0),
- hard_model));
- EXPECT(assert_equal(*expected2, *actual2, tol));
- }
- //******************************************************************************
- TEST( testNonlinearEqualityConstraint, odo_simple_optimize ) {
- // create a two-node graph, connected by an odometry constraint, with
- // a hard prior on one variable, and a conflicting soft prior
- // on the other variable - the constraints should override the soft constraint
- Point2 truth_pt1(1.0, 2.0), truth_pt2(3.0, 2.0);
- Symbol key1('x', 1), key2('x', 2);
- // hard prior on x1
- eq2D::UnaryEqualityConstraint::shared_ptr constraint1(
- new eq2D::UnaryEqualityConstraint(truth_pt1, key1));
- // soft prior on x2
- Point2 badPt(100.0, -200.0);
- simulated2D::Prior::shared_ptr factor(
- new simulated2D::Prior(badPt, soft_model, key2));
- // odometry constraint
- eq2D::OdoEqualityConstraint::shared_ptr constraint2(
- new eq2D::OdoEqualityConstraint(truth_pt2-truth_pt1, key1, key2));
- NonlinearFactorGraph graph;
- graph.push_back(constraint1);
- graph.push_back(constraint2);
- graph.push_back(factor);
- Values initValues;
- initValues.insert(key1, Point2(0,0));
- initValues.insert(key2, badPt);
- Values actual = LevenbergMarquardtOptimizer(graph, initValues).optimize();
- Values expected;
- expected.insert(key1, truth_pt1);
- expected.insert(key2, truth_pt2);
- CHECK(assert_equal(expected, actual, tol));
- }
- //******************************************************************************
- TEST (testNonlinearEqualityConstraint, two_pose ) {
- /*
- * Determining a ground truth linear system
- * with two poses seeing one landmark, with each pose
- * constrained to a particular value
- */
- NonlinearFactorGraph graph;
- Symbol x1('x', 1), x2('x', 2);
- Symbol l1('l', 1), l2('l', 2);
- Point2 pt_x1(1.0, 1.0), pt_x2(5.0, 6.0);
- graph += eq2D::UnaryEqualityConstraint(pt_x1, x1);
- graph += eq2D::UnaryEqualityConstraint(pt_x2, x2);
- Point2 z1(0.0, 5.0);
- SharedNoiseModel sigma(noiseModel::Isotropic::Sigma(2, 0.1));
- graph += simulated2D::Measurement(z1, sigma, x1, l1);
- Point2 z2(-4.0, 0.0);
- graph += simulated2D::Measurement(z2, sigma, x2, l2);
- graph += eq2D::PointEqualityConstraint(l1, l2);
- Values initialEstimate;
- initialEstimate.insert(x1, pt_x1);
- initialEstimate.insert(x2, Point2(0,0));
- initialEstimate.insert(l1, Point2(1.0, 6.0)); // ground truth
- initialEstimate.insert(l2, Point2(-4.0, 0.0)); // starting with a separate reference frame
- Values actual =
- LevenbergMarquardtOptimizer(graph, initialEstimate).optimize();
- Values expected;
- expected.insert(x1, pt_x1);
- expected.insert(l1, Point2(1.0, 6.0));
- expected.insert(l2, Point2(1.0, 6.0));
- expected.insert(x2, Point2(5.0, 6.0));
- CHECK(assert_equal(expected, actual, 1e-5));
- }
- //******************************************************************************
- TEST (testNonlinearEqualityConstraint, map_warp ) {
- // get a graph
- NonlinearFactorGraph graph;
- // keys
- Symbol x1('x', 1), x2('x', 2);
- Symbol l1('l', 1), l2('l', 2);
- // constant constraint on x1
- Point2 pose1(1.0, 1.0);
- graph += eq2D::UnaryEqualityConstraint(pose1, x1);
- SharedDiagonal sigma = noiseModel::Isotropic::Sigma(2, 0.1);
- // measurement from x1 to l1
- Point2 z1(0.0, 5.0);
- graph += simulated2D::Measurement(z1, sigma, x1, l1);
- // measurement from x2 to l2
- Point2 z2(-4.0, 0.0);
- graph += simulated2D::Measurement(z2, sigma, x2, l2);
- // equality constraint between l1 and l2
- graph += eq2D::PointEqualityConstraint(l1, l2);
- // create an initial estimate
- Values initialEstimate;
- initialEstimate.insert(x1, Point2(1.0, 1.0));
- initialEstimate.insert(l1, Point2(1.0, 6.0));
- initialEstimate.insert(l2, Point2(-4.0, 0.0)); // starting with a separate reference frame
- initialEstimate.insert(x2, Point2(0.0, 0.0)); // other pose starts at origin
- // optimize
- Values actual =
- LevenbergMarquardtOptimizer(graph, initialEstimate).optimize();
- Values expected;
- expected.insert(x1, Point2(1.0, 1.0));
- expected.insert(l1, Point2(1.0, 6.0));
- expected.insert(l2, Point2(1.0, 6.0));
- expected.insert(x2, Point2(5.0, 6.0));
- CHECK(assert_equal(expected, actual, tol));
- }
- //******************************************************************************
- TEST (testNonlinearEqualityConstraint, stereo_constrained ) {
- // make a realistic calibration matrix
- static double fov = 60; // degrees
- static int w = 640, h = 480;
- static Cal3_S2 K(fov, w, h);
- static boost::shared_ptr<Cal3_S2> shK(new Cal3_S2(K));
- // create initial estimates
- Rot3 faceTowardsY(Point3(1, 0, 0), Point3(0, 0, -1), Point3(0, 1, 0));
- Pose3 poseLeft(faceTowardsY, Point3(0, 0, 0)); // origin, left camera
- PinholeCamera<Cal3_S2> leftCamera(poseLeft, K);
- Pose3 poseRight(faceTowardsY, Point3(2, 0, 0)); // 2 units to the right
- PinholeCamera<Cal3_S2> rightCamera(poseRight, K);
- Point3 landmark(1, 5, 0); //centered between the cameras, 5 units away
- // keys
- Symbol key_x1('x', 1), key_x2('x', 2);
- Symbol key_l1('l', 1), key_l2('l', 2);
- // create graph
- NonlinearFactorGraph graph;
- // create equality constraints for poses
- graph += NonlinearEquality<Pose3>(key_x1, leftCamera.pose());
- graph += NonlinearEquality<Pose3>(key_x2, rightCamera.pose());
- // create factors
- SharedDiagonal vmodel = noiseModel::Unit::Create(2);
- graph += GenericProjectionFactor<Pose3, Point3, Cal3_S2>(
- leftCamera.project(landmark), vmodel, key_x1, key_l1, shK);
- graph += GenericProjectionFactor<Pose3, Point3, Cal3_S2>(
- rightCamera.project(landmark), vmodel, key_x2, key_l2, shK);
- // add equality constraint saying there is only one point
- graph += NonlinearEquality2<Point3>(key_l1, key_l2);
- // create initial data
- Point3 landmark1(0.5, 5, 0);
- Point3 landmark2(1.5, 5, 0);
- Values initValues;
- initValues.insert(key_x1, poseLeft);
- initValues.insert(key_x2, poseRight);
- initValues.insert(key_l1, landmark1);
- initValues.insert(key_l2, landmark2);
- // optimize
- Values actual = LevenbergMarquardtOptimizer(graph, initValues).optimize();
- // create config
- Values truthValues;
- truthValues.insert(key_x1, leftCamera.pose());
- truthValues.insert(key_x2, rightCamera.pose());
- truthValues.insert(key_l1, landmark);
- truthValues.insert(key_l2, landmark);
- // check if correct
- CHECK(assert_equal(truthValues, actual, 1e-5));
- }
- //******************************************************************************
- int main() {
- TestResult tr;
- return TestRegistry::runAllTests(tr);
- }
- //******************************************************************************
|