From 6c59273a1829e0b7fd3bcad9cf7269ae48129832 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 4 Jan 2019 15:10:47 -0500 Subject: [PATCH 001/160] Clear and functional --- gtsam/slam/SmartProjectionPoseFactor.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/gtsam/slam/SmartProjectionPoseFactor.h b/gtsam/slam/SmartProjectionPoseFactor.h index bb261a9c4e..be15841957 100644 --- a/gtsam/slam/SmartProjectionPoseFactor.h +++ b/gtsam/slam/SmartProjectionPoseFactor.h @@ -123,18 +123,16 @@ class SmartProjectionPoseFactor: public SmartProjectionFactor< */ typename Base::Cameras cameras(const Values& values) const { typename Base::Cameras cameras; - for(const Key& k: this->keys_) { - Pose3 pose = values.at(k); - if (Base::body_P_sensor_) - pose = pose.compose(*(Base::body_P_sensor_)); - - Camera camera(pose, K_); - cameras.push_back(camera); + for (const Key& k : this->keys_) { + const Pose3 world_P_sensor_k = + Base::body_P_sensor_ ? values.at(k) * *Base::body_P_sensor_ + : values.at(k); + cameras.emplace_back(world_P_sensor_k, K_); } return cameras; } -private: + private: /// Serialization function friend class boost::serialization::access; From 968c0c0d4b496535f21aeceffade14c40863d494 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 4 Jan 2019 15:11:07 -0500 Subject: [PATCH 002/160] Use test namespace --- .../tests/testSmartProjectionPoseFactor.cpp | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp b/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp index 080046dd4b..8b435b5659 100644 --- a/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp +++ b/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp @@ -199,29 +199,17 @@ TEST( SmartProjectionPoseFactor, noisy ) { } /* *************************************************************************/ -TEST( SmartProjectionPoseFactor, smartFactorWithSensorBodyTransform ){ - // make a realistic calibration matrix - double fov = 60; // degrees - size_t w=640,h=480; - - Cal3_S2::shared_ptr K(new Cal3_S2(fov,w,h)); - - // create first camera. Looking along X-axis, 1 meter above ground plane (x-y) - Pose3 cameraPose1 = Pose3(Rot3::Ypr(-M_PI/2, 0., -M_PI/2), gtsam::Point3(0,0,1)); // body poses - Pose3 cameraPose2 = cameraPose1 * Pose3(Rot3(), Point3(1,0,0)); - Pose3 cameraPose3 = cameraPose1 * Pose3(Rot3(), Point3(0,-1,0)); - - SimpleCamera cam1(cameraPose1, *K); // with camera poses - SimpleCamera cam2(cameraPose2, *K); - SimpleCamera cam3(cameraPose3, *K); +TEST(SmartProjectionPoseFactor, smartFactorWithSensorBodyTransform) { + using namespace vanillaPose; - // create arbitrary body_Pose_sensor (transforms from sensor to body) - Pose3 sensor_to_body = Pose3(Rot3::Ypr(-M_PI/2, 0., -M_PI/2), gtsam::Point3(1, 1, 1)); // Pose3(); // + // create arbitrary body_T_sensor (transforms from sensor to body) + Pose3 body_T_sensor = Pose3(Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2), Point3(1, 1, 1)); // These are the poses we want to estimate, from camera measurements - Pose3 bodyPose1 = cameraPose1.compose(sensor_to_body.inverse()); - Pose3 bodyPose2 = cameraPose2.compose(sensor_to_body.inverse()); - Pose3 bodyPose3 = cameraPose3.compose(sensor_to_body.inverse()); + const Pose3 sensor_T_body = body_T_sensor.inverse(); + Pose3 wTb1 = cam1.pose() * sensor_T_body; + Pose3 wTb2 = cam2.pose() * sensor_T_body; + Pose3 wTb3 = cam3.pose() * sensor_T_body; // three landmarks ~5 meters infront of camera Point3 landmark1(5, 0.5, 1.2); @@ -243,16 +231,16 @@ TEST( SmartProjectionPoseFactor, smartFactorWithSensorBodyTransform ){ SmartProjectionParams params; params.setRankTolerance(1.0); - params.setDegeneracyMode(gtsam::IGNORE_DEGENERACY); + params.setDegeneracyMode(IGNORE_DEGENERACY); params.setEnableEPI(false); - SmartProjectionPoseFactor smartFactor1(model, K, sensor_to_body, params); + SmartFactor smartFactor1(model, sharedK, body_T_sensor, params); smartFactor1.add(measurements_cam1, views); - SmartProjectionPoseFactor smartFactor2(model, K, sensor_to_body, params); + SmartFactor smartFactor2(model, sharedK, body_T_sensor, params); smartFactor2.add(measurements_cam2, views); - SmartProjectionPoseFactor smartFactor3(model, K, sensor_to_body, params); + SmartFactor smartFactor3(model, sharedK, body_T_sensor, params); smartFactor3.add(measurements_cam3, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -262,30 +250,32 @@ TEST( SmartProjectionPoseFactor, smartFactorWithSensorBodyTransform ){ graph.push_back(smartFactor1); graph.push_back(smartFactor2); graph.push_back(smartFactor3); - graph.emplace_shared >(x1, bodyPose1, noisePrior); - graph.emplace_shared >(x2, bodyPose2, noisePrior); + graph.emplace_shared >(x1, wTb1, noisePrior); + graph.emplace_shared >(x2, wTb2, noisePrior); // Check errors at ground truth poses Values gtValues; - gtValues.insert(x1, bodyPose1); - gtValues.insert(x2, bodyPose2); - gtValues.insert(x3, bodyPose3); + gtValues.insert(x1, wTb1); + gtValues.insert(x2, wTb2); + gtValues.insert(x3, wTb3); double actualError = graph.error(gtValues); double expectedError = 0.0; DOUBLES_EQUAL(expectedError, actualError, 1e-7) - Pose3 noise_pose = Pose3(Rot3::Ypr(-M_PI/100, 0., -M_PI/100), gtsam::Point3(0.1,0.1,0.1)); + Pose3 noise_pose = Pose3(Rot3::Ypr(-M_PI / 100, 0., -M_PI / 100), + Point3(0.1, 0.1, 0.1)); Values values; - values.insert(x1, bodyPose1); - values.insert(x2, bodyPose2); - // initialize third pose with some noise, we expect it to move back to original pose3 - values.insert(x3, bodyPose3*noise_pose); + values.insert(x1, wTb1); + values.insert(x2, wTb2); + // initialize third pose with some noise, we expect it to move back to + // original pose3 + values.insert(x3, wTb3 * noise_pose); LevenbergMarquardtParams lmParams; Values result; LevenbergMarquardtOptimizer optimizer(graph, values, lmParams); result = optimizer.optimize(); - EXPECT(assert_equal(bodyPose3,result.at(x3))); + EXPECT(assert_equal(wTb3, result.at(x3))); } /* *************************************************************************/ From 80a42fe2cd5303b4c132d4988368d4377acc90e2 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 4 Jan 2019 15:11:58 -0500 Subject: [PATCH 003/160] Reflect name of actual Factor --- ...rtProjectionCameraFactor.cpp => testSmartProjectionFactor.cpp} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gtsam/slam/tests/{testSmartProjectionCameraFactor.cpp => testSmartProjectionFactor.cpp} (100%) diff --git a/gtsam/slam/tests/testSmartProjectionCameraFactor.cpp b/gtsam/slam/tests/testSmartProjectionFactor.cpp similarity index 100% rename from gtsam/slam/tests/testSmartProjectionCameraFactor.cpp rename to gtsam/slam/tests/testSmartProjectionFactor.cpp From 67f3b51ab2266a3a94c5d97947ef71793b6b23d3 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 4 Jan 2019 15:50:12 -0500 Subject: [PATCH 004/160] Clean up --- .../slam/tests/testSmartProjectionFactor.cpp | 70 +++++++-------- .../tests/testSmartProjectionPoseFactor.cpp | 86 ++++--------------- 2 files changed, 49 insertions(+), 107 deletions(-) diff --git a/gtsam/slam/tests/testSmartProjectionFactor.cpp b/gtsam/slam/tests/testSmartProjectionFactor.cpp index aaffbf0e66..16eca65848 100644 --- a/gtsam/slam/tests/testSmartProjectionFactor.cpp +++ b/gtsam/slam/tests/testSmartProjectionFactor.cpp @@ -10,8 +10,8 @@ * -------------------------------------------------------------------------- */ /** - * @file testSmartProjectionCameraFactor.cpp - * @brief Unit tests for SmartProjectionCameraFactor Class + * @file testSmartProjectionFactor.cpp + * @brief Unit tests for SmartProjectionFactor Class * @author Chris Beall * @author Luca Carlone * @author Zsolt Kira @@ -29,19 +29,11 @@ using namespace boost::assign; -static bool isDebugTest = false; - -// Convenience for named keys -using symbol_shorthand::X; -using symbol_shorthand::L; - -static Key x1(1); -Symbol l1('l', 1), l2('l', 2), l3('l', 3); -Key c1 = 1, c2 = 2, c3 = 3; - -static Point2 measurement1(323.0, 240.0); - -static double rankTol = 1.0; +static const bool isDebugTest = false; +static const Symbol l1('l', 1), l2('l', 2), l3('l', 3); +static const Key c1 = 1, c2 = 2, c3 = 3; +static const Point2 measurement1(323.0, 240.0); +static const double rankTol = 1.0; template PinholeCamera perturbCameraPoseAndCalibration( @@ -59,7 +51,7 @@ PinholeCamera perturbCameraPoseAndCalibration( } /* ************************************************************************* */ -TEST( SmartProjectionCameraFactor, perturbCameraPose) { +TEST(SmartProjectionFactor, perturbCameraPose) { using namespace vanilla; Pose3 noise_pose = Pose3(Rot3::Ypr(-M_PI / 10, 0., -M_PI / 10), Point3(0.5, 0.1, 0.3)); @@ -71,45 +63,45 @@ TEST( SmartProjectionCameraFactor, perturbCameraPose) { } /* ************************************************************************* */ -TEST( SmartProjectionCameraFactor, Constructor) { +TEST(SmartProjectionFactor, Constructor) { using namespace vanilla; SmartFactor::shared_ptr factor1(new SmartFactor(unit2)); } /* ************************************************************************* */ -TEST( SmartProjectionCameraFactor, Constructor2) { +TEST(SmartProjectionFactor, Constructor2) { using namespace vanilla; params.setRankTolerance(rankTol); SmartFactor factor1(unit2, boost::none, params); } /* ************************************************************************* */ -TEST( SmartProjectionCameraFactor, Constructor3) { +TEST(SmartProjectionFactor, Constructor3) { using namespace vanilla; SmartFactor::shared_ptr factor1(new SmartFactor(unit2)); - factor1->add(measurement1, x1); + factor1->add(measurement1, c1); } /* ************************************************************************* */ -TEST( SmartProjectionCameraFactor, Constructor4) { +TEST(SmartProjectionFactor, Constructor4) { using namespace vanilla; params.setRankTolerance(rankTol); SmartFactor factor1(unit2, boost::none, params); - factor1.add(measurement1, x1); + factor1.add(measurement1, c1); } /* ************************************************************************* */ -TEST( SmartProjectionCameraFactor, Equals ) { +TEST(SmartProjectionFactor, Equals ) { using namespace vanilla; SmartFactor::shared_ptr factor1(new SmartFactor(unit2)); - factor1->add(measurement1, x1); + factor1->add(measurement1, c1); SmartFactor::shared_ptr factor2(new SmartFactor(unit2)); - factor2->add(measurement1, x1); + factor2->add(measurement1, c1); } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, noiseless ) { +TEST(SmartProjectionFactor, noiseless ) { using namespace vanilla; Values values; @@ -128,7 +120,7 @@ TEST( SmartProjectionCameraFactor, noiseless ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, noisy ) { +TEST(SmartProjectionFactor, noisy ) { using namespace vanilla; @@ -179,7 +171,7 @@ TEST( SmartProjectionCameraFactor, noisy ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, perturbPoseAndOptimize ) { +TEST(SmartProjectionFactor, perturbPoseAndOptimize ) { using namespace vanilla; @@ -278,7 +270,7 @@ TEST( SmartProjectionCameraFactor, perturbPoseAndOptimize ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, perturbPoseAndOptimizeFromSfM_tracks ) { +TEST(SmartProjectionFactor, perturbPoseAndOptimizeFromSfM_tracks ) { using namespace vanilla; @@ -348,7 +340,7 @@ TEST( SmartProjectionCameraFactor, perturbPoseAndOptimizeFromSfM_tracks ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, perturbCamerasAndOptimize ) { +TEST(SmartProjectionFactor, perturbCamerasAndOptimize ) { using namespace vanilla; @@ -425,7 +417,7 @@ TEST( SmartProjectionCameraFactor, perturbCamerasAndOptimize ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, Cal3Bundler ) { +TEST(SmartProjectionFactor, Cal3Bundler ) { using namespace bundler; @@ -498,7 +490,7 @@ TEST( SmartProjectionCameraFactor, Cal3Bundler ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, Cal3Bundler2 ) { +TEST(SmartProjectionFactor, Cal3Bundler2 ) { using namespace bundler; @@ -571,7 +563,7 @@ TEST( SmartProjectionCameraFactor, Cal3Bundler2 ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, noiselessBundler ) { +TEST(SmartProjectionFactor, noiselessBundler ) { using namespace bundler; Values values; @@ -599,7 +591,7 @@ TEST( SmartProjectionCameraFactor, noiselessBundler ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, comparisonGeneralSfMFactor ) { +TEST(SmartProjectionFactor, comparisonGeneralSfMFactor ) { using namespace bundler; Values values; @@ -638,7 +630,7 @@ TEST( SmartProjectionCameraFactor, comparisonGeneralSfMFactor ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, comparisonGeneralSfMFactor1 ) { +TEST(SmartProjectionFactor, comparisonGeneralSfMFactor1 ) { using namespace bundler; Values values; @@ -682,7 +674,7 @@ TEST( SmartProjectionCameraFactor, comparisonGeneralSfMFactor1 ) { /* *************************************************************************/ // Have to think about how to compare multiplyHessianAdd in generalSfMFactor and smartFactors -//TEST( SmartProjectionCameraFactor, comparisonGeneralSfMFactor2 ){ +//TEST(SmartProjectionFactor, comparisonGeneralSfMFactor2 ){ // // Values values; // values.insert(c1, level_camera); @@ -730,7 +722,7 @@ TEST( SmartProjectionCameraFactor, comparisonGeneralSfMFactor1 ) { // EXPECT(assert_equal(yActual,yExpected, 1e-7)); //} /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, computeImplicitJacobian ) { +TEST(SmartProjectionFactor, computeImplicitJacobian ) { using namespace bundler; Values values; @@ -768,7 +760,7 @@ TEST( SmartProjectionCameraFactor, computeImplicitJacobian ) { } /* *************************************************************************/ -TEST( SmartProjectionCameraFactor, implicitJacobianFactor ) { +TEST(SmartProjectionFactor, implicitJacobianFactor ) { using namespace bundler; @@ -829,7 +821,7 @@ BOOST_CLASS_EXPORT_GUID(gtsam::noiseModel::Isotropic, "gtsam_noiseModel_Isotropi BOOST_CLASS_EXPORT_GUID(gtsam::SharedNoiseModel, "gtsam_SharedNoiseModel"); BOOST_CLASS_EXPORT_GUID(gtsam::SharedDiagonal, "gtsam_SharedDiagonal"); -TEST( SmartProjectionCameraFactor, serialize) { +TEST(SmartProjectionFactor, serialize) { using namespace vanilla; using namespace gtsam::serializationTestHelpers; SmartFactor factor(unit2); diff --git a/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp b/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp index 8b435b5659..0197ba1b07 100644 --- a/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp +++ b/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp @@ -189,9 +189,7 @@ TEST( SmartProjectionPoseFactor, noisy ) { measurements.push_back(level_uv); measurements.push_back(level_uv_right); - KeyVector views; - views.push_back(x1); - views.push_back(x2); + KeyVector views {x1, x2}; factor2->add(measurements, views); double actualError2 = factor2->error(values); @@ -212,9 +210,7 @@ TEST(SmartProjectionPoseFactor, smartFactorWithSensorBodyTransform) { Pose3 wTb3 = cam3.pose() * sensor_T_body; // three landmarks ~5 meters infront of camera - Point3 landmark1(5, 0.5, 1.2); - Point3 landmark2(5, -0.5, 1.2); - Point3 landmark3(5, 0, 3.0); + Point3 landmark1(5, 0.5, 1.2), landmark2(5, -0.5, 1.2), landmark3(5, 0, 3.0); Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; @@ -224,10 +220,7 @@ TEST(SmartProjectionPoseFactor, smartFactorWithSensorBodyTransform) { projectToMultipleCameras(cam1, cam2, cam3, landmark3, measurements_cam3); // Create smart factors - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; SmartProjectionParams params; params.setRankTolerance(1.0); @@ -360,9 +353,7 @@ TEST( SmartProjectionPoseFactor, Factors ) { measurements_cam1.push_back(cam2.project(landmark1)); // Create smart factors - KeyVector views; - views.push_back(x1); - views.push_back(x2); + KeyVector views {x1, x2}; SmartFactor::shared_ptr smartFactor1 = boost::make_shared(model, sharedK); smartFactor1->add(measurements_cam1, views); @@ -510,10 +501,7 @@ TEST( SmartProjectionPoseFactor, 3poses_iterative_smart_projection_factor ) { using namespace vanillaPose; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; @@ -567,10 +555,7 @@ TEST( SmartProjectionPoseFactor, jacobianSVD ) { using namespace vanillaPose; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; @@ -628,10 +613,7 @@ TEST( SmartProjectionPoseFactor, landmarkDistance ) { double excludeLandmarksFutherThanDist = 2; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; @@ -691,10 +673,7 @@ TEST( SmartProjectionPoseFactor, dynamicOutlierRejection ) { double excludeLandmarksFutherThanDist = 1e10; double dynamicOutlierRejectionThreshold = 1; // max 1 pixel of average reprojection error - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; // add fourth landmark Point3 landmark4(5, -0.5, 1); @@ -757,10 +736,7 @@ TEST( SmartProjectionPoseFactor, jacobianQ ) { using namespace vanillaPose; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; @@ -811,10 +787,7 @@ TEST( SmartProjectionPoseFactor, 3poses_projection_factor ) { using namespace vanillaPose2; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; typedef GenericProjectionFactor ProjectionFactor; NonlinearFactorGraph graph; @@ -859,10 +832,7 @@ TEST( SmartProjectionPoseFactor, 3poses_projection_factor ) { /* *************************************************************************/ TEST( SmartProjectionPoseFactor, CheckHessian) { - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; using namespace vanillaPose; @@ -945,10 +915,7 @@ TEST( SmartProjectionPoseFactor, CheckHessian) { TEST( SmartProjectionPoseFactor, 3poses_2land_rotation_only_smart_projection_factor ) { using namespace vanillaPose2; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; // Two different cameras, at the same position, but different rotations Pose3 pose2 = level_pose * Pose3(Rot3::RzRyRx(-0.05, 0.0, -0.05), Point3(0,0,0)); @@ -1004,10 +971,7 @@ TEST( SmartProjectionPoseFactor, 3poses_rotation_only_smart_projection_factor ) // this test considers a condition in which the cheirality constraint is triggered using namespace vanillaPose; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; // Two different cameras, at the same position, but different rotations Pose3 pose2 = level_pose @@ -1089,9 +1053,7 @@ TEST( SmartProjectionPoseFactor, Hessian ) { using namespace vanillaPose2; - KeyVector views; - views.push_back(x1); - views.push_back(x2); + KeyVector views {x1, x2}; // Project three landmarks into 2 cameras Point2 cam1_uv1 = cam1.project(landmark1); @@ -1123,10 +1085,7 @@ TEST( SmartProjectionPoseFactor, HessianWithRotation ) { using namespace vanillaPose; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; @@ -1176,10 +1135,7 @@ TEST( SmartProjectionPoseFactor, HessianWithRotationDegenerate ) { using namespace vanillaPose2; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; // All cameras have the same pose so will be degenerate ! Camera cam2(level_pose, sharedK2); @@ -1250,10 +1206,7 @@ TEST( SmartProjectionPoseFactor, Cal3Bundler ) { projectToMultipleCameras(cam1, cam2, cam3, landmark2, measurements_cam2); projectToMultipleCameras(cam1, cam2, cam3, landmark3, measurements_cam3); - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; SmartFactor::shared_ptr smartFactor1(new SmartFactor(model, sharedBundlerK)); smartFactor1->add(measurements_cam1, views); @@ -1299,10 +1252,7 @@ TEST( SmartProjectionPoseFactor, Cal3BundlerRotationOnly ) { using namespace bundlerPose; - KeyVector views; - views.push_back(x1); - views.push_back(x2); - views.push_back(x3); + KeyVector views {x1, x2, x3}; // Two different cameras Pose3 pose2 = level_pose From 737d369ecf516c09d502984bb5486a436100e9f4 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 6 Jan 2019 18:18:23 -0500 Subject: [PATCH 005/160] Added test with transform --- .../slam/tests/testSmartProjectionFactor.cpp | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/gtsam/slam/tests/testSmartProjectionFactor.cpp b/gtsam/slam/tests/testSmartProjectionFactor.cpp index 16eca65848..6455db31b8 100644 --- a/gtsam/slam/tests/testSmartProjectionFactor.cpp +++ b/gtsam/slam/tests/testSmartProjectionFactor.cpp @@ -812,6 +812,64 @@ TEST(SmartProjectionFactor, implicitJacobianFactor ) { } +/* *************************************************************************/ +TEST(SmartProjectionFactor, smartFactorWithSensorBodyTransform) { + using namespace vanilla; + + // create arbitrary body_T_sensor (transforms from sensor to body) + Pose3 body_T_sensor = Pose3(Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2), Point3(1, 1, 1)); + + // These are the poses we want to estimate, from camera measurements + const Pose3 sensor_T_body = body_T_sensor.inverse(); + Pose3 wTb1 = cam1.pose() * sensor_T_body; + Pose3 wTb2 = cam2.pose() * sensor_T_body; + Pose3 wTb3 = cam3.pose() * sensor_T_body; + + // three landmarks ~5 meters infront of camera + Point3 landmark1(5, 0.5, 1.2), landmark2(5, -0.5, 1.2), landmark3(5, 0, 3.0); + + Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; + + // Project three landmarks into three cameras + projectToMultipleCameras(cam1, cam2, cam3, landmark1, measurements_cam1); + projectToMultipleCameras(cam1, cam2, cam3, landmark2, measurements_cam2); + projectToMultipleCameras(cam1, cam2, cam3, landmark3, measurements_cam3); + + // Create smart factors + KeyVector views {1, 2, 3}; + + SmartProjectionParams params; + params.setRankTolerance(1.0); + params.setDegeneracyMode(IGNORE_DEGENERACY); + params.setEnableEPI(false); + + SmartFactor smartFactor1(unit2, body_T_sensor, params); + smartFactor1.add(measurements_cam1, views); + + SmartFactor smartFactor2(unit2, body_T_sensor, params); + smartFactor2.add(measurements_cam2, views); + + SmartFactor smartFactor3(unit2, body_T_sensor, params); + smartFactor3.add(measurements_cam3, views); + + const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); + + // Put all factors in factor graph, adding priors + NonlinearFactorGraph graph; + graph.push_back(smartFactor1); + graph.push_back(smartFactor2); + graph.push_back(smartFactor3); + + // Check errors at ground truth poses + Values gtValues; + gtValues.insert(1, cam1); + gtValues.insert(2, cam2); + gtValues.insert(3, cam3); + double actualError = graph.error(gtValues); + double expectedError = 0.0; + DOUBLES_EQUAL(expectedError, actualError, 1e-7); +} + /* ************************************************************************* */ BOOST_CLASS_EXPORT_GUID(gtsam::noiseModel::Constrained, "gtsam_noiseModel_Constrained"); BOOST_CLASS_EXPORT_GUID(gtsam::noiseModel::Diagonal, "gtsam_noiseModel_Diagonal"); From ef8cddd455d4316e806569ce7efe81fbe93b71e9 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 30 May 2019 10:52:06 -0400 Subject: [PATCH 006/160] Added handy NavState expressions --- gtsam/navigation/expressions.h | 44 +++++++++++++ gtsam/navigation/tests/testNavExpressions.cpp | 62 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 gtsam/navigation/expressions.h create mode 100644 gtsam/navigation/tests/testNavExpressions.cpp diff --git a/gtsam/navigation/expressions.h b/gtsam/navigation/expressions.h new file mode 100644 index 0000000000..231ccb20d2 --- /dev/null +++ b/gtsam/navigation/expressions.h @@ -0,0 +1,44 @@ +/** + * @file expressions.h + * @brief Common expressions for solving navigation problems + * @date May, 2019 + * @author Frank Dellaert + */ + +#pragma once + +#include +#include +#include +#include + +namespace gtsam { + +typedef Expression NavState_; +typedef Expression Velocity3_; + +namespace internal { +// define getters that return a value rather than a reference +Rot3 attitude(const NavState& X, OptionalJacobian<3, 9> H) { + return X.attitude(H); +} +Point3 position(const NavState& X, OptionalJacobian<3, 9> H) { + return X.position(H); +} +Velocity3 velocity(const NavState& X, OptionalJacobian<3, 9> H) { + return X.velocity(H); +} +} // namespace internal + +// overloads for getters +inline Rot3_ attitude(const NavState_& X) { + return Rot3_(internal::attitude, X); +} +inline Point3_ position(const NavState_& X) { + return Point3_(internal::position, X); +} +inline Velocity3_ velocity(const NavState_& X) { + return Velocity3_(internal::velocity, X); +} + +} // namespace gtsam diff --git a/gtsam/navigation/tests/testNavExpressions.cpp b/gtsam/navigation/tests/testNavExpressions.cpp new file mode 100644 index 0000000000..374732fd4a --- /dev/null +++ b/gtsam/navigation/tests/testNavExpressions.cpp @@ -0,0 +1,62 @@ +/* ---------------------------------------------------------------------------- + + * 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 testExpressions.cpp + * @date May 2019 + * @author Frank Dellaert + * @brief unit tests for navigation expression helpers + */ + +#include +#include +#include +#include +#include +#include + +#include + +using namespace gtsam; + +// A NavState unknown expression wXb with key 42 +Expression wXb_(42); + +/* ************************************************************************* */ +// Example: absolute position measurement +TEST(Expressions, Position) { + auto absolutePosition_ = position(wXb_); + + // Test with some values + Values values; + values.insert(42, NavState(Rot3(), Point3(1, 2, 3), Velocity3(4, 5, 6))); + EXPECT(assert_equal(Point3(1, 2, 3), absolutePosition_.value(values))); +} + +/* ************************************************************************* */ +// Example: velocity measurement in body frame +TEST(Expressions, Velocity) { + // We want to predict h(wXb) = velocity in body frame + // h(wXb) = bRw(wXb) * wV(wXb) + auto bodyVelocity_ = unrotate(attitude(wXb_), velocity(wXb_)); + + // Test with some values + Values values; + values.insert(42, NavState(Rot3(), Point3(1, 2, 3), Velocity3(4, 5, 6))); + EXPECT(assert_equal(Velocity3(4, 5, 6), bodyVelocity_.value(values))); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} +/* ************************************************************************* */ From 0e309baab6218d522b8c0c811a29d2e094feb33e Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Tue, 28 May 2019 17:42:30 -0400 Subject: [PATCH 007/160] Make test with hard constraints --- tests/testDoglegOptimizer.cpp | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/testDoglegOptimizer.cpp b/tests/testDoglegOptimizer.cpp index 954afa2203..0a33473473 100644 --- a/tests/testDoglegOptimizer.cpp +++ b/tests/testDoglegOptimizer.cpp @@ -18,7 +18,11 @@ #include #include +#include +#include #include +#include +#include #include #include #include @@ -147,6 +151,42 @@ TEST(DoglegOptimizer, Iterate) { } } +/* ************************************************************************* */ +TEST(DoglegOptimizer, Constraint) { + // Create a pose-graph graph with a constraint on the first pose + NonlinearFactorGraph graph; + const Pose2 origin(0, 0, 0), pose2(2, 0, 0); + graph.emplace_shared >(1, origin); + auto model = noiseModel::Diagonal::Sigmas(Vector3(0.2, 0.2, 0.1)); + graph.emplace_shared >(1, 2, pose2, model); + + // Create feasible initial estimate + Values initial; + initial.insert(1, origin); // feasible ! + initial.insert(2, Pose2(2.3, 0.1, -0.2)); + + // Optimize the initial values using DoglegOptimizer + DoglegParams params; + params.setVerbosityDL("VERBOSITY"); + DoglegOptimizer optimizer(graph, initial, params); + Values result = optimizer.optimize(); + + // Check result + EXPECT(assert_equal(pose2, result.at(2))); + + // Create infeasible initial estimate + Values infeasible; + infeasible.insert(1, Pose2(0.1, 0, 0)); // infeasible ! + infeasible.insert(2, Pose2(2.3, 0.1, -0.2)); + + // Try optimizing with infeasible initial estimate + DoglegOptimizer optimizer2(graph, infeasible, params); + Values result2 = optimizer2.optimize(); + + // Check result + EXPECT(assert_equal(pose2, result2.at(2))); +} + /* ************************************************************************* */ int main() { TestResult tr; return TestRegistry::runAllTests(tr); } /* ************************************************************************* */ From 184324a2dce329e7c6caa9ad3bb42f9037b3e40f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 30 May 2019 16:43:34 -0400 Subject: [PATCH 008/160] Check exception type --- tests/testDoglegOptimizer.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/testDoglegOptimizer.cpp b/tests/testDoglegOptimizer.cpp index 0a33473473..f1f847e593 100644 --- a/tests/testDoglegOptimizer.cpp +++ b/tests/testDoglegOptimizer.cpp @@ -181,10 +181,7 @@ TEST(DoglegOptimizer, Constraint) { // Try optimizing with infeasible initial estimate DoglegOptimizer optimizer2(graph, infeasible, params); - Values result2 = optimizer2.optimize(); - - // Check result - EXPECT(assert_equal(pose2, result2.at(2))); + CHECK_EXCEPTION(optimizer2.optimize(), std::invalid_argument); } /* ************************************************************************* */ From 0a6cfe4358495d621465f6dc8fd1535a69c54410 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Fri, 31 May 2019 09:48:51 +0200 Subject: [PATCH 009/160] Add travis CI --- .travis.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ .travis.yml | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 ++ 3 files changed, 103 insertions(+) create mode 100755 .travis.sh create mode 100644 .travis.yml diff --git a/.travis.sh b/.travis.sh new file mode 100755 index 0000000000..71c946eb4c --- /dev/null +++ b/.travis.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +set -e # Make sure any error makes the script to return an error code +set -x # echo + +SOURCE_DIR=`pwd` +BUILD_DIR=build + +#CMAKE_C_FLAGS="-Wall -Wextra -Wabi -O2" +#CMAKE_CXX_FLAGS="-Wall -Wextra -Wabi -O2" + +function build_and_test () +{ + #env + git clean -fd || true + rm -fr $BUILD_DIR || true + mkdir $BUILD_DIR && cd $BUILD_DIR + + if [ ! -z "$GCC_VERSION" ]; then + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-$GCC_VERSION 60 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-$GCC_VERSION + sudo update-alternatives --set gcc /usr/bin/gcc-$GCC_VERSION + fi + + # gcc is too slow and we have a time limit in Travis CI: selective builds. + if [ "$BUILD_EXAMPLES" == "1" ]; then + GTSAM_BUILD_EXAMPLES_ALWAYS=FALSE + else + GTSAM_BUILD_EXAMPLES_ALWAYS=TRUE + fi + if [ "$RUN_TESTS" == "1" ]; then + GTSAM_BUILD_TESTS=FALSE + else + GTSAM_BUILD_TESTS=TRUE + fi + + cmake $SOURCE_DIR \ + -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ + -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS + + # Actual build: + make -j2 + + # Run tests: + if [ "$RUN_TESTS" == "1" ]; then + make check + fi + + cd $SOURCE_DIR +} + +build_and_test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..bce6767211 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,49 @@ +language: cpp +cache: ccache +sudo: required +dist: xenial + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-8 + - clang-3.8 + - build-essential + - pkg-config + - cmake + - libpython-dev python-numpy + - libboost-all-dev + +before_install: + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi + +script: + - bash .travis.sh + +env: + global: + - MAKEFLAGS="-j 2" + - CCACHE_SLOPPINESS=pch_defines,time_macros + +matrix: + include: + - compiler: gcc + os: linux + env: BUILD_EXAMPLES=1 + - compiler: gcc + os: linux + env: RUN_TESTS=1 + - compiler: gcc + os: linux + env: BUILD_EXAMPLES=1 GCC_VERSION="8" + - compiler: clang + os: linux + env: BUILD_EXAMPLES=1 + - compiler: gcc + os: osx + env: BUILD_EXAMPLES=1 + - compiler: clang + os: osx + env: RUN_TESTS=1 diff --git a/README.md b/README.md index 720517669d..07cf25a404 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Build Status](https://travis-ci.org/borglab/gtsam.svg?branch=develop)](https://travis-ci.org/borglab/gtsam) + README - Georgia Tech Smoothing and Mapping library =================================================== From f52c69b5a8a3968719b06830d6c0cab77bf5b9cf Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Fri, 31 May 2019 13:10:33 +0200 Subject: [PATCH 010/160] fix wrong logic --- .travis.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.sh b/.travis.sh index 71c946eb4c..522d823533 100755 --- a/.travis.sh +++ b/.travis.sh @@ -24,14 +24,14 @@ function build_and_test () # gcc is too slow and we have a time limit in Travis CI: selective builds. if [ "$BUILD_EXAMPLES" == "1" ]; then - GTSAM_BUILD_EXAMPLES_ALWAYS=FALSE + GTSAM_BUILD_EXAMPLES_ALWAYS=ON else - GTSAM_BUILD_EXAMPLES_ALWAYS=TRUE + GTSAM_BUILD_EXAMPLES_ALWAYS=OFF fi if [ "$RUN_TESTS" == "1" ]; then - GTSAM_BUILD_TESTS=FALSE + GTSAM_BUILD_TESTS=ON else - GTSAM_BUILD_TESTS=TRUE + GTSAM_BUILD_TESTS=OFF fi cmake $SOURCE_DIR \ From 72296f786bdffabba6d98770be791d8fec7cef8f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 31 May 2019 11:46:24 -0400 Subject: [PATCH 011/160] added clickable link to optimization notes --- doc/trustregion.bib | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/trustregion.bib b/doc/trustregion.bib index 7fcd509f4f..6118e40392 100644 --- a/doc/trustregion.bib +++ b/doc/trustregion.bib @@ -16,6 +16,7 @@ @webpage{Hauser06lecture Title = {Lecture Notes on Unconstrained Optimization}, Url = {http://www.numerical.rl.ac.uk/nimg/oupartc/lectures/raphael/}, Year = {2006}, + howpublished = {\href{http://www.numerical.rl.ac.uk/nimg/oupartc/lectures/raphael/}{link}}, Bdsk-Url-1 = {http://www.numerical.rl.ac.uk/nimg/oupartc/lectures/raphael/}, Bdsk-File-1 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmUxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+Sv+qSlgAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2OYAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmUxAAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQAxAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUxAAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUx0hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, Bdsk-File-2 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmUyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+Xv+qSowAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PMAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmUyAAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQAyAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUyAAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUy0hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, From c13bd50a1adfecadd521612ff9145f11b657d18a Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 31 May 2019 11:47:40 -0400 Subject: [PATCH 012/160] improved notes on trust region methods, corrected update rule as per Hauser notes and removed old links --- doc/trustregion.lyx | 65 ++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/doc/trustregion.lyx b/doc/trustregion.lyx index bc4393fdf7..3296da5127 100644 --- a/doc/trustregion.lyx +++ b/doc/trustregion.lyx @@ -1,10 +1,11 @@ -#LyX 2.0 created this file. For more info see http://www.lyx.org/ -\lyxformat 413 +#LyX 2.1 created this file. For more info see http://www.lyx.org/ +\lyxformat 474 \begin_document \begin_header \textclass article \begin_preamble -\usepackage{amssymb} +\usepackage{url} +\usepackage{hyperref} \end_preamble \use_default_options true \maintain_unincluded_children false @@ -15,13 +16,13 @@ \font_roman default \font_sans default \font_typewriter default +\font_math auto \font_default_family default \use_non_tex_fonts false \font_sc false \font_osf false \font_sf_scale 100 \font_tt_scale 100 - \graphics default \default_output_format default \output_sync 0 @@ -32,15 +33,24 @@ \use_hyperref false \papersize default \use_geometry false -\use_amsmath 1 -\use_esint 1 -\use_mhchem 1 -\use_mathdots 1 +\use_package amsmath 1 +\use_package amssymb 2 +\use_package cancel 1 +\use_package esint 1 +\use_package mathdots 1 +\use_package mathtools 1 +\use_package mhchem 1 +\use_package stackrel 0 +\use_package stmaryrd 1 +\use_package undertilde 1 \cite_engine basic +\cite_engine_type default +\biblio_style plain \use_bibtopic false \use_indices false \paperorientation portrait \suppress_date false +\justification true \use_refstyle 1 \index Index \shortcut idx @@ -231,7 +241,7 @@ key "Hauser06lecture" \end_inset - (in our /net/hp223/borg/Literature folder). +. \end_layout \begin_layout Standard @@ -465,22 +475,39 @@ where \end_inset . - A typical update rule [ -\color blue -see where this came from in paper -\color inherit -] is + A typical update rule, as per Lec. + 7-1.2 of +\begin_inset CommandInset citation +LatexCommand cite +key "Hauser06lecture" + +\end_inset + + is: \begin_inset Formula \[ -\Delta\leftarrow\begin{cases} -\max\left(\Delta,3\norm{\delta x_{d}}\right)\text{,} & \rho>0.75\\ -\Delta & 0.75>\rho>0.25\\ -\Delta/2 & 0.25>\rho +\Delta_{k+1}\leftarrow\begin{cases} +\Delta_{k}/4 & \rho<0.25\\ +\min\left(2\Delta_{k},\Delta_{max}\right)\text{,} & \rho>0.75\\ +\Delta_{k} & 0.75>\rho>0.25 \end{cases} \] \end_inset +where +\begin_inset Formula $\Delta_{k}\triangleq\norm{\delta x_{d}}$ +\end_inset + +. + Note that the rule is designed to ensure that +\begin_inset Formula $\Delta_{k}$ +\end_inset + +never exceeds the maximum trust region size +\begin_inset Formula $\Delta_{max}.$ +\end_inset + \end_layout @@ -641,7 +668,7 @@ Thus, mathematically, we can write the dogleg update \begin_inset Formula \[ \delta x_{d}^{\left(k\right)}=\begin{cases} --\frac{\Delta}{\norm{g^{\left(k\right)}}}g^{\left(k\right)}\text{,} & \Delta<\norm{\delta x_{u}^{\left(k\right)}}\\ +-\frac{\Delta}{\norm{\delta x_{u}^{\left(k\right)}}}\delta x_{u}^{\left(k\right)}\text{,} & \Delta<\norm{\delta x_{u}^{\left(k\right)}}\\ \left(1-\tau^{\left(k\right)}\right)\delta x_{u}^{\left(k\right)}+\tau^{\left(k\right)}\delta x_{n}^{\left(k\right)}\text{,} & \norm{\delta x_{u}^{\left(k\right)}}<\Delta<\norm{\delta x_{n}^{\left(k\right)}}\\ \delta x_{n}^{\left(k\right)}\text{,} & \norm{\delta x_{n}^{\left(k\right)}}<\Delta \end{cases} From c55f5b42abb5a31ec80e19df06b010f62df6f9d3 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 31 May 2019 11:47:59 -0400 Subject: [PATCH 013/160] new pdf based on updated lyx file for trustregion methods --- doc/trustregion.pdf | Bin 168223 -> 60124 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/trustregion.pdf b/doc/trustregion.pdf index 31ca0c5b10998bdd71fc237fd34dac2eb86a9802..0a733a059b4c7784c40c183d4d9cd87a4b9e1ccc 100644 GIT binary patch literal 60124 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%0AP^r+zXyN0 zO`JxJCktaFvXzG1pT6BGL@TuHvv+i&3>=E>F!-vSfIabsa=)W|D4|ADBV%xB?v=tZ zcqypFSD1g_-+n&lY1Z#>h&R$1pkMw)tmC1Yu|Rf5oi(zX(LGddc3!WR6%=E9Jr2`Z z8;S?K%RL0MX!7Rh$Kt)cPGoaE0ONh+NG0YC-?C_l+&llPzAkxvi=^-by`vK?LZ24J zqzlSIXlzkuvTlaS%eqM}Z@Xm3)jC3>RC2R(?#_!w!h5Zq0MR}TCQQf5M}P<}X#F6P zDhk3W(KYr6T~yn9lHXnbazMJ^L)V*}#7zpg1I?kW{CZ8pX6nLNPG@_C0{iL^%`|RS z#Ec>KA1h!t^9h($!7*heo2{D!BABt?)a%A^6+I0UUgEZkW6pL9cNsHnQ<^|_J2P?T z{8A&aDC}J}Al2IJ@-3TIGTq<0AtJNR-y1i}Zu^(y`X*i=j?FTowuaVz!+uEsgGWoCaAGodUt>2_3!Q$q#m$m8H+|ebU&`4np?=al?UwHyXJU z?2s4vgofGbc>&o8n9yZV)&i~jhNJS^1PUuiW1cnZIGr5u()dUp?5;`DYp8*`Aqbpm zK5CLviW|i2cw4b z9!rPAB>O$KoFN60F`=QT8J*pD^Y=tOvZ-%nH}Szp2j}EEMy7;nE##w~EG?f+ma-9J zCeN5t!iD2Az`7@q{x{n^K<8d}+ngyxX%3We{mH#a|C=#w$)uYnTNAcK{-Z?3g|fa> z9}r)}mRSxt{k+q-$M`mDIWx#^5#RwWXIc!mF{0V^d>(I|bk1ihbuaP_$~R!r<4YK_u z$Niv>-J{^)MZXLM>cmJ&W~B=sZu`mj4JsIwuVREQ#LaE%LFGi7u8HaJHl?_nnl(W{ z&!ILe{YKRXH#%yJwKD#cYn)7b^>GrKU#Z`b1k}YK3*hN+*ZaBy5jH*6fXR`Z&|mxw z`-&K*S544Xz-T)Y0pv}Fm&T{l#}qqDlF*(s7iHs9(EbHdW$F_Bxhnbc`kBepg={qaFn0vVqjb1veQy>>|(sOAQFRAFqVO ziWq_eVW3Ym#WGEi2ghIHq~8T1$Y3Q&A5s^&!)#UVHC6GWL({NYkf8^O7_bpROC~Jk z(Eu>E9jS>wn!0*OzM5}wKr@wyfBTmg6CSs$Db?nCr0NM{eGPsRC@f=hnJ;>zeu->L1y>y!ub98}U9a6e5#T(Te^BX(6<68{z+z>rF zcsSxwjo)RooE&<+o=sAEsVAHw+6Yv(h9NXV<*fNDAj)dkpyrCXph zTG)?x>*yGHE%Jm$1YAmII!k<;+!d%yE)`>&tnY*n^ZpViKT!*Wsk zaQrs*^aIF;iWA$p*QMURM%$c?J5`tHG+a-9s_|dGw`EFSPH0~anvK0f3|Od3RAe+H z`7mtinKhnkkzo}fIjp7EfR0imKK{uS7(u*M69&t)ri8G{G#wzJeCUgpsN5eqIiZJwqn&~|wP>0bajP_K0E;j6-5vu6a zh|ubH$ZDJ78n7x&3Af_#s)6jRyAjYvz9udzuD&pELpnBXqw#g_^LD%iulLB%+ycWP zSIK!>B06=~ZS_kTo7DL8$oy!+fYnF_ZUG9C!jkqupn0IP&e=szZ!3b+LvUGXhj+r- zXAha|5WXk8J3#yPfXh9LT>;6KTZ4hp%lkk1850o`8^?d7X&lV|k4*ZXX&NUp z$N!#1+6Kv*rG?8*3snHn4Lu8<_qzSJz#B0}VPh8pbPE6&be_-!qL^UN^CVEj(nx$z z36#5}6sLT*9zTB7w_1%WQ;oHq*H*i$-DWFwOqaNP^7g?z{wn*7N1(v`d8C3;qhrEA z|8S%sul|BU+U)E`P$)3pxT7X4;3T}n_Gw3c9+bp=FOp~_*0G{o7kvgmG7OZ0f>1<6 z)Ko;|&|n~70fG^|`1?>&{tew6L&$l9kaNLF z`;_AY@K?b&dLWTH3aov2SFq0lxVdIG$e;X?ut^9;*8ul#6UIOuVxKEyeIOWrVL|;g z3hIBBF(`2$`DI{noDCp8M8+!yv+G_wcVIsRNTNQ9yIg0#0zb0AK3~YqAi;mxmRA}f z*a@6{pkO}`pNb8C5kmr|KBCj-u{q*%aN)sq_cgJ+7i7}lZS^LoGCCa43uUO^t4AOb z5@HcWE(O?oB*{-LYnhR102g)t_I5vG5yegI7nKN-KGMb3*Xs}KDm(-r0Q|GtF|ZFW z+jrjn>0yr}K#0I2fGz3gHIHb^kI{?~g$N`G2^}37$e$3nuZIZpTMsgy4L_L zEDs2=4YChf3=|K{0i=-M9+daGyB~;j02UAJZa37g)*c)TC5aLbr@OFRIspSO_gfu+Jy&A3Rr}Y>x1$_T(EL7HAmg6X69)BFqmZ z9Q0KP2@DG6#oyE88&Yh34^pJRx8z+QjR+>Ffb-i6lyVLq~Pkbon@TGbs+voeP;eTCck5d8kMmYA6_F$r6ipjdxi-+VG57QCPYQx!^#kEBnfS#Q>!>uE-!G) zRqB$1HW{-?#3jw9Pt?#j&~zZl;pK-;#!l4qWnFpIvFfy`Xo$FnP{v7NI#VD*PL1|* zlP}PsZ>l2_qsdDu`%6sy*d5y{?T>DuKq#pWr7OjpH(;|)dIJCJ_1>lbPQGPMs(eL1 z$lQk&8@|1l+-YBZ0}5WuGT;POj@4Bx-l_Ov3JoK}5n%p;4#!iUoZDDDs8dcNtXn

AVmm9hWp)&vGpRrbt=<1xDT5mmmmbh!PF-L#RqfsL~uJN5*R4N52yf{6+n zSbe(tcPn?}-9!Z}tj!am4fI++vM{@r(o(8xghx3@;7U5P{C&Kf9J<(ZeiVU`tSMnl z5eu+!leO_X4pzrhjg_tuz~ill4?l1zgQa+I#&tu_Ef!%2Z%_jfo6>YZamwrAMfbX- zXAX(WPr`7b`qy9Ug^V!3x+57T`D`c_2D;&e+jE7K#>8*hKlP-UFv!VRiBq^%c7?}h zJB@Ksicr~PGP+w_yv8*2`!YK$Lge)K0Xo^K5WDc1r6Y$Q0Sb6xlEwTT09ul3%G`eM!e3L`#L!X{g){{wf{HmD+8Y)r|c+ zg`QzVm+ye>!$OSPBDc%@q>ex_f7+F`L3p)4w)LW7_vH}d@#loGf}CprMFXMQ<~PQ< z@;m1rWHUoH>)LIfm6#wb_sb>M7ks_G3t?$@mZ?G0QWe11q1iNvm!aML2Umhl3)>NBN3+6|cp=m#;S_fL&Q;1WOgY8cvc+4Xr@@<-0Si2cXUI!~{@9{nOCpDs< zQI71e=zyq|!~^5&io$C|93e^SIpx71oE^F&KeCTSd|5cG7X3L0230GAsG*i`PLybf14yi9>dDK$HycbL2 zP*p(co%hSztj&d8C@DwFmtxj^g%irCK#Bl_lGHxF~gHa zGtA%kbua3hNDYZF9rmCKhtVCqxD;o?lmh@W7^Bb3I-f!XRG(83Kqm>1|wq1$89;h=)Gg*Rdq`>9`&EKh#YuAUc9WJIPDp`z<1Z+|=+S&2B@ zbeNp10X}oFse_RrIf1fx(xtvMm(eXGv!=P@T0vODi<$aOhOwN#%v)~EhjpPl&&TEQ zx0WV)Ie?2)=tMjOE(TQyYUOR(^Lr4yqt=jiak15o=IChteJ6*zGNyl5@zoHj2vVDJNRyPtO87z^wJdo?!AGk;jn=Y(%9#r2$URY&P0+JejZD7|3FxfICY( z!97@M5;{}QguDefS+`eDczg*<7ssrg-;7{;n0EL;n8l&b|)J*)QH;}S{FYc{;1f@@b zgcrl62DEJNs*qsH`Y(UM{i&XxEk3S`-(HM zM~4q2k!kmG`aZHyrDQk*sVp2`7&CBY+O6Q`C4`I#p?68C!%w-^;jl1}j|mfeJDsnn zN0dPbEtzeJmWEYx0+k*YRh(ieJ{dku_w{r)iGd&6RkK6&;$)<6wXwaKrTnb3&SZOt zn;`3mV1uEftdT>MHmjiVfEBLvS}Q4vT|V8a``-kR0V9Cf-UCF-Ba!6hSU6gy-~sxL zC&Ioxvuz4Kyago;zDo5y$PbXu)L>{aLj&k7>R*6He50O}{=^25s$@%J4Le5V+czE- zxnW0v5VwNq9hYc7fFQtZt>694o$phW*F$o0wC~NRKmxecA0FF)bNAzQjRF z#G&7;)~=R*8+t7N1r=w|sg*+|lc3IQMPOircnCCVWBTwyEni0{La zC5XZLJHBx#W0nVFYSR2SWN;jgZ=YJX+#93W?I^rP@h*c=xO`9T13mBbV4+ZQXUm1w zqI3)ZS=esH0fZc4kcK3^-##IH&o*z9BlUBola6O0n_~Rm|KaprFn^9D6J`8}ZSD#6 z@F0@lJa|<5DBH6T@w7*sLZlbRl(rR+&v;Yxpx^w%Q#HsSWQ|wd;Gp;*cy>YcahQ44Yg(lmh@AQT^?We3ZD7_z& z0<>obmgMp$?8J!AUZ#Sg4Dx}gzijs9p% zEamsH_)uK90WM@cc5&g+BXVqH0O9E8mT>w8 zZ?^=7vLYjpTbq$c>WbrW-R1i&IC&!_UCTB&7oT2KmSueMoBEu8I=gQh!YVa~(1Yx( zhsem-$4_X%Yrcn|jw_s-^$^0`i42xr*I6(>{N!AJMU;%~hN(2_@>^|wWWQ5!wp35G zjoSvn)yO`6P_Ndm3bXK9!&fN$@=e8WY7FYvthQ|#p+WA@Na@oA%A)Bq=gXB}R>&*T zxH;h=$(g*Py2Cb!LyQR%4&VtAB$}l?hJN?t@}4e+Wwaj!sSX)0dGSxKZuOxQprY!8 z8{|4DSEKtqw^Z3y4`hD^g4SCwyBG3423uufV79%RQwp0qxf+7cL{ttLbgH!^4N8Si z8ZyYf9U~_o^O-wR72G`W^ugdFfMd(>XgN(ZFoxUR2mn3UZj}$H9l^DudIfZ#!g7cI z7_v|a$v!JQ^wpEX_g-g;lrMOZpvnuB;**esAHj4BRmTZ;^@?dVHl1_7U}wIz)wj3Q zt3v=dl#H=Lve#q+IN_a1jMEKAawD>SFnnCIFZzKvtZ1dU*2g0T+*K+HcV1aL7JPIi z9I}2(mE3eO7G5l+f(a{-nha}0%Bs51)kY;>z@1jAaRRZd{lW~i_Fu4qIn=VP1 zAP&e{u*-9%$Tu_dv-$N7mtf_KhzlpaR18Uqj@}(9P5}6U!mttr|E=c^$>iXaHU&V`vxae33ibw^ob9{J`8Zrylb`;YF6jH4{ zRJ%;`XZDEVb~<*7`BCO>P?-$PjhM0}M8+burYba_>3{2Z+z%zG&K!bm+HrRVY5Rr9 zsFcun>l>>xL0o#@cZtea3GEhhskaVS;42v|xpJ^8L!YjtE%=#?obSBwov&9#1y|Pb zxDJ%rnV<&%GVr!ntq3))1L1BBkVB08pgh#kr5kE!&Q6pq{a^qKXsDhj!d;t=2i+~3 zZWsm4#u)N>ESEjF=l-R0%q%@Y=EL*W<<0%wS_=NCC&whQXN^GTHn2m7ydl@_Tz~MsHF|<6YR| zxu|$Krmi-vUPiGmEv|_vygMKYU(ZJg#x~`0G3x9(04OeLa*2e7n6)KLc5$eYgMLB7df)aL?&ijRv*#&kL`8j zDD=!{nQCJ@k#1V|Z}|oP;=?bGF}pa86BriDVeV0VRmAHgcx5}(_;Cs}tW^`B6V9tyjkVBbkZZW6K>p;O9nO2j z7<$QA5;$%X#BN_Q`hK^so(8MUfE6v|A=H9}QIfD9@uFWT_|y)jJNXPe;7XQA;%=4K*@TeUy)a_cyeih58iSSM=AN#7hn%hDAraOJe_n&e3ferrQhJqVK z4{u#J32p3K^Dg5~Ir8D0;urz|e*EjzDhtvuj}np)u-L!qi}g3;7_v1+f$n{A&r*S2 zqp06lI9>a-Lm@hkQfS99O*^cybaRFEv<)+MY2JGT#%6ks=}s!T=K{z|$w5kTJ&wy1 z3F-pnX5+VRvT9L&3IBxRWqxVSYBkUK6ovRs+)-5B-ZKt)N`luQw9$>rk zd!bp&s*{G2&xb3AZ7N8rE4GNR%%UlS*8FerJZ*dVN+N^NqSP{!-kY$0mATH{$lvfB zFUigR+d3vb_X48|X(kx7_Ut`}#aCK$a^lM*bmcD)L0*@)it9g6*o&%On;Hk2s5(qo zV~XnyF0N@01g+*r(dfffu3f*8JrlsG#8_5Av_+?)4WG%s__d%~JU2vGcer;W`dDTm zZ#Sy1yr4pf;$XUy|bXj$yCX{XH zKhAu87pxJhmv%Q&5MQ+ni0Q^MJV-3;$71$e1>`7+DWSmY+>l(Zsz;_@!nOrI9%*k?FYda@WaBAS9YY z?S4>fk}ZZo7*&GKdC^`L+B16cMryV|S6dxe(y12deKIX1eao78;9rqNFKIUdRqZ71 z_ksqg*zIN9K%#>3Sl!0eSe*pqSmfEEb{$5?Uj7_5I($sTGVL8mIEH?g)bg8}}H!oC$Gck3Ox#i)l*28_Wtw25lI*Kw?Oi(+$9cB)Cc&0_mMU0838&(*A zHr4Q3SRjBdLOEz#&gLY?MBZuom>2pfNUnazh+q8>z?#X=}{ zTE6srV^#Ri`0AbVhGX6{c;DWuETI$=y1ONk{MPwp)agJFPR)V;iTR-Z0IL6zIxL*d zHP=7SSu{VqLc<&gi#QlRnLM1zM!OVPt=q;$y~i5=J4Y=tIwv`W067pxA$3T?R?kLTzT(R8 z(biuE+)UEgxopo^`v`G>Rfwb{cY@T+Tm}QYbkk%PYG|fc){OXqh4GR$X0G#_J)3%} z`t+wU5pl`)0%oGDS-)3PM^`ChIVV(YH{=3X7>Edq$scLKB}L z#$KIAWhIu|`H`SflZ)9Q8Sc`iLKGJJa3$h{UiML=P8=LtS_?okXG2_ZSljE+2ePSV zSxOY!SLQ8vmY61r?24OI7_i1Uq`3u6 zDM3nrt>n@iz778R6-aBHm7P5U2i@LDYCmUkp^Id|^196HXJz{u8InJ^pz6^gWTEX1 zVH_P2PZ1ju(Ne)&U_CjW>7?-TNceZG))aTHlLkH#^o3+6>vZCGA>Hcb=7>EuO&A}}q3^s8lXJPHjk$+0 ze8TUzF5{K{dNL}5=Q&}|B;|%>f2qRJ=rk%v^25rx6H5B&QXyM0t|eFhVbW42lj)2% z+34!@uaXutloD6FNE?^|>3SjaQ*sn-|)CbB@s|Wj7;?H;uG9^+I7#m zn9P8nFP=nyVqT0L$CYY-p54maB@}U)`elYS*AC=TxK>Mv+mmc9cO59cCQwI2-! zNz=qRo2rGcHG(CWN2O^>#bgbqdb~5SiZxU}dL%zSQ(kqkgxvjEiXh5WDkeqMOT{ER zx3qk9wLuDFPQpC6m?5xeJck_d)?i-xzE*B=g%+OOYG7o*HeX7QD7Lmurhmd*JK zuoP6wU=@-d{gr?DmI_5B#aob}P8|?*2|4PLwqh;NiM_v{d)G7SkTv_7K9x5;IYxQd zx?U@oDQ1OUv(4Dpy@q4iQrw$v&_Y+9cSxuj*Wn@~yJ(+eo4KQR&~ANVkQsM6Q=UP6 z@@VoGlW7)7ZFd^dRo!>MSe_{LjUFU|*XMPnFY~Gl6E<5TR<2?4!!%S7n)7Bzo0O2P zar5Hbzr{;BZLY%Y5|DT%f3c^tI@mJpBIdQGsY_BRZEzu`M0*)+@AKoM$wS4#B zCUpu|wwaQ?IB;M>tnl!8BVVy~`OSTQ{5NeM>@3BxOjXbId9l5PN;cuXyFQw2!N4eJ zqR&-4NYjGTndsf>sZnBv@L`fOM0l8Ii`to^eL3;PRxV`ojhxCc@PIqwT(RnIzeS>!e~>mdGp5Lc180s!Oipc=R%eV7+du5!BnJYJ^Q@2A4up%-5w z^hvPU^c~3$T}bBJL1`}0s*`cuZ0hyV^>+K_z9_ZFN@D3Rq7LP1>VBL4c@Ozt(Enu)s}&1-Rzl(07ApMCe62Qh99If;DeYUf(_I1z>E>qYSl%0f~TN9%RWl!DM2 z=b2r6&tHC9{OI8JEgz<2@Z0z`Pw-i|W)_$+-Ac>Q9&btAvS(L@7dK6jh)TIUJw^@) zcBs5AQI=I28o%eBI;~;Pwn~pe`Qw^hGxA=&iZTSRM~Hei#nOrK8>KjM;N~%2T4Rrz z5H4<|M&<5rkN`QbZ$kK3brX`}^Q_`1#XL?QZEJdMCki|Qjd(~Juv)Y2X39yW#*)*E zm6N*o8$S_!?~1G1(HSjXXU1{z0}%@|0N+LC^|uqQOw2nIrp_%RGQtY^(OaO5^^N(K z^SVLKplm9>+wl}xOmN;=SaFwq2PTDWw8sPEBhmBOah;kuka8~*v08V)qP^z%q^Ha} zf9(M!Sn8mFO$HWb&j0B(FcUGcu>Rj(!+$mzn3=d3|JSTzC0I`7 z4-5eZ?yQI#Exo!2|=>l9cC3_S83{+bjfpGhaPkz5hbIpC8TM=9Qn;6+3K(yC-Io$9loEp!_zf zY~e-_f`2q4XPmtGG&G!OA!ha$aeHK5TMF#jkYZ*qk?J17wciF%6xIkxH6VtR_Q{bz z+0bVvs!zwNtT_5}<|&7Qe=NcA@)&~=c`5tQMGUaV*9a0RizVZbT0u}H1W)LIoqzv} z87}PmFa{NaA@ln{2SV!7%Ep33`(O`=8EZbN!PmvZ4^e2SRr2NKiRz;QrGl(e(}2JO zVSzDI`WG0}5S5eqsU_iJ6$;~`0$P#cqq(~nfouGW7>VG11w$4x{}PRPFrXr;0A;{* ztJ4H2EYt@m_(Nk^A>=8Yp`MQvs6-$sTSFOM}6KfQW(xE#tYt5X58xK^<56 z$4Q1F-^*hKhvx}`_mL-6M(E^>GJrxoZ={170gfCg($ZuE z!h_kf&xxy`-m6aPv!u{5A?Fkomt=%SLSo&M6a_-{D}usCg8RQ3-zE+h@ViAIIS zRf6!awn_1?rKuW#;v4k;)cF2p#fX;hKKlWnl!<(2zNlz?mwxv8AWP7rlz$y5^zDAe zVIfmVAjdc&(|%#>!N%6qLrzH?9oTi#bBbC+;KHNGG$F=2#M!6aM(pwKm$@&wrmaQP z8sRGtjHuBe2ALobm7^&pmumx; z4zucN*qC0}bKlcO6LafoIb6y8-is(8w|#+6X$kf!qRKAtbM6wzU*~hRjnKh5XB@4 z$;mW+&kh8Z7b2@o%+O9ZPa1KsrnJ*0bn9t6lDiO`E|*4a0o2pFE&$gmRH~0Nc49# z`J~*w*z2~mjDgLD7Cy60$J0UI*lA3!XI`>O9TU&UY{Di*9QzcTZPX~UZ+B!`rnROi z`P_=)^%&coUQnKv&VE0@&kfactqQ!G5hAnM4o!gi6!6i0$YrcP;lXx`#B#qFlZFyq zjMYvVr8Ri@^E|cGMDV%&NI{aSq-F;WsccTlXAVCX?LDhAA*FrTK!$tE0wr0??H;gN zEF#0X%JtxY6K8MthatAMp*+@4X~ox}+V=Nf@@LV6MBNF0nMOT;fb&~tHsJY~|2E63 zI0_akvdanchux^1jZB5*hOPBsX|e^6N#;;nt6BK%&O_%W=@J%^oLlaqy48eJmi@gi zoBvpEbi(sAff~G7z_X<0l9E=^iQJV#Q$$e@&2DZl zCo9JkPdIRL$uhPX0NWzwl&-$mE?vF6I znYP&s!d-lePOIa|4i=4O^PNy* zmGR!=9|=mE6y1fz2|WR=a;>IZHWQVfr;lh71`+YSC99o-EgHz1e%Bd%40!tqwWL~Y z9v}4P9=vaNl{q7gI_0PQ7JShWPv-5sB7shbn6#Ts8_1oxF>x)JGB7W=wMRFVjVx zK@nce=$?iUp=l1UsrD!>#J@3S=%=a2TnmuBThi>K2U*`eB-)9jBchGMtv z^_)Qb-6OKl&*0fwqAHi2-&2!0?ir8#bk5JWd)0Af=+#AYewDPPC= z$$uGw{+n8!gPG$$W*}BBuK%i*|7VuxTjuNEhFWYk zTWp-8V&?0Y>#uLK(|Md$buP6|<+3YuDqXLY1Y{NBB-b|E%RIS(G_cgM(mjBvQGR96 zUR_NAnToS>!oVm{$om@?v9Z~na$|b-cLD|;>flSfb_yZ}3 zky*zm_s@b`(Ok zea<#8z2};I3S9arP{jVIN7>WT0xA7Q#q?*I+gd<~IoIou+Du;u<@`QmcX4^OJ0ldH z`MCp?|0yJf??W?xLEKm#5$_+vfXc&YZgsJD0p7g?M6Bm__YIc#uFse1y-pw{U4Bxz5NpB7O3+N=fsoc;K$ zobzk=p#pzB)seZrw&wkj>-WoebK@ds0#{JMhVJ-@?(ZEua3y~!NpU>^LQnNWV{>Hw z%01jQAS0RlnVRz}|8y$>qWuBT+Z`Pkp99Ysn0~ItG5%bBt}cA^QX(UJA=*KxV$cJD z;=)4uha5uE_-7u6-TsKI4lR#Eo1Z?|t^b@K_X7N4QIXDoSp&4RS~&LM>!s~rE1ysq zaw-FhGBcT!3$;vvb<)3Gs)H=KtPL#f-6Pye|Elf92y)_@PxX4$llZJkTcRO~zViG# zouK5mMgbJ1RZVWxTAxd_UpKJw{lN2MtgH%v9Pm9Z6WV()ibQ#abF$r3DGt?VVyVYb zjMV4ZfFc^Q>pEWbDWnP|LQ;yqLV&hPE5*_qq3Al8F}&BcKw%l6cEs9B`q=6yE8v_t z;a2I_{1`6=xHt9T^Bcjj8$TFPcpLVk9EU9CrZ6~B6qd`30=!NOpg9hxr&qN>L+`ka ziD_n5D?%@wucLxeE7OZfYqBA*%??WI2Puc<@i)2!+4pBg3C!)xhFzQ0I``9Fn$h3R z!;$J{@G454u-;=H~JA@>A6lFwR3De+6uCoLu zlNnRbHm&9opviWgqPpq_5n%bwV)zFkof-w1JxZ{%e_-q|sPrZy_^wPfUe6MGVoF(} zhG@-v7s&_vgxk_g7w(^^ioBQ>SH&v+sKX5N_AyS}>YE^!G22woG(izGefp36+Mo5^ zY7Cp>@K#qYNw!ym zVaW-TbXFm~;S>UsGSAXRh1p>VhCh}KuRk)R%jC-d&0v83Nt?E%;mIjWliv5w?GTSw`{b4LPZm4xUf*H7JX+#ZHipD z#0p_>zA=mlOGDfOr&;%RCol_NXA`;GmT%Vk|5_&GJVk<^wX$??#;RO zYJD@CRlTo?KjxjVeqfN#QoL+k;`C_Ge!{2iW|k{}N_b088$<33Uv7x!8T*>~)M5Pm z=lvZFHXNdxktCeDeyq~^AU=cOdrBWaRmsTo2iwAv$M}DGOYAF zc7Sh~C8q3k9uBv}bPUBM1Y%`dr7(}z-Pa0(<=|aC{`6_<@ludM#U@#;CYVrg9prL@ zuirw`cWlxWSjHthcUUiqErUVm+WZb9&`-|liGV7dZl=z5qNn(k)FdM zCZ-v}$aIm8Uktrx(d3b?LKPaVpLNmRfe{@+ho;%;z zr_OLo1YVta-2{dAsRHU)7OL}y9E;Q=G&fNR`lDZ~OttNrOXY?{@Q>jvyF?)sl~TD4 zPr1mLkWyToau1Cy78J9qi!Yd`)4kb}b5_RT%edc(&dbaT8%t~{1vpp^a1b;l%_k-z zdtCY+?a~{GAdVYAdQCI9(U}+9n*X<_vIJ8z1bHCt1DCxyv|+<|1H+uxfPf zAR`w2sQGRmcZ-Fg{?qu8fA6mN4SA2v467IMl%~Q}Z-Qvbn^RYxm1|nTZ$jB5kkp)E zHg31hH}~9SXrAc5X9it3{Wzf~i;aVw{+(!QUS{-U>3xgFdP=gEZ%ya=l^G8HJXnvs zg^+Pxo|t~~;>;=^Q&L!1IPN}KAoZ%I z=K{F~gPEl-ropC&zTa?s!0w}UB_umz0*k-q?)UnXafw*3Q5K%pcZZgcfvl#hVy>=JdBU>EA<_l3+M zIrFQI_WpFH1L$Y#QwxMrsWH@EHyi)iz3d5JaSqda@mAkmLVEEx_PG=nU5e=&Vv_QX^?J@AAw+PLGMhh)EL)l!cmUWeSwmUd8{2&yJ8E3IP7wxK9(9D#SuDU#wKSAc-*#k*2_Ns(AnRU{_cZ?pn1Gy4#J zGxGXWw5Sut(!6}CoB*wrSgZ#j+{s$QQFXzel4u>dGj)R_9BgSZh}N#|ttj$vkkmqv&WBQkk1a^{IA@j_D%1@$2*IwXzafUIwUTOoa!PP=`{j z;C&^faL%xx4k0_k2XA+-_?4`_c=-eAMmNMG8Tu=1JVT^I4ZZ1|9`tS+qP}nwr$(CZQHhO-nMPqw(ahDV`ejpFQR@xMMdPI zp3HNS(#esu}1q20L+fc@tsrRC`)c|4RgQsZ9X zy1w&bMBZZD#X_z&38C`iM7nPaDmM1hFQ>5sSGy#VL%KfG9vpwi%gS3(32h+}KZ}$F z(>=cyqW|3z!Aqrjfev2vWw>B=GCUp|E&JTwXKE$ED5U8zFXTGb zhA&2Rn}J@0W`p%MH)|g>^YM8y06ZZAb>V0=J%LE{*8y@HJbSrA{3{V@9eROqy&|+eps@0iuGB;wk3TGuk5>pCprv+xiWO2z^eKmkYA8Q$OEa-dd zh{(yJL_S&N>9m~KcU+xi`aH8g(8M^r;3nt!W?rEPL`e3746SOJVj5i;_t-D9OO|F1 zTpQuPovHLt&LJ$9@$uuD69KAV&mw0PK0=QM_8yB|YJpbG$Ae&Id)?}VhlIY5P5NP^ zDz5>`r6{nnrYZ3^JnI#rj6)NzO;{S=UQk`-lprVszIdbB=G5E0hMWeNq9;{;=4Mro zz8&$P;810I=|bSbV@PZEN8xs~ChJ{{3^Gp=zFhE1Q_ z#2(T|-nMNKLf4+FUA-%#+nYV07X!l_zs<0*jLJdntd(*j91Oe^tE;aGq!Ak$n%*bQ z#0oxP$pS`-3cfkTL-VwfJP20YNNWnF0r@?p2D6L&&jj5Pm$y}n3W;9WJxhf(Ux&IS zj*I+(ylPnNVcHe!_SjZuh*nx8yG73&JQqUu=7cYEA!L1b%5d4u=r6eRO%ER zok9m8=fwqJrO54>m2c(Lkh0a|)h8Ew>{`uN>Dhn`LLhAWso2)NLl+)OUhVT&fl%V& zuFq5q5@6zewq(^IyK=-uaLx~Ox$6tQWwS8$A76(Gya_wlEsy2v#bW=MMq006AcO(b zv4P1;s|M8h%uG~c96w3-JI>xsxDBNJAUkp?d$JA7qE)o+_KTx&Zty=#;5cGrje}q% z6N6*N+OCrd%CL@YlTO!KwiH$|4-JC?PeZthmxD4!8=1kVo!cLgr%kn;D`Q7FBcT~V zn&2IuXKxQKl1)*1J!IcyHqt+k_+J$&Xvx%I3?+%SlXR7wpATQA_;=^tJV1wK2l94> z0%$OWifx^%x;Yj=5PGbX(7SiImMz`1x@PMNA6idsH;IlQ*}l!1UF1Q_^A+JH9gPX_ zKGjr7@`VwV<0MwSU@7~uQiue_p_hwju9i-u$i_zm7_nDmNXLH66vcrXKP6$5nQKemX`YTnmN>PG8}WUs=>lK zq!`JjaBvEX!_W{heVpx#=IpvRfcbL~g*m?0NuA8AF)tKDN0v5e$cP?;j z1-3?1Uaf5L#{2wQXmZOuo%zBUXLK<1i5V!Qu#N@n4SZhN6QD~MHu$}yNtdlF`qaQZ zfEY`M$Cz0>Qy!*A?QuuPf5ROAX3*nE)lw{(a4U*pU%YbHE($ z3_fiq+?x2em%up5V`+t_RGRorI!WmZ&dSRo2(*JhOU|Ql#R4_R%cref=%8r*B=(`o zz!To5qOOTkP6NjuMOtf5ig`W@G#aXi@#k|~wLwA;K7{qEGjb{}KRhDXMDs{J;zGMJ z@4eTsh1n%&YHt=$sH;niedpE^i*%)b&q2?d?x*dlYZ5Likm36Al3o~~K&-{IUe_MI z+Y3+gYGff{+rj5%O;Ib^I_yk%-l{&B+&-`_>_95( z9Ai>qJ*1a?|2dgVIXF_th=?)#9pI`j9Nzum{(1ivC!2_@IDu?*ll#r7;i8bX|GB*! z>l0=@{5F0T>0VC9skDM#vxkf!8#6Gixh(31{6xa?^VDHM#4|>kI*78 z%{o^5`M<9@;`}r4jLcTsDMv1WEomrpXsK02fYKzS528Ke9G3t%vqNN973O=Gy$G^% z3FnxH{rE`bjBAoKyg?(0UALlWmesn%Ls8cxH4C`r!~CUB@SEwV z#nY&7X0^9mmjauJoB1q6%w$H0I1=53L|f5RTbr2Jr#-pQWqCx(uSD$n{p;qULyC#w zk@f1AM|Xy{TYNG+0e%l(W~krQLW@zndL2gCBhi)9?KM7R_Aq`Hf~b_5X?T0TXaJ7G z4pGTva)lZ)vDX`P#pNajnp$o^$s)?C><(H9f|vtU|2h^@B~vtBj5J}2BAmaZtN9y7 zOmlCEsu5ObAMpSKcjrEt;())7@7%h;91(O6!ecasTbXSlK;j$Pr6>v!1p8dS zt@izc%fh?)O0Gt&lHonuGlAu+e<4PVdZL^(0=|RI>-HrnaQFpj+yaZo%sML8lW3wh zUu4IYF;NH$l^)m`)ME|=TM=dC`pCv4jZMv45e_V(d5NH6Pz^gezw&mQKb~uP8LxTj zCQ_fXEEL?($Lda~n80e{ZdsnG9E*1C0+P*N{p7Ok0C3$SPWs6*_J3`)r$7f(99tyn ziCMs4icT~g0J``?;Q%{EJ9p~TV%64&MJcNa3E zM5I7ZEyi9)VDAPYkvvQ2d}^{fkAnc4eq#Zp#51CdS;IGf*Sxlr=xZqxm-|9^6~7nR ztn$f7xl^->mB)2~9p&&*+*p|XQNxf~l#3!p@70b5i62SDX*FI>Ir8xgFhc#EltMLq>@A!~Mkd9&OBf~=A)MJfs5 zJ4H#&f%m?3a10^@yYBzy;t zF8=D*_>^YtMzMy%Yj$?#=o4@7`$@*9T5P_>6IfveFk^y-TsTjQwgaBvEL%5>wL{6B zg_A~ALg#m#P_*wWTW|2DwLe}fp;?7$v6euEHk^5y!{!#XW?FTjv?b?}s}qnazPm z0|Rtp&%1$IY>)YL>F2C@!n zb|DQ_en5hnn>Vd`gt4c2W>^YRF1#$`Hn5i{lIo&0oH0yV?;lM|#EvhE+yycyyH1mwU zXI_ShqPE_&{HpxSuq#R+ID)nFvim8}*;z9cdW4%m$YiBi`ErGXe4$Sg2s^W2zQB+C z>ytjnN0ty1mQE3MKW!03eph%cKYxf&oA=!`cu__RHxySOS&LDZPJkl%qelA|%D;_v zErW_D;t*ZdrulXf<2sjOk1hFaP%D}{Q6{cgTwU{I05C}d$|y$u_qw9{wR#ZDsZcZy zU0Um8RN37_>FBzjjaLqw-`tLv_;41Q`g-}G)g6=(2#orM^mOjNf>SdABgm~C`m!zqdc!DBt?hsirk69{B~r~Q7G2ms za8tuBGiZ5DK2>>!j>a02M6)Nw(M{nwzxDl@8n2Dx;ZrDYr%l#qiu?!T^`b;+oe}J+ zkQvsb9NVE5Zmqb2h=JqLxCi7la<;RrZ`uQ1BXwj`Q+>s`{Dado6q7M8OpFwf!(xgj zO0PqJFZZ6CuiL zL(SaH*DuTC<_@x@hX~?kx*VsvVV;uNRY3EEIJch(NuzR-`GO?G9?Jo(q$$NF4x zUA5K~OrZv{=+1KeIz)PgDt5gxkkwpyafuFZV}^?DyEh`pZVLQZiOD5Q%_*Ha_V>aj42+Jry-?FGsJbs5Bxks6kWZk_58nNubulQv}t+7Zh+URDv~ySpq0k8|`%v7Cb- zYuh$Yf@VJW)%2Ag#9?=xC3P21*d`l8!-k&viL!70hI)!I%As3cJne|lHcJsFJoZcR z)OO@W@PPV)ra?3`aqM|!q^B`WtSy9&!UJxBLWdefzmcu?DK>#e#XA>P{cgup=P_O{ zx`u172vpteENLbt-jdwNiSyE9)5#NCx-NeDOzdvWOO4lm8)182Ha&v_3*~ zTs*c-(A;`POBtQM{bKlVP~aUz^{(01KD2iZIL?BJRBQG_+r9b1*1SWMcg5OUq!Xm| zIgwnB5N|pk8?}m))Kz{z-?$$_kx0*wZPQH+U3P=0fos=Z`Aq5_wmsRJ!0RR|{}%rJ z)w$(9cHd<5#q6pz>{Ryir%9G)hz5^DHJ{U4_waSzpA0XP+m+aUk2p)-?by85P~bkPKnZWC5eqy#WJ07!5wc$W%+uyFwt>M0nF0;+?_Kfoo zM2DkMHM`VEYPM(@h)o^*X>`VDixHm{$Z6gOj2R?Bbx&J4M^zM{hD zL3-Fp*Aif}Yl7rlS(DS`*hGhm$eD{JQi`~g;$~GQ+@Wk;n*DaD17hJcn@lpcVos9M zL30D{!$iVzV}z$iMoG-qAkFOhYp`{_ddktvMNX%^yH+d--;(M8J>d(jao$fqTd6*g zMoNaC=iSfD#MJVd<4#W;wN6=RhRFwRnh46o@H3upd_hXx1n!0maa2ng`_}c5wl?L0;b+FM1T;_JBA+4u@ zVBxB+M8B$h4+U;xDUmjxCwo~@01{1f=U?5g#nUG5%yG_n!hQU%|Ajrz^5ZqiOiOZD zEwqfC(^RWOyvNFmm7tne$odHVXY)Qc=!u`m7D~(etexkP?43LVd{p7n1^Lw>`l`6f z^*(Pw*CdmT5pLvPwrx7-@FHeqf5%H}T(|C_9uq=o_gU#H#yfI5vfTk;u|P9DM(Q~+ z>^rlGA^qEGM-E3%B+ZFvoZ3EMvl;B^`8xl_Im1E8VFJ*d|Mo?h&c@`a4mbhiteP0J z0>_W!sXl5bMWpk<=c{F$g7crVz(hB`#~ZU zdO2Gr@o0ThY6@mdz(UK?f~Y?iPLP-x3sa)3gw&ku^!{+Ijly@mPO)TIR4%Iq4dTTcakbB zn4|tuXP?OQe+q3haUgrb^9)_2m%hgMwj*?vOhno>N5jJ&lR>||9`kd`+X^GMq*sLQ zf;l?&melz9`Xzt90<7oxkIoT zBR+HzK}FR9SHpk6>_s!wH^u|AIDnQlq9@;?TGgi37>ePzbF!C^tnz;}l4AGhFvcWV z*iko89s{febY6#56hf}kigiDPg+y}3_Li{`(6N;+SA$vVZv29LEm~CC4=GrK3 zABe4boc#G{Oqot->J7zORT3L}A5VhD_TdallX@k+<|e2?U@r~!j9bCKMe;qxaRuI6 zb2zULc?FN6%B=Xb@DD5?CDH!M&!_t}7rrukBZ7R0tv}&&j4uE05w{1sC7Jn9)barv zc|;OorXZuBadJbpemgd)n$hz#7Z!;KGMl&Y z#x`J#jt@pYeu7KsdS$y=or-c!4W7s4yAgwSE;VAwB7dMHzo$y-8(Z0PiH+AUNVRM99YMmgu6*O%4e~4N zfekZlv+m!MHCnUGX#Czdt9*D07F|*}cjNVvo^qTg86ZBk|5DgS-aWP5Q|~sgyMuI? zbAs?S>$Ag^0JyUjj;Jctjx~g(^mRS)cGe*&Y)A|W`l5Djg!ao|KEf19Otkd|O)gTj z7R-EPIUhP&uMdQ6?YQiG?+ve?D8z}!-AR`GE@P8Lu|-(z7hv*-qOfwPLax+Lq?Fx^ z)P%QQ1I#F%Lsdlz^tTaXyMQj`dwj2`&I4st=-Po*SatkYzsn8qKTj5F^-1JR{K1{+ zd?~ST;xb0bS~OqJnm_(UnutK4g zuz?v05;=a$IKNreD;3R1{&j-dO9|fwy|?w9UCuv+={3Q+eMG*L2`>In_S|0$tzXhv z)oqbdqaD8mhy?`Cg1O$|bX1GKIZrR`DbMs`bQ<;Jh>#mMuwMyog5hjv*Wgh_fv)`+ z1Hs(^a=i^K*z}h@24Rib*;m=kbkGzd70lF}-FMAzAi!y-oU7LcRxRWcZ~BKEsa|+` zW0A^N+$O@%Qkank(7@Ms=utZRElqh=)G^cCJm^-Li96RH8a5>fCw>vgdp7jU>>Cl` zn5M-ow8L6uAngqsSA?z&kk5X$zAKqF4c<;<%@OIXR)TTl%Yr_q%_iK1C4&m=w(L#c z&9q2e5nuuT=wv6NAnDw)PM?nRn=1{@zLKBs1W5+wxPkJ(e6A1`4Dn2~f+5Vou;n}j zNiXWIFdeAP`_MUWDLEqX#d7;&Bv5mw^K}$*NPO>!21avOlRESOoC&Fj!s^}$N>MW4 zp(i&xTTy3x!yxHAQNs@b?^1ai>Lo>Ldh9l6zGlz)+`zS+o z;;~oHH0|UnbDAr&@xHl_$FGiRqq6`HifGrN8eQoR>?aDax$Ps|RelNTz~2yx58O3Z zLm|##h|{zPD(KPt-ZdE)j^Zt z@uSz8R3Xwk_~1v6a48j}Zltg;3slB!r+bY-OP>l-dy}(rLNSMsDsg~<;P^K7?tCtJ z{%6rY12tCLh(iz!QbQ9-Fj{mFMkuIeKSX$WZU%3Ih_IJo|bN@>u?M>lSg_uzBypRItgR=WMO6d89*M%>MBfbaNhb- z5Bw%Jk~d|NqrK!r_kHwR@cEJ^yog6D9BtC$({$3SSAO)hY{a?M+ss%TWZdsK&CmBs z@7=mO9PIUW&~Ane5azb|C_4(jc;Xe)fhk{|4uIN}>S;qXdE{k}vettED}OCSSQ+2G zyAoNh`lKcmg%i@G6`PIQ31j&*zS#d5YW|r#bz9cSlU{B;c@K-9TBpm9_K~`f0bc0C zJm&ZC#Iul56*?tYezluXZUN^=;f#;jv+3_t%sk<24*PC3&Z#z?60rhwx7(hBe5VF~ zSiYl$!Hv_u*~ZG%FJLrmNo16G3W46Gb13sdi-gSDF6piORmpZ$7XJFre%jc)7R1KnLuGNx?!S>|EKtHAA zz=f%~&)4#n;+WT8+d9>l>NBaB@#Ict_1X7(|6uxntd>{QEfdwv&8H^ReiYV zIFJe_WN(O^s~v*=ToFeLQ{$%3-`oz#&J>a1LDzYPB=+VsWUV2)e|bVsOVl^nj9V6hvdqwk3b0Ao}`{_FqRDkAjmhyI7J{ z?GAl^lu@+D#y?|%Uts;?<0<&3y{$4jE3IX8Z(jLF1_tL(%c}C@Tr3#z*c;v_f{<#J zu}FAh7ghY(dE%sd+a8mYp~1uQc9(mV^Ry?;pGQP-Bqg@I(%=ZLyeRAz5pLfqs0aUN zAk@{}@0n*wix|`nzBDrIp?kEGy}uv&%*FZj#s(dgL0(dS6~9Z0fpc;4L(#e*BzLBx zVxxoB>}9R9wCp;u&i>3Rho)$>SP;qlyh8A4Kgl_XOo&2urzl+F(T7%3xP?dWcpVa| zp^D~KhReUbjACpV;s)8xiU9d0iiw?S$Qmuqq-yByXkYAIT$I%L+lKC_fS%QKv!URIBjNS(fW z(7B1aC-PR7pap*CCQIrdshJO}KfDJv&?MmL)pBmB-TJ$VJj@^w1eeM`ft0PcrHH(4XF;`Q#*M*nz=v$l z5~AA6jr*6|);WR+b8(IpP(y|N=y_i`$Qdf77_@WDWvST9CeQ2eo&-`$B1g5|l2;)j zz|wp;?m$45E-0Mh+B%QKWLY@&+1fRjTB3Hua#Qad73FZ574;``kK{VQGt^FlIX5Q6 za0j~UvQ?1Yo`%PCe?aKMX~}(wSmJxM`@BeUz4L6YaQwa3E8mPLSKN3J*F_4kM?GqW zqFA?htn-?zf@ICg9TZ92_Kz$vQ(Dy{^!-Z3nTUJ{Nl+CU{9xPuieKGtIF0&l66g1 zF_##wSDCa>KLm(#rMt_l=osvNCTdYH*yfbTV)p&yW^TUl5cy$!AHVnVIV-27XH%}b zz50pVF@v+YmPst3<_dzIS4UR$7E+l55(;Z1~W=)#B4a3#{ycBh zGj`pk^xwAPOt|HbduBw1|DyC@T?@T@56sq6F<>9Qxnv&o2_M68b{BcIM%LL0T*lT% zp#0DV2O2i3;5sRIyqP*p$OeofX1gLlFZGU!Q0#B1w!t^Y)bA;QPCfeer)8;yvk6!x z>waurRsRF+*6=GkO`LNgndYH}U^E^q!M5EX;===2a5}*8Tj*MG7S`jbW9B$|FMkmJL49(Buc*ve=vBp3tov^uq4ak35jB$ zAewo4eI0@X`U(+jxJEabl+L6#rF|~(%gFdB75J@7snhN&QGNBk+u=ODR8;w?y zP=mg?REn(q1hUXIaq!uV7Y3fOPhKj?)2nYXzrrD|*E+5!wrQ|zlqxpKbfi0SaL;q@ z;4dOG9ktBWbAFrzzr$5eWb#K?Ku^6n9P#9}`j&(f!0d-<-4rx9-s(Em2?L`sZEUuL z>FVLl%i8(%4Fh;+zk5+fKU0+X(GhAt0hzVZvT+RMs|6Gv4)w)9MF0tsY+ZP!)*E|o?N)nrp{H~g zXReo0rebaMGdiKnXJ_*^0~;P@%iQ;0pKkbBgeIL8gV)kM)ona%Ux8Zsi}o*?-#{{4 z)ZxS834tMpMhQ}G6^`;Jb^h1IvH_)XOV({RJ)35b!9j~&k!*-kFyzngZ1 zg|8HY&-_Co6LqsaESl{Dt4dGpO>PSRigQ7Dm)-<(BJtqC^Cc1sk8?0biKv{c2yX#z z@u|dtpn)tE+50)Kjgf%|B}XH{@U~>{;aP1nPf68TyEngwQ{DSmY9G&soskj#Ym)(E ze;>BAd?-(KAzi_<+k8FNlNdS0hhL0h;+;lsG*73`5%*GS>%_&_Xxzp7hynev)Vx5^ zHZ=^{hL01R?J>l&@-`@UB{2SPPBM;^Kw zqmYK+WiOB^KY+HyJh-NoF9}sF0|fy z6tt4U8G;#8^H;i@(5)16-A+fx0UL%gB)y*c?s}IV7Xn%rr)r~P*s3pQKZejILr4nq z-Lbaw?savH8)zAyXP%NHur#6KSbx2@b-^SJIUCt8959)M+ipAFvjPU;&s77j3>=O@ zt6=9>57aZgG(-U)oRK`pU`OR9KZ*%3@WN?%9Wv{y60`Xu5VIUK!z5=bG?(;*MRE+k z?X}^Gb7;d1&#`3mtWK}#CBy)F-roe=6ERZh1+^e6GRu!xH(2!tFSEI)U3305G#!gKQP}MCm{jf4oK`RiH(UiNy1xtlJX|p!|UR>z!-~6AUhF z!3N*`$X!dTB-qz!@G)+tpV?9qurL63#KU1#iOubbXm8r3xk6SXKa*3St!`yS<43Yr z$Rfq2l*S8J_)?hBxqt}=vMQK>!ttYHXNfTJTc1kI8G-8~o0ar+vn(uzn8qD;v7XrZ zRFAM_g3?EwEB1DB{`-|`1FkctNid4XVqf`8*JltX z=oX-1{luvj1?=R{Fpa3AHN)h`2yszj+zL>^F0Sk!FohWsL`v$5o&Xc_POY?<#44uT z%sB8l16z-;#69x#fQC~)k)4O(PX&~Ra+f+b2;}|f`Ps|D&G6X$Sb>;#P>|}V>-$I# zh9-~{_O%lWh@$)}sL#`4|L_v0aPT>w2CPRosa7)vdfUIDh`uBlZOHuF7U++}Qm zmTDD{?Eu24ne~tMo_b9A%;v8<089QwSKqvq$Y-|MJ3fjn2=9kd{xJggk>5&&>>V-I zi{F=-36t?yBBirimc7~v0X=3ZK`>eVAo(lPw1c8zF$k$>D``MPIjXkC$lFZU*TVem zOO1{bnD>n6!{Ux5<>(-4`9cQHHG(!=2OEjagCJpDC#MxBy0-3sjLW_nVep#shCU>a z?o#%hoFs!gBc%CfY=3+$t<$n%f5hR%L59;H--rpp5)AB`sr-J`5Y!L(rH>99_u1#q z+rZ5huy9>u;qcR&nkfPhp4O?~=KQ>3M6nj6Fp4#`JQ0mXOG&G>CO8KPnP@v@tb2Ow zrJ?cBK)$qHx|Ao7qmU=DT|`*yMUI3=FE3o5Cd7Gv9ww$#OR*_`MWA60aGmxzrdoPd zU2>OHY!$3NKC(|05z)|KU{`#qfw$5iG;u$FvyR#O#d5r3dR_eQJp4#Rf&2`gkn*yD zyWT{D)&BEK!G@!TJqGr#T6o*>X6P5Ej>Gp1UY`i;2@a;E4PEEhvz^6n?Z79vAN7gH zst$W^A*$u)2*}su*8rmJDz?mS)AeDJ@?aa575wfVwDCO`9fh#BAetO&Di0gj*lS>p zfiqXuO{Lo68`ldUlo^r-kixO%FUxHfN>?AZ`9X=T>z~rN&0EGO37sog{^N1T zIN7usq+J}=KYQU-Yuw|}$^Zss1fxRICso~&mn3i{_Dj9ypUg274h>`>KkQVE#iAm= zy0JHe5e`LvM2OPhU!gZHmwn^M?NAY_Oc1QhnfHR50IH8RJeN?>khp=T=ME)sYI@ME z*C{hD`rSw)!G%9?4;nn6|G`$X{|~_oI}_vo$5t~lu>DWL3?nle^Zzrm>S!gm_qUKK{>lPnHt(cd2HHxI6Ii%EZW*^eT=Y~CCS+Q!>a%9xsgeV zwB>f6^}gk7>#euD@yDvZ((LKHs_bFMOpK_FO-|~{WkkkB<|QTkXHZEemCe(G;60OkH#YM%eB zF~8S7?NV$$?nGj3 zY-_gv?h^gC-p>TUU=0mfVGXnX==%9B4Y7r_iJkxP+eJ(Iv@f_Zzu52OwB`1{cQ+sK znSVCO0T+KCi=T3Er*;6cVxRuC;75IVe;S*A@T3F=SErI=)8jw|#)f7f42{fxAPx;o z-hc3&VY6^>qik}25Wj8dfA4=ZNsW!!jfB&mQMH8X38Gfg5*_gl2Rfh}GuttFOfyi( z_m?hnEo4m}37Ltv1EjOLNOF19IKMT0fN!%S_zzuXMn4+H?Zp$76h#a-s5$9INl?o9 zS+(pR|DKy6mzn|9##$J&;p$$y{5kdofb)`abvmaR3-U|J&P1_{qNgd&&!Oa|y=4R( zTan5ZDg`E%p5ur&%c_L4c|V96csG5=HDSKHAGh6_$ws$1$jQGeSmDK+ZIUgHU;4&{ z-KZpT;6I`4FfJYll}V5U7)*DC^d0eEcTlI{n^`{+W^uMx*$ean*KFImGQ;X4^%LU} zEuY7h8N~rV)%c$=w9-MwKx^UZxX?vysX`R5jiaN*yMyZ$S=4-#DaX%3Je- z@5e&7mFi>v$=2~|BKJ`w(c=1VDiNJLxKKu14dS;u$qO%ET%KAthb}He+S=1XUtS&U znlutS#{*$rv;07$hSN57E!Fr3XI?ZeO8&42j`+>vXba@E!SQ9+;Ds7mJMWqOS{joR zY|^&xRFUe7(4gZzJ7x12A$(I5ErHcW+ORwKV(4_4Lo4gt8u6~X$~qa#Hf=YcR&pR_ z@rqY%rXkHL?xSpqG?Q(yUUf?nCvTT)n}9EsP*mOfADsDp^`e2C-k`_d@j&Uzw%hsg z5fOWGAxp}hYR5U>!wFo+th+G#?t`?9z$v|nP(G^Zt!l+0!elkr0&sb~)2jM#gpwj3 zhg(wpoM?%8NYE4cc+<(D@Fxf3@Iqgn)beW?smhztavzYAy|;-{X#7#?E1FqN+s1ar z)MH6#IEt9&>c^<-qk{KEKc{Y{F^`zpOQT-$Jc#;lmEW7O@goc7GRMi~1NQoh>9vd?aLUH%Jizlaw|Msh^_jf%@TL$cT-T$&< zVw7b3;(6YDp_ULo)ku5a5-hO$B6waVIrPd5YaTRHGH%yIKSo+7J_Z|->5X9!5dQ( ztwy6}cDbn#d>rGmv$Xw#Xqf)NOO9ljCIH!_<4fWa>XW8sRo6_O(u)|f_-?gx@M|Bj z*fjNScid-^Rwr#;asTc7;h7H^ED!l8avQ}~kaK|CBs}Z$)2`B0{YzZ>moQ9ZbnD15 z3Dr|yfnH$$sbrf(=dO8XfR#F#ww?_@7n4;26}gF9pg7D1ypscokY!V>=t>XK0%=vl z-zB+lIxj{^qQz*9F($;Wa0J>XDdUKW6cdhgn&GIYKc-KXu0po%QYGUIyEGMP_@pu_ zyah#CZBYRa6y=mA3-d}ZtD;*qS7bJkm8}M>xoW;i_#)z{TQRcn^C(bqm4j6?0i-`} z3@szeI8ozhVNF|sJ7ZUh=`6dgBE1sJcHl3&s&d4m$juyeXZ!HlD}ZLhrc~UI3kMtN z1819uD*cJ|wHX+)JWbl{TL7e^(n|J}AqzQ<7#Rz9%^-Cs9GXwF_7thtUJ*E62bx=W z%|pU1`A8;Ni)T#02()%36FqyWRf6}@ehg=zOSeK1FhcPbse>29ID+29*3nK}*~3$7 z_EI-H6O)n?eiby}pBjep_%NNX;vCEAHufA*VSQ95`S23SYGf5^@%R@t##LD{0!uu_ zKwuj!eI6y|hIq!k?u!UPvdvtk*$;mJiX>i+Z%Wl9SKqDUvhzCpOIkVt9WZqcYaPuH zT=45KRZc#1Z!d|I$72zNSns~YHK=w@pAcRRM5Rpl?3Y-;=Khfz*V`mLLem0pPQ-<~ zM4Uz^>CT6AYu+LMhPp7bxos9Qfnf>-PCNX$A}Qu{t-6@Z`dTg#0<7|#j~ zZ3EI7MNHOZmwZihpIrXQ{0KQ=485@h zhQB`aeofjja)Y+5(xNx3L(GDY45>b-h)1?&-T$$5rqv(-PjP?_J-{FeAQPo0?9xpY zPOqaA6*^`AAy9%1XNyx$MKz&OjS!EdOLOX*L!^7cBiv#Ccce0os?(wgVgMbHgj5SB zA-&fFdb8uhf1!i4`6QXsUuoK*FZLj6Bt25Ej1SFr;rTjcngS;bVKv_MPBoqV8hiC2 zIeF7q-mMVb@&@5zfu&ebm9C(gI?sVzdCAr<^e_C^0%Jk5GD`d6%69YV3u(8QRSm_@ zE_Y-^2Vz$o7k_bLahOUsT&=7h#`F+{0NcnC37ZNPc=%qhIM!gh{7k#?7(={y!9bnkf)Rq#u-pM?3b}k zS7f8~lwMwj`X;<9Y!`9=1X1Y~V;%+t$KIj1;F;ZzhK5RlDlS&eR_EW#8ppV?HNmI3 zA-As-GVRo}edK;!;OHRIrHUEAEkJJ-d)R*+n|Y!3u}X1e#HP zbZY(fHK%~!<{7c}#;&rNa0KhW)}KZNiMn?{XNELMz8}7;m!o?y^j$p!3|1ypp@3)7 z?^B(9tdMPV8h5g8s-_fs`Y4LRX9#h00Qou!Fi z`_&y0JW2V^t)AQ5bafS6j2h}G^ms)*On!-mG4H<(=To=K=%j1MFm_jl*zqbh9LMDU z&(u3mWjuPgOV8XWX%35Fnv-YQNVxf4%Jr=2Ao3D2d3u&^QiQ_W6P#eM%fu zN;D8o>VQ!1OTfO0XW9GGB6qPO_3PILI4yDdQU)|A{oZ-hek1NC6df2{cCY$uyXahzxl?k zQUFW&(GM*6B8^)eK^bl8;S7q`tZXl~WXK_Xs0CD?Oz8%d;C=J~uv!Zw+M^s=QL_43 z$e*!~#KU3DgTk1JmeX4dRL)TM&0F#(dqwJ{WV-UiTBMkvq`-~2+)4_tSxJP9Qha{5 z! z$$mUB8t5T0Qj;@t>N^|ARy3km|NTA@`x16=cpr0%#9BTTu4N-$LLnTC0eUHCRdZk8S-aO^72_n2Ku(N;o0*RK1#jdk zu!X|1GkY(f8J^cTv;Tag6OSf?jM~5di>K9g{D`DEk0?z1kI7M&Bu%=i|Gs}O5$jdE z-BedF(~TKids6NNrjNlmdYI_tDY;8(NGmQjd~`9OcLf&}z5aCpXp9RSm^9u+5wBCv zdX6r5q{V!LdRWV{U@+~q}rL$Ptrg#NDb z|1fqBOrk_xcYsHCY}>Z)*tTukwr$(CZQHhOdvf1=Nh+yIRq_|QPFMHYwV$!aL~I2Dy=o>uI%{xS_j;U?{La(jR>Shk^~g z#YK-R5G41Xw|pkake50@kv)jKduq1ZaI#jnG#ZJMuKj4zlk+;+domS@@InpQ9Wb|5 zG(sGC!mKh#Jeey5VZaph8B~0L$G)sHFl_ZYs7dRVZ4QCDi<+$q!@Mrx8ZFDH&%UC{ zSh(4rml5@y#BaqUagvJu32?-=?f}gHNBTs*XI*PiCQ0dMj(J`#6~w-6|F*-Z-muNX zb%saU|L=A3HXf>#3~dBi$5z}}BZz=sA-WMcqS5i2nfI*OWNug4_b49Qh2v_9G+tZM zDDeX*Cwd|KQ_LM#J6(h*FIlvg_w7^;4;gbyw6n1BlYs1Tz@P*($OR5ih)4JLM~W10 z7a9wJI&PNP| zv5)71BvriM;yZEMC@5pCcpP2QdRq%VZ3mNmkA{JnU2g$Kt>OXBwbNFvf{AXZuAQ!K z)@4y4i7pK?3%j)VOn3hxQ&68dyiAl zSXa}wiZUaWOBttcdvi@}m=wa==t=knpb9#q7t<9O1(kX3*`0%opbWQ)fjgUXleAbPK zi(Aw3d3O{2CGG*?V4x&3%NYYldEEP)`e*!l9Eig|2IaixY#|kO!)B6S$5;=+X%3tR zLbD<=qUO=DHm{0jNT`!QhTPUOOY0p`Y zy>jI%DP62|2hq>!1jpoAb8Ai%3GI`=X=&L$#99p2J{?{rfl@dgf2)pv_ZD9L`9#7@ zpJq$;%V=DDNtBX==nRb`?JJXp851D_ktc-Ipi3$rp?XC%Xi@dx|E`E0!Nn0O{K1wpf9Pft+P!3p@k#0h)8g# zv1!g$LkOM13(P&x#B#dQO+MJEx~Tac$gn!b)HoKM+H7Q&1nvb0ZiuppZlGa7ioW+> zDLWE!(XxM!u?FfFcv53JfzSU2Wr;-+x{b1Vc^f8uZKEK#XfRTe7$s%(LZu5UUr|jG zE)PcV{v0aVX8HUV6xcwRK`By}3>I!}+hf{elq49Qy z0U|9pi$xlaSm=yjX%Z8y7)mgRVXtYOsSY(+9=~4<ZnTE6<R&#uVtL zIb=y##xO-NL}y?t2q+_5+woqQa;(#*f;}_H9;wP2->^j9^?=Mx37(aVit^5I^dZ?_ zdxQ+`MaAZ@4O&br??y<2*J~gHdE!x>(5?}X2oZWA0h{?kVZRzC4Rl2fY5jnu;ji#O zj~Gi1%NNYdVZ>&b)a*r0DWXhnU}kJG1?Ov5J)pMJKG_5t&d{|PpK%rWX1?5UKWluU zWdKE`zljWop^06qf&{505UfF2RUFF)*C7Y#segj5SrUtHwI68PXBPjKLOunB^gHed zGIqO6rTR4@GKxKsescb6t8SH#2LXcg5^aEx^cEl?J-36l>%^O&jAIpzV}&ID^QV;t zJT;dn5kZ=u!0oC?H(G4ZnlPQNrFXRRZSId*_2lVnvciCaC4tV$faUYBhF&SQm*U4- z@zn2=vYOzGYaBDoWmhiRs)zqR4S8=n4L3n-L zfU`HGZOt>G%=0}Fo9m^clw_3bh7Za$r zZLEQg*>5iz|3>X%fBEo10#zd}wNJkTVJly`J?>)kD3Ho#lv@R&z7!U-Qt@0U>A$be zc18l~V})Tvf6gE4K{1|$&T%~MglU8UYa;=1L(<+Uv7OYDV{XQ^${4H{SOrF~+L&QN zM!aMwBBRccMivSOTYM&MJx;O+nyUJ8b?#@yizgqyk2c4MjR7kt1=nE>04CFpao4W2 zD;Ta_ED~yTG{CM~<+`}g>g|oeB1%%_GaJA8na<@lbBm}xZ{8OMJaz(pt1g|MtO1=~ z&+Add(eUBk3hya<0Px*dLI{Ul6*GT9NIOWs;HeiW*23r+*LrAc7-G2bZ^h47e~$+x z(z8Ld^|xH@qQ|3ov&7^H5ySOT0pJeL(i~|>=yZ}}5lTqu%WVftL|(hld>I_YxuOfN zdP0Hm8O$T@`aX(iMIPCie!yMZZ2oO_j_AgTG&Q~36PHr&`MS(0sI=s#n2cj{e{aTl z7l1J#I%Rz^bn~uxv?G4JLw9u-wr-E?ef!5;m%hi7Lz4&IN9aN%6u0Sl`78QhH{VR> z&JC@77=551MQ!zc?U0oZ4{8+pnVprbO?+q*x?$GaV`99;fGi0WvJmatn}X@W zz$A{tUBNfQUg9-Q82&0qB`P9|p)YhVH;@&Bh%c)g6q(G{R5af-sb6jO1{reA8hzh! z66DT2j6xJYB1=(P>zMD+-X+B#1Vetr+$r3ip~KiemQX`|!*`JG57v?^Mc9%N6dB=# zRw5I833eTK{`ynV1*@+!<5v37{S-z!=*uRAJ_H>>?r;NWo^JG?fd^w6Vmult0S3kL zZh)R-bDg|$1rie--6ylJE_KU>3~zhzBu!5fa`H$O<_JnU8)F^wz-5-9`_ptr;PY+0 zRIumC1F;&!o~xEAWNs+T#E=n#phqxEo=$Um)v%mEHi|y2|Mmintqxz>3H6Vh=yHEr<^sfUfiox206AqF%0lWb0O=vpk0d8hH#(3DnKQ;A zbhw-bo-TX8WT>kWQF>3om)a(F^x`9H_ER}}z{YXP*{$sbf>nuoPk-A#PIznR+fSjz zKs`+-J?>ZXj@JFs8Ln9OA-Kel3^l6*YsOaIsBLZm`#Vv!GZa0XzMj7OG+(SJivC?y z=0L2x3g2f^!LqLG+JOAJq87@#yDc$;yKeXIcufp$a%&l5*I$h3DZ+dWeQu7;sxZHypyc73sW!z%= z0O+_Ry)xJuI@6DYD?CxD@m4FVcRpMXEU5}=KjtTJEwUd(Z4Z7zg|$TOQy^+B$NKeh zz5~EjiQ1-$b902=B1aQU@YB<9X*EPdxHzC2$nAseYF=8NGw_%|k$g@CCyO73vP_Zs zUe7;Ii;AkKo-XE)%(7Y5n-IYzL4np7!F)}XJCih~1UW6E1qDgwfoY1l5A=C{d-SK{ zsqvK?8+M7^sj=8>b5&O4o?=-ISflGG6^mnut~@Tky%vop5%r&i3?{*a4%*^i$+l6b zPW5GWEq(GtuAQb}?u2IFAH)mKel|nDd{Vjp<&zQ8BqY$8z#u&G!KEx`58s3On^Kn3 zfFxF2Vja30-UY^>VV>gaXkP@#=GZ`||EOaF1+$MKr%;~)+|6=iXDJE29+g0#rm&`0 zlB4F3I1!zj6Q8;@3(p^ITs6#Q0jE)5^RI|4KCV^5H9bZw40cw#lIIMsBvVpn%DA0E zJ_NMwRsBq;Q^d>b65Cjvm+e%s>wB=0 z$=eNr@DQs>2MBa%MFoE5GRRLGHt0>neYjdZR4l&LYYL&~P5$ddwAxeMF9G{P4aJ)B z$kFQt&7X!vD#==qtEM?ZLoq#q@=VS76fXYjyMgE~zn5$SHU^C8al6ml)_|+^-7T!h zTqMOKPvKn5akZgtiTZ0|HH>4acv$~=*xW4|#a(JxP&^RO#gxx7@$I^5DH@lBe zLX8d{isLdu1xQCJFty13edeo9uN!6L6-+$cO!nbWEr=_~W*~cGY=JvUGy;kITD^Dj z6@MsUdy(AIG=gADWjioOyts%ls+M}VyEj`89v+WL^kEJ)+X#YHf5(`2;i^o}!en#E ztr4A(3zz->v6~_42Q|=1(MQdT9^44)x)xSwQVczBEyz(Apgqs|q)A$IWNDG#ym==f+ z&hZ}P96eEMSl~uTl;D{0TL<5jR4q9}==~+?z(|C^4X$QYLmx#=^+&bqeqgpbW`cA%J@?@9bX& zvk|9?GraQO8q45OMnkiMx6Qp}155Yn6gL!oE9pFifEO;Gb&WuhvVAb~^o>6iW4lE=TcVc`ymC?g(?E?wKzr!nQ~?1H&E- z28=~XdMC0{73WJq4hYyRO0A!2rM6I^3Uqvdq4CJ)tE}CdCW$QA8urgv@4&jCwLu>r zpYd7!M6p9wbKwN^OfAha*D+1zDvKS<@qi5Udy*(G!c|GfLz^xTlM06|JiB1oO&ka3 z6lZEpOlSW+wSkB)6!#{lR8^wBYTjLd=3cUjhvk@tp+2m&nH$}+*)H#NEW)Rf(o^dZ zFyhkjSMA}NjJW={&r1TyQqD-H=CTNR&hz%Z8hwUx@qKEBWEFdDL_JGRUV<1A?as>L z>N?<_%7V-`M6Re_Jl0NTBVjq`e%z_);)Z(JipdqyPrs+IID9_4|4Af=iuIw>9S(m_#Pj$ z6S@+fx~IzxDb#@LNT_7J{cQ(8p>MhF1lKkRH&9)6AaFh`R%lAqyZ6 zMX0mJVca;WTa&ZwAce9ezLEITp*D(F6MtX-X$)#<+hNtDbd5OW!ruy>nOzB5-Gy5s z)DZoP7+7xHYwFqq4pdT|$)?MrgN7r@jbo2M&c8}xmgE}mp&MB-ueaYM@J#q#93OcK zjVO3-bU1lBDz^4USP=ev#PBd^-U zg}ItuSx8NY=8Y0{s4&!{$l&2sSzM^UXHp1Bux(1XzCIPU5||uPhz%w?1BJ%?O2MD( zeEtT&EI2=kqr#9fP=yIL4sIN-wFlmujPj3~G5%5*@zh~#Lu`YclmU%Jc%s?9(2u9q z^i4DQnw6PmCl27d1+7qrlz_UiXP*|DQ0DPE8k z;sI=vXY?P0Gj>w27xM_!#!<&!fB5m67F5zhIq1hzbjKf}oBRzlGRupot^zVSe_Agus(D`6&3MB4h-GCG93(fMynKdNMToK&xO2fp1;AUAg zOvK8BciBYT&6+O2J6^m;n1`H5=2Vw?cDglD9&|h9stnp-S#GXx<8aY$;TSIgs(8@x zKe?Yw>THVb;XT^_j=jb!7eX#u_R};M|C~RDot?G$u$EK~FJNe?3;sRLo&NgTPfVP| z_oFkb4iF@NnU;b$?T=+<;+lmZi0FhLyV`POFphkhna8-(ug_9n@;(yG;z)=WrM_J6 zSe?p$+_l))OZC(`;aL>3#0I|~&d+QIq~Q(=aW}4!Z~PMZ`gqCU9;v~~sel)vN1nY$ zAv;=9Yy)svipupsN&|H0eh;lhyRzam{uelGx`Nr2B*Ve$p!SkpW!`X249Oo$MM0&N zk)R|~x{kH(uWVnxS_GY?fg%)ZImLbgPinqRBN9}X8!!u#uVdCoFTnn-m7;UjMMTG) zgrn6Xp7k<)vrOHfM#N-1J1>ti2e)P#iXZ%P17YAT#-+$e?GGO`Xa;l5TJY`p+y=2S zOsN|}%QEN_?3wJXZYL@~!4aT}zAYI@R3;xyf!c^tH$PBQRUi~>MXu!a27@xSF6&-HCYlbBz3h1> zS{sNl+#P$TNbi{I){&EF1nO_rw|Mak;>huD@)d%@OH*##AP>)|j_$CL&j4^sh~S%v zNILIm@yB0^Sf0m{;DBoEh%3(w`dUHJ2;7@RUJWYTV;so@sCLg+HaAdF%ejHq&ZUE# z5nR;zx#t`1_Q?t8{FQ`6?eN*JWCsv*iZ(5IsUEO*1}D-eH+}6>rwLOs%(z7fW^)tO)TSbg+HK$d_tZC$aE@?eWY0TbWIWn+63vF ze>9u+DuK>hIR5Xq9wQPx_pANZCr~TH3F+=60eaK@kU2#?4zq6OIG}is+X_fnIOGWDrm(2)3TV2X4SHO9jiiTJ7Y5R` z8v3=mOIOhAV^x{ZC?$uARWWEj)>m^d=u-yH2u9fl>(E`r` zsGut>(|GtQJh&FI@>DcaksAy^KVhnm2`Q-s`Nkdn&$KHU3lu?rOj#$2HeJYDA`5=H z_}OzaNILf4$oDYL!p)c!9P!hsyAv=pvP-oH-EF*fU0KjP(-&=D7IUn^v48ISCHG)R zXQ5sJOg(#5g9N&y>w4SqP>OK)LUGPA!Xi3D;V})Y&_hbjXJD4!@62%^K!vT#&(bo4 zJT`S3`gACGI6yrk�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 From b1673557aedbf751261eba197d284341c70ee01f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 31 May 2019 12:05:19 -0400 Subject: [PATCH 014/160] updated README to nicer Markdown, added links to papers for convenience, added code formatting, and updated ROS package version --- README.md | 26 ++++++++++---------------- package.xml | 2 +- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 720517669d..043ff863b6 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -README - Georgia Tech Smoothing and Mapping library -=================================================== +# README - Georgia Tech Smoothing and Mapping library -What is GTSAM? --------------- +## What is GTSAM? GTSAM is a library of C++ classes that implement smoothing and mapping (SAM) in robotics and vision, using factor graphs and Bayes @@ -13,12 +11,11 @@ On top of the C++ library, GTSAM includes a MATLAB interface (enable GTSAM_INSTALL_MATLAB_TOOLBOX in CMake to build it). A Python interface is under development. -Quickstart ----------- +## Quickstart In the root library folder execute: -``` +```sh #!bash $ mkdir build $ cd build @@ -40,32 +37,29 @@ Optional prerequisites - used automatically if findable by CMake: - See [INSTALL.md](INSTALL.md) for more installation information - Note that MKL may not provide a speedup in all cases. Make sure to benchmark your problem with and without MKL. -GTSAM 4 Compatibility ---------------------- +## GTSAM 4 Compatibility GTSAM 4 will introduce several new features, most notably Expressions and a python toolbox. We will also deprecate some legacy functionality and wrongly named methods, but by default the flag GTSAM_ALLOW_DEPRECATED_SINCE_V4 is enabled, allowing anyone to just pull V4 and compile. To build the python toolbox, however, you will have to explicitly disable that flag. Also, GTSAM 4 introduces traits, a C++ technique that allows optimizing with non-GTSAM types. That opens the door to retiring geometric types such as Point2 and Point3 to pure Eigen types, which we will also do. A significant change which will not trigger a compile error is that zero-initializing of Point2 and Point3 will be deprecated, so please be aware that this might render functions using their default constructor incorrect. -The Preintegrated IMU Factor ----------------------------- +## The Preintegrated IMU Factor GTSAM includes a state of the art IMU handling scheme based on -- Todd Lupton and Salah Sukkarieh, "Visual-Inertial-Aided Navigation for High-Dynamic Motion in Built Environments Without Initial Conditions", TRO, 28(1):61-76, 2012. +- Todd Lupton and Salah Sukkarieh, "Visual-Inertial-Aided Navigation for High-Dynamic Motion in Built Environments Without Initial Conditions", TRO, 28(1):61-76, 2012. [[link]](https://ieeexplore.ieee.org/document/6092505) Our implementation improves on this using integration on the manifold, as detailed in -- Luca Carlone, Zsolt Kira, Chris Beall, Vadim Indelman, and Frank Dellaert, "Eliminating conditionally independent sets in factor graphs: a unifying perspective based on smart factors", Int. Conf. on Robotics and Automation (ICRA), 2014. -- Christian Forster, Luca Carlone, Frank Dellaert, and Davide Scaramuzza, "IMU Preintegration on Manifold for Efficient Visual-Inertial Maximum-a-Posteriori Estimation", Robotics: Science and Systems (RSS), 2015. +- Luca Carlone, Zsolt Kira, Chris Beall, Vadim Indelman, and Frank Dellaert, "Eliminating conditionally independent sets in factor graphs: a unifying perspective based on smart factors", Int. Conf. on Robotics and Automation (ICRA), 2014. [[link]](https://ieeexplore.ieee.org/abstract/document/6907483) +- Christian Forster, Luca Carlone, Frank Dellaert, and Davide Scaramuzza, "IMU Preintegration on Manifold for Efficient Visual-Inertial Maximum-a-Posteriori Estimation", Robotics: Science and Systems (RSS), 2015. [[link]](http://www.roboticsproceedings.org/rss11/p06.pdf) If you are using the factor in academic work, please cite the publications above. In GTSAM 4 a new and more efficient implementation, based on integrating on the NavState tangent space and detailed in docs/ImuFactor.pdf, is enabled by default. To switch to the RSS 2015 version, set the flag **GTSAM_TANGENT_PREINTEGRATION** to OFF. -Additional Information ----------------------- +## Additional Information There is a [`GTSAM users Google group`](https://groups.google.com/forum/#!forum/gtsam-users) for general discussion. diff --git a/package.xml b/package.xml index 5b50b5af9b..e14e77f2ad 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ gtsam - 3.2.1 + 4.0.0 gtsam Frank Dellaert From cc4bc458eb14d26d0bdc7073d869ff6a01de43ea Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Wed, 29 May 2019 10:43:56 +0200 Subject: [PATCH 015/160] Ensure users do not mix up Eigen versions --- CMakeLists.txt | 29 ++++++++++++++++++++++++----- gtsam/base/Vector.h | 13 +++++++++++++ gtsam/config.h.in | 6 ++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e9498ac218..585213159a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,6 +314,29 @@ else() set(GTSAM_EIGEN_INCLUDE_FOR_BUILD "${CMAKE_SOURCE_DIR}/gtsam/3rdparty/Eigen/") endif() +# Detect Eigen version: +set(EIGEN_VER_H "${GTSAM_EIGEN_INCLUDE_FOR_BUILD}/Eigen/src/Core/util/Macros.h") +if (EXISTS ${EIGEN_VER_H}) + file(READ "${EIGEN_VER_H}" STR_EIGEN_VERSION) + + # Extract the Eigen version from the Macros.h file, lines "#define EIGEN_WORLD_VERSION XX", etc... + + string(REGEX MATCH "EIGEN_WORLD_VERSION[ ]+[0-9]+" GTSAM_EIGEN_VERSION_WORLD "${STR_EIGEN_VERSION}") + string(REGEX MATCH "[0-9]+" GTSAM_EIGEN_VERSION_WORLD "${GTSAM_EIGEN_VERSION_WORLD}") + + string(REGEX MATCH "EIGEN_MAJOR_VERSION[ ]+[0-9]+" GTSAM_EIGEN_VERSION_MAJOR "${STR_EIGEN_VERSION}") + string(REGEX MATCH "[0-9]+" GTSAM_EIGEN_VERSION_MAJOR "${GTSAM_EIGEN_VERSION_MAJOR}") + + string(REGEX MATCH "EIGEN_MINOR_VERSION[ ]+[0-9]+" GTSAM_EIGEN_VERSION_MINOR "${STR_EIGEN_VERSION}") + string(REGEX MATCH "[0-9]+" GTSAM_EIGEN_VERSION_MINOR "${GTSAM_EIGEN_VERSION_MINOR}") + + set(GTSAM_EIGEN_VERSION "${GTSAM_EIGEN_VERSION_WORLD}.${GTSAM_EIGEN_VERSION_MAJOR}.${GTSAM_EIGEN_VERSION_MINOR}") + + message(STATUS "Found Eigen version: ${GTSAM_EIGEN_VERSION}") +else() + message(WARNING "Cannot determine Eigen version, missing file: `${EIGEN_VER_H}`") +endif () + if (MSVC) if (BUILD_SHARED_LIBS) # mute eigen static assert to avoid errors in shared lib @@ -499,11 +522,7 @@ if(NOT MSVC AND NOT XCODE_VERSION) message(STATUS " C compilation flags : ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${cmake_build_type_toupper}}") message(STATUS " C++ compilation flags : ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${cmake_build_type_toupper}}") endif() -if(GTSAM_USE_SYSTEM_EIGEN) - message(STATUS " Use System Eigen : Yes") -else() - message(STATUS " Use System Eigen : No") -endif() +message(STATUS " Use System Eigen : ${GTSAM_USE_SYSTEM_EIGEN} (Using version: ${GTSAM_EIGEN_VERSION})") if(GTSAM_USE_TBB) message(STATUS " Use Intel TBB : Yes") elseif(TBB_FOUND) diff --git a/gtsam/base/Vector.h b/gtsam/base/Vector.h index 91aec85b88..74cb89918c 100644 --- a/gtsam/base/Vector.h +++ b/gtsam/base/Vector.h @@ -63,6 +63,19 @@ GTSAM_MAKE_VECTOR_DEFS(12); typedef Eigen::VectorBlock SubVector; typedef Eigen::VectorBlock ConstSubVector; +/** + * Ensure we are not including a different version of Eigen in user code than + * while compiling gtsam, since it can lead to hard-to-understand runtime + * crashes. + */ +#if defined(GTSAM_EIGEN_VERSION_WORLD) +static_assert( + GTSAM_EIGEN_VERSION_WORLD==EIGEN_WORLD_VERSION && + GTSAM_EIGEN_VERSION_MAJOR==EIGEN_MAJOR_VERSION && + GTSAM_EIGEN_VERSION_MINOR==EIGEN_MINOR_VERSION, + "Error: GTSAM was built against a different version of Eigen"); +#endif + /** * print without optional string, must specify cout yourself */ diff --git a/gtsam/config.h.in b/gtsam/config.h.in index 8433f19b0d..92380f8eb5 100644 --- a/gtsam/config.h.in +++ b/gtsam/config.h.in @@ -52,6 +52,12 @@ // Whether Eigen with MKL will use OpenMP (if OpenMP was found, Eigen uses MKL, and GTSAM_WITH_EIGEN_MKL_OPENMP is enabled in CMake) #cmakedefine GTSAM_USE_EIGEN_MKL_OPENMP +// Eigen library version (needed to avoid mixing versions, which often leads +// to segfaults) +#cmakedefine GTSAM_EIGEN_VERSION_WORLD @GTSAM_EIGEN_VERSION_WORLD@ +#cmakedefine GTSAM_EIGEN_VERSION_MAJOR @GTSAM_EIGEN_VERSION_MAJOR@ +#cmakedefine GTSAM_EIGEN_VERSION_MINOR @GTSAM_EIGEN_VERSION_MINOR@ + // The default allocator to use #cmakedefine GTSAM_ALLOCATOR_BOOSTPOOL #cmakedefine GTSAM_ALLOCATOR_TBB From 14ffecbfed7a85797e584c47877ba75bc8d9ab6c Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 1 Jun 2019 15:22:38 -0400 Subject: [PATCH 016/160] removed shortcut notation in trust-region formula --- doc/trustregion.lyx | 6 +++--- doc/trustregion.pdf | Bin 60124 -> 59984 bytes 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/trustregion.lyx b/doc/trustregion.lyx index 3296da5127..97bc6ec74f 100644 --- a/doc/trustregion.lyx +++ b/doc/trustregion.lyx @@ -620,9 +620,9 @@ To find the intersection of the line between \begin{align*} \Delta & =\norm{\left(1-\tau\right)\delta x_{u}+\tau\delta x_{n}}\\ \Delta^{2} & =\left(1-\tau\right)^{2}\delta x_{u}^{\t}\delta x_{u}+2\tau\left(1-\tau\right)\delta x_{u}^{\t}\delta x_{n}+\tau^{2}\delta x_{n}^{\t}\delta x_{n}\\ -0 & =uu-2\tau uu+\tau^{2}uu+2\tau un-2\tau^{2}un+\tau^{2}nn-\Delta^{2}\\ -0 & =\left(uu-2un+nn\right)\tau^{2}+\left(2un-2uu\right)\tau-\Delta^{2}+uu\\ -\tau & =\frac{-\left(2un-2uu\right)\pm\sqrt{\left(2un-2uu\right)^{2}-4\left(uu-2un+nn\right)\left(uu-\Delta^{2}\right)}}{2\left(uu-un+nn\right)} +0 & =\delta x_{u}^{\t}\delta x_{u}-2\tau\delta x_{u}^{\t}\delta x_{u}+\tau^{2}\delta x_{u}^{\t}\delta x_{u}+2\tau\delta x_{u}^{\t}\delta x_{n}-2\tau^{2}\delta x_{u}^{\t}\delta x_{n}+\tau^{2}\delta x_{n}^{\t}\delta x_{n}-\Delta^{2}\\ +0 & =\left(\delta x_{u}^{\t}\delta x_{u}-2\delta x_{u}^{\t}\delta x_{n}+\delta x_{n}^{\t}\delta x_{n}\right)\tau^{2}+\left(2\delta x_{u}^{\t}\delta x_{n}-2\delta x_{u}^{\t}\delta x_{u}\right)\tau-\Delta^{2}+\delta x_{u}^{\t}\delta x_{u}\\ +\tau & =\frac{-\left(2\delta x_{u}^{\t}\delta x_{n}-2\delta x_{u}^{\t}\delta x_{u}\right)\pm\sqrt{\left(2\delta x_{u}^{\t}\delta x_{n}-2\delta x_{u}^{\t}\delta x_{u}\right)^{2}-4\left(\delta x_{u}^{\t}\delta x_{u}-2\delta x_{u}^{\t}\delta x_{n}+\delta x_{n}^{\t}\delta x_{n}\right)\left(\delta x_{u}^{\t}\delta x_{u}-\Delta^{2}\right)}}{2\left(\delta x_{u}^{\t}\delta x_{u}-\delta x_{u}^{\t}\delta x_{n}+\delta x_{n}^{\t}\delta x_{n}\right)} \end{align*} \end_inset diff --git a/doc/trustregion.pdf b/doc/trustregion.pdf index 0a733a059b4c7784c40c183d4d9cd87a4b9e1ccc..22dcac035f71186de65c374c7acd7b7f22c8c4a4 100644 GIT binary patch delta 19401 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 E3YC(XHvj+t From 3af1de25572f50cbecbf9a9cbfc482551e875155 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Sat, 1 Jun 2019 15:22:51 -0400 Subject: [PATCH 017/160] removed bdsk entries from bib file --- doc/trustregion.bib | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/doc/trustregion.bib b/doc/trustregion.bib index 6118e40392..7c87eef9b6 100644 --- a/doc/trustregion.bib +++ b/doc/trustregion.bib @@ -8,7 +8,6 @@ %% Saved with string encoding Unicode (UTF-8) - @webpage{Hauser06lecture, Author = {Raphael Hauser}, Date-Added = {2011-10-10 15:21:22 +0000}, @@ -17,11 +16,4 @@ @webpage{Hauser06lecture Url = {http://www.numerical.rl.ac.uk/nimg/oupartc/lectures/raphael/}, Year = {2006}, howpublished = {\href{http://www.numerical.rl.ac.uk/nimg/oupartc/lectures/raphael/}{link}}, - Bdsk-Url-1 = {http://www.numerical.rl.ac.uk/nimg/oupartc/lectures/raphael/}, - Bdsk-File-1 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmUxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+Sv+qSlgAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2OYAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmUxAAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQAxAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUxAAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUx0hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, - Bdsk-File-2 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmUyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+Xv+qSowAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PMAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmUyAAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQAyAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUyAAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUy0hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, - Bdsk-File-3 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmUzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+mv+qSpQAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PUAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmUzAAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQAzAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUzAAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmUz0hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, - Bdsk-File-4 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+vv+qSpwAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PcAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmU0AAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQA0AA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU0AAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU00hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, - Bdsk-File-5 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmU1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn+5v+qSqAAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PgAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmU1AAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQA1AA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU1AAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU10hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, - Bdsk-File-6 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmU2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn/Fv+qSqgAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PoAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmU2AAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQA2AA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU2AAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU20hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}, - Bdsk-File-7 = {YnBsaXN0MDDUAQIDBAUIJidUJHRvcFgkb2JqZWN0c1gkdmVyc2lvblkkYXJjaGl2ZXLRBgdUcm9vdIABqAkKFRYXGyIjVSRudWxs0wsMDQ4RFFpOUy5vYmplY3RzV05TLmtleXNWJGNsYXNzog8QgASABqISE4ACgAOAB1lhbGlhc0RhdGFccmVsYXRpdmVQYXRo0hgNGRpXTlMuZGF0YU8RAYwAAAAAAYwAAgAADE1hY2ludG9zaCBIRAAAAAAAAAAAAAAAAAAAAMpAsaxIKwAAAD/T8xBIYXVzZXIwNmxlY3R1cmU3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQn/Pv+qSrAAAAAAAAAAAAAMAAgAACSAAAAAAAAAAAAAAAAAAAAAKTGl0ZXJhdHVyZQAQAAgAAMpA6ewAAAARAAgAAL/q2PwAAAABAAwAP9PzAAUCJwAAvuwAAgA5TWFjaW50b3NoIEhEOlVzZXJzOgByaWNoYXJkOgBMaXRlcmF0dXJlOgBIYXVzZXIwNmxlY3R1cmU3AAAOACIAEABIAGEAdQBzAGUAcgAwADYAbABlAGMAdAB1AHIAZQA3AA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAKVVzZXJzL3JpY2hhcmQvTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU3AAATAAEvAAAVAAIADv//AACABdIcHR4fWCRjbGFzc2VzWiRjbGFzc25hbWWjHyAhXU5TTXV0YWJsZURhdGFWTlNEYXRhWE5TT2JqZWN0XxAkLi4vLi4vLi4vTGl0ZXJhdHVyZS9IYXVzZXIwNmxlY3R1cmU30hwdJCWiJSFcTlNEaWN0aW9uYXJ5EgABhqBfEA9OU0tleWVkQXJjaGl2ZXIACAARABYAHwAoADIANQA6ADwARQBLAFIAXQBlAGwAbwBxAHMAdgB4AHoAfACGAJMAmACgAjACMgI3AkACSwJPAl0CZAJtApQCmQKcAqkCrgAAAAAAAAIBAAAAAAAAACgAAAAAAAAAAAAAAAAAAALA}} +} From da1b7f92f2d73dff90491bf15fadd3d6eac21f69 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 16:46:17 -0400 Subject: [PATCH 018/160] Modernized enable_if, re-grouped methods to add factors --- gtsam/inference/FactorGraph.h | 644 ++++++++++++++++++---------------- 1 file changed, 334 insertions(+), 310 deletions(-) diff --git a/gtsam/inference/FactorGraph.h b/gtsam/inference/FactorGraph.h index 0ecfd87f19..ed14f18634 100644 --- a/gtsam/inference/FactorGraph.h +++ b/gtsam/inference/FactorGraph.h @@ -22,350 +22,374 @@ #pragma once -#include #include +#include #include #include // for Eigen::aligned_allocator -#include -#include #include #include #include +#include +#include +#include #include #include namespace gtsam { +/// Define collection type: +typedef FastVector FactorIndices; + +// Forward declarations +template +class BayesTree; + +/** Helper */ +template +class CRefCallPushBack { + C& obj; + + public: + explicit CRefCallPushBack(C& obj) : obj(obj) {} + template + void operator()(const A& a) { + obj.push_back(a); + } +}; + +/** Helper */ +template +class RefCallPushBack { + C& obj; + + public: + explicit RefCallPushBack(C& obj) : obj(obj) {} + template + void operator()(A& a) { + obj.push_back(a); + } +}; + +/** Helper */ +template +class CRefCallAddCopy { + C& obj; + + public: + explicit CRefCallAddCopy(C& obj) : obj(obj) {} + template + void operator()(const A& a) { + obj.addCopy(a); + } +}; - // Forward declarations - template class BayesTree; - - /** Helper */ - template - class CRefCallPushBack - { - C& obj; - public: - CRefCallPushBack(C& obj) : obj(obj) {} - template - void operator()(const A& a) { obj.push_back(a); } - }; - - /** Helper */ - template - class RefCallPushBack - { - C& obj; - public: - RefCallPushBack(C& obj) : obj(obj) {} - template - void operator()(A& a) { obj.push_back(a); } - }; - - /** Helper */ - template - class CRefCallAddCopy - { - C& obj; - public: - CRefCallAddCopy(C& obj) : obj(obj) {} - template - void operator()(const A& a) { obj.addCopy(a); } - }; +/** + * A factor graph is a bipartite graph with factor nodes connected to variable + * nodes. In this class, however, only factor nodes are kept around. + * \nosubgrouping + */ +template +class FactorGraph { + public: + typedef FACTOR FactorType; ///< factor type + typedef boost::shared_ptr + sharedFactor; ///< Shared pointer to a factor + typedef sharedFactor value_type; + typedef typename FastVector::iterator iterator; + typedef typename FastVector::const_iterator const_iterator; + + private: + typedef FactorGraph This; ///< Typedef for this class + typedef boost::shared_ptr + shared_ptr; ///< Shared pointer for this class + + /// Check if a DERIVEDFACTOR is in fact derived from FactorType. + template + using IsDerived = typename std::enable_if< + std::is_base_of::value>::type; + + /// Check if T has a value_type derived from FactorType. + template + using HasDerivedValueType = typename std::enable_if< + std::is_base_of::value>::type; + + /// Check if T has a value_type derived from FactorType. + template + using HasDerivedElementType = typename std::enable_if::value>::type; + + protected: + /** concept check, makes sure FACTOR defines print and equals */ + GTSAM_CONCEPT_TESTABLE_TYPE(FACTOR) + + /** Collection of factors */ + FastVector factors_; + + /// @name Standard Constructors + /// @{ + + /** Default constructor */ + FactorGraph() {} + + /** Constructor from iterator over factors (shared_ptr or plain objects) */ + template + FactorGraph(ITERATOR firstFactor, ITERATOR lastFactor) { + push_back(firstFactor, lastFactor); + } + + /** Construct from container of factors (shared_ptr or plain objects) */ + template + explicit FactorGraph(const CONTAINER& factors) { + push_back(factors); + } + + /// @} + + public: + /// @name Adding Single Factors + /// @{ /** - * A factor graph is a bipartite graph with factor nodes connected to variable nodes. - * In this class, however, only factor nodes are kept around. - * \nosubgrouping + * Reserve space for the specified number of factors if you know in + * advance how many there will be (works like FastVector::reserve). */ - template - class FactorGraph { - - public: - typedef FACTOR FactorType; ///< factor type - typedef boost::shared_ptr sharedFactor; ///< Shared pointer to a factor - typedef sharedFactor value_type; - typedef typename FastVector::iterator iterator; - typedef typename FastVector::const_iterator const_iterator; - - private: - typedef FactorGraph This; ///< Typedef for this class - typedef boost::shared_ptr shared_ptr; ///< Shared pointer for this class - - protected: - /** concept check, makes sure FACTOR defines print and equals */ - GTSAM_CONCEPT_TESTABLE_TYPE(FACTOR) - - /** Collection of factors */ - FastVector factors_; - - /// @name Standard Constructors - /// @{ - - /** Default constructor */ - FactorGraph() {} - - /** Constructor from iterator over factors (shared_ptr or plain objects) */ - template - FactorGraph(ITERATOR firstFactor, ITERATOR lastFactor) { push_back(firstFactor, lastFactor); } - - /** Construct from container of factors (shared_ptr or plain objects) */ - template - explicit FactorGraph(const CONTAINER& factors) { push_back(factors); } - - /// @} - /// @name Advanced Constructors - /// @{ - - // TODO: are these needed? - - ///** - // * @brief Constructor from a Bayes net - // * @param bayesNet the Bayes net to convert, type CONDITIONAL must yield compatible factor - // * @return a factor graph with all the conditionals, as factors - // */ - //template - //FactorGraph(const BayesNet& bayesNet); - - ///** convert from Bayes tree */ - //template - //FactorGraph(const BayesTree& bayesTree); - - ///** convert from a derived type */ - //template - //FactorGraph(const FactorGraph& factors) { - // factors_.assign(factors.begin(), factors.end()); - //} - - /// @} - - public: - /// @name Adding Factors - /// @{ - - /** - * Reserve space for the specified number of factors if you know in - * advance how many there will be (works like FastVector::reserve). - */ - void reserve(size_t size) { factors_.reserve(size); } - - // TODO: are these needed? - - /** Add a factor directly using a shared_ptr */ - template - typename std::enable_if::value>::type - push_back(boost::shared_ptr factor) { - factors_.push_back(boost::shared_ptr(factor)); } - - /** Add a factor directly using a shared_ptr */ - void push_back(const sharedFactor& factor) { - factors_.push_back(factor); } - - /** Emplace a factor */ - template - typename std::enable_if::value>::type - emplace_shared(Args&&... args) { - factors_.push_back(boost::allocate_shared(Eigen::aligned_allocator(), std::forward(args)...)); - } - - /** push back many factors with an iterator over shared_ptr (factors are not copied) */ - template - typename std::enable_if::value>::type - push_back(ITERATOR firstFactor, ITERATOR lastFactor) { - factors_.insert(end(), firstFactor, lastFactor); } - - /** push back many factors as shared_ptr's in a container (factors are not copied) */ - template - typename std::enable_if::value>::type - push_back(const CONTAINER& container) { - push_back(container.begin(), container.end()); - } - - /** push back a BayesTree as a collection of factors. NOTE: This should be hidden in derived - * classes in favor of a type-specialized version that calls this templated function. */ - template - typename std::enable_if::value>::type - push_back(const BayesTree& bayesTree) { - bayesTree.addFactorsToGraph(*this); - } - -//#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 - /** Add a factor by value, will be copy-constructed (use push_back with a shared_ptr to avoid - * the copy). */ - template - typename std::enable_if::value>::type - push_back(const DERIVEDFACTOR& factor) { - factors_.push_back(boost::allocate_shared(Eigen::aligned_allocator(), factor)); - } -//#endif - - /** push back many factors with an iterator over plain factors (factors are copied) */ - template - typename std::enable_if::value>::type - push_back(ITERATOR firstFactor, ITERATOR lastFactor) { - for (ITERATOR f = firstFactor; f != lastFactor; ++f) - push_back(*f); - } - - /** push back many factors as non-pointer objects in a container (factors are copied) */ - template - typename std::enable_if::value>::type - push_back(const CONTAINER& container) { - push_back(container.begin(), container.end()); - } - - /** Add a factor directly using a shared_ptr */ - template - typename std::enable_if::value, - boost::assign::list_inserter > >::type - operator+=(boost::shared_ptr factor) { - return boost::assign::make_list_inserter(RefCallPushBack(*this))(factor); - } - - /** Add a factor directly using a shared_ptr */ - boost::assign::list_inserter > - operator+=(const sharedFactor& factor) { - return boost::assign::make_list_inserter(CRefCallPushBack(*this))(factor); - } - - /** Add a factor or container of factors, including STL collections, BayesTrees, etc. */ - template - boost::assign::list_inserter > - operator+=(const FACTOR_OR_CONTAINER& factorOrContainer) { - return boost::assign::make_list_inserter(CRefCallPushBack(*this))(factorOrContainer); - } - - /** Add a factor directly using a shared_ptr */ - template - typename std::enable_if::value>::type - add(boost::shared_ptr factor) { - push_back(factor); - } - - /** Add a factor directly using a shared_ptr */ - void add(const sharedFactor& factor) { - push_back(factor); - } - - /** Add a factor or container of factors, including STL collections, BayesTrees, etc. */ - template - void add(const FACTOR_OR_CONTAINER& factorOrContainer) { - push_back(factorOrContainer); - } - - /// @} - /// @name Testable - /// @{ - - /** print out graph */ - void print(const std::string& s = "FactorGraph", - const KeyFormatter& formatter = DefaultKeyFormatter) const; - - /** Check equality */ - bool equals(const This& fg, double tol = 1e-9) const; - /// @} - - public: - /// @name Standard Interface - /// @{ - - /** return the number of factors (including any null factors set by remove() ). */ - size_t size() const { return factors_.size(); } - - /** Check if the graph is empty (null factors set by remove() will cause this to return false). */ - bool empty() const { return factors_.empty(); } - - /** Get a specific factor by index (this checks array bounds and may throw an exception, as - * opposed to operator[] which does not). - */ - const sharedFactor at(size_t i) const { return factors_.at(i); } - - /** Get a specific factor by index (this checks array bounds and may throw an exception, as - * opposed to operator[] which does not). - */ - sharedFactor& at(size_t i) { return factors_.at(i); } - - /** Get a specific factor by index (this does not check array bounds, as opposed to at() which - * does). - */ - const sharedFactor operator[](size_t i) const { return at(i); } - - /** Get a specific factor by index (this does not check array bounds, as opposed to at() which - * does). - */ - sharedFactor& operator[](size_t i) { return at(i); } - - /** Iterator to beginning of factors. */ - const_iterator begin() const { return factors_.begin();} - - /** Iterator to end of factors. */ - const_iterator end() const { return factors_.end(); } - - /** Get the first factor */ - sharedFactor front() const { return factors_.front(); } - - /** Get the last factor */ - sharedFactor back() const { return factors_.back(); } + void reserve(size_t size) { factors_.reserve(size); } + + /// Add a factor directly using a shared_ptr. + template + IsDerived push_back(boost::shared_ptr factor) { + factors_.push_back(boost::shared_ptr(factor)); + } + + /// Emplace a shared pointer to factor of given type. + template + IsDerived emplace_shared(Args&&... args) { + factors_.push_back(boost::allocate_shared( + Eigen::aligned_allocator(), + std::forward(args)...)); + } - /// @} - /// @name Modifying Factor Graphs (imperative, discouraged) - /// @{ + /** + * Add a factor by value, will be copy-constructed (use push_back with a + * shared_ptr to avoid the copy). + */ + template + IsDerived push_back(const DERIVEDFACTOR& factor) { + factors_.push_back(boost::allocate_shared( + Eigen::aligned_allocator(), factor)); + } + + /// `add` is a synonym for push_back. + template + IsDerived add(boost::shared_ptr factor) { + push_back(factor); + } + + /// `+=` works well with boost::assign list inserter. + template + typename std::enable_if< + std::is_base_of::value, + boost::assign::list_inserter>>::type + operator+=(boost::shared_ptr factor) { + return boost::assign::make_list_inserter(RefCallPushBack(*this))( + factor); + } + + /// @} + /// @name Adding via iterators + /// @{ - /** non-const STL-style begin() */ - iterator begin() { return factors_.begin();} + /** + * Push back many factors with an iterator over shared_ptr (factors are not + * copied) + */ + template + HasDerivedElementType push_back(ITERATOR firstFactor, + ITERATOR lastFactor) { + factors_.insert(end(), firstFactor, lastFactor); + } + + /// Push back many factors with an iterator (factors are copied) + template + HasDerivedValueType push_back(ITERATOR firstFactor, + ITERATOR lastFactor) { + for (ITERATOR f = firstFactor; f != lastFactor; ++f) push_back(*f); + } + + /// @} + /// @name Adding via container + /// @{ - /** non-const STL-style end() */ - iterator end() { return factors_.end(); } + /** + * Push back many factors as shared_ptr's in a container (factors are not + * copied) + */ + template + HasDerivedElementType push_back(const CONTAINER& container) { + push_back(container.begin(), container.end()); + } - /** Directly resize the number of factors in the graph. If the new size is less than the - * original, factors at the end will be removed. If the new size is larger than the original, - * null factors will be appended. - */ - void resize(size_t size) { factors_.resize(size); } + /// Push back non-pointer objects in a container (factors are copied). + template + HasDerivedValueType push_back(const CONTAINER& container) { + push_back(container.begin(), container.end()); + } - /** delete factor without re-arranging indexes by inserting a NULL pointer */ - void remove(size_t i) { factors_[i].reset();} + /** + * Add a factor or container of factors, including STL collections, + * BayesTrees, etc. + */ + template + void add(const FACTOR_OR_CONTAINER& factorOrContainer) { + push_back(factorOrContainer); + } - /** replace a factor by index */ - void replace(size_t index, sharedFactor factor) { at(index) = factor; } + /** + * Add a factor or container of factors, including STL collections, + * BayesTrees, etc. + */ + template + boost::assign::list_inserter> operator+=( + const FACTOR_OR_CONTAINER& factorOrContainer) { + return boost::assign::make_list_inserter(CRefCallPushBack(*this))( + factorOrContainer); + } - /** Erase factor and rearrange other factors to take up the empty space */ - iterator erase(iterator item) { return factors_.erase(item); } + /// @} + /// @name Specialized versions + /// @{ - /** Erase factors and rearrange other factors to take up the empty space */ - iterator erase(iterator first, iterator last) { return factors_.erase(first, last); } + /** + * Push back a BayesTree as a collection of factors. + * NOTE: This should be hidden in derived classes in favor of a + * type-specialized version that calls this templated function. + */ + template + typename std::enable_if< + std::is_base_of::value>::type + push_back(const BayesTree& bayesTree) { + bayesTree.addFactorsToGraph(*this); + } + + /// @} + /// @name Testable + /// @{ + + /** print out graph */ + void print(const std::string& s = "FactorGraph", + const KeyFormatter& formatter = DefaultKeyFormatter) const; + + /** Check equality */ + bool equals(const This& fg, double tol = 1e-9) const; + /// @} + + public: + /// @name Standard Interface + /// @{ + + /** return the number of factors (including any null factors set by remove() + * ). */ + size_t size() const { return factors_.size(); } + + /** Check if the graph is empty (null factors set by remove() will cause + * this to return false). */ + bool empty() const { return factors_.empty(); } + + /** Get a specific factor by index (this checks array bounds and may throw + * an exception, as opposed to operator[] which does not). + */ + const sharedFactor at(size_t i) const { return factors_.at(i); } + + /** Get a specific factor by index (this checks array bounds and may throw + * an exception, as opposed to operator[] which does not). + */ + sharedFactor& at(size_t i) { return factors_.at(i); } + + /** Get a specific factor by index (this does not check array bounds, as + * opposed to at() which does). + */ + const sharedFactor operator[](size_t i) const { return at(i); } + + /** Get a specific factor by index (this does not check array bounds, as + * opposed to at() which does). + */ + sharedFactor& operator[](size_t i) { return at(i); } + + /** Iterator to beginning of factors. */ + const_iterator begin() const { return factors_.begin(); } - /// @} - /// @name Advanced Interface - /// @{ + /** Iterator to end of factors. */ + const_iterator end() const { return factors_.end(); } - /** return the number of non-null factors */ - size_t nrFactors() const; + /** Get the first factor */ + sharedFactor front() const { return factors_.front(); } - /** Potentially slow function to return all keys involved, sorted, as a set */ - KeySet keys() const; + /** Get the last factor */ + sharedFactor back() const { return factors_.back(); } - /** Potentially slow function to return all keys involved, sorted, as a vector */ - KeyVector keyVector() const; + /// @} + /// @name Modifying Factor Graphs (imperative, discouraged) + /// @{ - /** MATLAB interface utility: Checks whether a factor index idx exists in the graph and is a live pointer */ - inline bool exists(size_t idx) const { return idx < size() && at(idx); } + /** non-const STL-style begin() */ + iterator begin() { return factors_.begin(); } - private: + /** non-const STL-style end() */ + iterator end() { return factors_.end(); } - /** Serialization function */ - friend class boost::serialization::access; - template - void serialize(ARCHIVE & ar, const unsigned int /*version*/) { - ar & BOOST_SERIALIZATION_NVP(factors_); - } + /** Directly resize the number of factors in the graph. If the new size is + * less than the original, factors at the end will be removed. If the new + * size is larger than the original, null factors will be appended. + */ + void resize(size_t size) { factors_.resize(size); } + + /** delete factor without re-arranging indexes by inserting a NULL pointer + */ + void remove(size_t i) { factors_[i].reset(); } + + /** replace a factor by index */ + void replace(size_t index, sharedFactor factor) { at(index) = factor; } + + /** Erase factor and rearrange other factors to take up the empty space */ + iterator erase(iterator item) { return factors_.erase(item); } - /// @} + /** Erase factors and rearrange other factors to take up the empty space */ + iterator erase(iterator first, iterator last) { + return factors_.erase(first, last); + } - }; // FactorGraph + /// @} + /// @name Advanced Interface + /// @{ -} // namespace gtsam + /** return the number of non-null factors */ + size_t nrFactors() const; + + /** Potentially slow function to return all keys involved, sorted, as a set + */ + KeySet keys() const; + + /** Potentially slow function to return all keys involved, sorted, as a + * vector + */ + KeyVector keyVector() const; + + /** MATLAB interface utility: Checks whether a factor index idx exists in + * the graph and is a live pointer */ + inline bool exists(size_t idx) const { return idx < size() && at(idx); } + + private: + /** Serialization function */ + friend class boost::serialization::access; + template + void serialize(ARCHIVE& ar, const unsigned int /*version*/) { + ar& BOOST_SERIALIZATION_NVP(factors_); + } + + /// @} +}; // FactorGraph +} // namespace gtsam #include From 495a840921e3dc5f1b7d6dfb14a98ee512147296 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 16:46:52 -0400 Subject: [PATCH 019/160] New add_factors method --- gtsam/inference/FactorGraph-inst.h | 153 ++++++---- gtsam/inference/FactorGraph.h | 8 + .../tests/testSymbolicFactorGraph.cpp | 279 +++++++++--------- 3 files changed, 240 insertions(+), 200 deletions(-) diff --git a/gtsam/inference/FactorGraph-inst.h b/gtsam/inference/FactorGraph-inst.h index 0aa4af2e16..34ca8ab7fd 100644 --- a/gtsam/inference/FactorGraph-inst.h +++ b/gtsam/inference/FactorGraph-inst.h @@ -26,74 +26,105 @@ #include #include +#include +#include // for cout :-( #include -#include // for cout :-( +#include namespace gtsam { - /* ************************************************************************* */ - template - void FactorGraph::print(const std::string& s, const KeyFormatter& formatter) const { - std::cout << s << std::endl; - std::cout << "size: " << size() << std::endl; - for (size_t i = 0; i < factors_.size(); i++) { - std::stringstream ss; - ss << "factor " << i << ": "; - if (factors_[i]) - factors_[i]->print(ss.str(), formatter); - } +/* ************************************************************************* */ +template +void FactorGraph::print(const std::string& s, + const KeyFormatter& formatter) const { + std::cout << s << std::endl; + std::cout << "size: " << size() << std::endl; + for (size_t i = 0; i < factors_.size(); i++) { + std::stringstream ss; + ss << "factor " << i << ": "; + if (factors_[i]) factors_[i]->print(ss.str(), formatter); } - - /* ************************************************************************* */ - template - bool FactorGraph::equals(const This& fg, double tol) const { - /** check whether the two factor graphs have the same number of factors_ */ - if (factors_.size() != fg.size()) return false; - - /** check whether the factors_ are the same */ - for (size_t i = 0; i < factors_.size(); i++) { - // TODO: Doesn't this force order of factor insertion? - sharedFactor f1 = factors_[i], f2 = fg.factors_[i]; - if (f1 == NULL && f2 == NULL) continue; - if (f1 == NULL || f2 == NULL) return false; - if (!f1->equals(*f2, tol)) return false; - } - return true; +} + +/* ************************************************************************* */ +template +bool FactorGraph::equals(const This& fg, double tol) const { + // check whether the two factor graphs have the same number of factors. + if (factors_.size() != fg.size()) return false; + + // check whether the factors are the same, in same order. + for (size_t i = 0; i < factors_.size(); i++) { + sharedFactor f1 = factors_[i], f2 = fg.factors_[i]; + if (f1 == NULL && f2 == NULL) continue; + if (f1 == NULL || f2 == NULL) return false; + if (!f1->equals(*f2, tol)) return false; } - - /* ************************************************************************* */ - template - size_t FactorGraph::nrFactors() const { - size_t size_ = 0; - for(const sharedFactor& factor: factors_) - if (factor) size_++; - return size_; + return true; +} + +/* ************************************************************************* */ +template +size_t FactorGraph::nrFactors() const { + size_t size_ = 0; + for (const sharedFactor& factor : factors_) + if (factor) size_++; + return size_; +} + +/* ************************************************************************* */ +template +KeySet FactorGraph::keys() const { + KeySet keys; + for (const sharedFactor& factor : this->factors_) { + if (factor) keys.insert(factor->begin(), factor->end()); } - - /* ************************************************************************* */ - template - KeySet FactorGraph::keys() const { - KeySet keys; - for(const sharedFactor& factor: this->factors_) { - if(factor) - keys.insert(factor->begin(), factor->end()); + return keys; +} + +/* ************************************************************************* */ +template +KeyVector FactorGraph::keyVector() const { + KeyVector keys; + keys.reserve(2 * size()); // guess at size + for (const sharedFactor& factor : factors_) + if (factor) keys.insert(keys.end(), factor->begin(), factor->end()); + std::sort(keys.begin(), keys.end()); + auto last = std::unique(keys.begin(), keys.end()); + keys.erase(last, keys.end()); + return keys; +} + +/* ************************************************************************* */ +template +template +FactorIndices FactorGraph::add_factors(const CONTAINER& factors, + bool useEmptySlots) { + const size_t num_factors = factors.size(); + FactorIndices newFactorIndices(num_factors); + if (useEmptySlots) { + size_t i = 0; + for (size_t j = 0; j < num_factors; ++j) { + // Loop to find the next available factor slot + do { + if (i >= size()) + // Make room for remaining factors, happens only once. + resize(size() + num_factors - j); + else if (at(i)) + ++i; // Move on to the next slot or past end. + else + break; // We found an empty slot, break to fill it. + } while (true); + + // Use the current slot, updating graph and newFactorSlots. + at(i) = factors[j]; + newFactorIndices[j] = i; } - return keys; - } - - /* ************************************************************************* */ - template - KeyVector FactorGraph::keyVector() const { - KeyVector keys; - keys.reserve(2 * size()); // guess at size - for (const sharedFactor& factor: factors_) - if (factor) - keys.insert(keys.end(), factor->begin(), factor->end()); - std::sort(keys.begin(), keys.end()); - auto last = std::unique(keys.begin(), keys.end()); - keys.erase(last, keys.end()); - return keys; + } else { + // We're not looking for unused slots, so just add the factors at the end. + for (size_t i = 0; i < num_factors; ++i) newFactorIndices[i] = i + size(); + push_back(factors); } + return newFactorIndices; +} - /* ************************************************************************* */ -} // namespace gtsam +} // namespace gtsam diff --git a/gtsam/inference/FactorGraph.h b/gtsam/inference/FactorGraph.h index ed14f18634..0959989f9d 100644 --- a/gtsam/inference/FactorGraph.h +++ b/gtsam/inference/FactorGraph.h @@ -273,6 +273,14 @@ class FactorGraph { bayesTree.addFactorsToGraph(*this); } + /** + * Add new factors to a factor graph and returns a list of new factor indices, + * optionally finding and reusing empty factor slots. + */ + template > + FactorIndices add_factors(const CONTAINER& factors, + bool useEmptySlots = false); + /// @} /// @name Testable /// @{ diff --git a/gtsam/symbolic/tests/testSymbolicFactorGraph.cpp b/gtsam/symbolic/tests/testSymbolicFactorGraph.cpp index 3fd318456d..8f4eb3c08d 100644 --- a/gtsam/symbolic/tests/testSymbolicFactorGraph.cpp +++ b/gtsam/symbolic/tests/testSymbolicFactorGraph.cpp @@ -15,14 +15,16 @@ * @author Christian Potthast **/ -#include - #include + +#include #include #include #include #include +#include + #include using namespace std; @@ -46,11 +48,10 @@ TEST(SymbolicFactorGraph, keys2) { } /* ************************************************************************* */ -TEST(SymbolicFactorGraph, eliminateFullSequential) -{ +TEST(SymbolicFactorGraph, eliminateFullSequential) { // Test with simpleTestGraph1 Ordering order; - order += 0,1,2,3,4; + order += 0, 1, 2, 3, 4; SymbolicBayesNet actual1 = *simpleTestGraph1.eliminateSequential(order); EXPECT(assert_equal(simpleTestGraph1BayesNet, actual1)); @@ -60,24 +61,20 @@ TEST(SymbolicFactorGraph, eliminateFullSequential) } /* ************************************************************************* */ -TEST(SymbolicFactorGraph, eliminatePartialSequential) -{ +TEST(SymbolicFactorGraph, eliminatePartialSequential) { // Eliminate 0 and 1 const Ordering order = list_of(0)(1); - const SymbolicBayesNet expectedBayesNet = list_of - (SymbolicConditional(0,1,2)) - (SymbolicConditional(1,2,3,4)); + const SymbolicBayesNet expectedBayesNet = + list_of(SymbolicConditional(0, 1, 2))(SymbolicConditional(1, 2, 3, 4)); - const SymbolicFactorGraph expectedSfg = list_of - (SymbolicFactor(2,3)) - (SymbolicFactor(4,5)) - (SymbolicFactor(2,3,4)); + const SymbolicFactorGraph expectedSfg = list_of(SymbolicFactor(2, 3))( + SymbolicFactor(4, 5))(SymbolicFactor(2, 3, 4)); SymbolicBayesNet::shared_ptr actualBayesNet; SymbolicFactorGraph::shared_ptr actualSfg; boost::tie(actualBayesNet, actualSfg) = - simpleTestGraph2.eliminatePartialSequential(Ordering(list_of(0)(1))); + simpleTestGraph2.eliminatePartialSequential(Ordering(list_of(0)(1))); EXPECT(assert_equal(expectedSfg, *actualSfg)); EXPECT(assert_equal(expectedBayesNet, *actualBayesNet)); @@ -85,75 +82,71 @@ TEST(SymbolicFactorGraph, eliminatePartialSequential) SymbolicBayesNet::shared_ptr actualBayesNet2; SymbolicFactorGraph::shared_ptr actualSfg2; boost::tie(actualBayesNet2, actualSfg2) = - simpleTestGraph2.eliminatePartialSequential(list_of(0)(1).convert_to_container()); + simpleTestGraph2.eliminatePartialSequential( + list_of(0)(1).convert_to_container()); EXPECT(assert_equal(expectedSfg, *actualSfg2)); EXPECT(assert_equal(expectedBayesNet, *actualBayesNet2)); } /* ************************************************************************* */ -TEST(SymbolicFactorGraph, eliminateFullMultifrontal) -{ - Ordering ordering; ordering += 0,1,2,3; - SymbolicBayesTree actual1 = - *simpleChain.eliminateMultifrontal(ordering); +TEST(SymbolicFactorGraph, eliminateFullMultifrontal) { + Ordering ordering; + ordering += 0, 1, 2, 3; + SymbolicBayesTree actual1 = *simpleChain.eliminateMultifrontal(ordering); EXPECT(assert_equal(simpleChainBayesTree, actual1)); - SymbolicBayesTree actual2 = - *asiaGraph.eliminateMultifrontal(asiaOrdering); + SymbolicBayesTree actual2 = *asiaGraph.eliminateMultifrontal(asiaOrdering); EXPECT(assert_equal(asiaBayesTree, actual2)); } /* ************************************************************************* */ -TEST(SymbolicFactorGraph, eliminatePartialMultifrontal) -{ +TEST(SymbolicFactorGraph, eliminatePartialMultifrontal) { SymbolicBayesTree expectedBayesTree; - SymbolicConditional::shared_ptr root = boost::make_shared( - SymbolicConditional::FromKeys(list_of(4)(5)(1), 2)); - expectedBayesTree.insertRoot(boost::make_shared(root)); + SymbolicConditional::shared_ptr root = + boost::make_shared( + SymbolicConditional::FromKeys(list_of(4)(5)(1), 2)); + expectedBayesTree.insertRoot( + boost::make_shared(root)); - SymbolicFactorGraph expectedFactorGraph = list_of - (SymbolicFactor(0,1)) - (SymbolicFactor(0,2)) - (SymbolicFactor(1,3)) - (SymbolicFactor(2,3)) - (SymbolicFactor(1)); + SymbolicFactorGraph expectedFactorGraph = + list_of(SymbolicFactor(0, 1))(SymbolicFactor(0, 2))(SymbolicFactor(1, 3))( + SymbolicFactor(2, 3))(SymbolicFactor(1)); SymbolicBayesTree::shared_ptr actualBayesTree; SymbolicFactorGraph::shared_ptr actualFactorGraph; boost::tie(actualBayesTree, actualFactorGraph) = - simpleTestGraph2.eliminatePartialMultifrontal(Ordering(list_of(4)(5))); + simpleTestGraph2.eliminatePartialMultifrontal(Ordering(list_of(4)(5))); EXPECT(assert_equal(expectedFactorGraph, *actualFactorGraph)); EXPECT(assert_equal(expectedBayesTree, *actualBayesTree)); SymbolicBayesTree expectedBayesTree2; - SymbolicBayesTreeClique::shared_ptr root2 = boost::make_shared( - boost::make_shared(4,1)); + SymbolicBayesTreeClique::shared_ptr root2 = + boost::make_shared( + boost::make_shared(4, 1)); root2->children.push_back(boost::make_shared( - boost::make_shared(5,4))); + boost::make_shared(5, 4))); expectedBayesTree2.insertRoot(root2); SymbolicBayesTree::shared_ptr actualBayesTree2; SymbolicFactorGraph::shared_ptr actualFactorGraph2; boost::tie(actualBayesTree2, actualFactorGraph2) = - simpleTestGraph2.eliminatePartialMultifrontal(list_of(4)(5).convert_to_container()); + simpleTestGraph2.eliminatePartialMultifrontal( + list_of(4)(5).convert_to_container()); EXPECT(assert_equal(expectedFactorGraph, *actualFactorGraph2)); EXPECT(assert_equal(expectedBayesTree2, *actualBayesTree2)); } /* ************************************************************************* */ -TEST(SymbolicFactorGraph, marginalMultifrontalBayesNet) -{ - SymbolicBayesNet expectedBayesNet = list_of - (SymbolicConditional(0, 1, 2)) - (SymbolicConditional(1, 2, 3)) - (SymbolicConditional(2, 3)) - (SymbolicConditional(3)); +TEST(SymbolicFactorGraph, marginalMultifrontalBayesNet) { + SymbolicBayesNet expectedBayesNet = + list_of(SymbolicConditional(0, 1, 2))(SymbolicConditional(1, 2, 3))( + SymbolicConditional(2, 3))(SymbolicConditional(3)); SymbolicBayesNet actual1 = *simpleTestGraph2.marginalMultifrontalBayesNet( - Ordering(list_of(0)(1)(2)(3))); + Ordering(list_of(0)(1)(2)(3))); EXPECT(assert_equal(expectedBayesNet, actual1)); } @@ -167,104 +160,75 @@ TEST(SymbolicFactorGraph, eliminate_disconnected_graph) { // create expected Chordal bayes Net SymbolicBayesNet expected; - expected.push_back(boost::make_shared(0,1,2)); - expected.push_back(boost::make_shared(1,2)); + expected.push_back(boost::make_shared(0, 1, 2)); + expected.push_back(boost::make_shared(1, 2)); expected.push_back(boost::make_shared(2)); - expected.push_back(boost::make_shared(3,4)); + expected.push_back(boost::make_shared(3, 4)); expected.push_back(boost::make_shared(4)); Ordering order; - order += 0,1,2,3,4; + order += 0, 1, 2, 3, 4; SymbolicBayesNet actual = *fg.eliminateSequential(order); - EXPECT(assert_equal(expected,actual)); + EXPECT(assert_equal(expected, actual)); } /* ************************************************************************* */ -//TEST(SymbolicFactorGraph, marginals) -//{ -// // Create factor graph -// SymbolicFactorGraph fg; -// fg.push_factor(0, 1); -// fg.push_factor(0, 2); -// fg.push_factor(1, 4); -// fg.push_factor(2, 4); -// fg.push_factor(3, 4); -// -// // eliminate -// SymbolicSequentialSolver solver(fg); -// SymbolicBayesNet::shared_ptr actual = solver.eliminate(); -// SymbolicBayesNet expected; -// expected.push_front(boost::make_shared(4)); -// expected.push_front(boost::make_shared(3, 4)); -// expected.push_front(boost::make_shared(2, 4)); -// expected.push_front(boost::make_shared(1, 2, 4)); -// expected.push_front(boost::make_shared(0, 1, 2)); -// EXPECT(assert_equal(expected,*actual)); -// -// { -// // jointBayesNet -// vector js; -// js.push_back(0); -// js.push_back(4); -// js.push_back(3); -// SymbolicBayesNet::shared_ptr actualBN = solver.jointBayesNet(js); -// SymbolicBayesNet expectedBN; -// expectedBN.push_front(boost::make_shared(3)); -// expectedBN.push_front(boost::make_shared(4, 3)); -// expectedBN.push_front(boost::make_shared(0, 4)); -// EXPECT( assert_equal(expectedBN,*actualBN)); -// -// // jointFactorGraph -// SymbolicFactorGraph::shared_ptr actualFG = solver.jointFactorGraph(js); -// SymbolicFactorGraph expectedFG; -// expectedFG.push_factor(0, 4); -// expectedFG.push_factor(4, 3); -// expectedFG.push_factor(3); -// EXPECT( assert_equal(expectedFG,(SymbolicFactorGraph)(*actualFG))); -// } -// -// { -// // jointBayesNet -// vector js; -// js.push_back(0); -// js.push_back(2); -// js.push_back(3); -// SymbolicBayesNet::shared_ptr actualBN = solver.jointBayesNet(js); -// SymbolicBayesNet expectedBN; -// expectedBN.push_front(boost::make_shared(2)); -// expectedBN.push_front(boost::make_shared(3, 2)); -// expectedBN.push_front(boost::make_shared(0, 3, 2)); -// EXPECT( assert_equal(expectedBN,*actualBN)); -// -// // jointFactorGraph -// SymbolicFactorGraph::shared_ptr actualFG = solver.jointFactorGraph(js); -// SymbolicFactorGraph expectedFG; -// expectedFG.push_factor(0, 3, 2); -// expectedFG.push_factor(3, 2); -// expectedFG.push_factor(2); -// EXPECT( assert_equal(expectedFG,(SymbolicFactorGraph)(*actualFG))); -// } -// -// { -// // conditionalBayesNet -// vector js; -// js.push_back(0); -// js.push_back(2); -// js.push_back(3); -// size_t nrFrontals = 2; -// SymbolicBayesNet::shared_ptr actualBN = // -// solver.conditionalBayesNet(js, nrFrontals); -// SymbolicBayesNet expectedBN; -// expectedBN.push_front(boost::make_shared(2, 3)); -// expectedBN.push_front(boost::make_shared(0, 2, 3)); -// EXPECT( assert_equal(expectedBN,*actualBN)); -// } -//} +TEST(SymbolicFactorGraph, marginals) { + // Create factor graph + SymbolicFactorGraph fg; + fg.push_factor(0, 1); + fg.push_factor(0, 2); + fg.push_factor(1, 4); + fg.push_factor(2, 4); + fg.push_factor(3, 4); + + // eliminate + Ordering ord(list_of(3)(4)(2)(1)(0)); + auto actual = fg.eliminateSequential(ord); + SymbolicBayesNet expected; + expected.emplace_shared(3, 4); + expected.emplace_shared(4, 1, 2); + expected.emplace_shared(2, 0, 1); + expected.emplace_shared(1, 0); + expected.emplace_shared(0); + EXPECT(assert_equal(expected, *actual)); + + { + // jointBayesNet + Ordering ord(list_of(0)(4)(3)); + auto actual = fg.eliminatePartialSequential(ord); + SymbolicBayesNet expectedBN; + expectedBN.emplace_shared(0, 1, 2); + expectedBN.emplace_shared(4, 1, 2, 3); + expectedBN.emplace_shared(3, 1, 2); + EXPECT(assert_equal(expectedBN, *(actual.first))); + } + + { + // jointBayesNet + Ordering ord(list_of(0)(2)(3)); + auto actual = fg.eliminatePartialSequential(ord); + SymbolicBayesNet expectedBN; + expectedBN.emplace_shared(0, 1, 2); + expectedBN.emplace_shared(2, 1, 4); + expectedBN.emplace_shared(3, 4); + EXPECT(assert_equal(expectedBN, *(actual.first))); + } + + { + // conditionalBayesNet + Ordering ord(list_of(0)(2)); + auto actual = fg.eliminatePartialSequential(ord); + SymbolicBayesNet expectedBN; + expectedBN.emplace_shared(0, 1, 2); + expectedBN.emplace_shared(2, 1, 4); + EXPECT(assert_equal(expectedBN, *(actual.first))); + } +} /* ************************************************************************* */ -TEST( SymbolicFactorGraph, constructFromBayesNet ) -{ +TEST(SymbolicFactorGraph, constructFromBayesNet) { // create expected factor graph SymbolicFactorGraph expected; expected.push_factor(0, 1, 2); @@ -284,8 +248,7 @@ TEST( SymbolicFactorGraph, constructFromBayesNet ) } /* ************************************************************************* */ -TEST( SymbolicFactorGraph, constructFromBayesTree ) -{ +TEST(SymbolicFactorGraph, constructFromBayesTree) { // create expected factor graph SymbolicFactorGraph expected; expected.push_factor(_E_, _L_, _B_); @@ -300,8 +263,7 @@ TEST( SymbolicFactorGraph, constructFromBayesTree ) } /* ************************************************************************* */ -TEST( SymbolicFactorGraph, push_back ) -{ +TEST(SymbolicFactorGraph, push_back) { // Create two factor graphs and expected combined graph SymbolicFactorGraph fg1, fg2, expected; @@ -321,8 +283,47 @@ TEST( SymbolicFactorGraph, push_back ) actual.push_back(fg1); actual.push_back(fg2); CHECK(assert_equal(expected, actual)); + + // combine in second way + SymbolicFactorGraph actual2 = fg1; + actual2.push_back(fg2); + CHECK(assert_equal(expected, actual2)); } /* ************************************************************************* */ -int main() { TestResult tr; return TestRegistry::runAllTests(tr); } +TEST(SymbolicFactorGraph, add_factors) { + SymbolicFactorGraph fg1; + fg1.push_factor(10); + fg1 += SymbolicFactor::shared_ptr(); // empty slot! + fg1.push_factor(11); + + SymbolicFactorGraph fg2; + fg2.push_factor(1); + fg2.push_factor(2); + + SymbolicFactorGraph expected; + expected.push_factor(10); + expected.push_factor(1); + expected.push_factor(11); + expected.push_factor(2); + const FactorIndices expectedIndices = list_of(1)(3); + const FactorIndices actualIndices = fg1.add_factors(fg2, true); + + EXPECT(assert_equal(expected, fg1)); + EXPECT(assert_container_equality(expectedIndices, actualIndices)); + + expected.push_factor(1); + expected.push_factor(2); + const FactorIndices expectedIndices2 = list_of(4)(5); + const FactorIndices actualIndices2 = fg1.add_factors(fg2, false); + + EXPECT(assert_equal(expected, fg1)); + EXPECT(assert_container_equality(expectedIndices2, actualIndices2)); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} /* ************************************************************************* */ From e7f41694c13217e227a7c56903f18439f1df6f0f Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 17:29:47 -0400 Subject: [PATCH 020/160] Removed stale assert --- gtsam/linear/GaussianBayesTree.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/gtsam/linear/GaussianBayesTree.cpp b/gtsam/linear/GaussianBayesTree.cpp index 2365388d6d..8d0fafb618 100644 --- a/gtsam/linear/GaussianBayesTree.cpp +++ b/gtsam/linear/GaussianBayesTree.cpp @@ -35,12 +35,15 @@ namespace gtsam { namespace internal { /* ************************************************************************* */ - double logDeterminant(const GaussianBayesTreeClique::shared_ptr& clique, double& parentSum) - { - parentSum += clique->conditional()->R().diagonal().unaryExpr(std::ptr_fun(log)).sum(); - assert(false); - return 0; - } + double logDeterminant(const GaussianBayesTreeClique::shared_ptr& clique, + double& parentSum) { + parentSum += clique->conditional() + ->R() + .diagonal() + .unaryExpr(std::ptr_fun(log)) + .sum(); + return 0; + } } /* ************************************************************************* */ From bf904f9ff88b754015d7a8f1252738f5a97cbd69 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 31 May 2019 00:40:01 -0400 Subject: [PATCH 021/160] moved gradientAtZero helper to clique class, where it belongs --- gtsam/nonlinear/ISAM2.cpp | 29 ++++------------------------- gtsam/nonlinear/ISAM2Clique.cpp | 28 +++++++++++++++++++++++++--- gtsam/nonlinear/ISAM2Clique.h | 5 ++++- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index c01d1c536d..988e6779f8 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -1113,6 +1113,8 @@ void ISAM2::updateDelta(bool forceFullSolve) const { doglegResult .dx_d; // Copy the VectorValues containing with the linear solution gttoc(Copy_dx_d); + } else { + throw std::runtime_error("iSAM2: unknown ISAM2Params type"); } } @@ -1155,37 +1157,14 @@ double ISAM2::error(const VectorValues& x) const { return GaussianFactorGraph(*this).error(x); } -/* ************************************************************************* */ -static void gradientAtZeroTreeAdder(const boost::shared_ptr& root, - VectorValues* g) { - // Loop through variables in each clique, adding contributions - DenseIndex variablePosition = 0; - for (GaussianConditional::const_iterator jit = root->conditional()->begin(); - jit != root->conditional()->end(); ++jit) { - const DenseIndex dim = root->conditional()->getDim(jit); - pair pos_ins = g->tryInsert( - *jit, root->gradientContribution().segment(variablePosition, dim)); - if (!pos_ins.second) - pos_ins.first->second += - root->gradientContribution().segment(variablePosition, dim); - variablePosition += dim; - } - - // Recursively add contributions from children - typedef boost::shared_ptr sharedClique; - for (const sharedClique& child : root->children) { - gradientAtZeroTreeAdder(child, g); - } -} - /* ************************************************************************* */ VectorValues ISAM2::gradientAtZero() const { // Create result VectorValues g; // Sum up contributions for each clique - for (const ISAM2::sharedClique& root : this->roots()) - gradientAtZeroTreeAdder(root, &g); + for (const auto& root : this->roots()) + root->addGradientAtZero(&g); return g; } diff --git a/gtsam/nonlinear/ISAM2Clique.cpp b/gtsam/nonlinear/ISAM2Clique.cpp index 96212c9238..f7a1c14a03 100644 --- a/gtsam/nonlinear/ISAM2Clique.cpp +++ b/gtsam/nonlinear/ISAM2Clique.cpp @@ -19,7 +19,9 @@ #include #include #include + #include +#include using namespace std; @@ -304,7 +306,7 @@ void ISAM2Clique::findAll(const KeySet& markedMask, KeySet* keys) const { static const bool debug = false; // does the separator contain any of the variables? bool found = false; - for (Key key : conditional()->parents()) { + for (Key key : conditional_->parents()) { if (markedMask.exists(key)) { found = true; break; @@ -312,14 +314,34 @@ void ISAM2Clique::findAll(const KeySet& markedMask, KeySet* keys) const { } if (found) { // then add this clique - keys->insert(conditional()->beginFrontals(), conditional()->endFrontals()); + keys->insert(conditional_->beginFrontals(), conditional_->endFrontals()); if (debug) print("Key(s) marked in clique "); - if (debug) cout << "so marking key " << conditional()->front() << endl; + if (debug) cout << "so marking key " << conditional_->front() << endl; } for (const auto& child : children) { child->findAll(markedMask, keys); } } +/* ************************************************************************* */ +void ISAM2Clique::addGradientAtZero(VectorValues* g) const { + // Loop through variables in each clique, adding contributions + DenseIndex position = 0; + for (auto it = conditional_->begin(); it != conditional_->end(); ++it) { + const DenseIndex dim = conditional_->getDim(it); + const Vector contribution = gradientContribution_.segment(position, dim); + VectorValues::iterator values_it; + bool success; + std::tie(values_it, success) = g->tryInsert(*it, contribution); + if (!success) values_it->second += contribution; + position += dim; + } + + // Recursively add contributions from children + for (const auto& child : children) { + child->addGradientAtZero(g); + } +} + /* ************************************************************************* */ } // namespace gtsam diff --git a/gtsam/nonlinear/ISAM2Clique.h b/gtsam/nonlinear/ISAM2Clique.h index 3c53e3d723..53bdaec819 100644 --- a/gtsam/nonlinear/ISAM2Clique.h +++ b/gtsam/nonlinear/ISAM2Clique.h @@ -75,9 +75,12 @@ class GTSAM_EXPORT ISAM2Clique /** Access the cached factor */ Base::FactorType::shared_ptr& cachedFactor() { return cachedFactor_; } - /** Access the gradient contribution */ + /// Access the gradient contribution const Vector& gradientContribution() const { return gradientContribution_; } + /// Recursively add gradient at zero to g + void addGradientAtZero(VectorValues* g) const; + bool equals(const This& other, double tol = 1e-9) const; /** print this node */ From 0a95ac292f31998da53962e35c33c5da514c57ae Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 31 May 2019 13:42:32 -0400 Subject: [PATCH 022/160] Removed useless typedef, add empty() --- gtsam/inference/EliminationTree-inst.h | 2 +- gtsam/inference/Ordering.cpp | 2 +- gtsam/inference/VariableIndex-inl.h | 4 +-- gtsam/inference/VariableIndex.h | 43 ++++++++++++++------------ gtsam_unstable/discrete/CSP.cpp | 2 +- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/gtsam/inference/EliminationTree-inst.h b/gtsam/inference/EliminationTree-inst.h index 33ba6e18da..3390a3aac8 100644 --- a/gtsam/inference/EliminationTree-inst.h +++ b/gtsam/inference/EliminationTree-inst.h @@ -98,7 +98,7 @@ namespace gtsam { for (size_t j = 0; j < n; j++) { // Retrieve the factors involving this variable and create the current node - const VariableIndex::Factors& factors = structure[order[j]]; + const FactorIndices& factors = structure[order[j]]; const sharedNode node = boost::make_shared(); node->key = order[j]; diff --git a/gtsam/inference/Ordering.cpp b/gtsam/inference/Ordering.cpp index 1165b4a0f7..da61ca57eb 100644 --- a/gtsam/inference/Ordering.cpp +++ b/gtsam/inference/Ordering.cpp @@ -79,7 +79,7 @@ Ordering Ordering::ColamdConstrained(const VariableIndex& variableIndex, size_t index = 0; for (auto key_factors: variableIndex) { // Arrange factor indices into COLAMD format - const VariableIndex::Factors& column = key_factors.second; + const FactorIndices& column = key_factors.second; for(size_t factorIndex: column) { A[count++] = (int) factorIndex; // copy sparse column } diff --git a/gtsam/inference/VariableIndex-inl.h b/gtsam/inference/VariableIndex-inl.h index bc8100e4a3..727ef8fd84 100644 --- a/gtsam/inference/VariableIndex-inl.h +++ b/gtsam/inference/VariableIndex-inl.h @@ -67,8 +67,8 @@ void VariableIndex::remove(ITERATOR firstFactor, ITERATOR lastFactor, "Internal error, requested inconsistent number of factor indices and factors in VariableIndex::remove"); if (factors[i]) { for(Key j: *factors[i]) { - Factors& factorEntries = internalAt(j); - Factors::iterator entry = std::find(factorEntries.begin(), + FactorIndices& factorEntries = internalAt(j); + auto entry = std::find(factorEntries.begin(), factorEntries.end(), *factorIndex); if (entry == factorEntries.end()) throw std::invalid_argument( diff --git a/gtsam/inference/VariableIndex.h b/gtsam/inference/VariableIndex.h index a96a532897..0d73204289 100644 --- a/gtsam/inference/VariableIndex.h +++ b/gtsam/inference/VariableIndex.h @@ -41,26 +41,22 @@ namespace gtsam { * \nosubgrouping */ class GTSAM_EXPORT VariableIndex { -public: - + public: typedef boost::shared_ptr shared_ptr; - typedef FactorIndices Factors; - typedef Factors::iterator Factor_iterator; - typedef Factors::const_iterator Factor_const_iterator; + typedef FactorIndices::iterator Factor_iterator; + typedef FactorIndices::const_iterator Factor_const_iterator; -protected: - typedef FastMap KeyMap; + protected: + typedef FastMap KeyMap; KeyMap index_; - size_t nFactors_; // Number of factors in the original factor graph. - size_t nEntries_; // Sum of involved variable counts of each factor. + size_t nFactors_; // Number of factors in the original factor graph. + size_t nEntries_; // Sum of involved variable counts of each factor. -public: + public: typedef KeyMap::const_iterator const_iterator; typedef KeyMap::const_iterator iterator; typedef KeyMap::value_type value_type; -public: - /// @name Standard Constructors /// @{ @@ -71,8 +67,10 @@ class GTSAM_EXPORT VariableIndex { * Create a VariableIndex that computes and stores the block column structure * of a factor graph. */ - template - VariableIndex(const FG& factorGraph) : nFactors_(0), nEntries_(0) { augment(factorGraph); } + template + explicit VariableIndex(const FG& factorGraph) : nFactors_(0), nEntries_(0) { + augment(factorGraph); + } /// @} /// @name Standard Interface @@ -88,7 +86,7 @@ class GTSAM_EXPORT VariableIndex { size_t nEntries() const { return nEntries_; } /// Access a list of factors by variable - const Factors& operator[](Key variable) const { + const FactorIndices& operator[](Key variable) const { KeyMap::const_iterator item = index_.find(variable); if(item == index_.end()) throw std::invalid_argument("Requested non-existent variable from VariableIndex"); @@ -96,6 +94,11 @@ class GTSAM_EXPORT VariableIndex { return item->second; } + /// Return true if no factors associated with a variable + const bool empty(Key variable) const { + return (*this)[variable].empty(); + } + /// @} /// @name Testable /// @{ @@ -166,16 +169,18 @@ class GTSAM_EXPORT VariableIndex { Factor_const_iterator factorsEnd(Key variable) const { return internalAt(variable).end(); } /// Internal version of 'at' that asserts existence - const Factors& internalAt(Key variable) const { + const FactorIndices& internalAt(Key variable) const { const KeyMap::const_iterator item = index_.find(variable); assert(item != index_.end()); - return item->second; } + return item->second; + } /// Internal version of 'at' that asserts existence - Factors& internalAt(Key variable) { + FactorIndices& internalAt(Key variable) { const KeyMap::iterator item = index_.find(variable); assert(item != index_.end()); - return item->second; } + return item->second; + } /// @} }; diff --git a/gtsam_unstable/discrete/CSP.cpp b/gtsam_unstable/discrete/CSP.cpp index cf5abdcb11..0223250b54 100644 --- a/gtsam_unstable/discrete/CSP.cpp +++ b/gtsam_unstable/discrete/CSP.cpp @@ -43,7 +43,7 @@ namespace gtsam { // keep track of which domains changed changed[v] = false; // loop over all factors/constraints for variable v - const VariableIndex::Factors& factors = index[v]; + const FactorIndices& factors = index[v]; for(size_t f: factors) { // if not already a singleton if (!domains[v].isSingleton()) { From 26f96f70919b8465576b8c8a144580f593240f74 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 31 May 2019 17:31:59 -0400 Subject: [PATCH 023/160] added timing --- gtsam/inference/BayesTree-inst.h | 1 + 1 file changed, 1 insertion(+) diff --git a/gtsam/inference/BayesTree-inst.h b/gtsam/inference/BayesTree-inst.h index 9935776a5e..16a11cc341 100644 --- a/gtsam/inference/BayesTree-inst.h +++ b/gtsam/inference/BayesTree-inst.h @@ -462,6 +462,7 @@ namespace gtsam { template void BayesTree::removeTop(const KeyVector& keys, BayesNetType& bn, Cliques& orphans) { + gttic(removetop); // process each key of the new factor for(const Key& j: keys) { From 5f8a00fa53d5b3bef521d9bcee9e305347b6a597 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 31 May 2019 17:32:16 -0400 Subject: [PATCH 024/160] made Roots type public --- gtsam/inference/BayesTree.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gtsam/inference/BayesTree.h b/gtsam/inference/BayesTree.h index 892ac5f31c..c221a5ed64 100644 --- a/gtsam/inference/BayesTree.h +++ b/gtsam/inference/BayesTree.h @@ -89,14 +89,14 @@ namespace gtsam { /** Map from keys to Clique */ typedef ConcurrentMap Nodes; + /** Root cliques */ + typedef FastVector Roots; + protected: /** Map from indices to Clique */ Nodes nodes_; - /** Root cliques */ - typedef FastVector Roots; - /** Root cliques */ Roots roots_; From 1a29ab5533a18066a7ce12549a40a8bcf8609f52 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 31 May 2019 17:33:16 -0400 Subject: [PATCH 025/160] Refactored iSAM2::update to reveal what is changed when --- gtsam/nonlinear/ISAM2-impl.cpp | 150 +---- gtsam/nonlinear/ISAM2-impl.h | 841 ++++++++++++++++++++++++++-- gtsam/nonlinear/ISAM2.cpp | 754 +++---------------------- gtsam/nonlinear/ISAM2.h | 55 +- gtsam/nonlinear/ISAM2Result.h | 23 +- gtsam/nonlinear/ISAM2UpdateParams.h | 16 +- tests/testGaussianISAM2.cpp | 13 +- 7 files changed, 915 insertions(+), 937 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.cpp b/gtsam/nonlinear/ISAM2-impl.cpp index 11be14c449..a809cbc083 100644 --- a/gtsam/nonlinear/ISAM2-impl.cpp +++ b/gtsam/nonlinear/ISAM2-impl.cpp @@ -31,150 +31,6 @@ using namespace std; namespace gtsam { -/* ************************************************************************* */ -void ISAM2::Impl::AddFactorsStep1(const NonlinearFactorGraph& newFactors, - bool useUnusedSlots, - NonlinearFactorGraph* nonlinearFactors, - FactorIndices* newFactorIndices) { - newFactorIndices->resize(newFactors.size()); - - if (useUnusedSlots) { - size_t globalFactorIndex = 0; - for (size_t newFactorIndex = 0; newFactorIndex < newFactors.size(); - ++newFactorIndex) { - // Loop to find the next available factor slot - do { - // If we need to add more factors than we have room for, resize - // nonlinearFactors, filling the new slots with NULL factors. Otherwise, - // check if the current factor in nonlinearFactors is already used, and - // if so, increase globalFactorIndex. If the current factor in - // nonlinearFactors is unused, break out of the loop and use the current - // slot. - if (globalFactorIndex >= nonlinearFactors->size()) - nonlinearFactors->resize(nonlinearFactors->size() + - newFactors.size() - newFactorIndex); - else if ((*nonlinearFactors)[globalFactorIndex]) - ++globalFactorIndex; - else - break; - } while (true); - - // Use the current slot, updating nonlinearFactors and newFactorSlots. - (*nonlinearFactors)[globalFactorIndex] = newFactors[newFactorIndex]; - (*newFactorIndices)[newFactorIndex] = globalFactorIndex; - } - } else { - // We're not looking for unused slots, so just add the factors at the end. - for (size_t i = 0; i < newFactors.size(); ++i) - (*newFactorIndices)[i] = i + nonlinearFactors->size(); - nonlinearFactors->push_back(newFactors); - } -} - -/* ************************************************************************* */ -KeySet ISAM2::Impl::CheckRelinearizationFull( - const VectorValues& delta, - const ISAM2Params::RelinearizationThreshold& relinearizeThreshold) { - KeySet relinKeys; - - if (const double* threshold = boost::get(&relinearizeThreshold)) { - for (const VectorValues::KeyValuePair& key_delta : delta) { - double maxDelta = key_delta.second.lpNorm(); - if (maxDelta >= *threshold) relinKeys.insert(key_delta.first); - } - } else if (const FastMap* thresholds = - boost::get >(&relinearizeThreshold)) { - for (const VectorValues::KeyValuePair& key_delta : delta) { - const Vector& threshold = - thresholds->find(Symbol(key_delta.first).chr())->second; - if (threshold.rows() != key_delta.second.rows()) - throw std::invalid_argument( - "Relinearization threshold vector dimensionality for '" + - std::string(1, Symbol(key_delta.first).chr()) + - "' passed into iSAM2 parameters does not match actual variable " - "dimensionality."); - if ((key_delta.second.array().abs() > threshold.array()).any()) - relinKeys.insert(key_delta.first); - } - } - - return relinKeys; -} - -/* ************************************************************************* */ -static void CheckRelinearizationRecursiveDouble( - double threshold, const VectorValues& delta, - const ISAM2::sharedClique& clique, KeySet* relinKeys) { - // Check the current clique for relinearization - bool relinearize = false; - for (Key var : *clique->conditional()) { - double maxDelta = delta[var].lpNorm(); - if (maxDelta >= threshold) { - relinKeys->insert(var); - relinearize = true; - } - } - - // If this node was relinearized, also check its children - if (relinearize) { - for (const ISAM2::sharedClique& child : clique->children) { - CheckRelinearizationRecursiveDouble(threshold, delta, child, relinKeys); - } - } -} - -/* ************************************************************************* */ -static void CheckRelinearizationRecursiveMap( - const FastMap& thresholds, const VectorValues& delta, - const ISAM2::sharedClique& clique, KeySet* relinKeys) { - // Check the current clique for relinearization - bool relinearize = false; - for (Key var : *clique->conditional()) { - // Find the threshold for this variable type - const Vector& threshold = thresholds.find(Symbol(var).chr())->second; - - const Vector& deltaVar = delta[var]; - - // Verify the threshold vector matches the actual variable size - if (threshold.rows() != deltaVar.rows()) - throw std::invalid_argument( - "Relinearization threshold vector dimensionality for '" + - std::string(1, Symbol(var).chr()) + - "' passed into iSAM2 parameters does not match actual variable " - "dimensionality."); - - // Check for relinearization - if ((deltaVar.array().abs() > threshold.array()).any()) { - relinKeys->insert(var); - relinearize = true; - } - } - - // If this node was relinearized, also check its children - if (relinearize) { - for (const ISAM2::sharedClique& child : clique->children) { - CheckRelinearizationRecursiveMap(thresholds, delta, child, relinKeys); - } - } -} - -/* ************************************************************************* */ -KeySet ISAM2::Impl::CheckRelinearizationPartial( - const ISAM2::Roots& roots, const VectorValues& delta, - const ISAM2Params::RelinearizationThreshold& relinearizeThreshold) { - KeySet relinKeys; - for (const ISAM2::sharedClique& root : roots) { - if (relinearizeThreshold.type() == typeid(double)) - CheckRelinearizationRecursiveDouble( - boost::get(relinearizeThreshold), delta, root, &relinKeys); - else if (relinearizeThreshold.type() == typeid(FastMap)) - CheckRelinearizationRecursiveMap( - boost::get >(relinearizeThreshold), delta, root, - &relinKeys); - } - return relinKeys; -} - /* ************************************************************************* */ namespace internal { inline static void optimizeInPlace(const ISAM2::sharedClique& clique, @@ -189,7 +45,7 @@ inline static void optimizeInPlace(const ISAM2::sharedClique& clique, } // namespace internal /* ************************************************************************* */ -size_t ISAM2::Impl::UpdateGaussNewtonDelta(const ISAM2::Roots& roots, +size_t DeltaImpl::UpdateGaussNewtonDelta(const ISAM2::Roots& roots, const KeySet& replacedKeys, double wildfireThreshold, VectorValues* delta) { @@ -272,7 +128,7 @@ void updateRgProd(const ISAM2::sharedClique& clique, const KeySet& replacedKeys, } // namespace internal /* ************************************************************************* */ -size_t ISAM2::Impl::UpdateRgProd(const ISAM2::Roots& roots, +size_t DeltaImpl::UpdateRgProd(const ISAM2::Roots& roots, const KeySet& replacedKeys, const VectorValues& gradAtZero, VectorValues* RgProd) { @@ -287,7 +143,7 @@ size_t ISAM2::Impl::UpdateRgProd(const ISAM2::Roots& roots, } /* ************************************************************************* */ -VectorValues ISAM2::Impl::ComputeGradientSearch(const VectorValues& gradAtZero, +VectorValues DeltaImpl::ComputeGradientSearch(const VectorValues& gradAtZero, const VectorValues& RgProd) { // Compute gradient squared-magnitude const double gradientSqNorm = gradAtZero.dot(gradAtZero); diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 8a30fb8cd4..f4dfcb99b8 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -11,18 +11,65 @@ /** * @file ISAM2-impl.h - * @brief Incremental update functionality (ISAM2) for BayesTree, with fluid relinearization. - * @author Michael Kaess, Richard Roberts + * @brief Incremental update functionality (ISAM2) for BayesTree, with fluid + * relinearization. + * @author Michael Kaess, Richard Roberts, Frank Dellaert */ #pragma once -#include #include +#include + +#include +#include // We need the inst file because we'll make a special JT templated on ISAM2 +#include +#include +#include +#include + +#include +#include +namespace br { +using namespace boost::range; +using namespace boost::adaptors; +} // namespace br + +#include +#include +#include +#include namespace gtsam { -struct GTSAM_EXPORT ISAM2::Impl { +/* ************************************************************************* */ +// Special BayesTree class that uses ISAM2 cliques - this is the result of +// reeliminating ISAM2 subtrees. +class ISAM2BayesTree : public ISAM2::Base { + public: + typedef ISAM2::Base Base; + typedef ISAM2BayesTree This; + typedef boost::shared_ptr shared_ptr; + + ISAM2BayesTree() {} +}; + +/* ************************************************************************* */ +// Special JunctionTree class that produces ISAM2 BayesTree cliques, used for +// reeliminating ISAM2 subtrees. +class ISAM2JunctionTree + : public JunctionTree { + public: + typedef JunctionTree Base; + typedef ISAM2JunctionTree This; + typedef boost::shared_ptr shared_ptr; + + explicit ISAM2JunctionTree(const GaussianEliminationTree& eliminationTree) + : Base(eliminationTree) {} +}; + +/* ************************************************************************* */ +struct GTSAM_EXPORT DeltaImpl { struct GTSAM_EXPORT PartialSolveResult { ISAM2::sharedClique bayesTree; }; @@ -34,57 +81,771 @@ struct GTSAM_EXPORT ISAM2::Impl { boost::optional > constrainedKeys; }; - /// Perform the first part of the bookkeeping updates for adding new factors. Adds them to the - /// complete list of nonlinear factors, and populates the list of new factor indices, both - /// optionally finding and reusing empty factor slots. - static void AddFactorsStep1(const NonlinearFactorGraph& newFactors, bool useUnusedSlots, - NonlinearFactorGraph* nonlinearFactors, FactorIndices* newFactorIndices); - - /** - * Find the set of variables to be relinearized according to relinearizeThreshold. - * Any variables in the VectorValues delta whose vector magnitude is greater than - * or equal to relinearizeThreshold are returned. - * @param delta The linear delta to check against the threshold - * @param keyFormatter Formatter for printing nonlinear keys during debugging - * @return The set of variable indices in delta whose magnitude is greater than or - * equal to relinearizeThreshold - */ - static KeySet CheckRelinearizationFull(const VectorValues& delta, - const ISAM2Params::RelinearizationThreshold& relinearizeThreshold); - - /** - * Find the set of variables to be relinearized according to relinearizeThreshold. - * This check is performed recursively, starting at the top of the tree. Once a - * variable in the tree does not need to be relinearized, no further checks in - * that branch are performed. This is an approximation of the Full version, designed - * to save time at the expense of accuracy. - * @param delta The linear delta to check against the threshold - * @param keyFormatter Formatter for printing nonlinear keys during debugging - * @return The set of variable indices in delta whose magnitude is greater than or - * equal to relinearizeThreshold - */ - static KeySet CheckRelinearizationPartial(const ISAM2::Roots& roots, - const VectorValues& delta, const ISAM2Params::RelinearizationThreshold& relinearizeThreshold); - /** * Update the Newton's method step point, using wildfire */ static size_t UpdateGaussNewtonDelta(const ISAM2::Roots& roots, - const KeySet& replacedKeys, double wildfireThreshold, VectorValues* delta); + const KeySet& replacedKeys, + double wildfireThreshold, + VectorValues* delta); /** * Update the RgProd (R*g) incrementally taking into account which variables * have been recalculated in \c replacedKeys. Only used in Dogleg. */ - static size_t UpdateRgProd(const ISAM2::Roots& roots, const KeySet& replacedKeys, - const VectorValues& gradAtZero, VectorValues* RgProd); + static size_t UpdateRgProd(const ISAM2::Roots& roots, + const KeySet& replacedKeys, + const VectorValues& gradAtZero, + VectorValues* RgProd); /** * Compute the gradient-search point. Only used in Dogleg. */ static VectorValues ComputeGradientSearch(const VectorValues& gradAtZero, const VectorValues& RgProd); +}; + +/* ************************************************************************* */ +/** + * Implementation functions for update method + * All of the methods below have clear inputs and outputs, even if not + * functional: iSAM2 is inherintly imperative. + */ +struct GTSAM_EXPORT UpdateImpl { + const ISAM2Params& params_; + const ISAM2UpdateParams& updateParams_; + UpdateImpl(const ISAM2Params& params, const ISAM2UpdateParams& updateParams) + : params_(params), updateParams_(updateParams) {} + + /// Perform the first part of the bookkeeping updates for adding new factors. + /// Adds them to the complete list of nonlinear factors, and populates the + /// list of new factor indices, both optionally finding and reusing empty + /// factor slots. + static void AddFactorsStep1(const NonlinearFactorGraph& newFactors, + bool useUnusedSlots, + NonlinearFactorGraph* nonlinearFactors, + FactorIndices* newFactorIndices) { + newFactorIndices->resize(newFactors.size()); + + if (useUnusedSlots) { + size_t globalFactorIndex = 0; + for (size_t newFactorIndex = 0; newFactorIndex < newFactors.size(); + ++newFactorIndex) { + // Loop to find the next available factor slot + do { + // If we need to add more factors than we have room for, resize + // nonlinearFactors, filling the new slots with NULL factors. + // Otherwise, check if the current factor in nonlinearFactors is + // already used, and if so, increase globalFactorIndex. If the + // current factor in nonlinearFactors is unused, break out of the loop + // and use the current slot. + if (globalFactorIndex >= nonlinearFactors->size()) + nonlinearFactors->resize(nonlinearFactors->size() + + newFactors.size() - newFactorIndex); + else if ((*nonlinearFactors)[globalFactorIndex]) + ++globalFactorIndex; + else + break; + } while (true); + + // Use the current slot, updating nonlinearFactors and newFactorSlots. + (*nonlinearFactors)[globalFactorIndex] = newFactors[newFactorIndex]; + (*newFactorIndices)[newFactorIndex] = globalFactorIndex; + } + } else { + // We're not looking for unused slots, so just add the factors at the end. + for (size_t i = 0; i < newFactors.size(); ++i) + (*newFactorIndices)[i] = i + nonlinearFactors->size(); + nonlinearFactors->push_back(newFactors); + } + } + + // 1. Add any new factors \Factors:=\Factors\cup\Factors'. + void pushBackFactors(const NonlinearFactorGraph& newFactors, + NonlinearFactorGraph* nonlinearFactors, + GaussianFactorGraph* linearFactors, + VariableIndex* variableIndex, + ISAM2Result* result) const { + gttic(pushBackFactors); + const bool debug = ISDEBUG("ISAM2 update"); + const bool verbose = ISDEBUG("ISAM2 update verbose"); + + // Add the new factor indices to the result struct + if (debug || verbose) newFactors.print("The new factors are: "); + AddFactorsStep1(newFactors, params_.findUnusedFactorSlots, nonlinearFactors, + &result->newFactorsIndices); + + // Remove the removed factors + NonlinearFactorGraph removedFactors; + removedFactors.reserve(updateParams_.removeFactorIndices.size()); + for (const auto index : updateParams_.removeFactorIndices) { + removedFactors.push_back(nonlinearFactors->at(index)); + nonlinearFactors->remove(index); + if (params_.cacheLinearizedFactors) linearFactors->remove(index); + } + + // Remove removed factors from the variable index so we do not attempt to + // relinearize them + variableIndex->remove(updateParams_.removeFactorIndices.begin(), + updateParams_.removeFactorIndices.end(), + removedFactors); + result->keysWithRemovedFactors = removedFactors.keys(); + + // Compute unused keys and indices + // Get keys from removed factors and new factors, and compute unused keys, + // i.e., keys that are empty now and do not appear in the new factors. + KeySet removedAndEmpty; + for (Key key : result->keysWithRemovedFactors) { + if (variableIndex->empty(key)) + removedAndEmpty.insert(removedAndEmpty.end(), key); + } + KeySet newFactorSymbKeys = newFactors.keys(); + std::set_difference( + removedAndEmpty.begin(), removedAndEmpty.end(), + newFactorSymbKeys.begin(), newFactorSymbKeys.end(), + std::inserter(result->unusedKeys, result->unusedKeys.end())); + + // Get indices for unused keys + for (Key key : result->unusedKeys) { + result->unusedIndices.insert(result->unusedIndices.end(), key); + } + } + + // 3. Mark linear update + void gatherInvolvedKeys(const NonlinearFactorGraph& newFactors, + const NonlinearFactorGraph& nonlinearFactors, + ISAM2Result* result) const { + gttic(gatherInvolvedKeys); + result->markedKeys = newFactors.keys(); // Get keys from new factors + // Also mark keys involved in removed factors + result->markedKeys.insert(result->keysWithRemovedFactors.begin(), + result->keysWithRemovedFactors.end()); + + // Also mark any provided extra re-eliminate keys + if (updateParams_.extraReelimKeys) { + for (Key key : *updateParams_.extraReelimKeys) { + result->markedKeys.insert(key); + } + } + // Also, keys that were not observed in existing factors, but whose affected + // keys have been extended now (e.g. smart factors) + if (updateParams_.newAffectedKeys) { + for (const auto& factorAddedKeys : *updateParams_.newAffectedKeys) { + const auto factorIdx = factorAddedKeys.first; + const auto& affectedKeys = nonlinearFactors.at(factorIdx)->keys(); + result->markedKeys.insert(affectedKeys.begin(), affectedKeys.end()); + } + } + + // Observed keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : result->markedKeys) { + result->detail->variableStatus[key].isObserved = true; + } + } + + for (Key index : result->markedKeys) { + // Only add if not unused + if (result->unusedIndices.find(index) == result->unusedIndices.end()) + // Make a copy of these, as we'll soon add to them + result->observedKeys.push_back(index); + } + } + + static void CheckRelinearizationRecursiveMap( + const FastMap& thresholds, const VectorValues& delta, + const ISAM2::sharedClique& clique, KeySet* relinKeys) { + // Check the current clique for relinearization + bool relinearize = false; + for (Key var : *clique->conditional()) { + // Find the threshold for this variable type + const Vector& threshold = thresholds.find(Symbol(var).chr())->second; + + const Vector& deltaVar = delta[var]; + + // Verify the threshold vector matches the actual variable size + if (threshold.rows() != deltaVar.rows()) + throw std::invalid_argument( + "Relinearization threshold vector dimensionality for '" + + std::string(1, Symbol(var).chr()) + + "' passed into iSAM2 parameters does not match actual variable " + "dimensionality."); + + // Check for relinearization + if ((deltaVar.array().abs() > threshold.array()).any()) { + relinKeys->insert(var); + relinearize = true; + } + } + + // If this node was relinearized, also check its children + if (relinearize) { + for (const ISAM2::sharedClique& child : clique->children) { + CheckRelinearizationRecursiveMap(thresholds, delta, child, relinKeys); + } + } + } + + static void CheckRelinearizationRecursiveDouble( + double threshold, const VectorValues& delta, + const ISAM2::sharedClique& clique, KeySet* relinKeys) { + // Check the current clique for relinearization + bool relinearize = false; + for (Key var : *clique->conditional()) { + double maxDelta = delta[var].lpNorm(); + if (maxDelta >= threshold) { + relinKeys->insert(var); + relinearize = true; + } + } + + // If this node was relinearized, also check its children + if (relinearize) { + for (const ISAM2::sharedClique& child : clique->children) { + CheckRelinearizationRecursiveDouble(threshold, delta, child, relinKeys); + } + } + } + + /** + * Find the set of variables to be relinearized according to + * relinearizeThreshold. This check is performed recursively, starting at the + * top of the tree. Once a variable in the tree does not need to be + * relinearized, no further checks in that branch are performed. This is an + * approximation of the Full version, designed to save time at the expense of + * accuracy. + * @param delta The linear delta to check against the threshold + * @param keyFormatter Formatter for printing nonlinear keys during debugging + * @return The set of variable indices in delta whose magnitude is greater + * than or equal to relinearizeThreshold + */ + static KeySet CheckRelinearizationPartial( + const ISAM2::Roots& roots, const VectorValues& delta, + const ISAM2Params::RelinearizationThreshold& relinearizeThreshold) { + KeySet relinKeys; + for (const ISAM2::sharedClique& root : roots) { + if (relinearizeThreshold.type() == typeid(double)) + CheckRelinearizationRecursiveDouble( + boost::get(relinearizeThreshold), delta, root, &relinKeys); + else if (relinearizeThreshold.type() == typeid(FastMap)) + CheckRelinearizationRecursiveMap( + boost::get >(relinearizeThreshold), delta, + root, &relinKeys); + } + return relinKeys; + } + + /** + * Find the set of variables to be relinearized according to + * relinearizeThreshold. Any variables in the VectorValues delta whose vector + * magnitude is greater than or equal to relinearizeThreshold are returned. + * @param delta The linear delta to check against the threshold + * @param keyFormatter Formatter for printing nonlinear keys during debugging + * @return The set of variable indices in delta whose magnitude is greater + * than or equal to relinearizeThreshold + */ + static KeySet CheckRelinearizationFull( + const VectorValues& delta, + const ISAM2Params::RelinearizationThreshold& relinearizeThreshold) { + KeySet relinKeys; + + if (const double* threshold = boost::get(&relinearizeThreshold)) { + for (const VectorValues::KeyValuePair& key_delta : delta) { + double maxDelta = key_delta.second.lpNorm(); + if (maxDelta >= *threshold) relinKeys.insert(key_delta.first); + } + } else if (const FastMap* thresholds = + boost::get >(&relinearizeThreshold)) { + for (const VectorValues::KeyValuePair& key_delta : delta) { + const Vector& threshold = + thresholds->find(Symbol(key_delta.first).chr())->second; + if (threshold.rows() != key_delta.second.rows()) + throw std::invalid_argument( + "Relinearization threshold vector dimensionality for '" + + std::string(1, Symbol(key_delta.first).chr()) + + "' passed into iSAM2 parameters does not match actual variable " + "dimensionality."); + if ((key_delta.second.array().abs() > threshold.array()).any()) + relinKeys.insert(key_delta.first); + } + } + + return relinKeys; + } + + /** + * Apply expmap to the given values, but only for indices appearing in + * \c mask. Values are expmapped in-place. + * \param mask Mask on linear indices, only \c true entries are expmapped + */ + void expmapMasked(const VectorValues& delta, const KeySet& mask, + Values* theta) const { + assert(theta->size() == delta.size()); + Values::iterator key_value; + VectorValues::const_iterator key_delta; +#ifdef GTSAM_USE_TBB + for (key_value = theta->begin(); key_value != theta->end(); ++key_value) { + key_delta = delta.find(key_value->key); +#else + for (key_value = theta->begin(), key_delta = delta.begin(); + key_value != theta->end(); ++key_value, ++key_delta) { + assert(key_value->key == key_delta->first); +#endif + Key var = key_value->key; + assert(static_cast(delta[var].size()) == key_value->value.dim()); + assert(delta[var].allFinite()); + if (mask.exists(var)) { + Value* retracted = key_value->value.retract_(delta[var]); + key_value->value = *retracted; + retracted->deallocate_(); + } + } + } + + KeySet relinearize(const ISAM2::Roots& roots, const VectorValues& delta, + const KeySet& fixedVariables, Values* theta, + ISAM2Result* result) const { + KeySet relinKeys; + gttic(gather_relinearize_keys); + // 4. Mark keys in \Delta above threshold \beta: + // J=\{\Delta_{j}\in\Delta|\Delta_{j}\geq\beta\}. + if (params_.enablePartialRelinearizationCheck) + relinKeys = CheckRelinearizationPartial(roots, delta, + params_.relinearizeThreshold); + else + relinKeys = CheckRelinearizationFull(delta, params_.relinearizeThreshold); + if (updateParams_.forceFullSolve) + relinKeys = + CheckRelinearizationFull(delta, 0.0); // This is used for debugging + + // Remove from relinKeys any keys whose linearization points are fixed + for (Key key : fixedVariables) { + relinKeys.erase(key); + } + if (updateParams_.noRelinKeys) { + for (Key key : *updateParams_.noRelinKeys) { + relinKeys.erase(key); + } + } + + // Above relin threshold keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : relinKeys) { + result->detail->variableStatus[key].isAboveRelinThreshold = true; + result->detail->variableStatus[key].isRelinearized = true; + } + } + + // Add the variables being relinearized to the marked keys + KeySet markedRelinMask; + for (const Key key : relinKeys) markedRelinMask.insert(key); + result->markedKeys.insert(relinKeys.begin(), relinKeys.end()); + gttoc(gather_relinearize_keys); + + gttic(fluid_find_all); + // 5. Mark all cliques that involve marked variables \Theta_{J} and all + // their ancestors. + if (!relinKeys.empty()) { + for (const auto& root : roots) + // add other cliques that have the marked ones in the separator + root->findAll(markedRelinMask, &result->markedKeys); + + // Relin involved keys for detailed results + if (params_.enableDetailedResults) { + KeySet involvedRelinKeys; + for (const auto& root : roots) + root->findAll(markedRelinMask, &involvedRelinKeys); + for (Key key : involvedRelinKeys) { + if (!result->detail->variableStatus[key].isAboveRelinThreshold) { + result->detail->variableStatus[key].isRelinearizeInvolved = true; + result->detail->variableStatus[key].isRelinearized = true; + } + } + } + } + gttoc(fluid_find_all); + + gttic(expmap); + // 6. Update linearization point for marked variables: + // \Theta_{J}:=\Theta_{J}+\Delta_{J}. + if (!relinKeys.empty()) expmapMasked(delta, markedRelinMask, theta); + gttoc(expmap); + + result->variablesRelinearized = result->markedKeys.size(); + return relinKeys; + } + + // 7. Linearize new factors + void linearizeNewFactors(const NonlinearFactorGraph& newFactors, + const Values& theta, size_t numNonlinearFactors, + const FactorIndices& newFactorsIndices, + GaussianFactorGraph* linearFactors) const { + gttic(linearizeNewFactors); + auto linearized = newFactors.linearize(theta); + if (params_.findUnusedFactorSlots) { + linearFactors->resize(numNonlinearFactors); + for (size_t i = 0; i < newFactors.size(); ++i) + (*linearFactors)[newFactorsIndices[i]] = (*linearized)[i]; + } else { + linearFactors->push_back(*linearized); + } + assert(linearFactors->size() == numNonlinearFactors); + } + + void augmentVariableIndex(const NonlinearFactorGraph& newFactors, + const FactorIndices& newFactorsIndices, + VariableIndex* variableIndex) const { + gttic(augmentVariableIndex); + // Augment the variable index with the new factors + if (params_.findUnusedFactorSlots) + variableIndex->augment(newFactors, newFactorsIndices); + else + variableIndex->augment(newFactors); + + // Augment it with existing factors which now affect to more variables: + if (updateParams_.newAffectedKeys) { + for (const auto& factorAddedKeys : *updateParams_.newAffectedKeys) { + const auto factorIdx = factorAddedKeys.first; + variableIndex->augmentExistingFactor(factorIdx, factorAddedKeys.second); + } + } + } + + void logRecalculateKeys(const ISAM2Result& result) const { + const bool debug = ISDEBUG("ISAM2 recalculate"); + + if (debug) { + std::cout << "markedKeys: "; + for (const Key key : result.markedKeys) { + std::cout << key << " "; + } + std::cout << std::endl; + std::cout << "observedKeys: "; + for (const Key key : result.observedKeys) { + std::cout << key << " "; + } + std::cout << std::endl; + } + } + + FactorIndexSet getAffectedFactors(const KeyList& keys, + const VariableIndex& variableIndex) const { + FactorIndexSet indices; + for (const Key key : keys) { + const FactorIndices& factors(variableIndex[key]); + indices.insert(factors.begin(), factors.end()); + } + return indices; + } + + // find intermediate (linearized) factors from cache that are passed into the + // affected area + GaussianFactorGraph getCachedBoundaryFactors( + const ISAM2::Cliques& orphans) const { + GaussianFactorGraph cachedBoundary; + + for (const auto& orphan : orphans) { + // retrieve the cached factor and add to boundary + cachedBoundary.push_back(orphan->cachedFactor()); + } + + return cachedBoundary; + } + + // retrieve all factors that ONLY contain the affected variables + // (note that the remaining stuff is summarized in the cached factors) + GaussianFactorGraph relinearizeAffectedFactors( + const FastList& affectedKeys, const KeySet& relinKeys, + const NonlinearFactorGraph& nonlinearFactors, + const VariableIndex& variableIndex, const Values& theta, + GaussianFactorGraph* linearFactors) const { + gttic(getAffectedFactors); + FactorIndexSet candidates = getAffectedFactors(affectedKeys, variableIndex); + gttoc(getAffectedFactors); + + gttic(affectedKeysSet); + // for fast lookup below + KeySet affectedKeysSet; + affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); + gttoc(affectedKeysSet); + + gttic(check_candidates_and_linearize); + GaussianFactorGraph linearized; + for (const FactorIndex idx : candidates) { + bool inside = true; + bool useCachedLinear = params_.cacheLinearizedFactors; + for (Key key : nonlinearFactors[idx]->keys()) { + if (affectedKeysSet.find(key) == affectedKeysSet.end()) { + inside = false; + break; + } + if (useCachedLinear && relinKeys.find(key) != relinKeys.end()) + useCachedLinear = false; + } + if (inside) { + if (useCachedLinear) { +#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS + assert((*linearFactors)[idx]); + assert((*linearFactors)[idx]->keys() == + nonlinearFactors[idx]->keys()); +#endif + linearized.push_back((*linearFactors)[idx]); + } else { + auto linearFactor = nonlinearFactors[idx]->linearize(theta); + linearized.push_back(linearFactor); + if (params_.cacheLinearizedFactors) { +#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS + assert((*linearFactors)[idx]->keys() == linearFactor->keys()); +#endif + (*linearFactors)[idx] = linearFactor; + } + } + } + } + gttoc(check_candidates_and_linearize); + + return linearized; + } + + KeySet recalculate(const Values& theta, const VariableIndex& variableIndex, + const NonlinearFactorGraph& nonlinearFactors, + const GaussianBayesNet& affectedBayesNet, + const ISAM2::Cliques& orphans, const KeySet& relinKeys, + GaussianFactorGraph* linearFactors, ISAM2::Roots* roots, + ISAM2::Nodes* nodes, ISAM2Result* result) const { + gttic(recalculate); + logRecalculateKeys(*result); + + // FactorGraph factors(affectedBayesNet); + // bug was here: we cannot reuse the original factors, because then the + // cached factors get messed up [all the necessary data is actually + // contained in the affectedBayesNet, including what was passed in from the + // boundaries, + // so this would be correct; however, in the process we also generate new + // cached_ entries that will be wrong (ie. they don't contain what would be + // passed up at a certain point if batch elimination was done, but that's + // what we need); we could choose not to update cached_ from here, but then + // the new information (and potentially different variable ordering) is not + // reflected in the cached_ values which again will be wrong] + // so instead we have to retrieve the original linearized factors AND add + // the cached factors from the boundary + + // BEGIN OF COPIED CODE + + // ordering provides all keys in conditionals, there cannot be others + // because path to root included + gttic(affectedKeys); + FastList affectedKeys; + for (const auto& conditional : affectedBayesNet) + affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), + conditional->endFrontals()); + gttoc(affectedKeys); + + KeySet affectedKeysSet; // Will return this result + + static const double kBatchThreshold = 0.65; + + if (affectedKeys.size() >= theta.size() * kBatchThreshold) { + // Do a batch step - reorder and relinearize all variables + gttic(batch); + + gttic(add_keys); + br::copy(variableIndex | br::map_keys, + std::inserter(affectedKeysSet, affectedKeysSet.end())); + + // Removed unused keys: + VariableIndex affectedFactorsVarIndex = variableIndex; + + affectedFactorsVarIndex.removeUnusedVariables( + result->unusedIndices.begin(), result->unusedIndices.end()); + + for (const Key key : result->unusedIndices) { + affectedKeysSet.erase(key); + } + gttoc(add_keys); + + gttic(ordering); + Ordering order; + if (updateParams_.constrainedKeys) { + order = Ordering::ColamdConstrained(affectedFactorsVarIndex, + *updateParams_.constrainedKeys); + } else { + if (theta.size() > result->observedKeys.size()) { + // Only if some variables are unconstrained + FastMap constraintGroups; + for (Key var : result->observedKeys) constraintGroups[var] = 1; + order = Ordering::ColamdConstrained(affectedFactorsVarIndex, + constraintGroups); + } else { + order = Ordering::Colamd(affectedFactorsVarIndex); + } + } + gttoc(ordering); + + gttic(linearize); + GaussianFactorGraph linearized = *nonlinearFactors.linearize(theta); + if (params_.cacheLinearizedFactors) *linearFactors = linearized; + gttoc(linearize); + + gttic(eliminate); + ISAM2BayesTree::shared_ptr bayesTree = + ISAM2JunctionTree(GaussianEliminationTree( + linearized, affectedFactorsVarIndex, order)) + .eliminate(params_.getEliminationFunction()) + .first; + gttoc(eliminate); + + gttic(insert); + roots->clear(); + roots->insert(roots->end(), bayesTree->roots().begin(), + bayesTree->roots().end()); + nodes->clear(); + nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); + gttoc(insert); + + result->variablesReeliminated = affectedKeysSet.size(); + result->factorsRecalculated = nonlinearFactors.size(); + + // Reeliminated keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : theta.keys()) { + result->detail->variableStatus[key].isReeliminated = true; + } + } + + gttoc(batch); + + } else { + gttic(incremental); + const bool debug = ISDEBUG("ISAM2 recalculate"); + + // 2. Add the new factors \Factors' into the resulting factor graph + FastList affectedAndNewKeys; + affectedAndNewKeys.insert(affectedAndNewKeys.end(), affectedKeys.begin(), + affectedKeys.end()); + affectedAndNewKeys.insert(affectedAndNewKeys.end(), + result->observedKeys.begin(), + result->observedKeys.end()); + gttic(relinearizeAffected); + GaussianFactorGraph factors = relinearizeAffectedFactors( + affectedAndNewKeys, relinKeys, nonlinearFactors, variableIndex, theta, + linearFactors); + gttoc(relinearizeAffected); + + if (debug) { + factors.print("Relinearized factors: "); + std::cout << "Affected keys: "; + for (const Key key : affectedKeys) { + std::cout << key << " "; + } + std::cout << std::endl; + } + + // Reeliminated keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : affectedAndNewKeys) { + result->detail->variableStatus[key].isReeliminated = true; + } + } + + result->variablesReeliminated = affectedAndNewKeys.size(); + result->factorsRecalculated = factors.size(); + + gttic(cached); + // add the cached intermediate results from the boundary of the orphans + // ... + GaussianFactorGraph cachedBoundary = getCachedBoundaryFactors(orphans); + if (debug) cachedBoundary.print("Boundary factors: "); + factors.push_back(cachedBoundary); + gttoc(cached); + + gttic(orphans); + // Add the orphaned subtrees + for (const auto& orphan : orphans) + factors += + boost::make_shared >(orphan); + gttoc(orphans); + + // END OF COPIED CODE + + // 3. Re-order and eliminate the factor graph into a Bayes net (Algorithm + // [alg:eliminate]), and re-assemble into a new Bayes tree (Algorithm + // [alg:BayesTree]) + + gttic(reorder_and_eliminate); + + gttic(list_to_set); + // create a partial reordering for the new and contaminated factors + // result->markedKeys are passed in: those variables will be forced to the + // end in the ordering + affectedKeysSet.insert(result->markedKeys.begin(), + result->markedKeys.end()); + affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); + gttoc(list_to_set); + + VariableIndex affectedFactorsVarIndex(factors); + + gttic(ordering_constraints); + // Create ordering constraints + FastMap constraintGroups; + if (updateParams_.constrainedKeys) { + constraintGroups = *updateParams_.constrainedKeys; + } else { + constraintGroups = FastMap(); + const int group = + result->observedKeys.size() < affectedFactorsVarIndex.size() ? 1 + : 0; + for (Key var : result->observedKeys) + constraintGroups.insert(std::make_pair(var, group)); + } + + // Remove unaffected keys from the constraints + for (FastMap::iterator iter = constraintGroups.begin(); + iter != constraintGroups.end(); + /*Incremented in loop ++iter*/) { + if (result->unusedIndices.exists(iter->first) || + !affectedKeysSet.exists(iter->first)) + constraintGroups.erase(iter++); + else + ++iter; + } + gttoc(ordering_constraints); + + // Generate ordering + gttic(Ordering); + Ordering ordering = Ordering::ColamdConstrained(affectedFactorsVarIndex, + constraintGroups); + gttoc(Ordering); + + ISAM2BayesTree::shared_ptr bayesTree = + ISAM2JunctionTree(GaussianEliminationTree( + factors, affectedFactorsVarIndex, ordering)) + .eliminate(params_.getEliminationFunction()) + .first; + + gttoc(reorder_and_eliminate); + + gttic(reassemble); + roots->insert(roots->end(), bayesTree->roots().begin(), + bayesTree->roots().end()); + nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); + gttoc(reassemble); + + // 4. The orphans have already been inserted during elimination + + gttoc(incremental); + } + + // Root clique variables for detailed results + if (params_.enableDetailedResults) { + for (const auto& root : *roots) + for (Key var : *root->conditional()) + result->detail->variableStatus[var].inRootClique = true; + } + return affectedKeysSet; + } }; +/* ************************************************************************* */ -} +} // namespace gtsam diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index 988e6779f8..0ce60fd6f1 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -16,26 +16,16 @@ * @author Michael Kaess, Richard Roberts, Frank Dellaert */ +#include #include +#include #include #include #include -#include // We need the inst file because we'll make a special JT templated on ISAM2 -#include #include -#include -#include -namespace br { -using namespace boost::range; -using namespace boost::adaptors; -} // namespace br - -#include -#include #include -#include using namespace std; @@ -44,35 +34,6 @@ namespace gtsam { // Instantiate base class template class BayesTree; -static const bool kDisableReordering = false; -static const double kBatchThreshold = 0.65; - -/* ************************************************************************* */ -// Special BayesTree class that uses ISAM2 cliques - this is the result of -// reeliminating ISAM2 subtrees. -class ISAM2BayesTree : public ISAM2::Base { - public: - typedef ISAM2::Base Base; - typedef ISAM2BayesTree This; - typedef boost::shared_ptr shared_ptr; - - ISAM2BayesTree() {} -}; - -/* ************************************************************************* */ -// Special JunctionTree class that produces ISAM2 BayesTree cliques, used for -// reeliminating ISAM2 subtrees. -class ISAM2JunctionTree - : public JunctionTree { - public: - typedef JunctionTree Base; - typedef ISAM2JunctionTree This; - typedef boost::shared_ptr shared_ptr; - - explicit ISAM2JunctionTree(const GaussianEliminationTree& eliminationTree) - : Base(eliminationTree) {} -}; - /* ************************************************************************* */ ISAM2::ISAM2(const ISAM2Params& params) : params_(params), update_count_(0) { if (params_.optimizationParams.type() == typeid(ISAM2DoglegParams)) @@ -96,392 +57,11 @@ bool ISAM2::equals(const ISAM2& other, double tol) const { } /* ************************************************************************* */ -FactorIndexSet ISAM2::getAffectedFactors(const KeyList& keys) const { - static const bool debug = false; - if (debug) cout << "Getting affected factors for "; - if (debug) { - for (const Key key : keys) { - cout << key << " "; - } - } - if (debug) cout << endl; - - FactorIndexSet indices; - for (const Key key : keys) { - const VariableIndex::Factors& factors(variableIndex_[key]); - indices.insert(factors.begin(), factors.end()); - } - if (debug) cout << "Affected factors are: "; - if (debug) { - for (const auto index : indices) { - cout << index << " "; - } - } - if (debug) cout << endl; - return indices; -} - -/* ************************************************************************* */ -// retrieve all factors that ONLY contain the affected variables -// (note that the remaining stuff is summarized in the cached factors) - -GaussianFactorGraph::shared_ptr ISAM2::relinearizeAffectedFactors( - const FastList& affectedKeys, const KeySet& relinKeys) const { - gttic(getAffectedFactors); - FactorIndexSet candidates = getAffectedFactors(affectedKeys); - gttoc(getAffectedFactors); - - gttic(affectedKeysSet); - // for fast lookup below - KeySet affectedKeysSet; - affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); - gttoc(affectedKeysSet); - - gttic(check_candidates_and_linearize); - auto linearized = boost::make_shared(); - for (const FactorIndex idx : candidates) { - bool inside = true; - bool useCachedLinear = params_.cacheLinearizedFactors; - for (Key key : nonlinearFactors_[idx]->keys()) { - if (affectedKeysSet.find(key) == affectedKeysSet.end()) { - inside = false; - break; - } - if (useCachedLinear && relinKeys.find(key) != relinKeys.end()) - useCachedLinear = false; - } - if (inside) { - if (useCachedLinear) { -#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS - assert(linearFactors_[idx]); - assert(linearFactors_[idx]->keys() == nonlinearFactors_[idx]->keys()); -#endif - linearized->push_back(linearFactors_[idx]); - } else { - auto linearFactor = nonlinearFactors_[idx]->linearize(theta_); - linearized->push_back(linearFactor); - if (params_.cacheLinearizedFactors) { -#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS - assert(linearFactors_[idx]->keys() == linearFactor->keys()); -#endif - linearFactors_[idx] = linearFactor; - } - } - } - } - gttoc(check_candidates_and_linearize); - - return linearized; -} - -/* ************************************************************************* */ -// find intermediate (linearized) factors from cache that are passed into the -// affected area - -GaussianFactorGraph ISAM2::getCachedBoundaryFactors(const Cliques& orphans) { - GaussianFactorGraph cachedBoundary; - - for (sharedClique orphan : orphans) { - // retrieve the cached factor and add to boundary - cachedBoundary.push_back(orphan->cachedFactor()); - } - - return cachedBoundary; -} - -/* ************************************************************************* */ -boost::shared_ptr ISAM2::recalculate( - const KeySet& markedKeys, const KeySet& relinKeys, - const KeyVector& observedKeys, const KeySet& unusedIndices, - const boost::optional >& constrainKeys, - ISAM2Result* result) { - // TODO(dellaert): new factors are linearized twice, - // the newFactors passed in are not used. - - const bool debug = ISDEBUG("ISAM2 recalculate"); - - // Input: BayesTree(this), newFactors - -// figures for paper, disable for timing -#ifdef PRINT_STATS - static int counter = 0; - int maxClique = 0; - double avgClique = 0; - int numCliques = 0; - int nnzR = 0; - if (counter > 0) { // cannot call on empty tree - GaussianISAM2_P::CliqueData cdata = this->getCliqueData(); - GaussianISAM2_P::CliqueStats cstats = cdata.getStats(); - maxClique = cstats.maxCONDITIONALSize; - avgClique = cstats.avgCONDITIONALSize; - numCliques = cdata.conditionalSizes.size(); - nnzR = calculate_nnz(this->root()); - } - counter++; -#endif - - if (debug) { - cout << "markedKeys: "; - for (const Key key : markedKeys) { - cout << key << " "; - } - cout << endl; - cout << "observedKeys: "; - for (const Key key : observedKeys) { - cout << key << " "; - } - cout << endl; - } - - // 1. Remove top of Bayes tree and convert to a factor graph: - // (a) For each affected variable, remove the corresponding clique and all - // parents up to the root. (b) Store orphaned sub-trees \BayesTree_{O} of - // removed cliques. - gttic(removetop); - Cliques orphans; - GaussianBayesNet affectedBayesNet; - this->removeTop(KeyVector(markedKeys.begin(), markedKeys.end()), - affectedBayesNet, orphans); - gttoc(removetop); - - // FactorGraph factors(affectedBayesNet); - // bug was here: we cannot reuse the original factors, because then the cached - // factors get messed up [all the necessary data is actually contained in the - // affectedBayesNet, including what was passed in from the boundaries, - // so this would be correct; however, in the process we also generate new - // cached_ entries that will be wrong (ie. they don't contain what would be - // passed up at a certain point if batch elimination was done, but that's - // what we need); we could choose not to update cached_ from here, but then - // the new information (and potentially different variable ordering) is not - // reflected in the cached_ values which again will be wrong] - // so instead we have to retrieve the original linearized factors AND add the - // cached factors from the boundary - - // BEGIN OF COPIED CODE - - // ordering provides all keys in conditionals, there cannot be others because - // path to root included - gttic(affectedKeys); - FastList affectedKeys; - for (const auto& conditional : affectedBayesNet) - affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), - conditional->endFrontals()); - gttoc(affectedKeys); - - boost::shared_ptr affectedKeysSet( - new KeySet()); // Will return this result - - if (affectedKeys.size() >= theta_.size() * kBatchThreshold) { - // Do a batch step - reorder and relinearize all variables - gttic(batch); - - gttic(add_keys); - br::copy(variableIndex_ | br::map_keys, - std::inserter(*affectedKeysSet, affectedKeysSet->end())); - - // Removed unused keys: - VariableIndex affectedFactorsVarIndex = variableIndex_; - - affectedFactorsVarIndex.removeUnusedVariables(unusedIndices.begin(), - unusedIndices.end()); - - for (const Key key : unusedIndices) { - affectedKeysSet->erase(key); - } - gttoc(add_keys); - - gttic(ordering); - Ordering order; - if (constrainKeys) { - order = - Ordering::ColamdConstrained(affectedFactorsVarIndex, *constrainKeys); - } else { - if (theta_.size() > observedKeys.size()) { - // Only if some variables are unconstrained - FastMap constraintGroups; - for (Key var : observedKeys) constraintGroups[var] = 1; - order = Ordering::ColamdConstrained(affectedFactorsVarIndex, - constraintGroups); - } else { - order = Ordering::Colamd(affectedFactorsVarIndex); - } - } - gttoc(ordering); - - gttic(linearize); - GaussianFactorGraph linearized = *nonlinearFactors_.linearize(theta_); - if (params_.cacheLinearizedFactors) linearFactors_ = linearized; - gttoc(linearize); - - gttic(eliminate); - ISAM2BayesTree::shared_ptr bayesTree = - ISAM2JunctionTree( - GaussianEliminationTree(linearized, affectedFactorsVarIndex, order)) - .eliminate(params_.getEliminationFunction()) - .first; - gttoc(eliminate); - - gttic(insert); - this->clear(); - this->roots_.insert(this->roots_.end(), bayesTree->roots().begin(), - bayesTree->roots().end()); - this->nodes_.insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); - gttoc(insert); - - result->variablesReeliminated = affectedKeysSet->size(); - result->factorsRecalculated = nonlinearFactors_.size(); - - lastAffectedMarkedCount = markedKeys.size(); - lastAffectedVariableCount = affectedKeysSet->size(); - lastAffectedFactorCount = linearized.size(); - - // Reeliminated keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : theta_.keys()) { - result->detail->variableStatus[key].isReeliminated = true; - } - } - - gttoc(batch); - - } else { - gttic(incremental); - - // 2. Add the new factors \Factors' into the resulting factor graph - FastList affectedAndNewKeys; - affectedAndNewKeys.insert(affectedAndNewKeys.end(), affectedKeys.begin(), - affectedKeys.end()); - affectedAndNewKeys.insert(affectedAndNewKeys.end(), observedKeys.begin(), - observedKeys.end()); - gttic(relinearizeAffected); - GaussianFactorGraph factors( - *relinearizeAffectedFactors(affectedAndNewKeys, relinKeys)); - if (debug) factors.print("Relinearized factors: "); - gttoc(relinearizeAffected); - - if (debug) { - cout << "Affected keys: "; - for (const Key key : affectedKeys) { - cout << key << " "; - } - cout << endl; - } - - // Reeliminated keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : affectedAndNewKeys) { - result->detail->variableStatus[key].isReeliminated = true; - } - } - - result->variablesReeliminated = affectedAndNewKeys.size(); - result->factorsRecalculated = factors.size(); - lastAffectedMarkedCount = markedKeys.size(); - lastAffectedVariableCount = affectedKeys.size(); - lastAffectedFactorCount = factors.size(); - -#ifdef PRINT_STATS - // output for generating figures - cout << "linear: #markedKeys: " << markedKeys.size() - << " #affectedVariables: " << affectedKeys.size() - << " #affectedFactors: " << factors.size() - << " maxCliqueSize: " << maxClique << " avgCliqueSize: " << avgClique - << " #Cliques: " << numCliques << " nnzR: " << nnzR << endl; -#endif - - gttic(cached); - // add the cached intermediate results from the boundary of the orphans ... - GaussianFactorGraph cachedBoundary = getCachedBoundaryFactors(orphans); - if (debug) cachedBoundary.print("Boundary factors: "); - factors.push_back(cachedBoundary); - gttoc(cached); - - gttic(orphans); - // Add the orphaned subtrees - for (const sharedClique& orphan : orphans) - factors += boost::make_shared >(orphan); - gttoc(orphans); - - // END OF COPIED CODE - - // 3. Re-order and eliminate the factor graph into a Bayes net (Algorithm - // [alg:eliminate]), and re-assemble into a new Bayes tree (Algorithm - // [alg:BayesTree]) - - gttic(reorder_and_eliminate); - - gttic(list_to_set); - // create a partial reordering for the new and contaminated factors - // markedKeys are passed in: those variables will be forced to the end in - // the ordering - affectedKeysSet->insert(markedKeys.begin(), markedKeys.end()); - affectedKeysSet->insert(affectedKeys.begin(), affectedKeys.end()); - gttoc(list_to_set); - - VariableIndex affectedFactorsVarIndex(factors); - - gttic(ordering_constraints); - // Create ordering constraints - FastMap constraintGroups; - if (constrainKeys) { - constraintGroups = *constrainKeys; - } else { - constraintGroups = FastMap(); - const int group = - observedKeys.size() < affectedFactorsVarIndex.size() ? 1 : 0; - for (Key var : observedKeys) - constraintGroups.insert(make_pair(var, group)); - } - - // Remove unaffected keys from the constraints - for (FastMap::iterator iter = constraintGroups.begin(); - iter != constraintGroups.end(); - /*Incremented in loop ++iter*/) { - if (unusedIndices.exists(iter->first) || - !affectedKeysSet->exists(iter->first)) - constraintGroups.erase(iter++); - else - ++iter; - } - gttoc(ordering_constraints); - - // Generate ordering - gttic(Ordering); - Ordering ordering = - Ordering::ColamdConstrained(affectedFactorsVarIndex, constraintGroups); - gttoc(Ordering); - - ISAM2BayesTree::shared_ptr bayesTree = - ISAM2JunctionTree( - GaussianEliminationTree(factors, affectedFactorsVarIndex, ordering)) - .eliminate(params_.getEliminationFunction()) - .first; - - gttoc(reorder_and_eliminate); - - gttic(reassemble); - this->roots_.insert(this->roots_.end(), bayesTree->roots().begin(), - bayesTree->roots().end()); - this->nodes_.insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); - gttoc(reassemble); - - // 4. The orphans have already been inserted during elimination - - gttoc(incremental); - } - - // Root clique variables for detailed results - if (params_.enableDetailedResults) { - for (const sharedNode& root : this->roots()) - for (Key var : *root->conditional()) - result->detail->variableStatus[var].inRootClique = true; - } - - return affectedKeysSet; -} - -/* ************************************************************************* */ -void ISAM2::addVariables(const Values& newTheta) { +void ISAM2::addVariables( + const Values& newTheta, + ISAM2Result::DetailedResults::StatusMap* variableStatus) { + gttic(addNewVariables); + // \Theta:=\Theta\cup\Theta_{new}. const bool debug = ISDEBUG("ISAM2 AddVariables"); theta_.insert(newTheta); @@ -490,6 +70,13 @@ void ISAM2::addVariables(const Values& newTheta) { delta_.insert(newTheta.zeroVectors()); deltaNewton_.insert(newTheta.zeroVectors()); RgProd_.insert(newTheta.zeroVectors()); + + // New keys for detailed results + if (variableStatus && params_.enableDetailedResults) { + for (Key key : newTheta.keys()) { + (*variableStatus)[key].isNew = true; + } + } } /* ************************************************************************* */ @@ -506,36 +93,6 @@ void ISAM2::removeVariables(const KeySet& unusedKeys) { } } -/* ************************************************************************* */ -void ISAM2::expmapMasked(const KeySet& mask) { - assert(theta_.size() == delta_.size()); - Values::iterator key_value; - VectorValues::const_iterator key_delta; -#ifdef GTSAM_USE_TBB - for (key_value = theta_.begin(); key_value != theta_.end(); ++key_value) { - key_delta = delta_.find(key_value->key); -#else - for (key_value = theta_.begin(), key_delta = delta_.begin(); - key_value != theta_.end(); ++key_value, ++key_delta) { - assert(key_value->key == key_delta->first); -#endif - Key var = key_value->key; - assert(static_cast(delta_[var].size()) == key_value->value.dim()); - assert(delta_[var].allFinite()); - if (mask.exists(var)) { - Value* retracted = key_value->value.retract_(delta_[var]); - key_value->value = *retracted; - retracted->deallocate_(); -#ifndef NDEBUG - // If debugging, invalidate delta_ entries to Inf, to trigger assertions - // if we try to re-use them. - delta_[var] = Vector::Constant(delta_[var].rows(), - numeric_limits::infinity()); -#endif - } - } -} - /* ************************************************************************* */ ISAM2Result ISAM2::update( const NonlinearFactorGraph& newFactors, const Values& newTheta, @@ -544,7 +101,6 @@ ISAM2Result ISAM2::update( const boost::optional >& noRelinKeys, const boost::optional >& extraReelimKeys, bool force_relinearize) { - ISAM2UpdateParams params; params.constrainedKeys = constrainedKeys; params.extraReelimKeys = extraReelimKeys; @@ -557,28 +113,21 @@ ISAM2Result ISAM2::update( /* ************************************************************************* */ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, - const Values& newTheta, - const ISAM2UpdateParams& updateParams) { - const bool debug = ISDEBUG("ISAM2 update"); - const bool verbose = ISDEBUG("ISAM2 update verbose"); - + const Values& newTheta, + const ISAM2UpdateParams& updateParams) { gttic(ISAM2_update); this->update_count_++; - lastAffectedVariableCount = 0; - lastAffectedFactorCount = 0; - lastAffectedCliqueCount = 0; - lastAffectedMarkedCount = 0; - lastBacksubVariableCount = 0; - lastNnzTop = 0; ISAM2Result result; if (params_.enableDetailedResults) result.detail = ISAM2Result::DetailedResults(); const bool relinearizeThisStep = - updateParams.force_relinearize || (params_.enableRelinearization && - update_count_ % params_.relinearizeSkip == 0); + updateParams.force_relinearize || + (params_.enableRelinearization && + update_count_ % params_.relinearizeSkip == 0); + const bool verbose = ISDEBUG("ISAM2 update verbose"); if (verbose) { cout << "ISAM2::update\n"; this->print("ISAM2: "); @@ -586,243 +135,66 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, // Update delta if we need it to check relinearization later if (relinearizeThisStep) { - gttic(updateDelta); - updateDelta(kDisableReordering); - gttoc(updateDelta); + updateDelta(updateParams.forceFullSolve); } - gttic(push_back_factors); - // 1. Add any new factors \Factors:=\Factors\cup\Factors'. - // Add the new factor indices to the result struct - if (debug || verbose) newFactors.print("The new factors are: "); - Impl::AddFactorsStep1(newFactors, params_.findUnusedFactorSlots, - &nonlinearFactors_, &result.newFactorsIndices); - - // Remove the removed factors - NonlinearFactorGraph removeFactors; - removeFactors.reserve(updateParams.removeFactorIndices.size()); - for (const auto index : updateParams.removeFactorIndices) { - removeFactors.push_back(nonlinearFactors_[index]); - nonlinearFactors_.remove(index); - if (params_.cacheLinearizedFactors) linearFactors_.remove(index); - } + UpdateImpl update(params_, updateParams); - // Remove removed factors from the variable index so we do not attempt to - // relinearize them - variableIndex_.remove(updateParams.removeFactorIndices.begin(), - updateParams.removeFactorIndices.end(), - removeFactors); - - // Compute unused keys and indices - KeySet unusedKeys; - KeySet unusedIndices; - { - // Get keys from removed factors and new factors, and compute unused keys, - // i.e., keys that are empty now and do not appear in the new factors. - KeySet removedAndEmpty; - for (Key key : removeFactors.keys()) { - if (variableIndex_[key].empty()) - removedAndEmpty.insert(removedAndEmpty.end(), key); - } - KeySet newFactorSymbKeys = newFactors.keys(); - std::set_difference(removedAndEmpty.begin(), removedAndEmpty.end(), - newFactorSymbKeys.begin(), newFactorSymbKeys.end(), - std::inserter(unusedKeys, unusedKeys.end())); - - // Get indices for unused keys - for (Key key : unusedKeys) { - unusedIndices.insert(unusedIndices.end(), key); - } - } - gttoc(push_back_factors); + // 1. Add any new factors \Factors:=\Factors\cup\Factors'. + update.pushBackFactors(newFactors, &nonlinearFactors_, &linearFactors_, + &variableIndex_, &result); - gttic(add_new_variables); // 2. Initialize any new variables \Theta_{new} and add - // \Theta:=\Theta\cup\Theta_{new}. - addVariables(newTheta); - // New keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : newTheta.keys()) { - result.detail->variableStatus[key].isNew = true; - } - } - gttoc(add_new_variables); + addVariables(newTheta, result.detail ? &result.detail->variableStatus : 0); gttic(evaluate_error_before); if (params_.evaluateNonlinearError) result.errorBefore.reset(nonlinearFactors_.error(calculateEstimate())); gttoc(evaluate_error_before); - gttic(gather_involved_keys); // 3. Mark linear update - KeySet markedKeys = newFactors.keys(); // Get keys from new factors - // Also mark keys involved in removed factors - { - KeySet markedRemoveKeys = - removeFactors.keys(); // Get keys involved in removed factors - markedKeys.insert( - markedRemoveKeys.begin(), - markedRemoveKeys.end()); // Add to the overall set of marked keys - } - // Also mark any provided extra re-eliminate keys - if (updateParams.extraReelimKeys) { - for (Key key : *updateParams.extraReelimKeys) { - markedKeys.insert(key); - } - } - // Also, keys that were not observed in existing factors, but whose affected - // keys have been extended now (e.g. smart factors) - if (updateParams.newAffectedKeys) { - for (const auto &factorAddedKeys : *updateParams.newAffectedKeys) { - const auto factorIdx = factorAddedKeys.first; - const auto& affectedKeys = nonlinearFactors_.at(factorIdx)->keys(); - markedKeys.insert(affectedKeys.begin(),affectedKeys.end()); - } - } - - // Observed keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : markedKeys) { - result.detail->variableStatus[key].isObserved = true; - } - } - - KeyVector observedKeys; - for (Key index : markedKeys) { - // Only add if not unused - if (unusedIndices.find(index) == unusedIndices.end()) - // Make a copy of these, as we'll soon add to them - observedKeys.push_back(index); - } - gttoc(gather_involved_keys); + update.gatherInvolvedKeys(newFactors, nonlinearFactors_, &result); // Check relinearization if we're at the nth step, or we are using a looser // loop relin threshold KeySet relinKeys; if (relinearizeThisStep) { - gttic(gather_relinearize_keys); - // 4. Mark keys in \Delta above threshold \beta: - // J=\{\Delta_{j}\in\Delta|\Delta_{j}\geq\beta\}. - if (params_.enablePartialRelinearizationCheck) - relinKeys = Impl::CheckRelinearizationPartial( - roots_, delta_, params_.relinearizeThreshold); - else - relinKeys = - Impl::CheckRelinearizationFull(delta_, params_.relinearizeThreshold); - if (kDisableReordering) - relinKeys = Impl::CheckRelinearizationFull( - delta_, 0.0); // This is used for debugging - - // Remove from relinKeys any keys whose linearization points are fixed - for (Key key : fixedVariables_) { - relinKeys.erase(key); - } - if (updateParams.noRelinKeys) { - for (Key key : *updateParams.noRelinKeys) { - relinKeys.erase(key); - } - } - - // Above relin threshold keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : relinKeys) { - result.detail->variableStatus[key].isAboveRelinThreshold = true; - result.detail->variableStatus[key].isRelinearized = true; - } - } - - // Add the variables being relinearized to the marked keys - KeySet markedRelinMask; - for (const Key key : relinKeys) markedRelinMask.insert(key); - markedKeys.insert(relinKeys.begin(), relinKeys.end()); - gttoc(gather_relinearize_keys); - - gttic(fluid_find_all); - // 5. Mark all cliques that involve marked variables \Theta_{J} and all - // their ancestors. - if (!relinKeys.empty()) { - for (const sharedClique& root : roots_) - // add other cliques that have the marked ones in the separator - root->findAll(markedRelinMask, &markedKeys); - - // Relin involved keys for detailed results - if (params_.enableDetailedResults) { - KeySet involvedRelinKeys; - for (const sharedClique& root : roots_) - root->findAll(markedRelinMask, &involvedRelinKeys); - for (Key key : involvedRelinKeys) { - if (!result.detail->variableStatus[key].isAboveRelinThreshold) { - result.detail->variableStatus[key].isRelinearizeInvolved = true; - result.detail->variableStatus[key].isRelinearized = true; - } - } - } - } - gttoc(fluid_find_all); - - gttic(expmap); - // 6. Update linearization point for marked variables: - // \Theta_{J}:=\Theta_{J}+\Delta_{J}. - if (!relinKeys.empty()) expmapMasked(markedRelinMask); - gttoc(expmap); - - result.variablesRelinearized = markedKeys.size(); + relinKeys = + update.relinearize(roots_, delta_, fixedVariables_, &theta_, &result); } else { result.variablesRelinearized = 0; } - gttic(linearize_new); // 7. Linearize new factors - if (params_.cacheLinearizedFactors) { - gttic(linearize); - auto linearFactors = newFactors.linearize(theta_); - if (params_.findUnusedFactorSlots) { - linearFactors_.resize(nonlinearFactors_.size()); - for (size_t newFactorI = 0; newFactorI < newFactors.size(); ++newFactorI) - linearFactors_[result.newFactorsIndices[newFactorI]] = - (*linearFactors)[newFactorI]; - } else { - linearFactors_.push_back(*linearFactors); - } - assert(nonlinearFactors_.size() == linearFactors_.size()); - gttoc(linearize); - } - gttoc(linearize_new); - - gttic(augment_VI); - // Augment the variable index with the new factors - if (params_.findUnusedFactorSlots) - variableIndex_.augment(newFactors, result.newFactorsIndices); - else - variableIndex_.augment(newFactors); - - // Augment it with existing factors which now affect to more variables: - if (updateParams.newAffectedKeys) { - for (const auto &factorAddedKeys : *updateParams.newAffectedKeys) { - const auto factorIdx = factorAddedKeys.first; - variableIndex_.augmentExistingFactor( - factorIdx, factorAddedKeys.second); - } - } - gttoc(augment_VI); + update.linearizeNewFactors(newFactors, theta_, nonlinearFactors_.size(), + result.newFactorsIndices, &linearFactors_); + update.augmentVariableIndex(newFactors, result.newFactorsIndices, + &variableIndex_); - gttic(recalculate); // 8. Redo top of Bayes tree - boost::shared_ptr replacedKeys; - if (!markedKeys.empty() || !observedKeys.empty()) - replacedKeys = recalculate( - markedKeys, relinKeys, observedKeys, unusedIndices, - updateParams.constrainedKeys, &result); - - // Update replaced keys mask (accumulates until back-substitution takes place) - if (replacedKeys) - deltaReplacedMask_.insert(replacedKeys->begin(), replacedKeys->end()); - gttoc(recalculate); + if (!result.markedKeys.empty() || !result.observedKeys.empty()) { + // Remove top of Bayes tree and convert to a factor graph: + // (a) For each affected variable, remove the corresponding clique and all + // parents up to the root. (b) Store orphaned sub-trees \BayesTree_{O} of + // removed cliques. + GaussianBayesNet affectedBayesNet; + Cliques orphans; + this->removeTop( + KeyVector(result.markedKeys.begin(), result.markedKeys.end()), + affectedBayesNet, orphans); + + KeySet affectedKeysSet = update.recalculate( + theta_, variableIndex_, nonlinearFactors_, affectedBayesNet, orphans, + relinKeys, &linearFactors_, &roots_, &nodes_, &result); + // Update replaced keys mask (accumulates until back-substitution takes + // place) + deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); + } // Update data structures to remove unused keys - if (!unusedKeys.empty()) { + if (!result.unusedKeys.empty()) { gttic(remove_variables); - removeVariables(unusedKeys); + removeVariables(result.unusedKeys); gttoc(remove_variables); } result.cliques = this->nodes().size(); @@ -966,7 +338,7 @@ void ISAM2::marginalizeLeaves( } } // Create factor graph from factor indices - for (const auto index: factorsFromMarginalizedInClique_step1) { + for (const auto index : factorsFromMarginalizedInClique_step1) { graph.push_back(nonlinearFactors_[index]->linearize(theta_)); } @@ -1040,7 +412,7 @@ void ISAM2::marginalizeLeaves( // Remove the factors to remove that have been summarized in the newly-added // marginal factors NonlinearFactorGraph removedFactors; - for (const auto index: factorIndicesToRemove) { + for (const auto index : factorIndicesToRemove) { removedFactors.push_back(nonlinearFactors_[index]); nonlinearFactors_.remove(index); if (params_.cacheLinearizedFactors) linearFactors_.remove(index); @@ -1057,6 +429,7 @@ void ISAM2::marginalizeLeaves( } /* ************************************************************************* */ +// Marked const but actually changes mutable delta void ISAM2::updateDelta(bool forceFullSolve) const { gttic(updateDelta); if (params_.optimizationParams.type() == typeid(ISAM2GaussNewtonParams)) { @@ -1066,8 +439,8 @@ void ISAM2::updateDelta(bool forceFullSolve) const { const double effectiveWildfireThreshold = forceFullSolve ? 0.0 : gaussNewtonParams.wildfireThreshold; gttic(Wildfire_update); - lastBacksubVariableCount = Impl::UpdateGaussNewtonDelta( - roots_, deltaReplacedMask_, effectiveWildfireThreshold, &delta_); + DeltaImpl::UpdateGaussNewtonDelta(roots_, deltaReplacedMask_, + effectiveWildfireThreshold, &delta_); deltaReplacedMask_.clear(); gttoc(Wildfire_update); @@ -1083,15 +456,15 @@ void ISAM2::updateDelta(bool forceFullSolve) const { // Compute Newton's method step gttic(Wildfire_update); - lastBacksubVariableCount = Impl::UpdateGaussNewtonDelta( + DeltaImpl::UpdateGaussNewtonDelta( roots_, deltaReplacedMask_, effectiveWildfireThreshold, &deltaNewton_); gttoc(Wildfire_update); // Compute steepest descent step const VectorValues gradAtZero = this->gradientAtZero(); // Compute gradient - Impl::UpdateRgProd(roots_, deltaReplacedMask_, gradAtZero, - &RgProd_); // Update RgProd - const VectorValues dx_u = Impl::ComputeGradientSearch( + DeltaImpl::UpdateRgProd(roots_, deltaReplacedMask_, gradAtZero, + &RgProd_); // Update RgProd + const VectorValues dx_u = DeltaImpl::ComputeGradientSearch( gradAtZero, RgProd_); // Compute gradient search point // Clear replaced keys mask because now we've updated deltaNewton_ and @@ -1163,8 +536,7 @@ VectorValues ISAM2::gradientAtZero() const { VectorValues g; // Sum up contributions for each clique - for (const auto& root : this->roots()) - root->addGradientAtZero(&g); + for (const auto& root : this->roots()) root->addGradientAtZero(&g); return g; } diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index 60cdc45feb..b0eb4693bf 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -20,12 +20,12 @@ #pragma once +#include +#include #include #include -#include #include #include -#include #include @@ -73,8 +73,8 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { * This is \c mutable because it is used internally to not update delta_ * until it is needed. */ - mutable KeySet - deltaReplacedMask_; // TODO(dellaert): Make sure accessed in the right way + mutable KeySet deltaReplacedMask_; // TODO(dellaert): Make sure accessed in + // the right way /** All original nonlinear factors are stored here to use during * relinearization */ @@ -175,9 +175,9 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { * @return An ISAM2Result struct containing information about the update * @note No default parameters to avoid ambiguous call errors. */ - virtual ISAM2Result update( - const NonlinearFactorGraph& newFactors, const Values& newTheta, - const ISAM2UpdateParams& updateParams); + virtual ISAM2Result update(const NonlinearFactorGraph& newFactors, + const Values& newTheta, + const ISAM2UpdateParams& updateParams); /** Marginalize out variables listed in leafKeys. These keys must be leaves * in the BayesTree. Throws MarginalizeNonleafException if non-leaves are @@ -226,7 +226,6 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { return traits::Retract(theta_.at(key), delta); } - /** Compute an estimate for a single variable using its incomplete linear * delta computed during the last update. This is faster than calling the * no-argument version of calculateEstimate, which operates on all variables. @@ -243,9 +242,6 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { /// @name Public members for non-typical usage /// @{ - /** Internal implementation functions */ - struct Impl; - /** Compute an estimate using a complete delta computed by a full * back-substitution. */ @@ -268,13 +264,6 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { /** Access the nonlinear variable index */ const KeySet& getFixedVariables() const { return fixedVariables_; } - size_t lastAffectedVariableCount; - size_t lastAffectedFactorCount; - size_t lastAffectedCliqueCount; - size_t lastAffectedMarkedCount; - mutable size_t lastBacksubVariableCount; - size_t lastNnzTop; - const ISAM2Params& params() const { return params_; } /** prints out clique statistics */ @@ -295,37 +284,17 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { /** * Add new variables to the ISAM2 system. * @param newTheta Initial values for new variables - * @param theta Current solution to be augmented with new initialization - * @param delta Current linear delta to be augmented with zeros - * @param deltaNewton - * @param RgProd - * @param keyFormatter Formatter for printing nonlinear keys during debugging + * @param variableStatus optional detailed result structure */ - void addVariables(const Values& newTheta); + void addVariables( + const Values& newTheta, + ISAM2Result::DetailedResults::StatusMap* variableStatus = 0); /** * Remove variables from the ISAM2 system. */ void removeVariables(const KeySet& unusedKeys); - /** - * Apply expmap to the given values, but only for indices appearing in - * \c mask. Values are expmapped in-place. - * \param mask Mask on linear indices, only \c true entries are expmapped - */ - void expmapMasked(const KeySet& mask); - - FactorIndexSet getAffectedFactors(const FastList& keys) const; - GaussianFactorGraph::shared_ptr relinearizeAffectedFactors( - const FastList& affectedKeys, const KeySet& relinKeys) const; - GaussianFactorGraph getCachedBoundaryFactors(const Cliques& orphans); - - virtual boost::shared_ptr recalculate( - const KeySet& markedKeys, const KeySet& relinKeys, - const KeyVector& observedKeys, const KeySet& unusedIndices, - const boost::optional >& constrainKeys, - ISAM2Result* result); - void updateDelta(bool forceFullSolve = false) const; }; // ISAM2 @@ -334,5 +303,3 @@ template <> struct traits : public Testable {}; } // namespace gtsam - -#include diff --git a/gtsam/nonlinear/ISAM2Result.h b/gtsam/nonlinear/ISAM2Result.h index 763a5e1985..d49c92627c 100644 --- a/gtsam/nonlinear/ISAM2Result.h +++ b/gtsam/nonlinear/ISAM2Result.h @@ -24,8 +24,8 @@ #include #include -#include #include +#include #include @@ -96,6 +96,20 @@ struct GTSAM_EXPORT ISAM2Result { */ FactorIndices newFactorsIndices; + /** Unused keys, and indices for unused keys. TODO(frank): the same?? + * i.e., keys that are empty now and do not appear in the new factors. + */ + KeySet unusedKeys, unusedIndices; + + /** keys for variables that were observed, i.e., not unused. */ + KeyVector observedKeys; + + /** Keys of variables that had factors removed. */ + KeySet keysWithRemovedFactors; + + /** All keys that were marked during the update process. */ + KeySet markedKeys; + /** A struct holding detailed results, which must be enabled with * ISAM2Params::enableDetailedResults. */ @@ -132,9 +146,10 @@ struct GTSAM_EXPORT ISAM2Result { inRootClique(false) {} }; - /** The status of each variable during this update, see VariableStatus. - */ - FastMap variableStatus; + typedef FastMap StatusMap; + + /// The status of each variable during this update, see VariableStatus. + StatusMap variableStatus; }; /** Detailed results, if enabled by ISAM2Params::enableDetailedResults. See diff --git a/gtsam/nonlinear/ISAM2UpdateParams.h b/gtsam/nonlinear/ISAM2UpdateParams.h index 77f722b12a..884ac7922a 100644 --- a/gtsam/nonlinear/ISAM2UpdateParams.h +++ b/gtsam/nonlinear/ISAM2UpdateParams.h @@ -16,11 +16,11 @@ #pragma once -#include #include -#include // GTSAM_EXPORT -#include // Key, KeySet -#include //FactorIndices +#include // GTSAM_EXPORT +#include // Key, KeySet +#include //FactorIndices +#include namespace gtsam { @@ -63,8 +63,12 @@ struct GTSAM_EXPORT ISAM2UpdateParams { * depend on Keys `X(2)`, `X(3)`. Next call to ISAM2::update() must include * its `newAffectedKeys` field with the map `13 -> {X(2), X(3)}`. */ - boost::optional> newAffectedKeys{boost::none}; + boost::optional> newAffectedKeys{boost::none}; + /** By default, iSAM2 uses a wildfire update scheme that stops updating when + * the deltas become too small down in the tree. This flagg forces a full + * solve instead. */ + bool forceFullSolve{false}; }; -} // namespace gtsam +} // namespace gtsam diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index 2b6809adaa..d8b8298726 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -4,7 +4,8 @@ * @author Michael Kaess */ -#include +#include +#include #include #include @@ -14,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -23,10 +23,13 @@ #include #include #include -#include #include -using namespace boost::assign; + +#include + +#include #include +using namespace boost::assign; namespace br { using namespace boost::adaptors; using namespace boost::range; } using namespace std; @@ -304,7 +307,7 @@ TEST(ISAM2, AddFactorsStep1) FactorIndices actualNewFactorIndices; - ISAM2::Impl::AddFactorsStep1(newFactors, true, &nonlinearFactors, &actualNewFactorIndices); + UpdateImpl::AddFactorsStep1(newFactors, true, &nonlinearFactors, &actualNewFactorIndices); EXPECT(assert_equal(expectedNonlinearFactors, nonlinearFactors)); EXPECT(assert_container_equality(expectedNewFactorIndices, actualNewFactorIndices)); From 5a819645a46d9a5e3af9aed64755671430383689 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 31 May 2019 20:10:44 -0400 Subject: [PATCH 026/160] Fixed print when no noise model --- gtsam/slam/PriorFactor.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gtsam/slam/PriorFactor.h b/gtsam/slam/PriorFactor.h index 8bb7a3ab84..5b085652f1 100644 --- a/gtsam/slam/PriorFactor.h +++ b/gtsam/slam/PriorFactor.h @@ -18,6 +18,8 @@ #include #include +#include + namespace gtsam { /** @@ -70,10 +72,14 @@ namespace gtsam { /** implement functions needed for Testable */ /** print */ - virtual void print(const std::string& s, const KeyFormatter& keyFormatter = DefaultKeyFormatter) const { + virtual void print(const std::string& s, const KeyFormatter& keyFormatter = + DefaultKeyFormatter) const { std::cout << s << "PriorFactor on " << keyFormatter(this->key()) << "\n"; traits::Print(prior_, " prior mean: "); - this->noiseModel_->print(" noise model: "); + if (this->noiseModel_) + this->noiseModel_->print(" noise model: "); + else + std::cout << "no noise model" << std::endl; } /** equals */ From ecacda68c04313a4e9ce5a687b8439fdeaa64f61 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 13:45:07 -0400 Subject: [PATCH 027/160] Further refactored pushNewFactors --- gtsam/nonlinear/ISAM2-impl.h | 65 +++++++++++++++++++++--------------- gtsam/nonlinear/ISAM2.cpp | 8 +---- tests/testGaussianISAM2.cpp | 9 +++-- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index f4dfcb99b8..352d26c8a1 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -117,17 +117,33 @@ struct GTSAM_EXPORT UpdateImpl { UpdateImpl(const ISAM2Params& params, const ISAM2UpdateParams& updateParams) : params_(params), updateParams_(updateParams) {} + // Provide some debugging information at the start of update + static void LogStartingUpdate(const NonlinearFactorGraph& newFactors, + const ISAM2& isam2) { + gttic(pushBackFactors); + const bool debug = ISDEBUG("ISAM2 update"); + const bool verbose = ISDEBUG("ISAM2 update verbose"); + + if (verbose) { + std::cout << "ISAM2::update\n"; + isam2.print("ISAM2: "); + } + + // Add the new factor indices to the result struct + if (debug || verbose) { + newFactors.print("The new factors are: "); + } + } + /// Perform the first part of the bookkeeping updates for adding new factors. /// Adds them to the complete list of nonlinear factors, and populates the /// list of new factor indices, both optionally finding and reusing empty /// factor slots. - static void AddFactorsStep1(const NonlinearFactorGraph& newFactors, - bool useUnusedSlots, - NonlinearFactorGraph* nonlinearFactors, - FactorIndices* newFactorIndices) { - newFactorIndices->resize(newFactors.size()); + FactorIndices addFactorsStep1(const NonlinearFactorGraph& newFactors, + NonlinearFactorGraph* nonlinearFactors) const { + FactorIndices newFactorIndices(newFactors.size()); - if (useUnusedSlots) { + if (params_.findUnusedFactorSlots) { size_t globalFactorIndex = 0; for (size_t newFactorIndex = 0; newFactorIndex < newFactors.size(); ++newFactorIndex) { @@ -150,14 +166,15 @@ struct GTSAM_EXPORT UpdateImpl { // Use the current slot, updating nonlinearFactors and newFactorSlots. (*nonlinearFactors)[globalFactorIndex] = newFactors[newFactorIndex]; - (*newFactorIndices)[newFactorIndex] = globalFactorIndex; + newFactorIndices[newFactorIndex] = globalFactorIndex; } } else { // We're not looking for unused slots, so just add the factors at the end. for (size_t i = 0; i < newFactors.size(); ++i) - (*newFactorIndices)[i] = i + nonlinearFactors->size(); + newFactorIndices[i] = i + nonlinearFactors->size(); nonlinearFactors->push_back(newFactors); } + return newFactorIndices; } // 1. Add any new factors \Factors:=\Factors\cup\Factors'. @@ -167,13 +184,9 @@ struct GTSAM_EXPORT UpdateImpl { VariableIndex* variableIndex, ISAM2Result* result) const { gttic(pushBackFactors); - const bool debug = ISDEBUG("ISAM2 update"); - const bool verbose = ISDEBUG("ISAM2 update verbose"); // Add the new factor indices to the result struct - if (debug || verbose) newFactors.print("The new factors are: "); - AddFactorsStep1(newFactors, params_.findUnusedFactorSlots, nonlinearFactors, - &result->newFactorsIndices); + result->newFactorsIndices = addFactorsStep1(newFactors, nonlinearFactors); // Remove the removed factors NonlinearFactorGraph removedFactors; @@ -378,8 +391,8 @@ struct GTSAM_EXPORT UpdateImpl { * \c mask. Values are expmapped in-place. * \param mask Mask on linear indices, only \c true entries are expmapped */ - void expmapMasked(const VectorValues& delta, const KeySet& mask, - Values* theta) const { + static void ExpmapMasked(const VectorValues& delta, const KeySet& mask, + Values* theta) { assert(theta->size() == delta.size()); Values::iterator key_value; VectorValues::const_iterator key_delta; @@ -468,7 +481,7 @@ struct GTSAM_EXPORT UpdateImpl { gttic(expmap); // 6. Update linearization point for marked variables: // \Theta_{J}:=\Theta_{J}+\Delta_{J}. - if (!relinKeys.empty()) expmapMasked(delta, markedRelinMask, theta); + if (!relinKeys.empty()) ExpmapMasked(delta, markedRelinMask, theta); gttoc(expmap); result->variablesRelinearized = result->markedKeys.size(); @@ -511,7 +524,7 @@ struct GTSAM_EXPORT UpdateImpl { } } - void logRecalculateKeys(const ISAM2Result& result) const { + static void LogRecalculateKeys(const ISAM2Result& result) { const bool debug = ISDEBUG("ISAM2 recalculate"); if (debug) { @@ -528,8 +541,9 @@ struct GTSAM_EXPORT UpdateImpl { } } - FactorIndexSet getAffectedFactors(const KeyList& keys, - const VariableIndex& variableIndex) const { + static FactorIndexSet GetAffectedFactors(const KeyList& keys, + const VariableIndex& variableIndex) { + gttic(GetAffectedFactors); FactorIndexSet indices; for (const Key key : keys) { const FactorIndices& factors(variableIndex[key]); @@ -540,8 +554,8 @@ struct GTSAM_EXPORT UpdateImpl { // find intermediate (linearized) factors from cache that are passed into the // affected area - GaussianFactorGraph getCachedBoundaryFactors( - const ISAM2::Cliques& orphans) const { + static GaussianFactorGraph GetCachedBoundaryFactors( + const ISAM2::Cliques& orphans) { GaussianFactorGraph cachedBoundary; for (const auto& orphan : orphans) { @@ -559,9 +573,7 @@ struct GTSAM_EXPORT UpdateImpl { const NonlinearFactorGraph& nonlinearFactors, const VariableIndex& variableIndex, const Values& theta, GaussianFactorGraph* linearFactors) const { - gttic(getAffectedFactors); - FactorIndexSet candidates = getAffectedFactors(affectedKeys, variableIndex); - gttoc(getAffectedFactors); + FactorIndexSet candidates = GetAffectedFactors(affectedKeys, variableIndex); gttic(affectedKeysSet); // for fast lookup below @@ -614,7 +626,7 @@ struct GTSAM_EXPORT UpdateImpl { GaussianFactorGraph* linearFactors, ISAM2::Roots* roots, ISAM2::Nodes* nodes, ISAM2Result* result) const { gttic(recalculate); - logRecalculateKeys(*result); + LogRecalculateKeys(*result); // FactorGraph factors(affectedBayesNet); // bug was here: we cannot reuse the original factors, because then the @@ -754,7 +766,7 @@ struct GTSAM_EXPORT UpdateImpl { gttic(cached); // add the cached intermediate results from the boundary of the orphans // ... - GaussianFactorGraph cachedBoundary = getCachedBoundaryFactors(orphans); + GaussianFactorGraph cachedBoundary = GetCachedBoundaryFactors(orphans); if (debug) cachedBoundary.print("Boundary factors: "); factors.push_back(cachedBoundary); gttoc(cached); @@ -846,6 +858,5 @@ struct GTSAM_EXPORT UpdateImpl { return affectedKeysSet; } }; -/* ************************************************************************* */ } // namespace gtsam diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index 0ce60fd6f1..d6a48c6d57 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -116,8 +116,8 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, const Values& newTheta, const ISAM2UpdateParams& updateParams) { gttic(ISAM2_update); - this->update_count_++; + UpdateImpl::LogStartingUpdate(newFactors, *this); ISAM2Result result; if (params_.enableDetailedResults) @@ -127,12 +127,6 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, (params_.enableRelinearization && update_count_ % params_.relinearizeSkip == 0); - const bool verbose = ISDEBUG("ISAM2 update verbose"); - if (verbose) { - cout << "ISAM2::update\n"; - this->print("ISAM2: "); - } - // Update delta if we need it to check relinearization later if (relinearizeThisStep) { updateDelta(updateParams.forceFullSolve); diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index d8b8298726..3f8619a774 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -305,9 +305,12 @@ TEST(ISAM2, AddFactorsStep1) const FactorIndices expectedNewFactorIndices = list_of(1)(3); - FactorIndices actualNewFactorIndices; - - UpdateImpl::AddFactorsStep1(newFactors, true, &nonlinearFactors, &actualNewFactorIndices); + ISAM2Params params; + ISAM2UpdateParams updateParams; + params.findUnusedFactorSlots = true; + UpdateImpl update(params, updateParams); + FactorIndices actualNewFactorIndices = + update.addFactorsStep1(newFactors, &nonlinearFactors); EXPECT(assert_equal(expectedNonlinearFactors, nonlinearFactors)); EXPECT(assert_container_equality(expectedNewFactorIndices, actualNewFactorIndices)); From 4b405728a789ff43b299cbc96b3b36e813400ca3 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 13:53:00 -0400 Subject: [PATCH 028/160] Get rid of deprecated LieScalar --- tests/testGaussianISAM2.cpp | 152 +++++++++++++++++------------------- 1 file changed, 73 insertions(+), 79 deletions(-) diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index 3f8619a774..8f01d22aa5 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include @@ -37,7 +36,6 @@ using namespace gtsam; using boost::shared_ptr; static const SharedNoiseModel model; -static const LieScalar Zero(0); // SETDEBUG("ISAM2 update", true); // SETDEBUG("ISAM2 update verbose", true); @@ -286,22 +284,21 @@ bool isam_check(const NonlinearFactorGraph& fullgraph, const Values& fullinit, c } /* ************************************************************************* */ -TEST(ISAM2, AddFactorsStep1) -{ +TEST(ISAM2, AddFactorsStep1) { NonlinearFactorGraph nonlinearFactors; - nonlinearFactors += PriorFactor(10, Zero, model); + nonlinearFactors += PriorFactor(10, 0.0, model); nonlinearFactors += NonlinearFactor::shared_ptr(); - nonlinearFactors += PriorFactor(11, Zero, model); + nonlinearFactors += PriorFactor(11, 0.0, model); NonlinearFactorGraph newFactors; - newFactors += PriorFactor(1, Zero, model); - newFactors += PriorFactor(2, Zero, model); + newFactors += PriorFactor(1, 0.0, model); + newFactors += PriorFactor(2, 0.0, model); NonlinearFactorGraph expectedNonlinearFactors; - expectedNonlinearFactors += PriorFactor(10, Zero, model); - expectedNonlinearFactors += PriorFactor(1, Zero, model); - expectedNonlinearFactors += PriorFactor(11, Zero, model); - expectedNonlinearFactors += PriorFactor(2, Zero, model); + expectedNonlinearFactors += PriorFactor(10, 0.0, model); + expectedNonlinearFactors += PriorFactor(1, 0.0, model); + expectedNonlinearFactors += PriorFactor(11, 0.0, model); + expectedNonlinearFactors += PriorFactor(2, 0.0, model); const FactorIndices expectedNewFactorIndices = list_of(1)(3); @@ -313,7 +310,8 @@ TEST(ISAM2, AddFactorsStep1) update.addFactorsStep1(newFactors, &nonlinearFactors); EXPECT(assert_equal(expectedNonlinearFactors, nonlinearFactors)); - EXPECT(assert_container_equality(expectedNewFactorIndices, actualNewFactorIndices)); + EXPECT(assert_container_equality(expectedNewFactorIndices, + actualNewFactorIndices)); } /* ************************************************************************* */ @@ -698,25 +696,24 @@ namespace { } /* ************************************************************************* */ -TEST(ISAM2, marginalizeLeaves1) -{ +TEST(ISAM2, marginalizeLeaves1) { ISAM2 isam; NonlinearFactorGraph factors; - factors += PriorFactor(0, Zero, model); + factors += PriorFactor(0, 0.0, model); - factors += BetweenFactor(0, 1, Zero, model); - factors += BetweenFactor(1, 2, Zero, model); - factors += BetweenFactor(0, 2, Zero, model); + factors += BetweenFactor(0, 1, 0.0, model); + factors += BetweenFactor(1, 2, 0.0, model); + factors += BetweenFactor(0, 2, 0.0, model); Values values; - values.insert(0, Zero); - values.insert(1, Zero); - values.insert(2, Zero); + values.insert(0, 0.0); + values.insert(1, 0.0); + values.insert(2, 0.0); - FastMap constrainedKeys; - constrainedKeys.insert(make_pair(0,0)); - constrainedKeys.insert(make_pair(1,1)); - constrainedKeys.insert(make_pair(2,2)); + FastMap constrainedKeys; + constrainedKeys.insert(make_pair(0, 0)); + constrainedKeys.insert(make_pair(1, 1)); + constrainedKeys.insert(make_pair(2, 2)); isam.update(factors, values, FactorIndices(), constrainedKeys); @@ -725,29 +722,28 @@ TEST(ISAM2, marginalizeLeaves1) } /* ************************************************************************* */ -TEST(ISAM2, marginalizeLeaves2) -{ +TEST(ISAM2, marginalizeLeaves2) { ISAM2 isam; NonlinearFactorGraph factors; - factors += PriorFactor(0, Zero, model); + factors += PriorFactor(0, 0.0, model); - factors += BetweenFactor(0, 1, Zero, model); - factors += BetweenFactor(1, 2, Zero, model); - factors += BetweenFactor(0, 2, Zero, model); - factors += BetweenFactor(2, 3, Zero, model); + factors += BetweenFactor(0, 1, 0.0, model); + factors += BetweenFactor(1, 2, 0.0, model); + factors += BetweenFactor(0, 2, 0.0, model); + factors += BetweenFactor(2, 3, 0.0, model); Values values; - values.insert(0, Zero); - values.insert(1, Zero); - values.insert(2, Zero); - values.insert(3, Zero); + values.insert(0, 0.0); + values.insert(1, 0.0); + values.insert(2, 0.0); + values.insert(3, 0.0); - FastMap constrainedKeys; - constrainedKeys.insert(make_pair(0,0)); - constrainedKeys.insert(make_pair(1,1)); - constrainedKeys.insert(make_pair(2,2)); - constrainedKeys.insert(make_pair(3,3)); + FastMap constrainedKeys; + constrainedKeys.insert(make_pair(0, 0)); + constrainedKeys.insert(make_pair(1, 1)); + constrainedKeys.insert(make_pair(2, 2)); + constrainedKeys.insert(make_pair(3, 3)); isam.update(factors, values, FactorIndices(), constrainedKeys); @@ -756,38 +752,37 @@ TEST(ISAM2, marginalizeLeaves2) } /* ************************************************************************* */ -TEST(ISAM2, marginalizeLeaves3) -{ +TEST(ISAM2, marginalizeLeaves3) { ISAM2 isam; NonlinearFactorGraph factors; - factors += PriorFactor(0, Zero, model); + factors += PriorFactor(0, 0.0, model); - factors += BetweenFactor(0, 1, Zero, model); - factors += BetweenFactor(1, 2, Zero, model); - factors += BetweenFactor(0, 2, Zero, model); + factors += BetweenFactor(0, 1, 0.0, model); + factors += BetweenFactor(1, 2, 0.0, model); + factors += BetweenFactor(0, 2, 0.0, model); - factors += BetweenFactor(2, 3, Zero, model); + factors += BetweenFactor(2, 3, 0.0, model); - factors += BetweenFactor(3, 4, Zero, model); - factors += BetweenFactor(4, 5, Zero, model); - factors += BetweenFactor(3, 5, Zero, model); + factors += BetweenFactor(3, 4, 0.0, model); + factors += BetweenFactor(4, 5, 0.0, model); + factors += BetweenFactor(3, 5, 0.0, model); Values values; - values.insert(0, Zero); - values.insert(1, Zero); - values.insert(2, Zero); - values.insert(3, Zero); - values.insert(4, Zero); - values.insert(5, Zero); - - FastMap constrainedKeys; - constrainedKeys.insert(make_pair(0,0)); - constrainedKeys.insert(make_pair(1,1)); - constrainedKeys.insert(make_pair(2,2)); - constrainedKeys.insert(make_pair(3,3)); - constrainedKeys.insert(make_pair(4,4)); - constrainedKeys.insert(make_pair(5,5)); + values.insert(0, 0.0); + values.insert(1, 0.0); + values.insert(2, 0.0); + values.insert(3, 0.0); + values.insert(4, 0.0); + values.insert(5, 0.0); + + FastMap constrainedKeys; + constrainedKeys.insert(make_pair(0, 0)); + constrainedKeys.insert(make_pair(1, 1)); + constrainedKeys.insert(make_pair(2, 2)); + constrainedKeys.insert(make_pair(3, 3)); + constrainedKeys.insert(make_pair(4, 4)); + constrainedKeys.insert(make_pair(5, 5)); isam.update(factors, values, FactorIndices(), constrainedKeys); @@ -796,24 +791,23 @@ TEST(ISAM2, marginalizeLeaves3) } /* ************************************************************************* */ -TEST(ISAM2, marginalizeLeaves4) -{ +TEST(ISAM2, marginalizeLeaves4) { ISAM2 isam; NonlinearFactorGraph factors; - factors += PriorFactor(0, Zero, model); - factors += BetweenFactor(0, 2, Zero, model); - factors += BetweenFactor(1, 2, Zero, model); + factors += PriorFactor(0, 0.0, model); + factors += BetweenFactor(0, 2, 0.0, model); + factors += BetweenFactor(1, 2, 0.0, model); Values values; - values.insert(0, Zero); - values.insert(1, Zero); - values.insert(2, Zero); - - FastMap constrainedKeys; - constrainedKeys.insert(make_pair(0,0)); - constrainedKeys.insert(make_pair(1,1)); - constrainedKeys.insert(make_pair(2,2)); + values.insert(0, 0.0); + values.insert(1, 0.0); + values.insert(2, 0.0); + + FastMap constrainedKeys; + constrainedKeys.insert(make_pair(0, 0)); + constrainedKeys.insert(make_pair(1, 1)); + constrainedKeys.insert(make_pair(2, 2)); isam.update(factors, values, FactorIndices(), constrainedKeys); From 42b5f8163311979b2fcd2de0a5c2f1355d23242f Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 17:49:05 -0400 Subject: [PATCH 029/160] Now using add_factors --- gtsam/nonlinear/ISAM2-impl.h | 50 +++++------------------------------- tests/testGaussianISAM2.cpp | 31 ---------------------- 2 files changed, 6 insertions(+), 75 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 352d26c8a1..8a7db626b7 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -135,48 +135,6 @@ struct GTSAM_EXPORT UpdateImpl { } } - /// Perform the first part of the bookkeeping updates for adding new factors. - /// Adds them to the complete list of nonlinear factors, and populates the - /// list of new factor indices, both optionally finding and reusing empty - /// factor slots. - FactorIndices addFactorsStep1(const NonlinearFactorGraph& newFactors, - NonlinearFactorGraph* nonlinearFactors) const { - FactorIndices newFactorIndices(newFactors.size()); - - if (params_.findUnusedFactorSlots) { - size_t globalFactorIndex = 0; - for (size_t newFactorIndex = 0; newFactorIndex < newFactors.size(); - ++newFactorIndex) { - // Loop to find the next available factor slot - do { - // If we need to add more factors than we have room for, resize - // nonlinearFactors, filling the new slots with NULL factors. - // Otherwise, check if the current factor in nonlinearFactors is - // already used, and if so, increase globalFactorIndex. If the - // current factor in nonlinearFactors is unused, break out of the loop - // and use the current slot. - if (globalFactorIndex >= nonlinearFactors->size()) - nonlinearFactors->resize(nonlinearFactors->size() + - newFactors.size() - newFactorIndex); - else if ((*nonlinearFactors)[globalFactorIndex]) - ++globalFactorIndex; - else - break; - } while (true); - - // Use the current slot, updating nonlinearFactors and newFactorSlots. - (*nonlinearFactors)[globalFactorIndex] = newFactors[newFactorIndex]; - newFactorIndices[newFactorIndex] = globalFactorIndex; - } - } else { - // We're not looking for unused slots, so just add the factors at the end. - for (size_t i = 0; i < newFactors.size(); ++i) - newFactorIndices[i] = i + nonlinearFactors->size(); - nonlinearFactors->push_back(newFactors); - } - return newFactorIndices; - } - // 1. Add any new factors \Factors:=\Factors\cup\Factors'. void pushBackFactors(const NonlinearFactorGraph& newFactors, NonlinearFactorGraph* nonlinearFactors, @@ -185,8 +143,12 @@ struct GTSAM_EXPORT UpdateImpl { ISAM2Result* result) const { gttic(pushBackFactors); - // Add the new factor indices to the result struct - result->newFactorsIndices = addFactorsStep1(newFactors, nonlinearFactors); + // Perform the first part of the bookkeeping updates for adding new factors. + // Adds them to the complete list of nonlinear factors, and populates the + // list of new factor indices, both optionally finding and reusing empty + // factor slots. + result->newFactorsIndices = nonlinearFactors->add_factors( + newFactors, params_.findUnusedFactorSlots); // Remove the removed factors NonlinearFactorGraph removedFactors; diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index 8f01d22aa5..52938d8dba 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -283,37 +283,6 @@ bool isam_check(const NonlinearFactorGraph& fullgraph, const Values& fullinit, c return nodeGradientsOk && expectedGradOk && totalGradOk && isamEqual && isamTreeEqual && consistent; } -/* ************************************************************************* */ -TEST(ISAM2, AddFactorsStep1) { - NonlinearFactorGraph nonlinearFactors; - nonlinearFactors += PriorFactor(10, 0.0, model); - nonlinearFactors += NonlinearFactor::shared_ptr(); - nonlinearFactors += PriorFactor(11, 0.0, model); - - NonlinearFactorGraph newFactors; - newFactors += PriorFactor(1, 0.0, model); - newFactors += PriorFactor(2, 0.0, model); - - NonlinearFactorGraph expectedNonlinearFactors; - expectedNonlinearFactors += PriorFactor(10, 0.0, model); - expectedNonlinearFactors += PriorFactor(1, 0.0, model); - expectedNonlinearFactors += PriorFactor(11, 0.0, model); - expectedNonlinearFactors += PriorFactor(2, 0.0, model); - - const FactorIndices expectedNewFactorIndices = list_of(1)(3); - - ISAM2Params params; - ISAM2UpdateParams updateParams; - params.findUnusedFactorSlots = true; - UpdateImpl update(params, updateParams); - FactorIndices actualNewFactorIndices = - update.addFactorsStep1(newFactors, &nonlinearFactors); - - EXPECT(assert_equal(expectedNonlinearFactors, nonlinearFactors)); - EXPECT(assert_container_equality(expectedNewFactorIndices, - actualNewFactorIndices)); -} - /* ************************************************************************* */ TEST(ISAM2, simple) { From 696d8076d1cf5cb630306c469421045124910e1f Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 1 Jun 2019 23:55:50 -0400 Subject: [PATCH 030/160] Removed unusedIndices --- gtsam/nonlinear/ISAM2-impl.h | 13 ++++--------- gtsam/nonlinear/ISAM2Result.h | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 8a7db626b7..f2592f5a14 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -179,11 +179,6 @@ struct GTSAM_EXPORT UpdateImpl { removedAndEmpty.begin(), removedAndEmpty.end(), newFactorSymbKeys.begin(), newFactorSymbKeys.end(), std::inserter(result->unusedKeys, result->unusedKeys.end())); - - // Get indices for unused keys - for (Key key : result->unusedKeys) { - result->unusedIndices.insert(result->unusedIndices.end(), key); - } } // 3. Mark linear update @@ -221,7 +216,7 @@ struct GTSAM_EXPORT UpdateImpl { for (Key index : result->markedKeys) { // Only add if not unused - if (result->unusedIndices.find(index) == result->unusedIndices.end()) + if (result->unusedKeys.find(index) == result->unusedKeys.end()) // Make a copy of these, as we'll soon add to them result->observedKeys.push_back(index); } @@ -631,9 +626,9 @@ struct GTSAM_EXPORT UpdateImpl { VariableIndex affectedFactorsVarIndex = variableIndex; affectedFactorsVarIndex.removeUnusedVariables( - result->unusedIndices.begin(), result->unusedIndices.end()); + result->unusedKeys.begin(), result->unusedKeys.end()); - for (const Key key : result->unusedIndices) { + for (const Key key : result->unusedKeys) { affectedKeysSet.erase(key); } gttoc(add_keys); @@ -777,7 +772,7 @@ struct GTSAM_EXPORT UpdateImpl { for (FastMap::iterator iter = constraintGroups.begin(); iter != constraintGroups.end(); /*Incremented in loop ++iter*/) { - if (result->unusedIndices.exists(iter->first) || + if (result->unusedKeys.exists(iter->first) || !affectedKeysSet.exists(iter->first)) constraintGroups.erase(iter++); else diff --git a/gtsam/nonlinear/ISAM2Result.h b/gtsam/nonlinear/ISAM2Result.h index d49c92627c..17968453d8 100644 --- a/gtsam/nonlinear/ISAM2Result.h +++ b/gtsam/nonlinear/ISAM2Result.h @@ -96,10 +96,10 @@ struct GTSAM_EXPORT ISAM2Result { */ FactorIndices newFactorsIndices; - /** Unused keys, and indices for unused keys. TODO(frank): the same?? + /** Unused keys, and indices for unused keys, * i.e., keys that are empty now and do not appear in the new factors. */ - KeySet unusedKeys, unusedIndices; + KeySet unusedKeys; /** keys for variables that were observed, i.e., not unused. */ KeyVector observedKeys; From 8b01b81027adadd5d2c002331e6f95acd04c356b Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 00:32:06 -0400 Subject: [PATCH 031/160] Split up relinearize again for clarity --- gtsam/nonlinear/ISAM2-impl.h | 144 ++++++++++++++++------------------- gtsam/nonlinear/ISAM2.cpp | 21 +++-- tests/testGaussianISAM2.cpp | 1 - 3 files changed, 83 insertions(+), 83 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index f2592f5a14..91cdd8c2e4 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -135,7 +135,7 @@ struct GTSAM_EXPORT UpdateImpl { } } - // 1. Add any new factors \Factors:=\Factors\cup\Factors'. + // Add any new factors \Factors:=\Factors\cup\Factors'. void pushBackFactors(const NonlinearFactorGraph& newFactors, NonlinearFactorGraph* nonlinearFactors, GaussianFactorGraph* linearFactors, @@ -181,7 +181,7 @@ struct GTSAM_EXPORT UpdateImpl { std::inserter(result->unusedKeys, result->unusedKeys.end())); } - // 3. Mark linear update + // Mark linear update void gatherInvolvedKeys(const NonlinearFactorGraph& newFactors, const NonlinearFactorGraph& nonlinearFactors, ISAM2Result* result) const { @@ -197,6 +197,7 @@ struct GTSAM_EXPORT UpdateImpl { result->markedKeys.insert(key); } } + // Also, keys that were not observed in existing factors, but whose affected // keys have been extended now (e.g. smart factors) if (updateParams_.newAffectedKeys) { @@ -343,50 +344,20 @@ struct GTSAM_EXPORT UpdateImpl { return relinKeys; } - /** - * Apply expmap to the given values, but only for indices appearing in - * \c mask. Values are expmapped in-place. - * \param mask Mask on linear indices, only \c true entries are expmapped - */ - static void ExpmapMasked(const VectorValues& delta, const KeySet& mask, - Values* theta) { - assert(theta->size() == delta.size()); - Values::iterator key_value; - VectorValues::const_iterator key_delta; -#ifdef GTSAM_USE_TBB - for (key_value = theta->begin(); key_value != theta->end(); ++key_value) { - key_delta = delta.find(key_value->key); -#else - for (key_value = theta->begin(), key_delta = delta.begin(); - key_value != theta->end(); ++key_value, ++key_delta) { - assert(key_value->key == key_delta->first); -#endif - Key var = key_value->key; - assert(static_cast(delta[var].size()) == key_value->value.dim()); - assert(delta[var].allFinite()); - if (mask.exists(var)) { - Value* retracted = key_value->value.retract_(delta[var]); - key_value->value = *retracted; - retracted->deallocate_(); - } - } - } - - KeySet relinearize(const ISAM2::Roots& roots, const VectorValues& delta, - const KeySet& fixedVariables, Values* theta, - ISAM2Result* result) const { - KeySet relinKeys; - gttic(gather_relinearize_keys); - // 4. Mark keys in \Delta above threshold \beta: + // Mark keys in \Delta above threshold \beta: + KeySet gatherRelinearizeKeys(const ISAM2::Roots& roots, + const VectorValues& delta, + const KeySet& fixedVariables, + ISAM2Result* result) const { + gttic(gatherRelinearizeKeys); // J=\{\Delta_{j}\in\Delta|\Delta_{j}\geq\beta\}. - if (params_.enablePartialRelinearizationCheck) - relinKeys = CheckRelinearizationPartial(roots, delta, - params_.relinearizeThreshold); - else - relinKeys = CheckRelinearizationFull(delta, params_.relinearizeThreshold); + KeySet relinKeys = + params_.enablePartialRelinearizationCheck + ? CheckRelinearizationPartial(roots, delta, + params_.relinearizeThreshold) + : CheckRelinearizationFull(delta, params_.relinearizeThreshold); if (updateParams_.forceFullSolve) - relinKeys = - CheckRelinearizationFull(delta, 0.0); // This is used for debugging + relinKeys = CheckRelinearizationFull(delta, 0.0); // for debugging // Remove from relinKeys any keys whose linearization points are fixed for (Key key : fixedVariables) { @@ -398,7 +369,7 @@ struct GTSAM_EXPORT UpdateImpl { } } - // Above relin threshold keys for detailed results + // Record above relinerization threshold keys in detailed results if (params_.enableDetailedResults) { for (Key key : relinKeys) { result->detail->variableStatus[key].isAboveRelinThreshold = true; @@ -407,45 +378,64 @@ struct GTSAM_EXPORT UpdateImpl { } // Add the variables being relinearized to the marked keys - KeySet markedRelinMask; - for (const Key key : relinKeys) markedRelinMask.insert(key); result->markedKeys.insert(relinKeys.begin(), relinKeys.end()); - gttoc(gather_relinearize_keys); + return relinKeys; + } - gttic(fluid_find_all); - // 5. Mark all cliques that involve marked variables \Theta_{J} and all - // their ancestors. - if (!relinKeys.empty()) { - for (const auto& root : roots) - // add other cliques that have the marked ones in the separator - root->findAll(markedRelinMask, &result->markedKeys); + // Mark all cliques that involve marked variables \Theta_{J} and all + // their ancestors. + void fluidFindAll(const ISAM2::Roots& roots, const KeySet& relinKeys, + ISAM2Result* result) const { + gttic(fluidFindAll); + for (const auto& root : roots) + // add other cliques that have the marked ones in the separator + root->findAll(relinKeys, &result->markedKeys); - // Relin involved keys for detailed results - if (params_.enableDetailedResults) { - KeySet involvedRelinKeys; - for (const auto& root : roots) - root->findAll(markedRelinMask, &involvedRelinKeys); - for (Key key : involvedRelinKeys) { - if (!result->detail->variableStatus[key].isAboveRelinThreshold) { - result->detail->variableStatus[key].isRelinearizeInvolved = true; - result->detail->variableStatus[key].isRelinearized = true; - } + // Relinearization-involved keys for detailed results + if (params_.enableDetailedResults) { + KeySet involvedRelinKeys; + for (const auto& root : roots) + root->findAll(relinKeys, &involvedRelinKeys); + for (Key key : involvedRelinKeys) { + if (!result->detail->variableStatus[key].isAboveRelinThreshold) { + result->detail->variableStatus[key].isRelinearizeInvolved = true; + result->detail->variableStatus[key].isRelinearized = true; } } } - gttoc(fluid_find_all); - - gttic(expmap); - // 6. Update linearization point for marked variables: - // \Theta_{J}:=\Theta_{J}+\Delta_{J}. - if (!relinKeys.empty()) ExpmapMasked(delta, markedRelinMask, theta); - gttoc(expmap); + } - result->variablesRelinearized = result->markedKeys.size(); - return relinKeys; + /** + * Apply expmap to the given values, but only for indices appearing in + * \c mask. Values are expmapped in-place. + * \param mask Mask on linear indices, only \c true entries are expmapped + */ + static void ExpmapMasked(const VectorValues& delta, const KeySet& mask, + Values* theta) { + gttic(ExpmapMasked); + assert(theta->size() == delta.size()); + Values::iterator key_value; + VectorValues::const_iterator key_delta; +#ifdef GTSAM_USE_TBB + for (key_value = theta->begin(); key_value != theta->end(); ++key_value) { + key_delta = delta.find(key_value->key); +#else + for (key_value = theta->begin(), key_delta = delta.begin(); + key_value != theta->end(); ++key_value, ++key_delta) { + assert(key_value->key == key_delta->first); +#endif + Key var = key_value->key; + assert(static_cast(delta[var].size()) == key_value->value.dim()); + assert(delta[var].allFinite()); + if (mask.exists(var)) { + Value* retracted = key_value->value.retract_(delta[var]); + key_value->value = *retracted; + retracted->deallocate_(); + } + } } - // 7. Linearize new factors + // Linearize new factors void linearizeNewFactors(const NonlinearFactorGraph& newFactors, const Values& theta, size_t numNonlinearFactors, const FactorIndices& newFactorsIndices, @@ -625,8 +615,8 @@ struct GTSAM_EXPORT UpdateImpl { // Removed unused keys: VariableIndex affectedFactorsVarIndex = variableIndex; - affectedFactorsVarIndex.removeUnusedVariables( - result->unusedKeys.begin(), result->unusedKeys.end()); + affectedFactorsVarIndex.removeUnusedVariables(result->unusedKeys.begin(), + result->unusedKeys.end()); for (const Key key : result->unusedKeys) { affectedKeysSet.erase(key); diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index d6a48c6d57..cce698be44 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -119,9 +119,6 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, this->update_count_++; UpdateImpl::LogStartingUpdate(newFactors, *this); - ISAM2Result result; - if (params_.enableDetailedResults) - result.detail = ISAM2Result::DetailedResults(); const bool relinearizeThisStep = updateParams.force_relinearize || (params_.enableRelinearization && @@ -132,6 +129,9 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, updateDelta(updateParams.forceFullSolve); } + ISAM2Result result; + if (params_.enableDetailedResults) + result.detail = ISAM2Result::DetailedResults(); UpdateImpl update(params_, updateParams); // 1. Add any new factors \Factors:=\Factors\cup\Factors'. @@ -150,14 +150,25 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, update.gatherInvolvedKeys(newFactors, nonlinearFactors_, &result); // Check relinearization if we're at the nth step, or we are using a looser - // loop relin threshold + // loop relinerization threshold. KeySet relinKeys; if (relinearizeThisStep) { + // 4. Mark keys in \Delta above threshold \beta: relinKeys = - update.relinearize(roots_, delta_, fixedVariables_, &theta_, &result); + update.gatherRelinearizeKeys(roots_, delta_, fixedVariables_, &result); + if (!relinKeys.empty()) { + // 5. Mark all cliques that involve marked variables \Theta_{J} and all + // their ancestors. + update.fluidFindAll(roots_, relinKeys, &result); + // 6. Update linearization point for marked variables: + // \Theta_{J}:=\Theta_{J}+\Delta_{J}. + UpdateImpl::ExpmapMasked(delta_, relinKeys, &theta_); + } + result.variablesRelinearized = result.markedKeys.size(); } else { result.variablesRelinearized = 0; } + // TODO(frank): should be result.variablesRelinearized = relinKeys.size(); ? // 7. Linearize new factors update.linearizeNewFactors(newFactors, theta_, nonlinearFactors_.size(), diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index 52938d8dba..871e4bc185 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -5,7 +5,6 @@ */ #include -#include #include #include From 38d59eed68e3c5fea07391f880f9ad8d9908d769 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 01:09:01 -0400 Subject: [PATCH 032/160] Split recalculate for clarity --- gtsam/nonlinear/ISAM2-impl.h | 408 ++++++++++++++++++----------------- 1 file changed, 212 insertions(+), 196 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 91cdd8c2e4..65441da0eb 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -566,6 +566,205 @@ struct GTSAM_EXPORT UpdateImpl { return linearized; } + // Do a batch step - reorder and relinearize all variables + void recalculateBatch(const Values& theta, const VariableIndex& variableIndex, + const NonlinearFactorGraph& nonlinearFactors, + GaussianFactorGraph* linearFactors, + KeySet* affectedKeysSet, ISAM2::Roots* roots, + ISAM2::Nodes* nodes, ISAM2Result* result) const { + gttic(batch); + + gttic(add_keys); + br::copy(variableIndex | br::map_keys, + std::inserter(*affectedKeysSet, affectedKeysSet->end())); + + // Removed unused keys: + VariableIndex affectedFactorsVarIndex = variableIndex; + + affectedFactorsVarIndex.removeUnusedVariables(result->unusedKeys.begin(), + result->unusedKeys.end()); + + for (const Key key : result->unusedKeys) { + affectedKeysSet->erase(key); + } + gttoc(add_keys); + + gttic(ordering); + Ordering order; + if (updateParams_.constrainedKeys) { + order = Ordering::ColamdConstrained(affectedFactorsVarIndex, + *updateParams_.constrainedKeys); + } else { + if (theta.size() > result->observedKeys.size()) { + // Only if some variables are unconstrained + FastMap constraintGroups; + for (Key var : result->observedKeys) constraintGroups[var] = 1; + order = Ordering::ColamdConstrained(affectedFactorsVarIndex, + constraintGroups); + } else { + order = Ordering::Colamd(affectedFactorsVarIndex); + } + } + gttoc(ordering); + + gttic(linearize); + GaussianFactorGraph linearized = *nonlinearFactors.linearize(theta); + if (params_.cacheLinearizedFactors) *linearFactors = linearized; + gttoc(linearize); + + gttic(eliminate); + ISAM2BayesTree::shared_ptr bayesTree = + ISAM2JunctionTree( + GaussianEliminationTree(linearized, affectedFactorsVarIndex, order)) + .eliminate(params_.getEliminationFunction()) + .first; + gttoc(eliminate); + + gttic(insert); + roots->clear(); + roots->insert(roots->end(), bayesTree->roots().begin(), + bayesTree->roots().end()); + nodes->clear(); + nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); + gttoc(insert); + + result->variablesReeliminated = affectedKeysSet->size(); + result->factorsRecalculated = nonlinearFactors.size(); + + // Reeliminated keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : theta.keys()) { + result->detail->variableStatus[key].isReeliminated = true; + } + } + } + + void recalculateIncremental(const Values& theta, + const VariableIndex& variableIndex, + const NonlinearFactorGraph& nonlinearFactors, + const ISAM2::Cliques& orphans, + const KeySet& relinKeys, + GaussianFactorGraph* linearFactors, + const FastList& affectedKeys, + KeySet* affectedKeysSet, ISAM2::Roots* roots, + ISAM2::Nodes* nodes, ISAM2Result* result) const { + gttic(incremental); + const bool debug = ISDEBUG("ISAM2 recalculate"); + + // 2. Add the new factors \Factors' into the resulting factor graph + FastList affectedAndNewKeys; + affectedAndNewKeys.insert(affectedAndNewKeys.end(), affectedKeys.begin(), + affectedKeys.end()); + affectedAndNewKeys.insert(affectedAndNewKeys.end(), + result->observedKeys.begin(), + result->observedKeys.end()); + gttic(relinearizeAffected); + GaussianFactorGraph factors = relinearizeAffectedFactors( + affectedAndNewKeys, relinKeys, nonlinearFactors, variableIndex, theta, + linearFactors); + gttoc(relinearizeAffected); + + if (debug) { + factors.print("Relinearized factors: "); + std::cout << "Affected keys: "; + for (const Key key : affectedKeys) { + std::cout << key << " "; + } + std::cout << std::endl; + } + + // Reeliminated keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : affectedAndNewKeys) { + result->detail->variableStatus[key].isReeliminated = true; + } + } + + result->variablesReeliminated = affectedAndNewKeys.size(); + result->factorsRecalculated = factors.size(); + + gttic(cached); + // add the cached intermediate results from the boundary of the orphans + // ... + GaussianFactorGraph cachedBoundary = GetCachedBoundaryFactors(orphans); + if (debug) cachedBoundary.print("Boundary factors: "); + factors.push_back(cachedBoundary); + gttoc(cached); + + gttic(orphans); + // Add the orphaned subtrees + for (const auto& orphan : orphans) + factors += + boost::make_shared >(orphan); + gttoc(orphans); + + // END OF COPIED CODE + + // 3. Re-order and eliminate the factor graph into a Bayes net (Algorithm + // [alg:eliminate]), and re-assemble into a new Bayes tree (Algorithm + // [alg:BayesTree]) + + gttic(reorder_and_eliminate); + + gttic(list_to_set); + // create a partial reordering for the new and contaminated factors + // result->markedKeys are passed in: those variables will be forced to the + // end in the ordering + affectedKeysSet->insert(result->markedKeys.begin(), + result->markedKeys.end()); + affectedKeysSet->insert(affectedKeys.begin(), affectedKeys.end()); + gttoc(list_to_set); + + VariableIndex affectedFactorsVarIndex(factors); + + gttic(ordering_constraints); + // Create ordering constraints + FastMap constraintGroups; + if (updateParams_.constrainedKeys) { + constraintGroups = *updateParams_.constrainedKeys; + } else { + constraintGroups = FastMap(); + const int group = + result->observedKeys.size() < affectedFactorsVarIndex.size() ? 1 : 0; + for (Key var : result->observedKeys) + constraintGroups.insert(std::make_pair(var, group)); + } + + // Remove unaffected keys from the constraints + for (FastMap::iterator iter = constraintGroups.begin(); + iter != constraintGroups.end(); + /*Incremented in loop ++iter*/) { + if (result->unusedKeys.exists(iter->first) || + !affectedKeysSet->exists(iter->first)) + constraintGroups.erase(iter++); + else + ++iter; + } + gttoc(ordering_constraints); + + // Generate ordering + gttic(Ordering); + Ordering ordering = + Ordering::ColamdConstrained(affectedFactorsVarIndex, constraintGroups); + gttoc(Ordering); + + ISAM2BayesTree::shared_ptr bayesTree = + ISAM2JunctionTree( + GaussianEliminationTree(factors, affectedFactorsVarIndex, ordering)) + .eliminate(params_.getEliminationFunction()) + .first; + + gttoc(reorder_and_eliminate); + + gttic(reassemble); + roots->insert(roots->end(), bayesTree->roots().begin(), + bayesTree->roots().end()); + nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); + gttoc(reassemble); + + // 4. The orphans have already been inserted during elimination + } + KeySet recalculate(const Values& theta, const VariableIndex& variableIndex, const NonlinearFactorGraph& nonlinearFactors, const GaussianBayesNet& affectedBayesNet, @@ -579,15 +778,14 @@ struct GTSAM_EXPORT UpdateImpl { // bug was here: we cannot reuse the original factors, because then the // cached factors get messed up [all the necessary data is actually // contained in the affectedBayesNet, including what was passed in from the - // boundaries, - // so this would be correct; however, in the process we also generate new - // cached_ entries that will be wrong (ie. they don't contain what would be - // passed up at a certain point if batch elimination was done, but that's - // what we need); we could choose not to update cached_ from here, but then - // the new information (and potentially different variable ordering) is not - // reflected in the cached_ values which again will be wrong] - // so instead we have to retrieve the original linearized factors AND add - // the cached factors from the boundary + // boundaries, so this would be correct; however, in the process we also + // generate new cached_ entries that will be wrong (ie. they don't contain + // what would be passed up at a certain point if batch elimination was done, + // but that's what we need); we could choose not to update cached_ from + // here, but then the new information (and potentially different variable + // ordering) is not reflected in the cached_ values which again will be + // wrong] so instead we have to retrieve the original linearized factors AND + // add the cached factors from the boundary // BEGIN OF COPIED CODE @@ -603,196 +801,14 @@ struct GTSAM_EXPORT UpdateImpl { KeySet affectedKeysSet; // Will return this result static const double kBatchThreshold = 0.65; - if (affectedKeys.size() >= theta.size() * kBatchThreshold) { // Do a batch step - reorder and relinearize all variables - gttic(batch); - - gttic(add_keys); - br::copy(variableIndex | br::map_keys, - std::inserter(affectedKeysSet, affectedKeysSet.end())); - - // Removed unused keys: - VariableIndex affectedFactorsVarIndex = variableIndex; - - affectedFactorsVarIndex.removeUnusedVariables(result->unusedKeys.begin(), - result->unusedKeys.end()); - - for (const Key key : result->unusedKeys) { - affectedKeysSet.erase(key); - } - gttoc(add_keys); - - gttic(ordering); - Ordering order; - if (updateParams_.constrainedKeys) { - order = Ordering::ColamdConstrained(affectedFactorsVarIndex, - *updateParams_.constrainedKeys); - } else { - if (theta.size() > result->observedKeys.size()) { - // Only if some variables are unconstrained - FastMap constraintGroups; - for (Key var : result->observedKeys) constraintGroups[var] = 1; - order = Ordering::ColamdConstrained(affectedFactorsVarIndex, - constraintGroups); - } else { - order = Ordering::Colamd(affectedFactorsVarIndex); - } - } - gttoc(ordering); - - gttic(linearize); - GaussianFactorGraph linearized = *nonlinearFactors.linearize(theta); - if (params_.cacheLinearizedFactors) *linearFactors = linearized; - gttoc(linearize); - - gttic(eliminate); - ISAM2BayesTree::shared_ptr bayesTree = - ISAM2JunctionTree(GaussianEliminationTree( - linearized, affectedFactorsVarIndex, order)) - .eliminate(params_.getEliminationFunction()) - .first; - gttoc(eliminate); - - gttic(insert); - roots->clear(); - roots->insert(roots->end(), bayesTree->roots().begin(), - bayesTree->roots().end()); - nodes->clear(); - nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); - gttoc(insert); - - result->variablesReeliminated = affectedKeysSet.size(); - result->factorsRecalculated = nonlinearFactors.size(); - - // Reeliminated keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : theta.keys()) { - result->detail->variableStatus[key].isReeliminated = true; - } - } - - gttoc(batch); - + recalculateBatch(theta, variableIndex, nonlinearFactors, linearFactors, + &affectedKeysSet, roots, nodes, result); } else { - gttic(incremental); - const bool debug = ISDEBUG("ISAM2 recalculate"); - - // 2. Add the new factors \Factors' into the resulting factor graph - FastList affectedAndNewKeys; - affectedAndNewKeys.insert(affectedAndNewKeys.end(), affectedKeys.begin(), - affectedKeys.end()); - affectedAndNewKeys.insert(affectedAndNewKeys.end(), - result->observedKeys.begin(), - result->observedKeys.end()); - gttic(relinearizeAffected); - GaussianFactorGraph factors = relinearizeAffectedFactors( - affectedAndNewKeys, relinKeys, nonlinearFactors, variableIndex, theta, - linearFactors); - gttoc(relinearizeAffected); - - if (debug) { - factors.print("Relinearized factors: "); - std::cout << "Affected keys: "; - for (const Key key : affectedKeys) { - std::cout << key << " "; - } - std::cout << std::endl; - } - - // Reeliminated keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : affectedAndNewKeys) { - result->detail->variableStatus[key].isReeliminated = true; - } - } - - result->variablesReeliminated = affectedAndNewKeys.size(); - result->factorsRecalculated = factors.size(); - - gttic(cached); - // add the cached intermediate results from the boundary of the orphans - // ... - GaussianFactorGraph cachedBoundary = GetCachedBoundaryFactors(orphans); - if (debug) cachedBoundary.print("Boundary factors: "); - factors.push_back(cachedBoundary); - gttoc(cached); - - gttic(orphans); - // Add the orphaned subtrees - for (const auto& orphan : orphans) - factors += - boost::make_shared >(orphan); - gttoc(orphans); - - // END OF COPIED CODE - - // 3. Re-order and eliminate the factor graph into a Bayes net (Algorithm - // [alg:eliminate]), and re-assemble into a new Bayes tree (Algorithm - // [alg:BayesTree]) - - gttic(reorder_and_eliminate); - - gttic(list_to_set); - // create a partial reordering for the new and contaminated factors - // result->markedKeys are passed in: those variables will be forced to the - // end in the ordering - affectedKeysSet.insert(result->markedKeys.begin(), - result->markedKeys.end()); - affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); - gttoc(list_to_set); - - VariableIndex affectedFactorsVarIndex(factors); - - gttic(ordering_constraints); - // Create ordering constraints - FastMap constraintGroups; - if (updateParams_.constrainedKeys) { - constraintGroups = *updateParams_.constrainedKeys; - } else { - constraintGroups = FastMap(); - const int group = - result->observedKeys.size() < affectedFactorsVarIndex.size() ? 1 - : 0; - for (Key var : result->observedKeys) - constraintGroups.insert(std::make_pair(var, group)); - } - - // Remove unaffected keys from the constraints - for (FastMap::iterator iter = constraintGroups.begin(); - iter != constraintGroups.end(); - /*Incremented in loop ++iter*/) { - if (result->unusedKeys.exists(iter->first) || - !affectedKeysSet.exists(iter->first)) - constraintGroups.erase(iter++); - else - ++iter; - } - gttoc(ordering_constraints); - - // Generate ordering - gttic(Ordering); - Ordering ordering = Ordering::ColamdConstrained(affectedFactorsVarIndex, - constraintGroups); - gttoc(Ordering); - - ISAM2BayesTree::shared_ptr bayesTree = - ISAM2JunctionTree(GaussianEliminationTree( - factors, affectedFactorsVarIndex, ordering)) - .eliminate(params_.getEliminationFunction()) - .first; - - gttoc(reorder_and_eliminate); - - gttic(reassemble); - roots->insert(roots->end(), bayesTree->roots().begin(), - bayesTree->roots().end()); - nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); - gttoc(reassemble); - - // 4. The orphans have already been inserted during elimination - - gttoc(incremental); + recalculateIncremental(theta, variableIndex, nonlinearFactors, orphans, + relinKeys, linearFactors, affectedKeys, + &affectedKeysSet, roots, nodes, result); } // Root clique variables for detailed results From 1757198463c252f0406ee9f2c8863f974b5cf801 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 01:51:14 -0400 Subject: [PATCH 033/160] Moved recalculate back to iSAM2.cpp --- gtsam/nonlinear/ISAM2-impl.h | 308 ----------------------------------- gtsam/nonlinear/ISAM2.cpp | 294 ++++++++++++++++++++++++++++++++- gtsam/nonlinear/ISAM2.h | 21 +++ 3 files changed, 312 insertions(+), 311 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 65441da0eb..b6e5663bf4 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -512,314 +512,6 @@ struct GTSAM_EXPORT UpdateImpl { return cachedBoundary; } - - // retrieve all factors that ONLY contain the affected variables - // (note that the remaining stuff is summarized in the cached factors) - GaussianFactorGraph relinearizeAffectedFactors( - const FastList& affectedKeys, const KeySet& relinKeys, - const NonlinearFactorGraph& nonlinearFactors, - const VariableIndex& variableIndex, const Values& theta, - GaussianFactorGraph* linearFactors) const { - FactorIndexSet candidates = GetAffectedFactors(affectedKeys, variableIndex); - - gttic(affectedKeysSet); - // for fast lookup below - KeySet affectedKeysSet; - affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); - gttoc(affectedKeysSet); - - gttic(check_candidates_and_linearize); - GaussianFactorGraph linearized; - for (const FactorIndex idx : candidates) { - bool inside = true; - bool useCachedLinear = params_.cacheLinearizedFactors; - for (Key key : nonlinearFactors[idx]->keys()) { - if (affectedKeysSet.find(key) == affectedKeysSet.end()) { - inside = false; - break; - } - if (useCachedLinear && relinKeys.find(key) != relinKeys.end()) - useCachedLinear = false; - } - if (inside) { - if (useCachedLinear) { -#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS - assert((*linearFactors)[idx]); - assert((*linearFactors)[idx]->keys() == - nonlinearFactors[idx]->keys()); -#endif - linearized.push_back((*linearFactors)[idx]); - } else { - auto linearFactor = nonlinearFactors[idx]->linearize(theta); - linearized.push_back(linearFactor); - if (params_.cacheLinearizedFactors) { -#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS - assert((*linearFactors)[idx]->keys() == linearFactor->keys()); -#endif - (*linearFactors)[idx] = linearFactor; - } - } - } - } - gttoc(check_candidates_and_linearize); - - return linearized; - } - - // Do a batch step - reorder and relinearize all variables - void recalculateBatch(const Values& theta, const VariableIndex& variableIndex, - const NonlinearFactorGraph& nonlinearFactors, - GaussianFactorGraph* linearFactors, - KeySet* affectedKeysSet, ISAM2::Roots* roots, - ISAM2::Nodes* nodes, ISAM2Result* result) const { - gttic(batch); - - gttic(add_keys); - br::copy(variableIndex | br::map_keys, - std::inserter(*affectedKeysSet, affectedKeysSet->end())); - - // Removed unused keys: - VariableIndex affectedFactorsVarIndex = variableIndex; - - affectedFactorsVarIndex.removeUnusedVariables(result->unusedKeys.begin(), - result->unusedKeys.end()); - - for (const Key key : result->unusedKeys) { - affectedKeysSet->erase(key); - } - gttoc(add_keys); - - gttic(ordering); - Ordering order; - if (updateParams_.constrainedKeys) { - order = Ordering::ColamdConstrained(affectedFactorsVarIndex, - *updateParams_.constrainedKeys); - } else { - if (theta.size() > result->observedKeys.size()) { - // Only if some variables are unconstrained - FastMap constraintGroups; - for (Key var : result->observedKeys) constraintGroups[var] = 1; - order = Ordering::ColamdConstrained(affectedFactorsVarIndex, - constraintGroups); - } else { - order = Ordering::Colamd(affectedFactorsVarIndex); - } - } - gttoc(ordering); - - gttic(linearize); - GaussianFactorGraph linearized = *nonlinearFactors.linearize(theta); - if (params_.cacheLinearizedFactors) *linearFactors = linearized; - gttoc(linearize); - - gttic(eliminate); - ISAM2BayesTree::shared_ptr bayesTree = - ISAM2JunctionTree( - GaussianEliminationTree(linearized, affectedFactorsVarIndex, order)) - .eliminate(params_.getEliminationFunction()) - .first; - gttoc(eliminate); - - gttic(insert); - roots->clear(); - roots->insert(roots->end(), bayesTree->roots().begin(), - bayesTree->roots().end()); - nodes->clear(); - nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); - gttoc(insert); - - result->variablesReeliminated = affectedKeysSet->size(); - result->factorsRecalculated = nonlinearFactors.size(); - - // Reeliminated keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : theta.keys()) { - result->detail->variableStatus[key].isReeliminated = true; - } - } - } - - void recalculateIncremental(const Values& theta, - const VariableIndex& variableIndex, - const NonlinearFactorGraph& nonlinearFactors, - const ISAM2::Cliques& orphans, - const KeySet& relinKeys, - GaussianFactorGraph* linearFactors, - const FastList& affectedKeys, - KeySet* affectedKeysSet, ISAM2::Roots* roots, - ISAM2::Nodes* nodes, ISAM2Result* result) const { - gttic(incremental); - const bool debug = ISDEBUG("ISAM2 recalculate"); - - // 2. Add the new factors \Factors' into the resulting factor graph - FastList affectedAndNewKeys; - affectedAndNewKeys.insert(affectedAndNewKeys.end(), affectedKeys.begin(), - affectedKeys.end()); - affectedAndNewKeys.insert(affectedAndNewKeys.end(), - result->observedKeys.begin(), - result->observedKeys.end()); - gttic(relinearizeAffected); - GaussianFactorGraph factors = relinearizeAffectedFactors( - affectedAndNewKeys, relinKeys, nonlinearFactors, variableIndex, theta, - linearFactors); - gttoc(relinearizeAffected); - - if (debug) { - factors.print("Relinearized factors: "); - std::cout << "Affected keys: "; - for (const Key key : affectedKeys) { - std::cout << key << " "; - } - std::cout << std::endl; - } - - // Reeliminated keys for detailed results - if (params_.enableDetailedResults) { - for (Key key : affectedAndNewKeys) { - result->detail->variableStatus[key].isReeliminated = true; - } - } - - result->variablesReeliminated = affectedAndNewKeys.size(); - result->factorsRecalculated = factors.size(); - - gttic(cached); - // add the cached intermediate results from the boundary of the orphans - // ... - GaussianFactorGraph cachedBoundary = GetCachedBoundaryFactors(orphans); - if (debug) cachedBoundary.print("Boundary factors: "); - factors.push_back(cachedBoundary); - gttoc(cached); - - gttic(orphans); - // Add the orphaned subtrees - for (const auto& orphan : orphans) - factors += - boost::make_shared >(orphan); - gttoc(orphans); - - // END OF COPIED CODE - - // 3. Re-order and eliminate the factor graph into a Bayes net (Algorithm - // [alg:eliminate]), and re-assemble into a new Bayes tree (Algorithm - // [alg:BayesTree]) - - gttic(reorder_and_eliminate); - - gttic(list_to_set); - // create a partial reordering for the new and contaminated factors - // result->markedKeys are passed in: those variables will be forced to the - // end in the ordering - affectedKeysSet->insert(result->markedKeys.begin(), - result->markedKeys.end()); - affectedKeysSet->insert(affectedKeys.begin(), affectedKeys.end()); - gttoc(list_to_set); - - VariableIndex affectedFactorsVarIndex(factors); - - gttic(ordering_constraints); - // Create ordering constraints - FastMap constraintGroups; - if (updateParams_.constrainedKeys) { - constraintGroups = *updateParams_.constrainedKeys; - } else { - constraintGroups = FastMap(); - const int group = - result->observedKeys.size() < affectedFactorsVarIndex.size() ? 1 : 0; - for (Key var : result->observedKeys) - constraintGroups.insert(std::make_pair(var, group)); - } - - // Remove unaffected keys from the constraints - for (FastMap::iterator iter = constraintGroups.begin(); - iter != constraintGroups.end(); - /*Incremented in loop ++iter*/) { - if (result->unusedKeys.exists(iter->first) || - !affectedKeysSet->exists(iter->first)) - constraintGroups.erase(iter++); - else - ++iter; - } - gttoc(ordering_constraints); - - // Generate ordering - gttic(Ordering); - Ordering ordering = - Ordering::ColamdConstrained(affectedFactorsVarIndex, constraintGroups); - gttoc(Ordering); - - ISAM2BayesTree::shared_ptr bayesTree = - ISAM2JunctionTree( - GaussianEliminationTree(factors, affectedFactorsVarIndex, ordering)) - .eliminate(params_.getEliminationFunction()) - .first; - - gttoc(reorder_and_eliminate); - - gttic(reassemble); - roots->insert(roots->end(), bayesTree->roots().begin(), - bayesTree->roots().end()); - nodes->insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); - gttoc(reassemble); - - // 4. The orphans have already been inserted during elimination - } - - KeySet recalculate(const Values& theta, const VariableIndex& variableIndex, - const NonlinearFactorGraph& nonlinearFactors, - const GaussianBayesNet& affectedBayesNet, - const ISAM2::Cliques& orphans, const KeySet& relinKeys, - GaussianFactorGraph* linearFactors, ISAM2::Roots* roots, - ISAM2::Nodes* nodes, ISAM2Result* result) const { - gttic(recalculate); - LogRecalculateKeys(*result); - - // FactorGraph factors(affectedBayesNet); - // bug was here: we cannot reuse the original factors, because then the - // cached factors get messed up [all the necessary data is actually - // contained in the affectedBayesNet, including what was passed in from the - // boundaries, so this would be correct; however, in the process we also - // generate new cached_ entries that will be wrong (ie. they don't contain - // what would be passed up at a certain point if batch elimination was done, - // but that's what we need); we could choose not to update cached_ from - // here, but then the new information (and potentially different variable - // ordering) is not reflected in the cached_ values which again will be - // wrong] so instead we have to retrieve the original linearized factors AND - // add the cached factors from the boundary - - // BEGIN OF COPIED CODE - - // ordering provides all keys in conditionals, there cannot be others - // because path to root included - gttic(affectedKeys); - FastList affectedKeys; - for (const auto& conditional : affectedBayesNet) - affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), - conditional->endFrontals()); - gttoc(affectedKeys); - - KeySet affectedKeysSet; // Will return this result - - static const double kBatchThreshold = 0.65; - if (affectedKeys.size() >= theta.size() * kBatchThreshold) { - // Do a batch step - reorder and relinearize all variables - recalculateBatch(theta, variableIndex, nonlinearFactors, linearFactors, - &affectedKeysSet, roots, nodes, result); - } else { - recalculateIncremental(theta, variableIndex, nonlinearFactors, orphans, - relinKeys, linearFactors, affectedKeys, - &affectedKeysSet, roots, nodes, result); - } - - // Root clique variables for detailed results - if (params_.enableDetailedResults) { - for (const auto& root : *roots) - for (Key var : *root->conditional()) - result->detail->variableStatus[var].inRootClique = true; - } - - return affectedKeysSet; - } }; } // namespace gtsam diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index cce698be44..c1ff1ad023 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -25,7 +25,9 @@ #include #include +#include #include +#include using namespace std; @@ -56,6 +58,293 @@ bool ISAM2::equals(const ISAM2& other, double tol) const { fixedVariables_ == other.fixedVariables_; } +/* ************************************************************************* */ +void ISAM2::recalculateBatch(const ISAM2UpdateParams& updateParams, + KeySet* affectedKeysSet, ISAM2Result* result) { + gttic(recalculateBatch); + + gttic(add_keys); + br::copy(variableIndex_ | br::map_keys, + std::inserter(*affectedKeysSet, affectedKeysSet->end())); + + // Removed unused keys: + VariableIndex affectedFactorsVarIndex = variableIndex_; + + affectedFactorsVarIndex.removeUnusedVariables(result->unusedKeys.begin(), + result->unusedKeys.end()); + + for (const Key key : result->unusedKeys) { + affectedKeysSet->erase(key); + } + gttoc(add_keys); + + gttic(ordering); + Ordering order; + if (updateParams.constrainedKeys) { + order = Ordering::ColamdConstrained(affectedFactorsVarIndex, + *updateParams.constrainedKeys); + } else { + if (theta_.size() > result->observedKeys.size()) { + // Only if some variables are unconstrained + FastMap constraintGroups; + for (Key var : result->observedKeys) constraintGroups[var] = 1; + order = Ordering::ColamdConstrained(affectedFactorsVarIndex, + constraintGroups); + } else { + order = Ordering::Colamd(affectedFactorsVarIndex); + } + } + gttoc(ordering); + + gttic(linearize); + auto linearized = nonlinearFactors_.linearize(theta_); + if (params_.cacheLinearizedFactors) linearFactors_ = *linearized; + gttoc(linearize); + + gttic(eliminate); + ISAM2BayesTree::shared_ptr bayesTree = + ISAM2JunctionTree( + GaussianEliminationTree(*linearized, affectedFactorsVarIndex, order)) + .eliminate(params_.getEliminationFunction()) + .first; + gttoc(eliminate); + + gttic(insert); + roots_.clear(); + roots_.insert(roots_.end(), bayesTree->roots().begin(), + bayesTree->roots().end()); + nodes_.clear(); + nodes_.insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); + gttoc(insert); + + result->variablesReeliminated = affectedKeysSet->size(); + result->factorsRecalculated = nonlinearFactors_.size(); + + // Reeliminated keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : theta_.keys()) { + result->detail->variableStatus[key].isReeliminated = true; + } + } +} + +/* ************************************************************************* */ +GaussianFactorGraph ISAM2::relinearizeAffectedFactors( + const ISAM2UpdateParams& updateParams, const FastList& affectedKeys, + const KeySet& relinKeys) { + gttic(relinearizeAffectedFactors); + FactorIndexSet candidates = + UpdateImpl::GetAffectedFactors(affectedKeys, variableIndex_); + + gttic(affectedKeysSet); + // for fast lookup below + KeySet affectedKeysSet; + affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); + gttoc(affectedKeysSet); + + gttic(check_candidates_and_linearize); + GaussianFactorGraph linearized; + for (const FactorIndex idx : candidates) { + bool inside = true; + bool useCachedLinear = params_.cacheLinearizedFactors; + for (Key key : nonlinearFactors_[idx]->keys()) { + if (affectedKeysSet.find(key) == affectedKeysSet.end()) { + inside = false; + break; + } + if (useCachedLinear && relinKeys.find(key) != relinKeys.end()) + useCachedLinear = false; + } + if (inside) { + if (useCachedLinear) { +#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS + assert(linearFactors_[idx]); + assert(linearFactors_[idx]->keys() == nonlinearFactors_[idx]->keys()); +#endif + linearized.push_back(linearFactors_[idx]); + } else { + auto linearFactor = nonlinearFactors_[idx]->linearize(theta_); + linearized.push_back(linearFactor); + if (params_.cacheLinearizedFactors) { +#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS + assert(linearFactors_[idx]->keys() == linearFactor->keys()); +#endif + linearFactors_[idx] = linearFactor; + } + } + } + } + gttoc(check_candidates_and_linearize); + + return linearized; +} + +/* ************************************************************************* */ +void ISAM2::recalculateIncremental(const ISAM2UpdateParams& updateParams, + const KeySet& relinKeys, + const FastList& affectedKeys, + KeySet* affectedKeysSet, Cliques* orphans, + ISAM2Result* result) { + gttic(recalculateIncremental); + const bool debug = ISDEBUG("ISAM2 recalculate"); + + // 2. Add the new factors \Factors' into the resulting factor graph + FastList affectedAndNewKeys; + affectedAndNewKeys.insert(affectedAndNewKeys.end(), affectedKeys.begin(), + affectedKeys.end()); + affectedAndNewKeys.insert(affectedAndNewKeys.end(), + result->observedKeys.begin(), + result->observedKeys.end()); + GaussianFactorGraph factors = + relinearizeAffectedFactors(updateParams, affectedAndNewKeys, relinKeys); + + if (debug) { + factors.print("Relinearized factors: "); + std::cout << "Affected keys: "; + for (const Key key : affectedKeys) { + std::cout << key << " "; + } + std::cout << std::endl; + } + + // Reeliminated keys for detailed results + if (params_.enableDetailedResults) { + for (Key key : affectedAndNewKeys) { + result->detail->variableStatus[key].isReeliminated = true; + } + } + + result->variablesReeliminated = affectedAndNewKeys.size(); + result->factorsRecalculated = factors.size(); + + gttic(cached); + // Add the cached intermediate results from the boundary of the orphans... + GaussianFactorGraph cachedBoundary = + UpdateImpl::GetCachedBoundaryFactors(*orphans); + if (debug) cachedBoundary.print("Boundary factors: "); + factors.push_back(cachedBoundary); + gttoc(cached); + + gttic(orphans); + // Add the orphaned subtrees + for (const auto& orphan : *orphans) + factors += + boost::make_shared >(orphan); + gttoc(orphans); + + // 3. Re-order and eliminate the factor graph into a Bayes net (Algorithm + // [alg:eliminate]), and re-assemble into a new Bayes tree (Algorithm + // [alg:BayesTree]) + + gttic(reorder_and_eliminate); + + gttic(list_to_set); + // create a partial reordering for the new and contaminated factors + // result->markedKeys are passed in: those variables will be forced to the + // end in the ordering + affectedKeysSet->insert(result->markedKeys.begin(), result->markedKeys.end()); + affectedKeysSet->insert(affectedKeys.begin(), affectedKeys.end()); + gttoc(list_to_set); + + VariableIndex affectedFactorsVarIndex(factors); + + gttic(ordering_constraints); + // Create ordering constraints + FastMap constraintGroups; + if (updateParams.constrainedKeys) { + constraintGroups = *updateParams.constrainedKeys; + } else { + constraintGroups = FastMap(); + const int group = + result->observedKeys.size() < affectedFactorsVarIndex.size() ? 1 : 0; + for (Key var : result->observedKeys) + constraintGroups.insert(std::make_pair(var, group)); + } + + // Remove unaffected keys from the constraints + for (FastMap::iterator iter = constraintGroups.begin(); + iter != constraintGroups.end(); + /*Incremented in loop ++iter*/) { + if (result->unusedKeys.exists(iter->first) || + !affectedKeysSet->exists(iter->first)) + constraintGroups.erase(iter++); + else + ++iter; + } + gttoc(ordering_constraints); + + // Generate ordering + gttic(Ordering); + const Ordering ordering = + Ordering::ColamdConstrained(affectedFactorsVarIndex, constraintGroups); + gttoc(Ordering); + + // Do elimination + GaussianEliminationTree etree(factors, affectedFactorsVarIndex, ordering); + auto bayesTree = ISAM2JunctionTree(etree) + .eliminate(params_.getEliminationFunction()) + .first; + gttoc(reorder_and_eliminate); + + gttic(reassemble); + roots_.insert(roots_.end(), bayesTree->roots().begin(), + bayesTree->roots().end()); + nodes_.insert(bayesTree->nodes().begin(), bayesTree->nodes().end()); + gttoc(reassemble); + + // 4. The orphans have already been inserted during elimination +} + +/* ************************************************************************* */ +KeySet ISAM2::recalculate(const ISAM2UpdateParams& updateParams, + const GaussianBayesNet& affectedBayesNet, + const KeySet& relinKeys, Cliques* orphans, + ISAM2Result* result) { + gttic(recalculate); + UpdateImpl::LogRecalculateKeys(*result); + + // FactorGraph factors(affectedBayesNet); + // bug was here: we cannot reuse the original factors, because then the + // cached factors get messed up [all the necessary data is actually + // contained in the affectedBayesNet, including what was passed in from the + // boundaries, so this would be correct; however, in the process we also + // generate new cached_ entries that will be wrong (ie. they don't contain + // what would be passed up at a certain point if batch elimination was done, + // but that's what we need); we could choose not to update cached_ from + // here, but then the new information (and potentially different variable + // ordering) is not reflected in the cached_ values which again will be + // wrong] so instead we have to retrieve the original linearized factors AND + // add the cached factors from the boundary + + // ordering provides all keys in conditionals, there cannot be others + // because path to root included + gttic(affectedKeys); + FastList affectedKeys; + for (const auto& conditional : affectedBayesNet) + affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), + conditional->endFrontals()); + gttoc(affectedKeys); + + KeySet affectedKeysSet; // Will return this result + + static const double kBatchThreshold = 0.65; + if (affectedKeys.size() >= theta_.size() * kBatchThreshold) { + // Do a batch step - reorder and relinearize all variables + recalculateBatch(updateParams, &affectedKeysSet, result); + } else { + recalculateIncremental(updateParams, relinKeys, affectedKeys, + &affectedKeysSet, orphans, result); + } + + // Root clique variables for detailed results + if (params_.enableDetailedResults) { + for (const auto& root : roots_) + for (Key var : *root->conditional()) + result->detail->variableStatus[var].inRootClique = true; + } + + return affectedKeysSet; +} /* ************************************************************************* */ void ISAM2::addVariables( const Values& newTheta, @@ -188,9 +477,8 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, KeyVector(result.markedKeys.begin(), result.markedKeys.end()), affectedBayesNet, orphans); - KeySet affectedKeysSet = update.recalculate( - theta_, variableIndex_, nonlinearFactors_, affectedBayesNet, orphans, - relinKeys, &linearFactors_, &roots_, &nodes_, &result); + KeySet affectedKeysSet = recalculate(updateParams, affectedBayesNet, + relinKeys, &orphans, &result); // Update replaced keys mask (accumulates until back-substitution takes // place) deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index b0eb4693bf..8d8299d816 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -281,6 +281,27 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { /// @} protected: +// Do a batch step - reorder and relinearize all variables + void recalculateBatch(const ISAM2UpdateParams& updateParams, + KeySet* affectedKeysSet, ISAM2Result* result); + + // retrieve all factors that ONLY contain the affected variables + // (note that the remaining stuff is summarized in the cached factors) + GaussianFactorGraph relinearizeAffectedFactors( + const ISAM2UpdateParams& updateParams, const FastList& affectedKeys, + const KeySet& relinKeys); + + void recalculateIncremental(const ISAM2UpdateParams& updateParams, + const KeySet& relinKeys, + const FastList& affectedKeys, + KeySet* affectedKeysSet, Cliques* orphans, + ISAM2Result* result); + + KeySet recalculate(const ISAM2UpdateParams& updateParams, + const GaussianBayesNet& affectedBayesNet, + const KeySet& relinKeys, Cliques* orphans, + ISAM2Result* result); + /** * Add new variables to the ISAM2 system. * @param newTheta Initial values for new variables From ab8b2f5263a572a3354fb7f8e194e1c9c4a87434 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Sun, 2 Jun 2019 11:32:38 +0200 Subject: [PATCH 034/160] -march=native is not for MSVC --- cmake/GtsamBuildTypes.cmake | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cmake/GtsamBuildTypes.cmake b/cmake/GtsamBuildTypes.cmake index b807652980..0c9a34da12 100644 --- a/cmake/GtsamBuildTypes.cmake +++ b/cmake/GtsamBuildTypes.cmake @@ -90,10 +90,12 @@ if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") endif() endif() -option(GTSAM_BUILD_WITH_MARCH_NATIVE "Enable/Disable building with all instructions supported by native architecture (binary may not be portable!)" ON) -if(GTSAM_BUILD_WITH_MARCH_NATIVE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") +if (NOT MSVC) + option(GTSAM_BUILD_WITH_MARCH_NATIVE "Enable/Disable building with all instructions supported by native architecture (binary may not be portable!)" ON) + if(GTSAM_BUILD_WITH_MARCH_NATIVE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") + endif() endif() # Set up build type library postfixes From b36dc081e09a08fe4063d450e9f7de21948ec7f2 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Sun, 2 Jun 2019 11:46:43 +0200 Subject: [PATCH 035/160] Nicer grouping in IDE "folders" --- cmake/GtsamTesting.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index 15d4219e69..e4c515d1f7 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -120,6 +120,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) # Add group target if it doesn't already exist if(NOT TARGET check.${groupName}) add_custom_target(check.${groupName} COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) + set_property(TARGET check.${groupName} PROPERTY FOLDER "Unit tests") endif() # Get all script files @@ -192,7 +193,9 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) # Add executable add_executable(${target_name} "${script_srcs}" ${script_headers}) target_link_libraries(${target_name} CppUnitLite ${linkLibraries}) - + + set_property(TARGET check_${groupName}_program PROPERTY FOLDER "Unit tests") + # Only have a main function in one script - use preprocessor set(rest_script_srcs ${script_srcs}) list(REMOVE_AT rest_script_srcs 0) From 5db75623f3be8808169d3cada1a82ec26ed8733b Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 10:02:05 -0400 Subject: [PATCH 036/160] Use more direct configuration of cmake, disable unstable for gcc tests --- .travis.sh | 15 ++------------- .travis.yml | 29 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/.travis.sh b/.travis.sh index 522d823533..209b08ee07 100755 --- a/.travis.sh +++ b/.travis.sh @@ -22,19 +22,8 @@ function build_and_test () sudo update-alternatives --set gcc /usr/bin/gcc-$GCC_VERSION fi - # gcc is too slow and we have a time limit in Travis CI: selective builds. - if [ "$BUILD_EXAMPLES" == "1" ]; then - GTSAM_BUILD_EXAMPLES_ALWAYS=ON - else - GTSAM_BUILD_EXAMPLES_ALWAYS=OFF - fi - if [ "$RUN_TESTS" == "1" ]; then - GTSAM_BUILD_TESTS=ON - else - GTSAM_BUILD_TESTS=OFF - fi - cmake $SOURCE_DIR \ + -GTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS @@ -42,7 +31,7 @@ function build_and_test () make -j2 # Run tests: - if [ "$RUN_TESTS" == "1" ]; then + if [ "$GTSAM_BUILD_TESTS" == "ON" ]; then make check fi diff --git a/.travis.yml b/.travis.yml index bce6767211..857c6469fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,23 +27,24 @@ env: - MAKEFLAGS="-j 2" - CCACHE_SLOPPINESS=pch_defines,time_macros +# gcc is too slow and we have a time limit in Travis CI: selective builds. matrix: include: - compiler: gcc os: linux - env: BUILD_EXAMPLES=1 + env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF - compiler: gcc os: linux - env: RUN_TESTS=1 - - compiler: gcc - os: linux - env: BUILD_EXAMPLES=1 GCC_VERSION="8" - - compiler: clang - os: linux - env: BUILD_EXAMPLES=1 - - compiler: gcc - os: osx - env: BUILD_EXAMPLES=1 - - compiler: clang - os: osx - env: RUN_TESTS=1 + env: GTSAM_BUILD_UNSTABLE=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON # gcc too slow for all tests + # - compiler: gcc + # os: linux + # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF GCC_VERSION="8" + # - compiler: clang + # os: linux + # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF + # - compiler: gcc + # os: osx + # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF + # - compiler: clang + # os: osx + # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON From 1dedbe65ff94f9d085e44d1ed27379e9041fbf77 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 10:08:55 -0400 Subject: [PATCH 037/160] Fix typo --- .travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.sh b/.travis.sh index 209b08ee07..bb3b497ed2 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,7 +23,7 @@ function build_and_test () fi cmake $SOURCE_DIR \ - -GTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ + -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS From 2382d958251b00c6e5fb8bbc93ab100120aba864 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 11:02:37 -0400 Subject: [PATCH 038/160] Re-enabled rest of matrix --- .travis.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 857c6469fe..f1ddd240c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,15 +36,15 @@ matrix: - compiler: gcc os: linux env: GTSAM_BUILD_UNSTABLE=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON # gcc too slow for all tests - # - compiler: gcc - # os: linux - # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF GCC_VERSION="8" - # - compiler: clang - # os: linux - # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF - # - compiler: gcc - # os: osx - # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF - # - compiler: clang - # os: osx - # env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON + - compiler: gcc + os: linux + env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF GCC_VERSION="8" + - compiler: clang + os: linux + env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF + - compiler: gcc + os: osx + env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF + - compiler: clang + os: osx + env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON From b06e5f0e832de341004de7adceead13e86f058ba Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 16:43:33 -0400 Subject: [PATCH 039/160] Install ccache on OSX and add GTSAM_BUILD_WITH_CCACHE flag to cmake --- .travis.sh | 1 + .travis.yml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.travis.sh b/.travis.sh index bb3b497ed2..76d384263b 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,6 +23,7 @@ function build_and_test () fi cmake $SOURCE_DIR \ + -DGTSAM_BUILD_WITH_CCACHE=ON -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS diff --git a/.travis.yml b/.travis.yml index f1ddd240c6..402dec48cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,10 @@ addons: before_install: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi +install: + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install ccache ; fi + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export PATH="/usr/local/opt/ccache/libexec:$PATH" ; fi + script: - bash .travis.sh From ea63adf20d6b7f3683e491d43460df7c85880062 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 16:48:33 -0400 Subject: [PATCH 040/160] Another typo :-( --- .travis.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.sh b/.travis.sh index 76d384263b..f7a9fef426 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,7 +23,7 @@ function build_and_test () fi cmake $SOURCE_DIR \ - -DGTSAM_BUILD_WITH_CCACHE=ON + -DGTSAM_BUILD_WITH_CCACHE=ON \ -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS From a1daa163be207d5c3e56f30c2e717dc331f08e6f Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 18:12:08 -0400 Subject: [PATCH 041/160] Add ccache stats --- .travis.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.sh b/.travis.sh index f7a9fef426..434187e4f1 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,7 +23,6 @@ function build_and_test () fi cmake $SOURCE_DIR \ - -DGTSAM_BUILD_WITH_CCACHE=ON \ -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS @@ -36,6 +35,9 @@ function build_and_test () make check fi + # Print ccache stats + ccache -s + cd $SOURCE_DIR } From 1e663a3a79e4c5c57db2c8e4e93a8f9c59f5b07f Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 22:55:57 -0400 Subject: [PATCH 042/160] Removed brew update and gcc build on mac Hoping that the latter will fix cache issue on Mac. And who uses gcc on Mac anyway? --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 402dec48cd..a7032638a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,8 @@ addons: - libpython-dev python-numpy - libboost-all-dev -before_install: - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi +# before_install: +# - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi install: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install ccache ; fi @@ -46,9 +46,9 @@ matrix: - compiler: clang os: linux env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF - - compiler: gcc - os: osx - env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF +# - compiler: gcc +# os: osx +# env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF - compiler: clang os: osx env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON From 0d731e9b0c11d90c4669dfbab2f7f03077e75cd8 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 23:58:46 -0400 Subject: [PATCH 043/160] Disable brew update --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a7032638a7..4b44871039 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ addons: # - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew update ; fi install: - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then brew install ccache ; fi + - if [ "$TRAVIS_OS_NAME" == "osx" ]; then HOMEBREW_NO_AUTO_UPDATE=1 brew install ccache ; fi - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export PATH="/usr/local/opt/ccache/libexec:$PATH" ; fi script: From 3b51bae5c1a4d4ff9a97f012bd9960f38450659a Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 16:36:36 -0400 Subject: [PATCH 044/160] made markedKeys explicit --- gtsam/nonlinear/ISAM2-impl.h | 53 ++++++++++++++++++++---------------- gtsam/nonlinear/ISAM2.cpp | 22 ++++++++------- gtsam/nonlinear/ISAM2.h | 2 +- 3 files changed, 42 insertions(+), 35 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index b6e5663bf4..7e17ec7853 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -140,14 +140,15 @@ struct GTSAM_EXPORT UpdateImpl { NonlinearFactorGraph* nonlinearFactors, GaussianFactorGraph* linearFactors, VariableIndex* variableIndex, - ISAM2Result* result) const { + FactorIndices* newFactorsIndices, + KeySet* keysWithRemovedFactors) const { gttic(pushBackFactors); // Perform the first part of the bookkeeping updates for adding new factors. // Adds them to the complete list of nonlinear factors, and populates the // list of new factor indices, both optionally finding and reusing empty // factor slots. - result->newFactorsIndices = nonlinearFactors->add_factors( + *newFactorsIndices = nonlinearFactors->add_factors( newFactors, params_.findUnusedFactorSlots); // Remove the removed factors @@ -164,37 +165,41 @@ struct GTSAM_EXPORT UpdateImpl { variableIndex->remove(updateParams_.removeFactorIndices.begin(), updateParams_.removeFactorIndices.end(), removedFactors); - result->keysWithRemovedFactors = removedFactors.keys(); + *keysWithRemovedFactors = removedFactors.keys(); + } - // Compute unused keys and indices - // Get keys from removed factors and new factors, and compute unused keys, - // i.e., keys that are empty now and do not appear in the new factors. + // Get keys from removed factors and new factors, and compute unused keys, + // i.e., keys that are empty now and do not appear in the new factors. + void computeUnusedKeys(const NonlinearFactorGraph& newFactors, + const VariableIndex& variableIndex, + const KeySet& keysWithRemovedFactors, + KeySet* unusedKeys) const { + gttic(computeUnusedKeys); KeySet removedAndEmpty; - for (Key key : result->keysWithRemovedFactors) { - if (variableIndex->empty(key)) + for (Key key : keysWithRemovedFactors) { + if (variableIndex.empty(key)) removedAndEmpty.insert(removedAndEmpty.end(), key); } KeySet newFactorSymbKeys = newFactors.keys(); - std::set_difference( - removedAndEmpty.begin(), removedAndEmpty.end(), - newFactorSymbKeys.begin(), newFactorSymbKeys.end(), - std::inserter(result->unusedKeys, result->unusedKeys.end())); + std::set_difference(removedAndEmpty.begin(), removedAndEmpty.end(), + newFactorSymbKeys.begin(), newFactorSymbKeys.end(), + std::inserter(*unusedKeys, unusedKeys->end())); } // Mark linear update void gatherInvolvedKeys(const NonlinearFactorGraph& newFactors, const NonlinearFactorGraph& nonlinearFactors, - ISAM2Result* result) const { + KeySet* markedKeys, ISAM2Result* result) const { gttic(gatherInvolvedKeys); - result->markedKeys = newFactors.keys(); // Get keys from new factors + *markedKeys = newFactors.keys(); // Get keys from new factors // Also mark keys involved in removed factors - result->markedKeys.insert(result->keysWithRemovedFactors.begin(), - result->keysWithRemovedFactors.end()); + markedKeys->insert(result->keysWithRemovedFactors.begin(), + result->keysWithRemovedFactors.end()); // Also mark any provided extra re-eliminate keys if (updateParams_.extraReelimKeys) { for (Key key : *updateParams_.extraReelimKeys) { - result->markedKeys.insert(key); + markedKeys->insert(key); } } @@ -204,18 +209,18 @@ struct GTSAM_EXPORT UpdateImpl { for (const auto& factorAddedKeys : *updateParams_.newAffectedKeys) { const auto factorIdx = factorAddedKeys.first; const auto& affectedKeys = nonlinearFactors.at(factorIdx)->keys(); - result->markedKeys.insert(affectedKeys.begin(), affectedKeys.end()); + markedKeys->insert(affectedKeys.begin(), affectedKeys.end()); } } // Observed keys for detailed results if (params_.enableDetailedResults) { - for (Key key : result->markedKeys) { + for (Key key : *markedKeys) { result->detail->variableStatus[key].isObserved = true; } } - for (Key index : result->markedKeys) { + for (Key index : *markedKeys) { // Only add if not unused if (result->unusedKeys.find(index) == result->unusedKeys.end()) // Make a copy of these, as we'll soon add to them @@ -347,7 +352,7 @@ struct GTSAM_EXPORT UpdateImpl { // Mark keys in \Delta above threshold \beta: KeySet gatherRelinearizeKeys(const ISAM2::Roots& roots, const VectorValues& delta, - const KeySet& fixedVariables, + const KeySet& fixedVariables, KeySet* markedKeys, ISAM2Result* result) const { gttic(gatherRelinearizeKeys); // J=\{\Delta_{j}\in\Delta|\Delta_{j}\geq\beta\}. @@ -378,18 +383,18 @@ struct GTSAM_EXPORT UpdateImpl { } // Add the variables being relinearized to the marked keys - result->markedKeys.insert(relinKeys.begin(), relinKeys.end()); + markedKeys->insert(relinKeys.begin(), relinKeys.end()); return relinKeys; } // Mark all cliques that involve marked variables \Theta_{J} and all // their ancestors. void fluidFindAll(const ISAM2::Roots& roots, const KeySet& relinKeys, - ISAM2Result* result) const { + KeySet* markedKeys, ISAM2Result* result) const { gttic(fluidFindAll); for (const auto& root : roots) // add other cliques that have the marked ones in the separator - root->findAll(relinKeys, &result->markedKeys); + root->findAll(relinKeys, markedKeys); // Relinearization-involved keys for detailed results if (params_.enableDetailedResults) { diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index c1ff1ad023..ce73706a08 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -350,11 +350,9 @@ void ISAM2::addVariables( const Values& newTheta, ISAM2Result::DetailedResults::StatusMap* variableStatus) { gttic(addNewVariables); - // \Theta:=\Theta\cup\Theta_{new}. - const bool debug = ISDEBUG("ISAM2 AddVariables"); theta_.insert(newTheta); - if (debug) newTheta.print("The new variables are: "); + if (ISDEBUG("ISAM2 AddVariables")) newTheta.print("The new variables are: "); // Add zeros into the VectorValues delta_.insert(newTheta.zeroVectors()); deltaNewton_.insert(newTheta.zeroVectors()); @@ -425,9 +423,13 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, // 1. Add any new factors \Factors:=\Factors\cup\Factors'. update.pushBackFactors(newFactors, &nonlinearFactors_, &linearFactors_, - &variableIndex_, &result); + &variableIndex_, &result.newFactorsIndices, + &result.keysWithRemovedFactors); + update.computeUnusedKeys(newFactors, variableIndex_, + result.keysWithRemovedFactors, &result.unusedKeys); // 2. Initialize any new variables \Theta_{new} and add + // \Theta:=\Theta\cup\Theta_{new}. addVariables(newTheta, result.detail ? &result.detail->variableStatus : 0); gttic(evaluate_error_before); @@ -436,19 +438,20 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, gttoc(evaluate_error_before); // 3. Mark linear update - update.gatherInvolvedKeys(newFactors, nonlinearFactors_, &result); + update.gatherInvolvedKeys(newFactors, nonlinearFactors_, &result.markedKeys, + &result); // Check relinearization if we're at the nth step, or we are using a looser // loop relinerization threshold. KeySet relinKeys; if (relinearizeThisStep) { // 4. Mark keys in \Delta above threshold \beta: - relinKeys = - update.gatherRelinearizeKeys(roots_, delta_, fixedVariables_, &result); + relinKeys = update.gatherRelinearizeKeys(roots_, delta_, fixedVariables_, + &result.markedKeys, &result); if (!relinKeys.empty()) { // 5. Mark all cliques that involve marked variables \Theta_{J} and all // their ancestors. - update.fluidFindAll(roots_, relinKeys, &result); + update.fluidFindAll(roots_, relinKeys, &result.markedKeys, &result); // 6. Update linearization point for marked variables: // \Theta_{J}:=\Theta_{J}+\Delta_{J}. UpdateImpl::ExpmapMasked(delta_, relinKeys, &theta_); @@ -479,8 +482,7 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, KeySet affectedKeysSet = recalculate(updateParams, affectedBayesNet, relinKeys, &orphans, &result); - // Update replaced keys mask (accumulates until back-substitution takes - // place) + // Update replaced keys mask (accumulates until back-substitution happens) deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); } diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index 8d8299d816..e2b9a18e8c 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -281,7 +281,7 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { /// @} protected: -// Do a batch step - reorder and relinearize all variables + // Do a batch step - reorder and relinearize all variables void recalculateBatch(const ISAM2UpdateParams& updateParams, KeySet* affectedKeysSet, ISAM2Result* result); From a9e955d56e08a7fb21255132f21d4f560c8e74d8 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 12:33:23 -0400 Subject: [PATCH 045/160] using rather than typedef --- gtsam/nonlinear/ISAM2.h | 10 +++++----- gtsam/nonlinear/ISAM2Result.h | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index e2b9a18e8c..19199b22d7 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -97,11 +97,11 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { ///< periodic relinearization public: - typedef ISAM2 This; ///< This class - typedef BayesTree Base; ///< The BayesTree base class - typedef Base::Clique Clique; ///< A clique - typedef Base::sharedClique sharedClique; ///< Shared pointer to a clique - typedef Base::Cliques Cliques; ///< List of Clique typedef from base class + using This = ISAM2; ///< This class + using Base = BayesTree; ///< The BayesTree base class + using Clique = Base::Clique; ///< A clique + using sharedClique = Base::sharedClique; ///< Shared pointer to a clique + using Cliques = Base::Cliques; ///< List of Cliques /** Create an empty ISAM2 instance */ explicit ISAM2(const ISAM2Params& params); diff --git a/gtsam/nonlinear/ISAM2Result.h b/gtsam/nonlinear/ISAM2Result.h index 17968453d8..1287b0e1a2 100644 --- a/gtsam/nonlinear/ISAM2Result.h +++ b/gtsam/nonlinear/ISAM2Result.h @@ -110,7 +110,8 @@ struct GTSAM_EXPORT ISAM2Result { /** All keys that were marked during the update process. */ KeySet markedKeys; - /** A struct holding detailed results, which must be enabled with + /** + * A struct holding detailed results, which must be enabled with * ISAM2Params::enableDetailedResults. */ struct DetailedResults { @@ -146,7 +147,7 @@ struct GTSAM_EXPORT ISAM2Result { inRootClique(false) {} }; - typedef FastMap StatusMap; + using StatusMap = FastMap; /// The status of each variable during this update, see VariableStatus. StatusMap variableStatus; From 62233395b2d9337b1a06e7f94c23e0e19c3cc97c Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 13:39:16 -0400 Subject: [PATCH 046/160] made gatherInvolvedKeys more explicit --- gtsam/nonlinear/ISAM2-impl.h | 39 +++++++++++++++++++++--------------- gtsam/nonlinear/ISAM2.cpp | 5 +++-- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 7e17ec7853..0fb70558ef 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -129,7 +129,6 @@ struct GTSAM_EXPORT UpdateImpl { isam2.print("ISAM2: "); } - // Add the new factor indices to the result struct if (debug || verbose) { newFactors.print("The new factors are: "); } @@ -189,12 +188,13 @@ struct GTSAM_EXPORT UpdateImpl { // Mark linear update void gatherInvolvedKeys(const NonlinearFactorGraph& newFactors, const NonlinearFactorGraph& nonlinearFactors, - KeySet* markedKeys, ISAM2Result* result) const { + const KeySet& keysWithRemovedFactors, + KeySet* markedKeys) const { gttic(gatherInvolvedKeys); *markedKeys = newFactors.keys(); // Get keys from new factors // Also mark keys involved in removed factors - markedKeys->insert(result->keysWithRemovedFactors.begin(), - result->keysWithRemovedFactors.end()); + markedKeys->insert(keysWithRemovedFactors.begin(), + keysWithRemovedFactors.end()); // Also mark any provided extra re-eliminate keys if (updateParams_.extraReelimKeys) { @@ -212,15 +212,19 @@ struct GTSAM_EXPORT UpdateImpl { markedKeys->insert(affectedKeys.begin(), affectedKeys.end()); } } + } + // Update detail, unused, and observed keys from markedKeys + void updateKeys(const KeySet& markedKeys, ISAM2Result* result) const { + gttic(updateKeys); // Observed keys for detailed results if (params_.enableDetailedResults) { - for (Key key : *markedKeys) { + for (Key key : markedKeys) { result->detail->variableStatus[key].isObserved = true; } } - for (Key index : *markedKeys) { + for (Key index : markedKeys) { // Only add if not unused if (result->unusedKeys.find(index) == result->unusedKeys.end()) // Make a copy of these, as we'll soon add to them @@ -285,13 +289,14 @@ struct GTSAM_EXPORT UpdateImpl { /** * Find the set of variables to be relinearized according to - * relinearizeThreshold. This check is performed recursively, starting at the - * top of the tree. Once a variable in the tree does not need to be + * relinearizeThreshold. This check is performed recursively, starting at + * the top of the tree. Once a variable in the tree does not need to be * relinearized, no further checks in that branch are performed. This is an - * approximation of the Full version, designed to save time at the expense of - * accuracy. + * approximation of the Full version, designed to save time at the expense + * of accuracy. * @param delta The linear delta to check against the threshold - * @param keyFormatter Formatter for printing nonlinear keys during debugging + * @param keyFormatter Formatter for printing nonlinear keys during + * debugging * @return The set of variable indices in delta whose magnitude is greater * than or equal to relinearizeThreshold */ @@ -313,10 +318,12 @@ struct GTSAM_EXPORT UpdateImpl { /** * Find the set of variables to be relinearized according to - * relinearizeThreshold. Any variables in the VectorValues delta whose vector - * magnitude is greater than or equal to relinearizeThreshold are returned. + * relinearizeThreshold. Any variables in the VectorValues delta whose + * vector magnitude is greater than or equal to relinearizeThreshold are + * returned. * @param delta The linear delta to check against the threshold - * @param keyFormatter Formatter for printing nonlinear keys during debugging + * @param keyFormatter Formatter for printing nonlinear keys during + * debugging * @return The set of variable indices in delta whose magnitude is greater * than or equal to relinearizeThreshold */ @@ -504,8 +511,8 @@ struct GTSAM_EXPORT UpdateImpl { return indices; } - // find intermediate (linearized) factors from cache that are passed into the - // affected area + // find intermediate (linearized) factors from cache that are passed into + // the affected area static GaussianFactorGraph GetCachedBoundaryFactors( const ISAM2::Cliques& orphans) { GaussianFactorGraph cachedBoundary; diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index ce73706a08..1d1447e920 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -438,8 +438,9 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, gttoc(evaluate_error_before); // 3. Mark linear update - update.gatherInvolvedKeys(newFactors, nonlinearFactors_, &result.markedKeys, - &result); + update.gatherInvolvedKeys(newFactors, nonlinearFactors_, + result.keysWithRemovedFactors, &result.markedKeys); + update.updateKeys(result.markedKeys, &result); // Check relinearization if we're at the nth step, or we are using a looser // loop relinerization threshold. From 3ab9a1e3cc3e7382d987a79b58a79ea2b4b0284d Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 13:53:37 -0400 Subject: [PATCH 047/160] Made detail handling more explicit in update --- gtsam/nonlinear/ISAM2-impl.h | 36 +++++++++++++++++++---------------- gtsam/nonlinear/ISAM2.cpp | 19 +++++++++--------- gtsam/nonlinear/ISAM2.h | 5 ++--- gtsam/nonlinear/ISAM2Params.h | 6 +++--- tests/testGaussianISAM2.cpp | 6 ++++-- 5 files changed, 39 insertions(+), 33 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 0fb70558ef..f668e12c70 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -218,7 +218,7 @@ struct GTSAM_EXPORT UpdateImpl { void updateKeys(const KeySet& markedKeys, ISAM2Result* result) const { gttic(updateKeys); // Observed keys for detailed results - if (params_.enableDetailedResults) { + if (result->detail && params_.enableDetailedResults) { for (Key key : markedKeys) { result->detail->variableStatus[key].isObserved = true; } @@ -359,8 +359,8 @@ struct GTSAM_EXPORT UpdateImpl { // Mark keys in \Delta above threshold \beta: KeySet gatherRelinearizeKeys(const ISAM2::Roots& roots, const VectorValues& delta, - const KeySet& fixedVariables, KeySet* markedKeys, - ISAM2Result* result) const { + const KeySet& fixedVariables, + KeySet* markedKeys) const { gttic(gatherRelinearizeKeys); // J=\{\Delta_{j}\in\Delta|\Delta_{j}\geq\beta\}. KeySet relinKeys = @@ -381,37 +381,41 @@ struct GTSAM_EXPORT UpdateImpl { } } - // Record above relinerization threshold keys in detailed results - if (params_.enableDetailedResults) { - for (Key key : relinKeys) { - result->detail->variableStatus[key].isAboveRelinThreshold = true; - result->detail->variableStatus[key].isRelinearized = true; - } - } - // Add the variables being relinearized to the marked keys markedKeys->insert(relinKeys.begin(), relinKeys.end()); return relinKeys; } + // Record relinerization threshold keys in detailed results + void recordRelinearizeDetail(const KeySet& relinKeys, + ISAM2Result::DetailedResults* detail) const { + if (detail && params_.enableDetailedResults) { + for (Key key : relinKeys) { + detail->variableStatus[key].isAboveRelinThreshold = true; + detail->variableStatus[key].isRelinearized = true; + } + } + } + // Mark all cliques that involve marked variables \Theta_{J} and all // their ancestors. void fluidFindAll(const ISAM2::Roots& roots, const KeySet& relinKeys, - KeySet* markedKeys, ISAM2Result* result) const { + KeySet* markedKeys, + ISAM2Result::DetailedResults* detail) const { gttic(fluidFindAll); for (const auto& root : roots) // add other cliques that have the marked ones in the separator root->findAll(relinKeys, markedKeys); // Relinearization-involved keys for detailed results - if (params_.enableDetailedResults) { + if (detail && params_.enableDetailedResults) { KeySet involvedRelinKeys; for (const auto& root : roots) root->findAll(relinKeys, &involvedRelinKeys); for (Key key : involvedRelinKeys) { - if (!result->detail->variableStatus[key].isAboveRelinThreshold) { - result->detail->variableStatus[key].isRelinearizeInvolved = true; - result->detail->variableStatus[key].isRelinearized = true; + if (!detail->variableStatus[key].isAboveRelinThreshold) { + detail->variableStatus[key].isRelinearizeInvolved = true; + detail->variableStatus[key].isRelinearized = true; } } } diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index 1d1447e920..4e4a507bf2 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -337,7 +337,7 @@ KeySet ISAM2::recalculate(const ISAM2UpdateParams& updateParams, } // Root clique variables for detailed results - if (params_.enableDetailedResults) { + if (result->detail && params_.enableDetailedResults) { for (const auto& root : roots_) for (Key var : *root->conditional()) result->detail->variableStatus[var].inRootClique = true; @@ -346,9 +346,8 @@ KeySet ISAM2::recalculate(const ISAM2UpdateParams& updateParams, return affectedKeysSet; } /* ************************************************************************* */ -void ISAM2::addVariables( - const Values& newTheta, - ISAM2Result::DetailedResults::StatusMap* variableStatus) { +void ISAM2::addVariables(const Values& newTheta, + ISAM2Result::DetailedResults* detail) { gttic(addNewVariables); theta_.insert(newTheta); @@ -359,9 +358,9 @@ void ISAM2::addVariables( RgProd_.insert(newTheta.zeroVectors()); // New keys for detailed results - if (variableStatus && params_.enableDetailedResults) { + if (detail && params_.enableDetailedResults) { for (Key key : newTheta.keys()) { - (*variableStatus)[key].isNew = true; + detail->variableStatus[key].isNew = true; } } } @@ -430,7 +429,7 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, // 2. Initialize any new variables \Theta_{new} and add // \Theta:=\Theta\cup\Theta_{new}. - addVariables(newTheta, result.detail ? &result.detail->variableStatus : 0); + addVariables(newTheta, result.detail.get_ptr()); gttic(evaluate_error_before); if (params_.evaluateNonlinearError) @@ -448,11 +447,13 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, if (relinearizeThisStep) { // 4. Mark keys in \Delta above threshold \beta: relinKeys = update.gatherRelinearizeKeys(roots_, delta_, fixedVariables_, - &result.markedKeys, &result); + &result.markedKeys); + update.recordRelinearizeDetail(relinKeys, result.detail.get_ptr()); if (!relinKeys.empty()) { // 5. Mark all cliques that involve marked variables \Theta_{J} and all // their ancestors. - update.fluidFindAll(roots_, relinKeys, &result.markedKeys, &result); + update.fluidFindAll(roots_, relinKeys, &result.markedKeys, + result.detail.get_ptr()); // 6. Update linearization point for marked variables: // \Theta_{J}:=\Theta_{J}+\Delta_{J}. UpdateImpl::ExpmapMasked(delta_, relinKeys, &theta_); diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index 19199b22d7..bd14770728 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -307,9 +307,8 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { * @param newTheta Initial values for new variables * @param variableStatus optional detailed result structure */ - void addVariables( - const Values& newTheta, - ISAM2Result::DetailedResults::StatusMap* variableStatus = 0); + void addVariables(const Values& newTheta, + ISAM2Result::DetailedResults* detail = 0); /** * Remove variables from the ISAM2 system. diff --git a/gtsam/nonlinear/ISAM2Params.h b/gtsam/nonlinear/ISAM2Params.h index afddd1f8e9..5cf962e43d 100644 --- a/gtsam/nonlinear/ISAM2Params.h +++ b/gtsam/nonlinear/ISAM2Params.h @@ -234,8 +234,8 @@ struct GTSAM_EXPORT ISAM2Params { Factorization _factorization = ISAM2Params::CHOLESKY, bool _cacheLinearizedFactors = true, const KeyFormatter& _keyFormatter = - DefaultKeyFormatter ///< see ISAM2::Params::keyFormatter - ) + DefaultKeyFormatter, ///< see ISAM2::Params::keyFormatter, + bool _enableDetailedResults = false) : optimizationParams(_optimizationParams), relinearizeThreshold(_relinearizeThreshold), relinearizeSkip(_relinearizeSkip), @@ -244,7 +244,7 @@ struct GTSAM_EXPORT ISAM2Params { factorization(_factorization), cacheLinearizedFactors(_cacheLinearizedFactors), keyFormatter(_keyFormatter), - enableDetailedResults(false), + enableDetailedResults(_enableDetailedResults), enablePartialRelinearizationCheck(false), findUnusedFactorSlots(false) {} diff --git a/tests/testGaussianISAM2.cpp b/tests/testGaussianISAM2.cpp index 871e4bc185..68d10bb7bb 100644 --- a/tests/testGaussianISAM2.cpp +++ b/tests/testGaussianISAM2.cpp @@ -47,9 +47,11 @@ SharedDiagonal brNoise = noiseModel::Diagonal::Sigmas((Vector(2) << M_PI/100.0, ISAM2 createSlamlikeISAM2( boost::optional init_values = boost::none, boost::optional full_graph = boost::none, - const ISAM2Params& params = ISAM2Params(ISAM2GaussNewtonParams(0.001), 0.0, 0, false, true), + const ISAM2Params& params = ISAM2Params(ISAM2GaussNewtonParams(0.001), 0.0, + 0, false, true, + ISAM2Params::CHOLESKY, true, + DefaultKeyFormatter, true), size_t maxPoses = 10) { - // These variables will be reused and accumulate factors and values ISAM2 isam(params); Values fullinit; From 6f7e92afdc1dfa5b85a73b9861dfec614a956b5f Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 14:12:31 -0400 Subject: [PATCH 048/160] Make error calculation concise --- gtsam/nonlinear/ISAM2-impl.h | 7 +++++++ gtsam/nonlinear/ISAM2.cpp | 12 ++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index f668e12c70..1dba44713b 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -185,6 +185,13 @@ struct GTSAM_EXPORT UpdateImpl { std::inserter(*unusedKeys, unusedKeys->end())); } + // Calculate nonlinear error + void error(const NonlinearFactorGraph& nonlinearFactors, + const Values& estimate, boost::optional* error) const { + gttic(error); + error->reset(nonlinearFactors.error(estimate)); + } + // Mark linear update void gatherInvolvedKeys(const NonlinearFactorGraph& newFactors, const NonlinearFactorGraph& nonlinearFactors, diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index 4e4a507bf2..9394b32017 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -367,6 +367,8 @@ void ISAM2::addVariables(const Values& newTheta, /* ************************************************************************* */ void ISAM2::removeVariables(const KeySet& unusedKeys) { + gttic(removeVariables); + variableIndex_.removeUnusedVariables(unusedKeys.begin(), unusedKeys.end()); for (Key key : unusedKeys) { delta_.erase(key); @@ -431,10 +433,8 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, // \Theta:=\Theta\cup\Theta_{new}. addVariables(newTheta, result.detail.get_ptr()); - gttic(evaluate_error_before); if (params_.evaluateNonlinearError) - result.errorBefore.reset(nonlinearFactors_.error(calculateEstimate())); - gttoc(evaluate_error_before); + update.error(nonlinearFactors_, calculateEstimate(), &result.errorBefore); // 3. Mark linear update update.gatherInvolvedKeys(newFactors, nonlinearFactors_, @@ -490,16 +490,12 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, // Update data structures to remove unused keys if (!result.unusedKeys.empty()) { - gttic(remove_variables); removeVariables(result.unusedKeys); - gttoc(remove_variables); } result.cliques = this->nodes().size(); - gttic(evaluate_error_after); if (params_.evaluateNonlinearError) - result.errorAfter.reset(nonlinearFactors_.error(calculateEstimate())); - gttoc(evaluate_error_after); + update.error(nonlinearFactors_, calculateEstimate(), &result.errorAfter); return result; } From 18553feb036fa38b1c7a6442f341e87396491c42 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 14:30:00 -0400 Subject: [PATCH 049/160] Recalculate now removes top --- gtsam/nonlinear/ISAM2-impl.h | 4 +- gtsam/nonlinear/ISAM2.cpp | 113 +++++++++++++++++------------------ gtsam/nonlinear/ISAM2.h | 10 ++-- 3 files changed, 61 insertions(+), 66 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 1dba44713b..6acdd28d52 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -187,9 +187,9 @@ struct GTSAM_EXPORT UpdateImpl { // Calculate nonlinear error void error(const NonlinearFactorGraph& nonlinearFactors, - const Values& estimate, boost::optional* error) const { + const Values& estimate, boost::optional* result) const { gttic(error); - error->reset(nonlinearFactors.error(estimate)); + result->reset(nonlinearFactors.error(estimate)); } // Mark linear update diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index 9394b32017..8b882f1edb 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -296,54 +296,64 @@ void ISAM2::recalculateIncremental(const ISAM2UpdateParams& updateParams, } /* ************************************************************************* */ -KeySet ISAM2::recalculate(const ISAM2UpdateParams& updateParams, - const GaussianBayesNet& affectedBayesNet, - const KeySet& relinKeys, Cliques* orphans, - ISAM2Result* result) { +void ISAM2::recalculate(const ISAM2UpdateParams& updateParams, + const KeySet& relinKeys, ISAM2Result* result) { gttic(recalculate); UpdateImpl::LogRecalculateKeys(*result); - // FactorGraph factors(affectedBayesNet); - // bug was here: we cannot reuse the original factors, because then the - // cached factors get messed up [all the necessary data is actually - // contained in the affectedBayesNet, including what was passed in from the - // boundaries, so this would be correct; however, in the process we also - // generate new cached_ entries that will be wrong (ie. they don't contain - // what would be passed up at a certain point if batch elimination was done, - // but that's what we need); we could choose not to update cached_ from - // here, but then the new information (and potentially different variable - // ordering) is not reflected in the cached_ values which again will be - // wrong] so instead we have to retrieve the original linearized factors AND - // add the cached factors from the boundary - - // ordering provides all keys in conditionals, there cannot be others - // because path to root included - gttic(affectedKeys); - FastList affectedKeys; - for (const auto& conditional : affectedBayesNet) - affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), - conditional->endFrontals()); - gttoc(affectedKeys); - - KeySet affectedKeysSet; // Will return this result - - static const double kBatchThreshold = 0.65; - if (affectedKeys.size() >= theta_.size() * kBatchThreshold) { - // Do a batch step - reorder and relinearize all variables - recalculateBatch(updateParams, &affectedKeysSet, result); - } else { - recalculateIncremental(updateParams, relinKeys, affectedKeys, - &affectedKeysSet, orphans, result); - } + if (!result->markedKeys.empty() || !result->observedKeys.empty()) { + // Remove top of Bayes tree and convert to a factor graph: + // (a) For each affected variable, remove the corresponding clique and all + // parents up to the root. (b) Store orphaned sub-trees \BayesTree_{O} of + // removed cliques. + GaussianBayesNet affectedBayesNet; + Cliques orphans; + this->removeTop( + KeyVector(result->markedKeys.begin(), result->markedKeys.end()), + &affectedBayesNet, &orphans); + + // FactorGraph factors(affectedBayesNet); + // bug was here: we cannot reuse the original factors, because then the + // cached factors get messed up [all the necessary data is actually + // contained in the affectedBayesNet, including what was passed in from the + // boundaries, so this would be correct; however, in the process we also + // generate new cached_ entries that will be wrong (ie. they don't contain + // what would be passed up at a certain point if batch elimination was done, + // but that's what we need); we could choose not to update cached_ from + // here, but then the new information (and potentially different variable + // ordering) is not reflected in the cached_ values which again will be + // wrong] so instead we have to retrieve the original linearized factors AND + // add the cached factors from the boundary + + // ordering provides all keys in conditionals, there cannot be others + // because path to root included + gttic(affectedKeys); + FastList affectedKeys; + for (const auto& conditional : affectedBayesNet) + affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), + conditional->endFrontals()); + gttoc(affectedKeys); + + KeySet affectedKeysSet; + static const double kBatchThreshold = 0.65; + if (affectedKeys.size() >= theta_.size() * kBatchThreshold) { + // Do a batch step - reorder and relinearize all variables + recalculateBatch(updateParams, &affectedKeysSet, result); + } else { + recalculateIncremental(updateParams, relinKeys, affectedKeys, + &affectedKeysSet, &orphans, result); + } - // Root clique variables for detailed results - if (result->detail && params_.enableDetailedResults) { - for (const auto& root : roots_) - for (Key var : *root->conditional()) - result->detail->variableStatus[var].inRootClique = true; - } + // Root clique variables for detailed results + if (result->detail && params_.enableDetailedResults) { + for (const auto& root : roots_) + for (Key var : *root->conditional()) + result->detail->variableStatus[var].inRootClique = true; + } - return affectedKeysSet; + // Update replaced keys mask (accumulates until back-substitution happens) + deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); + } } /* ************************************************************************* */ void ISAM2::addVariables(const Values& newTheta, @@ -471,22 +481,7 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, &variableIndex_); // 8. Redo top of Bayes tree - if (!result.markedKeys.empty() || !result.observedKeys.empty()) { - // Remove top of Bayes tree and convert to a factor graph: - // (a) For each affected variable, remove the corresponding clique and all - // parents up to the root. (b) Store orphaned sub-trees \BayesTree_{O} of - // removed cliques. - GaussianBayesNet affectedBayesNet; - Cliques orphans; - this->removeTop( - KeyVector(result.markedKeys.begin(), result.markedKeys.end()), - affectedBayesNet, orphans); - - KeySet affectedKeysSet = recalculate(updateParams, affectedBayesNet, - relinKeys, &orphans, &result); - // Update replaced keys mask (accumulates until back-substitution happens) - deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); - } + recalculate(updateParams, relinKeys, &result); // Update data structures to remove unused keys if (!result.unusedKeys.empty()) { diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index bd14770728..ec1b71a723 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -296,11 +296,11 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { const FastList& affectedKeys, KeySet* affectedKeysSet, Cliques* orphans, ISAM2Result* result); - - KeySet recalculate(const ISAM2UpdateParams& updateParams, - const GaussianBayesNet& affectedBayesNet, - const KeySet& relinKeys, Cliques* orphans, - ISAM2Result* result); + /** + * Remove marked top and either recalculate in batch or incrementally. + */ + void recalculate(const ISAM2UpdateParams& updateParams, + const KeySet& relinKeys, ISAM2Result* result); /** * Add new variables to the ISAM2 system. From a30855181a59ec7df3124f7a3a97ef238f5620fa Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 15:24:44 -0400 Subject: [PATCH 050/160] Deprecated non-const reference arguments in BayesTree and iSAM.h --- gtsam/inference/BayesTree-inst.h | 100 ++++++++++++++--------------- gtsam/inference/BayesTree.h | 29 +++++++-- gtsam/inference/FactorGraph.h | 2 +- gtsam/inference/ISAM-inst.h | 10 +-- gtsam/inference/ISAM.h | 107 +++++++++++++++++-------------- gtsam/nonlinear/ISAM2.cpp | 2 +- 6 files changed, 137 insertions(+), 113 deletions(-) diff --git a/gtsam/inference/BayesTree-inst.h b/gtsam/inference/BayesTree-inst.h index 9935776a5e..4df234004f 100644 --- a/gtsam/inference/BayesTree-inst.h +++ b/gtsam/inference/BayesTree-inst.h @@ -36,19 +36,20 @@ namespace gtsam { /* ************************************************************************* */ template BayesTreeCliqueData BayesTree::getCliqueData() const { - BayesTreeCliqueData data; - for(const sharedClique& root: roots_) - getCliqueData(data, root); - return data; + BayesTreeCliqueData stats; + for (const sharedClique& root : roots_) getCliqueData(root, &stats); + return stats; } /* ************************************************************************* */ - template - void BayesTree::getCliqueData(BayesTreeCliqueData& data, sharedClique clique) const { - data.conditionalSizes.push_back(clique->conditional()->nrFrontals()); - data.separatorSizes.push_back(clique->conditional()->nrParents()); - for(sharedClique c: clique->children) { - getCliqueData(data, c); + template + void BayesTree::getCliqueData(sharedClique clique, + BayesTreeCliqueData* stats) const { + const auto conditional = clique->conditional(); + stats->conditionalSizes.push_back(conditional->nrFrontals()); + stats->separatorSizes.push_back(conditional->nrParents()); + for (sharedClique c : clique->children) { + getCliqueData(c, stats); } } @@ -133,34 +134,26 @@ namespace gtsam { } /* ************************************************************************* */ - // TODO: Clean up namespace { - template - int _pushClique(FactorGraph& fg, const boost::shared_ptr& clique) { - fg.push_back(clique->conditional_); + template + struct _pushCliqueFunctor { + _pushCliqueFunctor(FactorGraph* graph_) : graph(graph_) {} + FactorGraph* graph; + int operator()(const boost::shared_ptr& clique, int dummy) { + graph->push_back(clique->conditional_); return 0; } - - template - struct _pushCliqueFunctor { - _pushCliqueFunctor(FactorGraph& graph_) : graph(graph_) {} - FactorGraph& graph; - int operator()(const boost::shared_ptr& clique, int dummy) { - graph.push_back(clique->conditional_); - return 0; - } - }; - } + }; + } // namespace /* ************************************************************************* */ - template - void BayesTree::addFactorsToGraph(FactorGraph& graph) const - { + template + void BayesTree::addFactorsToGraph( + FactorGraph* graph) const { // Traverse the BayesTree and add all conditionals to this graph - int data = 0; // Unused - _pushCliqueFunctor functor(graph); - treeTraversal::DepthFirstForest(*this, data, functor); // FIXME: sort of works? -// treeTraversal::DepthFirstForest(*this, data, boost::bind(&_pushClique, boost::ref(graph), _1)); + int data = 0; // Unused + _pushCliqueFunctor functor(graph); + treeTraversal::DepthFirstForest(*this, data, functor); } /* ************************************************************************* */ @@ -434,50 +427,51 @@ namespace gtsam { } /* ************************************************************************* */ - template - void BayesTree::removePath(sharedClique clique, BayesNetType& bn, Cliques& orphans) - { + template + void BayesTree::removePath(sharedClique clique, BayesNetType* bn, + Cliques* orphans) { // base case is NULL, if so we do nothing and return empties above if (clique) { - // remove the clique from orphans in case it has been added earlier - orphans.remove(clique); + orphans->remove(clique); // remove me this->removeClique(clique); // remove path above me - this->removePath(typename Clique::shared_ptr(clique->parent_.lock()), bn, orphans); + this->removePath(typename Clique::shared_ptr(clique->parent_.lock()), bn, + orphans); - // add children to list of orphans (splice also removed them from clique->children_) - orphans.insert(orphans.begin(), clique->children.begin(), clique->children.end()); + // add children to list of orphans (splice also removed them from + // clique->children_) + orphans->insert(orphans->begin(), clique->children.begin(), + clique->children.end()); clique->children.clear(); - bn.push_back(clique->conditional_); - + bn->push_back(clique->conditional_); } } - /* ************************************************************************* */ - template - void BayesTree::removeTop(const KeyVector& keys, BayesNetType& bn, Cliques& orphans) - { + /* ************************************************************************* + */ + template + void BayesTree::removeTop(const KeyVector& keys, BayesNetType* bn, + Cliques* orphans) { + gttic(removetop); // process each key of the new factor - for(const Key& j: keys) - { + for (const Key& j : keys) { // get the clique - // TODO: Nodes will be searched again in removeClique + // TODO(frank): Nodes will be searched again in removeClique typename Nodes::const_iterator node = nodes_.find(j); - if(node != nodes_.end()) { + if (node != nodes_.end()) { // remove path from clique to root this->removePath(node->second, bn, orphans); } } // Delete cachedShortcuts for each orphan subtree - //TODO: Consider Improving - for(sharedClique& orphan: orphans) - orphan->deleteCachedShortcuts(); + // TODO(frank): Consider Improving + for (sharedClique& orphan : *orphans) orphan->deleteCachedShortcuts(); } /* ************************************************************************* */ diff --git a/gtsam/inference/BayesTree.h b/gtsam/inference/BayesTree.h index 892ac5f31c..1b9ddf7ec4 100644 --- a/gtsam/inference/BayesTree.h +++ b/gtsam/inference/BayesTree.h @@ -208,13 +208,13 @@ namespace gtsam { * Remove path from clique to root and return that path as factors * plus a list of orphaned subtree roots. Used in removeTop below. */ - void removePath(sharedClique clique, BayesNetType& bn, Cliques& orphans); + void removePath(sharedClique clique, BayesNetType* bn, Cliques* orphans); /** * Given a list of indices, turn "contaminated" part of the tree back into a factor graph. * Factors and orphans are added to the in/out arguments. */ - void removeTop(const KeyVector& keys, BayesNetType& bn, Cliques& orphans); + void removeTop(const KeyVector& keys, BayesNetType* bn, Cliques* orphans); /** * Remove the requested subtree. */ @@ -229,7 +229,7 @@ namespace gtsam { void addClique(const sharedClique& clique, const sharedClique& parent_clique = sharedClique()); /** Add all cliques in this BayesTree to the specified factor graph */ - void addFactorsToGraph(FactorGraph& graph) const; + void addFactorsToGraph(FactorGraph* graph) const; protected: @@ -238,7 +238,7 @@ namespace gtsam { int parentnum = 0) const; /** Gather data on a single clique */ - void getCliqueData(BayesTreeCliqueData& stats, sharedClique clique) const; + void getCliqueData(sharedClique clique, BayesTreeCliqueData* stats) const; /** remove a clique: warning, can result in a forest */ void removeClique(sharedClique clique); @@ -249,7 +249,26 @@ namespace gtsam { // Friend JunctionTree because it directly fills roots and nodes index. template friend class EliminatableClusterTree; - private: +#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 + public: + /// @name Deprecated + /// @{ + void removePath(sharedClique clique, BayesNetType& bn, Cliques& orphans) { + removePath(clique, &bn, &orphans); + } + void removeTop(const KeyVector& keys, BayesNetType& bn, Cliques& orphans) { + removeTop(keys, &bn, &orphans); + } + void getCliqueData(BayesTreeCliqueData& stats, sharedClique clique) const { + getCliqueData(clique, &stats); + } + void addFactorsToGraph(FactorGraph& graph) const{ + addFactorsToGraph(& graph); + } + /// @} +#endif + + private: /** Serialization function */ friend class boost::serialization::access; template diff --git a/gtsam/inference/FactorGraph.h b/gtsam/inference/FactorGraph.h index 0959989f9d..600e3f9ed2 100644 --- a/gtsam/inference/FactorGraph.h +++ b/gtsam/inference/FactorGraph.h @@ -270,7 +270,7 @@ class FactorGraph { typename std::enable_if< std::is_base_of::value>::type push_back(const BayesTree& bayesTree) { - bayesTree.addFactorsToGraph(*this); + bayesTree.addFactorsToGraph(this); } /** diff --git a/gtsam/inference/ISAM-inst.h b/gtsam/inference/ISAM-inst.h index 8ed41826e0..1c183b70f8 100644 --- a/gtsam/inference/ISAM-inst.h +++ b/gtsam/inference/ISAM-inst.h @@ -24,14 +24,14 @@ namespace gtsam { /* ************************************************************************* */ template -void ISAM::update_internal(const FactorGraphType& newFactors, - Cliques& orphans, const Eliminate& function) { +void ISAM::updateInternal(const FactorGraphType& newFactors, + Cliques* orphans, const Eliminate& function) { // Remove the contaminated part of the Bayes tree BayesNetType bn; const KeySet newFactorKeys = newFactors.keys(); if (!this->empty()) { KeyVector keyVector(newFactorKeys.begin(), newFactorKeys.end()); - this->removeTop(keyVector, bn, orphans); + this->removeTop(keyVector, &bn, orphans); } // Add the removed top and the new factors @@ -40,7 +40,7 @@ void ISAM::update_internal(const FactorGraphType& newFactors, factors += newFactors; // Add the orphaned subtrees - for (const sharedClique& orphan : orphans) + for (const sharedClique& orphan : *orphans) factors += boost::make_shared >(orphan); // Get an ordering where the new keys are eliminated last @@ -62,7 +62,7 @@ template void ISAM::update(const FactorGraphType& newFactors, const Eliminate& function) { Cliques orphans; - this->update_internal(newFactors, orphans, function); + this->updateInternal(newFactors, &orphans, function); } } diff --git a/gtsam/inference/ISAM.h b/gtsam/inference/ISAM.h index d6a40b5393..fe6763a131 100644 --- a/gtsam/inference/ISAM.h +++ b/gtsam/inference/ISAM.h @@ -22,56 +22,67 @@ namespace gtsam { +/** + * A Bayes tree with an update methods that implements the iSAM algorithm. + * Given a set of new factors, it re-eliminates the invalidated part of the + * tree. \nosubgrouping + */ +template +class ISAM : public BAYESTREE { + public: + typedef BAYESTREE Base; + typedef typename Base::BayesNetType BayesNetType; + typedef typename Base::FactorGraphType FactorGraphType; + typedef typename Base::Clique Clique; + typedef typename Base::sharedClique sharedClique; + typedef typename Base::Cliques Cliques; + + private: + typedef typename Base::Eliminate Eliminate; + typedef typename Base::EliminationTraitsType EliminationTraitsType; + + public: + /// @name Standard Constructors + /// @{ + + /** Create an empty Bayes Tree */ + ISAM() {} + + /** Copy constructor */ + explicit ISAM(const Base& bayesTree) : Base(bayesTree) {} + + /// @} + /// @name Advanced Interface Interface + /// @{ + /** - * A Bayes tree with an update methods that implements the iSAM algorithm. - * Given a set of new factors, it re-eliminates the invalidated part of the tree. - * \nosubgrouping + * update the Bayes tree with a set of new factors, typically derived from + * measurements + * @param newFactors is a factor graph that contains the new factors + * @param function an elimination routine */ - template - class ISAM: public BAYESTREE - { - public: - - typedef BAYESTREE Base; - typedef typename Base::BayesNetType BayesNetType; - typedef typename Base::FactorGraphType FactorGraphType; - typedef typename Base::Clique Clique; - typedef typename Base::sharedClique sharedClique; - typedef typename Base::Cliques Cliques; - - private: - - typedef typename Base::Eliminate Eliminate; - typedef typename Base::EliminationTraitsType EliminationTraitsType; - - public: - - /// @name Standard Constructors - /// @{ - - /** Create an empty Bayes Tree */ - ISAM() {} - - /** Copy constructor */ - ISAM(const Base& bayesTree) : Base(bayesTree) {} - - /// @} - /// @name Advanced Interface Interface - /// @{ - - /** - * update the Bayes tree with a set of new factors, typically derived from measurements - * @param newFactors is a factor graph that contains the new factors - * @param function an elimination routine - */ - void update(const FactorGraphType& newFactors, const Eliminate& function = EliminationTraitsType::DefaultEliminate); - - /** update_internal provides access to list of orphans for drawing purposes */ - void update_internal(const FactorGraphType& newFactors, Cliques& orphans, + void update( + const FactorGraphType& newFactors, const Eliminate& function = EliminationTraitsType::DefaultEliminate); - /// @} - - }; + /** updateInternal provides access to list of orphans for drawing purposes + */ + void updateInternal( + const FactorGraphType& newFactors, Cliques* orphans, + const Eliminate& function = EliminationTraitsType::DefaultEliminate); -}/// namespace gtsam + /// @} + +#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 + /// @name Deprecated + /// @{ + void update_internal( + const FactorGraphType& newFactors, Cliques& orphans, + const Eliminate& function = EliminationTraitsType::DefaultEliminate) { + updateInternal(newFactors, &orphans, function); + } + /// @} +#endif +}; + +} // namespace gtsam diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index c01d1c536d..c38faae11e 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -241,7 +241,7 @@ boost::shared_ptr ISAM2::recalculate( Cliques orphans; GaussianBayesNet affectedBayesNet; this->removeTop(KeyVector(markedKeys.begin(), markedKeys.end()), - affectedBayesNet, orphans); + &affectedBayesNet, &orphans); gttoc(removetop); // FactorGraph factors(affectedBayesNet); From ebae189c7be5cf971de75ab46d5c3b511857f473 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 16:06:09 -0400 Subject: [PATCH 051/160] Re-ordered functions to minimize diff --- gtsam/nonlinear/ISAM2.cpp | 223 +++++++++++++++++++------------------- gtsam/nonlinear/ISAM2.h | 9 +- 2 files changed, 116 insertions(+), 116 deletions(-) diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index 8b882f1edb..fa765a109c 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -58,6 +58,118 @@ bool ISAM2::equals(const ISAM2& other, double tol) const { fixedVariables_ == other.fixedVariables_; } +/* ************************************************************************* */ +GaussianFactorGraph ISAM2::relinearizeAffectedFactors( + const ISAM2UpdateParams& updateParams, const FastList& affectedKeys, + const KeySet& relinKeys) { + gttic(relinearizeAffectedFactors); + FactorIndexSet candidates = + UpdateImpl::GetAffectedFactors(affectedKeys, variableIndex_); + + gttic(affectedKeysSet); + // for fast lookup below + KeySet affectedKeysSet; + affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); + gttoc(affectedKeysSet); + + gttic(check_candidates_and_linearize); + GaussianFactorGraph linearized; + for (const FactorIndex idx : candidates) { + bool inside = true; + bool useCachedLinear = params_.cacheLinearizedFactors; + for (Key key : nonlinearFactors_[idx]->keys()) { + if (affectedKeysSet.find(key) == affectedKeysSet.end()) { + inside = false; + break; + } + if (useCachedLinear && relinKeys.find(key) != relinKeys.end()) + useCachedLinear = false; + } + if (inside) { + if (useCachedLinear) { +#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS + assert(linearFactors_[idx]); + assert(linearFactors_[idx]->keys() == nonlinearFactors_[idx]->keys()); +#endif + linearized.push_back(linearFactors_[idx]); + } else { + auto linearFactor = nonlinearFactors_[idx]->linearize(theta_); + linearized.push_back(linearFactor); + if (params_.cacheLinearizedFactors) { +#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS + assert(linearFactors_[idx]->keys() == linearFactor->keys()); +#endif + linearFactors_[idx] = linearFactor; + } + } + } + } + gttoc(check_candidates_and_linearize); + + return linearized; +} + +/* ************************************************************************* */ +void ISAM2::recalculate(const ISAM2UpdateParams& updateParams, + const KeySet& relinKeys, ISAM2Result* result) { + gttic(recalculate); + UpdateImpl::LogRecalculateKeys(*result); + + if (!result->markedKeys.empty() || !result->observedKeys.empty()) { + // Remove top of Bayes tree and convert to a factor graph: + // (a) For each affected variable, remove the corresponding clique and all + // parents up to the root. (b) Store orphaned sub-trees \BayesTree_{O} of + // removed cliques. + GaussianBayesNet affectedBayesNet; + Cliques orphans; + this->removeTop( + KeyVector(result->markedKeys.begin(), result->markedKeys.end()), + &affectedBayesNet, &orphans); + + // FactorGraph factors(affectedBayesNet); + // bug was here: we cannot reuse the original factors, because then the + // cached factors get messed up [all the necessary data is actually + // contained in the affectedBayesNet, including what was passed in from the + // boundaries, so this would be correct; however, in the process we also + // generate new cached_ entries that will be wrong (ie. they don't contain + // what would be passed up at a certain point if batch elimination was done, + // but that's what we need); we could choose not to update cached_ from + // here, but then the new information (and potentially different variable + // ordering) is not reflected in the cached_ values which again will be + // wrong] so instead we have to retrieve the original linearized factors AND + // add the cached factors from the boundary + + // ordering provides all keys in conditionals, there cannot be others + // because path to root included + gttic(affectedKeys); + FastList affectedKeys; + for (const auto& conditional : affectedBayesNet) + affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), + conditional->endFrontals()); + gttoc(affectedKeys); + + KeySet affectedKeysSet; + static const double kBatchThreshold = 0.65; + if (affectedKeys.size() >= theta_.size() * kBatchThreshold) { + // Do a batch step - reorder and relinearize all variables + recalculateBatch(updateParams, &affectedKeysSet, result); + } else { + recalculateIncremental(updateParams, relinKeys, affectedKeys, + &affectedKeysSet, &orphans, result); + } + + // Root clique variables for detailed results + if (result->detail && params_.enableDetailedResults) { + for (const auto& root : roots_) + for (Key var : *root->conditional()) + result->detail->variableStatus[var].inRootClique = true; + } + + // Update replaced keys mask (accumulates until back-substitution happens) + deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); + } +} + /* ************************************************************************* */ void ISAM2::recalculateBatch(const ISAM2UpdateParams& updateParams, KeySet* affectedKeysSet, ISAM2Result* result) { @@ -128,57 +240,6 @@ void ISAM2::recalculateBatch(const ISAM2UpdateParams& updateParams, } } -/* ************************************************************************* */ -GaussianFactorGraph ISAM2::relinearizeAffectedFactors( - const ISAM2UpdateParams& updateParams, const FastList& affectedKeys, - const KeySet& relinKeys) { - gttic(relinearizeAffectedFactors); - FactorIndexSet candidates = - UpdateImpl::GetAffectedFactors(affectedKeys, variableIndex_); - - gttic(affectedKeysSet); - // for fast lookup below - KeySet affectedKeysSet; - affectedKeysSet.insert(affectedKeys.begin(), affectedKeys.end()); - gttoc(affectedKeysSet); - - gttic(check_candidates_and_linearize); - GaussianFactorGraph linearized; - for (const FactorIndex idx : candidates) { - bool inside = true; - bool useCachedLinear = params_.cacheLinearizedFactors; - for (Key key : nonlinearFactors_[idx]->keys()) { - if (affectedKeysSet.find(key) == affectedKeysSet.end()) { - inside = false; - break; - } - if (useCachedLinear && relinKeys.find(key) != relinKeys.end()) - useCachedLinear = false; - } - if (inside) { - if (useCachedLinear) { -#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS - assert(linearFactors_[idx]); - assert(linearFactors_[idx]->keys() == nonlinearFactors_[idx]->keys()); -#endif - linearized.push_back(linearFactors_[idx]); - } else { - auto linearFactor = nonlinearFactors_[idx]->linearize(theta_); - linearized.push_back(linearFactor); - if (params_.cacheLinearizedFactors) { -#ifdef GTSAM_EXTRA_CONSISTENCY_CHECKS - assert(linearFactors_[idx]->keys() == linearFactor->keys()); -#endif - linearFactors_[idx] = linearFactor; - } - } - } - } - gttoc(check_candidates_and_linearize); - - return linearized; -} - /* ************************************************************************* */ void ISAM2::recalculateIncremental(const ISAM2UpdateParams& updateParams, const KeySet& relinKeys, @@ -295,66 +356,6 @@ void ISAM2::recalculateIncremental(const ISAM2UpdateParams& updateParams, // 4. The orphans have already been inserted during elimination } -/* ************************************************************************* */ -void ISAM2::recalculate(const ISAM2UpdateParams& updateParams, - const KeySet& relinKeys, ISAM2Result* result) { - gttic(recalculate); - UpdateImpl::LogRecalculateKeys(*result); - - if (!result->markedKeys.empty() || !result->observedKeys.empty()) { - // Remove top of Bayes tree and convert to a factor graph: - // (a) For each affected variable, remove the corresponding clique and all - // parents up to the root. (b) Store orphaned sub-trees \BayesTree_{O} of - // removed cliques. - GaussianBayesNet affectedBayesNet; - Cliques orphans; - this->removeTop( - KeyVector(result->markedKeys.begin(), result->markedKeys.end()), - &affectedBayesNet, &orphans); - - // FactorGraph factors(affectedBayesNet); - // bug was here: we cannot reuse the original factors, because then the - // cached factors get messed up [all the necessary data is actually - // contained in the affectedBayesNet, including what was passed in from the - // boundaries, so this would be correct; however, in the process we also - // generate new cached_ entries that will be wrong (ie. they don't contain - // what would be passed up at a certain point if batch elimination was done, - // but that's what we need); we could choose not to update cached_ from - // here, but then the new information (and potentially different variable - // ordering) is not reflected in the cached_ values which again will be - // wrong] so instead we have to retrieve the original linearized factors AND - // add the cached factors from the boundary - - // ordering provides all keys in conditionals, there cannot be others - // because path to root included - gttic(affectedKeys); - FastList affectedKeys; - for (const auto& conditional : affectedBayesNet) - affectedKeys.insert(affectedKeys.end(), conditional->beginFrontals(), - conditional->endFrontals()); - gttoc(affectedKeys); - - KeySet affectedKeysSet; - static const double kBatchThreshold = 0.65; - if (affectedKeys.size() >= theta_.size() * kBatchThreshold) { - // Do a batch step - reorder and relinearize all variables - recalculateBatch(updateParams, &affectedKeysSet, result); - } else { - recalculateIncremental(updateParams, relinKeys, affectedKeys, - &affectedKeysSet, &orphans, result); - } - - // Root clique variables for detailed results - if (result->detail && params_.enableDetailedResults) { - for (const auto& root : roots_) - for (Key var : *root->conditional()) - result->detail->variableStatus[var].inRootClique = true; - } - - // Update replaced keys mask (accumulates until back-substitution happens) - deltaReplacedMask_.insert(affectedKeysSet.begin(), affectedKeysSet.end()); - } -} /* ************************************************************************* */ void ISAM2::addVariables(const Values& newTheta, ISAM2Result::DetailedResults* detail) { diff --git a/gtsam/nonlinear/ISAM2.h b/gtsam/nonlinear/ISAM2.h index ec1b71a723..9f33e757ff 100644 --- a/gtsam/nonlinear/ISAM2.h +++ b/gtsam/nonlinear/ISAM2.h @@ -281,6 +281,10 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { /// @} protected: + /// Remove marked top and either recalculate in batch or incrementally. + void recalculate(const ISAM2UpdateParams& updateParams, + const KeySet& relinKeys, ISAM2Result* result); + // Do a batch step - reorder and relinearize all variables void recalculateBatch(const ISAM2UpdateParams& updateParams, KeySet* affectedKeysSet, ISAM2Result* result); @@ -296,11 +300,6 @@ class GTSAM_EXPORT ISAM2 : public BayesTree { const FastList& affectedKeys, KeySet* affectedKeysSet, Cliques* orphans, ISAM2Result* result); - /** - * Remove marked top and either recalculate in batch or incrementally. - */ - void recalculate(const ISAM2UpdateParams& updateParams, - const KeySet& relinKeys, ISAM2Result* result); /** * Add new variables to the ISAM2 system. From d18795e8443ccf6df1b2e7708cfbb40f103131fa Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 3 Jun 2019 17:29:21 -0400 Subject: [PATCH 052/160] Some last cosmetic changes to make update fit on single screen --- gtsam/nonlinear/ISAM2-impl.h | 16 +++++++++---- gtsam/nonlinear/ISAM2.cpp | 45 ++++++++++------------------------- gtsam/nonlinear/ISAM2Result.h | 8 +++++++ 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/gtsam/nonlinear/ISAM2-impl.h b/gtsam/nonlinear/ISAM2-impl.h index 6acdd28d52..eb2285b281 100644 --- a/gtsam/nonlinear/ISAM2-impl.h +++ b/gtsam/nonlinear/ISAM2-impl.h @@ -134,6 +134,14 @@ struct GTSAM_EXPORT UpdateImpl { } } + // Check relinearization if we're at the nth step, or we are using a looser + // loop relinerization threshold. + bool relinarizationNeeded(size_t update_count) const { + return updateParams_.force_relinearize || + (params_.enableRelinearization && + update_count % params_.relinearizeSkip == 0); + } + // Add any new factors \Factors:=\Factors\cup\Factors'. void pushBackFactors(const NonlinearFactorGraph& newFactors, NonlinearFactorGraph* nonlinearFactors, @@ -406,10 +414,10 @@ struct GTSAM_EXPORT UpdateImpl { // Mark all cliques that involve marked variables \Theta_{J} and all // their ancestors. - void fluidFindAll(const ISAM2::Roots& roots, const KeySet& relinKeys, - KeySet* markedKeys, - ISAM2Result::DetailedResults* detail) const { - gttic(fluidFindAll); + void findFluid(const ISAM2::Roots& roots, const KeySet& relinKeys, + KeySet* markedKeys, + ISAM2Result::DetailedResults* detail) const { + gttic(findFluid); for (const auto& root : roots) // add other cliques that have the marked ones in the separator root->findAll(relinKeys, markedKeys); diff --git a/gtsam/nonlinear/ISAM2.cpp b/gtsam/nonlinear/ISAM2.cpp index fa765a109c..f56b23777c 100644 --- a/gtsam/nonlinear/ISAM2.cpp +++ b/gtsam/nonlinear/ISAM2.cpp @@ -415,23 +415,14 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, const Values& newTheta, const ISAM2UpdateParams& updateParams) { gttic(ISAM2_update); - this->update_count_++; + this->update_count_ += 1; UpdateImpl::LogStartingUpdate(newFactors, *this); - - const bool relinearizeThisStep = - updateParams.force_relinearize || - (params_.enableRelinearization && - update_count_ % params_.relinearizeSkip == 0); + ISAM2Result result(params_.enableDetailedResults); + UpdateImpl update(params_, updateParams); // Update delta if we need it to check relinearization later - if (relinearizeThisStep) { + if (update.relinarizationNeeded(update_count_)) updateDelta(updateParams.forceFullSolve); - } - - ISAM2Result result; - if (params_.enableDetailedResults) - result.detail = ISAM2Result::DetailedResults(); - UpdateImpl update(params_, updateParams); // 1. Add any new factors \Factors:=\Factors\cup\Factors'. update.pushBackFactors(newFactors, &nonlinearFactors_, &linearFactors_, @@ -442,8 +433,7 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, // 2. Initialize any new variables \Theta_{new} and add // \Theta:=\Theta\cup\Theta_{new}. - addVariables(newTheta, result.detail.get_ptr()); - + addVariables(newTheta, result.details()); if (params_.evaluateNonlinearError) update.error(nonlinearFactors_, calculateEstimate(), &result.errorBefore); @@ -452,28 +442,22 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, result.keysWithRemovedFactors, &result.markedKeys); update.updateKeys(result.markedKeys, &result); - // Check relinearization if we're at the nth step, or we are using a looser - // loop relinerization threshold. KeySet relinKeys; - if (relinearizeThisStep) { + result.variablesRelinearized = 0; + if (update.relinarizationNeeded(update_count_)) { // 4. Mark keys in \Delta above threshold \beta: relinKeys = update.gatherRelinearizeKeys(roots_, delta_, fixedVariables_, &result.markedKeys); - update.recordRelinearizeDetail(relinKeys, result.detail.get_ptr()); + update.recordRelinearizeDetail(relinKeys, result.details()); if (!relinKeys.empty()) { - // 5. Mark all cliques that involve marked variables \Theta_{J} and all - // their ancestors. - update.fluidFindAll(roots_, relinKeys, &result.markedKeys, - result.detail.get_ptr()); + // 5. Mark cliques that involve marked variables \Theta_{J} and ancestors. + update.findFluid(roots_, relinKeys, &result.markedKeys, result.details()); // 6. Update linearization point for marked variables: // \Theta_{J}:=\Theta_{J}+\Delta_{J}. UpdateImpl::ExpmapMasked(delta_, relinKeys, &theta_); } result.variablesRelinearized = result.markedKeys.size(); - } else { - result.variablesRelinearized = 0; } - // TODO(frank): should be result.variablesRelinearized = relinKeys.size(); ? // 7. Linearize new factors update.linearizeNewFactors(newFactors, theta_, nonlinearFactors_.size(), @@ -481,18 +465,13 @@ ISAM2Result ISAM2::update(const NonlinearFactorGraph& newFactors, update.augmentVariableIndex(newFactors, result.newFactorsIndices, &variableIndex_); - // 8. Redo top of Bayes tree + // 8. Redo top of Bayes tree and update data structures recalculate(updateParams, relinKeys, &result); - - // Update data structures to remove unused keys - if (!result.unusedKeys.empty()) { - removeVariables(result.unusedKeys); - } + if (!result.unusedKeys.empty()) removeVariables(result.unusedKeys); result.cliques = this->nodes().size(); if (params_.evaluateNonlinearError) update.error(nonlinearFactors_, calculateEstimate(), &result.errorAfter); - return result; } diff --git a/gtsam/nonlinear/ISAM2Result.h b/gtsam/nonlinear/ISAM2Result.h index 1287b0e1a2..3953c760b2 100644 --- a/gtsam/nonlinear/ISAM2Result.h +++ b/gtsam/nonlinear/ISAM2Result.h @@ -157,6 +157,14 @@ struct GTSAM_EXPORT ISAM2Result { * Detail for information about the results data stored here. */ boost::optional detail; + explicit ISAM2Result(bool enableDetailedResults = false) { + if (enableDetailedResults) detail.reset(DetailedResults()); + } + + /// Return pointer to detail, 0 if no detail requested + DetailedResults* details() { return detail.get_ptr(); } + + /// Print results void print(const std::string str = "") const { using std::cout; cout << str << " Reelimintated: " << variablesReeliminated From ac49bd0c1f35e7bf6583b41552b2905c9a56dea5 Mon Sep 17 00:00:00 2001 From: dellaert Date: Tue, 4 Jun 2019 16:03:36 -0400 Subject: [PATCH 053/160] Fixed a number of deprecated instances.... --- .../symbolic/tests/testSymbolicBayesTree.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gtsam/symbolic/tests/testSymbolicBayesTree.cpp b/gtsam/symbolic/tests/testSymbolicBayesTree.cpp index fdc28c5c84..33fc3243bb 100644 --- a/gtsam/symbolic/tests/testSymbolicBayesTree.cpp +++ b/gtsam/symbolic/tests/testSymbolicBayesTree.cpp @@ -144,7 +144,7 @@ TEST( BayesTree, removePath ) SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removePath(bayesTree[_C_], bn, orphans); + bayesTree.removePath(bayesTree[_C_], &bn, &orphans); SymbolicFactorGraph factors(bn); CHECK(assert_equal(expected, factors)); CHECK(assert_container_equal(expectedOrphans|indirected, orphans|indirected)); @@ -161,7 +161,7 @@ TEST( BayesTree, removePath ) SymbolicBayesNet bn2; SymbolicBayesTree::Cliques orphans2; - bayesTree.removePath(bayesTree[_E_], bn2, orphans2); + bayesTree.removePath(bayesTree[_E_], &bn2, &orphans2); SymbolicFactorGraph factors2(bn2); CHECK(assert_equal(expected2, factors2)); CHECK(assert_container_equal(expectedOrphans2|indirected, orphans2|indirected)); @@ -175,7 +175,7 @@ TEST( BayesTree, removePath2 ) // Call remove-path with clique B SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removePath(bayesTree[_B_], bn, orphans); + bayesTree.removePath(bayesTree[_B_], &bn, &orphans); SymbolicFactorGraph factors(bn); // Check expected outcome @@ -195,7 +195,7 @@ TEST(BayesTree, removePath3) // Call remove-path with clique T SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removePath(bayesTree[_T_], bn, orphans); + bayesTree.removePath(bayesTree[_T_], &bn, &orphans); SymbolicFactorGraph factors(bn); // Check expected outcome @@ -279,7 +279,7 @@ TEST( BayesTree, removeTop ) // Remove the contaminated part of the Bayes tree SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removeTop(list_of(_B_)(_S_), bn, orphans); + bayesTree.removeTop(list_of(_B_)(_S_), &bn, &orphans); // Check expected outcome SymbolicBayesNet expected; @@ -295,7 +295,7 @@ TEST( BayesTree, removeTop ) //boost::shared_ptr newFactor2(new IndexFactor(_B_)); SymbolicBayesNet bn2; SymbolicBayesTree::Cliques orphans2; - bayesTree.removeTop(list_of(_B_), bn2, orphans2); + bayesTree.removeTop(list_of(_B_), &bn2, &orphans2); SymbolicFactorGraph factors2(bn2); SymbolicFactorGraph expected2; CHECK(assert_equal(expected2, factors2)); @@ -316,7 +316,7 @@ TEST( BayesTree, removeTop2 ) // Remove the contaminated part of the Bayes tree SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removeTop(list_of(_T_), bn, orphans); + bayesTree.removeTop(list_of(_T_), &bn, &orphans); // Check expected outcome SymbolicBayesNet expected = list_of @@ -343,7 +343,7 @@ TEST( BayesTree, removeTop3 ) // remove all SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removeTop(list_of(L(5))(X(4))(X(2))(X(3)), bn, orphans); + bayesTree.removeTop(list_of(L(5))(X(4))(X(2))(X(3)), &bn, &orphans); SymbolicBayesNet expectedBn = list_of (SymbolicConditional::FromKeys(list_of(X(4))(L(5)), 2)) @@ -367,7 +367,7 @@ TEST( BayesTree, removeTop4 ) // remove all SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removeTop(list_of(X(2))(L(5))(X(4))(X(3)), bn, orphans); + bayesTree.removeTop(list_of(X(2))(L(5))(X(4))(X(3)), &bn, &orphans); SymbolicBayesNet expectedBn = list_of (SymbolicConditional::FromKeys(list_of(X(4))(L(5)), 2)) @@ -392,7 +392,7 @@ TEST( BayesTree, removeTop5 ) // Remove nonexistant SymbolicBayesNet bn; SymbolicBayesTree::Cliques orphans; - bayesTree.removeTop(list_of(X(10)), bn, orphans); + bayesTree.removeTop(list_of(X(10)), &bn, &orphans); SymbolicBayesNet expectedBn; EXPECT(assert_equal(expectedBn, bn)); From f27964a6cd4bb694f46b72ebb5c8759f9217a42c Mon Sep 17 00:00:00 2001 From: dellaert Date: Tue, 4 Jun 2019 16:10:33 -0400 Subject: [PATCH 054/160] Deprecated OFF by default, one new clang run for deprecated ON --- .travis.sh | 3 ++- .travis.yml | 15 +++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.travis.sh b/.travis.sh index 434187e4f1..4b80d23a7f 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,9 +23,10 @@ function build_and_test () fi cmake $SOURCE_DIR \ + -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS \ -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ - -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS + -DGTSAM_ALLOW_DEPRECATED_SINCE_V4=$GTSAM_ALLOW_DEPRECATED_SINCE_V4 # Actual build: make -j2 diff --git a/.travis.yml b/.travis.yml index 4b44871039..37f377846d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,19 +36,22 @@ matrix: include: - compiler: gcc os: linux - env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON - compiler: gcc os: linux - env: GTSAM_BUILD_UNSTABLE=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON # gcc too slow for all tests + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # gcc too slow for all tests - compiler: gcc os: linux - env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF GCC_VERSION="8" + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GCC_VERSION="8" - compiler: clang os: linux - env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - compiler: clang + os: linux + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON # - compiler: gcc # os: osx -# env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GTSAM_BUILD_TESTS=OFF +# env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON - compiler: clang os: osx - env: GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF GTSAM_BUILD_TESTS=ON + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF From b1a30ff6cb1e6431fe044cea82431283654775d4 Mon Sep 17 00:00:00 2001 From: dellaert Date: Tue, 4 Jun 2019 18:27:04 -0400 Subject: [PATCH 055/160] wrap printErrors --- gtsam.h | 1 + 1 file changed, 1 insertion(+) diff --git a/gtsam.h b/gtsam.h index d40a109b39..ba5aa5f4f9 100644 --- a/gtsam.h +++ b/gtsam.h @@ -1870,6 +1870,7 @@ class NonlinearFactorGraph { // FactorGraph void print(string s) const; + void printErrors(const gtsam::Values& values); bool equals(const gtsam::NonlinearFactorGraph& fg, double tol) const; size_t size() const; bool empty() const; From 314b6173fcd51baea5664f9112a426a7e6352b43 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 6 Jun 2019 12:08:51 -0400 Subject: [PATCH 056/160] Updated build status link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d61dd5b459..68bae879e1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/borglab/gtsam.svg?branch=develop)](https://travis-ci.org/borglab/gtsam) +[![Build Status](https://travis-ci.com/borglab/gtsam.svg?branch=develop)](https://travis-ci.com/borglab/gtsam/) # README - Georgia Tech Smoothing and Mapping library From 78ef13426ee085afecabe049f9af236284894d98 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 6 Jun 2019 17:46:27 -0400 Subject: [PATCH 057/160] Travis stages --- .travis.yml | 58 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 37f377846d..c192f4df00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,32 +26,42 @@ install: script: - bash .travis.sh -env: - global: - - MAKEFLAGS="-j 2" - - CCACHE_SLOPPINESS=pch_defines,time_macros +# We first do the compile stage specified below, then the matrix expansion specified after. +stages: + - compile + - test -# gcc is too slow and we have a time limit in Travis CI: selective builds. -matrix: +# Compile stage without building examples/tests to populate the caches. +jobs: include: - - compiler: gcc - os: linux - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON - - compiler: gcc - os: linux - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # gcc too slow for all tests - - compiler: gcc +# on Linux, GCC + - stage: compile os: linux - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON GCC_VERSION="8" - - compiler: clang + compiler: gcc + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF +# on Linux, CLANG + - stage: compile os: linux - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON - - compiler: clang + compiler: clang + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON +# on Linux, with deprecated ON to make sure that path still compiles + - stage: compile os: linux - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON -# - compiler: gcc -# os: osx -# env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON - - compiler: clang - os: osx - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + compiler: clang + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON + +# Matrix configuration: +os: + - linux + # - osx +compiler: + - gcc + - clang +env: + global: + - MAKEFLAGS="-j 2" + - CCACHE_SLOPPINESS=pch_defines,time_macros + matrix: + - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + From 703c7055335fa36c1c7e811dce227b7aaab52a24 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 6 Jun 2019 18:22:37 -0400 Subject: [PATCH 058/160] Full matrix, including OSX --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c192f4df00..b76da0d95d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,17 +43,17 @@ jobs: - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, with deprecated ON to make sure that path still compiles - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # Matrix configuration: os: - linux - # - osx + - osx compiler: - gcc - clang From a30d19649dce41ff55799e13ded3ee0ab7131361 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 6 Jun 2019 18:25:55 -0400 Subject: [PATCH 059/160] Add OSX to compile --- .travis.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b76da0d95d..a00393b56b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,6 +34,16 @@ stages: # Compile stage without building examples/tests to populate the caches. jobs: include: +# on Mac, GCC + - stage: compile + os: osx + compiler: gcc + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF +# on Mac, CLANG + - stage: compile + os: osx + compiler: clang + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, GCC - stage: compile os: linux @@ -52,8 +62,8 @@ jobs: # Matrix configuration: os: - - linux - osx + - linux compiler: - gcc - clang From 308e6aa8fe0e11d1ea970fa21ed024ffa06199f4 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 9 Jun 2019 11:14:31 -0400 Subject: [PATCH 060/160] Global env variables --- .travis.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index a00393b56b..a18fd91ba7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,27 +38,27 @@ jobs: - stage: compile os: osx compiler: gcc - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Mac, CLANG - stage: compile os: osx compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, GCC - stage: compile os: linux compiler: gcc - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, CLANG - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, with deprecated ON to make sure that path still compiles - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # Matrix configuration: os: @@ -71,7 +71,9 @@ env: global: - MAKEFLAGS="-j 2" - CCACHE_SLOPPINESS=pch_defines,time_macros + - GTSAM_BUILD_UNSTABLE=ON + - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF matrix: - - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=ON - - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=ON GTSAM_BUILD_UNSTABLE=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + - GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - GTSAM_BUILD_TESTS=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF From 3a85be2531d949e3d80ae805b27b189b51240f0d Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Mon, 10 Jun 2019 12:41:27 +0200 Subject: [PATCH 061/160] provide --- CMakeLists.txt | 15 +++++++++++++-- cmake/cmake_uninstall.cmake.in | 27 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 cmake/cmake_uninstall.cmake.in diff --git a/CMakeLists.txt b/CMakeLists.txt index e9498ac218..c07cad6209 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,17 @@ else() set(GTSAM_UNSTABLE_AVAILABLE 0) endif() +# ---------------------------------------------------------------------------- +# Uninstall target, for "make uninstall" +# ---------------------------------------------------------------------------- +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + +add_custom_target(uninstall + "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") + ############################################################################### # Set up options @@ -131,8 +142,8 @@ if(MSVC) endif() endif() -# If building DLLs in MSVC, we need to avoid EIGEN_STATIC_ASSERT() -# or explicit instantiation will generate build errors. +# If building DLLs in MSVC, we need to avoid EIGEN_STATIC_ASSERT() +# or explicit instantiation will generate build errors. # See: https://bitbucket.org/gtborg/gtsam/issues/417/fail-to-build-on-msvc-2017 # if(MSVC AND BUILD_SHARED_LIBS) diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000000..aa20a69a62 --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,27 @@ +# ----------------------------------------------- +# File that provides "make uninstall" target +# We use the file 'install_manifest.txt' +# ----------------------------------------------- +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if(EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif(NOT "${rm_retval}" STREQUAL 0) + else(EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif(EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) + + From 7cee39f482dbca6caf9c088586467747d56bc626 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 10 Jun 2019 10:16:51 -0400 Subject: [PATCH 062/160] Always run in debug mode --- .travis.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.sh b/.travis.sh index 4b80d23a7f..14eb9f455a 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,6 +23,7 @@ function build_and_test () fi cmake $SOURCE_DIR \ + -DCMAKE_BUILD_TYPE=Debug \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS \ -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ From 2c7389db76ad8d59b4522251f07225a8af5f3e80 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Mon, 10 Jun 2019 18:08:05 +0200 Subject: [PATCH 063/160] add command `make check_valgrind` --- cmake/GtsamTesting.cmake | 57 ++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index e4c515d1f7..9ce5981e56 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -8,7 +8,7 @@ # Macro: # # gtsamAddTestsGlob(groupName globPatterns excludedFiles linkLibraries) -# +# # Add a group of unit tests. A list of unit test .cpp files or glob patterns specifies the # tests to create. Tests are assigned into a group name so they can easily by run # independently with a make target. Running 'make check' builds and runs all tests. @@ -35,7 +35,7 @@ endmacro() # Macro: # # gtsamAddExamplesGlob(globPatterns excludedFiles linkLibraries) -# +# # Add scripts that will serve as examples of how to use the library. A list of files or # glob patterns is specified, and one executable will be created for each matching .cpp # file. These executables will not be installed. They are built with 'make all' if @@ -60,7 +60,7 @@ endmacro() # Macro: # # gtsamAddTimingGlob(globPatterns excludedFiles linkLibraries) -# +# # Add scripts that time aspects of the library. A list of files or # glob patterns is specified, and one executable will be created for each matching .cpp # file. These executables will not be installed. They are not built with 'make all', @@ -101,7 +101,14 @@ mark_as_advanced(GTSAM_SINGLE_TEST_EXE) # Enable make check (http://www.cmake.org/Wiki/CMakeEmulateMakeCheck) if(GTSAM_BUILD_TESTS) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) - + # Also add alternative checks using valgrind. + # We don't look for valgrind being installed in the system, since these + # targets are not invoked unless directly instructed by the user. + if (UNIX) + # Run all tests using valgrind: + add_custom_target(check_valgrind) + endif() + # Add target to build tests without running add_custom_target(all.tests) endif() @@ -122,7 +129,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) add_custom_target(check.${groupName} COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) set_property(TARGET check.${groupName} PROPERTY FOLDER "Unit tests") endif() - + # Get all script files file(GLOB script_files ${globPatterns}) @@ -135,7 +142,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) list(REMOVE_ITEM script_files ${excludedFilePaths}) endif() endif() - + # Separate into source files and headers (allows for adding headers to show up in # MSVC and Xcode projects). set(script_srcs "") @@ -148,7 +155,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) list(APPEND script_srcs ${script_file}) endif() endforeach() - + # Don't put test files in folders in MSVC and Xcode because they're already grouped source_group("" FILES ${script_srcs} ${script_headers}) @@ -157,26 +164,38 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) foreach(script_src IN ITEMS ${script_srcs}) # Get test base name get_filename_component(script_name ${script_src} NAME_WE) - + # Add executable add_executable(${script_name} ${script_src} ${script_headers}) target_link_libraries(${script_name} CppUnitLite ${linkLibraries}) - + # Add target dependencies add_test(NAME ${script_name} COMMAND ${script_name}) add_dependencies(check.${groupName} ${script_name}) add_dependencies(check ${script_name}) add_dependencies(all.tests ${script_name}) if(NOT MSVC AND NOT XCODE_VERSION) - add_custom_target(${script_name}.run ${EXECUTABLE_OUTPUT_PATH}${script_name} DEPENDS ${script_name}) + # Regular test run: + add_custom_target(${script_name}.run + COMMAND ${EXECUTABLE_OUTPUT_PATH}${script_name} + DEPENDS ${script_name} + ) + + # Run with valgrind: + set(GENERATED_EXE "$") + add_custom_target(${script_name}.run.valgrind + COMMAND "valgrind" "--error-exitcode=1" ${GENERATED_EXE} + DEPENDS ${script_name} + ) + add_dependencies(check_valgrind ${script_name}.run.valgrind) endif() - + # Add TOPSRCDIR set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") - + # Exclude from 'make all' and 'make install' set_target_properties(${script_name} PROPERTIES EXCLUDE_FROM_ALL ON) - + # Configure target folder (for MSVC and Xcode) set_property(TARGET ${script_name} PROPERTY FOLDER "Unit tests/${groupName}") endforeach() @@ -189,7 +208,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) # Default on MSVC and XCode - combine test group into a single exectuable set(target_name check_${groupName}_program) - + # Add executable add_executable(${target_name} "${script_srcs}" ${script_headers}) target_link_libraries(${target_name} CppUnitLite ${linkLibraries}) @@ -200,7 +219,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) set(rest_script_srcs ${script_srcs}) list(REMOVE_AT rest_script_srcs 0) set_property(SOURCE ${rest_script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "main=inline no_main") - + # Add target dependencies add_test(NAME ${target_name} COMMAND ${target_name}) add_dependencies(check.${groupName} ${target_name}) @@ -208,10 +227,10 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) if(NOT XCODE_VERSION) add_dependencies(all.tests ${target_name}) endif() - + # Add TOPSRCDIR set_property(SOURCE ${script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") - + # Exclude from 'make all' and 'make install' set_target_properties(${target_name} PROPERTIES EXCLUDE_FROM_ALL ON) @@ -260,7 +279,7 @@ macro(gtsamAddExesGlob_impl globPatterns excludedFiles linkLibraries groupName b # Add executable add_executable(${script_name} ${script_src} ${script_headers}) target_link_libraries(${script_name} ${linkLibraries}) - + # Add target dependencies add_dependencies(${groupName} ${script_name}) if(NOT MSVC AND NOT XCODE_VERSION) @@ -270,7 +289,7 @@ macro(gtsamAddExesGlob_impl globPatterns excludedFiles linkLibraries groupName b # Add TOPSRCDIR set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") - # Exclude from all or not - note weird variable assignment because we're in a macro + # Exclude from all or not - note weird variable assignment because we're in a macro set(buildWithAll_on ${buildWithAll}) if(NOT buildWithAll_on) # Exclude from 'make all' and 'make install' From 96fe0474e601ee2502ac5e26499d2f14bbf6f370 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 10 Jun 2019 10:22:25 -0400 Subject: [PATCH 064/160] Removed redundant flags. Add commented out code to turn off clang on Linux. --- .travis.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index a18fd91ba7..9d88f93ae4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,22 +38,22 @@ jobs: - stage: compile os: osx compiler: gcc - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Mac, CLANG - stage: compile os: osx compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, GCC - stage: compile os: linux compiler: gcc - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, CLANG - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF # on Linux, with deprecated ON to make sure that path still compiles - stage: compile os: linux @@ -69,7 +69,7 @@ compiler: - clang env: global: - - MAKEFLAGS="-j 2" + - MAKEFLAGS="-j2" - CCACHE_SLOPPINESS=pch_defines,time_macros - GTSAM_BUILD_UNSTABLE=ON - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF @@ -77,3 +77,8 @@ env: - GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON - GTSAM_BUILD_TESTS=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF +# Uncomment this if you want to exclude clang on linux +# matrix: +# exclude: +# - os: linux +# compiler: clang From 502bf7cc0440f285bebbef127943991c92eb0d14 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 10 Jun 2019 13:25:30 -0400 Subject: [PATCH 065/160] Test both Debug and Release --- .travis.sh | 2 +- .travis.yml | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.travis.sh b/.travis.sh index 14eb9f455a..b27ec81b0a 100755 --- a/.travis.sh +++ b/.travis.sh @@ -23,7 +23,7 @@ function build_and_test () fi cmake $SOURCE_DIR \ - -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS \ -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ diff --git a/.travis.yml b/.travis.yml index 9d88f93ae4..ac59b5ad92 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,27 +38,27 @@ jobs: - stage: compile os: osx compiler: gcc - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Mac, CLANG - stage: compile os: osx compiler: clang - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Linux, GCC - stage: compile os: linux compiler: gcc - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Linux, CLANG - stage: compile os: linux compiler: clang - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Linux, with deprecated ON to make sure that path still compiles - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # Matrix configuration: os: @@ -73,9 +73,11 @@ env: - CCACHE_SLOPPINESS=pch_defines,time_macros - GTSAM_BUILD_UNSTABLE=ON - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF + - GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + - GTSAM_BUILD_TESTS=ON matrix: - - GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON - - GTSAM_BUILD_TESTS=ON GTSAM_BUILD_EXAMPLES_ALWAYS=OFF + - CMAKE_BUILD_TYPE=Debug + - CMAKE_BUILD_TYPE=release # Uncomment this if you want to exclude clang on linux # matrix: From 2ac505f35ef909acc19c0acd8d218ab2098c7a90 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Mon, 10 Jun 2019 14:04:21 -0400 Subject: [PATCH 066/160] Add Debug/Release in compile stage as well --- .travis.yml | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index ac59b5ad92..d8d1c07b53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,22 +38,38 @@ jobs: - stage: compile os: osx compiler: gcc - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - stage: compile + os: osx + compiler: gcc + env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Mac, CLANG - stage: compile os: osx compiler: clang - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - stage: compile + os: osx + compiler: clang + env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Linux, GCC - stage: compile os: linux compiler: gcc - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - stage: compile + os: linux + compiler: gcc + env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Linux, CLANG - stage: compile os: linux compiler: clang - env: GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + - stage: compile + os: linux + compiler: clang + env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON # on Linux, with deprecated ON to make sure that path still compiles - stage: compile os: linux @@ -77,7 +93,7 @@ env: - GTSAM_BUILD_TESTS=ON matrix: - CMAKE_BUILD_TYPE=Debug - - CMAKE_BUILD_TYPE=release + - CMAKE_BUILD_TYPE=Release # Uncomment this if you want to exclude clang on linux # matrix: From 2d67ade5f232fc91c0063393e509bda23ccc267e Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Tue, 11 Jun 2019 07:19:26 +0200 Subject: [PATCH 067/160] targets renamed: xxx.valgrind --- cmake/GtsamTesting.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index 9ce5981e56..567c29e23e 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -183,11 +183,11 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) # Run with valgrind: set(GENERATED_EXE "$") - add_custom_target(${script_name}.run.valgrind + add_custom_target(${script_name}.valgrind COMMAND "valgrind" "--error-exitcode=1" ${GENERATED_EXE} DEPENDS ${script_name} ) - add_dependencies(check_valgrind ${script_name}.run.valgrind) + add_dependencies(check_valgrind ${script_name}.valgrind) endif() # Add TOPSRCDIR From 05e8fbcb146ed7c4dae13abeb14b03717fba89e2 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Tue, 11 Jun 2019 07:21:10 +0200 Subject: [PATCH 068/160] re-indent: script had an inconsistent mix of tabs and spaces --- cmake/GtsamTesting.cmake | 88 ++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index 567c29e23e..d115f395ad 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -88,36 +88,36 @@ enable_testing() option(GTSAM_BUILD_TESTS "Enable/Disable building of tests" ON) option(GTSAM_BUILD_EXAMPLES_ALWAYS "Build examples with 'make all' (build with 'make examples' if not)" ON) -option(GTSAM_BUILD_TIMING_ALWAYS "Build timing scripts with 'make all' (build with 'make timing' if not" OFF) - -# Add option for combining unit tests -if(MSVC OR XCODE_VERSION) - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) -else() - option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) -endif() -mark_as_advanced(GTSAM_SINGLE_TEST_EXE) - -# Enable make check (http://www.cmake.org/Wiki/CMakeEmulateMakeCheck) -if(GTSAM_BUILD_TESTS) - add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) - # Also add alternative checks using valgrind. - # We don't look for valgrind being installed in the system, since these - # targets are not invoked unless directly instructed by the user. - if (UNIX) - # Run all tests using valgrind: - add_custom_target(check_valgrind) + option(GTSAM_BUILD_TIMING_ALWAYS "Build timing scripts with 'make all' (build with 'make timing' if not" OFF) + + # Add option for combining unit tests + if(MSVC OR XCODE_VERSION) + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" ON) + else() + option(GTSAM_SINGLE_TEST_EXE "Combine unit tests into single executable (faster compile)" OFF) endif() + mark_as_advanced(GTSAM_SINGLE_TEST_EXE) + + # Enable make check (http://www.cmake.org/Wiki/CMakeEmulateMakeCheck) + if(GTSAM_BUILD_TESTS) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) + # Also add alternative checks using valgrind. + # We don't look for valgrind being installed in the system, since these + # targets are not invoked unless directly instructed by the user. + if (UNIX) + # Run all tests using valgrind: + add_custom_target(check_valgrind) + endif() - # Add target to build tests without running - add_custom_target(all.tests) -endif() + # Add target to build tests without running + add_custom_target(all.tests) + endif() -# Add examples target -add_custom_target(examples) + # Add examples target + add_custom_target(examples) -# Add timing target -add_custom_target(timing) + # Add timing target + add_custom_target(timing) # Implementations of this file's macros: @@ -125,23 +125,23 @@ add_custom_target(timing) macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) if(GTSAM_BUILD_TESTS) # Add group target if it doesn't already exist - if(NOT TARGET check.${groupName}) + if(NOT TARGET check.${groupName}) add_custom_target(check.${groupName} COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) set_property(TARGET check.${groupName} PROPERTY FOLDER "Unit tests") endif() - # Get all script files - file(GLOB script_files ${globPatterns}) + # Get all script files + file(GLOB script_files ${globPatterns}) - # Remove excluded scripts from the list - if(NOT "${excludedFiles}" STREQUAL "") + # Remove excluded scripts from the list + if(NOT "${excludedFiles}" STREQUAL "") file(GLOB excludedFilePaths ${excludedFiles}) if("${excludedFilePaths}" STREQUAL "") message(WARNING "The pattern '${excludedFiles}' for excluding tests from group ${groupName} did not match any files") else() - list(REMOVE_ITEM script_files ${excludedFilePaths}) + list(REMOVE_ITEM script_files ${excludedFilePaths}) endif() - endif() + endif() # Separate into source files and headers (allows for adding headers to show up in # MSVC and Xcode projects). @@ -173,10 +173,10 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) add_test(NAME ${script_name} COMMAND ${script_name}) add_dependencies(check.${groupName} ${script_name}) add_dependencies(check ${script_name}) - add_dependencies(all.tests ${script_name}) + add_dependencies(all.tests ${script_name}) if(NOT MSVC AND NOT XCODE_VERSION) # Regular test run: - add_custom_target(${script_name}.run + add_custom_target(${script_name}.run COMMAND ${EXECUTABLE_OUTPUT_PATH}${script_name} DEPENDS ${script_name} ) @@ -242,18 +242,18 @@ endmacro() macro(gtsamAddExesGlob_impl globPatterns excludedFiles linkLibraries groupName buildWithAll) - # Get all script files - file(GLOB script_files ${globPatterns}) + # Get all script files + file(GLOB script_files ${globPatterns}) - # Remove excluded scripts from the list - if(NOT "${excludedFiles}" STREQUAL "") + # Remove excluded scripts from the list + if(NOT "${excludedFiles}" STREQUAL "") file(GLOB excludedFilePaths ${excludedFiles}) if("${excludedFilePaths}" STREQUAL "") message(WARNING "The script exclusion pattern '${excludedFiles}' did not match any files") else() - list(REMOVE_ITEM script_files ${excludedFilePaths}) + list(REMOVE_ITEM script_files ${excludedFilePaths}) endif() - endif() + endif() # Separate into source files and headers (allows for adding headers to show up in # MSVC and Xcode projects). @@ -283,14 +283,14 @@ macro(gtsamAddExesGlob_impl globPatterns excludedFiles linkLibraries groupName b # Add target dependencies add_dependencies(${groupName} ${script_name}) if(NOT MSVC AND NOT XCODE_VERSION) - add_custom_target(${script_name}.run ${EXECUTABLE_OUTPUT_PATH}${script_name} DEPENDS ${script_name}) + add_custom_target(${script_name}.run ${EXECUTABLE_OUTPUT_PATH}${script_name} DEPENDS ${script_name}) endif() # Add TOPSRCDIR set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") - # Exclude from all or not - note weird variable assignment because we're in a macro - set(buildWithAll_on ${buildWithAll}) + # Exclude from all or not - note weird variable assignment because we're in a macro + set(buildWithAll_on ${buildWithAll}) if(NOT buildWithAll_on) # Exclude from 'make all' and 'make install' set_target_properties("${script_name}" PROPERTIES EXCLUDE_FROM_ALL ON) From 07b670423d4bfeec13e6d62783ada024ff212ee0 Mon Sep 17 00:00:00 2001 From: dellaert Date: Tue, 11 Jun 2019 13:40:29 -0400 Subject: [PATCH 069/160] Fixed exception type for TBB path --- tests/testDoglegOptimizer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/testDoglegOptimizer.cpp b/tests/testDoglegOptimizer.cpp index f1f847e593..b0978feb9c 100644 --- a/tests/testDoglegOptimizer.cpp +++ b/tests/testDoglegOptimizer.cpp @@ -10,9 +10,10 @@ * -------------------------------------------------------------------------- */ /** - * @file DoglegOptimizer.h + * @file testDoglegOptimizer.cpp * @brief Unit tests for DoglegOptimizer * @author Richard Roberts + * @author Frank dellaert */ #include @@ -181,7 +182,12 @@ TEST(DoglegOptimizer, Constraint) { // Try optimizing with infeasible initial estimate DoglegOptimizer optimizer2(graph, infeasible, params); + +#ifdef GTSAM_USE_TBB + CHECK_EXCEPTION(optimizer2.optimize(), std::exception); +#else CHECK_EXCEPTION(optimizer2.optimize(), std::invalid_argument); +#endif } /* ************************************************************************* */ From 9d52deaf75d37cdd8aab1ee758a476b7318a470a Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Tue, 11 Jun 2019 18:10:05 -0400 Subject: [PATCH 070/160] added inline keywords to new functions introduced in PR #32 --- gtsam/slam/expressions.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gtsam/slam/expressions.h b/gtsam/slam/expressions.h index 17a8d849f7..4294d17d11 100644 --- a/gtsam/slam/expressions.h +++ b/gtsam/slam/expressions.h @@ -42,7 +42,7 @@ inline Point3_ transformFrom(const Pose3_& x, const Point3_& p) { namespace internal { // define getter that returns value rather than reference -Rot3 rotation(const Pose3& pose, OptionalJacobian<3, 6> H) { +inline Rot3 rotation(const Pose3& pose, OptionalJacobian<3, 6> H) { return pose.rotation(H); } } // namespace internal @@ -70,12 +70,12 @@ inline Unit3_ unrotate(const Rot3_& x, const Unit3_& p) { #ifndef GTSAM_TYPEDEF_POINTS_TO_VECTORS namespace internal { // define a rotate and unrotate for Vector3 -Vector3 rotate(const Rot3& R, const Vector3& v, +inline Vector3 rotate(const Rot3& R, const Vector3& v, OptionalJacobian<3, 3> H1 = boost::none, OptionalJacobian<3, 3> H2 = boost::none) { return R.rotate(v, H1, H2); } -Vector3 unrotate(const Rot3& R, const Vector3& v, +inline Vector3 unrotate(const Rot3& R, const Vector3& v, OptionalJacobian<3, 3> H1 = boost::none, OptionalJacobian<3, 3> H2 = boost::none) { return R.unrotate(v, H1, H2); From 692959f0f31a6bfbaa29e8d814a6c51d4f16d490 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 17:48:24 -0400 Subject: [PATCH 071/160] Fix for issue #38 (gcc specific issue, but fix works for all) --- gtsam_unstable/slam/SmartRangeFactor.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gtsam_unstable/slam/SmartRangeFactor.h b/gtsam_unstable/slam/SmartRangeFactor.h index b3d71d05fd..c90a9646f6 100644 --- a/gtsam_unstable/slam/SmartRangeFactor.h +++ b/gtsam_unstable/slam/SmartRangeFactor.h @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -89,7 +88,6 @@ class SmartRangeFactor: public NoiseModelFactor { * Raise runtime_error if not well defined. */ Point2 triangulate(const Values& x) const { - // gttic_(triangulate); // create n circles corresponding to measured range around each pose std::list circles; size_t n = size(); @@ -100,7 +98,7 @@ class SmartRangeFactor: public NoiseModelFactor { Circle2 circle1 = circles.front(); boost::optional best_fh; - boost::optional bestCircle2; + auto bestCircle2 = boost::make_optional(false, circle1); // fixes issue #38 // loop over all circles for (const Circle2& it : circles) { @@ -136,7 +134,6 @@ class SmartRangeFactor: public NoiseModelFactor { } else { throw std::runtime_error("triangulate failed"); } - // gttoc_(triangulate); } /** From 30644e9590be33c75c23a11c74d0935920343a0b Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 2 Jun 2019 17:55:47 -0400 Subject: [PATCH 072/160] Fix file handling (removed absolute paths) --- .../examples/SmartRangeExample_plaza1.cpp | 30 ++++++++----------- .../examples/SmartRangeExample_plaza2.cpp | 23 +++++++------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp index 0ee601d269..17f21cf9a8 100644 --- a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp +++ b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp @@ -10,7 +10,7 @@ * -------------------------------------------------------------------------- */ /** - * @file SmartRangeExample_plaza2.cpp + * @file SmartRangeExample_plaza1.cpp * @brief A 2D Range SLAM example * @date June 20, 2013 * @author FRank Dellaert @@ -42,6 +42,9 @@ #include #include +// To find data files, we can use `findExampleDataFile`, declared here: +#include + // Standard headers, added last, so we know headers above work on their own #include #include @@ -59,10 +62,9 @@ namespace NM = gtsam::noiseModel; typedef pair TimedOdometry; list readOdometry() { list odometryList; - ifstream is("/Users/dellaert/borg/gtsam/examples/Data/Plaza1_DR.txt"); - if (!is) - throw runtime_error( - "/Users/dellaert/borg/gtsam/examples/Data/Plaza1_DR.txt file not found"); + string drFile = findExampleDataFile("Plaza1_DR.txt"); + ifstream is(drFile); + if (!is) throw runtime_error("Plaza1_DR.txt file not found"); while (is) { double t, distance_traveled, delta_heading; @@ -79,10 +81,9 @@ list readOdometry() { typedef boost::tuple RangeTriple; vector readTriples() { vector triples; - ifstream is("/Users/dellaert/borg/gtsam/examples/Data/Plaza1_TD.txt"); - if (!is) - throw runtime_error( - "/Users/dellaert/borg/gtsam/examples/Data/Plaza1_TD.txt file not found"); + string tdFile = findExampleDataFile("Plaza1_TD.txt"); + ifstream is(tdFile); + if (!is) throw runtime_error("Plaza1_TD.txt file not found"); while (is) { double t, sender, receiver, range; @@ -98,8 +99,6 @@ int main(int argc, char** argv) { // load Plaza1 data list odometry = readOdometry(); -// size_t M = odometry.size(); - vector triples = readTriples(); size_t K = triples.size(); @@ -130,10 +129,8 @@ int main(int argc, char** argv) { NonlinearFactorGraph newFactors; newFactors.push_back(PriorFactor(0, pose0, priorNoise)); - ofstream os2( - "/Users/dellaert/borg/gtsam/gtsam_unstable/examples/rangeResultLM.txt"); - ofstream os3( - "/Users/dellaert/borg/gtsam/gtsam_unstable/examples/rangeResultSR.txt"); + ofstream os2("rangeResultLM.txt"); + ofstream os3("rangeResultSR.txt"); // initialize points (Gaussian) Values initial; @@ -254,8 +251,7 @@ int main(int argc, char** argv) { // Write result to file Values result = isam.calculateEstimate(); - ofstream os( - "/Users/dellaert/borg/gtsam/gtsam_unstable/examples/rangeResult.txt"); + ofstream os("rangeResult.txt"); for(const Values::ConstFiltered::KeyValuePair& it: result.filter()) os << it.key << "\t" << it.value.x() << "\t" << it.value.y() << "\t" << it.value.theta() << endl; diff --git a/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp b/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp index 5f33a215b4..a83eb06acc 100644 --- a/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp +++ b/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp @@ -41,6 +41,9 @@ #include #include +// To find data files, we can use `findExampleDataFile`, declared here: +#include + // Standard headers, added last, so we know headers above work on their own #include #include @@ -58,10 +61,9 @@ namespace NM = gtsam::noiseModel; typedef pair TimedOdometry; list readOdometry() { list odometryList; - ifstream is("/Users/dellaert/borg/gtsam/examples/Data/Plaza1_DR.txt"); - if (!is) - throw runtime_error( - "/Users/dellaert/borg/gtsam/examples/Data/Plaza1_DR.txt file not found"); + string drFile = findExampleDataFile("Plaza2_DR.txt"); + ifstream is(drFile); + if (!is) throw runtime_error("Plaza2_DR.txt file not found"); while (is) { double t, distance_traveled, delta_heading; @@ -78,10 +80,9 @@ list readOdometry() { typedef boost::tuple RangeTriple; vector readTriples() { vector triples; - ifstream is("/Users/dellaert/borg/gtsam/examples/Data/Plaza1_TD.txt"); - if (!is) - throw runtime_error( - "/Users/dellaert/borg/gtsam/examples/Data/Plaza1_TD.txt file not found"); + string tdFile = findExampleDataFile("Plaza2_TD.txt"); + ifstream is(tdFile); + if (!is) throw runtime_error("Plaza2_TD.txt file not found"); while (is) { double t, sender, receiver, range; @@ -201,13 +202,11 @@ int main(int argc, char** argv) { // Write result to file Values result = isam.calculateEstimate(); - ofstream os2( - "/Users/dellaert/borg/gtsam/gtsam_unstable/examples/rangeResultLM.txt"); + ofstream os2("rangeResultLM.txt"); for(const Values::ConstFiltered::KeyValuePair& it: result.filter()) os2 << it.key << "\t" << it.value.x() << "\t" << it.value.y() << "\t1" << endl; - ofstream os( - "/Users/dellaert/borg/gtsam/gtsam_unstable/examples/rangeResult.txt"); + ofstream os("rangeResult.txt"); for(const Values::ConstFiltered::KeyValuePair& it: result.filter()) os << it.key << "\t" << it.value.x() << "\t" << it.value.y() << "\t" << it.value.theta() << endl; From c007c7715c5b6ee71da0b3563576e2508b5039bb Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 9 Jun 2019 13:23:15 -0400 Subject: [PATCH 073/160] Duplicate measurements are disallowed -> leads to duplicate keys in JacobianFactor --- gtsam/slam/SmartFactorBase.h | 20 ++++++++++--------- .../examples/SmartRangeExample_plaza1.cpp | 9 +++++++-- gtsam_unstable/slam/SmartRangeFactor.h | 4 ++++ .../slam/tests/testSmartRangeFactor.cpp | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/gtsam/slam/SmartFactorBase.h b/gtsam/slam/SmartFactorBase.h index 489a4adf40..6e55eb50cf 100644 --- a/gtsam/slam/SmartFactorBase.h +++ b/gtsam/slam/SmartFactorBase.h @@ -118,14 +118,17 @@ class SmartFactorBase: public NonlinearFactor { } /** - * Add a new measurement and pose key - * @param measured_i is the 2m dimensional projection of a single landmark - * @param poseKey is the index corresponding to the camera observing the landmark - * @param sharedNoiseModel is the measurement noise + * Add a new measurement and pose/camera key + * @param measured is the 2m dimensional projection of a single landmark + * @param key is the index corresponding to the camera observing the landmark */ - void add(const Z& measured_i, const Key& cameraKey_i) { - this->measured_.push_back(measured_i); - this->keys_.push_back(cameraKey_i); + void add(const Z& measured, const Key& key) { + if(std::find(keys_.begin(), keys_.end(), key) != keys_.end()) { + throw std::invalid_argument( + "SmartFactorBase::add: adding duplicate measurement for key."); + } + this->measured_.push_back(measured); + this->keys_.push_back(key); } /** @@ -133,8 +136,7 @@ class SmartFactorBase: public NonlinearFactor { */ void add(ZVector& measurements, KeyVector& cameraKeys) { for (size_t i = 0; i < measurements.size(); i++) { - this->measured_.push_back(measurements.at(i)); - this->keys_.push_back(cameraKeys.at(i)); + this->add(measurements.at(i), cameraKeys.at(i)); } } diff --git a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp index 17f21cf9a8..43a9c588ea 100644 --- a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp +++ b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp @@ -185,8 +185,13 @@ int main(int argc, char** argv) { double range = boost::get<2>(triples[k]); if (i > start) { if (smart && totalCount < minK) { - smartFactors[j]->addRange(i, range); - printf("adding range %g for %d on %d",range,(int)j,(int)i);cout << endl; + try { + smartFactors[j]->addRange(i, range); + printf("adding range %g for %d on %d",range,(int)j,(int)i); + } catch (const invalid_argument& e) { + printf("warning: omitting duplicate range %g for %d on %d",range,(int)j,(int)i); + } + cout << endl; } else { RangeFactor factor(i, symbol('L', j), range, diff --git a/gtsam_unstable/slam/SmartRangeFactor.h b/gtsam_unstable/slam/SmartRangeFactor.h index c90a9646f6..c05633345b 100644 --- a/gtsam_unstable/slam/SmartRangeFactor.h +++ b/gtsam_unstable/slam/SmartRangeFactor.h @@ -59,6 +59,10 @@ class SmartRangeFactor: public NoiseModelFactor { /// Add a range measurement to a pose with given key. void addRange(Key key, double measuredRange) { + if(std::find(keys_.begin(), keys_.end(), key) != keys_.end()) { + throw std::invalid_argument( + "SmartRangeFactor::addRange: adding duplicate measurement for key."); + } keys_.push_back(key); measurements_.push_back(measuredRange); size_t n = keys_.size(); diff --git a/gtsam_unstable/slam/tests/testSmartRangeFactor.cpp b/gtsam_unstable/slam/tests/testSmartRangeFactor.cpp index ead8071381..8a6aab6b73 100644 --- a/gtsam_unstable/slam/tests/testSmartRangeFactor.cpp +++ b/gtsam_unstable/slam/tests/testSmartRangeFactor.cpp @@ -46,7 +46,7 @@ TEST( SmartRangeFactor, constructor ) { TEST( SmartRangeFactor, addRange ) { SmartRangeFactor f(sigma); f.addRange(1, 10); - f.addRange(1, 12); + f.addRange(2, 12); LONGS_EQUAL(2, f.size()) } /* ************************************************************************* */ From 6bcbfe2c6788c0ca116f28d27fc1b7774275c3ca Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sun, 9 Jun 2019 19:22:54 -0400 Subject: [PATCH 074/160] Tightened odometry sigmas to avoid ILS --- examples/RangeISAMExample_plaza2.cpp | 4 ++-- gtsam_unstable/examples/SmartRangeExample_plaza1.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/RangeISAMExample_plaza2.cpp b/examples/RangeISAMExample_plaza2.cpp index 032d61a4af..7b1667e122 100644 --- a/examples/RangeISAMExample_plaza2.cpp +++ b/examples/RangeISAMExample_plaza2.cpp @@ -108,7 +108,7 @@ int main (int argc, char** argv) { // Set Noise parameters Vector priorSigmas = Vector3(1,1,M_PI); - Vector odoSigmas = Vector3(0.05, 0.01, 0.2); + Vector odoSigmas = Vector3(0.05, 0.01, 0.1); double sigmaR = 100; // range standard deviation const NM::Base::shared_ptr // all same type priorNoise = NM::Diagonal::Sigmas(priorSigmas), //prior @@ -157,7 +157,7 @@ int main (int argc, char** argv) { boost::tie(t, odometry) = timedOdometry; // add odometry factor - newFactors.push_back(BetweenFactor(i-1, i, odometry,NM::Diagonal::Sigmas(odoSigmas))); + newFactors.push_back(BetweenFactor(i-1, i, odometry, odoNoise)); // predict pose and add as initial estimate Pose2 predictedPose = lastPose.compose(odometry); diff --git a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp index 43a9c588ea..24bab3feb1 100644 --- a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp +++ b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp @@ -111,11 +111,11 @@ int main(int argc, char** argv) { // Set Noise parameters Vector priorSigmas = Vector3(1, 1, M_PI); - Vector odoSigmas = Vector3(0.05, 0.01, 0.2); + Vector odoSigmas = Vector3(0.05, 0.01, 0.1); + auto odoNoise = NM::Diagonal::Sigmas(odoSigmas); double sigmaR = 100; // range standard deviation const NM::Base::shared_ptr // all same type priorNoise = NM::Diagonal::Sigmas(priorSigmas), //prior - odoNoise = NM::Diagonal::Sigmas(odoSigmas), // odometry gaussian = NM::Isotropic::Sigma(1, sigmaR), // non-robust tukey = NM::Robust::Create(NM::mEstimator::Tukey::Create(15), gaussian), //robust rangeNoise = robust ? tukey : gaussian; @@ -167,11 +167,11 @@ int main(int argc, char** argv) { double t; Pose2 odometry; boost::tie(t, odometry) = timedOdometry; + printf("step %d, time = %g\n",(int)i,t); // add odometry factor newFactors.push_back( - BetweenFactor(i - 1, i, odometry, - NM::Diagonal::Sigmas(odoSigmas))); + BetweenFactor(i - 1, i, odometry, odoNoise)); // predict pose and add as initial estimate Pose2 predictedPose = lastPose.compose(odometry); @@ -187,9 +187,9 @@ int main(int argc, char** argv) { if (smart && totalCount < minK) { try { smartFactors[j]->addRange(i, range); - printf("adding range %g for %d on %d",range,(int)j,(int)i); + printf("adding range %g for %d",range,(int)j); } catch (const invalid_argument& e) { - printf("warning: omitting duplicate range %g for %d on %d",range,(int)j,(int)i); + printf("warning: omitting duplicate range %g for %d",range,(int)j); } cout << endl; } From f09d118eb8935c65a1534d1b6acee67c8c42b41e Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 00:23:40 -0400 Subject: [PATCH 075/160] Use command line argument for script so that cache names are not polluted by environment variables. This should allow ccache to use caches from build stage for testing stage. --- .travis.sh | 79 +++++++++++++++++++++++++++++++++++++++-------------- .travis.yml | 35 ++++++++++++++---------- 2 files changed, 79 insertions(+), 35 deletions(-) diff --git a/.travis.sh b/.travis.sh index b27ec81b0a..2a9e1f213a 100755 --- a/.travis.sh +++ b/.travis.sh @@ -1,46 +1,85 @@ #!/bin/bash -set -e # Make sure any error makes the script to return an error code -set -x # echo - -SOURCE_DIR=`pwd` -BUILD_DIR=build +# common tasks before either build or test +function prepare () +{ + set -e # Make sure any error makes the script to return an error code + set -x # echo -#CMAKE_C_FLAGS="-Wall -Wextra -Wabi -O2" -#CMAKE_CXX_FLAGS="-Wall -Wextra -Wabi -O2" + SOURCE_DIR=`pwd` + BUILD_DIR=build -function build_and_test () -{ #env git clean -fd || true rm -fr $BUILD_DIR || true mkdir $BUILD_DIR && cd $BUILD_DIR + if [ -z "$CMAKE_BUILD_TYPE" ]; then + CMAKE_BUILD_TYPE=Debug + fi + + if [ -z "$GTSAM_ALLOW_DEPRECATED_SINCE_V4" ]; then + GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF + fi + if [ ! -z "$GCC_VERSION" ]; then sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-$GCC_VERSION 60 \ --slave /usr/bin/g++ g++ /usr/bin/g++-$GCC_VERSION sudo update-alternatives --set gcc /usr/bin/gcc-$GCC_VERSION fi +} + +# common tasks after either build or test +function finish () +{ + # Print ccache stats + ccache -s + + cd $SOURCE_DIR +} + +# compile the code with the intent of populating the cache +function build () +{ + prepare cmake $SOURCE_DIR \ -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ - -DGTSAM_BUILD_TESTS=$GTSAM_BUILD_TESTS \ - -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ - -DGTSAM_BUILD_EXAMPLES_ALWAYS=$GTSAM_BUILD_EXAMPLES_ALWAYS \ + -DGTSAM_BUILD_TESTS=OFF \ + -DGTSAM_BUILD_UNSTABLE=ON \ + -DGTSAM_BUILD_EXAMPLES_ALWAYS=ON \ -DGTSAM_ALLOW_DEPRECATED_SINCE_V4=$GTSAM_ALLOW_DEPRECATED_SINCE_V4 # Actual build: make -j2 - # Run tests: - if [ "$GTSAM_BUILD_TESTS" == "ON" ]; then - make check - fi + finish +} - # Print ccache stats - ccache -s +# run the tests +function test () +{ + prepare - cd $SOURCE_DIR + cmake $SOURCE_DIR \ + -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ + -DGTSAM_BUILD_TESTS=ON \ + -DGTSAM_BUILD_UNSTABLE=ON \ + -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF \ + -DGTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF + + # Actual build: + make -j2 check + + finish } -build_and_test +# select between build or test +case $1 in + -b) + build + ;; + -t) + test + ;; +esac diff --git a/.travis.yml b/.travis.yml index d8d1c07b53..bdf8d570a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,9 +23,6 @@ install: - if [ "$TRAVIS_OS_NAME" == "osx" ]; then HOMEBREW_NO_AUTO_UPDATE=1 brew install ccache ; fi - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export PATH="/usr/local/opt/ccache/libexec:$PATH" ; fi -script: - - bash .travis.sh - # We first do the compile stage specified below, then the matrix expansion specified after. stages: - compile @@ -38,43 +35,52 @@ jobs: - stage: compile os: osx compiler: gcc - env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug + script: bash .travis.sh -b - stage: compile os: osx compiler: gcc - env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Release + script: bash .travis.sh -b # on Mac, CLANG - stage: compile os: osx compiler: clang - env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug + script: bash .travis.sh -b - stage: compile os: osx compiler: clang - env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Release + script: bash .travis.sh -b # on Linux, GCC - stage: compile os: linux compiler: gcc - env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug + script: bash .travis.sh -b - stage: compile os: linux compiler: gcc - env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Release + script: bash .travis.sh -b # on Linux, CLANG - stage: compile os: linux compiler: clang - env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug + script: bash .travis.sh -b - stage: compile os: linux compiler: clang - env: CMAKE_BUILD_TYPE=Release GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Release + script: bash .travis.sh -b # on Linux, with deprecated ON to make sure that path still compiles - stage: compile os: linux compiler: clang - env: GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON GTSAM_BUILD_TESTS=OFF GTSAM_BUILD_EXAMPLES_ALWAYS=ON + env: CMAKE_BUILD_TYPE=Debug GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON + script: bash .travis.sh -b # Matrix configuration: os: @@ -87,13 +93,12 @@ env: global: - MAKEFLAGS="-j2" - CCACHE_SLOPPINESS=pch_defines,time_macros - - GTSAM_BUILD_UNSTABLE=ON - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF - - GTSAM_BUILD_EXAMPLES_ALWAYS=OFF - - GTSAM_BUILD_TESTS=ON matrix: - CMAKE_BUILD_TYPE=Debug - CMAKE_BUILD_TYPE=Release +script: + - bash .travis.sh -t # Uncomment this if you want to exclude clang on linux # matrix: From f2b3497df598c322c6caf92bf680885bf5842443 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 07:10:28 -0400 Subject: [PATCH 076/160] exclude clang on linux --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index bdf8d570a1..41479c75ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,8 +100,9 @@ env: script: - bash .travis.sh -t -# Uncomment this if you want to exclude clang on linux -# matrix: -# exclude: -# - os: linux -# compiler: clang +Uncomment this if you want to exclude clang on linux +matrix: + exclude: + - os: linux + compiler: clang + env : CMAKE_BUILD_TYPE=Release From e61ce989ca341890d40cef748085a703a763a41e Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 09:03:30 -0400 Subject: [PATCH 077/160] Fix syntax error, add comment mentioning issue #57 --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 41479c75ae..54177fc093 100644 --- a/.travis.yml +++ b/.travis.yml @@ -100,9 +100,9 @@ env: script: - bash .travis.sh -t -Uncomment this if you want to exclude clang on linux +# Exclude clang on Linux/clang in release until issue #57 is solved matrix: exclude: - - os: linux - compiler: clang - env : CMAKE_BUILD_TYPE=Release + - os: linux + compiler: clang + env : CMAKE_BUILD_TYPE=Release From 6f5e332a98e0c66fb73cd786c6d30441c0804f87 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 14:21:08 -0400 Subject: [PATCH 078/160] Reduced time to run very slow testScenario by reducing sample count for estimating covariance. --- gtsam/navigation/ScenarioRunner.cpp | 1 + gtsam/navigation/tests/testScenarios.cpp | 57 ++++++++++++++++-------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/gtsam/navigation/ScenarioRunner.cpp b/gtsam/navigation/ScenarioRunner.cpp index fcba92f60a..3938ce86c4 100644 --- a/gtsam/navigation/ScenarioRunner.cpp +++ b/gtsam/navigation/ScenarioRunner.cpp @@ -31,6 +31,7 @@ static const Matrix3 kIntegrationErrorCovariance = intNoiseVar * I_3x3; PreintegratedImuMeasurements ScenarioRunner::integrate( double T, const Bias& estimatedBias, bool corrupted) const { + gttic_(integrate); PreintegratedImuMeasurements pim(p_, estimatedBias); const double dt = imuSampleTime(); diff --git a/gtsam/navigation/tests/testScenarios.cpp b/gtsam/navigation/tests/testScenarios.cpp index 2129d2d922..adac0fb535 100644 --- a/gtsam/navigation/tests/testScenarios.cpp +++ b/gtsam/navigation/tests/testScenarios.cpp @@ -15,6 +15,8 @@ * @author Frank Dellaert */ +// #define ENABLE_TIMING // uncomment for timing results + #include #include #include @@ -46,6 +48,7 @@ static boost::shared_ptr defaultParams() { /* ************************************************************************* */ TEST(ScenarioRunner, Spin) { + gttic(Spin); // angular velocity 6 degree/sec const double w = 6 * kDegree; const Vector3 W(0, 0, w), V(0, 0, 0); @@ -63,12 +66,12 @@ TEST(ScenarioRunner, Spin) { Matrix6 expected; expected << p->accelerometerCovariance / kDt, Z_3x3, // Z_3x3, p->gyroscopeCovariance / kDt; - Matrix6 actual = runner.estimateNoiseCovariance(10000); + Matrix6 actual = runner.estimateNoiseCovariance(1000); EXPECT(assert_equal(expected, actual, 1e-2)); #endif // Check calculated covariance against Monte Carlo estimate - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 1e-5)); } @@ -79,6 +82,7 @@ ConstantTwistScenario scenario(Z_3x1, Vector3(v, 0, 0)); } /* ************************************************************************* */ TEST(ScenarioRunner, Forward) { + gttic(Forward); using namespace forward; ScenarioRunner runner(scenario, defaultParams(), kDt); const double T = 0.1; // seconds @@ -86,24 +90,26 @@ TEST(ScenarioRunner, Forward) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 1e-5)); } /* ************************************************************************* */ TEST(ScenarioRunner, ForwardWithBias) { + gttic(ForwardWithBias); using namespace forward; ScenarioRunner runner(scenario, defaultParams(), kDt); const double T = 0.1; // seconds auto pim = runner.integrate(T, kNonZeroBias); - Matrix9 estimatedCov = runner.estimateCovariance(T, 1000, kNonZeroBias); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100, kNonZeroBias); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, Circle) { + gttic(Circle); // Forward velocity 2m/s, angular velocity 6 degree/sec const double v = 2, w = 6 * kDegree; ConstantTwistScenario scenario(Vector3(0, 0, w), Vector3(v, 0, 0)); @@ -114,13 +120,14 @@ TEST(ScenarioRunner, Circle) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 0.1)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 1e-5)); } /* ************************************************************************* */ TEST(ScenarioRunner, Loop) { + gttic(Loop); // Forward velocity 2m/s // Pitch up with angular velocity 6 degree/sec (negative in FLU) const double v = 2, w = 6 * kDegree; @@ -132,7 +139,7 @@ TEST(ScenarioRunner, Loop) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 0.1)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 1e-5)); } @@ -156,29 +163,32 @@ const double T = 3; // seconds /* ************************************************************************* */ TEST(ScenarioRunner, Accelerating) { + gttic(Accelerating); using namespace accelerating; ScenarioRunner runner(scenario, defaultParams(), T / 10); auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingWithBias) { + gttic(AcceleratingWithBias); using namespace accelerating; ScenarioRunner runner(scenario, defaultParams(), T / 10, kNonZeroBias); auto pim = runner.integrate(T, kNonZeroBias); - Matrix9 estimatedCov = runner.estimateCovariance(T, 10000, kNonZeroBias); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100, kNonZeroBias); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingAndRotating) { + gttic(AcceleratingAndRotating); using namespace initial; const double a = 0.2; // m/s^2 const Vector3 A(0, a, 0), W(0, 0.1, 0); @@ -190,7 +200,7 @@ TEST(ScenarioRunner, AcceleratingAndRotating) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } @@ -215,29 +225,32 @@ const double T = 3; // seconds /* ************************************************************************* */ TEST(ScenarioRunner, Accelerating2) { + gttic(Accelerating2); using namespace accelerating2; ScenarioRunner runner(scenario, defaultParams(), T / 10); auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingWithBias2) { + gttic(AcceleratingWithBias2); using namespace accelerating2; ScenarioRunner runner(scenario, defaultParams(), T / 10, kNonZeroBias); auto pim = runner.integrate(T, kNonZeroBias); - Matrix9 estimatedCov = runner.estimateCovariance(T, 10000, kNonZeroBias); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100, kNonZeroBias); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingAndRotating2) { + gttic(AcceleratingAndRotating2); using namespace initial2; const double a = 0.2; // m/s^2 const Vector3 A(0, a, 0), W(0, 0.1, 0); @@ -249,7 +262,7 @@ TEST(ScenarioRunner, AcceleratingAndRotating2) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } @@ -275,29 +288,32 @@ const double T = 3; // seconds /* ************************************************************************* */ TEST(ScenarioRunner, Accelerating3) { + gttic(Accelerating3); using namespace accelerating3; ScenarioRunner runner(scenario, defaultParams(), T / 10); auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingWithBias3) { + gttic(AcceleratingWithBias3); using namespace accelerating3; ScenarioRunner runner(scenario, defaultParams(), T / 10, kNonZeroBias); auto pim = runner.integrate(T, kNonZeroBias); - Matrix9 estimatedCov = runner.estimateCovariance(T, 10000, kNonZeroBias); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100, kNonZeroBias); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingAndRotating3) { + gttic(AcceleratingAndRotating3); using namespace initial3; const double a = 0.2; // m/s^2 const Vector3 A(0, a, 0), W(0, 0.1, 0); @@ -309,7 +325,7 @@ TEST(ScenarioRunner, AcceleratingAndRotating3) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } @@ -336,29 +352,32 @@ const double T = 3; // seconds /* ************************************************************************* */ TEST(ScenarioRunner, Accelerating4) { + gttic(Accelerating4); using namespace accelerating4; ScenarioRunner runner(scenario, defaultParams(), T / 10); auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingWithBias4) { + gttic(AcceleratingWithBias4); using namespace accelerating4; ScenarioRunner runner(scenario, defaultParams(), T / 10, kNonZeroBias); auto pim = runner.integrate(T, kNonZeroBias); - Matrix9 estimatedCov = runner.estimateCovariance(T, 10000, kNonZeroBias); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100, kNonZeroBias); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } /* ************************************************************************* */ TEST(ScenarioRunner, AcceleratingAndRotating4) { + gttic(AcceleratingAndRotating4); using namespace initial4; const double a = 0.2; // m/s^2 const Vector3 A(0, a, 0), W(0, 0.1, 0); @@ -370,7 +389,7 @@ TEST(ScenarioRunner, AcceleratingAndRotating4) { auto pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT_NEAR(estimatedCov.diagonal(), pim.preintMeasCov().diagonal(), 0.1); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } @@ -379,7 +398,9 @@ TEST(ScenarioRunner, AcceleratingAndRotating4) { int main() { TestResult tr; auto result = TestRegistry::runAllTests(tr); +#ifdef ENABLE_TIMING tictoc_print_(); +#endif return result; } /* ************************************************************************* */ From d102a223a5a3ec554ef6c9a1d4871df1f6141d6d Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 14:39:04 -0400 Subject: [PATCH 079/160] Reduced time for ImuFactor tests --- .../navigation/tests/testCombinedImuFactor.cpp | 8 ++++---- gtsam/navigation/tests/testImuFactor.cpp | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/gtsam/navigation/tests/testCombinedImuFactor.cpp b/gtsam/navigation/tests/testCombinedImuFactor.cpp index c1c17a6d40..3ef810cad9 100644 --- a/gtsam/navigation/tests/testCombinedImuFactor.cpp +++ b/gtsam/navigation/tests/testCombinedImuFactor.cpp @@ -163,11 +163,11 @@ TEST(CombinedImuFactor, PredictPositionAndVelocity) { // Measurements const Vector3 measuredOmega(0, 0.1, 0); // M_PI/10.0+0.3; const Vector3 measuredAcc(0, 1.1, -kGravity); - const double deltaT = 0.001; + const double deltaT = 0.01; PreintegratedCombinedMeasurements pim(p, bias); - for (int i = 0; i < 1000; ++i) + for (int i = 0; i < 100; ++i) pim.integrateMeasurement(measuredAcc, measuredOmega, deltaT); // Create factor @@ -190,9 +190,9 @@ TEST(CombinedImuFactor, PredictRotation) { PreintegratedCombinedMeasurements pim(p, bias); const Vector3 measuredAcc = - kGravityAlongNavZDown; const Vector3 measuredOmega(0, 0, M_PI / 10.0); - const double deltaT = 0.001; + const double deltaT = 0.01; const double tol = 1e-4; - for (int i = 0; i < 1000; ++i) + for (int i = 0; i < 100; ++i) pim.integrateMeasurement(measuredAcc, measuredOmega, deltaT); const CombinedImuFactor Combinedfactor(X(1), V(1), X(2), V(2), B(1), B(2), pim); diff --git a/gtsam/navigation/tests/testImuFactor.cpp b/gtsam/navigation/tests/testImuFactor.cpp index 48e4dd5e33..46a84ba181 100644 --- a/gtsam/navigation/tests/testImuFactor.cpp +++ b/gtsam/navigation/tests/testImuFactor.cpp @@ -18,6 +18,8 @@ * @author Stephen Williams */ +#define ENABLE_TIMING // uncomment for timing results + #include #include #include @@ -111,7 +113,7 @@ TEST(ImuFactor, Accelerating) { PreintegratedImuMeasurements pim = runner.integrate(T); EXPECT(assert_equal(scenario.pose(T), runner.predict(pim).pose(), 1e-9)); - Matrix9 estimatedCov = runner.estimateCovariance(T); + Matrix9 estimatedCov = runner.estimateCovariance(T, 100); EXPECT(assert_equal(estimatedCov, pim.preintMeasCov(), 0.1)); } @@ -569,6 +571,7 @@ TEST(ImuFactor, ErrorWithBiasesAndSensorBodyDisplacement) { /* ************************************************************************* */ TEST(ImuFactor, PredictPositionAndVelocity) { + gttic(PredictPositionAndVelocity); Bias bias(Vector3(0, 0, 0), Vector3(0, 0, 0)); // Biases (acc, rot) // Measurements @@ -597,6 +600,7 @@ TEST(ImuFactor, PredictPositionAndVelocity) { /* ************************************************************************* */ TEST(ImuFactor, PredictRotation) { + gttic(PredictRotation); Bias bias(Vector3(0, 0, 0), Vector3(0, 0, 0)); // Biases (acc, rot) // Measurements @@ -623,6 +627,7 @@ TEST(ImuFactor, PredictRotation) { /* ************************************************************************* */ TEST(ImuFactor, PredictArbitrary) { + gttic(PredictArbitrary); Pose3 x1; const Vector3 v1(0, 0, 0); @@ -675,6 +680,7 @@ TEST(ImuFactor, PredictArbitrary) { /* ************************************************************************* */ TEST(ImuFactor, bodyPSensorNoBias) { + gttic(bodyPSensorNoBias); Bias bias(Vector3(0, 0, 0), Vector3(0, 0.1, 0)); // Biases (acc, rot) // Rotate sensor (z-down) to body (same as navigation) i.e. z-up @@ -716,10 +722,11 @@ TEST(ImuFactor, bodyPSensorNoBias) { #include TEST(ImuFactor, bodyPSensorWithBias) { + gttic(bodyPSensorWithBias); using noiseModel::Diagonal; typedef Bias Bias; - int numFactors = 80; + int numFactors = 10; Vector6 noiseBetweenBiasSigma; noiseBetweenBiasSigma << Vector3(2.0e-5, 2.0e-5, 2.0e-5), Vector3(3.0e-6, 3.0e-6, 3.0e-6); @@ -899,6 +906,7 @@ TEST(ImuFactor, MergeWithCoriolis) { /* ************************************************************************* */ // Same values as pre-integration test but now testing covariance TEST(ImuFactor, CheckCovariance) { + gttic(CheckCovariance); // Measurements Vector3 measuredAcc(0.1, 0.0, 0.0); Vector3 measuredOmega(M_PI / 100.0, 0.0, 0.0); @@ -922,6 +930,10 @@ TEST(ImuFactor, CheckCovariance) { /* ************************************************************************* */ int main() { TestResult tr; - return TestRegistry::runAllTests(tr); + auto result = TestRegistry::runAllTests(tr); +#ifdef ENABLE_TIMING + tictoc_print_(); +#endif + return result; } /* ************************************************************************* */ From 5b686d3ec367dfa11b9dbcf7810c3444efeb036e Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 15:33:25 -0400 Subject: [PATCH 080/160] Fixed warning --- gtsam_unstable/partition/FindSeparator-inl.h | 1 - 1 file changed, 1 deletion(-) diff --git a/gtsam_unstable/partition/FindSeparator-inl.h b/gtsam_unstable/partition/FindSeparator-inl.h index 2088ee6466..297057e3fe 100644 --- a/gtsam_unstable/partition/FindSeparator-inl.h +++ b/gtsam_unstable/partition/FindSeparator-inl.h @@ -175,7 +175,6 @@ namespace gtsam { namespace partition { void prepareMetisGraph(const GenericGraph& graph, const std::vector& keys, WorkSpace& workspace, sharedInts* ptr_xadj, sharedInts* ptr_adjncy, sharedInts* ptr_adjwgt) { - typedef int Weight; typedef std::vector Weights; typedef std::vector Neighbors; typedef std::pair NeighborsInfo; From 225bd36019879dc275ae2eb1e8bdd42a0d99fbf7 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 15:33:42 -0400 Subject: [PATCH 081/160] Turned off timing --- gtsam/navigation/tests/testImuFactor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/navigation/tests/testImuFactor.cpp b/gtsam/navigation/tests/testImuFactor.cpp index 46a84ba181..460695ec69 100644 --- a/gtsam/navigation/tests/testImuFactor.cpp +++ b/gtsam/navigation/tests/testImuFactor.cpp @@ -18,7 +18,7 @@ * @author Stephen Williams */ -#define ENABLE_TIMING // uncomment for timing results +// #define ENABLE_TIMING // uncomment for timing results #include #include From 7619ae29852b56488349e5ef64a0363c2720d0cc Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 12 Jun 2019 15:37:30 -0400 Subject: [PATCH 082/160] Unstable will not be built or tested in Debug mode: takes too long. --- .travis.sh | 4 ++-- .travis.yml | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.travis.sh b/.travis.sh index 2a9e1f213a..c2feb71f2f 100755 --- a/.travis.sh +++ b/.travis.sh @@ -46,7 +46,7 @@ function build () cmake $SOURCE_DIR \ -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ -DGTSAM_BUILD_TESTS=OFF \ - -DGTSAM_BUILD_UNSTABLE=ON \ + -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=ON \ -DGTSAM_ALLOW_DEPRECATED_SINCE_V4=$GTSAM_ALLOW_DEPRECATED_SINCE_V4 @@ -64,7 +64,7 @@ function test () cmake $SOURCE_DIR \ -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE \ -DGTSAM_BUILD_TESTS=ON \ - -DGTSAM_BUILD_UNSTABLE=ON \ + -DGTSAM_BUILD_UNSTABLE=$GTSAM_BUILD_UNSTABLE \ -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF \ -DGTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF diff --git a/.travis.yml b/.travis.yml index 54177fc093..b2d44a9ccd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ jobs: - stage: compile os: osx compiler: gcc - env: CMAKE_BUILD_TYPE=Debug + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF script: bash .travis.sh -b - stage: compile os: osx @@ -46,7 +46,7 @@ jobs: - stage: compile os: osx compiler: clang - env: CMAKE_BUILD_TYPE=Debug + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF script: bash .travis.sh -b - stage: compile os: osx @@ -57,7 +57,7 @@ jobs: - stage: compile os: linux compiler: gcc - env: CMAKE_BUILD_TYPE=Debug + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF script: bash .travis.sh -b - stage: compile os: linux @@ -68,7 +68,7 @@ jobs: - stage: compile os: linux compiler: clang - env: CMAKE_BUILD_TYPE=Debug + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF script: bash .travis.sh -b - stage: compile os: linux @@ -79,7 +79,7 @@ jobs: - stage: compile os: linux compiler: clang - env: CMAKE_BUILD_TYPE=Debug GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON + env: CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF GTSAM_ALLOW_DEPRECATED_SINCE_V4=ON script: bash .travis.sh -b # Matrix configuration: @@ -94,8 +94,9 @@ env: - MAKEFLAGS="-j2" - CCACHE_SLOPPINESS=pch_defines,time_macros - GTSAM_ALLOW_DEPRECATED_SINCE_V4=OFF + - GTSAM_BUILD_UNSTABLE=ON matrix: - - CMAKE_BUILD_TYPE=Debug + - CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF - CMAKE_BUILD_TYPE=Release script: - bash .travis.sh -t From 39e08c9a4e54d2de1b4a76e16f7e581231dd0c36 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Wed, 12 Jun 2019 11:53:27 +0200 Subject: [PATCH 083/160] fix warning "const" ignored in this return type --- gtsam/inference/VariableIndex.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/inference/VariableIndex.h b/gtsam/inference/VariableIndex.h index 0d73204289..f2ba3e31cb 100644 --- a/gtsam/inference/VariableIndex.h +++ b/gtsam/inference/VariableIndex.h @@ -95,7 +95,7 @@ class GTSAM_EXPORT VariableIndex { } /// Return true if no factors associated with a variable - const bool empty(Key variable) const { + bool empty(Key variable) const { return (*this)[variable].empty(); } From e75c3ba3d396338d7d0608cca9762ea13124532f Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 13 Jun 2019 16:59:24 -0400 Subject: [PATCH 084/160] remove old, obsolete files --- bitbucket-pipelines.yml | 15 - gtsampy.h | 2658 --------------------------------------- 2 files changed, 2673 deletions(-) delete mode 100644 bitbucket-pipelines.yml delete mode 100644 gtsampy.h diff --git a/bitbucket-pipelines.yml b/bitbucket-pipelines.yml deleted file mode 100644 index 9a94aa86c8..0000000000 --- a/bitbucket-pipelines.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Built from sample configuration for C++ – Make. -# Check https://confluence.atlassian.com/x/5Q4SMw for more examples. -# ----- -# Our custom docker image from Docker Hub as the build environment. -image: dellaert/ubuntu-boost-tbb-eigen3:bionic - -pipelines: - default: - - step: - script: # Modify the commands below to build your repository. - - mkdir build - - cd build - - cmake -DGTSAM_USE_SYSTEM_EIGEN=OFF -DGTSAM_USE_EIGEN_MKL=OFF .. - - make -j2 - - make -j2 check \ No newline at end of file diff --git a/gtsampy.h b/gtsampy.h deleted file mode 100644 index cc5ccb1b4b..0000000000 --- a/gtsampy.h +++ /dev/null @@ -1,2658 +0,0 @@ -/** - - * GTSAM Wrap Module Definition - * - * These are the current classes available through the matlab toolbox interface, - * add more functions/classes as they are available. - * - * Requirements: - * Classes must start with an uppercase letter - * - Can wrap a typedef - * Only one Method/Constructor per line, though methods/constructors can extend across multiple lines - * Methods can return - * - Eigen types: Matrix, Vector - * - C/C++ basic types: string, bool, size_t, int, double, char, unsigned char - * - void - * - Any class with which be copied with boost::make_shared() - * - boost::shared_ptr of any object type - * Constructors - * - Overloads are supported - * - A class with no constructors can be returned from other functions but not allocated directly in MATLAB - * Methods - * - Constness has no effect - * - Specify by-value (not reference) return types, even if C++ method returns reference - * - Must start with a letter (upper or lowercase) - * - Overloads are supported - * Static methods - * - Must start with a letter (upper or lowercase) and use the "static" keyword - * - The first letter will be made uppercase in the generated MATLAB interface - * - Overloads are supported - * Arguments to functions any of - * - Eigen types: Matrix, Vector - * - Eigen types and classes as an optionally const reference - * - C/C++ basic types: string, bool, size_t, size_t, double, char, unsigned char - * - Any class with which be copied with boost::make_shared() (except Eigen) - * - boost::shared_ptr of any object type (except Eigen) - * Comments can use either C++ or C style, with multiple lines - * Namespace definitions - * - Names of namespaces must start with a lowercase letter - * - start a namespace with "namespace {" - * - end a namespace with exactly "}" - * - Namespaces can be nested - * Namespace usage - * - Namespaces can be specified for classes in arguments and return values - * - In each case, the namespace must be fully specified, e.g., "namespace1::namespace2::ClassName" - * Includes in C++ wrappers - * - All includes will be collected and added in a single file - * - All namespaces must have angle brackets: - * - No default includes will be added - * Global/Namespace functions - * - Functions specified outside of a class are global - * - Can be overloaded with different arguments - * - Can have multiple functions of the same name in different namespaces - * Using classes defined in other modules - * - If you are using a class 'OtherClass' not wrapped in this definition file, add "class OtherClass;" to avoid a dependency error - * Virtual inheritance - * - Specify fully-qualified base classes, i.e. "virtual class Derived : ns::Base {" where "ns" is the namespace - * - Mark with 'virtual' keyword, e.g. "virtual class Base {", and also "virtual class Derived : ns::Base {" - * - Forward declarations must also be marked virtual, e.g. "virtual class ns::Base;" and - * also "virtual class ns::Derived;" - * - Pure virtual (abstract) classes should list no constructors in this interface file - * - Virtual classes must have a clone() function in C++ (though it does not have to be included - * in the MATLAB interface). clone() will be called whenever an object copy is needed, instead - * of using the copy constructor (which is used for non-virtual objects). - * - Signature of clone function - will be called virtually, so must appear at least at the top of the inheritance tree - * virtual boost::shared_ptr clone() const; - * Class Templates - * - Basic templates are supported either with an explicit list of types to instantiate, - * e.g. template class Class1 { ... }; - * or with typedefs, e.g. - * template class Class2 { ... }; - * typedef Class2 MyInstantiatedClass; - * - In the class definition, appearances of the template argument(s) will be replaced with their - * instantiated types, e.g. 'void setValue(const T& value);'. - * - To refer to the instantiation of the template class itself, use 'This', i.e. 'static This Create();' - * - To create new instantiations in other modules, you must copy-and-paste the whole class definition - * into the new module, but use only your new instantiation types. - * - When forward-declaring template instantiations, use the generated/typedefed name, e.g. - * class gtsam::Class1Pose2; - * class gtsam::MyInstantiatedClass; - * Boost.serialization within Matlab: - * - you need to mark classes as being serializable in the markup file (see this file for an example). - * - There are two options currently, depending on the class. To "mark" a class as serializable, - * add a function with a particular signature so that wrap will catch it. - * - Add "void serialize()" to a class to create serialization functions for a class. - * Adding this flag subsumes the serializable() flag below. Requirements: - * - A default constructor must be publicly accessible - * - Must not be an abstract base class - * - The class must have an actual boost.serialization serialize() function. - * - Add "void serializable()" to a class if you only want the class to be serialized as a - * part of a container (such as noisemodel). This version does not require a publicly - * accessible default constructor. - */ - -/** - * Status: - * - TODO: default values for arguments - * - WORKAROUND: make multiple versions of the same function for different configurations of default arguments - * - TODO: Handle gtsam::Rot3M conversions to quaternions - * - TODO: Parse return of const ref arguments - * - TODO: Parse std::string variants and convert directly to special string - * - TODO: Add enum support - * - TODO: Add generalized serialization support via boost.serialization with hooks to matlab save/load - */ - -namespace std { - #include - template - class vector - { - //Do we need these? - //Capacity - /*size_t size() const; - size_t max_size() const; - //void resize(size_t sz); - size_t capacity() const; - bool empty() const; - void reserve(size_t n); - - //Element access - T* at(size_t n); - T* front(); - T* back(); - - //Modifiers - void assign(size_t n, const T& u); - void push_back(const T& x); - void pop_back();*/ - }; - //typedef std::vector - - #include - template - class list - { - - - }; - -} - -namespace gtsam { - -//************************************************************************* -// base -//************************************************************************* - -/** gtsam namespace functions */ -bool linear_independent(Matrix A, Matrix B, double tol); - -virtual class Value { - // No constructors because this is an abstract class - - // Testable - void print(string s) const; - - // Manifold - size_t dim() const; -}; - -#include -class LieScalar { - // Standard constructors - LieScalar(); - LieScalar(double d); - - // Standard interface - double value() const; - - // Testable - void print(string s) const; - bool equals(const gtsam::LieScalar& expected, double tol) const; - - // Group - static gtsam::LieScalar identity(); - gtsam::LieScalar inverse() const; - gtsam::LieScalar compose(const gtsam::LieScalar& p) const; - gtsam::LieScalar between(const gtsam::LieScalar& l2) const; - - // Manifold - size_t dim() const; - gtsam::LieScalar retract(Vector v) const; - Vector localCoordinates(const gtsam::LieScalar& t2) const; - - // Lie group - static gtsam::LieScalar Expmap(Vector v); - static Vector Logmap(const gtsam::LieScalar& p); -}; - -#include -class LieVector { - // Standard constructors - LieVector(); - LieVector(Vector v); - - // Standard interface - Vector vector() const; - - // Testable - void print(string s) const; - bool equals(const gtsam::LieVector& expected, double tol) const; - - // Group - static gtsam::LieVector identity(); - gtsam::LieVector inverse() const; - gtsam::LieVector compose(const gtsam::LieVector& p) const; - gtsam::LieVector between(const gtsam::LieVector& l2) const; - - // Manifold - size_t dim() const; - gtsam::LieVector retract(Vector v) const; - Vector localCoordinates(const gtsam::LieVector& t2) const; - - // Lie group - static gtsam::LieVector Expmap(Vector v); - static Vector Logmap(const gtsam::LieVector& p); - - // enabling serialization functionality - void serialize() const; -}; - -#include -class LieMatrix { - // Standard constructors - LieMatrix(); - LieMatrix(Matrix v); - - // Standard interface - Matrix matrix() const; - - // Testable - void print(string s) const; - bool equals(const gtsam::LieMatrix& expected, double tol) const; - - // Group - static gtsam::LieMatrix identity(); - gtsam::LieMatrix inverse() const; - gtsam::LieMatrix compose(const gtsam::LieMatrix& p) const; - gtsam::LieMatrix between(const gtsam::LieMatrix& l2) const; - - // Manifold - size_t dim() const; - gtsam::LieMatrix retract(Vector v) const; - Vector localCoordinates(const gtsam::LieMatrix & t2) const; - - // Lie group - static gtsam::LieMatrix Expmap(Vector v); - static Vector Logmap(const gtsam::LieMatrix& p); - - // enabling serialization functionality - void serialize() const; -}; - -//************************************************************************* -// geometry -//************************************************************************* - -class Point2 { - // Standard Constructors - Point2(); - Point2(double x, double y); - Point2(Vector v); - - // Testable - void print(string s) const; - bool equals(const gtsam::Point2& pose, double tol) const; - - // Group - static gtsam::Point2 identity(); - gtsam::Point2 inverse() const; - gtsam::Point2 compose(const gtsam::Point2& p2) const; - gtsam::Point2 between(const gtsam::Point2& p2) const; - - // Manifold - gtsam::Point2 retract(Vector v) const; - Vector localCoordinates(const gtsam::Point2& p) const; - - // Lie Group - static gtsam::Point2 Expmap(Vector v); - static Vector Logmap(const gtsam::Point2& p); - - // Standard Interface - double x() const; - double y() const; - Vector vector() const; - double dist(const gtsam::Point2& p2) const; - double norm() const; - - // enabling serialization functionality - void serialize() const; -}; - -// std::vector -class Point2Vector -{ - // Constructors - Point2Vector(); - Point2Vector(const gtsam::Point2Vector& v); - - //Capacity - size_t size() const; - size_t max_size() const; - void resize(size_t sz); - size_t capacity() const; - bool empty() const; - void reserve(size_t n); - - //Element access - gtsam::Point2 at(size_t n) const; - gtsam::Point2 front() const; - gtsam::Point2 back() const; - - //Modifiers - void assign(size_t n, const gtsam::Point2& u); - void push_back(const gtsam::Point2& x); - void pop_back(); -}; - -class StereoPoint2 { - // Standard Constructors - StereoPoint2(); - StereoPoint2(double uL, double uR, double v); - - // Testable - void print(string s) const; - bool equals(const gtsam::StereoPoint2& point, double tol) const; - - // Group - static gtsam::StereoPoint2 identity(); - gtsam::StereoPoint2 inverse() const; - gtsam::StereoPoint2 compose(const gtsam::StereoPoint2& p2) const; - gtsam::StereoPoint2 between(const gtsam::StereoPoint2& p2) const; - - // Manifold - gtsam::StereoPoint2 retract(Vector v) const; - Vector localCoordinates(const gtsam::StereoPoint2& p) const; - - // Lie Group - static gtsam::StereoPoint2 Expmap(Vector v); - static Vector Logmap(const gtsam::StereoPoint2& p); - - // Standard Interface - Vector vector() const; - double uL() const; - double uR() const; - double v() const; - - // enabling serialization functionality - void serialize() const; -}; - -class Point3 { - // Standard Constructors - Point3(); - Point3(double x, double y, double z); - Point3(Vector v); - - // Testable - void print(string s) const; - bool equals(const gtsam::Point3& p, double tol) const; - - // Group - static gtsam::Point3 identity(); - - // Standard Interface - Vector vector() const; - double x() const; - double y() const; - double z() const; - - // enabling serialization functionality - void serialize() const; -}; - -class Rot2 { - // Standard Constructors and Named Constructors - Rot2(); - Rot2(double theta); - static gtsam::Rot2 fromAngle(double theta); - static gtsam::Rot2 fromDegrees(double theta); - static gtsam::Rot2 fromCosSin(double c, double s); - - // Testable - void print(string s) const; - bool equals(const gtsam::Rot2& rot, double tol) const; - - // Group - static gtsam::Rot2 identity(); - gtsam::Rot2 inverse(); - gtsam::Rot2 compose(const gtsam::Rot2& p2) const; - gtsam::Rot2 between(const gtsam::Rot2& p2) const; - - // Manifold - gtsam::Rot2 retract(Vector v) const; - Vector localCoordinates(const gtsam::Rot2& p) const; - - // Lie Group - static gtsam::Rot2 Expmap(Vector v); - static Vector Logmap(const gtsam::Rot2& p); - - // Group Action on Point2 - gtsam::Point2 rotate(const gtsam::Point2& point) const; - gtsam::Point2 unrotate(const gtsam::Point2& point) const; - - // Standard Interface - static gtsam::Rot2 relativeBearing(const gtsam::Point2& d); // Ignoring derivative - static gtsam::Rot2 atan2(double y, double x); - double theta() const; - double degrees() const; - double c() const; - double s() const; - Matrix matrix() const; - - // enabling serialization functionality - void serialize() const; -}; - -class Rot3 { - // Standard Constructors and Named Constructors - Rot3(); - Rot3(Matrix R); - static gtsam::Rot3 Rx(double t); - static gtsam::Rot3 Ry(double t); - static gtsam::Rot3 Rz(double t); - static gtsam::Rot3 RzRyRx(double x, double y, double z); - static gtsam::Rot3 RzRyRx(Vector xyz); - static gtsam::Rot3 yaw(double t); // positive yaw is to right (as in aircraft heading) - static gtsam::Rot3 pitch(double t); // positive pitch is up (increasing aircraft altitude) - static gtsam::Rot3 roll(double t); // positive roll is to right (increasing yaw in aircraft) - static gtsam::Rot3 ypr(double y, double p, double r); - static gtsam::Rot3 quaternion(double w, double x, double y, double z); - static gtsam::Rot3 Rodrigues(Vector v); - - // Testable - void print(string s) const; - bool equals(const gtsam::Rot3& rot, double tol) const; - - // Group - static gtsam::Rot3 identity(); - gtsam::Rot3 inverse() const; - gtsam::Rot3 compose(const gtsam::Rot3& p2) const; - gtsam::Rot3 between(const gtsam::Rot3& p2) const; - - // Manifold - //gtsam::Rot3 retractCayley(Vector v) const; // FIXME, does not exist in both Matrix and Quaternion options - gtsam::Rot3 retract(Vector v) const; - Vector localCoordinates(const gtsam::Rot3& p) const; - - // Group Action on Point3 - gtsam::Point3 rotate(const gtsam::Point3& p) const; - gtsam::Point3 unrotate(const gtsam::Point3& p) const; - - // Standard Interface - static gtsam::Rot3 Expmap(Vector v); - static Vector Logmap(const gtsam::Rot3& p); - Matrix matrix() const; - Matrix transpose() const; - gtsam::Point3 column(size_t index) const; - Vector xyz() const; - Vector ypr() const; - Vector rpy() const; - double roll() const; - double pitch() const; - double yaw() const; -// Vector toQuaternion() const; // FIXME: Can't cast to Vector properly - Vector quaternion() const; - - // enabling serialization functionality - void serialize() const; -}; - -class Pose2 { - // Standard Constructor - Pose2(); - Pose2(const gtsam::Pose2& pose); - Pose2(double x, double y, double theta); - Pose2(double theta, const gtsam::Point2& t); - Pose2(const gtsam::Rot2& r, const gtsam::Point2& t); - Pose2(Vector v); - - // Testable - void print(string s) const; - bool equals(const gtsam::Pose2& pose, double tol) const; - - // Group - static gtsam::Pose2 identity(); - gtsam::Pose2 inverse() const; - gtsam::Pose2 compose(const gtsam::Pose2& p2) const; - gtsam::Pose2 between(const gtsam::Pose2& p2) const; - - // Manifold - gtsam::Pose2 retract(Vector v) const; - Vector localCoordinates(const gtsam::Pose2& p) const; - - // Lie Group - static gtsam::Pose2 Expmap(Vector v); - static Vector Logmap(const gtsam::Pose2& p); - Matrix AdjointMap() const; - Vector Adjoint(const Vector& xi) const; - static Matrix wedge(double vx, double vy, double w); - - // Group Actions on Point2 - gtsam::Point2 transformFrom(const gtsam::Point2& p) const; - gtsam::Point2 transformTo(const gtsam::Point2& p) const; - - // Standard Interface - double x() const; - double y() const; - double theta() const; - gtsam::Rot2 bearing(const gtsam::Point2& point) const; - double range(const gtsam::Point2& point) const; - gtsam::Point2 translation() const; - gtsam::Rot2 rotation() const; - Matrix matrix() const; - - // enabling serialization functionality - void serialize() const; -}; - -class Pose3 { - // Standard Constructors - Pose3(); - Pose3(const gtsam::Pose3& pose); - Pose3(const gtsam::Rot3& r, const gtsam::Point3& t); - Pose3(const gtsam::Pose2& pose2); // FIXME: shadows Pose3(Pose3 pose) - Pose3(Matrix t); - - // Testable - void print(string s) const; - bool equals(const gtsam::Pose3& pose, double tol) const; - - // Group - static gtsam::Pose3 identity(); - gtsam::Pose3 inverse() const; - gtsam::Pose3 compose(const gtsam::Pose3& p2) const; - gtsam::Pose3 between(const gtsam::Pose3& p2) const; - - // Manifold - gtsam::Pose3 retract(Vector v) const; - Vector localCoordinates(const gtsam::Pose3& T2) const; - - // Lie Group - static gtsam::Pose3 Expmap(Vector v); - static Vector Logmap(const gtsam::Pose3& p); - Matrix AdjointMap() const; - Vector Adjoint(Vector xi) const; - static Matrix wedge(double wx, double wy, double wz, double vx, double vy, double vz); - - // Group Action on Point3 - gtsam::Point3 transformFrom(const gtsam::Point3& p) const; - gtsam::Point3 transformTo(const gtsam::Point3& p) const; - - // Standard Interface - gtsam::Rot3 rotation() const; - gtsam::Point3 translation() const; - double x() const; - double y() const; - double z() const; - Matrix matrix() const; - gtsam::Pose3 transformTo(const gtsam::Pose3& pose) const; // FIXME: shadows other transformTo() - double range(const gtsam::Point3& point); - double range(const gtsam::Pose3& pose); - - // enabling serialization functionality - void serialize() const; -}; - -// std::vector -class Pose3Vector -{ - Pose3Vector(); - size_t size() const; - bool empty() const; - gtsam::Pose3 at(size_t n) const; - void push_back(const gtsam::Pose3& x); -}; - -#include -class Unit3 { - // Standard Constructors - Unit3(); - Unit3(const gtsam::Point3& pose); - - // Testable - void print(string s) const; - bool equals(const gtsam::Unit3& pose, double tol) const; - - // Other functionality - Matrix basis() const; - Matrix skew() const; - - // Manifold - static size_t Dim(); - size_t dim() const; - gtsam::Unit3 retract(Vector v) const; - Vector localCoordinates(const gtsam::Unit3& s) const; -}; - -#include -class EssentialMatrix { - // Standard Constructors - EssentialMatrix(const gtsam::Rot3& aRb, const gtsam::Unit3& aTb); - - // Testable - void print(string s) const; - bool equals(const gtsam::EssentialMatrix& pose, double tol) const; - - // Manifold - static size_t Dim(); - size_t dim() const; - gtsam::EssentialMatrix retract(Vector v) const; - Vector localCoordinates(const gtsam::EssentialMatrix& s) const; - - // Other methods: - gtsam::Rot3 rotation() const; - gtsam::Unit3 direction() const; - Matrix matrix() const; - double error(Vector vA, Vector vB); -}; - -class Cal3_S2 { - // Standard Constructors - Cal3_S2(); - Cal3_S2(double fx, double fy, double s, double u0, double v0); - Cal3_S2(Vector v); - Cal3_S2(double fov, int w, int h); - - // Testable - void print(string s) const; - bool equals(const gtsam::Cal3_S2& rhs, double tol) const; - - // Manifold - static size_t Dim(); - size_t dim() const; - gtsam::Cal3_S2 retract(Vector v) const; - Vector localCoordinates(const gtsam::Cal3_S2& c) const; - - // Action on Point2 - gtsam::Point2 calibrate(const gtsam::Point2& p) const; - gtsam::Point2 uncalibrate(const gtsam::Point2& p) const; - - // Standard Interface - double fx() const; - double fy() const; - double skew() const; - double px() const; - double py() const; - gtsam::Point2 principalPoint() const; - Vector vector() const; - Matrix matrix() const; - Matrix matrix_inverse() const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class Cal3DS2_Base { - // Standard Constructors - Cal3DS2_Base(); - - // Testable - void print(string s) const; - - // Standard Interface - double fx() const; - double fy() const; - double skew() const; - double px() const; - double py() const; - double k1() const; - double k2() const; - - // Action on Point2 - gtsam::Point2 uncalibrate(const gtsam::Point2& p) const; - gtsam::Point2 calibrate(const gtsam::Point2& p, double tol) const; - gtsam::Point2 calibrate(const gtsam::Point2& p) const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class Cal3DS2 : gtsam::Cal3DS2_Base { - // Standard Constructors - Cal3DS2(); - Cal3DS2(double fx, double fy, double s, double u0, double v0, double k1, double k2); - Cal3DS2(double fx, double fy, double s, double u0, double v0, double k1, double k2, double p1, double p2); - Cal3DS2(Vector v); - - // Testable - bool equals(const gtsam::Cal3DS2& rhs, double tol) const; - - // Manifold - size_t dim() const; - static size_t Dim(); - gtsam::Cal3DS2 retract(Vector v) const; - Vector localCoordinates(const gtsam::Cal3DS2& c) const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class Cal3Unified : gtsam::Cal3DS2_Base { - // Standard Constructors - Cal3Unified(); - Cal3Unified(double fx, double fy, double s, double u0, double v0, double k1, double k2); - Cal3Unified(double fx, double fy, double s, double u0, double v0, double k1, double k2, double p1, double p2, double xi); - Cal3Unified(Vector v); - - // Testable - bool equals(const gtsam::Cal3Unified& rhs, double tol) const; - - // Standard Interface - double xi() const; - gtsam::Point2 spaceToNPlane(const gtsam::Point2& p) const; - gtsam::Point2 nPlaneToSpace(const gtsam::Point2& p) const; - - // Manifold - size_t dim() const; - static size_t Dim(); - gtsam::Cal3Unified retract(Vector v) const; - Vector localCoordinates(const gtsam::Cal3Unified& c) const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -class Cal3_S2Stereo { - // Standard Constructors - Cal3_S2Stereo(); - Cal3_S2Stereo(double fx, double fy, double s, double u0, double v0, double b); - Cal3_S2Stereo(Vector v); - - // Testable - void print(string s) const; - bool equals(const gtsam::Cal3_S2Stereo& K, double tol) const; - - // Standard Interface - double fx() const; - double fy() const; - double skew() const; - double px() const; - double py() const; - gtsam::Point2 principalPoint() const; - double baseline() const; -}; - -#include -class Cal3Bundler { - // Standard Constructors - Cal3Bundler(); - Cal3Bundler(double fx, double k1, double k2, double u0, double v0); - - // Testable - void print(string s) const; - bool equals(const gtsam::Cal3Bundler& rhs, double tol) const; - - // Manifold - static size_t Dim(); - size_t dim() const; - gtsam::Cal3Bundler retract(Vector v) const; - Vector localCoordinates(const gtsam::Cal3Bundler& c) const; - - // Action on Point2 - gtsam::Point2 calibrate(const gtsam::Point2& p, double tol) const; - gtsam::Point2 calibrate(const gtsam::Point2& p) const; - gtsam::Point2 uncalibrate(const gtsam::Point2& p) const; - - // Standard Interface - double fx() const; - double fy() const; - double k1() const; - double k2() const; - double u0() const; - double v0() const; - Vector vector() const; - Vector k() const; - //Matrix K() const; //FIXME: Uppercase - - // enabling serialization functionality - void serialize() const; -}; - -class CalibratedCamera { - // Standard Constructors and Named Constructors - CalibratedCamera(); - CalibratedCamera(const gtsam::Pose3& pose); - CalibratedCamera(const Vector& v); - static gtsam::CalibratedCamera Level(const gtsam::Pose2& pose2, double height); - - // Testable - void print(string s) const; - bool equals(const gtsam::CalibratedCamera& camera, double tol) const; - - // Manifold - static size_t Dim(); - size_t dim() const; - gtsam::CalibratedCamera retract(const Vector& d) const; - Vector localCoordinates(const gtsam::CalibratedCamera& T2) const; - - // Action on Point3 - gtsam::Point2 project(const gtsam::Point3& point) const; - static gtsam::Point2 Project(const gtsam::Point3& cameraPoint); - - // Standard Interface - gtsam::Pose3 pose() const; - double range(const gtsam::Point3& p) const; // TODO: Other overloaded range methods - - // enabling serialization functionality - void serialize() const; -}; - -template -class PinholeCamera { - // Standard Constructors and Named Constructors - PinholeCamera(); - PinholeCamera(const gtsam::Pose3& pose); - PinholeCamera(const gtsam::Pose3& pose, const CALIBRATION& K); - static This Level(const CALIBRATION& K, const gtsam::Pose2& pose, double height); - static This Level(const gtsam::Pose2& pose, double height); - static This Lookat(const gtsam::Point3& eye, const gtsam::Point3& target, - const gtsam::Point3& upVector, const CALIBRATION& K); - - // Testable - void print(string s) const; - bool equals(const This& camera, double tol) const; - - // Standard Interface - gtsam::Pose3 pose() const; - CALIBRATION calibration() const; - - // Manifold - This retract(const Vector& d) const; - Vector localCoordinates(const This& T2) const; - size_t dim() const; - static size_t Dim(); - - // Transformations and measurement functions - static gtsam::Point2 Project(const gtsam::Point3& cameraPoint); - pair projectSafe(const gtsam::Point3& pw) const; - gtsam::Point2 project(const gtsam::Point3& point); - gtsam::Point3 backproject(const gtsam::Point2& p, double depth) const; - double range(const gtsam::Point3& point); - double range(const gtsam::Pose3& point); - - // enabling serialization functionality - void serialize() const; -}; - -virtual class SimpleCamera { - // Standard Constructors and Named Constructors - SimpleCamera(); - SimpleCamera(const gtsam::Pose3& pose); - SimpleCamera(const gtsam::Pose3& pose, const gtsam::Cal3_S2& K); - static gtsam::SimpleCamera Level(const gtsam::Cal3_S2& K, const gtsam::Pose2& pose, double height); - static gtsam::SimpleCamera Level(const gtsam::Pose2& pose, double height); - static gtsam::SimpleCamera Lookat(const gtsam::Point3& eye, const gtsam::Point3& target, - const gtsam::Point3& upVector, const gtsam::Cal3_S2& K); - - // Testable - void print(string s) const; - bool equals(const gtsam::SimpleCamera& camera, double tol) const; - - // Standard Interface - gtsam::Pose3 pose() const; - gtsam::Cal3_S2 calibration() const; - - // Manifold - gtsam::SimpleCamera retract(const Vector& d) const; - Vector localCoordinates(const gtsam::SimpleCamera& T2) const; - size_t dim() const; - static size_t Dim(); - - // Transformations and measurement functions - static gtsam::Point2 Project(const gtsam::Point3& cameraPoint); - pair projectSafe(const gtsam::Point3& pw) const; - gtsam::Point2 project(const gtsam::Point3& point); - gtsam::Point3 backproject(const gtsam::Point2& p, double depth) const; - double range(const gtsam::Point3& point); - double range(const gtsam::Pose3& point); - - // enabling serialization functionality - void serialize() const; - -}; - -// Some typedefs for common camera types -// PinholeCameraCal3_S2 is the same as SimpleCamera above -typedef gtsam::PinholeCamera PinholeCameraCal3_S2; -typedef gtsam::PinholeCamera PinholeCameraCal3DS2; -typedef gtsam::PinholeCamera PinholeCameraCal3Unified; -typedef gtsam::PinholeCamera PinholeCameraCal3Bundler; - -class StereoCamera { - // Standard Constructors and Named Constructors - StereoCamera(); - StereoCamera(const gtsam::Pose3& pose, const gtsam::Cal3_S2Stereo* K); - - // Testable - void print(string s) const; - bool equals(const gtsam::StereoCamera& camera, double tol) const; - - // Standard Interface - gtsam::Pose3 pose() const; - double baseline() const; - gtsam::Cal3_S2Stereo calibration() const; - - // Manifold - gtsam::StereoCamera retract(const Vector& d) const; - Vector localCoordinates(const gtsam::StereoCamera& T2) const; - size_t dim() const; - static size_t Dim(); - - // Transformations and measurement functions - gtsam::StereoPoint2 project(const gtsam::Point3& point); - gtsam::Point3 backproject(const gtsam::StereoPoint2& p) const; - - // enabling serialization functionality - void serialize() const; -}; - -#include - -// Templates appear not yet supported for free functions -gtsam::Point3 triangulatePoint3(const gtsam::Pose3Vector& poses, - gtsam::Cal3_S2* sharedCal, const gtsam::Point2Vector& measurements, - double rank_tol, bool optimize); -gtsam::Point3 triangulatePoint3(const gtsam::Pose3Vector& poses, - gtsam::Cal3Bundler* sharedCal, const gtsam::Point2Vector& measurements, - double rank_tol, bool optimize); - -//************************************************************************* -// Symbolic -//************************************************************************* - -#include -virtual class SymbolicFactor { - // Standard Constructors and Named Constructors - SymbolicFactor(const gtsam::SymbolicFactor& f); - SymbolicFactor(); - SymbolicFactor(size_t j); - SymbolicFactor(size_t j1, size_t j2); - SymbolicFactor(size_t j1, size_t j2, size_t j3); - SymbolicFactor(size_t j1, size_t j2, size_t j3, size_t j4); - SymbolicFactor(size_t j1, size_t j2, size_t j3, size_t j4, size_t j5); - SymbolicFactor(size_t j1, size_t j2, size_t j3, size_t j4, size_t j5, size_t j6); - static gtsam::SymbolicFactor FromKeys(const gtsam::KeyVector& js); - - // From Factor - size_t size() const; - void print(string s) const; - bool equals(const gtsam::SymbolicFactor& other, double tol) const; - gtsam::KeyVector keys(); -}; - -#include -virtual class SymbolicFactorGraph { - SymbolicFactorGraph(); - SymbolicFactorGraph(const gtsam::SymbolicBayesNet& bayesNet); - SymbolicFactorGraph(const gtsam::SymbolicBayesTree& bayesTree); - - // From FactorGraph - void push_back(gtsam::SymbolicFactor* factor); - void print(string s) const; - bool equals(const gtsam::SymbolicFactorGraph& rhs, double tol) const; - size_t size() const; - bool exists(size_t idx) const; - - // Standard interface - gtsam::KeySet keys() const; - void push_back(gtsam::SymbolicFactor* factor); - void push_back(const gtsam::SymbolicFactorGraph& graph); - void push_back(const gtsam::SymbolicBayesNet& bayesNet); - void push_back(const gtsam::SymbolicBayesTree& bayesTree); - - //Advanced Interface - void push_factor(size_t key); - void push_factor(size_t key1, size_t key2); - void push_factor(size_t key1, size_t key2, size_t key3); - void push_factor(size_t key1, size_t key2, size_t key3, size_t key4); - - gtsam::SymbolicBayesNet* eliminateSequential(); - gtsam::SymbolicBayesNet* eliminateSequential(const gtsam::Ordering& ordering); - gtsam::SymbolicBayesTree* eliminateMultifrontal(); - gtsam::SymbolicBayesTree* eliminateMultifrontal(const gtsam::Ordering& ordering); - pair eliminatePartialSequential( - const gtsam::Ordering& ordering); - pair eliminatePartialSequential( - const gtsam::KeyVector& keys); - pair eliminatePartialMultifrontal( - const gtsam::Ordering& ordering); - pair eliminatePartialMultifrontal( - const gtsam::KeyVector& keys); - gtsam::SymbolicBayesNet* marginalMultifrontalBayesNet(const gtsam::Ordering& variables); - gtsam::SymbolicBayesNet* marginalMultifrontalBayesNet(const gtsam::KeyVector& variables); - gtsam::SymbolicBayesNet* marginalMultifrontalBayesNet(const gtsam::Ordering& variables, - const gtsam::Ordering& marginalizedVariableOrdering); - gtsam::SymbolicBayesNet* marginalMultifrontalBayesNet(const gtsam::KeyVector& variables, - const gtsam::Ordering& marginalizedVariableOrdering); - gtsam::SymbolicFactorGraph* marginal(const gtsam::KeyVector& variables); -}; - -#include -virtual class SymbolicConditional : gtsam::SymbolicFactor { - // Standard Constructors and Named Constructors - SymbolicConditional(); - SymbolicConditional(const gtsam::SymbolicConditional& other); - SymbolicConditional(size_t key); - SymbolicConditional(size_t key, size_t parent); - SymbolicConditional(size_t key, size_t parent1, size_t parent2); - SymbolicConditional(size_t key, size_t parent1, size_t parent2, size_t parent3); - static gtsam::SymbolicConditional FromKeys(const gtsam::KeyVector& keys, size_t nrFrontals); - - // Testable - void print(string s) const; - bool equals(const gtsam::SymbolicConditional& other, double tol) const; - - // Standard interface - size_t nrFrontals() const; - size_t nrParents() const; -}; - -#include -class SymbolicBayesNet { - SymbolicBayesNet(); - SymbolicBayesNet(const gtsam::SymbolicBayesNet& other); - // Testable - void print(string s) const; - bool equals(const gtsam::SymbolicBayesNet& other, double tol) const; - - // Standard interface - size_t size() const; - void saveGraph(string s) const; - gtsam::SymbolicConditional* at(size_t idx) const; - gtsam::SymbolicConditional* front() const; - gtsam::SymbolicConditional* back() const; - void push_back(gtsam::SymbolicConditional* conditional); - void push_back(const gtsam::SymbolicBayesNet& bayesNet); -}; - -#include -class SymbolicBayesTree { - - //Constructors - SymbolicBayesTree(); - SymbolicBayesTree(const gtsam::SymbolicBayesTree& other); - - // Testable - void print(string s); - bool equals(const gtsam::SymbolicBayesTree& other, double tol) const; - - //Standard Interface - //size_t findParentClique(const gtsam::IndexVector& parents) const; - size_t size(); - void saveGraph(string s) const; - void clear(); - void deleteCachedShortcuts(); - size_t numCachedSeparatorMarginals() const; - - gtsam::SymbolicConditional* marginalFactor(size_t key) const; - gtsam::SymbolicFactorGraph* joint(size_t key1, size_t key2) const; - gtsam::SymbolicBayesNet* jointBayesNet(size_t key1, size_t key2) const; -}; - -// class SymbolicBayesTreeClique { -// BayesTreeClique(); -// BayesTreeClique(CONDITIONAL* conditional); -// // BayesTreeClique(const std::pair& result) : Base(result) {} -// -// bool equals(const This& other, double tol) const; -// void print(string s) const; -// void printTree() const; // Default indent of "" -// void printTree(string indent) const; -// size_t numCachedSeparatorMarginals() const; -// -// CONDITIONAL* conditional() const; -// bool isRoot() const; -// size_t treeSize() const; -// // const std::list& children() const { return children_; } -// // derived_ptr parent() const { return parent_.lock(); } -// -// // FIXME: need wrapped versions graphs, BayesNet -// // BayesNet shortcut(derived_ptr root, Eliminate function) const; -// // FactorGraph marginal(derived_ptr root, Eliminate function) const; -// // FactorGraph joint(derived_ptr C2, derived_ptr root, Eliminate function) const; -// -// void deleteCachedShortcuts(); -// }; - -#include -class VariableIndex { - // Standard Constructors and Named Constructors - VariableIndex(); - // TODO: Templetize constructor when wrap supports it - //template - //VariableIndex(const T& factorGraph, size_t nVariables); - //VariableIndex(const T& factorGraph); - VariableIndex(const gtsam::SymbolicFactorGraph& factorGraph); - VariableIndex(const gtsam::GaussianFactorGraph& factorGraph); - VariableIndex(const gtsam::NonlinearFactorGraph& factorGraph); - VariableIndex(const gtsam::VariableIndex& other); - - // Testable - bool equals(const gtsam::VariableIndex& other, double tol) const; - void print(string s) const; - - // Standard interface - size_t size() const; - size_t nFactors() const; - size_t nEntries() const; -}; - -//************************************************************************* -// linear -//************************************************************************* - -namespace noiseModel { -#include -virtual class Base { -}; - -virtual class Gaussian : gtsam::noiseModel::Base { - static gtsam::noiseModel::Gaussian* SqrtInformation(Matrix R); - static gtsam::noiseModel::Gaussian* Covariance(Matrix R); - Matrix R() const; - bool equals(gtsam::noiseModel::Base& expected, double tol); - void print(string s) const; - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Diagonal : gtsam::noiseModel::Gaussian { - static gtsam::noiseModel::Diagonal* Sigmas(Vector sigmas); - static gtsam::noiseModel::Diagonal* Variances(Vector variances); - static gtsam::noiseModel::Diagonal* Precisions(Vector precisions); - Matrix R() const; - void print(string s) const; - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Constrained : gtsam::noiseModel::Diagonal { - static gtsam::noiseModel::Constrained* MixedSigmas(const Vector& mu, const Vector& sigmas); - static gtsam::noiseModel::Constrained* MixedSigmas(double m, const Vector& sigmas); - static gtsam::noiseModel::Constrained* MixedVariances(const Vector& mu, const Vector& variances); - static gtsam::noiseModel::Constrained* MixedVariances(const Vector& variances); - static gtsam::noiseModel::Constrained* MixedPrecisions(const Vector& mu, const Vector& precisions); - static gtsam::noiseModel::Constrained* MixedPrecisions(const Vector& precisions); - - static gtsam::noiseModel::Constrained* All(size_t dim); - static gtsam::noiseModel::Constrained* All(size_t dim, double mu); - - gtsam::noiseModel::Constrained* unit() const; - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Isotropic : gtsam::noiseModel::Diagonal { - static gtsam::noiseModel::Isotropic* Sigma(size_t dim, double sigma); - static gtsam::noiseModel::Isotropic* Variance(size_t dim, double varianace); - static gtsam::noiseModel::Isotropic* Precision(size_t dim, double precision); - void print(string s) const; - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Unit : gtsam::noiseModel::Isotropic { - static gtsam::noiseModel::Unit* Create(size_t dim); - void print(string s) const; - - // enabling serialization functionality - void serializable() const; -}; - -namespace mEstimator { -virtual class Base { -}; - -virtual class Null: gtsam::noiseModel::mEstimator::Base { - Null(); - void print(string s) const; - static gtsam::noiseModel::mEstimator::Null* Create(); - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Fair: gtsam::noiseModel::mEstimator::Base { - Fair(double c); - void print(string s) const; - static gtsam::noiseModel::mEstimator::Fair* Create(double c); - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Huber: gtsam::noiseModel::mEstimator::Base { - Huber(double k); - void print(string s) const; - static gtsam::noiseModel::mEstimator::Huber* Create(double k); - - // enabling serialization functionality - void serializable() const; -}; - -virtual class Tukey: gtsam::noiseModel::mEstimator::Base { - Tukey(double k); - void print(string s) const; - static gtsam::noiseModel::mEstimator::Tukey* Create(double k); - - // enabling serialization functionality - void serializable() const; -}; - -}///\namespace mEstimator - -virtual class Robust : gtsam::noiseModel::Base { - Robust(const gtsam::noiseModel::mEstimator::Base* robust, const gtsam::noiseModel::Base* noise); - static gtsam::noiseModel::Robust* Create(const gtsam::noiseModel::mEstimator::Base* robust, const gtsam::noiseModel::Base* noise); - void print(string s) const; - - // enabling serialization functionality - void serializable() const; -}; - -}///\namespace noiseModel - -#include -class Sampler { - //Constructors - Sampler(gtsam::noiseModel::Diagonal* model, int seed); - Sampler(Vector sigmas, int seed); - Sampler(int seed); - - //Standard Interface - size_t dim() const; - Vector sigmas() const; - gtsam::noiseModel::Diagonal* model() const; - Vector sample(); - Vector sampleNewModel(gtsam::noiseModel::Diagonal* model); -}; - -#include -class VectorValues { - //Constructors - VectorValues(); - VectorValues(const gtsam::VectorValues& other); - - //Named Constructors - static gtsam::VectorValues Zero(const gtsam::VectorValues& model); - - //Standard Interface - size_t size() const; - size_t dim(size_t j) const; - bool exists(size_t j) const; - void print(string s) const; - bool equals(const gtsam::VectorValues& expected, double tol) const; - void insert(size_t j, Vector value); - Vector vector() const; - Vector at(size_t j) const; - void update(const gtsam::VectorValues& values); - - //Advanced Interface - void setZero(); - - gtsam::VectorValues add(const gtsam::VectorValues& c) const; - void addInPlace(const gtsam::VectorValues& c); - gtsam::VectorValues subtract(const gtsam::VectorValues& c) const; - gtsam::VectorValues scale(double a) const; - void scaleInPlace(double a); - - bool hasSameStructure(const gtsam::VectorValues& other) const; - double dot(const gtsam::VectorValues& V) const; - double norm() const; - double squaredNorm() const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class GaussianFactor { - gtsam::KeyVector keys() const; - void print(string s) const; - bool equals(const gtsam::GaussianFactor& lf, double tol) const; - double error(const gtsam::VectorValues& c) const; - gtsam::GaussianFactor* clone() const; - gtsam::GaussianFactor* negate() const; - Matrix augmentedInformation() const; - Matrix information() const; - Matrix augmentedJacobian() const; - pair jacobian() const; - size_t size() const; - bool empty() const; -}; - -#include -virtual class JacobianFactor : gtsam::GaussianFactor { - //Constructors - JacobianFactor(); - JacobianFactor(const gtsam::GaussianFactor& factor); - JacobianFactor(Vector b_in); - JacobianFactor(size_t i1, Matrix A1, Vector b, - const gtsam::noiseModel::Diagonal* model); - JacobianFactor(size_t i1, Matrix A1, size_t i2, Matrix A2, Vector b, - const gtsam::noiseModel::Diagonal* model); - JacobianFactor(size_t i1, Matrix A1, size_t i2, Matrix A2, size_t i3, Matrix A3, - Vector b, const gtsam::noiseModel::Diagonal* model); - JacobianFactor(const gtsam::GaussianFactorGraph& graph); - - //Testable - void print(string s) const; - void printKeys(string s) const; - bool equals(const gtsam::GaussianFactor& lf, double tol) const; - size_t size() const; - Vector unweighted_error(const gtsam::VectorValues& c) const; - Vector error_vector(const gtsam::VectorValues& c) const; - double error(const gtsam::VectorValues& c) const; - - //Standard Interface - Matrix getA() const; - Vector getb() const; - size_t rows() const; - size_t cols() const; - bool isConstrained() const; - pair jacobianUnweighted() const; - Matrix augmentedJacobianUnweighted() const; - - void transposeMultiplyAdd(double alpha, const Vector& e, gtsam::VectorValues& x) const; - gtsam::JacobianFactor whiten() const; - - pair eliminate(const gtsam::Ordering& keys) const; - - void setModel(bool anyConstrained, const Vector& sigmas); - - gtsam::noiseModel::Diagonal* get_model() const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class HessianFactor : gtsam::GaussianFactor { - //Constructors - HessianFactor(); - HessianFactor(const gtsam::GaussianFactor& factor); - HessianFactor(size_t j, Matrix G, Vector g, double f); - HessianFactor(size_t j, Vector mu, Matrix Sigma); - HessianFactor(size_t j1, size_t j2, Matrix G11, Matrix G12, Vector g1, Matrix G22, - Vector g2, double f); - HessianFactor(size_t j1, size_t j2, size_t j3, Matrix G11, Matrix G12, Matrix G13, - Vector g1, Matrix G22, Matrix G23, Vector g2, Matrix G33, Vector g3, - double f); - HessianFactor(const gtsam::GaussianFactorGraph& factors); - - //Testable - size_t size() const; - void print(string s) const; - void printKeys(string s) const; - bool equals(const gtsam::GaussianFactor& lf, double tol) const; - double error(const gtsam::VectorValues& c) const; - - //Standard Interface - size_t rows() const; - Matrix info() const; - double constantTerm() const; - Vector linearTerm() const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -class GaussianFactorGraph { - GaussianFactorGraph(); - GaussianFactorGraph(const gtsam::GaussianBayesNet& bayesNet); - GaussianFactorGraph(const gtsam::GaussianBayesTree& bayesTree); - - // From FactorGraph - void print(string s) const; - bool equals(const gtsam::GaussianFactorGraph& lfgraph, double tol) const; - size_t size() const; - gtsam::GaussianFactor* at(size_t idx) const; - gtsam::KeySet keys() const; - bool exists(size_t idx) const; - - // Building the graph - void push_back(const gtsam::GaussianFactor* factor); - void push_back(const gtsam::GaussianConditional* factor); - void push_back(const gtsam::GaussianFactorGraph& graph); - void push_back(const gtsam::GaussianBayesNet& bayesNet); - void push_back(const gtsam::GaussianBayesTree& bayesTree); - void add(const gtsam::GaussianFactor& factor); - void add(Vector b); - void add(size_t key1, Matrix A1, Vector b, const gtsam::noiseModel::Diagonal* model); - void add(size_t key1, Matrix A1, size_t key2, Matrix A2, Vector b, - const gtsam::noiseModel::Diagonal* model); - void add(size_t key1, Matrix A1, size_t key2, Matrix A2, size_t key3, Matrix A3, - Vector b, const gtsam::noiseModel::Diagonal* model); - - // error and probability - double error(const gtsam::VectorValues& c) const; - double probPrime(const gtsam::VectorValues& c) const; - - gtsam::GaussianFactorGraph clone() const; - gtsam::GaussianFactorGraph negate() const; - - // Optimizing and linear algebra - gtsam::VectorValues optimize() const; - gtsam::VectorValues optimize(const gtsam::Ordering& ordering) const; - gtsam::VectorValues optimizeGradientSearch() const; - gtsam::VectorValues gradient(const gtsam::VectorValues& x0) const; - gtsam::VectorValues gradientAtZero() const; - - // Elimination and marginals - gtsam::GaussianBayesNet* eliminateSequential(); - gtsam::GaussianBayesNet* eliminateSequential(const gtsam::Ordering& ordering); - gtsam::GaussianBayesTree* eliminateMultifrontal(); - gtsam::GaussianBayesTree* eliminateMultifrontal(const gtsam::Ordering& ordering); - pair eliminatePartialSequential( - const gtsam::Ordering& ordering); - pair eliminatePartialSequential( - const gtsam::KeyVector& keys); - pair eliminatePartialMultifrontal( - const gtsam::Ordering& ordering); - pair eliminatePartialMultifrontal( - const gtsam::KeyVector& keys); - gtsam::GaussianBayesNet* marginalMultifrontalBayesNet(const gtsam::Ordering& variables); - gtsam::GaussianBayesNet* marginalMultifrontalBayesNet(const gtsam::KeyVector& variables); - gtsam::GaussianBayesNet* marginalMultifrontalBayesNet(const gtsam::Ordering& variables, - const gtsam::Ordering& marginalizedVariableOrdering); - gtsam::GaussianBayesNet* marginalMultifrontalBayesNet(const gtsam::KeyVector& variables, - const gtsam::Ordering& marginalizedVariableOrdering); - gtsam::GaussianFactorGraph* marginal(const gtsam::KeyVector& variables); - - // Conversion to matrices - Matrix sparseJacobian_() const; - Matrix augmentedJacobian() const; - Matrix augmentedJacobian(const gtsam::Ordering& ordering) const; - pair jacobian() const; - pair jacobian(const gtsam::Ordering& ordering) const; - Matrix augmentedHessian() const; - Matrix augmentedHessian(const gtsam::Ordering& ordering) const; - pair hessian() const; - pair hessian(const gtsam::Ordering& ordering) const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class GaussianConditional : gtsam::GaussianFactor { - //Constructors - GaussianConditional(size_t key, Vector d, Matrix R, const gtsam::noiseModel::Diagonal* sigmas); - GaussianConditional(size_t key, Vector d, Matrix R, size_t name1, Matrix S, - const gtsam::noiseModel::Diagonal* sigmas); - GaussianConditional(size_t key, Vector d, Matrix R, size_t name1, Matrix S, - size_t name2, Matrix T, const gtsam::noiseModel::Diagonal* sigmas); - - //Constructors with no noise model - GaussianConditional(size_t key, Vector d, Matrix R); - GaussianConditional(size_t key, Vector d, Matrix R, size_t name1, Matrix S); - GaussianConditional(size_t key, Vector d, Matrix R, size_t name1, Matrix S, - size_t name2, Matrix T); - - //Standard Interface - void print(string s) const; - bool equals(const gtsam::GaussianConditional &cg, double tol) const; - - //Advanced Interface - gtsam::VectorValues solve(const gtsam::VectorValues& parents) const; - gtsam::VectorValues solveOtherRHS(const gtsam::VectorValues& parents, const gtsam::VectorValues& rhs) const; - void solveTransposeInPlace(gtsam::VectorValues& gy) const; - void scaleFrontalsBySigma(gtsam::VectorValues& gy) const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -virtual class GaussianDensity : gtsam::GaussianConditional { - //Constructors - GaussianDensity(size_t key, Vector d, Matrix R, const gtsam::noiseModel::Diagonal* sigmas); - - //Standard Interface - void print(string s) const; - bool equals(const gtsam::GaussianDensity &cg, double tol) const; - Vector mean() const; - Matrix covariance() const; -}; - -#include -virtual class GaussianBayesNet { - //Constructors - GaussianBayesNet(); - GaussianBayesNet(const gtsam::GaussianConditional* conditional); - - // Testable - void print(string s) const; - bool equals(const gtsam::GaussianBayesNet& other, double tol) const; - size_t size() const; - - // FactorGraph derived interface - size_t size() const; - gtsam::GaussianConditional* at(size_t idx) const; - gtsam::KeySet keys() const; - bool exists(size_t idx) const; - - gtsam::GaussianConditional* front() const; - gtsam::GaussianConditional* back() const; - void push_back(gtsam::GaussianConditional* conditional); - void push_back(const gtsam::GaussianBayesNet& bayesNet); - - gtsam::VectorValues optimize() const; - gtsam::VectorValues optimize(gtsam::VectorValues& solutionForMissing) const; - gtsam::VectorValues optimizeGradientSearch() const; - gtsam::VectorValues gradient(const gtsam::VectorValues& x0) const; - gtsam::VectorValues gradientAtZero() const; - double error(const gtsam::VectorValues& x) const; - double determinant() const; - double logDeterminant() const; - gtsam::VectorValues backSubstitute(const gtsam::VectorValues& gx) const; - gtsam::VectorValues backSubstituteTranspose(const gtsam::VectorValues& gx) const; -}; - -#include -virtual class GaussianBayesTree { - // Standard Constructors and Named Constructors - GaussianBayesTree(); - GaussianBayesTree(const gtsam::GaussianBayesTree& other); - bool equals(const gtsam::GaussianBayesTree& other, double tol) const; - void print(string s); - size_t size() const; - bool empty() const; - size_t numCachedSeparatorMarginals() const; - void saveGraph(string s) const; - - gtsam::VectorValues optimize() const; - gtsam::VectorValues optimizeGradientSearch() const; - gtsam::VectorValues gradient(const gtsam::VectorValues& x0) const; - gtsam::VectorValues gradientAtZero() const; - double error(const gtsam::VectorValues& x) const; - double determinant() const; - double logDeterminant() const; - Matrix marginalCovariance(size_t key) const; - gtsam::GaussianConditional* marginalFactor(size_t key) const; - gtsam::GaussianFactorGraph* joint(size_t key1, size_t key2) const; - gtsam::GaussianBayesNet* jointBayesNet(size_t key1, size_t key2) const; -}; - -class Errors { - //Constructors - Errors(); - Errors(const gtsam::VectorValues& V); - - //Testable - void print(string s); - bool equals(const gtsam::Errors& expected, double tol) const; -}; - -class GaussianISAM { - //Constructor - GaussianISAM(); - - //Standard Interface - void update(const gtsam::GaussianFactorGraph& newFactors); - void saveGraph(string s) const; - void clear(); -}; - -#include -virtual class IterativeOptimizationParameters { - string getVerbosity() const; - void setVerbosity(string s) ; - void print() const; -}; - -//virtual class IterativeSolver { -// IterativeSolver(); -// gtsam::VectorValues optimize (); -//}; - -#include -virtual class ConjugateGradientParameters : gtsam::IterativeOptimizationParameters { - ConjugateGradientParameters(); - int getMinIterations() const ; - int getMaxIterations() const ; - int getReset() const; - double getEpsilon_rel() const; - double getEpsilon_abs() const; - - void setMinIterations(int value); - void setMaxIterations(int value); - void setReset(int value); - void setEpsilon_rel(double value); - void setEpsilon_abs(double value); - void print(); -}; - -#include -virtual class SubgraphSolverParameters : gtsam::ConjugateGradientParameters { - SubgraphSolverParameters(); - void print() const; -}; - -virtual class SubgraphSolver { - SubgraphSolver(const gtsam::GaussianFactorGraph &A, const gtsam::SubgraphSolverParameters ¶meters, const gtsam::Ordering& ordering); - SubgraphSolver(const gtsam::GaussianFactorGraph &Ab1, const gtsam::GaussianFactorGraph &Ab2, const gtsam::SubgraphSolverParameters ¶meters, const gtsam::Ordering& ordering); - gtsam::VectorValues optimize() const; -}; - -#include -class KalmanFilter { - KalmanFilter(size_t n); - // gtsam::GaussianDensity* init(Vector x0, const gtsam::SharedDiagonal& P0); - gtsam::GaussianDensity* init(Vector x0, Matrix P0); - void print(string s) const; - static size_t step(gtsam::GaussianDensity* p); - gtsam::GaussianDensity* predict(gtsam::GaussianDensity* p, Matrix F, - Matrix B, Vector u, const gtsam::noiseModel::Diagonal* modelQ); - gtsam::GaussianDensity* predictQ(gtsam::GaussianDensity* p, Matrix F, - Matrix B, Vector u, Matrix Q); - gtsam::GaussianDensity* predict2(gtsam::GaussianDensity* p, Matrix A0, - Matrix A1, Vector b, const gtsam::noiseModel::Diagonal* model); - gtsam::GaussianDensity* update(gtsam::GaussianDensity* p, Matrix H, - Vector z, const gtsam::noiseModel::Diagonal* model); - gtsam::GaussianDensity* updateQ(gtsam::GaussianDensity* p, Matrix H, - Vector z, Matrix Q); -}; - -//************************************************************************* -// nonlinear -//************************************************************************* - -#include -size_t symbol(char chr, size_t index); -char symbolChr(size_t key); -size_t symbolIndex(size_t key); - -// Default keyformatter -void PrintKeyList (const gtsam::KeyList& keys); -void PrintKeyList (const gtsam::KeyList& keys, string s); -void PrintKeyVector(const gtsam::KeyVector& keys); -void PrintKeyVector(const gtsam::KeyVector& keys, string s); -void PrintKeySet (const gtsam::KeySet& keys); -void PrintKeySet (const gtsam::KeySet& keys, string s); - -#include -class LabeledSymbol { - LabeledSymbol(size_t full_key); - LabeledSymbol(const gtsam::LabeledSymbol& key); - LabeledSymbol(unsigned char valType, unsigned char label, size_t j); - - size_t key() const; - unsigned char label() const; - unsigned char chr() const; - size_t index() const; - - gtsam::LabeledSymbol upper() const; - gtsam::LabeledSymbol lower() const; - gtsam::LabeledSymbol newChr(unsigned char c) const; - gtsam::LabeledSymbol newLabel(unsigned char label) const; - - void print(string s) const; -}; - -size_t mrsymbol(unsigned char c, unsigned char label, size_t j); -unsigned char mrsymbolChr(size_t key); -unsigned char mrsymbolLabel(size_t key); -size_t mrsymbolIndex(size_t key); - -#include -class Ordering { - // Standard Constructors and Named Constructors - Ordering(); - Ordering(const gtsam::Ordering& other); - - // Testable - void print(string s) const; - bool equals(const gtsam::Ordering& ord, double tol) const; - - // Standard interface - size_t size() const; - size_t at(size_t key) const; - void push_back(size_t key); - - // enabling serialization functionality - void serialize() const; -}; - -class NonlinearFactorGraph { - NonlinearFactorGraph(); - NonlinearFactorGraph(const gtsam::NonlinearFactorGraph& graph); - - // FactorGraph - void print(string s) const; - bool equals(const gtsam::NonlinearFactorGraph& fg, double tol) const; - size_t size() const; - bool empty() const; - void remove(size_t i); - size_t nrFactors() const; - gtsam::NonlinearFactor* at(size_t idx) const; - void push_back(const gtsam::NonlinearFactorGraph& factors); - void push_back(gtsam::NonlinearFactor* factor); - void add(gtsam::NonlinearFactor* factor); - bool exists(size_t idx) const; - gtsam::KeySet keys() const; - - // NonlinearFactorGraph - double error(const gtsam::Values& values) const; - double probPrime(const gtsam::Values& values) const; - gtsam::Ordering orderingCOLAMD() const; - // Ordering* orderingCOLAMDConstrained(const gtsam::Values& c, const std::map& constraints) const; - gtsam::GaussianFactorGraph* linearize(const gtsam::Values& values) const; - gtsam::NonlinearFactorGraph clone() const; - - // enabling serialization functionality - void serialize() const; -}; - -virtual class NonlinearFactor { - // Factor base class - size_t size() const; - gtsam::KeyVector keys() const; - void print(string s) const; - void printKeys(string s) const; - // NonlinearFactor - void equals(const gtsam::NonlinearFactor& other, double tol) const; - double error(const gtsam::Values& c) const; - size_t dim() const; - bool active(const gtsam::Values& c) const; - gtsam::GaussianFactor* linearize(const gtsam::Values& c) const; - gtsam::NonlinearFactor* clone() const; - // gtsam::NonlinearFactor* rekey(const gtsam::KeyVector& newKeys) const; //FIXME: Conversion from KeyVector to std::vector does not happen -}; - -virtual class NoiseModelFactor: gtsam::NonlinearFactor { - void equals(const gtsam::NoiseModelFactor& other, double tol) const; - gtsam::noiseModel::Base* noiseModel() const; - Vector unwhitenedError(const gtsam::Values& x) const; - Vector whitenedError(const gtsam::Values& x) const; -}; - -#include -class Values { - Values(); - Values(const gtsam::Values& other); - - size_t size() const; - bool empty() const; - void clear(); - size_t dim() const; - - void print(string s) const; - bool equals(const gtsam::Values& other, double tol) const; - - void insert(const gtsam::Values& values); - void update(const gtsam::Values& values); - void erase(size_t j); - void swap(gtsam::Values& values); - - bool exists(size_t j) const; - gtsam::KeyVector keys() const; - - gtsam::VectorValues zeroVectors() const; - - gtsam::Values retract(const gtsam::VectorValues& delta) const; - gtsam::VectorValues localCoordinates(const gtsam::Values& cp) const; - - // enabling serialization functionality - void serialize() const; - - // New in 4.0, we have to specialize every insert/update/at to generate wrappers - // Instead of the old: - // void insert(size_t j, const gtsam::Value& value); - // void update(size_t j, const gtsam::Value& val); - // gtsam::Value at(size_t j) const; - - void insert(size_t j, const gtsam::Point2& t); - void insert(size_t j, const gtsam::Point3& t); - void insert(size_t j, const gtsam::Rot2& t); - void insert(size_t j, const gtsam::Pose2& t); - void insert(size_t j, const gtsam::Rot3& t); - void insert(size_t j, const gtsam::Pose3& t); - void insert(size_t j, const gtsam::Cal3_S2& t); - void insert(size_t j, const gtsam::Cal3DS2& t); - void insert(size_t j, const gtsam::Cal3Bundler& t); - void insert(size_t j, const gtsam::EssentialMatrix& t); - void insert(size_t j, const gtsam::SimpleCamera& t); - void insert(size_t j, const gtsam::imuBias::ConstantBias& t); - void insert(size_t j, Vector t); - void insert(size_t j, Matrix t); - - // Fixed size version - void insertFixed(size_t j, Vector t, size_t n); - - void update(size_t j, const gtsam::Point2& t); - void update(size_t j, const gtsam::Point3& t); - void update(size_t j, const gtsam::Rot2& t); - void update(size_t j, const gtsam::Pose2& t); - void update(size_t j, const gtsam::Rot3& t); - void update(size_t j, const gtsam::Pose3& t); - void update(size_t j, const gtsam::Cal3_S2& t); - void update(size_t j, const gtsam::Cal3DS2& t); - void update(size_t j, const gtsam::Cal3Bundler& t); - void update(size_t j, const gtsam::EssentialMatrix& t); - void update(size_t j, const gtsam::imuBias::ConstantBias& t); - void update(size_t j, Vector t); - void update(size_t j, Matrix t); - - template - T at(size_t j); - - /// Fixed size versions, for n in 1..9 - void insertFixed(size_t j, Vector t, size_t n); - - /// Fixed size versions, for n in 1..9 - Vector atFixed(size_t j, size_t n); - - /// version for double - void insertDouble(size_t j, double c); - double atDouble(size_t j) const; -}; - -// Actually a FastList -#include -class KeyList { - KeyList(); - KeyList(const gtsam::KeyList& other); - - // Note: no print function - - // common STL methods - size_t size() const; - bool empty() const; - void clear(); - - // structure specific methods - size_t front() const; - size_t back() const; - void push_back(size_t key); - void push_front(size_t key); - void pop_back(); - void pop_front(); - void sort(); - void remove(size_t key); - - void serialize() const; -}; - -// Actually a FastSet -class KeySet { - KeySet(); - KeySet(const gtsam::KeySet& other); - KeySet(const gtsam::KeyVector& other); - KeySet(const gtsam::KeyList& other); - - // Testable - void print(string s) const; - bool equals(const gtsam::KeySet& other) const; - - // common STL methods - size_t size() const; - bool empty() const; - void clear(); - - // structure specific methods - void insert(size_t key); - void merge(gtsam::KeySet& other); - bool erase(size_t key); // returns true if value was removed - bool count(size_t key) const; // returns true if value exists - - void serialize() const; -}; - -// Actually a vector -class KeyVector { - KeyVector(); - KeyVector(const gtsam::KeyVector& other); - - // Note: no print function - - // common STL methods - size_t size() const; - bool empty() const; - void clear(); - - // structure specific methods - size_t at(size_t i) const; - size_t front() const; - size_t back() const; - void push_back(size_t key) const; - - void serialize() const; -}; - -// Actually a FastMap -class KeyGroupMap { - KeyGroupMap(); - - // Note: no print function - - // common STL methods - size_t size() const; - bool empty() const; - void clear(); - - // structure specific methods - size_t at(size_t key) const; - int erase(size_t key); - bool insert2(size_t key, int val); -}; - -#include -class Marginals { - Marginals(const gtsam::NonlinearFactorGraph& graph, - const gtsam::Values& solution); - - void print(string s) const; - Matrix marginalCovariance(size_t variable) const; - Matrix marginalInformation(size_t variable) const; - gtsam::JointMarginal jointMarginalCovariance(const gtsam::KeyVector& variables) const; - gtsam::JointMarginal jointMarginalInformation(const gtsam::KeyVector& variables) const; -}; - -class JointMarginal { - Matrix at(size_t iVariable, size_t jVariable) const; - Matrix fullMatrix() const; - void print(string s) const; - void print() const; -}; - -#include -virtual class LinearContainerFactor : gtsam::NonlinearFactor { - - LinearContainerFactor(gtsam::GaussianFactor* factor, const gtsam::Values& linearizationPoint); - LinearContainerFactor(gtsam::GaussianFactor* factor); - - gtsam::GaussianFactor* factor() const; -// const boost::optional& linearizationPoint() const; - - bool isJacobian() const; - gtsam::JacobianFactor* toJacobian() const; - gtsam::HessianFactor* toHessian() const; - - static gtsam::NonlinearFactorGraph convertLinearGraph(const gtsam::GaussianFactorGraph& linear_graph, - const gtsam::Values& linearizationPoint); - - static gtsam::NonlinearFactorGraph convertLinearGraph(const gtsam::GaussianFactorGraph& linear_graph); - - // enabling serialization functionality - void serializable() const; -}; // \class LinearContainerFactor - -// Summarization functionality -//#include -// -//// Uses partial QR approach by default -//gtsam::GaussianFactorGraph summarize( -// const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& values, -// const gtsam::KeySet& saved_keys); -// -//gtsam::NonlinearFactorGraph summarizeAsNonlinearContainer( -// const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& values, -// const gtsam::KeySet& saved_keys); - -//************************************************************************* -// Nonlinear optimizers -//************************************************************************* - -#include -#include -virtual class NonlinearOptimizerParams { - NonlinearOptimizerParams(); - void print(string s) const; - - int getMaxIterations() const; - double getRelativeErrorTol() const; - double getAbsoluteErrorTol() const; - double getErrorTol() const; - string getVerbosity() const; - - void setMaxIterations(int value); - void setRelativeErrorTol(double value); - void setAbsoluteErrorTol(double value); - void setErrorTol(double value); - void setVerbosity(string s); - - string getLinearSolverType() const; - - void setLinearSolverType(string solver); - void setOrdering(const gtsam::Ordering& ordering); - void setIterativeParams(gtsam::IterativeOptimizationParameters* params); - - bool isMultifrontal() const; - bool isSequential() const; - bool isCholmod() const; - bool isIterative() const; -}; - -bool checkConvergence(double relativeErrorTreshold, - double absoluteErrorTreshold, double errorThreshold, - double currentError, double newError); - -#include -virtual class GaussNewtonParams : gtsam::NonlinearOptimizerParams { - GaussNewtonParams(); -}; - -#include -virtual class LevenbergMarquardtParams : gtsam::NonlinearOptimizerParams { - LevenbergMarquardtParams(); - - double getlambdaInitial() const; - double getlambdaFactor() const; - double getlambdaUpperBound() const; - string getVerbosityLM() const; - - void setlambdaInitial(double value); - void setlambdaFactor(double value); - void setlambdaUpperBound(double value); - void setVerbosityLM(string s); -}; - -#include -virtual class DoglegParams : gtsam::NonlinearOptimizerParams { - DoglegParams(); - - double getDeltaInitial() const; - string getVerbosityDL() const; - - void setDeltaInitial(double deltaInitial) const; - void setVerbosityDL(string verbosityDL) const; -}; - -virtual class NonlinearOptimizer { - gtsam::Values optimize(); - gtsam::Values optimizeSafely(); - double error() const; - int iterations() const; - gtsam::Values values() const; - gtsam::GaussianFactorGraph* iterate() const; -}; - -virtual class GaussNewtonOptimizer : gtsam::NonlinearOptimizer { - GaussNewtonOptimizer(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& initialValues); - GaussNewtonOptimizer(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& initialValues, const gtsam::GaussNewtonParams& params); -}; - -virtual class DoglegOptimizer : gtsam::NonlinearOptimizer { - DoglegOptimizer(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& initialValues); - DoglegOptimizer(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& initialValues, const gtsam::DoglegParams& params); - double getDelta() const; -}; - -virtual class LevenbergMarquardtOptimizer : gtsam::NonlinearOptimizer { - LevenbergMarquardtOptimizer(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& initialValues); - LevenbergMarquardtOptimizer(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& initialValues, const gtsam::LevenbergMarquardtParams& params); - double lambda() const; - void print(string str) const; -}; - -#include -class ISAM2GaussNewtonParams { - ISAM2GaussNewtonParams(); - - void print(string str) const; - - /** Getters and Setters for all properties */ - double getWildfireThreshold() const; - void setWildfireThreshold(double wildfireThreshold); -}; - -class ISAM2DoglegParams { - ISAM2DoglegParams(); - - void print(string str) const; - - /** Getters and Setters for all properties */ - double getWildfireThreshold() const; - void setWildfireThreshold(double wildfireThreshold); - double getInitialDelta() const; - void setInitialDelta(double initialDelta); - string getAdaptationMode() const; - void setAdaptationMode(string adaptationMode); - bool isVerbose() const; - void setVerbose(bool verbose); -}; - -class ISAM2ThresholdMapValue { - ISAM2ThresholdMapValue(char c, Vector thresholds); - ISAM2ThresholdMapValue(const gtsam::ISAM2ThresholdMapValue& other); -}; - -class ISAM2ThresholdMap { - ISAM2ThresholdMap(); - ISAM2ThresholdMap(const gtsam::ISAM2ThresholdMap& other); - - // Note: no print function - - // common STL methods - size_t size() const; - bool empty() const; - void clear(); - - // structure specific methods - void insert(const gtsam::ISAM2ThresholdMapValue& value) const; -}; - -class ISAM2Params { - ISAM2Params(); - - void print(string str) const; - - /** Getters and Setters for all properties */ - void setOptimizationParams(const gtsam::ISAM2GaussNewtonParams& params); - void setOptimizationParams(const gtsam::ISAM2DoglegParams& params); - void setRelinearizeThreshold(double relinearizeThreshold); - void setRelinearizeThreshold(const gtsam::ISAM2ThresholdMap& relinearizeThreshold); - int getRelinearizeSkip() const; - void setRelinearizeSkip(int relinearizeSkip); - bool isEnableRelinearization() const; - void setEnableRelinearization(bool enableRelinearization); - bool isEvaluateNonlinearError() const; - void setEvaluateNonlinearError(bool evaluateNonlinearError); - string getFactorization() const; - void setFactorization(string factorization); - bool isCacheLinearizedFactors() const; - void setCacheLinearizedFactors(bool cacheLinearizedFactors); - bool isEnableDetailedResults() const; - void setEnableDetailedResults(bool enableDetailedResults); - bool isEnablePartialRelinearizationCheck() const; - void setEnablePartialRelinearizationCheck(bool enablePartialRelinearizationCheck); -}; - -class ISAM2Clique { - - //Constructors - ISAM2Clique(); - - //Standard Interface - Vector gradientContribution() const; - void print(string s); -}; - -class ISAM2Result { - ISAM2Result(); - - void print(string str) const; - - /** Getters and Setters for all properties */ - size_t getVariablesRelinearized() const; - size_t getVariablesReeliminated() const; - size_t getCliques() const; -}; - -class ISAM2 { - ISAM2(); - ISAM2(const gtsam::ISAM2Params& params); - ISAM2(const gtsam::ISAM2& other); - - bool equals(const gtsam::ISAM2& other, double tol) const; - void print(string s) const; - void printStats() const; - void saveGraph(string s) const; - - gtsam::ISAM2Result update(); - gtsam::ISAM2Result update(const gtsam::NonlinearFactorGraph& newFactors, const gtsam::Values& newTheta); - gtsam::ISAM2Result update(const gtsam::NonlinearFactorGraph& newFactors, const gtsam::Values& newTheta, const gtsam::KeyVector& removeFactorIndices); - gtsam::ISAM2Result update(const gtsam::NonlinearFactorGraph& newFactors, const gtsam::Values& newTheta, const gtsam::KeyVector& removeFactorIndices, const gtsam::KeyGroupMap& constrainedKeys); - // TODO: wrap the full version of update - //void update(const gtsam::NonlinearFactorGraph& newFactors, const gtsam::Values& newTheta, const gtsam::KeyVector& removeFactorIndices, FastMap& constrainedKeys); - //void update(const gtsam::NonlinearFactorGraph& newFactors, const gtsam::Values& newTheta, const gtsam::KeyVector& removeFactorIndices, FastMap& constrainedKeys, bool force_relinearize); - - gtsam::Values getLinearizationPoint() const; - gtsam::Values calculateEstimate() const; - gtsam::Value calculateEstimate(size_t key) const; - gtsam::Values calculateBestEstimate() const; - Matrix marginalCovariance(size_t key) const; - gtsam::VectorValues getDelta() const; - gtsam::NonlinearFactorGraph getFactorsUnsafe() const; - gtsam::VariableIndex getVariableIndex() const; - gtsam::ISAM2Params params() const; -}; - -#include -class NonlinearISAM { - NonlinearISAM(); - NonlinearISAM(int reorderInterval); - void print(string s) const; - void printStats() const; - void saveGraph(string s) const; - gtsam::Values estimate() const; - Matrix marginalCovariance(size_t key) const; - int reorderInterval() const; - int reorderCounter() const; - void update(const gtsam::NonlinearFactorGraph& newFactors, const gtsam::Values& initialValues); - void reorder_relinearize(); - - // These might be expensive as instead of a reference the wrapper will make a copy - gtsam::GaussianISAM bayesTree() const; - gtsam::Values getLinearizationPoint() const; - gtsam::NonlinearFactorGraph getFactorsUnsafe() const; -}; - -//************************************************************************* -// Nonlinear factor types -//************************************************************************* -#include -#include -#include - -#include -template -virtual class PriorFactor : gtsam::NoiseModelFactor { - PriorFactor(size_t key, const T& prior, const gtsam::noiseModel::Base* noiseModel); - T prior() const; - - // enabling serialization functionality - void serialize() const; -}; - - -#include -template -virtual class BetweenFactor : gtsam::NoiseModelFactor { - BetweenFactor(size_t key1, size_t key2, const T& relativePose, const gtsam::noiseModel::Base* noiseModel); - T measured() const; - - // enabling serialization functionality - void serialize() const; -}; - - - -#include -template -virtual class NonlinearEquality : gtsam::NoiseModelFactor { - // Constructor - forces exact evaluation - NonlinearEquality(size_t j, const T& feasible); - // Constructor - allows inexact evaluation - NonlinearEquality(size_t j, const T& feasible, double error_gain); - - // enabling serialization functionality - void serialize() const; -}; - - -#include -template -virtual class RangeFactor : gtsam::NoiseModelFactor { - RangeFactor(size_t key1, size_t key2, double measured, const gtsam::noiseModel::Base* noiseModel); -}; - -typedef gtsam::RangeFactor RangeFactor2D; -typedef gtsam::RangeFactor RangeFactor3D; -typedef gtsam::RangeFactor RangeFactorPose2; -typedef gtsam::RangeFactor RangeFactorPose3; -typedef gtsam::RangeFactor RangeFactorCalibratedCameraPoint; -typedef gtsam::RangeFactor RangeFactorSimpleCameraPoint; -typedef gtsam::RangeFactor RangeFactorCalibratedCamera; -typedef gtsam::RangeFactor RangeFactorSimpleCamera; - - -#include -template -virtual class BearingFactor : gtsam::NoiseModelFactor { - BearingFactor(size_t key1, size_t key2, const BEARING& measured, const gtsam::noiseModel::Base* noiseModel); - - // enabling serialization functionality - void serialize() const; -}; - -typedef gtsam::BearingFactor BearingFactor2D; - -#include -template -virtual class BearingRangeFactor : gtsam::NoiseModelFactor { - BearingRangeFactor(size_t poseKey, size_t pointKey, - const BEARING& measuredBearing, const RANGE& measuredRange, - const gtsam::noiseModel::Base* noiseModel); - - // enabling serialization functionality - void serialize() const; -}; - -typedef gtsam::BearingRangeFactor BearingRangeFactor2D; - - -#include -template -virtual class GenericProjectionFactor : gtsam::NoiseModelFactor { - GenericProjectionFactor(const gtsam::Point2& measured, const gtsam::noiseModel::Base* noiseModel, - size_t poseKey, size_t pointKey, const CALIBRATION* k); - GenericProjectionFactor(const gtsam::Point2& measured, const gtsam::noiseModel::Base* noiseModel, - size_t poseKey, size_t pointKey, const CALIBRATION* k, const POSE& body_P_sensor); - - GenericProjectionFactor(const gtsam::Point2& measured, const gtsam::noiseModel::Base* noiseModel, - size_t poseKey, size_t pointKey, const CALIBRATION* k, bool throwCheirality, bool verboseCheirality); - GenericProjectionFactor(const gtsam::Point2& measured, const gtsam::noiseModel::Base* noiseModel, - size_t poseKey, size_t pointKey, const CALIBRATION* k, bool throwCheirality, bool verboseCheirality, - const POSE& body_P_sensor); - - gtsam::Point2 measured() const; - CALIBRATION* calibration() const; - bool verboseCheirality() const; - bool throwCheirality() const; - - // enabling serialization functionality - void serialize() const; -}; -typedef gtsam::GenericProjectionFactor GenericProjectionFactorCal3_S2; -typedef gtsam::GenericProjectionFactor GenericProjectionFactorCal3DS2; - - -#include -template -virtual class GeneralSFMFactor : gtsam::NoiseModelFactor { - GeneralSFMFactor(const gtsam::Point2& measured, const gtsam::noiseModel::Base* model, size_t cameraKey, size_t landmarkKey); - gtsam::Point2 measured() const; -}; -typedef gtsam::GeneralSFMFactor GeneralSFMFactorCal3_S2; -typedef gtsam::GeneralSFMFactor GeneralSFMFactorCal3DS2; - -template -virtual class GeneralSFMFactor2 : gtsam::NoiseModelFactor { - GeneralSFMFactor2(const gtsam::Point2& measured, const gtsam::noiseModel::Base* model, size_t poseKey, size_t landmarkKey, size_t calibKey); - gtsam::Point2 measured() const; - - // enabling serialization functionality - void serialize() const; -}; - -#include -class SmartProjectionParams { - SmartProjectionParams(); - // TODO(frank): make these work: - // void setLinearizationMode(LinearizationMode linMode); - // void setDegeneracyMode(DegeneracyMode degMode); - void setRankTolerance(double rankTol); - void setEnableEPI(bool enableEPI); - void setLandmarkDistanceThreshold(bool landmarkDistanceThreshold); - void setDynamicOutlierRejectionThreshold(bool dynOutRejectionThreshold); -}; - -#include -template -virtual class SmartProjectionPoseFactor: gtsam::NonlinearFactor { - - SmartProjectionPoseFactor(const gtsam::noiseModel::Base* noise, - const CALIBRATION* K); - SmartProjectionPoseFactor(const gtsam::noiseModel::Base* noise, - const CALIBRATION* K, - const gtsam::Pose3& body_P_sensor); - SmartProjectionPoseFactor(const gtsam::noiseModel::Base* noise, - const CALIBRATION* K, - const gtsam::Pose3& body_P_sensor, - const gtsam::SmartProjectionParams& params); - - void add(const gtsam::Point2& measured_i, size_t poseKey_i); - - // enabling serialization functionality - //void serialize() const; -}; - -typedef gtsam::SmartProjectionPoseFactor SmartProjectionPose3Factor; - - -#include -template -virtual class GenericStereoFactor : gtsam::NoiseModelFactor { - GenericStereoFactor(const gtsam::StereoPoint2& measured, const gtsam::noiseModel::Base* noiseModel, - size_t poseKey, size_t landmarkKey, const gtsam::Cal3_S2Stereo* K); - gtsam::StereoPoint2 measured() const; - gtsam::Cal3_S2Stereo* calibration() const; - - // enabling serialization functionality - void serialize() const; -}; -typedef gtsam::GenericStereoFactor GenericStereoFactor3D; - -#include -template -virtual class PoseTranslationPrior : gtsam::NoiseModelFactor { - PoseTranslationPrior(size_t key, const POSE& pose_z, const gtsam::noiseModel::Base* noiseModel); -}; - -typedef gtsam::PoseTranslationPrior PoseTranslationPrior2D; -typedef gtsam::PoseTranslationPrior PoseTranslationPrior3D; - -#include -template -virtual class PoseRotationPrior : gtsam::NoiseModelFactor { - PoseRotationPrior(size_t key, const POSE& pose_z, const gtsam::noiseModel::Base* noiseModel); -}; - -typedef gtsam::PoseRotationPrior PoseRotationPrior2D; -typedef gtsam::PoseRotationPrior PoseRotationPrior3D; - -#include -virtual class EssentialMatrixFactor : gtsam::NoiseModelFactor { - EssentialMatrixFactor(size_t key, const gtsam::Point2& pA, const gtsam::Point2& pB, - const gtsam::noiseModel::Base* noiseModel); -}; - -#include -pair load2D(string filename, - gtsam::noiseModel::Diagonal* model, int maxID, bool addNoise, bool smart); -pair load2D(string filename, - gtsam::noiseModel::Diagonal* model, int maxID, bool addNoise); -pair load2D(string filename, - gtsam::noiseModel::Diagonal* model, int maxID); -pair load2D(string filename, - gtsam::noiseModel::Diagonal* model); -pair load2D(string filename); -pair load2D_robust(string filename, - gtsam::noiseModel::Base* model); -void save2D(const gtsam::NonlinearFactorGraph& graph, - const gtsam::Values& config, gtsam::noiseModel::Diagonal* model, - string filename); - -pair readG2o(string filename); -void writeG2o(const gtsam::NonlinearFactorGraph& graph, - const gtsam::Values& estimate, string filename); - -//************************************************************************* -// Navigation -//************************************************************************* -namespace imuBias { -#include - -class ConstantBias { - // Standard Constructor - ConstantBias(); - ConstantBias(Vector biasAcc, Vector biasGyro); - - // Testable - void print(string s) const; - bool equals(const gtsam::imuBias::ConstantBias& expected, double tol) const; - - // Group - static gtsam::imuBias::ConstantBias identity(); - gtsam::imuBias::ConstantBias inverse() const; - gtsam::imuBias::ConstantBias compose(const gtsam::imuBias::ConstantBias& b) const; - gtsam::imuBias::ConstantBias between(const gtsam::imuBias::ConstantBias& b) const; - - // Manifold - gtsam::imuBias::ConstantBias retract(Vector v) const; - Vector localCoordinates(const gtsam::imuBias::ConstantBias& b) const; - - // Lie Group - static gtsam::imuBias::ConstantBias Expmap(Vector v); - static Vector Logmap(const gtsam::imuBias::ConstantBias& b); - - // Standard Interface - Vector vector() const; - Vector accelerometer() const; - Vector gyroscope() const; - Vector correctAccelerometer(Vector measurement) const; - Vector correctGyroscope(Vector measurement) const; -}; - -}///\namespace imuBias - -#include -class PoseVelocityBias{ - PoseVelocityBias(const gtsam::Pose3& pose, Vector velocity, const gtsam::imuBias::ConstantBias& bias); - }; -class PreintegratedImuMeasurements { - // Standard Constructor - PreintegratedImuMeasurements(const gtsam::imuBias::ConstantBias& bias, Matrix measuredAccCovariance,Matrix measuredOmegaCovariance, Matrix integrationErrorCovariance, bool use2ndOrderIntegration); - PreintegratedImuMeasurements(const gtsam::imuBias::ConstantBias& bias, Matrix measuredAccCovariance,Matrix measuredOmegaCovariance, Matrix integrationErrorCovariance); - // PreintegratedImuMeasurements(const gtsam::PreintegratedImuMeasurements& rhs); - - // Testable - void print(string s) const; - bool equals(const gtsam::PreintegratedImuMeasurements& expected, double tol); - - double deltaTij() const; - gtsam::Rot3 deltaRij() const; - Vector deltaPij() const; - Vector deltaVij() const; - Vector biasHatVector() const; - Matrix delPdelBiasAcc() const; - Matrix delPdelBiasOmega() const; - Matrix delVdelBiasAcc() const; - Matrix delVdelBiasOmega() const; - Matrix delRdelBiasOmega() const; - Matrix preintMeasCov() const; - - // Standard Interface - void integrateMeasurement(Vector measuredAcc, Vector measuredOmega, double deltaT); - gtsam::PoseVelocityBias predict(const gtsam::Pose3& pose_i, Vector vel_i, const gtsam::imuBias::ConstantBias& bias, - Vector gravity, Vector omegaCoriolis) const; -}; - -virtual class ImuFactor : gtsam::NonlinearFactor { - ImuFactor(size_t pose_i, size_t vel_i, size_t pose_j, size_t vel_j, size_t bias, - const gtsam::PreintegratedImuMeasurements& preintegratedMeasurements, Vector gravity, Vector omegaCoriolis); - ImuFactor(size_t pose_i, size_t vel_i, size_t pose_j, size_t vel_j, size_t bias, - const gtsam::PreintegratedImuMeasurements& preintegratedMeasurements, Vector gravity, Vector omegaCoriolis, - const gtsam::Pose3& body_P_sensor); - // Standard Interface - gtsam::PreintegratedImuMeasurements preintegratedMeasurements() const; -}; - -#include -class PreintegratedCombinedMeasurements { - // Standard Constructor - PreintegratedCombinedMeasurements( - const gtsam::imuBias::ConstantBias& bias, - Matrix measuredAccCovariance, - Matrix measuredOmegaCovariance, - Matrix integrationErrorCovariance, - Matrix biasAccCovariance, - Matrix biasOmegaCovariance, - Matrix biasAccOmegaInit); - PreintegratedCombinedMeasurements( - const gtsam::imuBias::ConstantBias& bias, - Matrix measuredAccCovariance, - Matrix measuredOmegaCovariance, - Matrix integrationErrorCovariance, - Matrix biasAccCovariance, - Matrix biasOmegaCovariance, - Matrix biasAccOmegaInit, - bool use2ndOrderIntegration); - // PreintegratedCombinedMeasurements(const gtsam::PreintegratedCombinedMeasurements& rhs); - - // Testable - void print(string s) const; - bool equals(const gtsam::PreintegratedCombinedMeasurements& expected, double tol); - - double deltaTij() const; - gtsam::Rot3 deltaRij() const; - Vector deltaPij() const; - Vector deltaVij() const; - Vector biasHatVector() const; - Matrix delPdelBiasAcc() const; - Matrix delPdelBiasOmega() const; - Matrix delVdelBiasAcc() const; - Matrix delVdelBiasOmega() const; - Matrix delRdelBiasOmega() const; - Matrix preintMeasCov() const; - - // Standard Interface - void integrateMeasurement(Vector measuredAcc, Vector measuredOmega, double deltaT); - gtsam::PoseVelocityBias predict(const gtsam::Pose3& pose_i, Vector vel_i, const gtsam::imuBias::ConstantBias& bias, - Vector gravity, Vector omegaCoriolis) const; -}; - -virtual class CombinedImuFactor : gtsam::NonlinearFactor { - CombinedImuFactor(size_t pose_i, size_t vel_i, size_t pose_j, size_t vel_j, size_t bias_i, size_t bias_j, - const gtsam::PreintegratedCombinedMeasurements& CombinedPreintegratedMeasurements, Vector gravity, Vector omegaCoriolis); - // Standard Interface - gtsam::PreintegratedCombinedMeasurements preintegratedMeasurements() const; -}; - -#include -class PreintegratedAhrsMeasurements { - // Standard Constructor - PreintegratedAhrsMeasurements(Vector bias, Matrix measuredOmegaCovariance); - PreintegratedAhrsMeasurements(Vector bias, Matrix measuredOmegaCovariance); - PreintegratedAhrsMeasurements(const gtsam::PreintegratedAhrsMeasurements& rhs); - - // Testable - void print(string s) const; - bool equals(const gtsam::PreintegratedAhrsMeasurements& expected, double tol); - - // get Data - gtsam::Rot3 deltaRij() const; - double deltaTij() const; - Vector biasHat() const; - - // Standard Interface - void integrateMeasurement(Vector measuredOmega, double deltaT); - void resetIntegration() ; -}; - -virtual class AHRSFactor : gtsam::NonlinearFactor { - AHRSFactor(size_t rot_i, size_t rot_j,size_t bias, - const gtsam::PreintegratedAhrsMeasurements& preintegratedMeasurements, Vector omegaCoriolis); - AHRSFactor(size_t rot_i, size_t rot_j, size_t bias, - const gtsam::PreintegratedAhrsMeasurements& preintegratedMeasurements, Vector omegaCoriolis, - const gtsam::Pose3& body_P_sensor); - - // Standard Interface - gtsam::PreintegratedAhrsMeasurements preintegratedMeasurements() const; - Vector evaluateError(const gtsam::Rot3& rot_i, const gtsam::Rot3& rot_j, - Vector bias) const; - gtsam::Rot3 predict(const gtsam::Rot3& rot_i, Vector bias, - const gtsam::PreintegratedAhrsMeasurements& preintegratedMeasurements, - Vector omegaCoriolis) const; -}; - -#include -//virtual class AttitudeFactor : gtsam::NonlinearFactor { -// AttitudeFactor(const Unit3& nZ, const Unit3& bRef); -// AttitudeFactor(); -//}; -virtual class Rot3AttitudeFactor : gtsam::NonlinearFactor{ - Rot3AttitudeFactor(size_t key, const gtsam::Unit3& nZ, const gtsam::noiseModel::Diagonal* model, - const gtsam::Unit3& bRef); - Rot3AttitudeFactor(size_t key, const gtsam::Unit3& nZ, const gtsam::noiseModel::Diagonal* model); - Rot3AttitudeFactor(); - void print(string s) const; - bool equals(const gtsam::NonlinearFactor& expected, double tol) const; - gtsam::Unit3 nZ() const; - gtsam::Unit3 bRef() const; -}; - -virtual class Pose3AttitudeFactor : gtsam::NonlinearFactor{ - Pose3AttitudeFactor(size_t key, const gtsam::Unit3& nZ, const gtsam::noiseModel::Diagonal* model, - const gtsam::Unit3& bRef); - Pose3AttitudeFactor(size_t key, const gtsam::Unit3& nZ, const gtsam::noiseModel::Diagonal* model); - Pose3AttitudeFactor(); - void print(string s) const; - bool equals(const gtsam::NonlinearFactor& expected, double tol) const; - gtsam::Unit3 nZ() const; - gtsam::Unit3 bRef() const; -}; - -//************************************************************************* -// Utilities -//************************************************************************* - -namespace utilities { - - #include - gtsam::KeyList createKeyList(Vector I); - gtsam::KeyList createKeyList(string s, Vector I); - gtsam::KeyVector createKeyVector(Vector I); - gtsam::KeyVector createKeyVector(string s, Vector I); - gtsam::KeySet createKeySet(Vector I); - gtsam::KeySet createKeySet(string s, Vector I); - Matrix extractPoint2(const gtsam::Values& values); - Matrix extractPoint3(const gtsam::Values& values); - Matrix extractPose2(const gtsam::Values& values); - gtsam::Values allPose3s(gtsam::Values& values); - Matrix extractPose3(const gtsam::Values& values); - void perturbPoint2(gtsam::Values& values, double sigma, int seed); - void perturbPose2 (gtsam::Values& values, double sigmaT, double sigmaR, int seed); - void perturbPoint3(gtsam::Values& values, double sigma, int seed); - void insertBackprojections(gtsam::Values& values, const gtsam::SimpleCamera& c, Vector J, Matrix Z, double depth); - void insertProjectionFactors(gtsam::NonlinearFactorGraph& graph, size_t i, Vector J, Matrix Z, const gtsam::noiseModel::Base* model, const gtsam::Cal3_S2* K); - void insertProjectionFactors(gtsam::NonlinearFactorGraph& graph, size_t i, Vector J, Matrix Z, const gtsam::noiseModel::Base* model, const gtsam::Cal3_S2* K, const gtsam::Pose3& body_P_sensor); - Matrix reprojectionErrors(const gtsam::NonlinearFactorGraph& graph, const gtsam::Values& values); - gtsam::Values localToWorld(const gtsam::Values& local, const gtsam::Pose2& base); - gtsam::Values localToWorld(const gtsam::Values& local, const gtsam::Pose2& base, const gtsam::KeyVector& keys); - -} //\namespace utilities - -} From 7395cdd10f6728269ba6046f24fdcfc80be32f95 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 13 Jun 2019 17:05:15 -0400 Subject: [PATCH 085/160] typecast to size_t to remove build warnings --- gtsam/base/cholesky.cpp | 2 +- gtsam/linear/HessianFactor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/base/cholesky.cpp b/gtsam/base/cholesky.cpp index 31e4b82442..17cb291f02 100644 --- a/gtsam/base/cholesky.cpp +++ b/gtsam/base/cholesky.cpp @@ -111,7 +111,7 @@ bool choleskyPartial(Matrix& ABC, size_t nFrontal, size_t topleft) { return true; assert(ABC.cols() == ABC.rows()); - assert(ABC.rows() >= topleft); + assert(size_t(ABC.rows()) >= topleft); const size_t n = static_cast(ABC.rows() - topleft); assert(nFrontal <= size_t(n)); diff --git a/gtsam/linear/HessianFactor.cpp b/gtsam/linear/HessianFactor.cpp index d16373c78e..c208259b86 100644 --- a/gtsam/linear/HessianFactor.cpp +++ b/gtsam/linear/HessianFactor.cpp @@ -505,7 +505,7 @@ VectorValues HessianFactor::solve() { // Do Cholesky Factorization const size_t n = size(); - assert(info_.nBlocks() == n + 1); + assert(size_t(info_.nBlocks()) == n + 1); info_.choleskyPartial(n); auto R = info_.triangularView(0, n); auto eta = linearTerm(); From e72b42b61479df736b0469f4eee0aeef2378a6d5 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 13 Jun 2019 17:09:50 -0400 Subject: [PATCH 086/160] make all top level docs as markdown files --- DEVELOP => DEVELOP.md | 0 THANKS => THANKS.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename DEVELOP => DEVELOP.md (100%) rename THANKS => THANKS.md (100%) diff --git a/DEVELOP b/DEVELOP.md similarity index 100% rename from DEVELOP rename to DEVELOP.md diff --git a/THANKS b/THANKS.md similarity index 100% rename from THANKS rename to THANKS.md From b8292399d643f2ae445116b81caee7c57c9dcb96 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 13 Jun 2019 17:26:07 -0400 Subject: [PATCH 087/160] renamed all READMEs to README.md and updated markdown syntax --- DEVELOP.md | 20 +-- USAGE.md | 68 +++++----- cmake/README.md | 19 +-- cython/README.md | 122 ++++++++---------- gtsam_unstable/examples/{README => README.md} | 0 wrap/{README => README.md} | 14 +- 6 files changed, 110 insertions(+), 133 deletions(-) rename gtsam_unstable/examples/{README => README.md} (100%) rename wrap/{README => README.md} (71%) diff --git a/DEVELOP.md b/DEVELOP.md index 483197bc8c..133f3ea110 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -1,19 +1,19 @@ -Information for developers +# Information for Developers -Coding Conventions: +### Coding Conventions -* Classes are Uppercase, methods and functions lowerMixedCase -* We use a modified K&R Style, with 2-space tabs, inserting spaces for tabs -* Use meaningful variable names, e.g., measurement not msm +* Classes are Uppercase, methods and functions lowerMixedCase. +* We use a modified K&R Style, with 2-space tabs, inserting spaces for tabs. +* Use meaningful variable names, e.g. `measurement` not `msm`. -Windows: +### Windows -On Windows it is necessary to explicitly export all functions from the library -which should be externally accessible. To do this, include the macro -GTSAM_EXPORT in your class or function definition. +On Windows it is necessary to explicitly export all functions from the library which should be externally accessible. To do this, include the macro `GTSAM_EXPORT` in your class or function definition. For example: +```cpp class GTSAM_EXPORT MyClass { ... }; -GTSAM_EXPORT myFunction(); \ No newline at end of file +GTSAM_EXPORT myFunction(); +``` diff --git a/USAGE.md b/USAGE.md index 0493db6809..f8e4625502 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,48 +1,42 @@ -USAGE - Georgia Tech Smoothing and Mapping library -=================================== -What is this file? +# GTSAM USAGE - This file explains how to make use of the library for common SLAM tasks, - using a visual SLAM implementation as an example. - +This file explains how to make use of the library for common SLAM tasks, using a visual SLAM implementation as an example. + +## Getting Started + +### Install -Getting Started ---------------------------------------------------- -Install: - Follow the installation instructions in the README file to build and - install gtsam, as well as running tests to ensure the library is working - properly. - -Compiling/Linking with gtsam: - The installation creates a binary "libgtsam" at the installation prefix, - and an include folder "gtsam". These are the only required includes, but - the library has also been designed to make use of XML serialization through - the Boost.serialization library, which requires the the Boost.serialization - headers and binaries to be linked. - - If you use CMake for your project, you can use the CMake scripts in the - cmake folder for finding GTSAM, CppUnitLite, and Wrap. - -Examples: - To see how the library works, examine the unit tests provided. +Follow the installation instructions in the README file to build and install gtsam, as well as running tests to ensure the library is working properly. + +### Compiling/Linking with GTSAM + +The installation creates a binary `libgtsam` at the installation prefix, and an include folder `gtsam`. These are the only required includes, but the library has also been designed to make use of XML serialization through the `Boost.serialization` library, which requires the the Boost.serialization headers and binaries to be linked. + +If you use CMake for your project, you can use the CMake scripts in the cmake folder for finding `GTSAM`, `CppUnitLite`, and `Wrap`. + +### Examples + +To see how the library works, examine the unit tests provided. +## Overview + +The GTSAM library has three primary components necessary for the construction of factor graph representation and optimization which users will need to adapt to their particular problem. + +* FactorGraph + + A factor graph contains a set of variables to solve for (i.e., robot poses, landmark poses, etc.) and a set of constraints between these variables, which make up factors. + +* Values: + + Values is a single object containing labeled values for all of the variables. Currently, all variables are labeled with strings, but the type or organization of the variables can change. + +* Factors -Overview ---------------------------------------------------- -The GTSAM library has three primary components necessary for the construction -of factor graph representation and optimization which users will need to -adapt to their particular problem. - -* FactorGraph: - A factor graph contains a set of variables to solve for (i.e., robot poses, landmark poses, etc.) and a set of constraints between these variables, which make up factors. -* Values: - Values is a single object containing labeled values for all of the variables. Currently, all variables are labeled with strings, but the type or organization of the variables can change -* Factors: A nonlinear factor expresses a constraint between variables, which in the SLAM example, is a measurement such as a visual reading on a landmark or odometry. The library is organized according to the following directory structure: - 3rdparty local copies of third party libraries - Eigen3 and CCOLAMD + 3rdparty local copies of third party libraries e.g. Eigen3 and CCOLAMD base provides some base Math and data structures, as well as test-related utilities geometry points, poses, tensors, etc inference core graphical model inference such as factor graphs, junction trees, Bayes nets, Bayes trees diff --git a/cmake/README.md b/cmake/README.md index 34d1ffb525..7f38bbcf23 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -1,5 +1,4 @@ -GTSAMCMakeTools -=============== +# GTSAMCMakeTools This is the collection of GTSAM CMake tools that may be useful in external projects. The way to use this collection is by first making a find_package call: @@ -7,8 +6,7 @@ This is the collection of GTSAM CMake tools that may be useful in external proje which will add a directory containing the GTSAM CMake tools to the CMAKE_MODULE_PATH variable. After that, you may include the files you would like to use. These files and the functions they define are explained below. -GtsamBuildTypes ---------------- +## GtsamBuildTypes include(GtsamBuildTypes) @@ -17,8 +15,8 @@ Including this file immediately sets up the following build types and a drop-dow * `Debug` * `Release` * `RelWithDebInfo` -* `Profiling`: All optimizations enabled and minimal debug symbols -* `Timing`: Defines the symbol GTSAM_ENABLE_TIMING for using GTSAM timing instrumentation +* `Profiling`: All optimizations enabled and minimal debug symbols +* `Timing`: Defines the symbol GTSAM_ENABLE_TIMING for using GTSAM timing instrumentation It also configures several minor details, as follows: @@ -30,8 +28,7 @@ It defines the following functions: * `gtsam_assign_source_folders( [files] )` Organizes files in the IDE into folders to reflect the actual directory structure of those files. Folders will be determined relative to the current source folder when this function is called. * `gtsam_assign_all_source_folders()` Calls `gtsam_assign_source_folders` on all cpp, c, and h files recursively in the current source folder. -GtsamTesting ------------- +## GtsamTesting include(GtsamTesting) @@ -70,8 +67,7 @@ Defines two useful functions for creating CTest unit tests. Also immediately cr an empty string "" if nothing needs to be excluded. linkLibraries: The list of libraries to link to. -GtsamMatlabWrap ---------------- +## GtsamMatlabWrap include(GtsamMatlabWrap) @@ -97,8 +93,7 @@ Defines functions for generating MATLAB wrappers. Also immediately creates seve extraMexFlags: Any *additional* flags to pass to the compiler when building the wrap code. Normally, leave this empty. -GtsamMakeConfigFile -------------------- +## GtsamMakeConfigFile include(GtsamMakeConfigFile) diff --git a/cython/README.md b/cython/README.md index cb23b0d4a9..6dcdd7c1ce 100644 --- a/cython/README.md +++ b/cython/README.md @@ -1,7 +1,7 @@ This is the Cython/Python wrapper around the GTSAM C++ library. -INSTALL -======= +# INSTALL + - if you want to build the gtsam python library for a specific python version (eg 2.7), use the `-DGTSAM_PYTHON_VERSION=2.7` option when running `cmake` otherwise the default interpreter will be used. - If the interpreter is inside an environment (such as an anaconda environment or virtualenv environment) then the environment should be active while building gtsam. - This wrapper needs Cython(>=0.25.2), backports_abc>=0.5, and numpy. These can be installed as follows: @@ -27,8 +27,8 @@ export PYTHONPATH=$PYTHONPATH: - if you run `setup.py` from the build directory rather than the installation directory, the script will warn you with the message: `setup.py is being run from an unexpected location`. Before `make install` is run, not all the components of the package have been copied across, so running `setup.py` from the build directory would result in an incomplete package. -UNIT TESTS -========== +# UNIT TESTS + The Cython toolbox also has a small set of unit tests located in the test directory. To run them: @@ -37,8 +37,8 @@ test directory. To run them: python -m unittest discover ``` -WRITING YOUR OWN SCRIPTS -======================== +# WRITING YOUR OWN SCRIPTS + See the tests for examples. ## Some important notes: @@ -66,8 +66,8 @@ Examples: noiseGaussian = dynamic_cast_noiseModel_Gaussian_noiseModel_Base(noiseBase) ``` -WRAPPING YOUR OWN PROJECT THAT USES GTSAM -========================================= +# WRAPPING YOUR OWN PROJECT THAT USES GTSAM + - Set PYTHONPATH to include ${GTSAM_CYTHON_INSTALL_PATH} + so that it can find gtsam Cython header: gtsam/gtsam.pxd @@ -99,63 +99,55 @@ KNOWN ISSUES - support these constructors by default and declare "delete" for special classes? -TODO -===== -☐ allow duplication of parent' functions in child classes. Not allowed for now due to conflicts in Cython. -☐ a common header for boost shared_ptr? (Or wait until everything is switched to std::shared_ptr in gtsam?) -☐ inner namespaces ==> inner packages? -☐ Wrap fixed-size Matrices/Vectors? - - -Completed/Cancelled: -===== -✔ Fix Python tests: don't use " import * ": Bad style!!! @done (18-03-17 19:50) -✔ Unit tests for cython wrappers @done (18-03-17 18:45) -- simply compare generated files -✔ Wrap unstable @done (18-03-17 15:30) -✔ Unify cython/gtsam.h and the original gtsam.h @done (18-03-17 15:30) - ✔ 18-03-17: manage to unify the two versions by removing std container stubs from the matlab version,and keeping KeyList/KeyVector/KeySet as in the matlab version. Probably Cython 0.25 fixes the casting problem. - ✔ 06-03-17: manage to remove the requirements for default and copy constructors - ✘ 25-11-16: - Try to unify but failed. Main reasons are: Key/size_t, std containers, KeyVector/KeyList/KeySet. - Matlab doesn't need to know about Key, but I can't make Cython to ignore Key as it couldn't cast KeyVector, i.e. FastVector, to FastVector. -✘ Marginal and JointMarginal: revert changes @failed (17-03-17 11:00) -- Cython does need a default constructor! It produces cpp code like this: ```gtsam::JointMarginal __pyx_t_1;``` Users don't have to wrap this constructor, however. -✔ Convert input numpy Matrix/Vector to float dtype and storage order 'F' automatically, cannot crash! @done (15-03-17 13:00) -✔ Remove requirements.txt - Frank: don't bother with only 2 packages and a special case for eigency! @done (08-03-17 10:30) -✔ CMake install script @done (25-11-16 02:30) -✘ [REFACTOR] better name for uninstantiateClass: very vague!! @cancelled (25-11-16 02:30) -- lazy -✘ forward declaration? @cancelled (23-11-16 13:00) - nothing to do, seem to work? -✔ wrap VariableIndex: why is it in inference? If need to, shouldn't have constructors to specific FactorGraphs @done (23-11-16 13:00) -✔ Global functions @done (22-11-16 21:00) -✔ [REFACTOR] typesEqual --> isSameSignature @done (22-11-16 21:00) -✔ Proper overloads (constructors, static methods, methods) @done (20-11-16 21:00) -✔ Allow overloading methods. The current solution is annoying!!! @done (20-11-16 21:00) -✔ Casting from parent and grandparents @done (16-11-16 17:00) -✔ Allow overloading constructors. The current solution is annoying!!! @done (16-11-16 17:00) -✔ Support "print obj" @done (16-11-16 17:00) -✔ methods for FastVector: at, [], ... @done (16-11-16 17:00) -✔ Cython: Key and size_t: traits doesn't exist @done (16-09-12 18:34) -✔ KeyVector, KeyList, KeySet... @done (16-09-13 17:19) -✔ [Nice to have] parse typedef @done (16-09-13 17:19) -✔ ctypedef at correct places @done (16-09-12 18:34) -✔ expand template variable type in constructor/static methods? @done (16-09-12 18:34) -✔ NonlinearOptimizer: copy constructor deleted!!! @done (16-09-13 17:20) -✔ Value: no default constructor @done (16-09-13 17:20) -✔ ctypedef PriorFactor[Vector] PriorFactorVector @done (16-09-19 12:25) -✔ Delete duplicate methods in derived class @done (16-09-12 13:38) -✔ Fix return properly @done (16-09-11 17:14) - ✔ handle pair @done (16-09-11 17:14) -✔ Eigency: ambiguous call: A(const T&) A(const Vector& v) and Eigency A(Map[Vector]& v) @done (16-09-11 07:59) -✔ Eigency: Constructor: ambiguous construct from Vector/Matrix @done (16-09-11 07:59) -✔ Eigency: Fix method template of Vector/Matrix: template argument is [Vector] while arugment is Map[Vector] @done (16-09-11 08:22) -✔ Robust noise: copy assignment operator is deleted because of shared_ptr of the abstract Base class @done (16-09-10 09:05) -✘ Cython: Constructor: generate default constructor? (hack: if it's serializable?) @cancelled (16-09-13 17:20) -✘ Eigency: Map[] to Block @created(16-09-10 07:59) @cancelled (16-09-11 08:28) +# TODO + +- [ ] allow duplication of parent' functions in child classes. Not allowed for now due to conflicts in Cython. +- [ ] a common header for boost shared_ptr? (Or wait until everything is switched to std::shared_ptr in gtsam?) +- [ ] inner namespaces ==> inner packages? +- [ ] Wrap fixed-size Matrices/Vectors? + + +# Completed/Cancelled: + +- [x] Fix Python tests: don't use " import * ": Bad style!!! (18-03-17 19:50) +- [x] Unit tests for cython wrappers @done (18-03-17 18:45) -- simply compare generated files +- [x] Wrap unstable @done (18-03-17 15:30) +- [x] Unify cython/gtsam.h and the original gtsam.h @done (18-03-17 15:30) +- [x] 18-03-17: manage to unify the two versions by removing std container stubs from the matlab version,and keeping KeyList/KeyVector/KeySet as in the matlab version. Probably Cython 0.25 fixes the casting problem. +- [x] 06-03-17: manage to remove the requirements for default and copy constructors +- [ ] 25-11-16: Try to unify but failed. Main reasons are: Key/size_t, std containers, KeyVector/KeyList/KeySet. Matlab doesn't need to know about Key, but I can't make Cython to ignore Key as it couldn't cast KeyVector, i.e. FastVector, to FastVector. +- [ ] Marginal and JointMarginal: revert changes @failed (17-03-17 11:00) -- Cython does need a default constructor! It produces cpp code like this: ```gtsam::JointMarginal __pyx_t_1;``` Users don't have to wrap this constructor, however. +- [x] Convert input numpy Matrix/Vector to float dtype and storage order 'F' automatically, cannot crash! @done (15-03-17 13:00) +- [x] Remove requirements.txt - Frank: don't bother with only 2 packages and a special case for eigency! @done (08-03-17 10:30) +- [x] CMake install script @done (25-11-16 02:30) +- [ ] [REFACTOR] better name for uninstantiateClass: very vague!! @cancelled (25-11-16 02:30) -- lazy +- [ ] forward declaration? @cancelled (23-11-16 13:00) - nothing to do, seem to work? +- [x] wrap VariableIndex: why is it in inference? If need to, shouldn't have constructors to specific FactorGraphs @done (23-11-16 13:00) +- [x] Global functions @done (22-11-16 21:00) +- [x] [REFACTOR] typesEqual --> isSameSignature @done (22-11-16 21:00) +- [x] Proper overloads (constructors, static methods, methods) @done (20-11-16 21:00) +- [x] Allow overloading methods. The current solution is annoying!!! @done (20-11-16 21:00) +- [x] Casting from parent and grandparents @done (16-11-16 17:00) +- [x] Allow overloading constructors. The current solution is annoying!!! @done (16-11-16 17:00) +- [x] Support "print obj" @done (16-11-16 17:00) +- [x] methods for FastVector: at, [], ... @done (16-11-16 17:00) +- [x] Cython: Key and size_t: traits doesn't exist @done (16-09-12 18:34) +- [x] KeyVector, KeyList, KeySet... @done (16-09-13 17:19) +- [x] [Nice to have] parse typedef @done (16-09-13 17:19) +- [x] ctypedef at correct places @done (16-09-12 18:34) +- [x] expand template variable type in constructor/static methods? @done (16-09-12 18:34) +- [x] NonlinearOptimizer: copy constructor deleted!!! @done (16-09-13 17:20) +- [x] Value: no default constructor @done (16-09-13 17:20) +- [x] ctypedef PriorFactor[Vector] PriorFactorVector @done (16-09-19 12:25) +- [x] Delete duplicate methods in derived class @done (16-09-12 13:38) +- [x] Fix return properly @done (16-09-11 17:14) +- [x] handle pair @done (16-09-11 17:14) +- [x] Eigency: ambiguous call: A(const T&) A(const Vector& v) and Eigency A(Map[Vector]& v) @done (16-09-11 07:59) +- [x] Eigency: Constructor: ambiguous construct from Vector/Matrix @done (16-09-11 07:59) +- [x] Eigency: Fix method template of Vector/Matrix: template argument is [Vector] while arugment is Map[Vector] @done (16-09-11 08:22) +- [x] Robust noise: copy assignment operator is deleted because of shared_ptr of the abstract Base class @done (16-09-10 09:05) +- [ ] Cython: Constructor: generate default constructor? (hack: if it's serializable?) @cancelled (16-09-13 17:20) +- [ ] Eigency: Map[] to Block @created(16-09-10 07:59) @cancelled (16-09-11 08:28) - inference before symbolic/linear - what's the purpose of "virtual" ?? - -Installation: - ☐ Prerequisite: - - Users create venv and pip install requirements before compiling - - Wrap cython script in gtsam/cython folder - ☐ Install built module into venv? diff --git a/gtsam_unstable/examples/README b/gtsam_unstable/examples/README.md similarity index 100% rename from gtsam_unstable/examples/README rename to gtsam_unstable/examples/README.md diff --git a/wrap/README b/wrap/README.md similarity index 71% rename from wrap/README rename to wrap/README.md index 92fd1967cf..014577b5a9 100644 --- a/wrap/README +++ b/wrap/README.md @@ -1,25 +1,21 @@ -Frank Dellaert -October 2011 +# WRAP README The wrap library wraps the GTSAM library into a MATLAB toolbox. -It was designed to be more general than just wrapping GTSAM, but a small amount of -GTSAM specific code exists in matlab.h, the include file that is included by the -mex files. The GTSAM-specific functionality consists primarily of handling of -Eigen Matrix and Vector classes. +It was designed to be more general than just wrapping GTSAM, but a small amount of GTSAM specific code exists in matlab.h, the include file that is included by the mex files. The GTSAM-specific functionality consists primarily of handling of Eigen Matrix and Vector classes. -For notes on creating a wrap interface, see gtsam.h for what features can be -wrapped into a toolbox, as well as the current state of the toolbox for gtsam. -For more technical details on the interface, please read comments in matlab.h +For notes on creating a wrap interface, see gtsam.h for what features can be wrapped into a toolbox, as well as the current state of the toolbox for gtsam. For more technical details on the interface, please read comments in matlab.h Some good things to know: OBJECT CREATION + - Classes are created by special constructors, e.g., new_GaussianFactorGraph_.cpp. These constructors are called from the MATLAB class @GaussianFactorGraph. new_GaussianFactorGraph_ calls wrap_constructed in matlab.h, see documentation there METHOD (AND CONSTRUCTOR) ARGUMENTS + - Simple argument types of methods, such as "double", will be converted in the mex wrappers by calling unwrap, defined in matlab.h - Vector and Matrix arguments are normally passed by reference in GTSAM, but From 0448c4a982a9d05c3fede7d7cfc8882cab365411 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 13 Jun 2019 17:26:36 -0400 Subject: [PATCH 088/160] added blank github issue template --- .github/ISSUE_TEMPLATE | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 0000000000..e69de29bb2 From 7b4100794197bf8f0ff9984cd68b80966bbc46fc Mon Sep 17 00:00:00 2001 From: mxie32 Date: Fri, 14 Jun 2019 11:14:01 -0400 Subject: [PATCH 089/160] print key_values in order --- gtsam/linear/VectorValues.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index e8304e6e72..10556a073b 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -128,13 +128,32 @@ namespace gtsam { v.setZero(); } - /* ************************************************************************* */ - void VectorValues::print(const string& str, const KeyFormatter& formatter) const { - cout << str << ": " << size() << " elements\n"; - for(const value_type& key_value: *this) - cout << " " << formatter(key_value.first) << ": " << key_value.second.transpose() << "\n"; - cout.flush(); +/* ************************************************************************* */ +bool compare(std::pair& lhs, std::pair& rhs) { + return lhs.first < rhs.first; +} + +void VectorValues::print(const string& str, + const KeyFormatter& formatter) const { + cout << str << ": " << size() << " elements\n"; + // Change print depending on whether we are using TBB +#ifdef GTSAM_USE_TBB + std::vector> vec; + vec.reserve(size()); + for (const value_type& key_value : *this) { + vec.push_back(std::make_pair(key_value.first, key_value.second)); } + sort(vec.begin(), vec.end(), compare); + for (const auto& key_value : vec) + cout << " " << formatter(key_value.first) << ": " + << key_value.second.transpose() << "\n"; +#else + for (const value_type& key_value : *this) + cout << " " << formatter(key_value.first) << ": " + << key_value.second.transpose() << "\n"; +#endif + cout.flush(); +} /* ************************************************************************* */ bool VectorValues::equals(const VectorValues& x, double tol) const { From 87614cdc8dd7d99d83108b9c35e6eaf996cbf038 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 14 Jun 2019 13:31:38 -0400 Subject: [PATCH 090/160] github issue templates conforming to new workflow --- .github/ISSUE_TEMPLATE | 0 .../ISSUE_TEMPLATE/questions-help-support.md | 5 +++ .github/bug-report.md | 35 +++++++++++++++++++ .github/feature-request.md | 24 +++++++++++++ 4 files changed, 64 insertions(+) delete mode 100644 .github/ISSUE_TEMPLATE create mode 100644 .github/ISSUE_TEMPLATE/questions-help-support.md create mode 100644 .github/bug-report.md create mode 100644 .github/feature-request.md diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.github/ISSUE_TEMPLATE/questions-help-support.md b/.github/ISSUE_TEMPLATE/questions-help-support.md new file mode 100644 index 0000000000..49dcefbd01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/questions-help-support.md @@ -0,0 +1,5 @@ +--- +name: "Questions/Help/Support" +--- + +Please post questions and support requests in the [GTSAM Google group](https://groups.google.com/forum/#!forum/gtsam-users) and not on Github. diff --git a/.github/bug-report.md b/.github/bug-report.md new file mode 100644 index 0000000000..9f15b2b7c2 --- /dev/null +++ b/.github/bug-report.md @@ -0,0 +1,35 @@ +--- +name: "Bug Report" +about: Submit a bug report to help us improve GTSAM +--- + + + + + + + +## Description + + + +## Steps to reproduce + +1. +2. + + + +## Expected behavior + + + +## Environment + + + + + +## Additional information + + \ No newline at end of file diff --git a/.github/feature-request.md b/.github/feature-request.md new file mode 100644 index 0000000000..e1e13650a2 --- /dev/null +++ b/.github/feature-request.md @@ -0,0 +1,24 @@ +--- +name: "Feature Request" +about: Submit a proposal/request for a new GTSAM feature +--- + +## Feature + + + +## Motivation + + + +## Pitch + + + +## Alternatives + + + +## Additional context + + \ No newline at end of file From a18424666d4a7534556c050a211fb426d40e690e Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Fri, 14 Jun 2019 13:43:01 -0400 Subject: [PATCH 091/160] moved templates from .github to .github/ISSUE_TEMPLATE --- .github/{ => ISSUE_TEMPLATE}/bug-report.md | 0 .github/{ => ISSUE_TEMPLATE}/feature-request.md | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename .github/{ => ISSUE_TEMPLATE}/bug-report.md (100%) rename .github/{ => ISSUE_TEMPLATE}/feature-request.md (100%) diff --git a/.github/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md similarity index 100% rename from .github/bug-report.md rename to .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md similarity index 100% rename from .github/feature-request.md rename to .github/ISSUE_TEMPLATE/feature-request.md From 9ac72a017b588e7a734fe4bdede43a229d5ab3eb Mon Sep 17 00:00:00 2001 From: mxie32 Date: Fri, 14 Jun 2019 14:57:56 -0400 Subject: [PATCH 092/160] overload operator <<, and add unittest --- gtsam/linear/VectorValues.cpp | 49 ++++++++++++++----------- gtsam/linear/VectorValues.h | 6 +++ gtsam/linear/tests/testVectorValues.cpp | 20 ++++++++++ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index 10556a073b..2da1a12f1f 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -128,31 +128,38 @@ namespace gtsam { v.setZero(); } -/* ************************************************************************* */ -bool compare(std::pair& lhs, std::pair& rhs) { - return lhs.first < rhs.first; -} + /* ************************************************************************* */ + bool compare(std::pair& lhs, std::pair& rhs) { + return lhs.first < rhs.first; + } -void VectorValues::print(const string& str, - const KeyFormatter& formatter) const { - cout << str << ": " << size() << " elements\n"; - // Change print depending on whether we are using TBB + ostream& operator<<(ostream& ss, const VectorValues& v) { + ss << "VectorValues: " + << ": " << v.size() << " elements\n"; + // Change print depending on whether we are using TBB #ifdef GTSAM_USE_TBB - std::vector> vec; - vec.reserve(size()); - for (const value_type& key_value : *this) { - vec.push_back(std::make_pair(key_value.first, key_value.second)); - } - sort(vec.begin(), vec.end(), compare); - for (const auto& key_value : vec) - cout << " " << formatter(key_value.first) << ": " - << key_value.second.transpose() << "\n"; + std::vector> vec; + vec.reserve(v.size()); + for (const auto& key_value : v) { + vec.push_back(std::make_pair(key_value.first, key_value.second)); + } + sort(vec.begin(), vec.end(), compare); + for (const auto& key_value : vec) + ss << " " << key_value.first << ": " << key_value.second.transpose() + << "\n"; #else - for (const value_type& key_value : *this) - cout << " " << formatter(key_value.first) << ": " - << key_value.second.transpose() << "\n"; + for (const auto& key_value : v) + ss << " " << key_value.first << ": " << key_value.second.transpose() + << "\n"; #endif - cout.flush(); + return ss; + } + + /* ************************************************************************* */ + void VectorValues::print(const string& str, + const KeyFormatter& formatter) const { + cout << *this; + cout.flush(); } /* ************************************************************************* */ diff --git a/gtsam/linear/VectorValues.h b/gtsam/linear/VectorValues.h index b1d9de585d..32a311848a 100644 --- a/gtsam/linear/VectorValues.h +++ b/gtsam/linear/VectorValues.h @@ -29,6 +29,7 @@ #include #include +#include namespace gtsam { @@ -228,6 +229,11 @@ namespace gtsam { */ const_iterator find(Key j) const { return values_.find(j); } + /** + * overload operator << to print to stringstream + */ + friend std::ostream& operator<<(std::ostream& ss, const VectorValues& v); + /** print required by Testable for unit testing */ void print(const std::string& str = "VectorValues: ", const KeyFormatter& formatter = DefaultKeyFormatter) const; diff --git a/gtsam/linear/tests/testVectorValues.cpp b/gtsam/linear/tests/testVectorValues.cpp index d1d9990b0a..28600ecadb 100644 --- a/gtsam/linear/tests/testVectorValues.cpp +++ b/gtsam/linear/tests/testVectorValues.cpp @@ -24,6 +24,8 @@ #include #include +#include + using namespace std; using namespace boost::assign; using boost::adaptors::map_keys; @@ -228,6 +230,24 @@ TEST(VectorValues, vector_sub) EXPECT(assert_equal(expected, vv.vector(dims))); } +/* ************************************************************************* */ +TEST(VectorValues, print) +{ + VectorValues vv; + vv.insert(0, (Vector(1) << 1).finished()); + vv.insert(1, Vector2(2, 3)); + vv.insert(2, Vector2(4, 5)); + vv.insert(5, Vector2(6, 7)); + vv.insert(7, Vector2(8, 9)); + + string expected = + "VectorValues: : 5 elements\n 0: 1\n 1: 2 3\n 2: 4 5\n 5: 6 7\n 7: 8 9\n"; + stringstream actual; + actual << vv; + + EXPECT(expected == actual.str()); +} + /* ************************************************************************* */ int main() { TestResult tr; return TestRegistry::runAllTests(tr); } /* ************************************************************************* */ From 6c665b818bffac765c7d5efe52918f7ee2f67631 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 14 Jun 2019 15:01:33 -0400 Subject: [PATCH 093/160] Streaming Key values with a custom formatter --- gtsam/inference/Key.cpp | 40 ++++++++++++++++++++++++- gtsam/inference/Key.h | 30 +++++++++++++++++++ gtsam/inference/tests/testKey.cpp | 50 ++++++++++++++++++++++++------- 3 files changed, 109 insertions(+), 11 deletions(-) diff --git a/gtsam/inference/Key.cpp b/gtsam/inference/Key.cpp index f257274410..dd433ff094 100644 --- a/gtsam/inference/Key.cpp +++ b/gtsam/inference/Key.cpp @@ -56,7 +56,7 @@ string _multirobotKeyFormatter(Key key) { /* ************************************************************************* */ template -static void Print(const CONTAINER& keys, const string& s, +void Print(const CONTAINER& keys, const string& s, const KeyFormatter& keyFormatter) { cout << s << " "; if (keys.empty()) @@ -83,6 +83,44 @@ void PrintKeySet(const KeySet& keys, const string& s, const KeyFormatter& keyFormatter) { Print(keys, s, keyFormatter); } + +/* ************************************************************************* */ +// Access to custom stream property. +void *&key_formatter::property(ios_base &s) { + static int kUniqueIndex = ios_base::xalloc(); + return s.pword(kUniqueIndex); +} + +/* ************************************************************************* */ +// Store pointer to formatter in property. +void key_formatter::set_property(ios_base &s, const KeyFormatter &f) { + property(s) = (void *)(&f); +} + +/* ************************************************************************* */ +// Get pointer to formatter from property. +KeyFormatter *key_formatter::get_property(ios_base &s) { + return (KeyFormatter *)(property(s)); +} + +/* ************************************************************************* */ +// Stream operator that will take a key_formatter and set the stream property. +ostream &operator<<(ostream &os, const key_formatter &m) { + key_formatter::set_property(os, m.formatter_); + return os; +} + +/* ************************************************************************* */ +// Stream operator that takes a StreamedKey and properly formats it +ostream &operator<<(ostream &os, const StreamedKey &streamedKey) { + const KeyFormatter *formatter = key_formatter::get_property(os); + if (formatter == nullptr) { + formatter = &DefaultKeyFormatter; + } + os << (*formatter)(streamedKey.key_); + return (os); +} + /* ************************************************************************* */ } // \namespace gtsam diff --git a/gtsam/inference/Key.h b/gtsam/inference/Key.h index d400a33c06..ae3f3844b2 100644 --- a/gtsam/inference/Key.h +++ b/gtsam/inference/Key.h @@ -27,6 +27,8 @@ #include +#include + namespace gtsam { /// Typedef for a function to format a key, i.e. to convert it to a string @@ -52,6 +54,34 @@ GTSAM_EXPORT std::string _multirobotKeyFormatter(gtsam::Key key); static const gtsam::KeyFormatter MultiRobotKeyFormatter = &_multirobotKeyFormatter; +/// To use the key_formatter on Keys, they must be wrapped in a StreamedKey. +struct StreamedKey { + const Key &key_; + explicit StreamedKey(const Key &key) : key_(key) {} + friend std::ostream &operator<<(std::ostream &, const StreamedKey &); +}; + +/** + * Output stream manipulator that will format gtsam::Keys according to the given + * KeyFormatter, as long as Key values are wrapped in a gtsam::StreamedKey. + * LabeledSymbol and Symbol values do not have to be wrapped. + * usage: + * Key key = LabeledSymbol('x', 'A', 5); // cast to key type + * cout << key_formatter(MultiRobotKeyFormatter) << StreamedKey(key); + */ +class key_formatter { + public: + explicit key_formatter(KeyFormatter v) : formatter_(v) {} + friend std::ostream &operator<<(std::ostream &, const key_formatter &); + friend std::ostream &operator<<(std::ostream &, const StreamedKey &); + + private: + KeyFormatter formatter_; + static void *&property(std::ios_base &s); + static void set_property(std::ios_base &s, const KeyFormatter &f); + static KeyFormatter *get_property(std::ios_base &s); +}; + /// Define collection type once and for all - also used in wrappers typedef FastVector KeyVector; diff --git a/gtsam/inference/tests/testKey.cpp b/gtsam/inference/tests/testKey.cpp index 93a161ccde..fcdb5709b0 100644 --- a/gtsam/inference/tests/testKey.cpp +++ b/gtsam/inference/tests/testKey.cpp @@ -22,6 +22,9 @@ #include #include // for operator += + +#include + using namespace boost::assign; using namespace std; using namespace gtsam; @@ -41,17 +44,15 @@ TEST(Key, KeySymbolConversion) { template Key KeyTestValue(); -template<> -Key KeyTestValue<8>() -{ +template <> +Key KeyTestValue<8>() { return 0x6100000000000005; -}; +} -template<> -Key KeyTestValue<4>() -{ +template <> +Key KeyTestValue<4>() { return 0x61000005; -}; +} /* ************************************************************************* */ TEST(Key, KeySymbolEncoding) { @@ -68,12 +69,41 @@ TEST(Key, KeySymbolEncoding) { /* ************************************************************************* */ TEST(Key, ChrTest) { - Key key = Symbol('c',3); + Symbol key('c', 3); EXPECT(Symbol::ChrTest('c')(key)); EXPECT(!Symbol::ChrTest('d')(key)); } /* ************************************************************************* */ -int main() { TestResult tr; return TestRegistry::runAllTests(tr); } +// A custom (nonsensical) formatter. +string myFormatter(Key key) { + return "special"; +} + +TEST(Key, Formatting) { + Symbol key('c', 3); + EXPECT("c3" == DefaultKeyFormatter(key)); + + // Try streaming keys, should be default-formatted. + stringstream ss; + ss << StreamedKey(key); + EXPECT("c3" == ss.str()); + + // use key_formatter with a function pointer + stringstream ss2; + ss2 << key_formatter(myFormatter) << StreamedKey(key); + EXPECT("special" == ss2.str()); + + // use key_formatter with a function object. + stringstream ss3; + ss3 << key_formatter(DefaultKeyFormatter) << StreamedKey(key); + EXPECT("c3" == ss3.str()); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} /* ************************************************************************* */ From 4e2713026cb12d7de65422621d8d33a589ee556b Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 14 Jun 2019 15:31:08 -0400 Subject: [PATCH 094/160] Streaming for Symbols --- gtsam/inference/Symbol.cpp | 5 +++ gtsam/inference/Symbol.h | 3 ++ gtsam/inference/tests/testSymbol.cpp | 50 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 gtsam/inference/tests/testSymbol.cpp diff --git a/gtsam/inference/Symbol.cpp b/gtsam/inference/Symbol.cpp index ccabcb07e0..5e41b3eac4 100644 --- a/gtsam/inference/Symbol.cpp +++ b/gtsam/inference/Symbol.cpp @@ -66,5 +66,10 @@ boost::function Symbol::ChrTest(unsigned char c) { return bind(&Symbol::chr, bind(make, _1)) == c; } +std::ostream &operator<<(std::ostream &os, const Symbol &symbol) { + os << StreamedKey(symbol); + return os; +} + } // namespace gtsam diff --git a/gtsam/inference/Symbol.h b/gtsam/inference/Symbol.h index 8e22202ed9..86574f70d4 100644 --- a/gtsam/inference/Symbol.h +++ b/gtsam/inference/Symbol.h @@ -112,6 +112,9 @@ class GTSAM_EXPORT Symbol { */ static boost::function ChrTest(unsigned char c); + /// Output stream operator that can be used with key_formatter (see Key.h). + friend std::ostream &operator<<(std::ostream &, const Symbol &); + private: /** Serialization function */ diff --git a/gtsam/inference/tests/testSymbol.cpp b/gtsam/inference/tests/testSymbol.cpp new file mode 100644 index 0000000000..43a0e219a1 --- /dev/null +++ b/gtsam/inference/tests/testSymbol.cpp @@ -0,0 +1,50 @@ +/* ---------------------------------------------------------------------------- + + * 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 testSymbol.cpp + * @author Frank Dellaert + */ + +#include + +#include + +using namespace std; +using namespace gtsam; + +/* ************************************************************************* */ +// A custom (nonsensical) formatter. +string myFormatter(Key key) { + return "special"; +} + +TEST(Symbol, Formatting) { + Symbol symbol('c', 3); + + // use key_formatter with a function pointer + stringstream ss2; + ss2 << key_formatter(myFormatter) << symbol; + EXPECT("special" == ss2.str()); + + // use key_formatter with a function object. + stringstream ss3; + ss3 << key_formatter(MultiRobotKeyFormatter) << symbol; + EXPECT("c3" == ss3.str()); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} +/* ************************************************************************* */ + From 86b4be3304dbf12053349125de083fee6c783f69 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Fri, 14 Jun 2019 15:31:23 -0400 Subject: [PATCH 095/160] Streaming for LabeledSymbols --- gtsam/inference/LabeledSymbol.cpp | 7 ++++++ gtsam/inference/LabeledSymbol.h | 7 +++++- gtsam/inference/tests/testLabeledSymbol.cpp | 25 ++++++++++++++++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/gtsam/inference/LabeledSymbol.cpp b/gtsam/inference/LabeledSymbol.cpp index 0d9c35d2c7..6d34883fa3 100644 --- a/gtsam/inference/LabeledSymbol.cpp +++ b/gtsam/inference/LabeledSymbol.cpp @@ -121,6 +121,13 @@ boost::function LabeledSymbol::TypeLabelTest(unsigned char c, return boost::bind(&LabeledSymbol::chr, boost::bind(make, _1)) == c && boost::bind(&LabeledSymbol::label, boost::bind(make, _1)) == label; } + +/* ************************************************************************* */ +std::ostream &operator<<(std::ostream &os, const LabeledSymbol &symbol) { + os << StreamedKey(symbol); + return os; +} + /* ************************************************************************* */ } // \namespace gtsam diff --git a/gtsam/inference/LabeledSymbol.h b/gtsam/inference/LabeledSymbol.h index 8c521b0678..5b3ec8766f 100644 --- a/gtsam/inference/LabeledSymbol.h +++ b/gtsam/inference/LabeledSymbol.h @@ -100,10 +100,15 @@ class GTSAM_EXPORT LabeledSymbol { LabeledSymbol upper() const { return LabeledSymbol(c_, toupper(label_), j_); } LabeledSymbol lower() const { return LabeledSymbol(c_, tolower(label_), j_); } - // Create a new symbol with a different value + // Create a new symbol with a different character. LabeledSymbol newChr(unsigned char c) const { return LabeledSymbol(c, label_, j_); } + + // Create a new symbol with a different label. LabeledSymbol newLabel(unsigned char label) const { return LabeledSymbol(c_, label, j_); } + /// Output stream operator that can be used with key_formatter (see Key.h). + friend std::ostream &operator<<(std::ostream &, const LabeledSymbol &); + private: /** Serialization function */ diff --git a/gtsam/inference/tests/testLabeledSymbol.cpp b/gtsam/inference/tests/testLabeledSymbol.cpp index b463f41310..2a56b39c21 100644 --- a/gtsam/inference/tests/testLabeledSymbol.cpp +++ b/gtsam/inference/tests/testLabeledSymbol.cpp @@ -80,6 +80,29 @@ TEST(LabeledSymbol, ChrTest) { } /* ************************************************************************* */ -int main() { TestResult tr; return TestRegistry::runAllTests(tr); } +// A custom (nonsensical) formatter. +string myFormatter(Key key) { + return "special"; +} + +TEST(LabeledSymbol, Formatting) { + LabeledSymbol symbol('c', 'A', 3); + + // use key_formatter with a function pointer + stringstream ss2; + ss2 << key_formatter(myFormatter) << symbol; + EXPECT("special" == ss2.str()); + + // use key_formatter with a function object. + stringstream ss3; + ss3 << key_formatter(MultiRobotKeyFormatter) << symbol; + EXPECT("cA3" == ss3.str()); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} /* ************************************************************************* */ From 5ba91939c7215ac9392ec4e75d76c7f2c8795dc8 Mon Sep 17 00:00:00 2001 From: mxie32 Date: Fri, 14 Jun 2019 15:55:08 -0400 Subject: [PATCH 096/160] fix issues according to pr comments --- gtsam/linear/VectorValues.cpp | 24 ++++++++---------------- gtsam/linear/VectorValues.h | 10 ++++------ gtsam/linear/tests/testVectorValues.cpp | 3 +-- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index 2da1a12f1f..bb0dbf6254 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -129,30 +129,22 @@ namespace gtsam { } /* ************************************************************************* */ - bool compare(std::pair& lhs, std::pair& rhs) { - return lhs.first < rhs.first; - } - - ostream& operator<<(ostream& ss, const VectorValues& v) { - ss << "VectorValues: " + ostream& operator<<(ostream& os, const VectorValues& v) { + os << "VectorValues" << ": " << v.size() << " elements\n"; // Change print depending on whether we are using TBB #ifdef GTSAM_USE_TBB - std::vector> vec; - vec.reserve(v.size()); + map sorted; for (const auto& key_value : v) { - vec.push_back(std::make_pair(key_value.first, key_value.second)); + sorted.insert(std::make_pair(key_value.first, key_value.second)); } - sort(vec.begin(), vec.end(), compare); - for (const auto& key_value : vec) - ss << " " << key_value.first << ": " << key_value.second.transpose() - << "\n"; + for (const auto& key_value : sorted) #else for (const auto& key_value : v) - ss << " " << key_value.first << ": " << key_value.second.transpose() - << "\n"; #endif - return ss; + os << " " << key_value.first << ": " << key_value.second.transpose() + << "\n"; + return os; } /* ************************************************************************* */ diff --git a/gtsam/linear/VectorValues.h b/gtsam/linear/VectorValues.h index 32a311848a..fe6b5fcb2c 100644 --- a/gtsam/linear/VectorValues.h +++ b/gtsam/linear/VectorValues.h @@ -29,7 +29,7 @@ #include #include -#include +#include namespace gtsam { @@ -229,13 +229,11 @@ namespace gtsam { */ const_iterator find(Key j) const { return values_.find(j); } - /** - * overload operator << to print to stringstream - */ - friend std::ostream& operator<<(std::ostream& ss, const VectorValues& v); + /// overload operator << to print to stringstream + friend std::ostream& operator<<(std::ostream&, const VectorValues&); /** print required by Testable for unit testing */ - void print(const std::string& str = "VectorValues: ", + void print(const std::string& str = "VectorValues", const KeyFormatter& formatter = DefaultKeyFormatter) const; /** equals required by Testable for unit testing */ diff --git a/gtsam/linear/tests/testVectorValues.cpp b/gtsam/linear/tests/testVectorValues.cpp index 28600ecadb..2f7e0a35c7 100644 --- a/gtsam/linear/tests/testVectorValues.cpp +++ b/gtsam/linear/tests/testVectorValues.cpp @@ -241,10 +241,9 @@ TEST(VectorValues, print) vv.insert(7, Vector2(8, 9)); string expected = - "VectorValues: : 5 elements\n 0: 1\n 1: 2 3\n 2: 4 5\n 5: 6 7\n 7: 8 9\n"; + "VectorValues: 5 elements\n 0: 1\n 1: 2 3\n 2: 4 5\n 5: 6 7\n 7: 8 9\n"; stringstream actual; actual << vv; - EXPECT(expected == actual.str()); } From 8e9aa9718d4c5fa8455b24ac0ecc69ddbb7d46e1 Mon Sep 17 00:00:00 2001 From: mxie32 Date: Fri, 14 Jun 2019 16:20:41 -0400 Subject: [PATCH 097/160] use map::emplace instead of insert --- gtsam/linear/VectorValues.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index bb0dbf6254..288b4471a8 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -136,14 +136,16 @@ namespace gtsam { #ifdef GTSAM_USE_TBB map sorted; for (const auto& key_value : v) { - sorted.insert(std::make_pair(key_value.first, key_value.second)); + sorted.emplace(std::make_pair(key_value.first, key_value.second)); } for (const auto& key_value : sorted) #else for (const auto& key_value : v) #endif + { os << " " << key_value.first << ": " << key_value.second.transpose() << "\n"; + } return os; } From c087dd336ba2af674d7b561b222af2da14409ebc Mon Sep 17 00:00:00 2001 From: mxie32 Date: Fri, 14 Jun 2019 16:44:45 -0400 Subject: [PATCH 098/160] delete make_pair --- gtsam/linear/VectorValues.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index 288b4471a8..976f65971c 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -136,7 +136,7 @@ namespace gtsam { #ifdef GTSAM_USE_TBB map sorted; for (const auto& key_value : v) { - sorted.emplace(std::make_pair(key_value.first, key_value.second)); + sorted.emplace(key_value.first, key_value.second); } for (const auto& key_value : sorted) #else From 0fc812f4df4c7f9ed05fbb1b429553defa0385ae Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Sat, 15 Jun 2019 10:13:03 +0200 Subject: [PATCH 099/160] Missing DLL export macros --- gtsam/navigation/CombinedImuFactor.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/navigation/CombinedImuFactor.h b/gtsam/navigation/CombinedImuFactor.h index a7802120c9..2ad71cb3cb 100644 --- a/gtsam/navigation/CombinedImuFactor.h +++ b/gtsam/navigation/CombinedImuFactor.h @@ -64,7 +64,7 @@ typedef ManifoldPreintegration PreintegrationType; * * @addtogroup SLAM */ -class PreintegratedCombinedMeasurements : public PreintegrationType { +class GTSAM_EXPORT PreintegratedCombinedMeasurements : public PreintegrationType { public: @@ -222,7 +222,7 @@ class PreintegratedCombinedMeasurements : public PreintegrationType { * * @addtogroup SLAM */ -class CombinedImuFactor: public NoiseModelFactor6 { public: From bf20ec91defcfc393885dd666fd207c519e4c192 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Sat, 15 Jun 2019 10:15:34 +0200 Subject: [PATCH 100/160] cmake: fix unset variable for MSVC --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85bb700360..92da64c11d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -527,7 +527,7 @@ if(GTSAM_UNSTABLE_AVAILABLE) print_config_flag(${GTSAM_BUILD_UNSTABLE} "Build libgtsam_unstable ") endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" cmake_build_type_toupper) -print_config_flag(${GTSAM_BUILD_WITH_MARCH_NATIVE} "Build for native architecture ") +print_config_flag("${GTSAM_BUILD_WITH_MARCH_NATIVE}" "Build for native architecture ") if(NOT MSVC AND NOT XCODE_VERSION) message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") message(STATUS " C compilation flags : ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${cmake_build_type_toupper}}") From 89f8f501949c6f3fca50210208758f606ccf0eb3 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Sat, 15 Jun 2019 14:39:58 +0200 Subject: [PATCH 101/160] T made non-const for MSVC2017 to build --- gtsam/nonlinear/Values-inl.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/nonlinear/Values-inl.h b/gtsam/nonlinear/Values-inl.h index 7e14578c39..ff220044a0 100644 --- a/gtsam/nonlinear/Values-inl.h +++ b/gtsam/nonlinear/Values-inl.h @@ -145,13 +145,13 @@ namespace gtsam { boost::make_filter_iterator(filter, ((const Values&) values).begin(), ((const Values&) values).end()), - &ValuesCastHelper::cast)), constEnd_( boost::make_transform_iterator( boost::make_filter_iterator(filter, ((const Values&) values).end(), ((const Values&) values).end()), - &ValuesCastHelper::cast)) { } From ba91bd53fd26d9a5de372e474d21d277239b97de Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 15 Jun 2019 11:11:11 -0400 Subject: [PATCH 102/160] Add better error reporting --- gtsam.h | 2 +- wrap/Class.cpp | 44 ++++++++++++++++++++++++++++++-------------- wrap/Method.cpp | 8 ++++---- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/gtsam.h b/gtsam.h index ba5aa5f4f9..97eb2d8c30 100644 --- a/gtsam.h +++ b/gtsam.h @@ -1870,7 +1870,6 @@ class NonlinearFactorGraph { // FactorGraph void print(string s) const; - void printErrors(const gtsam::Values& values); bool equals(const gtsam::NonlinearFactorGraph& fg, double tol) const; size_t size() const; bool empty() const; @@ -1887,6 +1886,7 @@ class NonlinearFactorGraph { gtsam::KeyVector keyVector() const; // NonlinearFactorGraph + void printErrors(const gtsam::Values& values) const; double error(const gtsam::Values& values) const; double probPrime(const gtsam::Values& values) const; gtsam::Ordering orderingCOLAMD() const; diff --git a/wrap/Class.cpp b/wrap/Class.cpp index 5c1e6187ed..65ce9eab7c 100644 --- a/wrap/Class.cpp +++ b/wrap/Class.cpp @@ -342,17 +342,21 @@ vector Class::expandTemplate(Str templateArg, /* ************************************************************************* */ void Class::addMethod(bool verbose, bool is_const, Str methodName, - const ArgumentList& argumentList, const ReturnValue& returnValue, - const Template& tmplate) { + const ArgumentList& argumentList, + const ReturnValue& returnValue, const Template& tmplate) { // Check if templated if (tmplate.valid()) { - templateMethods_[methodName].addOverload(methodName, argumentList, - returnValue, is_const, - tmplate.argName(), verbose); + try { + templateMethods_[methodName].addOverload(methodName, argumentList, + returnValue, is_const, + tmplate.argName(), verbose); + } catch (const std::runtime_error& e) { + throw std::runtime_error("Class::addMethod: error adding " + name_ + + "::" + methodName + "\n" + e.what()); + } // Create method to expand // For all values of the template argument, create a new method - for(const Qualified& instName: tmplate.argValues()) { - + for (const Qualified& instName : tmplate.argValues()) { const TemplateSubstitution ts(tmplate.argName(), instName, *this); // substitute template in arguments ArgumentList expandedArgs = argumentList.expandTemplate(ts); @@ -361,15 +365,27 @@ void Class::addMethod(bool verbose, bool is_const, Str methodName, // Now stick in new overload stack with expandedMethodName key // but note we use the same, unexpanded methodName in overload string expandedMethodName = methodName + instName.name(); - methods_[expandedMethodName].addOverload(methodName, expandedArgs, - expandedRetVal, is_const, instName, verbose); + try { + methods_[expandedMethodName].addOverload(methodName, expandedArgs, + expandedRetVal, is_const, + instName, verbose); + } catch (const std::runtime_error& e) { + throw std::runtime_error("Class::addMethod: error adding " + name_ + + "::" + expandedMethodName + "\n" + e.what()); + } } } else { - // just add overload - methods_[methodName].addOverload(methodName, argumentList, returnValue, - is_const, boost::none, verbose); - nontemplateMethods_[methodName].addOverload(methodName, argumentList, returnValue, - is_const, boost::none, verbose); + try { + // just add overload + methods_[methodName].addOverload(methodName, argumentList, returnValue, + is_const, boost::none, verbose); + nontemplateMethods_[methodName].addOverload(methodName, argumentList, + returnValue, is_const, + boost::none, verbose); + } catch (const std::runtime_error& e) { + throw std::runtime_error("Class::addMethod: error adding " + name_ + + "::" + methodName + "\n" + e.what()); + } } } diff --git a/wrap/Method.cpp b/wrap/Method.cpp index f7247341ce..2a4b0b3afd 100644 --- a/wrap/Method.cpp +++ b/wrap/Method.cpp @@ -38,12 +38,12 @@ bool Method::addOverload(Str name, const ArgumentList& args, is_const_ = is_const; else if (is_const && !is_const_) throw std::runtime_error( - "Method::addOverload now designated as const whereas before it was " - "not"); + "Method::addOverload: " + name + + " now designated as const whereas before it was not"); else if (!is_const && is_const_) throw std::runtime_error( - "Method::addOverload now designated as non-const whereas before it " - "was"); + "Method::addOverload: " + name + + " now designated as non-const whereas before it was"); return first; } From 01ce03673c1f5df4d7560a0a748f75b9992b1957 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 13 Jun 2019 10:33:45 -0400 Subject: [PATCH 103/160] Excluded build that consistently times out --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b2d44a9ccd..1e2d6760a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -101,9 +101,13 @@ env: script: - bash .travis.sh -t -# Exclude clang on Linux/clang in release until issue #57 is solved matrix: exclude: + # Exclude g++ debug on Linux as it consistently times out + - os: linux + compiler: gcc + env : CMAKE_BUILD_TYPE=Debug GTSAM_BUILD_UNSTABLE=OFF + # Exclude clang on Linux/clang in release until issue #57 is solved - os: linux compiler: clang env : CMAKE_BUILD_TYPE=Release From 876475a774cb5d102801bbc7f297f73bf451b51c Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Sat, 15 Jun 2019 14:09:19 -0400 Subject: [PATCH 104/160] Restored default deltaInit to 1.0 to resolve issue #9 --- gtsam/nonlinear/DoglegOptimizer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/nonlinear/DoglegOptimizer.h b/gtsam/nonlinear/DoglegOptimizer.h index 1fa25fd60c..6b30974762 100644 --- a/gtsam/nonlinear/DoglegOptimizer.h +++ b/gtsam/nonlinear/DoglegOptimizer.h @@ -41,7 +41,7 @@ class GTSAM_EXPORT DoglegParams : public NonlinearOptimizerParams { VerbosityDL verbosityDL; ///< The verbosity level for Dogleg (default: SILENT), see also NonlinearOptimizerParams::verbosity DoglegParams() : - deltaInitial(10.0), verbosityDL(SILENT) {} + deltaInitial(1.0), verbosityDL(SILENT) {} virtual ~DoglegParams() {} From ae79c27d450d184c8dd510e64a4dc2a937d4c496 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Sat, 8 Jun 2019 09:52:42 +0200 Subject: [PATCH 105/160] fix unused parameter warning --- wrap/FileWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrap/FileWriter.cpp b/wrap/FileWriter.cpp index 2c3843b372..c07de0eb01 100644 --- a/wrap/FileWriter.cpp +++ b/wrap/FileWriter.cpp @@ -29,7 +29,7 @@ void FileWriter::emit(bool add_header, bool force_overwrite) const { bool file_exists = true; try { existing_contents = file_contents(filename_.c_str(), add_header); - } catch (const CantOpenFile& e) { + } catch (const CantOpenFile& ) { file_exists = false; } From 7f43054c37930261ddd88716e79dba896ef02d1a Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Wed, 29 May 2019 11:02:10 +0200 Subject: [PATCH 106/160] Refactor build flags via CMake target properties Also: - Allow users to edit cmake target build options in the cache variables. - We had to add project() commands for gtsam and gtsam_unstable, the PROJECT_SOURCE_DIR changed, but the root GTSAM_SOURCE_DIR instead. - Ensure use of standard C++11 (no extensions) --- .travis.sh | 2 +- CMakeLists.txt | 39 +++-- cmake/GtsamBuildTypes.cmake | 182 +++++++++++++++------- cmake/GtsamPrinting.cmake | 43 ++++- cmake/GtsamTesting.cmake | 15 +- cmake/obsolete/GtsamTestingObsolete.cmake | 64 ++++---- gtsam/CMakeLists.txt | 21 +-- gtsam/config.h.in | 2 +- gtsam_unstable/CMakeLists.txt | 6 + wrap/CMakeLists.txt | 3 + 10 files changed, 257 insertions(+), 120 deletions(-) diff --git a/.travis.sh b/.travis.sh index c2feb71f2f..3cec20f539 100755 --- a/.travis.sh +++ b/.travis.sh @@ -51,7 +51,7 @@ function build () -DGTSAM_ALLOW_DEPRECATED_SINCE_V4=$GTSAM_ALLOW_DEPRECATED_SINCE_V4 # Actual build: - make -j2 + VERBOSE=1 make -j2 finish } diff --git a/CMakeLists.txt b/CMakeLists.txt index 92da64c11d..202c19842e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,3 @@ - project(GTSAM CXX C) cmake_minimum_required(VERSION 3.0) @@ -134,11 +133,11 @@ if(MSVC) # If we use Boost shared libs, disable auto linking. # Some libraries, at least Boost Program Options, rely on this to export DLL symbols. if(NOT Boost_USE_STATIC_LIBS) - list(APPEND GTSAM_COMPILE_DEFINITIONS_PUBLIC BOOST_ALL_NO_LIB BOOST_ALL_DYN_LINK) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PUBLIC BOOST_ALL_NO_LIB BOOST_ALL_DYN_LINK) endif() # Virtual memory range for PCH exceeded on VS2015 if(MSVC_VERSION LESS 1910) # older than VS2017 - list(APPEND GTSAM_COMPILE_OPTIONS_PRIVATE -Zm295) + list_append_cache(GTSAM_COMPILE_OPTIONS_PRIVATE -Zm295) endif() endif() @@ -147,7 +146,7 @@ endif() # See: https://bitbucket.org/gtborg/gtsam/issues/417/fail-to-build-on-msvc-2017 # if(MSVC AND BUILD_SHARED_LIBS) - list(APPEND GTSAM_COMPILE_DEFINITIONS_PUBLIC EIGEN_NO_STATIC_ASSERT) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PUBLIC EIGEN_NO_STATIC_ASSERT) endif() # Store these in variables so they are automatically replicated in GTSAMConfig.cmake and such. @@ -188,7 +187,7 @@ set(GTSAM_BOOST_LIBRARIES message(STATUS "GTSAM_BOOST_LIBRARIES: ${GTSAM_BOOST_LIBRARIES}") if (GTSAM_DISABLE_NEW_TIMERS) message("WARNING: GTSAM timing instrumentation manually disabled") - list(APPEND GTSAM_COMPILE_DEFINITIONS_PUBLIC DGTSAM_DISABLE_NEW_TIMERS) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PUBLIC DGTSAM_DISABLE_NEW_TIMERS) else() if(Boost_TIMER_LIBRARY) list(APPEND GTSAM_BOOST_LIBRARIES @@ -208,7 +207,7 @@ endif() if(NOT (${Boost_VERSION} LESS 105600)) message("Ignoring Boost restriction on optional lvalue assignment from rvalues") - list(APPEND GTSAM_COMPILE_DEFINITIONS_PUBLIC BOOST_OPTIONAL_ALLOW_BINDING_TO_RVALUES BOOST_OPTIONAL_CONFIG_ALLOW_BINDING_TO_RVALUES) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PUBLIC BOOST_OPTIONAL_ALLOW_BINDING_TO_RVALUES BOOST_OPTIONAL_CONFIG_ALLOW_BINDING_TO_RVALUES) endif() ############################################################################### @@ -276,7 +275,7 @@ find_package(OpenMP) # do this here to generate correct message if disabled if(GTSAM_WITH_EIGEN_MKL AND GTSAM_WITH_EIGEN_MKL_OPENMP AND GTSAM_USE_EIGEN_MKL) if(OPENMP_FOUND AND GTSAM_USE_EIGEN_MKL AND GTSAM_WITH_EIGEN_MKL_OPENMP) set(GTSAM_USE_EIGEN_MKL_OPENMP 1) # This will go into config.h - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + list_append_cache(GTSAM_COMPILE_OPTIONS_PUBLIC ${OpenMP_CXX_FLAGS}) endif() endif() @@ -351,9 +350,9 @@ endif () if (MSVC) if (BUILD_SHARED_LIBS) # mute eigen static assert to avoid errors in shared lib - list(APPEND GTSAM_COMPILE_DEFINITIONS_PUBLIC DEIGEN_NO_STATIC_ASSERT) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PUBLIC EIGEN_NO_STATIC_ASSERT) endif() - list(APPEND GTSAM_COMPILE_OPTIONS_PRIVATE "/wd4244") # Disable loss of precision which is thrown all over our Eigen + list_append_cache(GTSAM_COMPILE_OPTIONS_PRIVATE "/wd4244") # Disable loss of precision which is thrown all over our Eigen endif() ############################################################################### @@ -395,28 +394,28 @@ elseif("${GTSAM_DEFAULT_ALLOCATOR}" STREQUAL "tcmalloc") endif() if(MSVC) - list(APPEND GTSAM_COMPILE_DEFINITIONS_PRIVATE _CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNINGS) - list(APPEND GTSAM_COMPILE_OPTIONS_PRIVATE /wd4251 /wd4275 /wd4251 /wd4661 /wd4344 /wd4503) # Disable non-DLL-exported base class and other warnings - list(APPEND GTSAM_COMPILE_OPTIONS_PRIVATE /bigobj) # Allow large object files for template-based code + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PRIVATE _CRT_SECURE_NO_WARNINGS _SCL_SECURE_NO_WARNINGS) + list_append_cache(GTSAM_COMPILE_OPTIONS_PRIVATE /wd4251 /wd4275 /wd4251 /wd4661 /wd4344 /wd4503) # Disable non-DLL-exported base class and other warnings + list_append_cache(GTSAM_COMPILE_OPTIONS_PRIVATE /bigobj) # Allow large object files for template-based code endif() # GCC 4.8+ complains about local typedefs which we use for shared_ptr etc. if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8) - list(APPEND GTSAM_COMPILE_OPTIONS_PRIVATE -Wno-unused-local-typedefs) + list_append_cache(GTSAM_COMPILE_OPTIONS_PRIVATE -Wno-unused-local-typedefs) endif() endif() # As of XCode 7, clang also complains about this if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7.0) - list(APPEND GTSAM_COMPILE_OPTIONS_PRIVATE -Wno-unused-local-typedefs) + list_append_cache(GTSAM_COMPILE_OPTIONS_PRIVATE -Wno-unused-local-typedefs) endif() endif() if(GTSAM_ENABLE_CONSISTENCY_CHECKS) # This should be made PUBLIC if GTSAM_EXTRA_CONSISTENCY_CHECKS is someday used in a public .h - list(APPEND GTSAM_COMPILE_DEFINITIONS_PRIVATE GTSAM_EXTRA_CONSISTENCY_CHECKS) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PRIVATE GTSAM_EXTRA_CONSISTENCY_CHECKS) endif() ############################################################################### @@ -514,6 +513,9 @@ message(STATUS "===============================================================" message(STATUS "================ Configuration Options ======================") message(STATUS " CMAKE_CXX_COMPILER_ID type : ${CMAKE_CXX_COMPILER_ID}") message(STATUS " CMAKE_CXX_COMPILER_VERSION : ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS " CMake version : ${CMAKE_VERSION}") +message(STATUS " CMake generator : ${CMAKE_GENERATOR}") +message(STATUS " CMake build tool : ${CMAKE_BUILD_TOOL}") message(STATUS "Build flags ") print_config_flag(${GTSAM_BUILD_TESTS} "Build Tests ") print_config_flag(${GTSAM_BUILD_EXAMPLES_ALWAYS} "Build examples with 'make all' ") @@ -527,13 +529,16 @@ if(GTSAM_UNSTABLE_AVAILABLE) print_config_flag(${GTSAM_BUILD_UNSTABLE} "Build libgtsam_unstable ") endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" cmake_build_type_toupper) -print_config_flag("${GTSAM_BUILD_WITH_MARCH_NATIVE}" "Build for native architecture ") if(NOT MSVC AND NOT XCODE_VERSION) + print_config_flag(${GTSAM_BUILD_WITH_MARCH_NATIVE} "Build for native architecture ") message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") - message(STATUS " C compilation flags : ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${cmake_build_type_toupper}}") message(STATUS " C++ compilation flags : ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${cmake_build_type_toupper}}") endif() + +print_build_options_for_target(gtsam) + message(STATUS " Use System Eigen : ${GTSAM_USE_SYSTEM_EIGEN} (Using version: ${GTSAM_EIGEN_VERSION})") + if(GTSAM_USE_TBB) message(STATUS " Use Intel TBB : Yes") elseif(TBB_FOUND) diff --git a/cmake/GtsamBuildTypes.cmake b/cmake/GtsamBuildTypes.cmake index 0c9a34da12..bc0d6e2330 100644 --- a/cmake/GtsamBuildTypes.cmake +++ b/cmake/GtsamBuildTypes.cmake @@ -1,7 +1,45 @@ +# function: list_append_cache(var [new_values ...]) +# Like "list(APPEND ...)" but working for CACHE variables. +# ----------------------------------------------------------- +function(list_append_cache var) + set(cur_value ${${var}}) + list(APPEND cur_value ${ARGN}) + get_property(MYVAR_DOCSTRING CACHE ${var} PROPERTY HELPSTRING) + set(${var} "${cur_value}" CACHE STRING "${MYVAR_DOCSTRING}" FORCE) +endfunction() + +# function: append_config_if_not_empty(TARGET_VARIABLE build_type) +# Auxiliary function used to merge configuration-specific flags into the +# global variables that will actually be send to cmake targets. +# ----------------------------------------------------------- +function(append_config_if_not_empty TARGET_VARIABLE_ build_type) + string(TOUPPER "${build_type}" build_type_toupper) + set(flags_variable_name "${TARGET_VARIABLE_}_${build_type_toupper}") + set(flags_ ${${flags_variable_name}}) + if (NOT "${flags_}" STREQUAL "") + if ("${build_type_toupper}" STREQUAL "COMMON") + # Special "COMMON" configuration type, just append without CMake expression: + list_append_cache(${TARGET_VARIABLE_} "${flags_}") + else() + # Regular configuration type: + list_append_cache(${TARGET_VARIABLE_} "$<$:${flags_}>") + endif() + endif() +endfunction() + + # Add install prefix to search path list(APPEND CMAKE_PREFIX_PATH "${CMAKE_INSTALL_PREFIX}") + +# Set up build types for MSVC and XCode +set(GTSAM_CMAKE_CONFIGURATION_TYPES Debug Release Timing Profiling RelWithDebInfo MinSizeRel + CACHE STRING "Build types available to MSVC and XCode") +mark_as_advanced(FORCE GTSAM_CMAKE_CONFIGURATION_TYPES) +set(CMAKE_CONFIGURATION_TYPES ${GTSAM_CMAKE_CONFIGURATION_TYPES} CACHE STRING "Build configurations" FORCE) + + # Default to Release mode if(NOT CMAKE_BUILD_TYPE AND NOT MSVC AND NOT XCODE_VERSION) set(GTSAM_CMAKE_BUILD_TYPE "Release" CACHE STRING @@ -13,39 +51,76 @@ endif() # Add option for using build type postfixes to allow installing multiple build modes option(GTSAM_BUILD_TYPE_POSTFIXES "Enable/Disable appending the build type to the name of compiled libraries" ON) -# Set custom compilation flags. -# NOTE: We set all the CACHE variables with a GTSAM prefix, and then set a normal local variable below -# so that we don't "pollute" the global variable namespace in the cmake cache. - # Set all CMAKE_BUILD_TYPE flags: - # (see https://cmake.org/Wiki/CMake_Useful_Variables#Compilers_and_Tools) +# Define all cache variables, to be populated below depending on the OS/compiler: +set(GTSAM_COMPILE_OPTIONS_PRIVATE "" CACHE STRING "(Do not edit) Private compiler flags for all build configurations." FORCE) +set(GTSAM_COMPILE_OPTIONS_PUBLIC "" CACHE STRING "(Do not edit) Public compiler flags (exported to user projects) for all build configurations." FORCE) +set(GTSAM_COMPILE_DEFINITIONS_PRIVATE "" CACHE STRING "(Do not edit) Private preprocessor macros for all build configurations." FORCE) +set(GTSAM_COMPILE_DEFINITIONS_PUBLIC "" CACHE STRING "(Do not edit) Public preprocessor macros for all build configurations." FORCE) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PRIVATE) +mark_as_advanced(GTSAM_COMPILE_OPTIONS_PUBLIC) +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PRIVATE) +mark_as_advanced(GTSAM_COMPILE_DEFINITIONS_PUBLIC) + +foreach(build_type ${GTSAM_CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${build_type}" build_type_toupper) + + # Define empty cache variables for "public". "private" are creaed below. + set(GTSAM_COMPILE_OPTIONS_PUBLIC_${build_type_toupper} "" CACHE STRING "(User editable) Public compiler flags (exported to user projects) for `${build_type_toupper}` configuration.") + set(GTSAM_COMPILE_DEFINITIONS_PUBLIC_${build_type_toupper} "" CACHE STRING "(User editable) Public preprocessor macros for `${build_type_toupper}` configuration.") +endforeach() + +# Common preprocessor macros for each configuration: +set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_DEBUG "_DEBUG;EIGEN_INITIALIZE_MATRICES_BY_NAN" CACHE STRING "(User editable) Private preprocessor macros for Debug configuration.") +set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_RELWITHDEBINFO "NDEBUG" CACHE STRING "(User editable) Private preprocessor macros for RelWithDebInfo configuration.") +set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_RELEASE "NDEBUG" CACHE STRING "(User editable) Private preprocessor macros for Release configuration.") +set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_PROFILING "NDEBUG" CACHE STRING "(User editable) Private preprocessor macros for Profiling configuration.") +set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_TIMING "NDEBUG;ENABLE_TIMING" CACHE STRING "(User editable) Private preprocessor macros for Timing configuration.") +if(MSVC) + # Common to all configurations: + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PRIVATE WINDOWS_LEAN_AND_MEAN) +endif() + +# Other (non-preprocessor macros) compiler flags: if(MSVC) - set(GTSAM_CMAKE_C_FLAGS "/W3 /GR /EHsc /MP /DWINDOWS_LEAN_AND_MEAN" CACHE STRING "Flags used by the compiler for all builds.") - set(GTSAM_CMAKE_CXX_FLAGS "/W3 /GR /EHsc /MP /DWINDOWS_LEAN_AND_MEAN" CACHE STRING "Flags used by the compiler for all builds.") - set(GTSAM_CMAKE_C_FLAGS_DEBUG "/D_DEBUG /MDd /Zi /Ob0 /Od /RTC1 /DEIGEN_INITIALIZE_MATRICES_BY_NAN" CACHE STRING "Extra flags used by the compiler during debug builds.") - set(GTSAM_CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Zi /Ob0 /Od /RTC1 /DEIGEN_INITIALIZE_MATRICES_BY_NAN" CACHE STRING "Extra flags used by the compiler during debug builds.") - set(GTSAM_CMAKE_C_FLAGS_RELWITHDEBINFO "/MD /O2 /DNDEBUG /Zi /d2Zi+" CACHE STRING "Extra flags used by the compiler during relwithdebinfo builds.") - set(GTSAM_CMAKE_CXX_FLAGS_RELWITHDEBINFO "/MD /O2 /DNDEBUG /Zi /d2Zi+" CACHE STRING "Extra flags used by the compiler during relwithdebinfo builds.") - set(GTSAM_CMAKE_C_FLAGS_RELEASE "/MD /O2 /DNDEBUG" CACHE STRING "Extra flags used by the compiler during release builds.") - set(GTSAM_CMAKE_CXX_FLAGS_RELEASE "/MD /O2 /DNDEBUG" CACHE STRING "Extra flags used by the compiler during release builds.") - set(GTSAM_CMAKE_C_FLAGS_PROFILING "${GTSAM_CMAKE_C_FLAGS_RELEASE} /Zi" CACHE STRING "Extra flags used by the compiler during profiling builds.") - set(GTSAM_CMAKE_CXX_FLAGS_PROFILING "${GTSAM_CMAKE_CXX_FLAGS_RELEASE} /Zi" CACHE STRING "Extra flags used by the compiler during profiling builds.") - set(GTSAM_CMAKE_C_FLAGS_TIMING "${GTSAM_CMAKE_C_FLAGS_RELEASE} /DENABLE_TIMING" CACHE STRING "Extra flags used by the compiler during timing builds.") - set(GTSAM_CMAKE_CXX_FLAGS_TIMING "${GTSAM_CMAKE_CXX_FLAGS_RELEASE} /DENABLE_TIMING" CACHE STRING "Extra flags used by the compiler during timing builds.") + # Common to all configurations, next for each configuration: + set(GTSAM_COMPILE_OPTIONS_PRIVATE_COMMON /W3 /GR /EHsc /MP CACHE STRING "(User editable) Private compiler flags for all configurations.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_DEBUG /MDd /Zi /Ob0 /Od /RTC1 CACHE STRING "(User editable) Private compiler flags for Debug configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_RELWITHDEBINFO /MD /O2 /D /Zi /d2Zi+ CACHE STRING "(User editable) Private compiler flags for RelWithDebInfo configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_RELEASE /MD /O2 CACHE STRING "(User editable) Private compiler flags for Release configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_PROFILING /MD /O2 /Zi CACHE STRING "(User editable) Private compiler flags for Profiling configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_TIMING /MD /O2 CACHE STRING "(User editable) Private compiler flags for Timing configuration.") +else() + # Common to all configurations, next for each configuration: + set(GTSAM_COMPILE_OPTIONS_PRIVATE_COMMON -Wall CACHE STRING "(User editable) Private compiler flags for all configurations.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_DEBUG -g -fno-inline CACHE STRING "(User editable) Private compiler flags for Debug configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_RELWITHDEBINFO -g -O3 CACHE STRING "(User editable) Private compiler flags for RelWithDebInfo configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_RELEASE -O3 CACHE STRING "(User editable) Private compiler flags for Release configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_PROFILING -O3 CACHE STRING "(User editable) Private compiler flags for Profiling configuration.") + set(GTSAM_COMPILE_OPTIONS_PRIVATE_TIMING -g -O3 CACHE STRING "(User editable) Private compiler flags for Timing configuration.") +endif() + +# Enable C++11: +if (NOT CMAKE_VERSION VERSION_LESS 3.8) + set(GTSAM_COMPILE_FEATURES_PUBLIC "cxx_std_11" CACHE STRING "CMake compile features property for all gtsam targets.") + # See: https://cmake.org/cmake/help/latest/prop_tgt/CXX_EXTENSIONS.html + # This is to enable -std=c++11 instead of -std=g++11 + set(CMAKE_CXX_EXTENSIONS OFF) else() - set(GTSAM_CMAKE_C_FLAGS "-std=c11 -Wall" CACHE STRING "Flags used by the compiler for all builds.") - set(GTSAM_CMAKE_CXX_FLAGS "-std=c++11 -Wall" CACHE STRING "Flags used by the compiler for all builds.") - set(GTSAM_CMAKE_C_FLAGS_DEBUG "-g -fno-inline -DEIGEN_INITIALIZE_MATRICES_BY_NAN" CACHE STRING "Extra flags used by the compiler during debug builds.") - set(GTSAM_CMAKE_CXX_FLAGS_DEBUG "-g -fno-inline -DEIGEN_INITIALIZE_MATRICES_BY_NAN" CACHE STRING "Extra flags used by the compiler during debug builds.") - set(GTSAM_CMAKE_C_FLAGS_RELWITHDEBINFO "-g -O3 -DNDEBUG" CACHE STRING "Extra flags used by the compiler during relwithdebinfo builds.") - set(GTSAM_CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -O3 -DNDEBUG" CACHE STRING "Extra flags used by the compiler during relwithdebinfo builds.") - set(GTSAM_CMAKE_C_FLAGS_RELEASE " -O3 -DNDEBUG" CACHE STRING "Extra flags used by the compiler during release builds.") - set(GTSAM_CMAKE_CXX_FLAGS_RELEASE " -O3 -DNDEBUG" CACHE STRING "Extra flags used by the compiler during release builds.") - set(GTSAM_CMAKE_C_FLAGS_PROFILING "${GTSAM_CMAKE_C_FLAGS_RELEASE}" CACHE STRING "Extra flags used by the compiler during profiling builds.") - set(GTSAM_CMAKE_CXX_FLAGS_PROFILING "${GTSAM_CMAKE_CXX_FLAGS_RELEASE}" CACHE STRING "Extra flags used by the compiler during profiling builds.") - set(GTSAM_CMAKE_C_FLAGS_TIMING "${GTSAM_CMAKE_C_FLAGS_RELEASE} -DENABLE_TIMING" CACHE STRING "Extra flags used by the compiler during timing builds.") - set(GTSAM_CMAKE_CXX_FLAGS_TIMING "${GTSAM_CMAKE_CXX_FLAGS_RELEASE} -DENABLE_TIMING" CACHE STRING "Extra flags used by the compiler during timing builds.") + # Old cmake versions: + if (NOT MSVC) + list_append_cache(GTSAM_COMPILE_OPTIONS_PUBLIC $<$:-std=c++11>) + endif() endif() +# Merge all user-defined flags into the variables that are to be actually used by CMake: +foreach(build_type "common" ${GTSAM_CMAKE_CONFIGURATION_TYPES}) + append_config_if_not_empty(GTSAM_COMPILE_OPTIONS_PRIVATE ${build_type}) + append_config_if_not_empty(GTSAM_COMPILE_OPTIONS_PUBLIC ${build_type}) + append_config_if_not_empty(GTSAM_COMPILE_DEFINITIONS_PRIVATE ${build_type}) + append_config_if_not_empty(GTSAM_COMPILE_DEFINITIONS_PUBLIC ${build_type}) +endforeach() + +# Linker flags: set(GTSAM_CMAKE_SHARED_LINKER_FLAGS_TIMING "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}" CACHE STRING "Linker flags during timing builds.") set(GTSAM_CMAKE_MODULE_LINKER_FLAGS_TIMING "${CMAKE_MODULE_LINKER_FLAGS_RELEASE}" CACHE STRING "Linker flags during timing builds.") set(GTSAM_CMAKE_EXE_LINKER_FLAGS_TIMING "${CMAKE_EXE_LINKER_FLAGS_RELEASE}" CACHE STRING "Linker flags during timing builds.") @@ -54,26 +129,11 @@ set(GTSAM_CMAKE_SHARED_LINKER_FLAGS_PROFILING "${CMAKE_SHARED_LINKER_FLAGS_RELEA set(GTSAM_CMAKE_MODULE_LINKER_FLAGS_PROFILING "${CMAKE_MODULE_LINKER_FLAGS_RELEASE}" CACHE STRING "Linker flags during profiling builds.") set(GTSAM_CMAKE_EXE_LINKER_FLAGS_PROFILING "${CMAKE_EXE_LINKER_FLAGS_RELEASE}" CACHE STRING "Linker flags during profiling builds.") -mark_as_advanced(GTSAM_CMAKE_C_FLAGS_TIMING GTSAM_CMAKE_CXX_FLAGS_TIMING GTSAM_CMAKE_EXE_LINKER_FLAGS_TIMING +mark_as_advanced(GTSAM_CMAKE_EXE_LINKER_FLAGS_TIMING GTSAM_CMAKE_SHARED_LINKER_FLAGS_TIMING GTSAM_CMAKE_MODULE_LINKER_FLAGS_TIMING - GTSAM_CMAKE_C_FLAGS_PROFILING GTSAM_CMAKE_CXX_FLAGS_PROFILING GTSAM_CMAKE_EXE_LINKER_FLAGS_PROFILING + GTSAM_CMAKE_C_FLAGS_PROFILING GTSAM_ GTSAM_CMAKE_EXE_LINKER_FLAGS_PROFILING GTSAM_CMAKE_SHARED_LINKER_FLAGS_PROFILING GTSAM_CMAKE_MODULE_LINKER_FLAGS_PROFILING) -# Apply the gtsam specific build flags as normal variables. This makes it so that they only -# apply to the gtsam part of the build if gtsam is built as a subproject -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GTSAM_CMAKE_C_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GTSAM_CMAKE_CXX_FLAGS}") -set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${GTSAM_CMAKE_C_FLAGS_DEBUG}") -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${GTSAM_CMAKE_CXX_FLAGS_DEBUG}") -set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${GTSAM_CMAKE_C_FLAGS_RELWITHDEBINFO}") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${GTSAM_CMAKE_CXX_FLAGS_RELWITHDEBINFO}") -set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${GTSAM_CMAKE_C_FLAGS_RELEASE}") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${GTSAM_CMAKE_CXX_FLAGS_RELEASE}") -set(CMAKE_C_FLAGS_PROFILING "${CMAKE_C_FLAGS_PROFILING} ${GTSAM_CMAKE_C_FLAGS_PROFILING}") -set(CMAKE_CXX_FLAGS_PROFILING "${CMAKE_CXX_FLAGS_PROFILING} ${GTSAM_CMAKE_CXX_FLAGS_PROFILING}") -set(CMAKE_C_FLAGS_TIMING "${CMAKE_C_FLAGS_TIMING} ${GTSAM_CMAKE_C_FLAGS_TIMING}") -set(CMAKE_CXX_FLAGS_TIMING "${CMAKE_CXX_FLAGS_TIMING} ${GTSAM_CMAKE_CXX_FLAGS_TIMING}") - set(CMAKE_SHARED_LINKER_FLAGS_TIMING ${GTSAM_CMAKE_SHARED_LINKER_FLAGS_TIMING}) set(CMAKE_MODULE_LINKER_FLAGS_TIMING ${GTSAM_CMAKE_MODULE_LINKER_FLAGS_TIMING}) set(CMAKE_EXE_LINKER_FLAGS_TIMING ${GTSAM_CMAKE_EXE_LINKER_FLAGS_TIMING}) @@ -86,15 +146,16 @@ set(CMAKE_EXE_LINKER_FLAGS_PROFILING ${GTSAM_CMAKE_EXE_LINKER_FLAGS_PROFILING}) if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang") # Apple Clang before 5.0 does not support -ftemplate-depth. if(NOT (APPLE AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS "5.0")) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth=1024") + list_append_cache(GTSAM_COMPILE_OPTIONS_PUBLIC "-ftemplate-depth=1024") endif() endif() if (NOT MSVC) option(GTSAM_BUILD_WITH_MARCH_NATIVE "Enable/Disable building with all instructions supported by native architecture (binary may not be portable!)" ON) if(GTSAM_BUILD_WITH_MARCH_NATIVE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=native") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") + # Add as public flag so all dependant projects also use it, as required + # by Eigen to avid crashes due to SIMD vectorization: + list_append_cache(GTSAM_COMPILE_OPTIONS_PUBLIC "-march=native") endif() endif() @@ -120,12 +181,6 @@ if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") endif() endif() -# Set up build types for MSVC and XCode -set(GTSAM_CMAKE_CONFIGURATION_TYPES Debug Release Timing Profiling RelWithDebInfo MinSizeRel - CACHE STRING "Build types available to MSVC and XCode") -mark_as_advanced(FORCE GTSAM_CMAKE_CONFIGURATION_TYPES) -set(CMAKE_CONFIGURATION_TYPES ${GTSAM_CMAKE_CONFIGURATION_TYPES}) - # Check build types string(TOLOWER "${CMAKE_BUILD_TYPE}" cmake_build_type_tolower) if( NOT cmake_build_type_tolower STREQUAL "" @@ -161,3 +216,20 @@ function(gtsam_assign_all_source_folders) gtsam_assign_source_folders("${all_c_srcs};${all_cpp_srcs};${all_headers}") endfunction() +# Applies the per-config build flags to the given target (e.g. gtsam, wrap_lib) +function(gtsam_apply_build_flags target_name_) + # To enable C++11: the use of target_compile_features() is preferred since + # it will be not in conflict with a more modern C++ standard, if used in a + # client program. + if (NOT "${GTSAM_COMPILE_FEATURES_PUBLIC}" STREQUAL "") + target_compile_features(${target_name_} PUBLIC ${GTSAM_COMPILE_FEATURES_PUBLIC}) + endif() + + target_compile_definitions(${target_name_} PRIVATE ${GTSAM_COMPILE_DEFINITIONS_PRIVATE}) + target_compile_definitions(${target_name_} PUBLIC ${GTSAM_COMPILE_DEFINITIONS_PUBLIC}) + if (NOT "${GTSAM_COMPILE_OPTIONS_PUBLIC}" STREQUAL "") + target_compile_options(${target_name_} PUBLIC ${GTSAM_COMPILE_OPTIONS_PUBLIC}) + endif() + target_compile_options(${target_name_} PRIVATE ${GTSAM_COMPILE_OPTIONS_PRIVATE}) + +endfunction(gtsam_apply_build_flags) diff --git a/cmake/GtsamPrinting.cmake b/cmake/GtsamPrinting.cmake index 674fd40865..e53f9c54fe 100644 --- a/cmake/GtsamPrinting.cmake +++ b/cmake/GtsamPrinting.cmake @@ -7,4 +7,45 @@ function(print_config_flag flag msg) else () message(STATUS " ${msg}: Disabled") endif () -endfunction(print_config_flag) +endfunction() + +# Based on https://github.com/jimbraun/XCDF/blob/master/cmake/CMakePadString.cmake +function(string_pad RESULT_NAME DESIRED_LENGTH VALUE) + string(LENGTH "${VALUE}" VALUE_LENGTH) + math(EXPR REQUIRED_PADS "${DESIRED_LENGTH} - ${VALUE_LENGTH}") + set(PAD ${VALUE}) + if(REQUIRED_PADS GREATER 0) + math(EXPR REQUIRED_MINUS_ONE "${REQUIRED_PADS} - 1") + foreach(FOO RANGE ${REQUIRED_MINUS_ONE}) + set(PAD "${PAD} ") + endforeach() + endif() + set(${RESULT_NAME} "${PAD}" PARENT_SCOPE) +endfunction() + +set(GTSAM_PRINT_SUMMARY_PADDING_LENGTH 50 CACHE STRING "Padding of cmake summary report lines after configuring.") +mark_as_advanced(GTSAM_PRINT_SUMMARY_PADDING_LENGTH) + +# Print " var: ${var}" padding with spaces as needed +function(print_padded variable_name) + string_pad(padded_prop ${GTSAM_PRINT_SUMMARY_PADDING_LENGTH} " ${variable_name}") + message(STATUS "${padded_prop}: ${${variable_name}}") +endfunction() + + +# Prints all the relevant CMake build options for a given target: +function(print_build_options_for_target target_name_) + print_padded(GTSAM_COMPILE_FEATURES_PUBLIC) + print_padded(GTSAM_COMPILE_OPTIONS_PRIVATE) + print_padded(GTSAM_COMPILE_OPTIONS_PUBLIC) + print_padded(GTSAM_COMPILE_DEFINITIONS_PRIVATE) + print_padded(GTSAM_COMPILE_DEFINITIONS_PUBLIC) + + foreach(build_type ${GTSAM_CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER "${build_type}" build_type_toupper) + print_padded(GTSAM_COMPILE_OPTIONS_PRIVATE_${build_type_toupper}) + print_padded(GTSAM_COMPILE_OPTIONS_PUBLIC_${build_type_toupper}) + print_padded(GTSAM_COMPILE_DEFINITIONS_PRIVATE_${build_type_toupper}) + print_padded(GTSAM_COMPILE_DEFINITIONS_PUBLIC_${build_type_toupper}) + endforeach() +endfunction() diff --git a/cmake/GtsamTesting.cmake b/cmake/GtsamTesting.cmake index d115f395ad..3b42ffa215 100644 --- a/cmake/GtsamTesting.cmake +++ b/cmake/GtsamTesting.cmake @@ -169,6 +169,9 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) add_executable(${script_name} ${script_src} ${script_headers}) target_link_libraries(${script_name} CppUnitLite ${linkLibraries}) + # Apply user build flags from CMake cache variables: + gtsam_apply_build_flags(${script_name}) + # Add target dependencies add_test(NAME ${script_name} COMMAND ${script_name}) add_dependencies(check.${groupName} ${script_name}) @@ -191,7 +194,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) endif() # Add TOPSRCDIR - set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") + set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${GTSAM_SOURCE_DIR}\"") # Exclude from 'make all' and 'make install' set_target_properties(${script_name} PROPERTIES EXCLUDE_FROM_ALL ON) @@ -213,6 +216,9 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) add_executable(${target_name} "${script_srcs}" ${script_headers}) target_link_libraries(${target_name} CppUnitLite ${linkLibraries}) + # Apply user build flags from CMake cache variables: + gtsam_apply_build_flags(${target_name}) + set_property(TARGET check_${groupName}_program PROPERTY FOLDER "Unit tests") # Only have a main function in one script - use preprocessor @@ -229,7 +235,7 @@ macro(gtsamAddTestsGlob_impl groupName globPatterns excludedFiles linkLibraries) endif() # Add TOPSRCDIR - set_property(SOURCE ${script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") + set_property(SOURCE ${script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${GTSAM_SOURCE_DIR}\"") # Exclude from 'make all' and 'make install' set_target_properties(${target_name} PROPERTIES EXCLUDE_FROM_ALL ON) @@ -280,6 +286,9 @@ macro(gtsamAddExesGlob_impl globPatterns excludedFiles linkLibraries groupName b add_executable(${script_name} ${script_src} ${script_headers}) target_link_libraries(${script_name} ${linkLibraries}) + # Apply user build flags from CMake cache variables: + gtsam_apply_build_flags(${script_name}) + # Add target dependencies add_dependencies(${groupName} ${script_name}) if(NOT MSVC AND NOT XCODE_VERSION) @@ -287,7 +296,7 @@ macro(gtsamAddExesGlob_impl globPatterns excludedFiles linkLibraries groupName b endif() # Add TOPSRCDIR - set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") + set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${GTSAM_SOURCE_DIR}\"") # Exclude from all or not - note weird variable assignment because we're in a macro set(buildWithAll_on ${buildWithAll}) diff --git a/cmake/obsolete/GtsamTestingObsolete.cmake b/cmake/obsolete/GtsamTestingObsolete.cmake index f56d138e67..c90abfa6c4 100644 --- a/cmake/obsolete/GtsamTestingObsolete.cmake +++ b/cmake/obsolete/GtsamTestingObsolete.cmake @@ -1,22 +1,22 @@ -# Macro for adding categorized tests in a "tests" folder, with +# Macro for adding categorized tests in a "tests" folder, with # optional exclusion of tests and convenience library linking options -# +# # By default, all tests are linked with CppUnitLite and boost -# Arguments: +# Arguments: # - subdir The name of the category for this test # - local_libs A list of convenience libraries to use (if GTSAM_BUILD_CONVENIENCE_LIBRARIES is true) # - full_libs The main library to link against if not using convenience libraries -# - excluded_tests A list of test files that should not be compiled - use for debugging +# - excluded_tests A list of test files that should not be compiled - use for debugging function(gtsam_add_subdir_tests subdir local_libs full_libs excluded_tests) # Subdirectory target for tests add_custom_target(check.${subdir} COMMAND ${CMAKE_CTEST_COMMAND} -C $ --output-on-failure) set(is_test TRUE) # Put check target in Visual Studio solution folder - file(RELATIVE_PATH relative_path "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + file(RELATIVE_PATH relative_path "${GTSAM_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") set_property(TARGET check.${subdir} PROPERTY FOLDER "${relative_path}") - + # Link with CppUnitLite - pulled from gtsam installation list(APPEND local_libs CppUnitLite) list(APPEND full_libs CppUnitLite) @@ -29,15 +29,15 @@ function(gtsam_add_subdir_tests subdir local_libs full_libs excluded_tests) ${is_test}) # Set all as tests endfunction() -# Macro for adding categorized timing scripts in a "tests" folder, with +# Macro for adding categorized timing scripts in a "tests" folder, with # optional exclusion of tests and convenience library linking options -# +# # By default, all tests are linked with boost -# Arguments: +# Arguments: # - subdir The name of the category for this timing script # - local_libs A list of convenience libraries to use (if GTSAM_BUILD_CONVENIENCE_LIBRARIES is true) # - full_libs The main library to link against if not using convenience libraries -# - excluded_srcs A list of timing files that should not be compiled - use for debugging +# - excluded_srcs A list of timing files that should not be compiled - use for debugging macro(gtsam_add_subdir_timing subdir local_libs full_libs excluded_srcs) # Subdirectory target for timing - does not actually execute the scripts add_custom_target(timing.${subdir}) @@ -60,11 +60,11 @@ endmacro() # - excluded_srcs A list of timing files that should not be compiled - use for debugging function(gtsam_add_executables pattern local_libs full_libs excluded_srcs) set(is_test FALSE) - + if(NOT excluded_srcs) set(excluded_srcs "") endif() - + # Build executables gtsam_add_grouped_scripts("" "${pattern}" "" "Executable" "${local_libs}" "${full_libs}" "${excluded_srcs}" ${is_test}) endfunction() @@ -73,7 +73,7 @@ endfunction() macro(gtsam_add_grouped_scripts group pattern target_prefix pretty_prefix_name local_libs full_libs excluded_srcs is_test) # Print warning about using this obsolete function message(AUTHOR_WARNING "Warning: Please see GtsamTesting.cmake - obsolete cmake cmake macro for creating unit tests, examples, and scripts was called. This will be removed in the future. The new macros are much easier anyway!!") - + # Get all script files set(script_files "") foreach(one_pattern ${pattern}) @@ -102,20 +102,20 @@ macro(gtsam_add_grouped_scripts group pattern target_prefix pretty_prefix_name l list(APPEND script_srcs ${script_file}) endif() endforeach() - - + + # Add targets and dependencies for each script if(NOT "${group}" STREQUAL "") message(STATUS "Adding ${pretty_prefix_name}s in ${group}") endif() - + # Create exe's for each script, unless we're in SINGLE_TEST_EXE mode if(NOT is_test OR NOT GTSAM_SINGLE_TEST_EXE) foreach(script_src ${script_srcs}) get_filename_component(script_base ${script_src} NAME_WE) if (script_base) # Check for null filenames and headers set( script_bin ${script_base} ) - message(STATUS "Adding ${pretty_prefix_name} ${script_bin}") + message(STATUS "Adding ${pretty_prefix_name} ${script_bin}") add_executable(${script_bin} ${script_src} ${script_headers}) if(NOT "${target_prefix}" STREQUAL "") if(NOT "${group}" STREQUAL "") @@ -123,37 +123,37 @@ macro(gtsam_add_grouped_scripts group pattern target_prefix pretty_prefix_name l endif() add_dependencies(${target_prefix} ${script_bin}) endif() - + # Add TOPSRCDIR - set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") + set_property(SOURCE ${script_src} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${GTSAM_SOURCE_DIR}\"") # Disable building during make all/install if (GTSAM_DISABLE_TESTS_ON_INSTALL) set_target_properties(${script_bin} PROPERTIES EXCLUDE_FROM_ALL ON) endif() - + if (is_test) add_test(NAME ${script_base} COMMAND ${script_bin}) endif() - + # Linking and dependendencies if (GTSAM_BUILD_CONVENIENCE_LIBRARIES) target_link_libraries(${script_bin} ${local_libs} ${GTSAM_BOOST_LIBRARIES}) else() target_link_libraries(${script_bin} ${full_libs} ${GTSAM_BOOST_LIBRARIES}) endif() - + # Add .run target if(NOT MSVC AND NOT XCODE_VERSION) add_custom_target(${script_bin}.run ${EXECUTABLE_OUTPUT_PATH}${script_bin} ${ARGN}) endif() - + # Set up Visual Studio folders - file(RELATIVE_PATH relative_path "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + file(RELATIVE_PATH relative_path "${GTSAM_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") set_property(TARGET ${script_bin} PROPERTY FOLDER "${relative_path}") endif() endforeach(script_src) - + if(MSVC) source_group("" FILES ${script_srcs} ${script_headers}) endif() @@ -166,28 +166,28 @@ macro(gtsam_add_grouped_scripts group pattern target_prefix pretty_prefix_name l else() target_link_libraries(${script_bin} ${Boost_LIBRARIES} ${full_libs}) endif() - + # Only have a main function in one script set(rest_script_srcs ${script_srcs}) list(REMOVE_AT rest_script_srcs 0) set_property(SOURCE ${rest_script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "main=static no_main") - + # Add TOPSRCDIR - set_property(SOURCE ${script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${PROJECT_SOURCE_DIR}\"") - + set_property(SOURCE ${script_srcs} APPEND PROPERTY COMPILE_DEFINITIONS "TOPSRCDIR=\"${GTSAM_SOURCE_DIR}\"") + # Add test add_dependencies(${target_prefix}.${group} ${script_bin}) add_dependencies(${target_prefix} ${script_bin}) add_test(NAME ${target_prefix}.${group} COMMAND ${script_bin}) - + # Disable building during make all/install if (GTSAM_DISABLE_TESTS_ON_INSTALL) set_target_properties(${script_bin} PROPERTIES EXCLUDE_FROM_ALL ON) endif() - + # Set up Visual Studio folders if(MSVC) - file(RELATIVE_PATH relative_path "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") + file(RELATIVE_PATH relative_path "${GTSAM_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}") set_property(TARGET ${script_bin} PROPERTY FOLDER "${relative_path}") source_group("" FILES ${script_srcs} ${script_headers}) endif() diff --git a/gtsam/CMakeLists.txt b/gtsam/CMakeLists.txt index 60915eeadf..b4a33943e5 100644 --- a/gtsam/CMakeLists.txt +++ b/gtsam/CMakeLists.txt @@ -1,3 +1,5 @@ +project(gtsam LANGUAGES CXX) + # We split the library in to separate subfolders, each containing # tests, timing, and an optional convenience library. # The following variable is the master list of subdirs to add @@ -82,9 +84,9 @@ ENDIF(MSVC) # Generate and install config and dllexport files configure_file(config.h.in config.h) set(library_name GTSAM) # For substitution in dllexport.h.in -configure_file("${PROJECT_SOURCE_DIR}/cmake/dllexport.h.in" "dllexport.h") -list(APPEND gtsam_srcs "${PROJECT_BINARY_DIR}/gtsam/config.h" "${PROJECT_BINARY_DIR}/gtsam/dllexport.h") -install(FILES "${PROJECT_BINARY_DIR}/gtsam/config.h" "${PROJECT_BINARY_DIR}/gtsam/dllexport.h" DESTINATION include/gtsam) +configure_file("${GTSAM_SOURCE_DIR}/cmake/dllexport.h.in" "dllexport.h") +list(APPEND gtsam_srcs "${PROJECT_BINARY_DIR}/config.h" "${PROJECT_BINARY_DIR}/dllexport.h") +install(FILES "${PROJECT_BINARY_DIR}/config.h" "${PROJECT_BINARY_DIR}/dllexport.h" DESTINATION include/gtsam) if(GTSAM_SUPPORT_NESTED_DISSECTION) list(APPEND GTSAM_ADDITIONAL_LIBRARIES metis) @@ -101,17 +103,16 @@ message(STATUS "Building GTSAM - shared: ${BUILD_SHARED_LIBS}") # BUILD_SHARED_LIBS automatically defines static/shared libs: add_library(gtsam ${gtsam_srcs}) + # Boost: target_link_libraries(gtsam PUBLIC ${GTSAM_BOOST_LIBRARIES}) target_include_directories(gtsam PUBLIC ${Boost_INCLUDE_DIR}) # Other deps: target_link_libraries(gtsam PUBLIC ${GTSAM_ADDITIONAL_LIBRARIES}) -target_compile_definitions(gtsam PRIVATE ${GTSAM_COMPILE_DEFINITIONS_PRIVATE}) -target_compile_definitions(gtsam PUBLIC ${GTSAM_COMPILE_DEFINITIONS_PUBLIC}) -if (NOT "${GTSAM_COMPILE_OPTIONS_PUBLIC}" STREQUAL "") - target_compile_options(gtsam PUBLIC ${GTSAM_COMPILE_OPTIONS_PUBLIC}) -endif() -target_compile_options(gtsam PRIVATE ${GTSAM_COMPILE_OPTIONS_PRIVATE}) + +# Apply build flags: +gtsam_apply_build_flags(gtsam) + set_target_properties(gtsam PROPERTIES OUTPUT_NAME gtsam CLEAN_DIRECT_OUTPUT 1 @@ -171,7 +172,7 @@ if(WIN32) # Add 'lib' prefix to static library to avoid filename collision with set_target_properties(gtsam PROPERTIES PREFIX "" DEFINE_SYMBOL GTSAM_EXPORTS - RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") + RUNTIME_OUTPUT_DIRECTORY "${GTSAM_BINARY_DIR}/bin") endif() endif() diff --git a/gtsam/config.h.in b/gtsam/config.h.in index 92380f8eb5..4b8bd180d3 100644 --- a/gtsam/config.h.in +++ b/gtsam/config.h.in @@ -25,7 +25,7 @@ #define GTSAM_VERSION_STRING "@GTSAM_VERSION_STRING@" // Paths to example datasets distributed with GTSAM -#define GTSAM_SOURCE_TREE_DATASET_DIR "@PROJECT_SOURCE_DIR@/examples/Data" +#define GTSAM_SOURCE_TREE_DATASET_DIR "@GTSAM_SOURCE_DIR@/examples/Data" #define GTSAM_INSTALLED_DATASET_DIR "@GTSAM_TOOLBOX_INSTALL_PATH@/gtsam_examples/Data" // Whether GTSAM is compiled to use quaternions for Rot3 (otherwise uses rotation matrices) diff --git a/gtsam_unstable/CMakeLists.txt b/gtsam_unstable/CMakeLists.txt index 3273262019..65ba4848d9 100644 --- a/gtsam_unstable/CMakeLists.txt +++ b/gtsam_unstable/CMakeLists.txt @@ -1,3 +1,5 @@ +project(gtsam_unstable LANGUAGES CXX) + # Build full gtsam_unstable library as a single library # and also build tests set (gtsam_unstable_subdirs @@ -65,6 +67,10 @@ message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") # BUILD_SHARED_LIBS automatically defines static/shared libs: add_library(gtsam_unstable ${gtsam_unstable_srcs}) + +# Apply build flags: +gtsam_apply_build_flags(gtsam_unstable) + set_target_properties(gtsam_unstable PROPERTIES OUTPUT_NAME gtsam_unstable CLEAN_DIRECT_OUTPUT 1 diff --git a/wrap/CMakeLists.txt b/wrap/CMakeLists.txt index 3daa87c7c1..6e046e3ab7 100644 --- a/wrap/CMakeLists.txt +++ b/wrap/CMakeLists.txt @@ -26,6 +26,9 @@ if (NOT GTSAM_WRAP_SERIALIZATION) target_compile_definitions(wrap_lib PUBLIC -DWRAP_DISABLE_SERIALIZE) endif() +# Apply build flags: +gtsam_apply_build_flags(wrap_lib) + target_link_libraries(wrap_lib ${WRAP_BOOST_LIBRARIES}) target_include_directories(wrap_lib PUBLIC ${Boost_INCLUDE_DIR}) From 0a6fecd30bc0bdb4d98b690aa004534aabc11a7e Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Sun, 16 Jun 2019 02:30:28 +0200 Subject: [PATCH 107/160] fix cmake warning --- cmake/GtsamBuildTypes.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/GtsamBuildTypes.cmake b/cmake/GtsamBuildTypes.cmake index bc0d6e2330..56dd7dc088 100644 --- a/cmake/GtsamBuildTypes.cmake +++ b/cmake/GtsamBuildTypes.cmake @@ -18,7 +18,7 @@ function(append_config_if_not_empty TARGET_VARIABLE_ build_type) set(flags_variable_name "${TARGET_VARIABLE_}_${build_type_toupper}") set(flags_ ${${flags_variable_name}}) if (NOT "${flags_}" STREQUAL "") - if ("${build_type_toupper}" STREQUAL "COMMON") + if (${build_type_toupper} STREQUAL "COMMON") # Special "COMMON" configuration type, just append without CMake expression: list_append_cache(${TARGET_VARIABLE_} "${flags_}") else() From 45244d3ffdfc01537fba88df8f03501088fafc53 Mon Sep 17 00:00:00 2001 From: mxie32 Date: Sun, 16 Jun 2019 10:31:35 -0400 Subject: [PATCH 108/160] move cout string to print function --- gtsam/linear/VectorValues.cpp | 3 +-- gtsam/linear/tests/testVectorValues.cpp | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index 976f65971c..1d2ce38472 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -130,8 +130,6 @@ namespace gtsam { /* ************************************************************************* */ ostream& operator<<(ostream& os, const VectorValues& v) { - os << "VectorValues" - << ": " << v.size() << " elements\n"; // Change print depending on whether we are using TBB #ifdef GTSAM_USE_TBB map sorted; @@ -152,6 +150,7 @@ namespace gtsam { /* ************************************************************************* */ void VectorValues::print(const string& str, const KeyFormatter& formatter) const { + cout << str << ": " << size() << " elements\n"; cout << *this; cout.flush(); } diff --git a/gtsam/linear/tests/testVectorValues.cpp b/gtsam/linear/tests/testVectorValues.cpp index 2f7e0a35c7..af6be8aa98 100644 --- a/gtsam/linear/tests/testVectorValues.cpp +++ b/gtsam/linear/tests/testVectorValues.cpp @@ -239,9 +239,10 @@ TEST(VectorValues, print) vv.insert(2, Vector2(4, 5)); vv.insert(5, Vector2(6, 7)); vv.insert(7, Vector2(8, 9)); + vv.print(); string expected = - "VectorValues: 5 elements\n 0: 1\n 1: 2 3\n 2: 4 5\n 5: 6 7\n 7: 8 9\n"; + " 0: 1\n 1: 2 3\n 2: 4 5\n 5: 6 7\n 7: 8 9\n"; stringstream actual; actual << vv; EXPECT(expected == actual.str()); From addbfe82344bd8f97375b9aa587504badcab6b55 Mon Sep 17 00:00:00 2001 From: mxie32 Date: Sun, 16 Jun 2019 11:53:41 -0400 Subject: [PATCH 109/160] use key_formatter in print function --- gtsam/linear/VectorValues.cpp | 4 ++-- gtsam/linear/tests/testVectorValues.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gtsam/linear/VectorValues.cpp b/gtsam/linear/VectorValues.cpp index 1d2ce38472..cd3ae815b7 100644 --- a/gtsam/linear/VectorValues.cpp +++ b/gtsam/linear/VectorValues.cpp @@ -141,7 +141,7 @@ namespace gtsam { for (const auto& key_value : v) #endif { - os << " " << key_value.first << ": " << key_value.second.transpose() + os << " " << StreamedKey(key_value.first) << ": " << key_value.second.transpose() << "\n"; } return os; @@ -151,7 +151,7 @@ namespace gtsam { void VectorValues::print(const string& str, const KeyFormatter& formatter) const { cout << str << ": " << size() << " elements\n"; - cout << *this; + cout << key_formatter(formatter) << *this; cout.flush(); } diff --git a/gtsam/linear/tests/testVectorValues.cpp b/gtsam/linear/tests/testVectorValues.cpp index af6be8aa98..f97f96aafd 100644 --- a/gtsam/linear/tests/testVectorValues.cpp +++ b/gtsam/linear/tests/testVectorValues.cpp @@ -17,6 +17,7 @@ #include #include +#include #include @@ -239,7 +240,6 @@ TEST(VectorValues, print) vv.insert(2, Vector2(4, 5)); vv.insert(5, Vector2(6, 7)); vv.insert(7, Vector2(8, 9)); - vv.print(); string expected = " 0: 1\n 1: 2 3\n 2: 4 5\n 5: 6 7\n 7: 8 9\n"; From bc16290e9613690e92360499f72ef77659a241ed Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 12 Jun 2019 15:01:45 -0400 Subject: [PATCH 110/160] Main CMakeLists.txt edit - Added top-level cmake build type upper case variable. - Added new GTSAM_BUILD_TAG variable for use in wrapping gtsam_eigency. - Removed FATAL message regarding GTSAM_BUILD_TYPE_POSTFIXES. --- CMakeLists.txt | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 202c19842e..5b33b5d57c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,13 +75,24 @@ option(GTSAM_WITH_EIGEN_MKL "Eigen will use Intel MKL if available" option(GTSAM_WITH_EIGEN_MKL_OPENMP "Eigen, when using Intel MKL, will also use OpenMP for multithreading if available" OFF) option(GTSAM_THROW_CHEIRALITY_EXCEPTION "Throw exception when a triangulated point is behind a camera" ON) option(GTSAM_ALLOW_DEPRECATED_SINCE_V4 "Allow use of methods/functions deprecated in GTSAM 4" ON) -option(GTSAM_TYPEDEF_POINTS_TO_VECTORS "Typdef Point2 and Point3 to Eigen::Vector equivalents" OFF) +option(GTSAM_TYPEDEF_POINTS_TO_VECTORS "Typedef Point2 and Point3 to Eigen::Vector equivalents" OFF) option(GTSAM_SUPPORT_NESTED_DISSECTION "Support Metis-based nested dissection" ON) option(GTSAM_TANGENT_PREINTEGRATION "Use new ImuFactor with integration on tangent space" ON) if(NOT MSVC AND NOT XCODE_VERSION) option(GTSAM_BUILD_WITH_CCACHE "Use ccache compiler cache" ON) endif() +# Set the build type to upper case for downstream use +string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) + +# Set the GTSAM_BUILD_TAG variable. +# If build type is Release, set to blank (""), else set to the build type. +if(${CMAKE_BUILD_TYPE_UPPER} STREQUAL "RELEASE") + set(GTSAM_BUILD_TAG "") # Don't create release mode tag on installed directory +else() + set(GTSAM_BUILD_TAG "${CMAKE_BUILD_TYPE}") +endif() + # Options relating to MATLAB wrapper # TODO: Check for matlab mex binary before handling building of binaries option(GTSAM_INSTALL_MATLAB_TOOLBOX "Enable/Disable installation of matlab toolbox" OFF) @@ -94,10 +105,7 @@ if((GTSAM_INSTALL_MATLAB_TOOLBOX OR GTSAM_INSTALL_CYTHON_TOOLBOX) AND NOT GTSAM_ message(FATAL_ERROR "GTSAM_INSTALL_MATLAB_TOOLBOX or GTSAM_INSTALL_CYTHON_TOOLBOX is enabled, please also enable GTSAM_BUILD_WRAP") endif() if((GTSAM_INSTALL_MATLAB_TOOLBOX OR GTSAM_INSTALL_CYTHON_TOOLBOX) AND GTSAM_BUILD_TYPE_POSTFIXES) - set(CURRENT_POSTFIX ${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}) - if(NOT "${CURRENT_POSTFIX}" STREQUAL "") - message(FATAL_ERROR "Cannot use executable postfixes with the matlab or cython wrappers. Please disable GTSAM_BUILD_TYPE_POSTFIXES") - endif() + set(CURRENT_POSTFIX ${CMAKE_${CMAKE_BUILD_TYPE_UPPER}_POSTFIX}) endif() if(GTSAM_INSTALL_WRAP AND NOT GTSAM_BUILD_WRAP) message(FATAL_ERROR "GTSAM_INSTALL_WRAP is enabled, please also enable GTSAM_BUILD_WRAP") @@ -528,11 +536,13 @@ print_config_flag(${GTSAM_BUILD_TYPE_POSTFIXES} "Put build type in librar if(GTSAM_UNSTABLE_AVAILABLE) print_config_flag(${GTSAM_BUILD_UNSTABLE} "Build libgtsam_unstable ") endif() -string(TOUPPER "${CMAKE_BUILD_TYPE}" cmake_build_type_toupper) + +print_config_flag(${GTSAM_BUILD_WITH_MARCH_NATIVE} "Build for native architecture ") if(NOT MSVC AND NOT XCODE_VERSION) print_config_flag(${GTSAM_BUILD_WITH_MARCH_NATIVE} "Build for native architecture ") message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") - message(STATUS " C++ compilation flags : ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${cmake_build_type_toupper}}") + message(STATUS " C compilation flags : ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}") + message(STATUS " C++ compilation flags : ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}") endif() print_build_options_for_target(gtsam) From 4f5d13f328b18f6ed82d8ca1bbfc91e38edff6ed Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 12 Jun 2019 15:06:13 -0400 Subject: [PATCH 111/160] GtsamCythonWrap.cmake update - Update install directory so that the release tag is appended to the cython directory only rather than the specific subdirectories. - Update the target properties so that the .so files don't have the build type appended as a postfix. --- cmake/GtsamCythonWrap.cmake | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/cmake/GtsamCythonWrap.cmake b/cmake/GtsamCythonWrap.cmake index 6366c15083..c3cad9d83c 100644 --- a/cmake/GtsamCythonWrap.cmake +++ b/cmake/GtsamCythonWrap.cmake @@ -86,8 +86,13 @@ function(build_cythonized_cpp target cpp_file output_lib_we output_dir) if(APPLE) set(link_flags "-undefined dynamic_lookup") endif() - set_target_properties(${target} PROPERTIES COMPILE_FLAGS "-w" LINK_FLAGS "${link_flags}" - OUTPUT_NAME ${output_lib_we} PREFIX "" LIBRARY_OUTPUT_DIRECTORY ${output_dir}) + set_target_properties(${target} + PROPERTIES COMPILE_FLAGS "-w" + LINK_FLAGS "${link_flags}" + OUTPUT_NAME ${output_lib_we} + PREFIX "" + ${CMAKE_BUILD_TYPE_UPPER}_POSTFIX "" + LIBRARY_OUTPUT_DIRECTORY ${output_dir}) endfunction() # Cythonize a pyx from the command line as described at @@ -161,9 +166,13 @@ endfunction() function(install_cython_wrapped_library interface_header generated_files_path install_path) get_filename_component(module_name "${interface_header}" NAME_WE) - # NOTE: only installs .pxd and .pyx and binary files (not .cpp) - the trailing slash on the directory name + # NOTE: only installs .pxd and .pyx and binary files (not .cpp) - the trailing slash on the directory name # here prevents creating the top-level module name directory in the destination. - message(STATUS "Installing Cython Toolbox to ${install_path}") #${GTSAM_CYTHON_INSTALL_PATH}") + # Split up filename to strip trailing '/' in GTSAM_CYTHON_INSTALL_PATH/subdirectory if there is one + get_filename_component(location "${install_path}" PATH) + get_filename_component(name "${install_path}" NAME) + message(STATUS "Installing Cython Toolbox to ${location}${GTSAM_BUILD_TAG}/${name}") #${GTSAM_CYTHON_INSTALL_PATH}" + if(GTSAM_BUILD_TYPE_POSTFIXES) foreach(build_type ${CMAKE_CONFIGURATION_TYPES}) string(TOUPPER "${build_type}" build_type_upper) @@ -172,10 +181,8 @@ function(install_cython_wrapped_library interface_header generated_files_path in else() set(build_type_tag "${build_type}") endif() - # Split up filename to strip trailing '/' in GTSAM_CYTHON_INSTALL_PATH if there is one - get_filename_component(location "${install_path}" PATH) - get_filename_component(name "${install_path}" NAME) - install(DIRECTORY "${generated_files_path}/" DESTINATION "${location}/${name}${build_type_tag}" + + install(DIRECTORY "${generated_files_path}/" DESTINATION "${location}${build_type_tag}/${name}" CONFIGURATIONS "${build_type}" PATTERN "build" EXCLUDE PATTERN "CMakeFiles" EXCLUDE From 914ea4cfc84d91fa93fc80abc6608e2af06aa472 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 12 Jun 2019 15:07:41 -0400 Subject: [PATCH 112/160] install gtsam_eigency to the correct cython directory --- cython/gtsam_eigency/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cython/gtsam_eigency/CMakeLists.txt b/cython/gtsam_eigency/CMakeLists.txt index 4bff567ebe..77bead834d 100644 --- a/cython/gtsam_eigency/CMakeLists.txt +++ b/cython/gtsam_eigency/CMakeLists.txt @@ -39,11 +39,11 @@ add_dependencies(cythonize_eigency cythonize_eigency_conversions cythonize_eigen # install install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - DESTINATION ${GTSAM_CYTHON_INSTALL_PATH} + DESTINATION "${GTSAM_CYTHON_INSTALL_PATH}${GTSAM_BUILD_TAG}" PATTERN "CMakeLists.txt" EXCLUDE PATTERN "__init__.py.in" EXCLUDE) install(TARGETS cythonize_eigency_core cythonize_eigency_conversions - DESTINATION "${GTSAM_CYTHON_INSTALL_PATH}/gtsam_eigency") -install(FILES ${OUTPUT_DIR}/conversions_api.h DESTINATION ${GTSAM_CYTHON_INSTALL_PATH}/gtsam_eigency) + DESTINATION "${GTSAM_CYTHON_INSTALL_PATH}${GTSAM_BUILD_TAG}/gtsam_eigency") +install(FILES ${OUTPUT_DIR}/conversions_api.h DESTINATION ${GTSAM_CYTHON_INSTALL_PATH}${GTSAM_BUILD_TAG}/gtsam_eigency) configure_file(__init__.py.in ${OUTPUT_DIR}/__init__.py) -install(FILES ${OUTPUT_DIR}/__init__.py DESTINATION ${GTSAM_CYTHON_INSTALL_PATH}/gtsam_eigency) +install(FILES ${OUTPUT_DIR}/__init__.py DESTINATION ${GTSAM_CYTHON_INSTALL_PATH}${GTSAM_BUILD_TAG}/gtsam_eigency) From 2071aa7556f8c27eee39f3f3c8cdf055938307db Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 12 Jun 2019 15:08:22 -0400 Subject: [PATCH 113/160] update to setup.py install check to account for build type --- cython/setup.py.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/setup.py.in b/cython/setup.py.in index ed02372d80..c35e54079d 100644 --- a/cython/setup.py.in +++ b/cython/setup.py.in @@ -7,7 +7,7 @@ except ImportError: if 'SETUP_PY_NO_CHECK' not in os.environ: script_path = os.path.abspath(os.path.realpath(__file__)) - install_path = os.path.abspath(os.path.realpath(os.path.join('${GTSAM_CYTHON_INSTALL_PATH}', 'setup.py'))) + install_path = os.path.abspath(os.path.realpath(os.path.join('${GTSAM_CYTHON_INSTALL_PATH}${GTSAM_BUILD_TAG}', 'setup.py'))) if script_path != install_path: print('setup.py is being run from an unexpected location: "{}"'.format(script_path)) print('please run `make install` and run the script from there') From 162d748dd1acc35b3b605a5ead788a115d15e74b Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 12 Jun 2019 17:50:51 -0400 Subject: [PATCH 114/160] [ci skip] Added free function to check if compiled library is debug version or not --- gtsam.h | 3 +++ gtsam/base/debug.cpp | 11 +++++++++++ gtsam/base/debug.h | 1 + 3 files changed, 15 insertions(+) diff --git a/gtsam.h b/gtsam.h index 97eb2d8c30..826f8472ec 100644 --- a/gtsam.h +++ b/gtsam.h @@ -248,6 +248,9 @@ class FactorIndices { /** gtsam namespace functions */ +#include +bool isDebugVersion(); + #include class IndexPair { IndexPair(); diff --git a/gtsam/base/debug.cpp b/gtsam/base/debug.cpp index 1c4d08dcdc..d6d2a4fe02 100644 --- a/gtsam/base/debug.cpp +++ b/gtsam/base/debug.cpp @@ -47,4 +47,15 @@ void guardedSetDebug(const std::string& s, const bool v) { gtsam::debugFlags[s] = v; } +bool isDebugVersion() { +#ifdef NDEBUG + // nondebug + return false; +#else + // debug + return true; +#endif + +} + } diff --git a/gtsam/base/debug.h b/gtsam/base/debug.h index e21efcc831..7fa9435031 100644 --- a/gtsam/base/debug.h +++ b/gtsam/base/debug.h @@ -47,6 +47,7 @@ namespace gtsam { // Non-guarded use led to crashes, and solved in commit cd35db2 bool GTSAM_EXPORT guardedIsDebug(const std::string& s); void GTSAM_EXPORT guardedSetDebug(const std::string& s, const bool v); + bool GTSAM_EXPORT isDebugVersion(); } #undef ISDEBUG From 37aa1676852ffbdf40150558f748f8179f836519 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 12 Jun 2019 22:57:31 -0400 Subject: [PATCH 115/160] [ci skip] added comment and formatting for isDebugVersion --- gtsam/base/debug.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gtsam/base/debug.h b/gtsam/base/debug.h index 7fa9435031..5f42580b1f 100644 --- a/gtsam/base/debug.h +++ b/gtsam/base/debug.h @@ -47,6 +47,8 @@ namespace gtsam { // Non-guarded use led to crashes, and solved in commit cd35db2 bool GTSAM_EXPORT guardedIsDebug(const std::string& s); void GTSAM_EXPORT guardedSetDebug(const std::string& s, const bool v); + + // function to check if compiled version has debug information bool GTSAM_EXPORT isDebugVersion(); } From c572a14d7b7be4badd65414cfb75d2f94bebfa08 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 20 Jun 2019 21:18:55 +0200 Subject: [PATCH 116/160] Fix more CMake errors for MSVC builds --- CMakeLists.txt | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b33b5d57c..bfe57b599e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,15 +82,17 @@ if(NOT MSVC AND NOT XCODE_VERSION) option(GTSAM_BUILD_WITH_CCACHE "Use ccache compiler cache" ON) endif() -# Set the build type to upper case for downstream use -string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) - -# Set the GTSAM_BUILD_TAG variable. -# If build type is Release, set to blank (""), else set to the build type. -if(${CMAKE_BUILD_TYPE_UPPER} STREQUAL "RELEASE") - set(GTSAM_BUILD_TAG "") # Don't create release mode tag on installed directory -else() - set(GTSAM_BUILD_TAG "${CMAKE_BUILD_TYPE}") +if(NOT MSVC AND NOT XCODE_VERSION) + # Set the build type to upper case for downstream use + string(TOUPPER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_UPPER) + + # Set the GTSAM_BUILD_TAG variable. + # If build type is Release, set to blank (""), else set to the build type. + if("${CMAKE_BUILD_TYPE_UPPER}" STREQUAL "RELEASE") + set(GTSAM_BUILD_TAG "") # Don't create release mode tag on installed directory + else() + set(GTSAM_BUILD_TAG "${CMAKE_BUILD_TYPE}") + endif() endif() # Options relating to MATLAB wrapper @@ -537,7 +539,6 @@ if(GTSAM_UNSTABLE_AVAILABLE) print_config_flag(${GTSAM_BUILD_UNSTABLE} "Build libgtsam_unstable ") endif() -print_config_flag(${GTSAM_BUILD_WITH_MARCH_NATIVE} "Build for native architecture ") if(NOT MSVC AND NOT XCODE_VERSION) print_config_flag(${GTSAM_BUILD_WITH_MARCH_NATIVE} "Build for native architecture ") message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") From e3b4b5f33d2845c8b83330bb9bdb9f01b85a04e7 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Sat, 22 Jun 2019 12:35:32 +0200 Subject: [PATCH 117/160] Allow Eigen versions of different patch number --- gtsam/base/Vector.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gtsam/base/Vector.h b/gtsam/base/Vector.h index 74cb89918c..99c5a4d425 100644 --- a/gtsam/base/Vector.h +++ b/gtsam/base/Vector.h @@ -71,8 +71,7 @@ typedef Eigen::VectorBlock ConstSubVector; #if defined(GTSAM_EIGEN_VERSION_WORLD) static_assert( GTSAM_EIGEN_VERSION_WORLD==EIGEN_WORLD_VERSION && - GTSAM_EIGEN_VERSION_MAJOR==EIGEN_MAJOR_VERSION && - GTSAM_EIGEN_VERSION_MINOR==EIGEN_MINOR_VERSION, + GTSAM_EIGEN_VERSION_MAJOR==EIGEN_MAJOR_VERSION, "Error: GTSAM was built against a different version of Eigen"); #endif From 090994a1a3cbcfaebc9ab7f934fffb3c0a5ccc07 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 10 Jul 2019 15:56:07 -0400 Subject: [PATCH 118/160] propagate exceptions from global functions from cpp to python --- wrap/GlobalFunction.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/wrap/GlobalFunction.cpp b/wrap/GlobalFunction.cpp index 13616d64ad..02ab196572 100644 --- a/wrap/GlobalFunction.cpp +++ b/wrap/GlobalFunction.cpp @@ -143,6 +143,7 @@ void GlobalFunction::emit_cython_pxd(FileWriter& file) const { "\"("; argumentList(i).emit_cython_pxd(file, "", vector()); file.oss << ")"; + file.oss << " except +"; file.oss << "\n"; } } @@ -206,16 +207,18 @@ void GlobalFunction::emit_cython_pyx(FileWriter& file) const { /// Call corresponding cython function file.oss << argumentList(i).pyx_convertEigenTypeAndStorageOrder(" "); + // catch exception which indicates the parameters passed are incorrect. + file.oss << " except:\n"; + file.oss << " return False, None\n\n"; + string call = pyx_functionCall("", pxdName(), i); if (!returnVals_[i].isVoid()) { - file.oss << " return_value = " << call << "\n"; - file.oss << " return True, " << returnVals_[i].pyx_casting("return_value") << "\n"; + file.oss << " return_value = " << call << "\n"; + file.oss << " return True, " << returnVals_[i].pyx_casting("return_value") << "\n"; } else { - file.oss << " " << call << "\n"; - file.oss << " return True, None\n"; + file.oss << " " << call << "\n"; + file.oss << " return True, None\n"; } - file.oss << " except:\n"; - file.oss << " return False, None\n\n"; } } /* ************************************************************************* */ From 86323031574316b26968bba8dc62d5929df2ffe9 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 10 Jul 2019 15:56:32 -0400 Subject: [PATCH 119/160] updated tests to work with new wrap code generation --- wrap/tests/expected-cython/geometry.pxd | 6 +++--- wrap/tests/expected-cython/geometry.pyx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wrap/tests/expected-cython/geometry.pxd b/wrap/tests/expected-cython/geometry.pxd index f2cd513e21..0d9adac5fa 100644 --- a/wrap/tests/expected-cython/geometry.pxd +++ b/wrap/tests/expected-cython/geometry.pxd @@ -145,7 +145,7 @@ cdef extern from "folder/path/to/Test.h": cdef extern from "folder/path/to/Test.h" namespace "": - VectorXd pxd_aGlobalFunction "aGlobalFunction"() + VectorXd pxd_aGlobalFunction "aGlobalFunction"() except + cdef extern from "folder/path/to/Test.h" namespace "": - VectorXd pxd_overloadedGlobalFunction "overloadedGlobalFunction"(int a) - VectorXd pxd_overloadedGlobalFunction "overloadedGlobalFunction"(int a, double b) + VectorXd pxd_overloadedGlobalFunction "overloadedGlobalFunction"(int a) except + + VectorXd pxd_overloadedGlobalFunction "overloadedGlobalFunction"(int a, double b) except + diff --git a/wrap/tests/expected-cython/geometry.pyx b/wrap/tests/expected-cython/geometry.pyx index cae19d600d..606fde3e34 100644 --- a/wrap/tests/expected-cython/geometry.pyx +++ b/wrap/tests/expected-cython/geometry.pyx @@ -464,11 +464,11 @@ def overloadedGlobalFunction_0(args, kwargs): try: __params = process_args(['a'], args, kwargs) a = (__params[0]) - return_value = pxd_overloadedGlobalFunction(a) - return True, ndarray_copy(return_value).squeeze() except: return False, None + return_value = pxd_overloadedGlobalFunction(a) + return True, ndarray_copy(return_value).squeeze() def overloadedGlobalFunction_1(args, kwargs): cdef list __params cdef VectorXd return_value @@ -476,8 +476,8 @@ def overloadedGlobalFunction_1(args, kwargs): __params = process_args(['a', 'b'], args, kwargs) a = (__params[0]) b = (__params[1]) - return_value = pxd_overloadedGlobalFunction(a, b) - return True, ndarray_copy(return_value).squeeze() except: return False, None + return_value = pxd_overloadedGlobalFunction(a, b) + return True, ndarray_copy(return_value).squeeze() From 64ead677c2650941852f5d8f7033a164edb94542 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Sat, 22 Jun 2019 09:51:15 +0200 Subject: [PATCH 120/160] add minimal cmake-based user project template --- cmake/example_cmake_find_gtsam/CMakeLists.txt | 17 +++ cmake/example_cmake_find_gtsam/main.cpp | 127 ++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 cmake/example_cmake_find_gtsam/CMakeLists.txt create mode 100644 cmake/example_cmake_find_gtsam/main.cpp diff --git a/cmake/example_cmake_find_gtsam/CMakeLists.txt b/cmake/example_cmake_find_gtsam/CMakeLists.txt new file mode 100644 index 0000000000..9a4be4d70c --- /dev/null +++ b/cmake/example_cmake_find_gtsam/CMakeLists.txt @@ -0,0 +1,17 @@ +# This file shows how to build and link a user project against GTSAM using CMake +################################################################################### +# To create your own project, replace "example" with the actual name of your project +cmake_minimum_required(VERSION 3.0) +project(example CXX) + +# Find GTSAM, either from a local build, or from a Debian/Ubuntu package. +find_package(GTSAM REQUIRED) + +add_executable(example + main.cpp +) + +# By using CMake exported targets, a simple "link" dependency introduces the +# include directories (-I) flags, links against Boost, and add any other +# required build flags (e.g. C++11, etc.) +target_link_libraries(example PRIVATE gtsam) diff --git a/cmake/example_cmake_find_gtsam/main.cpp b/cmake/example_cmake_find_gtsam/main.cpp new file mode 100644 index 0000000000..4d93e1b19f --- /dev/null +++ b/cmake/example_cmake_find_gtsam/main.cpp @@ -0,0 +1,127 @@ +/* ---------------------------------------------------------------------------- + * 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 Pose2SLAMExample.cpp + * @brief A 2D Pose SLAM example + * @date Oct 21, 2010 + * @author Yong Dian Jian + */ + +/** + * A simple 2D pose slam example + * - The robot moves in a 2 meter square + * - The robot moves 2 meters each step, turning 90 degrees after each step + * - The robot initially faces along the X axis (horizontal, to the right in 2D) + * - We have full odometry between pose + * - We have a loop closure constraint when the robot returns to the first position + */ + +// In planar SLAM example we use Pose2 variables (x, y, theta) to represent the robot poses +#include + +// We will use simple integer Keys to refer to the robot poses. +#include + +// In GTSAM, measurement functions are represented as 'factors'. Several common factors +// have been provided with the library for solving robotics/SLAM/Bundle Adjustment problems. +// Here we will use Between factors for the relative motion described by odometry measurements. +// We will also use a Between Factor to encode the loop closure constraint +// Also, we will initialize the robot at the origin using a Prior factor. +#include +#include + +// When the factors are created, we will add them to a Factor Graph. As the factors we are using +// are nonlinear factors, we will need a Nonlinear Factor Graph. +#include + +// Finally, once all of the factors have been added to our factor graph, we will want to +// solve/optimize to graph to find the best (Maximum A Posteriori) set of variable values. +// GTSAM includes several nonlinear optimizers to perform this step. Here we will use the +// a Gauss-Newton solver +#include + +// Once the optimized values have been calculated, we can also calculate the marginal covariance +// of desired variables +#include + +// The nonlinear solvers within GTSAM are iterative solvers, meaning they linearize the +// nonlinear functions around an initial linearization point, then solve the linear system +// to update the linearization point. This happens repeatedly until the solver converges +// to a consistent set of variable values. This requires us to specify an initial guess +// for each variable, held in a Values container. +#include + + +using namespace std; +using namespace gtsam; + +int main(int argc, char** argv) { + + // 1. Create a factor graph container and add factors to it + NonlinearFactorGraph graph; + + // 2a. Add a prior on the first pose, setting it to the origin + // A prior factor consists of a mean and a noise model (covariance matrix) + noiseModel::Diagonal::shared_ptr priorNoise = noiseModel::Diagonal::Sigmas(Vector3(0.3, 0.3, 0.1)); + graph.emplace_shared >(1, Pose2(0, 0, 0), priorNoise); + + // For simplicity, we will use the same noise model for odometry and loop closures + noiseModel::Diagonal::shared_ptr model = noiseModel::Diagonal::Sigmas(Vector3(0.2, 0.2, 0.1)); + + // 2b. Add odometry factors + // Create odometry (Between) factors between consecutive poses + graph.emplace_shared >(1, 2, Pose2(2, 0, 0 ), model); + graph.emplace_shared >(2, 3, Pose2(2, 0, M_PI_2), model); + graph.emplace_shared >(3, 4, Pose2(2, 0, M_PI_2), model); + graph.emplace_shared >(4, 5, Pose2(2, 0, M_PI_2), model); + + // 2c. Add the loop closure constraint + // This factor encodes the fact that we have returned to the same pose. In real systems, + // these constraints may be identified in many ways, such as appearance-based techniques + // with camera images. We will use another Between Factor to enforce this constraint: + graph.emplace_shared >(5, 2, Pose2(2, 0, M_PI_2), model); + graph.print("\nFactor Graph:\n"); // print + + // 3. Create the data structure to hold the initialEstimate estimate to the solution + // For illustrative purposes, these have been deliberately set to incorrect values + Values initialEstimate; + initialEstimate.insert(1, Pose2(0.5, 0.0, 0.2 )); + initialEstimate.insert(2, Pose2(2.3, 0.1, -0.2 )); + initialEstimate.insert(3, Pose2(4.1, 0.1, M_PI_2)); + initialEstimate.insert(4, Pose2(4.0, 2.0, M_PI )); + initialEstimate.insert(5, Pose2(2.1, 2.1, -M_PI_2)); + initialEstimate.print("\nInitial Estimate:\n"); // print + + // 4. Optimize the initial values using a Gauss-Newton nonlinear optimizer + // The optimizer accepts an optional set of configuration parameters, + // controlling things like convergence criteria, the type of linear + // system solver to use, and the amount of information displayed during + // optimization. We will set a few parameters as a demonstration. + GaussNewtonParams parameters; + // Stop iterating once the change in error between steps is less than this value + parameters.relativeErrorTol = 1e-5; + // Do not perform more than N iteration steps + parameters.maxIterations = 100; + // Create the optimizer ... + GaussNewtonOptimizer optimizer(graph, initialEstimate, parameters); + // ... and optimize + Values result = optimizer.optimize(); + result.print("Final Result:\n"); + + // 5. Calculate and print marginal covariances for all variables + cout.precision(3); + Marginals marginals(graph, result); + cout << "x1 covariance:\n" << marginals.marginalCovariance(1) << endl; + cout << "x2 covariance:\n" << marginals.marginalCovariance(2) << endl; + cout << "x3 covariance:\n" << marginals.marginalCovariance(3) << endl; + cout << "x4 covariance:\n" << marginals.marginalCovariance(4) << endl; + cout << "x5 covariance:\n" << marginals.marginalCovariance(5) << endl; + + return 0; +} From 325d4a674d744365c475f944458c0ce2c178aa11 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Claraco Date: Thu, 20 Jun 2019 01:02:38 +0200 Subject: [PATCH 121/160] add Debian scripts --- debian/README.md | 12 ++ debian/changelog | 5 + debian/compat | 1 + debian/control | 15 ++ debian/copyright | 15 ++ debian/gtsam-docs.docs | 0 debian/rules | 25 +++ debian/source/format | 1 + package_scripts/README.md | 14 ++ package_scripts/prepare_debian.sh | 179 ++++++++++++++++++ .../prepare_debian_gen_snapshot_version.sh | 25 +++ package_scripts/prepare_release.sh | 71 +++++++ .../prepare_ubuntu_pkgs_for_ppa.sh | 90 +++++++++ package_scripts/upload_all_gtsam_ppa.sh | 3 + 14 files changed, 456 insertions(+) create mode 100644 debian/README.md create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/gtsam-docs.docs create mode 100755 debian/rules create mode 100644 debian/source/format create mode 100644 package_scripts/README.md create mode 100755 package_scripts/prepare_debian.sh create mode 100755 package_scripts/prepare_debian_gen_snapshot_version.sh create mode 100755 package_scripts/prepare_release.sh create mode 100755 package_scripts/prepare_ubuntu_pkgs_for_ppa.sh create mode 100755 package_scripts/upload_all_gtsam_ppa.sh diff --git a/debian/README.md b/debian/README.md new file mode 100644 index 0000000000..74eb351cd8 --- /dev/null +++ b/debian/README.md @@ -0,0 +1,12 @@ +# How to build a GTSAM debian package + +To use the ``debuild`` command, install the ``devscripts`` package + + sudo apt install devscripts + +Change into the gtsam directory, then run: + + debuild -us -uc -j4 + +Adjust the ``-j4`` depending on how many CPUs you want to build on in +parallel. diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000000..ef5d5ab972 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +gtsam (4.0.0-1berndpfrommer) bionic; urgency=medium + + * initial release + + -- Bernd Pfrommer Wed, 18 Jul 2018 20:36:44 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000000..ec635144f6 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000000..9a37e4377d --- /dev/null +++ b/debian/control @@ -0,0 +1,15 @@ +Source: gtsam +Section: libs +Priority: optional +Maintainer: Frank Dellaert +Uploaders: Jose Luis Blanco Claraco , Bernd Pfrommer +Build-Depends: cmake, libboost-all-dev (>= 1.58), libeigen3-dev, libtbb-dev, debhelper (>=9) +Standards-Version: 3.9.7 +Homepage: https://github.com/borglab/gtsam +Vcs-Browser: https://github.com/borglab/gtsam + +Package: libgtsam-dev +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Georgia Tech Smoothing and Mapping Library + gtsam: Georgia Tech Smoothing and Mapping library for SLAM type applications diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000000..c2f41d83d5 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,15 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: gtsam +Source: https://bitbucket.org/gtborg/gtsam.git + +Files: * +Copyright: 2017, Frank Dellaert +License: BSD + +Files: gtsam/3rdparty/CCOLAMD/* +Copyright: 2005-2011, Univ. of Florida. Authors: Timothy A. Davis, Sivasankaran Rajamanickam, and Stefan Larimore. Closely based on COLAMD by Davis, Stefan Larimore, in collaboration with Esmond Ng, and John Gilbert. http://www.cise.ufl.edu/research/sparse +License: GNU LESSER GENERAL PUBLIC LICENSE + +Files: gtsam/3rdparty/Eigen/* +Copyright: 2017, Multiple Authors +License: MPL2 diff --git a/debian/gtsam-docs.docs b/debian/gtsam-docs.docs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000000..6a8d21c00f --- /dev/null +++ b/debian/rules @@ -0,0 +1,25 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +export DH_VERBOSE = 1 + + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +%: + dh $@ --parallel + + +# dh_make generated override targets +# This is example for Cmake (See https://bugs.debian.org/641051 ) +override_dh_auto_configure: + dh_auto_configure -- -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=/usr -DGTSAM_BUILD_EXAMPLES_ALWAYS=OFF -DGTSAM_BUILD_TESTS=OFF -DGTSAM_BUILD_WRAP=OFF -DGTSAM_BUILD_DOCS=OFF -DGTSAM_INSTALL_CPPUNITLITE=OFF -DGTSAM_INSTALL_GEOGRAPHICLIB=OFF -DGTSAM_BUILD_TYPE_POSTFIXES=OFF + diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/package_scripts/README.md b/package_scripts/README.md new file mode 100644 index 0000000000..1e33369aa6 --- /dev/null +++ b/package_scripts/README.md @@ -0,0 +1,14 @@ + +# How to generate Debian packages + + cd [GTSAM_SOURCE_ROOT] + bash package_scripts/prepare_debian.sh + + +# How to generate Ubuntu packages for a PPA + + cd [GTSAM_SOURCE_ROOT] + bash package_scripts/prepare_ubuntu_pkgs_for_ppa.sh + cd ~/gtsam_ubuntu + bash [GTSAM_SOURCE_ROOT]/upload_all_gtsam_ppa.sh + diff --git a/package_scripts/prepare_debian.sh b/package_scripts/prepare_debian.sh new file mode 100755 index 0000000000..7b083d8bbc --- /dev/null +++ b/package_scripts/prepare_debian.sh @@ -0,0 +1,179 @@ +#!/bin/bash +# Prepare to build a Debian package. +# Jose Luis Blanco Claraco, 2019 (for GTSAM) +# Jose Luis Blanco Claraco, 2008-2018 (for MRPT) + +set -e # end on error +#set -x # for debugging + +APPEND_SNAPSHOT_NUM=0 +IS_FOR_UBUNTU=0 +APPEND_LINUX_DISTRO="" +VALUE_EXTRA_CMAKE_PARAMS="" +while getopts "sud:c:" OPTION +do + case $OPTION in + s) + APPEND_SNAPSHOT_NUM=1 + ;; + u) + IS_FOR_UBUNTU=1 + ;; + d) + APPEND_LINUX_DISTRO=$OPTARG + ;; + c) + VALUE_EXTRA_CMAKE_PARAMS=$OPTARG + ;; + ?) + echo "Unknown command line argument!" + exit 1 + ;; + esac +done + +if [ -f CMakeLists.txt ]; +then + source package_scripts/prepare_debian_gen_snapshot_version.sh +else + echo "Error: cannot find CMakeList.txt. This script is intended to be run from the root of the source tree." + exit 1 +fi + +# Append snapshot? +if [ $APPEND_SNAPSHOT_NUM == "1" ]; +then + CUR_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + source $CUR_SCRIPT_DIR/prepare_debian_gen_snapshot_version.sh # populate GTSAM_SNAPSHOT_VERSION + + GTSAM_VERSION_STR="${GTSAM_VERSION_STR}~snapshot${GTSAM_SNAPSHOT_VERSION}${APPEND_LINUX_DISTRO}" +else + GTSAM_VERSION_STR="${GTSAM_VERSION_STR}${APPEND_LINUX_DISTRO}" +fi + +# Call prepare_release +GTSAMSRC=`pwd` + +if [ -f $HOME/gtsam_release/gtsam*.tar.gz ]; +then + echo "## release file already exists. Reusing it." +else + source package_scripts/prepare_release.sh + echo + echo "## Done prepare_release.sh" +fi + +echo "=========== Generating GTSAM ${GTSAM_VER_MMP} Debian package ==============" +cd $GTSAMSRC + +set -x +if [ -z "$GTSAM_DEB_DIR" ]; then + GTSAM_DEB_DIR="$HOME/gtsam_debian" +fi +GTSAM_EXTERN_DEBIAN_DIR="$GTSAMSRC/debian/" +GTSAM_EXTERN_UBUNTU_PPA_DIR="$GTSAMSRC/debian/" + +if [ -f ${GTSAM_EXTERN_DEBIAN_DIR}/control ]; +then + echo "Using debian dir: ${GTSAM_EXTERN_DEBIAN_DIR}" +else + echo "ERROR: Cannot find ${GTSAM_EXTERN_DEBIAN_DIR}" + exit 1 +fi + +GTSAM_DEBSRC_DIR=$GTSAM_DEB_DIR/gtsam-${GTSAM_VERSION_STR} + +echo "GTSAM_VERSION_STR: ${GTSAM_VERSION_STR}" +echo "GTSAM_DEBSRC_DIR: ${GTSAM_DEBSRC_DIR}" + +# Prepare a directory for building the debian package: +# +rm -fR $GTSAM_DEB_DIR || true +mkdir -p $GTSAM_DEB_DIR || true + +# Orig tarball: +echo "Copying orig tarball: gtsam_${GTSAM_VERSION_STR}.orig.tar.gz" +cp $HOME/gtsam_release/gtsam*.tar.gz $GTSAM_DEB_DIR/gtsam_${GTSAM_VERSION_STR}.orig.tar.gz +cd ${GTSAM_DEB_DIR} +tar -xf gtsam_${GTSAM_VERSION_STR}.orig.tar.gz + +if [ ! -d "${GTSAM_DEBSRC_DIR}" ]; +then + mv gtsam-* ${GTSAM_DEBSRC_DIR} # fix different dir names for Ubuntu PPA packages +fi + +if [ ! -f "${GTSAM_DEBSRC_DIR}/CMakeLists.txt" ]; +then + echo "*ERROR*: Seems there was a problem copying sources to ${GTSAM_DEBSRC_DIR}... aborting script." + exit 1 +fi + +cd ${GTSAM_DEBSRC_DIR} + +# Copy debian directory: +#mkdir debian +cp -r ${GTSAM_EXTERN_DEBIAN_DIR}/* debian + +# Use modified control & rules files for Ubuntu PPA packages: +#if [ $IS_FOR_UBUNTU == "1" ]; +#then + # already done: cp ${GTSAM_EXTERN_UBUNTU_PPA_DIR}/control.in debian/ + # Ubuntu: force use of gcc-7: + #sed -i '9i\export CXX=/usr/bin/g++-7\' debian/rules + #sed -i '9i\export CC=/usr/bin/gcc-7\' debian/rules7 +#fi + +# Export signing pub key: +mkdir debian/upstream/ +gpg --export --export-options export-minimal --armor > debian/upstream/signing-key.asc + +# Parse debian/ control.in --> control +#mv debian/control.in debian/control +#sed -i "s/@GTSAM_VER_MM@/${GTSAM_VER_MM}/g" debian/control + +# Replace the text "REPLACE_HERE_EXTRA_CMAKE_PARAMS" in the "debian/rules" file +# with: ${${VALUE_EXTRA_CMAKE_PARAMS}} +RULES_FILE=debian/rules +sed -i -e "s/REPLACE_HERE_EXTRA_CMAKE_PARAMS/${VALUE_EXTRA_CMAKE_PARAMS}/g" $RULES_FILE +echo "Using these extra parameters for CMake: '${VALUE_EXTRA_CMAKE_PARAMS}'" + +# Strip my custom files... +rm debian/*.new || true + + +# Figure out the next Debian version number: +echo "Detecting next Debian version number..." + +CHANGELOG_UPSTREAM_VER=$( dpkg-parsechangelog | sed -n 's/Version:.*\([0-9]\.[0-9]*\.[0-9]*.*snapshot.*\)-.*/\1/p' ) +CHANGELOG_LAST_DEBIAN_VER=$( dpkg-parsechangelog | sed -n 's/Version:.*\([0-9]\.[0-9]*\.[0-9]*\).*-\([0-9]*\).*/\2/p' ) + +echo " -> PREVIOUS UPSTREAM: $CHANGELOG_UPSTREAM_VER -> New: ${GTSAM_VERSION_STR}" +echo " -> PREVIOUS DEBIAN VERSION: $CHANGELOG_LAST_DEBIAN_VER" + +# If we have the same upstream versions, increase the Debian version, otherwise create a new entry: +if [ "$CHANGELOG_UPSTREAM_VER" = "$GTSAM_VERSION_STR" ]; +then + NEW_DEBIAN_VER=$[$CHANGELOG_LAST_DEBIAN_VER + 1] + echo "Changing to a new Debian version: ${GTSAM_VERSION_STR}-${NEW_DEBIAN_VER}" + DEBCHANGE_CMD="--newversion ${GTSAM_VERSION_STR}-${NEW_DEBIAN_VER}" +else + DEBCHANGE_CMD="--newversion ${GTSAM_VERSION_STR}-1" +fi + +echo "Adding a new entry to debian/changelog..." + +DEBEMAIL="Jose Luis Blanco Claraco " debchange $DEBCHANGE_CMD -b --distribution unstable --force-distribution New version of upstream sources. + +echo "Copying back the new changelog to a temporary file in: ${GTSAM_EXTERN_DEBIAN_DIR}changelog.new" +cp debian/changelog ${GTSAM_EXTERN_DEBIAN_DIR}changelog.new + +set +x + +echo "==============================================================" +echo "Now, you can build the source Deb package with 'debuild -S -sa'" +echo "==============================================================" + +cd .. +ls -lh + +exit 0 diff --git a/package_scripts/prepare_debian_gen_snapshot_version.sh b/package_scripts/prepare_debian_gen_snapshot_version.sh new file mode 100755 index 0000000000..589d422fe2 --- /dev/null +++ b/package_scripts/prepare_debian_gen_snapshot_version.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# See https://reproducible-builds.org/specs/source-date-epoch/ +# get SOURCE_DATE_EPOCH with UNIX time_t +if [ -d ".git" ]; +then + SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) +else + echo "Error: intended for use from within a git repository" + exit 1 +fi +GTSAM_SNAPSHOT_VERSION=$(date -d @$SOURCE_DATE_EPOCH +%Y%m%d-%H%M) + +GTSAM_SNAPSHOT_VERSION+="-git-" +GTSAM_SNAPSHOT_VERSION+=`git rev-parse --short=8 HEAD` +GTSAM_SNAPSHOT_VERSION+="-" + +# x.y.z version components: +GTSAM_VERSION_MAJOR=$(grep "(GTSAM_VERSION_MAJOR" CMakeLists.txt | sed -r 's/^.*GTSAM_VERSION_MAJOR\s*([0-9])*.*$/\1/g') +GTSAM_VERSION_MINOR=$(grep "(GTSAM_VERSION_MINOR" CMakeLists.txt | sed -r 's/^.*GTSAM_VERSION_MINOR\s*([0-9])*.*$/\1/g') +GTSAM_VERSION_PATCH=$(grep "(GTSAM_VERSION_PATCH" CMakeLists.txt | sed -r 's/^.*GTSAM_VERSION_PATCH\s*([0-9])*.*$/\1/g') + +GTSAM_VER_MM="${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}" +GTSAM_VER_MMP="${GTSAM_VERSION_MAJOR}.${GTSAM_VERSION_MINOR}.${GTSAM_VERSION_PATCH}" +GTSAM_VERSION_STR=$GTSAM_VER_MMP diff --git a/package_scripts/prepare_release.sh b/package_scripts/prepare_release.sh new file mode 100755 index 0000000000..750fc27b31 --- /dev/null +++ b/package_scripts/prepare_release.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# Export sources from a git tree and prepare it for a public release. +# Jose Luis Blanco Claraco, 2019 (for GTSAM) +# Jose Luis Blanco Claraco, 2008-2018 (for MRPT) + +set -e # exit on error +#set -x # for debugging + +# Checks +# -------------------------------- +if [ -f version_prefix.txt ]; +then + if [ -z ${GTSAM_VERSION_STR+x} ]; + then + source package_scripts/prepare_debian_gen_snapshot_version.sh + fi + echo "ERROR: Run this script from the GTSAM source tree root directory." + exit 1 +fi + +GTSAM_SRC=`pwd` +OUT_RELEASES_DIR="$HOME/gtsam_release" + +OUT_DIR=$OUT_RELEASES_DIR/gtsam-${GTSAM_VERSION_STR} + +echo "=========== Generating GTSAM release ${GTSAM_VER_MMP} ==================" +echo "GTSAM_VERSION_STR : ${GTSAM_VERSION_STR}" +echo "OUT_DIR : ${OUT_DIR}" +echo "============================================================" +echo + +# Prepare output directory: +rm -fR $OUT_RELEASES_DIR || true +mkdir -p ${OUT_DIR} + +# Export / copy sources to target dir: +if [ -d "$GTSAM_SRC/.git" ]; +then + echo "# Exporting git source tree to ${OUT_DIR}" + git archive --format=tar HEAD | tar -x -C ${OUT_DIR} + + # Remove VCS control files: + find ${OUT_DIR} -name '.gitignore' | xargs rm + + # Generate ./SOURCE_DATE_EPOCH with UNIX time_t + SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) +else + echo "# Copying sources to ${OUT_DIR}" + cp -R . ${OUT_DIR} + + # Generate ./SOURCE_DATE_EPOCH with UNIX time_t + SOURCE_DATE_EPOCH=$(date +%s) +fi + +# See https://reproducible-builds.org/specs/source-date-epoch/ +echo $SOURCE_DATE_EPOCH > ${OUT_DIR}/SOURCE_DATE_EPOCH + +cd ${OUT_DIR} + +# Dont include Debian files in releases: +rm -fR package_scripts + +# Orig tarball: +cd .. +echo "# Creating orig tarball: gtsam-${GTSAM_VERSION_STR}.tar.gz" +tar czf gtsam-${GTSAM_VERSION_STR}.tar.gz gtsam-${GTSAM_VERSION_STR} + +rm -fr gtsam-${GTSAM_VERSION_STR} + +# GPG signature: +gpg --armor --detach-sign gtsam-${GTSAM_VERSION_STR}.tar.gz diff --git a/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh b/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh new file mode 100755 index 0000000000..40eeb16411 --- /dev/null +++ b/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# Creates a set of packages for each different Ubuntu distribution, with the +# intention of uploading them to: +# https://launchpad.net/~joseluisblancoc/ +# +# JLBC, 2010 +# [Addition 2012:] +# +# You can declare a variable (in the caller shell) with extra flags for the +# CMake in the final ./configure like: +# GTSAM_PKG_CUSTOM_CMAKE_PARAMS="\"-DDISABLE_SSE3=ON\"" +# + +set -e + +# List of distributions to create PPA packages for: +LST_DISTROS=(xenial bionic cosmic disco) + +# Checks +# -------------------------------- +if [ -f CMakeLists.txt ]; +then + source package_scripts/prepare_debian_gen_snapshot_version.sh + echo "GTSAM version: ${GTSAM_VER_MMP}" +else + echo "ERROR: Run this script from the GTSAM root directory." + exit 1 +fi + +if [ -z "${gtsam_ubuntu_OUT_DIR}" ]; then + export gtsam_ubuntu_OUT_DIR="$HOME/gtsam_ubuntu" +fi +GTSAMSRC=`pwd` +if [ -z "${GTSAM_DEB_DIR}" ]; then + export GTSAM_DEB_DIR="$HOME/gtsam_debian" +fi +GTSAM_EXTERN_DEBIAN_DIR="$GTSAMSRC/debian/" +EMAIL4DEB="Jose Luis Blanco (University of Malaga) " + +# Clean out dirs: +rm -fr $gtsam_ubuntu_OUT_DIR/ + +# ------------------------------------------------------------------- +# And now create the custom packages for each Ubuntu distribution: +# ------------------------------------------------------------------- +count=${#LST_DISTROS[@]} +IDXS=$(seq 0 $(expr $count - 1)) + +cp ${GTSAM_EXTERN_DEBIAN_DIR}/changelog /tmp/my_changelog + +for IDX in ${IDXS}; +do + DEBIAN_DIST=${LST_DISTROS[$IDX]} + + # ------------------------------------------------------------------- + # Call the standard "prepare_debian.sh" script: + # ------------------------------------------------------------------- + cd ${GTSAMSRC} + bash package_scripts/prepare_debian.sh -s -u -d ${DEBIAN_DIST} -c "${GTSAM_PKG_CUSTOM_CMAKE_PARAMS}" + + CUR_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + source $CUR_SCRIPT_DIR/prepare_debian_gen_snapshot_version.sh # populate GTSAM_SNAPSHOT_VERSION + + echo "===== Distribution: ${DEBIAN_DIST} =========" + cd ${GTSAM_DEB_DIR}/gtsam-${GTSAM_VER_MMP}~snapshot${GTSAM_SNAPSHOT_VERSION}${DEBIAN_DIST}/debian + #cp ${GTSAM_EXTERN_DEBIAN_DIR}/changelog changelog + cp /tmp/my_changelog changelog + DEBCHANGE_CMD="--newversion ${GTSAM_VERSION_STR}~snapshot${GTSAM_SNAPSHOT_VERSION}${DEBIAN_DIST}-1" + echo "Changing to a new Debian version: ${DEBCHANGE_CMD}" + echo "Adding a new entry to debian/changelog for distribution ${DEBIAN_DIST}" + DEBEMAIL="Jose Luis Blanco Claraco " debchange $DEBCHANGE_CMD -b --distribution ${DEBIAN_DIST} --force-distribution New version of upstream sources. + + cp changelog /tmp/my_changelog + + echo "Now, let's build the source Deb package with 'debuild -S -sa':" + cd .. + # -S: source package + # -sa: force inclusion of sources + # -d: don't check dependencies in this system + debuild -S -sa -d + + # Make a copy of all these packages: + cd .. + mkdir -p $gtsam_ubuntu_OUT_DIR/$DEBIAN_DIST + cp gtsam_* $gtsam_ubuntu_OUT_DIR/${DEBIAN_DIST}/ + echo ">>>>>> Saving packages to: $gtsam_ubuntu_OUT_DIR/$DEBIAN_DIST/" +done + + +exit 0 diff --git a/package_scripts/upload_all_gtsam_ppa.sh b/package_scripts/upload_all_gtsam_ppa.sh new file mode 100755 index 0000000000..c9d113f00a --- /dev/null +++ b/package_scripts/upload_all_gtsam_ppa.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +find . -name '*.changes' | xargs -I FIL dput ppa:joseluisblancoc/gtsam-develop FIL From 2cf4514e81fc3716b3c26c4c56bc3a644ff5f2da Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Tue, 9 Jul 2019 17:11:01 -0400 Subject: [PATCH 122/160] Changes to get gtsam to compile in Windows --- CMakeLists.txt | 36 ++++++++++++++----------------- cmake/GtsamMatlabWrap.cmake | 19 ++++++++++------ gtsam/geometry/CalibratedCamera.h | 7 ++++-- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bfe57b599e..738a434f24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,20 +179,18 @@ option(GTSAM_DISABLE_NEW_TIMERS "Disables using Boost.chrono for timing" OFF) # so we downgraded this to classic filenames-based variables, and manually adding # the target_include_directories(xxx ${Boost_INCLUDE_DIR}) set(GTSAM_BOOST_LIBRARIES - optimized - ${Boost_SERIALIZATION_LIBRARY_RELEASE} - ${Boost_SYSTEM_LIBRARY_RELEASE} - ${Boost_FILESYSTEM_LIBRARY_RELEASE} - ${Boost_THREAD_LIBRARY_RELEASE} - ${Boost_DATE_TIME_LIBRARY_RELEASE} - ${Boost_REGEX_LIBRARY_RELEASE} - debug - ${Boost_SERIALIZATION_LIBRARY_DEBUG} - ${Boost_SYSTEM_LIBRARY_DEBUG} - ${Boost_FILESYSTEM_LIBRARY_DEBUG} - ${Boost_THREAD_LIBRARY_DEBUG} - ${Boost_DATE_TIME_LIBRARY_DEBUG} - ${Boost_REGEX_LIBRARY_DEBUG} + optimized ${Boost_SERIALIZATION_LIBRARY_RELEASE} + optimized ${Boost_SYSTEM_LIBRARY_RELEASE} + optimized ${Boost_FILESYSTEM_LIBRARY_RELEASE} + optimized ${Boost_THREAD_LIBRARY_RELEASE} + optimized ${Boost_DATE_TIME_LIBRARY_RELEASE} + optimized ${Boost_REGEX_LIBRARY_RELEASE} + debug ${Boost_SERIALIZATION_LIBRARY_DEBUG} + debug ${Boost_SYSTEM_LIBRARY_DEBUG} + debug ${Boost_FILESYSTEM_LIBRARY_DEBUG} + debug ${Boost_THREAD_LIBRARY_DEBUG} + debug ${Boost_DATE_TIME_LIBRARY_DEBUG} + debug ${Boost_REGEX_LIBRARY_DEBUG} ) message(STATUS "GTSAM_BOOST_LIBRARIES: ${GTSAM_BOOST_LIBRARIES}") if (GTSAM_DISABLE_NEW_TIMERS) @@ -201,12 +199,10 @@ if (GTSAM_DISABLE_NEW_TIMERS) else() if(Boost_TIMER_LIBRARY) list(APPEND GTSAM_BOOST_LIBRARIES - optimized - ${Boost_TIMER_LIBRARY_RELEASE} - ${Boost_CHRONO_LIBRARY_RELEASE} - debug - ${Boost_TIMER_LIBRARY_DEBUG} - ${Boost_CHRONO_LIBRARY_DEBUG} + optimized ${Boost_TIMER_LIBRARY_RELEASE} + optimized ${Boost_CHRONO_LIBRARY_RELEASE} + debug ${Boost_TIMER_LIBRARY_DEBUG} + debug ${Boost_CHRONO_LIBRARY_DEBUG} ) else() list(APPEND GTSAM_BOOST_LIBRARIES rt) # When using the header-only boost timer library, need -lrt diff --git a/cmake/GtsamMatlabWrap.cmake b/cmake/GtsamMatlabWrap.cmake index bdd868665f..fd6d4bed54 100644 --- a/cmake/GtsamMatlabWrap.cmake +++ b/cmake/GtsamMatlabWrap.cmake @@ -35,7 +35,11 @@ mark_as_advanced(FORCE MEX_COMMAND) # Now that we have mex, trace back to find the Matlab installation root get_filename_component(MEX_COMMAND "${MEX_COMMAND}" REALPATH) get_filename_component(mex_path "${MEX_COMMAND}" PATH) -get_filename_component(MATLAB_ROOT "${mex_path}/.." ABSOLUTE) +if(mex_path MATCHES ".*/win64$") + get_filename_component(MATLAB_ROOT "${mex_path}/../.." ABSOLUTE) +else() + get_filename_component(MATLAB_ROOT "${mex_path}/.." ABSOLUTE) +endif() set(MATLAB_ROOT "${MATLAB_ROOT}" CACHE PATH "Path to MATLAB installation root (e.g. /usr/local/MATLAB/R2012a)") @@ -97,9 +101,9 @@ function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs ex set(compiled_mex_modules_root "${PROJECT_BINARY_DIR}/wrap/${moduleName}_mex") message(STATUS "Building wrap module ${moduleName}") - + # Find matlab.h in GTSAM - if("${PROJECT_NAME}" STREQUAL "GTSAM") + if("${PROJECT_NAME}" STREQUAL "gtsam") set(matlab_h_path "${PROJECT_SOURCE_DIR}") else() if(NOT GTSAM_INCLUDE_DIR) @@ -115,14 +119,15 @@ function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs ex set(automaticDependencies "") foreach(lib ${moduleName} ${linkLibraries}) #message("MODULE NAME: ${moduleName}") + #message("lib: ${lib}") if(TARGET "${lib}") get_target_property(dependentLibraries ${lib} INTERFACE_LINK_LIBRARIES) - # message("DEPENDENT LIBRARIES: ${dependentLibraries}") - if(dependentLibraries) - list(APPEND automaticDependencies ${dependentLibraries}) + #message("DEPENDENT LIBRARIES: ${dependentLibraries}") + if(dependentLibraries) + list(APPEND automaticDependencies ${dependentLibraries}) endif() endif() - endforeach() + endforeach() ## CHRIS: Temporary fix. On my system the get_target_property above returned Not-found for gtsam module ## This needs to be fixed!! diff --git a/gtsam/geometry/CalibratedCamera.h b/gtsam/geometry/CalibratedCamera.h index 1f791935d6..f2bc2f84ab 100644 --- a/gtsam/geometry/CalibratedCamera.h +++ b/gtsam/geometry/CalibratedCamera.h @@ -18,6 +18,10 @@ #pragma once +//Needed if windows.h is included somehow. +//It defines min and max, which conflicts with numeric_limits::max() +#define NOMINMAX + #include #include #include @@ -29,8 +33,7 @@ namespace gtsam { -class GTSAM_EXPORT CheiralityException: public ThreadsafeException< - CheiralityException> { +class GTSAM_EXPORT CheiralityException: public ThreadsafeException { public: CheiralityException() : CheiralityException(std::numeric_limits::max()) {} From f52db1eadc8dfaed097a2436819829d867ac9324 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 11 Jul 2019 14:23:14 +0200 Subject: [PATCH 123/160] undo whitespace changes --- cmake/GtsamMatlabWrap.cmake | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/cmake/GtsamMatlabWrap.cmake b/cmake/GtsamMatlabWrap.cmake index fd6d4bed54..28b3dac2c1 100644 --- a/cmake/GtsamMatlabWrap.cmake +++ b/cmake/GtsamMatlabWrap.cmake @@ -101,7 +101,7 @@ function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs ex set(compiled_mex_modules_root "${PROJECT_BINARY_DIR}/wrap/${moduleName}_mex") message(STATUS "Building wrap module ${moduleName}") - + # Find matlab.h in GTSAM if("${PROJECT_NAME}" STREQUAL "gtsam") set(matlab_h_path "${PROJECT_SOURCE_DIR}") @@ -119,15 +119,14 @@ function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs ex set(automaticDependencies "") foreach(lib ${moduleName} ${linkLibraries}) #message("MODULE NAME: ${moduleName}") - #message("lib: ${lib}") if(TARGET "${lib}") get_target_property(dependentLibraries ${lib} INTERFACE_LINK_LIBRARIES) - #message("DEPENDENT LIBRARIES: ${dependentLibraries}") - if(dependentLibraries) - list(APPEND automaticDependencies ${dependentLibraries}) + # message("DEPENDENT LIBRARIES: ${dependentLibraries}") + if(dependentLibraries) + list(APPEND automaticDependencies ${dependentLibraries}) endif() endif() - endforeach() + endforeach() ## CHRIS: Temporary fix. On my system the get_target_property above returned Not-found for gtsam module ## This needs to be fixed!! From 5384d209625958acadb723d793c32c4e747ca2c4 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 11 Jul 2019 14:26:50 +0200 Subject: [PATCH 124/160] Undo changes in CalibratedCamera --- gtsam/geometry/CalibratedCamera.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gtsam/geometry/CalibratedCamera.h b/gtsam/geometry/CalibratedCamera.h index f2bc2f84ab..1f791935d6 100644 --- a/gtsam/geometry/CalibratedCamera.h +++ b/gtsam/geometry/CalibratedCamera.h @@ -18,10 +18,6 @@ #pragma once -//Needed if windows.h is included somehow. -//It defines min and max, which conflicts with numeric_limits::max() -#define NOMINMAX - #include #include #include @@ -33,7 +29,8 @@ namespace gtsam { -class GTSAM_EXPORT CheiralityException: public ThreadsafeException { +class GTSAM_EXPORT CheiralityException: public ThreadsafeException< + CheiralityException> { public: CheiralityException() : CheiralityException(std::numeric_limits::max()) {} From 69c3eafb300750b6441317c3d9ffcd23aa41b135 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 11 Jul 2019 14:27:09 +0200 Subject: [PATCH 125/160] Add NOMINMAX macro for MSVC via cmake --- cmake/GtsamBuildTypes.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/GtsamBuildTypes.cmake b/cmake/GtsamBuildTypes.cmake index 56dd7dc088..aa35fea05f 100644 --- a/cmake/GtsamBuildTypes.cmake +++ b/cmake/GtsamBuildTypes.cmake @@ -77,7 +77,10 @@ set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_PROFILING "NDEBUG" CACHE STRING "(Us set(GTSAM_COMPILE_DEFINITIONS_PRIVATE_TIMING "NDEBUG;ENABLE_TIMING" CACHE STRING "(User editable) Private preprocessor macros for Timing configuration.") if(MSVC) # Common to all configurations: - list_append_cache(GTSAM_COMPILE_DEFINITIONS_PRIVATE WINDOWS_LEAN_AND_MEAN) + list_append_cache(GTSAM_COMPILE_DEFINITIONS_PRIVATE + WINDOWS_LEAN_AND_MEAN + NOMINMAX + ) endif() # Other (non-preprocessor macros) compiler flags: From 6dec8b12686500ffdb81c374e129e31ce6e40066 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Fri, 12 Jul 2019 01:16:24 +0200 Subject: [PATCH 126/160] fix cmake error with MATLAB --- cmake/GtsamMatlabWrap.cmake | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cmake/GtsamMatlabWrap.cmake b/cmake/GtsamMatlabWrap.cmake index 28b3dac2c1..9f46e898a4 100644 --- a/cmake/GtsamMatlabWrap.cmake +++ b/cmake/GtsamMatlabWrap.cmake @@ -82,28 +82,29 @@ function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs ex set(mexModuleExt mexw32) endif() endif() - + # Wrap codegen interface #usage: wrap interfacePath moduleName toolboxPath headerPath # interfacePath : *absolute* path to directory of module interface file # moduleName : the name of the module, interface file must be called moduleName.h # toolboxPath : the directory in which to generate the wrappers - # headerPath : path to matlab.h - + # headerPath : path to matlab.h + # Extract module name from interface header file name get_filename_component(interfaceHeader "${interfaceHeader}" ABSOLUTE) get_filename_component(modulePath "${interfaceHeader}" PATH) get_filename_component(moduleName "${interfaceHeader}" NAME_WE) - + # Paths for generated files set(generated_files_path "${PROJECT_BINARY_DIR}/wrap/${moduleName}") set(generated_cpp_file "${generated_files_path}/${moduleName}_wrapper.cpp") set(compiled_mex_modules_root "${PROJECT_BINARY_DIR}/wrap/${moduleName}_mex") - + message(STATUS "Building wrap module ${moduleName}") - + # Find matlab.h in GTSAM - if("${PROJECT_NAME}" STREQUAL "gtsam") + if(("${PROJECT_NAME}" STREQUAL "gtsam") OR + ("${PROJECT_NAME}" STREQUAL "gtsam_unstable")) set(matlab_h_path "${PROJECT_SOURCE_DIR}") else() if(NOT GTSAM_INCLUDE_DIR) From 1ca0e3395ca4359687caff1a369f27a6cc753abf Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Fri, 12 Jul 2019 07:58:18 +0200 Subject: [PATCH 127/160] Partial fix to MSVC Matlab builds --- cmake/GtsamMatlabWrap.cmake | 1 + gtsam/navigation/ScenarioRunner.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/GtsamMatlabWrap.cmake b/cmake/GtsamMatlabWrap.cmake index 9f46e898a4..5fc829bf21 100644 --- a/cmake/GtsamMatlabWrap.cmake +++ b/cmake/GtsamMatlabWrap.cmake @@ -226,6 +226,7 @@ function(wrap_library_internal interfaceHeader linkLibraries extraIncludeDirs ex string(REPLACE ";" " " mexFlagsSpaced "${GTSAM_BUILD_MEX_BINARY_FLAGS}") add_library(${moduleName}_matlab_wrapper MODULE ${generated_cpp_file} ${interfaceHeader} ${otherSourcesAndObjects}) target_link_libraries(${moduleName}_matlab_wrapper ${correctedOtherLibraries}) + target_link_libraries(${moduleName}_matlab_wrapper ${moduleName}) set_target_properties(${moduleName}_matlab_wrapper PROPERTIES OUTPUT_NAME "${moduleName}_wrapper" PREFIX "" diff --git a/gtsam/navigation/ScenarioRunner.h b/gtsam/navigation/ScenarioRunner.h index de451b7e35..5fb9f78d77 100644 --- a/gtsam/navigation/ScenarioRunner.h +++ b/gtsam/navigation/ScenarioRunner.h @@ -36,7 +36,7 @@ static noiseModel::Diagonal::shared_ptr Diagonal(const Matrix& covariance) { * Simple class to test navigation scenarios. * Takes a trajectory scenario as input, and can generate IMU measurements */ -class ScenarioRunner { +class GTSAM_EXPORT ScenarioRunner { public: typedef imuBias::ConstantBias Bias; typedef boost::shared_ptr SharedParams; From 33bda1ef71186bed91067d458d381fb5ff6b328d Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Fri, 12 Jul 2019 20:42:45 -0400 Subject: [PATCH 128/160] Resolved several MSVC compiling errors * The check.base unit tests all pass now. * The gstam_matlab_wrapper class compiles with now errors now. * Note that I had to remove all LieMatrix, LieVector, and LieScalar stuff to get this to work... --- gtsam.h | 93 --------------------- gtsam/CMakeLists.txt | 7 ++ gtsam/base/Matrix.h | 2 +- gtsam/base/tests/testLieMatrix.cpp | 70 ---------------- gtsam/base/tests/testLieScalar.cpp | 64 -------------- gtsam/base/tests/testLieVector.cpp | 66 --------------- gtsam/base/tests/testTestableAssertions.cpp | 10 --- 7 files changed, 8 insertions(+), 304 deletions(-) delete mode 100644 gtsam/base/tests/testLieMatrix.cpp delete mode 100644 gtsam/base/tests/testLieScalar.cpp delete mode 100644 gtsam/base/tests/testLieVector.cpp diff --git a/gtsam.h b/gtsam.h index 826f8472ec..d86887e8b7 100644 --- a/gtsam.h +++ b/gtsam.h @@ -286,99 +286,6 @@ virtual class GenericValue : gtsam::Value { void serializable() const; }; -#include -class LieScalar { - // Standard constructors - LieScalar(); - LieScalar(double d); - - // Standard interface - double value() const; - - // Testable - void print(string s) const; - bool equals(const gtsam::LieScalar& expected, double tol) const; - - // Group - static gtsam::LieScalar identity(); - gtsam::LieScalar inverse() const; - gtsam::LieScalar compose(const gtsam::LieScalar& p) const; - gtsam::LieScalar between(const gtsam::LieScalar& l2) const; - - // Manifold - size_t dim() const; - gtsam::LieScalar retract(Vector v) const; - Vector localCoordinates(const gtsam::LieScalar& t2) const; - - // Lie group - static gtsam::LieScalar Expmap(Vector v); - static Vector Logmap(const gtsam::LieScalar& p); -}; - -#include -class LieVector { - // Standard constructors - LieVector(); - LieVector(Vector v); - - // Standard interface - Vector vector() const; - - // Testable - void print(string s) const; - bool equals(const gtsam::LieVector& expected, double tol) const; - - // Group - static gtsam::LieVector identity(); - gtsam::LieVector inverse() const; - gtsam::LieVector compose(const gtsam::LieVector& p) const; - gtsam::LieVector between(const gtsam::LieVector& l2) const; - - // Manifold - size_t dim() const; - gtsam::LieVector retract(Vector v) const; - Vector localCoordinates(const gtsam::LieVector& t2) const; - - // Lie group - static gtsam::LieVector Expmap(Vector v); - static Vector Logmap(const gtsam::LieVector& p); - - // enabling serialization functionality - void serialize() const; -}; - -#include -class LieMatrix { - // Standard constructors - LieMatrix(); - LieMatrix(Matrix v); - - // Standard interface - Matrix matrix() const; - - // Testable - void print(string s) const; - bool equals(const gtsam::LieMatrix& expected, double tol) const; - - // Group - static gtsam::LieMatrix identity(); - gtsam::LieMatrix inverse() const; - gtsam::LieMatrix compose(const gtsam::LieMatrix& p) const; - gtsam::LieMatrix between(const gtsam::LieMatrix& l2) const; - - // Manifold - size_t dim() const; - gtsam::LieMatrix retract(Vector v) const; - Vector localCoordinates(const gtsam::LieMatrix & t2) const; - - // Lie group - static gtsam::LieMatrix Expmap(Vector v); - static Vector Logmap(const gtsam::LieMatrix& p); - - // enabling serialization functionality - void serialize() const; -}; - //************************************************************************* // geometry //************************************************************************* diff --git a/gtsam/CMakeLists.txt b/gtsam/CMakeLists.txt index b4a33943e5..b80f1626c1 100644 --- a/gtsam/CMakeLists.txt +++ b/gtsam/CMakeLists.txt @@ -39,6 +39,12 @@ set (excluded_sources #"") set (excluded_headers #"") "${CMAKE_CURRENT_SOURCE_DIR}/slam/serialization.h" + "${CMAKE_CURRENT_SOURCE_DIR}/base/LieMatrix.h" + "${CMAKE_CURRENT_SOURCE_DIR}/base/LieVector.h" + "${CMAKE_CURRENT_SOURCE_DIR}/base/LieScalar.h" + "${CMAKE_CURRENT_SOURCE_DIR}/base/deprecated/LieMatrix.h" + "${CMAKE_CURRENT_SOURCE_DIR}/base/deprecated/LieVector.h" + "${CMAKE_CURRENT_SOURCE_DIR}/base/deprecated/LieScalar.h" ) if(GTSAM_USE_QUATERNIONS) @@ -56,6 +62,7 @@ foreach(subdir ${gtsam_subdirs}) # Build convenience libraries file(GLOB_RECURSE subdir_srcs "${subdir}/*.cpp" "${subdir}/*.h") # Include header files so they show up in Visual Studio list(REMOVE_ITEM subdir_srcs ${excluded_sources}) + list(REMOVE_ITEM subdir_srcs ${excluded_headers}) file(GLOB subdir_test_files "${subdir}/tests/*") list(REMOVE_ITEM subdir_srcs ${subdir_test_files}) # Remove test files from sources compiled into library gtsam_assign_source_folders("${subdir_srcs}") # Create MSVC structure diff --git a/gtsam/base/Matrix.h b/gtsam/base/Matrix.h index 235cd30f35..8060ae7f4d 100644 --- a/gtsam/base/Matrix.h +++ b/gtsam/base/Matrix.h @@ -299,7 +299,7 @@ GTSAM_EXPORT std::pair qr(const Matrix& A); * @param A is the input matrix, and is the output * @param clear_below_diagonal enables zeroing out below diagonal */ -void inplace_QR(Matrix& A); +GTSAM_EXPORT void inplace_QR(Matrix& A); /** * Imperative algorithm for in-place full elimination with diff --git a/gtsam/base/tests/testLieMatrix.cpp b/gtsam/base/tests/testLieMatrix.cpp deleted file mode 100644 index 8c68bf8a00..0000000000 --- a/gtsam/base/tests/testLieMatrix.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* ---------------------------------------------------------------------------- - - * 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 testLieMatrix.cpp - * @author Richard Roberts - */ - -#include -#include -#include -#include - -using namespace gtsam; - -GTSAM_CONCEPT_TESTABLE_INST(LieMatrix) -GTSAM_CONCEPT_LIE_INST(LieMatrix) - -/* ************************************************************************* */ -TEST( LieMatrix, construction ) { - Matrix m = (Matrix(2,2) << 1.0,2.0, 3.0,4.0).finished(); - LieMatrix lie1(m), lie2(m); - - EXPECT(traits::GetDimension(m) == 4); - EXPECT(assert_equal(m, lie1.matrix())); - EXPECT(assert_equal(lie1, lie2)); -} - -/* ************************************************************************* */ -TEST( LieMatrix, other_constructors ) { - Matrix init = (Matrix(2,2) << 10.0,20.0, 30.0,40.0).finished(); - LieMatrix exp(init); - double data[] = {10,30,20,40}; - LieMatrix b(2,2,data); - EXPECT(assert_equal(exp, b)); -} - -/* ************************************************************************* */ -TEST(LieMatrix, retract) { - LieMatrix init((Matrix(2,2) << 1.0,2.0,3.0,4.0).finished()); - Vector update = (Vector(4) << 3.0, 4.0, 6.0, 7.0).finished(); - - LieMatrix expected((Matrix(2,2) << 4.0, 6.0, 9.0, 11.0).finished()); - LieMatrix actual = traits::Retract(init,update); - - EXPECT(assert_equal(expected, actual)); - - Vector expectedUpdate = update; - Vector actualUpdate = traits::Local(init,actual); - - EXPECT(assert_equal(expectedUpdate, actualUpdate)); - - Vector expectedLogmap = (Vector(4) << 1, 2, 3, 4).finished(); - Vector actualLogmap = traits::Logmap(LieMatrix((Matrix(2,2) << 1.0, 2.0, 3.0, 4.0).finished())); - EXPECT(assert_equal(expectedLogmap, actualLogmap)); -} - -/* ************************************************************************* */ -int main() { TestResult tr; return TestRegistry::runAllTests(tr); } -/* ************************************************************************* */ - - diff --git a/gtsam/base/tests/testLieScalar.cpp b/gtsam/base/tests/testLieScalar.cpp deleted file mode 100644 index 74f5e0d414..0000000000 --- a/gtsam/base/tests/testLieScalar.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* ---------------------------------------------------------------------------- - - * 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 testLieScalar.cpp - * @author Kai Ni - */ - -#include -#include -#include -#include - -using namespace gtsam; - -GTSAM_CONCEPT_TESTABLE_INST(LieScalar) -GTSAM_CONCEPT_LIE_INST(LieScalar) - -const double tol=1e-9; - -//****************************************************************************** -TEST(LieScalar , Concept) { - BOOST_CONCEPT_ASSERT((IsGroup)); - BOOST_CONCEPT_ASSERT((IsManifold)); - BOOST_CONCEPT_ASSERT((IsLieGroup)); -} - -//****************************************************************************** -TEST(LieScalar , Invariants) { - LieScalar lie1(2), lie2(3); - CHECK(check_group_invariants(lie1, lie2)); - CHECK(check_manifold_invariants(lie1, lie2)); -} - -/* ************************************************************************* */ -TEST( testLieScalar, construction ) { - double d = 2.; - LieScalar lie1(d), lie2(d); - - EXPECT_DOUBLES_EQUAL(2., lie1.value(),tol); - EXPECT_DOUBLES_EQUAL(2., lie2.value(),tol); - EXPECT(traits::dimension == 1); - EXPECT(assert_equal(lie1, lie2)); -} - -/* ************************************************************************* */ -TEST( testLieScalar, localCoordinates ) { - LieScalar lie1(1.), lie2(3.); - - Vector1 actual = traits::Local(lie1, lie2); - EXPECT( assert_equal((Vector)(Vector(1) << 2).finished(), actual)); -} - -/* ************************************************************************* */ -int main() { TestResult tr; return TestRegistry::runAllTests(tr); } -/* ************************************************************************* */ diff --git a/gtsam/base/tests/testLieVector.cpp b/gtsam/base/tests/testLieVector.cpp deleted file mode 100644 index 76c4fc490a..0000000000 --- a/gtsam/base/tests/testLieVector.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* ---------------------------------------------------------------------------- - - * 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 testLieVector.cpp - * @author Alex Cunningham - */ - -#include -#include -#include -#include - -using namespace gtsam; - -GTSAM_CONCEPT_TESTABLE_INST(LieVector) -GTSAM_CONCEPT_LIE_INST(LieVector) - -//****************************************************************************** -TEST(LieVector , Concept) { - BOOST_CONCEPT_ASSERT((IsGroup)); - BOOST_CONCEPT_ASSERT((IsManifold)); - BOOST_CONCEPT_ASSERT((IsLieGroup)); -} - -//****************************************************************************** -TEST(LieVector , Invariants) { - Vector v = Vector3(1.0, 2.0, 3.0); - LieVector lie1(v), lie2(v); - check_manifold_invariants(lie1, lie2); -} - -//****************************************************************************** -TEST( testLieVector, construction ) { - Vector v = Vector3(1.0, 2.0, 3.0); - LieVector lie1(v), lie2(v); - - EXPECT(lie1.dim() == 3); - EXPECT(assert_equal(v, lie1.vector())); - EXPECT(assert_equal(lie1, lie2)); -} - -//****************************************************************************** -TEST( testLieVector, other_constructors ) { - Vector init = Vector2(10.0, 20.0); - LieVector exp(init); - double data[] = { 10, 20 }; - LieVector b(2, data); - EXPECT(assert_equal(exp, b)); -} - -/* ************************************************************************* */ -int main() { - TestResult tr; - return TestRegistry::runAllTests(tr); -} -/* ************************************************************************* */ - diff --git a/gtsam/base/tests/testTestableAssertions.cpp b/gtsam/base/tests/testTestableAssertions.cpp index 305aa7ca93..3fd2e9003b 100644 --- a/gtsam/base/tests/testTestableAssertions.cpp +++ b/gtsam/base/tests/testTestableAssertions.cpp @@ -15,20 +15,10 @@ */ #include -#include #include using namespace gtsam; -/* ************************************************************************* */ -TEST( testTestableAssertions, optional ) { - typedef boost::optional OptionalScalar; - LieScalar x(1.0); - OptionalScalar ox(x), dummy = boost::none; - EXPECT(assert_equal(ox, ox)); - EXPECT(assert_equal(x, ox)); - EXPECT(assert_equal(dummy, dummy)); -} /* ************************************************************************* */ int main() { TestResult tr; return TestRegistry::runAllTests(tr); } From 063cf72c00482a7c1b36be003efac38742aa1e50 Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Sat, 13 Jul 2019 22:08:29 -0400 Subject: [PATCH 129/160] Revert "Resolved several MSVC compiling errors" -- bring back Lie* This reverts commit 33bda1ef71186bed91067d458d381fb5ff6b328d. --- gtsam.h | 93 +++++++++++++++++++++ gtsam/CMakeLists.txt | 7 -- gtsam/base/Matrix.h | 2 +- gtsam/base/tests/testLieMatrix.cpp | 70 ++++++++++++++++ gtsam/base/tests/testLieScalar.cpp | 64 ++++++++++++++ gtsam/base/tests/testLieVector.cpp | 66 +++++++++++++++ gtsam/base/tests/testTestableAssertions.cpp | 10 +++ 7 files changed, 304 insertions(+), 8 deletions(-) create mode 100644 gtsam/base/tests/testLieMatrix.cpp create mode 100644 gtsam/base/tests/testLieScalar.cpp create mode 100644 gtsam/base/tests/testLieVector.cpp diff --git a/gtsam.h b/gtsam.h index d86887e8b7..826f8472ec 100644 --- a/gtsam.h +++ b/gtsam.h @@ -286,6 +286,99 @@ virtual class GenericValue : gtsam::Value { void serializable() const; }; +#include +class LieScalar { + // Standard constructors + LieScalar(); + LieScalar(double d); + + // Standard interface + double value() const; + + // Testable + void print(string s) const; + bool equals(const gtsam::LieScalar& expected, double tol) const; + + // Group + static gtsam::LieScalar identity(); + gtsam::LieScalar inverse() const; + gtsam::LieScalar compose(const gtsam::LieScalar& p) const; + gtsam::LieScalar between(const gtsam::LieScalar& l2) const; + + // Manifold + size_t dim() const; + gtsam::LieScalar retract(Vector v) const; + Vector localCoordinates(const gtsam::LieScalar& t2) const; + + // Lie group + static gtsam::LieScalar Expmap(Vector v); + static Vector Logmap(const gtsam::LieScalar& p); +}; + +#include +class LieVector { + // Standard constructors + LieVector(); + LieVector(Vector v); + + // Standard interface + Vector vector() const; + + // Testable + void print(string s) const; + bool equals(const gtsam::LieVector& expected, double tol) const; + + // Group + static gtsam::LieVector identity(); + gtsam::LieVector inverse() const; + gtsam::LieVector compose(const gtsam::LieVector& p) const; + gtsam::LieVector between(const gtsam::LieVector& l2) const; + + // Manifold + size_t dim() const; + gtsam::LieVector retract(Vector v) const; + Vector localCoordinates(const gtsam::LieVector& t2) const; + + // Lie group + static gtsam::LieVector Expmap(Vector v); + static Vector Logmap(const gtsam::LieVector& p); + + // enabling serialization functionality + void serialize() const; +}; + +#include +class LieMatrix { + // Standard constructors + LieMatrix(); + LieMatrix(Matrix v); + + // Standard interface + Matrix matrix() const; + + // Testable + void print(string s) const; + bool equals(const gtsam::LieMatrix& expected, double tol) const; + + // Group + static gtsam::LieMatrix identity(); + gtsam::LieMatrix inverse() const; + gtsam::LieMatrix compose(const gtsam::LieMatrix& p) const; + gtsam::LieMatrix between(const gtsam::LieMatrix& l2) const; + + // Manifold + size_t dim() const; + gtsam::LieMatrix retract(Vector v) const; + Vector localCoordinates(const gtsam::LieMatrix & t2) const; + + // Lie group + static gtsam::LieMatrix Expmap(Vector v); + static Vector Logmap(const gtsam::LieMatrix& p); + + // enabling serialization functionality + void serialize() const; +}; + //************************************************************************* // geometry //************************************************************************* diff --git a/gtsam/CMakeLists.txt b/gtsam/CMakeLists.txt index b80f1626c1..b4a33943e5 100644 --- a/gtsam/CMakeLists.txt +++ b/gtsam/CMakeLists.txt @@ -39,12 +39,6 @@ set (excluded_sources #"") set (excluded_headers #"") "${CMAKE_CURRENT_SOURCE_DIR}/slam/serialization.h" - "${CMAKE_CURRENT_SOURCE_DIR}/base/LieMatrix.h" - "${CMAKE_CURRENT_SOURCE_DIR}/base/LieVector.h" - "${CMAKE_CURRENT_SOURCE_DIR}/base/LieScalar.h" - "${CMAKE_CURRENT_SOURCE_DIR}/base/deprecated/LieMatrix.h" - "${CMAKE_CURRENT_SOURCE_DIR}/base/deprecated/LieVector.h" - "${CMAKE_CURRENT_SOURCE_DIR}/base/deprecated/LieScalar.h" ) if(GTSAM_USE_QUATERNIONS) @@ -62,7 +56,6 @@ foreach(subdir ${gtsam_subdirs}) # Build convenience libraries file(GLOB_RECURSE subdir_srcs "${subdir}/*.cpp" "${subdir}/*.h") # Include header files so they show up in Visual Studio list(REMOVE_ITEM subdir_srcs ${excluded_sources}) - list(REMOVE_ITEM subdir_srcs ${excluded_headers}) file(GLOB subdir_test_files "${subdir}/tests/*") list(REMOVE_ITEM subdir_srcs ${subdir_test_files}) # Remove test files from sources compiled into library gtsam_assign_source_folders("${subdir_srcs}") # Create MSVC structure diff --git a/gtsam/base/Matrix.h b/gtsam/base/Matrix.h index 8060ae7f4d..235cd30f35 100644 --- a/gtsam/base/Matrix.h +++ b/gtsam/base/Matrix.h @@ -299,7 +299,7 @@ GTSAM_EXPORT std::pair qr(const Matrix& A); * @param A is the input matrix, and is the output * @param clear_below_diagonal enables zeroing out below diagonal */ -GTSAM_EXPORT void inplace_QR(Matrix& A); +void inplace_QR(Matrix& A); /** * Imperative algorithm for in-place full elimination with diff --git a/gtsam/base/tests/testLieMatrix.cpp b/gtsam/base/tests/testLieMatrix.cpp new file mode 100644 index 0000000000..8c68bf8a00 --- /dev/null +++ b/gtsam/base/tests/testLieMatrix.cpp @@ -0,0 +1,70 @@ +/* ---------------------------------------------------------------------------- + + * 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 testLieMatrix.cpp + * @author Richard Roberts + */ + +#include +#include +#include +#include + +using namespace gtsam; + +GTSAM_CONCEPT_TESTABLE_INST(LieMatrix) +GTSAM_CONCEPT_LIE_INST(LieMatrix) + +/* ************************************************************************* */ +TEST( LieMatrix, construction ) { + Matrix m = (Matrix(2,2) << 1.0,2.0, 3.0,4.0).finished(); + LieMatrix lie1(m), lie2(m); + + EXPECT(traits::GetDimension(m) == 4); + EXPECT(assert_equal(m, lie1.matrix())); + EXPECT(assert_equal(lie1, lie2)); +} + +/* ************************************************************************* */ +TEST( LieMatrix, other_constructors ) { + Matrix init = (Matrix(2,2) << 10.0,20.0, 30.0,40.0).finished(); + LieMatrix exp(init); + double data[] = {10,30,20,40}; + LieMatrix b(2,2,data); + EXPECT(assert_equal(exp, b)); +} + +/* ************************************************************************* */ +TEST(LieMatrix, retract) { + LieMatrix init((Matrix(2,2) << 1.0,2.0,3.0,4.0).finished()); + Vector update = (Vector(4) << 3.0, 4.0, 6.0, 7.0).finished(); + + LieMatrix expected((Matrix(2,2) << 4.0, 6.0, 9.0, 11.0).finished()); + LieMatrix actual = traits::Retract(init,update); + + EXPECT(assert_equal(expected, actual)); + + Vector expectedUpdate = update; + Vector actualUpdate = traits::Local(init,actual); + + EXPECT(assert_equal(expectedUpdate, actualUpdate)); + + Vector expectedLogmap = (Vector(4) << 1, 2, 3, 4).finished(); + Vector actualLogmap = traits::Logmap(LieMatrix((Matrix(2,2) << 1.0, 2.0, 3.0, 4.0).finished())); + EXPECT(assert_equal(expectedLogmap, actualLogmap)); +} + +/* ************************************************************************* */ +int main() { TestResult tr; return TestRegistry::runAllTests(tr); } +/* ************************************************************************* */ + + diff --git a/gtsam/base/tests/testLieScalar.cpp b/gtsam/base/tests/testLieScalar.cpp new file mode 100644 index 0000000000..74f5e0d414 --- /dev/null +++ b/gtsam/base/tests/testLieScalar.cpp @@ -0,0 +1,64 @@ +/* ---------------------------------------------------------------------------- + + * 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 testLieScalar.cpp + * @author Kai Ni + */ + +#include +#include +#include +#include + +using namespace gtsam; + +GTSAM_CONCEPT_TESTABLE_INST(LieScalar) +GTSAM_CONCEPT_LIE_INST(LieScalar) + +const double tol=1e-9; + +//****************************************************************************** +TEST(LieScalar , Concept) { + BOOST_CONCEPT_ASSERT((IsGroup)); + BOOST_CONCEPT_ASSERT((IsManifold)); + BOOST_CONCEPT_ASSERT((IsLieGroup)); +} + +//****************************************************************************** +TEST(LieScalar , Invariants) { + LieScalar lie1(2), lie2(3); + CHECK(check_group_invariants(lie1, lie2)); + CHECK(check_manifold_invariants(lie1, lie2)); +} + +/* ************************************************************************* */ +TEST( testLieScalar, construction ) { + double d = 2.; + LieScalar lie1(d), lie2(d); + + EXPECT_DOUBLES_EQUAL(2., lie1.value(),tol); + EXPECT_DOUBLES_EQUAL(2., lie2.value(),tol); + EXPECT(traits::dimension == 1); + EXPECT(assert_equal(lie1, lie2)); +} + +/* ************************************************************************* */ +TEST( testLieScalar, localCoordinates ) { + LieScalar lie1(1.), lie2(3.); + + Vector1 actual = traits::Local(lie1, lie2); + EXPECT( assert_equal((Vector)(Vector(1) << 2).finished(), actual)); +} + +/* ************************************************************************* */ +int main() { TestResult tr; return TestRegistry::runAllTests(tr); } +/* ************************************************************************* */ diff --git a/gtsam/base/tests/testLieVector.cpp b/gtsam/base/tests/testLieVector.cpp new file mode 100644 index 0000000000..76c4fc490a --- /dev/null +++ b/gtsam/base/tests/testLieVector.cpp @@ -0,0 +1,66 @@ +/* ---------------------------------------------------------------------------- + + * 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 testLieVector.cpp + * @author Alex Cunningham + */ + +#include +#include +#include +#include + +using namespace gtsam; + +GTSAM_CONCEPT_TESTABLE_INST(LieVector) +GTSAM_CONCEPT_LIE_INST(LieVector) + +//****************************************************************************** +TEST(LieVector , Concept) { + BOOST_CONCEPT_ASSERT((IsGroup)); + BOOST_CONCEPT_ASSERT((IsManifold)); + BOOST_CONCEPT_ASSERT((IsLieGroup)); +} + +//****************************************************************************** +TEST(LieVector , Invariants) { + Vector v = Vector3(1.0, 2.0, 3.0); + LieVector lie1(v), lie2(v); + check_manifold_invariants(lie1, lie2); +} + +//****************************************************************************** +TEST( testLieVector, construction ) { + Vector v = Vector3(1.0, 2.0, 3.0); + LieVector lie1(v), lie2(v); + + EXPECT(lie1.dim() == 3); + EXPECT(assert_equal(v, lie1.vector())); + EXPECT(assert_equal(lie1, lie2)); +} + +//****************************************************************************** +TEST( testLieVector, other_constructors ) { + Vector init = Vector2(10.0, 20.0); + LieVector exp(init); + double data[] = { 10, 20 }; + LieVector b(2, data); + EXPECT(assert_equal(exp, b)); +} + +/* ************************************************************************* */ +int main() { + TestResult tr; + return TestRegistry::runAllTests(tr); +} +/* ************************************************************************* */ + diff --git a/gtsam/base/tests/testTestableAssertions.cpp b/gtsam/base/tests/testTestableAssertions.cpp index 3fd2e9003b..305aa7ca93 100644 --- a/gtsam/base/tests/testTestableAssertions.cpp +++ b/gtsam/base/tests/testTestableAssertions.cpp @@ -15,10 +15,20 @@ */ #include +#include #include using namespace gtsam; +/* ************************************************************************* */ +TEST( testTestableAssertions, optional ) { + typedef boost::optional OptionalScalar; + LieScalar x(1.0); + OptionalScalar ox(x), dummy = boost::none; + EXPECT(assert_equal(ox, ox)); + EXPECT(assert_equal(x, ox)); + EXPECT(assert_equal(dummy, dummy)); +} /* ************************************************************************* */ int main() { TestResult tr; return TestRegistry::runAllTests(tr); } From 44faca1b1c74c64f40082c97d23bf41d8059cc87 Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Sat, 13 Jul 2019 22:35:08 -0400 Subject: [PATCH 130/160] Fixed compilation issues for MSVC -- for real this time. --- gtsam/base/Matrix.h | 2 +- gtsam/base/deprecated/LieMatrix.h | 2 +- gtsam/base/deprecated/LieScalar.h | 2 +- gtsam/base/deprecated/LieVector.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gtsam/base/Matrix.h b/gtsam/base/Matrix.h index 235cd30f35..8060ae7f4d 100644 --- a/gtsam/base/Matrix.h +++ b/gtsam/base/Matrix.h @@ -299,7 +299,7 @@ GTSAM_EXPORT std::pair qr(const Matrix& A); * @param A is the input matrix, and is the output * @param clear_below_diagonal enables zeroing out below diagonal */ -void inplace_QR(Matrix& A); +GTSAM_EXPORT void inplace_QR(Matrix& A); /** * Imperative algorithm for in-place full elimination with diff --git a/gtsam/base/deprecated/LieMatrix.h b/gtsam/base/deprecated/LieMatrix.h index 953537bf7d..a3d0a43289 100644 --- a/gtsam/base/deprecated/LieMatrix.h +++ b/gtsam/base/deprecated/LieMatrix.h @@ -29,7 +29,7 @@ namespace gtsam { * we can directly add double, Vector, and Matrix into values now, because of * gtsam::traits. */ -struct GTSAM_EXPORT LieMatrix : public Matrix { +struct LieMatrix : public Matrix { /// @name Constructors /// @{ diff --git a/gtsam/base/deprecated/LieScalar.h b/gtsam/base/deprecated/LieScalar.h index 50ea9d6957..4e9bfb7dbb 100644 --- a/gtsam/base/deprecated/LieScalar.h +++ b/gtsam/base/deprecated/LieScalar.h @@ -28,7 +28,7 @@ namespace gtsam { * we can directly add double, Vector, and Matrix into values now, because of * gtsam::traits. */ - struct GTSAM_EXPORT LieScalar { + struct LieScalar { enum { dimension = 1 }; diff --git a/gtsam/base/deprecated/LieVector.h b/gtsam/base/deprecated/LieVector.h index 60c8103e28..745189c3de 100644 --- a/gtsam/base/deprecated/LieVector.h +++ b/gtsam/base/deprecated/LieVector.h @@ -27,7 +27,7 @@ namespace gtsam { * we can directly add double, Vector, and Matrix into values now, because of * gtsam::traits. */ -struct GTSAM_EXPORT LieVector : public Vector { +struct LieVector : public Vector { enum { dimension = Eigen::Dynamic }; From ffb3043284cfcfd48a84f0a5713236570e0a6d60 Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Sun, 14 Jul 2019 00:14:16 -0400 Subject: [PATCH 131/160] More GTSAM_EXPORT fixes. This allows gtsam_unstable to compile --- gtsam/geometry/Point2.h | 16 ++++++++-------- gtsam/geometry/Point3.h | 20 ++++++++++---------- gtsam/geometry/Pose2.h | 38 +++++++++++++++++++------------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/gtsam/geometry/Point2.h b/gtsam/geometry/Point2.h index 30e902c2b9..718fb2992e 100644 --- a/gtsam/geometry/Point2.h +++ b/gtsam/geometry/Point2.h @@ -37,7 +37,7 @@ namespace gtsam { * @addtogroup geometry * \nosubgrouping */ -class GTSAM_EXPORT Point2 : public Vector2 { +class Point2 : public Vector2 { private: public: @@ -66,10 +66,10 @@ class GTSAM_EXPORT Point2 : public Vector2 { /// @{ /// print with optional string - void print(const std::string& s = "") const; + GTSAM_EXPORT void print(const std::string& s = "") const; /// equals with an tolerance, prints out message if unequal - bool equals(const Point2& q, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const Point2& q, double tol = 1e-9) const; /// @} /// @name Group @@ -86,10 +86,10 @@ class GTSAM_EXPORT Point2 : public Vector2 { Point2 unit() const { return *this/norm(); } /** norm of point, with derivative */ - double norm(OptionalJacobian<1,2> H = boost::none) const; + GTSAM_EXPORT double norm(OptionalJacobian<1,2> H = boost::none) const; /** distance between two points */ - double distance(const Point2& p2, OptionalJacobian<1,2> H1 = boost::none, + GTSAM_EXPORT double distance(const Point2& p2, OptionalJacobian<1,2> H1 = boost::none, OptionalJacobian<1,2> H2 = boost::none) const; /// @} @@ -124,9 +124,9 @@ class GTSAM_EXPORT Point2 : public Vector2 { static Vector2 Logmap(const Point2& p) { return p;} static Point2 Expmap(const Vector2& v) { return Point2(v);} inline double dist(const Point2& p2) const {return distance(p2);} - static boost::optional CircleCircleIntersection(double R_d, double r_d, double tol = 1e-9); - static std::list CircleCircleIntersection(Point2 c1, Point2 c2, boost::optional fh); - static std::list CircleCircleIntersection(Point2 c1, double r1, Point2 c2, double r2, double tol = 1e-9); + GTSAM_EXPORT static boost::optional CircleCircleIntersection(double R_d, double r_d, double tol = 1e-9); + GTSAM_EXPORT static std::list CircleCircleIntersection(Point2 c1, Point2 c2, boost::optional fh); + GTSAM_EXPORT static std::list CircleCircleIntersection(Point2 c1, double r1, Point2 c2, double r2, double tol = 1e-9); /// @} #endif diff --git a/gtsam/geometry/Point3.h b/gtsam/geometry/Point3.h index 215161b3a4..3b2330403c 100644 --- a/gtsam/geometry/Point3.h +++ b/gtsam/geometry/Point3.h @@ -42,7 +42,7 @@ namespace gtsam { * @addtogroup geometry * \nosubgrouping */ -class GTSAM_EXPORT Point3 : public Vector3 { +class Point3 : public Vector3 { public: @@ -63,10 +63,10 @@ class GTSAM_EXPORT Point3 : public Vector3 { /// @{ /** print with optional string */ - void print(const std::string& s = "") const; + GTSAM_EXPORT void print(const std::string& s = "") const; /** equals with an tolerance */ - bool equals(const Point3& p, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const Point3& p, double tol = 1e-9) const; /// @} /// @name Group @@ -80,21 +80,21 @@ class GTSAM_EXPORT Point3 : public Vector3 { /// @{ /** distance between two points */ - double distance(const Point3& p2, OptionalJacobian<1, 3> H1 = boost::none, + GTSAM_EXPORT double distance(const Point3& p2, OptionalJacobian<1, 3> H1 = boost::none, OptionalJacobian<1, 3> H2 = boost::none) const; /** Distance of the point from the origin, with Jacobian */ - double norm(OptionalJacobian<1,3> H = boost::none) const; + GTSAM_EXPORT double norm(OptionalJacobian<1,3> H = boost::none) const; /** normalize, with optional Jacobian */ - Point3 normalized(OptionalJacobian<3, 3> H = boost::none) const; + GTSAM_EXPORT Point3 normalized(OptionalJacobian<3, 3> H = boost::none) const; /** cross product @return this x q */ - Point3 cross(const Point3 &q, OptionalJacobian<3, 3> H_p = boost::none, // + GTSAM_EXPORT 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, OptionalJacobian<1, 3> H_p = boost::none, // + GTSAM_EXPORT double dot(const Point3 &q, OptionalJacobian<1, 3> H_p = boost::none, // OptionalJacobian<1, 3> H_q = boost::none) const; /// @} @@ -130,9 +130,9 @@ class GTSAM_EXPORT Point3 : public Vector3 { static Point3 Expmap(const Vector3& v) { return Point3(v);} inline double dist(const Point3& q) const { return (q - *this).norm(); } Point3 normalize(OptionalJacobian<3, 3> H = boost::none) const { return normalized(H);} - Point3 add(const Point3& q, OptionalJacobian<3, 3> H1 = boost::none, + GTSAM_EXPORT Point3 add(const Point3& q, OptionalJacobian<3, 3> H1 = boost::none, OptionalJacobian<3, 3> H2 = boost::none) const; - Point3 sub(const Point3& q, OptionalJacobian<3, 3> H1 = boost::none, + GTSAM_EXPORT Point3 sub(const Point3& q, OptionalJacobian<3, 3> H1 = boost::none, OptionalJacobian<3, 3> H2 = boost::none) const; /// @} #endif diff --git a/gtsam/geometry/Pose2.h b/gtsam/geometry/Pose2.h index efd6a7f88d..388318827d 100644 --- a/gtsam/geometry/Pose2.h +++ b/gtsam/geometry/Pose2.h @@ -33,7 +33,7 @@ namespace gtsam { * @addtogroup geometry * \nosubgrouping */ -class GTSAM_EXPORT Pose2: public LieGroup { +class Pose2: public LieGroup { public: @@ -97,10 +97,10 @@ class GTSAM_EXPORT Pose2: public LieGroup { /// @{ /** print with optional string */ - void print(const std::string& s = "") const; + GTSAM_EXPORT void print(const std::string& s = "") const; /** assert equality up to a tolerance */ - bool equals(const Pose2& pose, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const Pose2& pose, double tol = 1e-9) const; /// @} /// @name Group @@ -110,7 +110,7 @@ class GTSAM_EXPORT Pose2: public LieGroup { inline static Pose2 identity() { return Pose2(); } /// inverse - Pose2 inverse() const; + GTSAM_EXPORT Pose2 inverse() const; /// compose syntactic sugar inline Pose2 operator*(const Pose2& p2) const { @@ -122,16 +122,16 @@ class GTSAM_EXPORT Pose2: public LieGroup { /// @{ ///Exponential map at identity - create a rotation from canonical coordinates \f$ [T_x,T_y,\theta] \f$ - static Pose2 Expmap(const Vector3& xi, ChartJacobian H = boost::none); + GTSAM_EXPORT static Pose2 Expmap(const Vector3& xi, ChartJacobian H = boost::none); ///Log map at identity - return the canonical coordinates \f$ [T_x,T_y,\theta] \f$ of this rotation - static Vector3 Logmap(const Pose2& p, ChartJacobian H = boost::none); + GTSAM_EXPORT static Vector3 Logmap(const Pose2& p, ChartJacobian H = boost::none); /** * Calculate Adjoint map * Ad_pose is 3*3 matrix that when applied to twist xi \f$ [T_x,T_y,\theta] \f$, returns Ad_pose(xi) */ - Matrix3 AdjointMap() const; + GTSAM_EXPORT Matrix3 AdjointMap() const; /// Apply AdjointMap to twist xi inline Vector3 Adjoint(const Vector3& xi) const { @@ -141,7 +141,7 @@ class GTSAM_EXPORT Pose2: public LieGroup { /** * Compute the [ad(w,v)] operator for SE2 as in [Kobilarov09siggraph], pg 19 */ - static Matrix3 adjointMap(const Vector3& v); + GTSAM_EXPORT static Matrix3 adjointMap(const Vector3& v); /** * Action of the adjointMap on a Lie-algebra vector y, with optional derivatives @@ -177,15 +177,15 @@ class GTSAM_EXPORT Pose2: public LieGroup { } /// Derivative of Expmap - static Matrix3 ExpmapDerivative(const Vector3& v); + GTSAM_EXPORT static Matrix3 ExpmapDerivative(const Vector3& v); /// Derivative of Logmap - static Matrix3 LogmapDerivative(const Pose2& v); + GTSAM_EXPORT static Matrix3 LogmapDerivative(const Pose2& v); // Chart at origin, depends on compile-time flag SLOW_BUT_CORRECT_EXPMAP struct ChartAtOrigin { - static Pose2 Retract(const Vector3& v, ChartJacobian H = boost::none); - static Vector3 Local(const Pose2& r, ChartJacobian H = boost::none); + GTSAM_EXPORT static Pose2 Retract(const Vector3& v, ChartJacobian H = boost::none); + GTSAM_EXPORT static Vector3 Local(const Pose2& r, ChartJacobian H = boost::none); }; using LieGroup::inverse; // version with derivative @@ -195,12 +195,12 @@ class GTSAM_EXPORT Pose2: public LieGroup { /// @{ /** Return point coordinates in pose coordinate frame */ - Point2 transformTo(const Point2& point, + GTSAM_EXPORT Point2 transformTo(const Point2& point, OptionalJacobian<2, 3> Dpose = boost::none, OptionalJacobian<2, 2> Dpoint = boost::none) const; /** Return point coordinates in global frame */ - Point2 transformFrom(const Point2& point, + GTSAM_EXPORT Point2 transformFrom(const Point2& point, OptionalJacobian<2, 3> Dpose = boost::none, OptionalJacobian<2, 2> Dpoint = boost::none) const; @@ -233,14 +233,14 @@ class GTSAM_EXPORT Pose2: public LieGroup { inline const Rot2& rotation() const { return r_; } //// return transformation matrix - Matrix3 matrix() const; + GTSAM_EXPORT Matrix3 matrix() const; /** * Calculate bearing to a landmark * @param point 2D location of landmark * @return 2D rotation \f$ \in SO(2) \f$ */ - Rot2 bearing(const Point2& point, + GTSAM_EXPORT Rot2 bearing(const Point2& point, OptionalJacobian<1, 3> H1=boost::none, OptionalJacobian<1, 2> H2=boost::none) const; /** @@ -248,7 +248,7 @@ class GTSAM_EXPORT Pose2: public LieGroup { * @param point SO(2) location of other pose * @return 2D rotation \f$ \in SO(2) \f$ */ - Rot2 bearing(const Pose2& pose, + GTSAM_EXPORT Rot2 bearing(const Pose2& pose, OptionalJacobian<1, 3> H1=boost::none, OptionalJacobian<1, 3> H2=boost::none) const; /** @@ -256,7 +256,7 @@ class GTSAM_EXPORT Pose2: public LieGroup { * @param point 2D location of landmark * @return range (double) */ - double range(const Point2& point, + GTSAM_EXPORT double range(const Point2& point, OptionalJacobian<1, 3> H1=boost::none, OptionalJacobian<1, 2> H2=boost::none) const; @@ -265,7 +265,7 @@ class GTSAM_EXPORT Pose2: public LieGroup { * @param point 2D location of other pose * @return range (double) */ - double range(const Pose2& point, + GTSAM_EXPORT double range(const Pose2& point, OptionalJacobian<1, 3> H1=boost::none, OptionalJacobian<1, 3> H2=boost::none) const; From e28fb84fef494109a1a928417da369f408b5b4eb Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Mon, 15 Jul 2019 11:43:54 -0400 Subject: [PATCH 132/160] Added document explaining GTSAM_EXPORT usage rules --- Using-GTSAM-EXPORT.md | 41 +++++++++++++++++++ gtsam/geometry/SO3.h | 12 +++--- gtsam/geometry/tests/testCalibratedCamera.cpp | 2 +- gtsam/geometry/tests/testPinholePose.cpp | 4 +- gtsam/geometry/tests/testQuaternion.cpp | 8 ++-- gtsam_unstable/geometry/Event.h | 6 +-- gtsam_unstable/geometry/Similarity3.h | 38 ++++++++--------- 7 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 Using-GTSAM-EXPORT.md diff --git a/Using-GTSAM-EXPORT.md b/Using-GTSAM-EXPORT.md new file mode 100644 index 0000000000..6c8480d5d8 --- /dev/null +++ b/Using-GTSAM-EXPORT.md @@ -0,0 +1,41 @@ +# Using GTSAM_EXPORT + +To create a DLL in windows, the `GTSAM_EXPORT` keyword has been created and needs to be applied to different methods and classes in the code to expose this code outside of the DLL. However, there are several intricacies that make this more difficult than it sounds. In general, if you follow the following three rules, GTSAM_EXPORT should work properly. The rest of the document also describes (1) the common error message encountered when you are not following these rules and (2) the reasoning behind these usage rules. + +## Usage Rules +1. Put `GTSAM_EXPORT` in front of any function that you want exported in the DLL _if and only if_ that function is defined in a .cpp file, not just a .h file. +2. Use `GTSAM_EXPORT` in a class definition (i.e. `class GSTAM_EXPORT MyClass {...}`) only if: + * At least one of the functions inside that class is defined in a .cpp file and not just the .h file. + * You can `GTSAM_EXPORT` any class it inherits from as well. (Note that this implictly requires the class does not derive from a "header-only" class. Note that Eigen is a "header-only" library, so if your class derives from Eigen, _do not_ use `GTSAM_EXPORT` in the class definition!) +3. If you have defined a class using `GTSAM_EXPORT`, do not use `GTSAM_EXPORT` in any of its individual function declarations. (Note that you _can_ put `GTSAM_EXPORT` in the definition of individual functions within a class as long as you don't put `GTSAM_EXPORT` in the class definition.) + +## When is GTSAM_EXPORT being used incorrectly +Unfortunately, using `GTSAM_EXPORT` incorrectly often does not cause a compiler or linker error in the library that is being compiled, but only when you try to use that DLL in a different library. For example, an error in gtsam/base will often show up when compiling the check_base_program or the MATLAB wrapper, but not when compiling/linking gtsam itself. The most common errors will say something like: + +``` +Error LNK2019 unresolved external symbol "public: void __cdecl gtsam::SO3::print(class std::basic_string,class std::allocator > const &)const " (?print@SO3@gtsam@@QEBAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "public: static void __cdecl gtsam::Testable::Print(class gtsam::SO3 const &,class std::basic_string,class std::allocator > const &)" (?Print@?$Testable@VSO3@gtsam@@@gtsam@@SAXAEBVSO3@2@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) check_geometry_program C:\AFIT\lib\gtsam\build\gtsam\geometry\tests\testSO3.obj +``` + +Sorry for the long error message there, but I want to quickly decode it to help understand what is going on. First, there is an unresolved symbol `gtsam::SO3::print`. This can occur because _either_ `GTSAM_EXPORT` was not added to the print function definition when it should have been, _OR_ because `GTSAM_EXPORT` was added to the print function definition when it is fully declared in the header. This error was detected while compiling `check_geometry_program` and pulling in `...\testSO3.obj`. Specifically, within the function call `gtsam::Testabl::Print (...)`. Note that this error did _not_ occur when compiling the library that actually has SO3 inside of it. + +## But Why? +To me, the behavior of GTSAM_EXPORT was very baffling. However, I believe it can be explained by understanding the rules that MSVC operates under. + +First, we need to understand exactly what `GTSAM_EXPORT` does. When you use `GTSAM_EXPORT` on a function (or class) definition, it actually does two things. First, when you are building the library, it turns into a "dllexport" command, telling the compiler that this function should go into the DLL and be visible externally. When you pull in that same header for a different library, it switches to a "dllimport" command, telling the compiler it should find this function in a DLL somewhere. This leads to the first rule the compiler uses. (Note that I say "compiler rules" when the rules may actually be in the linker, but I am conflating the two terms here when I speak of the "compiler rules".) + +***Compiler Rule #1*** If a `dllimport` command is used in defining a function or class, that function or class _must_ be found in a DLL. + +Rule #1 doesn't seem very bad, until you combine it with rule #2 + +***Compiler Rule #2*** Anything defined in a header file is not included in a DLL. + +When these two rules are combined, you get some very confusing results. For example, a class which is completely defined in a header (e.g. LieMatrix) cannot use `GTSAM_EXPORT` in its definition. If LieMatrix is defined with `GTSAM_EXPORT`, then the compiler _must_ find LieMatrix in a DLL. Because LieMatrix is a header-only class, however, it can't find it, leading to a very confusing "I can't find this symbol" type of error. Note that the linker says it can't find the symbol even though the compiler found the header file that completely defines the class. + +This leads to another compiler rule that finishes explaining the usage rules proposed above. + +***Compiler Rule #3*** If a class is defined with `GTSAM_EXPORT`, then any classes it inherits from must be definable with a dllexport command as well. + +One of the most immediate results of this compiler rule is that any class that inherits from an Eigen class _cannot_ be defined with `GTSAM_EXPORT`. Because Eigen is a header-only class, compiler rule #2 prohibits any Eigen class from being defined with a "dllexport" command. Therefore, no Eigen inherited classes can use `GTSAM_EXPORT` either. (Note that individual functions with an inherited class can be "exported." Just not the entire class.) + +## Conclusion +Hopefully, this little document clarifies when `GTSAM_EXPORT` should and should not be used whenever future GTSAM code is being written. Following the usage rules above, I have been able to get all of the libraries, together with their test and wrapping libraries, to compile/link successfully. \ No newline at end of file diff --git a/gtsam/geometry/SO3.h b/gtsam/geometry/SO3.h index 3b27d45c51..a4f6861cc5 100644 --- a/gtsam/geometry/SO3.h +++ b/gtsam/geometry/SO3.h @@ -62,13 +62,13 @@ class SO3: public Matrix3, public LieGroup { } /// Static, named constructor TODO think about relation with above - static SO3 AxisAngle(const Vector3& axis, double theta); + GTSAM_EXPORT static SO3 AxisAngle(const Vector3& axis, double theta); /// @} /// @name Testable /// @{ - void print(const std::string& s) const; + GTSAM_EXPORT void print(const std::string& s) const; bool equals(const SO3 & R, double tol) const { return equal_with_abs_tol(*this, R, tol); @@ -96,19 +96,19 @@ class SO3: public Matrix3, public LieGroup { * Exponential map at identity - create a rotation from canonical coordinates * \f$ [R_x,R_y,R_z] \f$ using Rodrigues' formula */ - static SO3 Expmap(const Vector3& omega, ChartJacobian H = boost::none); + GTSAM_EXPORT static SO3 Expmap(const Vector3& omega, ChartJacobian H = boost::none); /// Derivative of Expmap - static Matrix3 ExpmapDerivative(const Vector3& omega); + GTSAM_EXPORT static Matrix3 ExpmapDerivative(const Vector3& omega); /** * Log map at identity - returns the canonical coordinates * \f$ [R_x,R_y,R_z] \f$ of this rotation */ - static Vector3 Logmap(const SO3& R, ChartJacobian H = boost::none); + GTSAM_EXPORT static Vector3 Logmap(const SO3& R, ChartJacobian H = boost::none); /// Derivative of Logmap - static Matrix3 LogmapDerivative(const Vector3& omega); + GTSAM_EXPORT static Matrix3 LogmapDerivative(const Vector3& omega); Matrix3 AdjointMap() const { return *this; diff --git a/gtsam/geometry/tests/testCalibratedCamera.cpp b/gtsam/geometry/tests/testCalibratedCamera.cpp index f5f824d059..1982b8f505 100644 --- a/gtsam/geometry/tests/testCalibratedCamera.cpp +++ b/gtsam/geometry/tests/testCalibratedCamera.cpp @@ -206,7 +206,7 @@ TEST( CalibratedCamera, DBackprojectFromCamera) static Point3 backproject(const Pose3& pose, const Point2& point, const double& depth) { return CalibratedCamera(pose).backproject(point, depth); } -TEST( PinholePose, Dbackproject) +TEST( PinholePose, DbackprojectCalibCamera) { Matrix36 Dpose; Matrix31 Ddepth; diff --git a/gtsam/geometry/tests/testPinholePose.cpp b/gtsam/geometry/tests/testPinholePose.cpp index 05d48f4ccd..1cf2f4a3fd 100644 --- a/gtsam/geometry/tests/testPinholePose.cpp +++ b/gtsam/geometry/tests/testPinholePose.cpp @@ -58,6 +58,7 @@ TEST( PinholePose, constructor) } //****************************************************************************** +/* Already in testPinholeCamera??? TEST(PinholeCamera, Pose) { Matrix actualH; @@ -69,6 +70,7 @@ TEST(PinholeCamera, Pose) { Matrix numericalH = numericalDerivative11(f,camera); EXPECT(assert_equal(numericalH, actualH, 1e-9)); } +*/ /* ************************************************************************* */ TEST( PinholePose, lookat) @@ -207,7 +209,7 @@ static Point3 backproject(const Pose3& pose, const Cal3_S2& cal, return Camera(pose, cal.vector()).backproject(p, depth); } -TEST( PinholePose, Dbackproject) +TEST( PinholePose, DbackprojectRegCamera) { Matrix36 Dpose; Matrix31 Ddepth; diff --git a/gtsam/geometry/tests/testQuaternion.cpp b/gtsam/geometry/tests/testQuaternion.cpp index 843dc6cc18..e862b94adf 100644 --- a/gtsam/geometry/tests/testQuaternion.cpp +++ b/gtsam/geometry/tests/testQuaternion.cpp @@ -81,13 +81,14 @@ TEST(Quaternion , Compose) { } //****************************************************************************** -Vector3 z_axis(0, 0, 1); -Q id(Eigen::AngleAxisd(0, z_axis)); -Q R1(Eigen::AngleAxisd(1, z_axis)); +Vector3 Q_z_axis(0, 0, 1); +Q id(Eigen::AngleAxisd(0, Q_z_axis)); +Q R1(Eigen::AngleAxisd(1, Q_z_axis)); Q R2(Eigen::AngleAxisd(2, Vector3(0, 1, 0))); //****************************************************************************** TEST(Quaternion , Between) { + Vector3 z_axis(0, 0, 1); Q q1(Eigen::AngleAxisd(0.2, z_axis)); Q q2(Eigen::AngleAxisd(0.1, z_axis)); @@ -98,6 +99,7 @@ TEST(Quaternion , Between) { //****************************************************************************** TEST(Quaternion , Inverse) { + Vector3 z_axis(0, 0, 1); Q q1(Eigen::AngleAxisd(0.1, z_axis)); Q expected(Eigen::AngleAxisd(-0.1, z_axis)); diff --git a/gtsam_unstable/geometry/Event.h b/gtsam_unstable/geometry/Event.h index d9bacd106c..b221641175 100644 --- a/gtsam_unstable/geometry/Event.h +++ b/gtsam_unstable/geometry/Event.h @@ -60,10 +60,10 @@ class Event { } /** print with optional string */ - void print(const std::string& s = "") const; + GTSAM_EXPORT void print(const std::string& s = "") const; /** equals with an tolerance */ - bool equals(const Event& other, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const Event& other, double tol = 1e-9) const; /// Updates a with tangent space delta inline Event retract(const Vector4& v) const { @@ -94,6 +94,6 @@ class Event { // Define GTSAM traits template<> -struct GTSAM_EXPORT traits : internal::Manifold {}; +struct traits : internal::Manifold {}; } //\ namespace gtsam diff --git a/gtsam_unstable/geometry/Similarity3.h b/gtsam_unstable/geometry/Similarity3.h index f7ba53d874..a3d80c1d00 100644 --- a/gtsam_unstable/geometry/Similarity3.h +++ b/gtsam_unstable/geometry/Similarity3.h @@ -49,32 +49,32 @@ class Similarity3: public LieGroup { /// @{ /// Default constructor - Similarity3(); + GTSAM_EXPORT Similarity3(); /// Construct pure scaling - Similarity3(double s); + GTSAM_EXPORT Similarity3(double s); /// Construct from GTSAM types - Similarity3(const Rot3& R, const Point3& t, double s); + GTSAM_EXPORT Similarity3(const Rot3& R, const Point3& t, double s); /// Construct from Eigen types - Similarity3(const Matrix3& R, const Vector3& t, double s); + GTSAM_EXPORT Similarity3(const Matrix3& R, const Vector3& t, double s); /// Construct from matrix [R t; 0 s^-1] - Similarity3(const Matrix4& T); + GTSAM_EXPORT Similarity3(const Matrix4& T); /// @} /// @name Testable /// @{ /// Compare with tolerance - bool equals(const Similarity3& sim, double tol) const; + GTSAM_EXPORT bool equals(const Similarity3& sim, double tol) const; /// Exact equality - bool operator==(const Similarity3& other) const; + GTSAM_EXPORT bool operator==(const Similarity3& other) const; /// Print with optional string - void print(const std::string& s) const; + GTSAM_EXPORT void print(const std::string& s) const; GTSAM_EXPORT friend std::ostream &operator<<(std::ostream &os, const Similarity3& p); @@ -83,25 +83,25 @@ class Similarity3: public LieGroup { /// @{ /// Return an identity transform - static Similarity3 identity(); + GTSAM_EXPORT static Similarity3 identity(); /// Composition - Similarity3 operator*(const Similarity3& T) const; + GTSAM_EXPORT Similarity3 operator*(const Similarity3& T) const; /// Return the inverse - Similarity3 inverse() const; + GTSAM_EXPORT Similarity3 inverse() const; /// @} /// @name Group action on Point3 /// @{ /// Action on a point p is s*(R*p+t) - Point3 transformFrom(const Point3& p, // + GTSAM_EXPORT Point3 transformFrom(const Point3& p, // OptionalJacobian<3, 7> H1 = boost::none, // OptionalJacobian<3, 3> H2 = boost::none) const; /** syntactic sugar for transformFrom */ - Point3 operator*(const Point3& p) const; + GTSAM_EXPORT Point3 operator*(const Point3& p) const; /// @} /// @name Lie Group @@ -110,12 +110,12 @@ class Similarity3: public LieGroup { /** Log map at the identity * \f$ [R_x,R_y,R_z, t_x, t_y, t_z, \lambda] \f$ */ - static Vector7 Logmap(const Similarity3& s, // + GTSAM_EXPORT static Vector7 Logmap(const Similarity3& s, // OptionalJacobian<7, 7> Hm = boost::none); /** Exponential map at the identity */ - static Similarity3 Expmap(const Vector7& v, // + GTSAM_EXPORT static Similarity3 Expmap(const Vector7& v, // OptionalJacobian<7, 7> Hm = boost::none); /// Chart at the origin @@ -136,17 +136,17 @@ class Similarity3: public LieGroup { * @return 4*4 element of Lie algebra that can be exponentiated * TODO(frank): rename to Hat, make part of traits */ - static Matrix4 wedge(const Vector7& xi); + GTSAM_EXPORT static Matrix4 wedge(const Vector7& xi); /// Project from one tangent space to another - Matrix7 AdjointMap() const; + GTSAM_EXPORT Matrix7 AdjointMap() const; /// @} /// @name Standard interface /// @{ /// Calculate 4*4 matrix group equivalent - const Matrix4 matrix() const; + GTSAM_EXPORT const Matrix4 matrix() const; /// Return a GTSAM rotation const Rot3& rotation() const { @@ -165,7 +165,7 @@ class Similarity3: public LieGroup { /// Convert to a rigid body pose (R, s*t) /// TODO(frank): why is this here? Red flag! Definitely don't have it as a cast. - operator Pose3() const; + GTSAM_EXPORT operator Pose3() const; /// Dimensionality of tangent space = 7 DOF - used to autodetect sizes inline static size_t Dim() { From a49ed61a58ed0d0dce076a11565de076d98bd09d Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Mon, 15 Jul 2019 20:19:00 -0400 Subject: [PATCH 133/160] Reducing errors in check* libraries when compiling --- gtsam/inference/tests/testKey.cpp | 4 ++-- gtsam/inference/tests/testLabeledSymbol.cpp | 4 ++-- gtsam/inference/tests/testSymbol.cpp | 4 ++-- timing/timeGaussianFactorGraph.cpp | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gtsam/inference/tests/testKey.cpp b/gtsam/inference/tests/testKey.cpp index fcdb5709b0..a602585814 100644 --- a/gtsam/inference/tests/testKey.cpp +++ b/gtsam/inference/tests/testKey.cpp @@ -76,7 +76,7 @@ TEST(Key, ChrTest) { /* ************************************************************************* */ // A custom (nonsensical) formatter. -string myFormatter(Key key) { +string keyMyFormatter(Key key) { return "special"; } @@ -91,7 +91,7 @@ TEST(Key, Formatting) { // use key_formatter with a function pointer stringstream ss2; - ss2 << key_formatter(myFormatter) << StreamedKey(key); + ss2 << key_formatter(keyMyFormatter) << StreamedKey(key); EXPECT("special" == ss2.str()); // use key_formatter with a function object. diff --git a/gtsam/inference/tests/testLabeledSymbol.cpp b/gtsam/inference/tests/testLabeledSymbol.cpp index 2a56b39c21..4ac171c73d 100644 --- a/gtsam/inference/tests/testLabeledSymbol.cpp +++ b/gtsam/inference/tests/testLabeledSymbol.cpp @@ -81,7 +81,7 @@ TEST(LabeledSymbol, ChrTest) { /* ************************************************************************* */ // A custom (nonsensical) formatter. -string myFormatter(Key key) { +string labeledSymbolMyFormatter(Key key) { return "special"; } @@ -90,7 +90,7 @@ TEST(LabeledSymbol, Formatting) { // use key_formatter with a function pointer stringstream ss2; - ss2 << key_formatter(myFormatter) << symbol; + ss2 << key_formatter(labeledSymbolMyFormatter) << symbol; EXPECT("special" == ss2.str()); // use key_formatter with a function object. diff --git a/gtsam/inference/tests/testSymbol.cpp b/gtsam/inference/tests/testSymbol.cpp index 43a0e219a1..bedd690441 100644 --- a/gtsam/inference/tests/testSymbol.cpp +++ b/gtsam/inference/tests/testSymbol.cpp @@ -23,7 +23,7 @@ using namespace gtsam; /* ************************************************************************* */ // A custom (nonsensical) formatter. -string myFormatter(Key key) { +string symbolMyFormatter(Key key) { return "special"; } @@ -32,7 +32,7 @@ TEST(Symbol, Formatting) { // use key_formatter with a function pointer stringstream ss2; - ss2 << key_formatter(myFormatter) << symbol; + ss2 << key_formatter(symbolMyFormatter) << symbol; EXPECT("special" == ss2.str()); // use key_formatter with a function object. diff --git a/timing/timeGaussianFactorGraph.cpp b/timing/timeGaussianFactorGraph.cpp index 1efdb95427..3258edb493 100644 --- a/timing/timeGaussianFactorGraph.cpp +++ b/timing/timeGaussianFactorGraph.cpp @@ -153,13 +153,13 @@ TEST(timeGaussianFactorGraph, linearTime) // Switch to 100*100 grid = 10K poses // 1879: 15.6498 15.3851 15.5279 -int size = 100; +int grid_size = 100; /* ************************************************************************* */ TEST(timeGaussianFactorGraph, planar_old) { cout << "Timing planar - original version" << endl; - double time = timePlanarSmoother(size); + double time = timePlanarSmoother(grid_size); cout << "timeGaussianFactorGraph : " << time << endl; //DOUBLES_EQUAL(5.97,time,0.1); } @@ -168,7 +168,7 @@ TEST(timeGaussianFactorGraph, planar_old) TEST(timeGaussianFactorGraph, planar_new) { cout << "Timing planar - new version" << endl; - double time = timePlanarSmoother(size, false); + double time = timePlanarSmoother(grid_size, false); cout << "timeGaussianFactorGraph : " << time << endl; //DOUBLES_EQUAL(5.97,time,0.1); } @@ -177,7 +177,7 @@ TEST(timeGaussianFactorGraph, planar_new) TEST(timeGaussianFactorGraph, planar_eliminate_old) { cout << "Timing planar Eliminate - original version" << endl; - double time = timePlanarSmootherEliminate(size); + double time = timePlanarSmootherEliminate(grid_size); cout << "timeGaussianFactorGraph : " << time << endl; //DOUBLES_EQUAL(5.97,time,0.1); } @@ -186,7 +186,7 @@ TEST(timeGaussianFactorGraph, planar_eliminate_old) TEST(timeGaussianFactorGraph, planar_eliminate_new) { cout << "Timing planar Eliminate - new version" << endl; - double time = timePlanarSmootherEliminate(size, false); + double time = timePlanarSmootherEliminate(grid_size, false); cout << "timeGaussianFactorGraph : " << time << endl; //DOUBLES_EQUAL(5.97,time,0.1); } From 393e9edf18c7f72512efee8f38021ab73c703001 Mon Sep 17 00:00:00 2001 From: Clark Taylor Date: Mon, 15 Jul 2019 22:22:47 -0400 Subject: [PATCH 134/160] Updated Using GTSAM_EXPORT document --- Using-GTSAM-EXPORT.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Using-GTSAM-EXPORT.md b/Using-GTSAM-EXPORT.md index 6c8480d5d8..41eccc178d 100644 --- a/Using-GTSAM-EXPORT.md +++ b/Using-GTSAM-EXPORT.md @@ -3,9 +3,9 @@ To create a DLL in windows, the `GTSAM_EXPORT` keyword has been created and needs to be applied to different methods and classes in the code to expose this code outside of the DLL. However, there are several intricacies that make this more difficult than it sounds. In general, if you follow the following three rules, GTSAM_EXPORT should work properly. The rest of the document also describes (1) the common error message encountered when you are not following these rules and (2) the reasoning behind these usage rules. ## Usage Rules -1. Put `GTSAM_EXPORT` in front of any function that you want exported in the DLL _if and only if_ that function is defined in a .cpp file, not just a .h file. +1. Put `GTSAM_EXPORT` in front of any function that you want exported in the DLL _if and only if_ that function is declared in a .cpp file, not just a .h file. 2. Use `GTSAM_EXPORT` in a class definition (i.e. `class GSTAM_EXPORT MyClass {...}`) only if: - * At least one of the functions inside that class is defined in a .cpp file and not just the .h file. + * At least one of the functions inside that class is declared in a .cpp file and not just the .h file. * You can `GTSAM_EXPORT` any class it inherits from as well. (Note that this implictly requires the class does not derive from a "header-only" class. Note that Eigen is a "header-only" library, so if your class derives from Eigen, _do not_ use `GTSAM_EXPORT` in the class definition!) 3. If you have defined a class using `GTSAM_EXPORT`, do not use `GTSAM_EXPORT` in any of its individual function declarations. (Note that you _can_ put `GTSAM_EXPORT` in the definition of individual functions within a class as long as you don't put `GTSAM_EXPORT` in the class definition.) @@ -16,26 +16,22 @@ Unfortunately, using `GTSAM_EXPORT` incorrectly often does not cause a compiler Error LNK2019 unresolved external symbol "public: void __cdecl gtsam::SO3::print(class std::basic_string,class std::allocator > const &)const " (?print@SO3@gtsam@@QEBAXAEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "public: static void __cdecl gtsam::Testable::Print(class gtsam::SO3 const &,class std::basic_string,class std::allocator > const &)" (?Print@?$Testable@VSO3@gtsam@@@gtsam@@SAXAEBVSO3@2@AEBV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) check_geometry_program C:\AFIT\lib\gtsam\build\gtsam\geometry\tests\testSO3.obj ``` -Sorry for the long error message there, but I want to quickly decode it to help understand what is going on. First, there is an unresolved symbol `gtsam::SO3::print`. This can occur because _either_ `GTSAM_EXPORT` was not added to the print function definition when it should have been, _OR_ because `GTSAM_EXPORT` was added to the print function definition when it is fully declared in the header. This error was detected while compiling `check_geometry_program` and pulling in `...\testSO3.obj`. Specifically, within the function call `gtsam::Testabl::Print (...)`. Note that this error did _not_ occur when compiling the library that actually has SO3 inside of it. +Let's analyze this error statement. First, there is an unresolved symbol `gtsam::SO3::print`. This can occur because _either_ `GTSAM_EXPORT` was not added to the print function definition when it should have been, _OR_ because `GTSAM_EXPORT` was added to the print function definition when it is fully declared in the header. This error was detected while compiling `check_geometry_program` and pulling in `...\testSO3.obj`. Specifically, within the function call `gtsam::Testable::Print (...)`. Note that this error did _not_ occur when compiling the library that actually has SO3 inside of it. ## But Why? -To me, the behavior of GTSAM_EXPORT was very baffling. However, I believe it can be explained by understanding the rules that MSVC operates under. +I believe that how the compiler/linker interacts with GTSAM_EXPORT can be explained by understanding the rules that MSVC operates under. -First, we need to understand exactly what `GTSAM_EXPORT` does. When you use `GTSAM_EXPORT` on a function (or class) definition, it actually does two things. First, when you are building the library, it turns into a "dllexport" command, telling the compiler that this function should go into the DLL and be visible externally. When you pull in that same header for a different library, it switches to a "dllimport" command, telling the compiler it should find this function in a DLL somewhere. This leads to the first rule the compiler uses. (Note that I say "compiler rules" when the rules may actually be in the linker, but I am conflating the two terms here when I speak of the "compiler rules".) +But first, we need to understand exactly what `GTSAM_EXPORT` is. `GTSAM_EXPORT` is a `#define` macro that is created by CMAKE when GTSAM is being compiled on a Windows machine. Inside the GTSAM project, GTSAM export will be set to a "dllexport" command. A "dllexport" command tells the compiler that this function should go into the DLL and be visible externally. In any other library, `GTSAM_EXPORT` will be set to a "dllimport" command, telling the linker it should find this function in a DLL somewhere. This leads to the first rule the compiler uses. (Note that I say "compiler rules" when the rules may actually be in the linker, but I am conflating the two terms here when I speak of the "compiler rules".) ***Compiler Rule #1*** If a `dllimport` command is used in defining a function or class, that function or class _must_ be found in a DLL. Rule #1 doesn't seem very bad, until you combine it with rule #2 -***Compiler Rule #2*** Anything defined in a header file is not included in a DLL. +***Compiler Rule #2*** Anything declared in a header file is not included in a DLL. When these two rules are combined, you get some very confusing results. For example, a class which is completely defined in a header (e.g. LieMatrix) cannot use `GTSAM_EXPORT` in its definition. If LieMatrix is defined with `GTSAM_EXPORT`, then the compiler _must_ find LieMatrix in a DLL. Because LieMatrix is a header-only class, however, it can't find it, leading to a very confusing "I can't find this symbol" type of error. Note that the linker says it can't find the symbol even though the compiler found the header file that completely defines the class. -This leads to another compiler rule that finishes explaining the usage rules proposed above. - -***Compiler Rule #3*** If a class is defined with `GTSAM_EXPORT`, then any classes it inherits from must be definable with a dllexport command as well. - -One of the most immediate results of this compiler rule is that any class that inherits from an Eigen class _cannot_ be defined with `GTSAM_EXPORT`. Because Eigen is a header-only class, compiler rule #2 prohibits any Eigen class from being defined with a "dllexport" command. Therefore, no Eigen inherited classes can use `GTSAM_EXPORT` either. (Note that individual functions with an inherited class can be "exported." Just not the entire class.) +Also note that when a class that you want to export inherits from another class that is not exportable, this can cause significant issues. According to this [MSVC Warning page](https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=vs-2019), it may not strictly be a rule, but we have seen several linker errors when a class that is defined with `GTSAM_EXPORT` extended an Eigen class. In general, it appears that any inheritance of non-exportable class by an exportable class is a bad idea. ## Conclusion -Hopefully, this little document clarifies when `GTSAM_EXPORT` should and should not be used whenever future GTSAM code is being written. Following the usage rules above, I have been able to get all of the libraries, together with their test and wrapping libraries, to compile/link successfully. \ No newline at end of file +Hopefully, this little document clarifies when `GTSAM_EXPORT` should and should not be used whenever future GTSAM code is being written. Following the usage rules above, we have been able to get all of the libraries, together with their test and wrapping libraries, to compile/link successfully. \ No newline at end of file From 52b0579a9b0a6ed82f2e0d20a5696126e29c716b Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 18 Jul 2019 08:22:41 +0200 Subject: [PATCH 135/160] Consistent dllexport.h across gtsam & gtsam_unstable --- gtsam_unstable/CMakeLists.txt | 6 ++++ gtsam_unstable/base/Dummy.h | 2 +- gtsam_unstable/base/dllexport.h | 36 ------------------- gtsam_unstable/discrete/Constraint.h | 2 +- gtsam_unstable/dynamics/PoseRTV.h | 2 +- gtsam_unstable/geometry/BearingS2.h | 2 +- gtsam_unstable/geometry/Pose3Upright.h | 2 +- gtsam_unstable/geometry/SimWall2D.h | 2 +- .../ConcurrentFilteringAndSmoothing.h | 2 +- gtsam_unstable/nonlinear/FixedLagSmoother.h | 2 +- gtsam_unstable/nonlinear/LinearizedFactor.h | 2 +- gtsam_unstable/slam/AHRS.h | 2 +- gtsam_unstable/slam/DummyFactor.h | 2 +- gtsam_unstable/slam/Mechanization_bRn2.h | 2 +- gtsam_unstable/slam/RelativeElevationFactor.h | 2 +- gtsam_unstable/slam/SmartRangeFactor.h | 2 +- 16 files changed, 20 insertions(+), 50 deletions(-) delete mode 100644 gtsam_unstable/base/dllexport.h diff --git a/gtsam_unstable/CMakeLists.txt b/gtsam_unstable/CMakeLists.txt index 65ba4848d9..53ba83fade 100644 --- a/gtsam_unstable/CMakeLists.txt +++ b/gtsam_unstable/CMakeLists.txt @@ -44,6 +44,12 @@ foreach(subdir ${gtsam_unstable_subdirs}) add_subdirectory(${subdir}) endforeach(subdir) +# dllexport.h +set(library_name GTSAM_UNSTABLE) # For substitution in dllexport.h.in +configure_file("${GTSAM_SOURCE_DIR}/cmake/dllexport.h.in" "dllexport.h") +list(APPEND gtsam_unstable_srcs "${PROJECT_BINARY_DIR}/dllexport.h") +install(FILES "${PROJECT_BINARY_DIR}/dllexport.h" DESTINATION include/gtsam_unstable) + # assemble gtsam_unstable components set(gtsam_unstable_srcs ${base_srcs} diff --git a/gtsam_unstable/base/Dummy.h b/gtsam_unstable/base/Dummy.h index 14b4b48549..a2f544de58 100644 --- a/gtsam_unstable/base/Dummy.h +++ b/gtsam_unstable/base/Dummy.h @@ -18,7 +18,7 @@ */ #include -#include +#include #include namespace gtsam { diff --git a/gtsam_unstable/base/dllexport.h b/gtsam_unstable/base/dllexport.h deleted file mode 100644 index 903996db4a..0000000000 --- a/gtsam_unstable/base/dllexport.h +++ /dev/null @@ -1,36 +0,0 @@ -/* ---------------------------------------------------------------------------- - - * 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 dllexport.h - * @brief Symbols for exporting classes and methods from DLLs - * @author Richard Roberts - * @date Mar 9, 2013 - */ - -#ifdef _WIN32 -# ifdef GTSAM_UNSTABLE_EXPORTS -# define GTSAM_UNSTABLE_EXPORT __declspec(dllexport) -# define GTSAM_UNSTABLE_EXTERN_EXPORT __declspec(dllexport) extern -# else -# ifndef GTSAM_UNSTABLE_IMPORT_STATIC -# define GTSAM_UNSTABLE_EXPORT __declspec(dllimport) -# define GTSAM_UNSTABLE_EXTERN_EXPORT __declspec(dllimport) -# else /* GTSAM_UNSTABLE_IMPORT_STATIC */ -# define GTSAM_UNSTABLE_EXPORT -# define GTSAM_UNSTABLE_EXTERN_EXPORT extern -# endif /* GTSAM_UNSTABLE_IMPORT_STATIC */ -# endif /* GTSAM_UNSTABLE_EXPORTS */ -#else /* _WIN32 */ -# define GTSAM_UNSTABLE_EXPORT -# define GTSAM_UNSTABLE_EXTERN_EXPORT extern -#endif - diff --git a/gtsam_unstable/discrete/Constraint.h b/gtsam_unstable/discrete/Constraint.h index 32fb6f1ce3..136704c2de 100644 --- a/gtsam_unstable/discrete/Constraint.h +++ b/gtsam_unstable/discrete/Constraint.h @@ -17,7 +17,7 @@ #pragma once -#include +#include #include #include diff --git a/gtsam_unstable/dynamics/PoseRTV.h b/gtsam_unstable/dynamics/PoseRTV.h index 0c00e5d951..b1cfb6f303 100644 --- a/gtsam_unstable/dynamics/PoseRTV.h +++ b/gtsam_unstable/dynamics/PoseRTV.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include #include diff --git a/gtsam_unstable/geometry/BearingS2.h b/gtsam_unstable/geometry/BearingS2.h index 70a22b9a5b..197d4910d0 100644 --- a/gtsam_unstable/geometry/BearingS2.h +++ b/gtsam_unstable/geometry/BearingS2.h @@ -9,7 +9,7 @@ #pragma once -#include +#include #include #include diff --git a/gtsam_unstable/geometry/Pose3Upright.h b/gtsam_unstable/geometry/Pose3Upright.h index 9d01e20a59..d833c9cf46 100644 --- a/gtsam_unstable/geometry/Pose3Upright.h +++ b/gtsam_unstable/geometry/Pose3Upright.h @@ -11,7 +11,7 @@ #pragma once -#include +#include #include #include diff --git a/gtsam_unstable/geometry/SimWall2D.h b/gtsam_unstable/geometry/SimWall2D.h index c143bc36d0..fd5afbc54b 100644 --- a/gtsam_unstable/geometry/SimWall2D.h +++ b/gtsam_unstable/geometry/SimWall2D.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include #include diff --git a/gtsam_unstable/nonlinear/ConcurrentFilteringAndSmoothing.h b/gtsam_unstable/nonlinear/ConcurrentFilteringAndSmoothing.h index 42f82f52f6..316db921ae 100644 --- a/gtsam_unstable/nonlinear/ConcurrentFilteringAndSmoothing.h +++ b/gtsam_unstable/nonlinear/ConcurrentFilteringAndSmoothing.h @@ -20,7 +20,7 @@ // \callgraph #pragma once -#include +#include #include #include #include diff --git a/gtsam_unstable/nonlinear/FixedLagSmoother.h b/gtsam_unstable/nonlinear/FixedLagSmoother.h index bef35ffcee..362cfae968 100644 --- a/gtsam_unstable/nonlinear/FixedLagSmoother.h +++ b/gtsam_unstable/nonlinear/FixedLagSmoother.h @@ -20,7 +20,7 @@ // \callgraph #pragma once -#include +#include #include #include #include diff --git a/gtsam_unstable/nonlinear/LinearizedFactor.h b/gtsam_unstable/nonlinear/LinearizedFactor.h index ece8cd2f6c..128889b829 100644 --- a/gtsam_unstable/nonlinear/LinearizedFactor.h +++ b/gtsam_unstable/nonlinear/LinearizedFactor.h @@ -18,7 +18,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/gtsam_unstable/slam/AHRS.h b/gtsam_unstable/slam/AHRS.h index e15e6e0f73..f22de48cfd 100644 --- a/gtsam_unstable/slam/AHRS.h +++ b/gtsam_unstable/slam/AHRS.h @@ -9,7 +9,7 @@ #define AHRS_H_ #include "Mechanization_bRn2.h" -#include +#include #include namespace gtsam { diff --git a/gtsam_unstable/slam/DummyFactor.h b/gtsam_unstable/slam/DummyFactor.h index 9f749e9e59..574efabea6 100644 --- a/gtsam_unstable/slam/DummyFactor.h +++ b/gtsam_unstable/slam/DummyFactor.h @@ -9,7 +9,7 @@ #pragma once -#include +#include #include namespace gtsam { diff --git a/gtsam_unstable/slam/Mechanization_bRn2.h b/gtsam_unstable/slam/Mechanization_bRn2.h index a228b23478..5a6f1df6d2 100644 --- a/gtsam_unstable/slam/Mechanization_bRn2.h +++ b/gtsam_unstable/slam/Mechanization_bRn2.h @@ -9,7 +9,7 @@ #include #include -#include +#include #include namespace gtsam { diff --git a/gtsam_unstable/slam/RelativeElevationFactor.h b/gtsam_unstable/slam/RelativeElevationFactor.h index 3d81fbab3d..3507a44929 100644 --- a/gtsam_unstable/slam/RelativeElevationFactor.h +++ b/gtsam_unstable/slam/RelativeElevationFactor.h @@ -9,7 +9,7 @@ #pragma once -#include +#include #include #include diff --git a/gtsam_unstable/slam/SmartRangeFactor.h b/gtsam_unstable/slam/SmartRangeFactor.h index c05633345b..5511a02091 100644 --- a/gtsam_unstable/slam/SmartRangeFactor.h +++ b/gtsam_unstable/slam/SmartRangeFactor.h @@ -9,7 +9,7 @@ #pragma once -#include +#include #include #include #include From 438b4ff014447c59ae1fa2b9d4115e348a7df36b Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Thu, 18 Jul 2019 11:09:08 +0200 Subject: [PATCH 136/160] more DLL warnings & errors fixed --- gtsam/base/timing.h | 18 +++++----- gtsam/geometry/CalibratedCamera.h | 3 +- gtsam/geometry/EssentialMatrix.h | 14 ++++---- gtsam/geometry/SO3.h | 8 ++--- gtsam/geometry/Unit3.h | 28 +++++++-------- gtsam/geometry/triangulation.h | 2 +- gtsam/inference/Key.h | 6 ++-- gtsam/linear/IterativeSolver.h | 20 +++++------ gtsam/nonlinear/ISAM2Result.h | 2 +- gtsam/nonlinear/ISAM2UpdateParams.h | 2 +- gtsam/nonlinear/LinearContainerFactor.h | 38 ++++++++++---------- gtsam/nonlinear/NonlinearOptimizerParams.h | 16 ++++----- gtsam/nonlinear/Values.h | 18 +++++----- gtsam/symbolic/SymbolicBayesNet.h | 6 ++-- gtsam/symbolic/SymbolicBayesTree.h | 6 ++-- gtsam_unstable/geometry/Event.h | 5 +-- gtsam_unstable/geometry/Similarity3.h | 41 +++++++++++----------- 17 files changed, 117 insertions(+), 116 deletions(-) diff --git a/gtsam/base/timing.h b/gtsam/base/timing.h index ec80baad37..557500e73c 100644 --- a/gtsam/base/timing.h +++ b/gtsam/base/timing.h @@ -142,7 +142,7 @@ namespace gtsam { /** * Timing Entry, arranged in a tree */ - class GTSAM_EXPORT TimingOutline { + class TimingOutline { protected: size_t id_; size_t t_; @@ -174,21 +174,21 @@ namespace gtsam { public: /// Constructor - TimingOutline(const std::string& label, size_t myId); - size_t time() const; ///< time taken, including children + GTSAM_EXPORT TimingOutline(const std::string& label, size_t myId); + GTSAM_EXPORT size_t time() const; ///< time taken, including children double secs() const { return double(time()) / 1000000.0;} ///< time taken, in seconds, including children double self() const { return double(t_) / 1000000.0;} ///< self time only, in seconds double wall() const { return double(tWall_) / 1000000.0;} ///< wall time, in seconds double min() const { return double(tMin_) / 1000000.0;} ///< min time, in seconds double max() const { return double(tMax_) / 1000000.0;} ///< max time, in seconds double mean() const { return self() / double(n_); } ///< mean self time, in seconds - void print(const std::string& outline = "") const; - void print2(const std::string& outline = "", const double parentTotal = -1.0) const; - const boost::shared_ptr& + GTSAM_EXPORT void print(const std::string& outline = "") const; + GTSAM_EXPORT void print2(const std::string& outline = "", const double parentTotal = -1.0) const; + GTSAM_EXPORT const boost::shared_ptr& child(size_t child, const std::string& label, const boost::weak_ptr& thisPtr); - void tic(); - void toc(); - void finishedIteration(); + GTSAM_EXPORT void tic(); + GTSAM_EXPORT void toc(); + GTSAM_EXPORT void finishedIteration(); GTSAM_EXPORT friend void toc(size_t id, const char *label); }; // \TimingOutline diff --git a/gtsam/geometry/CalibratedCamera.h b/gtsam/geometry/CalibratedCamera.h index 1f791935d6..9d9b37d7a9 100644 --- a/gtsam/geometry/CalibratedCamera.h +++ b/gtsam/geometry/CalibratedCamera.h @@ -29,8 +29,7 @@ namespace gtsam { -class GTSAM_EXPORT CheiralityException: public ThreadsafeException< - CheiralityException> { +class GTSAM_EXPORT CheiralityException: public ThreadsafeException { public: CheiralityException() : CheiralityException(std::numeric_limits::max()) {} diff --git a/gtsam/geometry/EssentialMatrix.h b/gtsam/geometry/EssentialMatrix.h index 891902da7a..3235fdedd1 100644 --- a/gtsam/geometry/EssentialMatrix.h +++ b/gtsam/geometry/EssentialMatrix.h @@ -23,7 +23,7 @@ namespace gtsam { * but here we choose instead to parameterize it as a (Rot3,Unit3) pair. * We can then non-linearly optimize immediately on this 5-dimensional manifold. */ -class GTSAM_EXPORT EssentialMatrix { +class EssentialMatrix { private: Rot3 R_; ///< Rotation Unit3 t_; ///< Translation @@ -48,12 +48,12 @@ class GTSAM_EXPORT EssentialMatrix { } /// Named constructor with derivatives - static EssentialMatrix FromRotationAndDirection(const Rot3& aRb, const Unit3& aTb, + GTSAM_EXPORT static EssentialMatrix FromRotationAndDirection(const Rot3& aRb, const Unit3& aTb, OptionalJacobian<5, 3> H1 = boost::none, OptionalJacobian<5, 2> H2 = boost::none); /// Named constructor converting a Pose3 with scale to EssentialMatrix (no scale) - static EssentialMatrix FromPose3(const Pose3& _1P2_, + GTSAM_EXPORT static EssentialMatrix FromPose3(const Pose3& _1P2_, OptionalJacobian<5, 6> H = boost::none); /// Random, using Rot3::Random and Unit3::Random @@ -70,7 +70,7 @@ class GTSAM_EXPORT EssentialMatrix { /// @{ /// print with optional string - void print(const std::string& s = "") const; + GTSAM_EXPORT void print(const std::string& s = "") const; /// assert equality up to a tolerance bool equals(const EssentialMatrix& other, double tol = 1e-8) const { @@ -138,7 +138,7 @@ class GTSAM_EXPORT EssentialMatrix { * @param Dpoint optional 3*3 Jacobian wrpt point * @return point in pose coordinates */ - Point3 transformTo(const Point3& p, + GTSAM_EXPORT Point3 transformTo(const Point3& p, OptionalJacobian<3, 5> DE = boost::none, OptionalJacobian<3, 3> Dpoint = boost::none) const; @@ -147,7 +147,7 @@ class GTSAM_EXPORT EssentialMatrix { * @param cRb rotation from body frame to camera frame * @param E essential matrix E in camera frame C */ - EssentialMatrix rotate(const Rot3& cRb, OptionalJacobian<5, 5> HE = + GTSAM_EXPORT EssentialMatrix rotate(const Rot3& cRb, OptionalJacobian<5, 5> HE = boost::none, OptionalJacobian<5, 3> HR = boost::none) const; /** @@ -160,7 +160,7 @@ class GTSAM_EXPORT EssentialMatrix { } /// epipolar error, algebraic - double error(const Vector3& vA, const Vector3& vB, + GTSAM_EXPORT double error(const Vector3& vA, const Vector3& vB, OptionalJacobian<1, 5> H = boost::none) const; /// @} diff --git a/gtsam/geometry/SO3.h b/gtsam/geometry/SO3.h index a4f6861cc5..5f1c7d1bf5 100644 --- a/gtsam/geometry/SO3.h +++ b/gtsam/geometry/SO3.h @@ -156,14 +156,14 @@ class GTSAM_EXPORT ExpmapFunctor { }; /// Functor that implements Exponential map *and* its derivatives -class GTSAM_EXPORT DexpFunctor : public ExpmapFunctor { +class DexpFunctor : public ExpmapFunctor { const Vector3 omega; double a, b; Matrix3 dexp_; public: /// Constructor with element of Lie algebra so(3) - DexpFunctor(const Vector3& omega, bool nearZeroApprox = false); + GTSAM_EXPORT DexpFunctor(const Vector3& omega, bool nearZeroApprox = false); // NOTE(luca): Right Jacobian for Exponential map in SO(3) - equation // (10.86) and following equations in G.S. Chirikjian, "Stochastic Models, @@ -174,11 +174,11 @@ class GTSAM_EXPORT DexpFunctor : public ExpmapFunctor { const Matrix3& dexp() const { return dexp_; } /// Multiplies with dexp(), with optional derivatives - Vector3 applyDexp(const Vector3& v, OptionalJacobian<3, 3> H1 = boost::none, + GTSAM_EXPORT Vector3 applyDexp(const Vector3& v, OptionalJacobian<3, 3> H1 = boost::none, OptionalJacobian<3, 3> H2 = boost::none) const; /// Multiplies with dexp().inverse(), with optional derivatives - Vector3 applyInvDexp(const Vector3& v, + GTSAM_EXPORT Vector3 applyInvDexp(const Vector3& v, OptionalJacobian<3, 3> H1 = boost::none, OptionalJacobian<3, 3> H2 = boost::none) const; }; diff --git a/gtsam/geometry/Unit3.h b/gtsam/geometry/Unit3.h index f182df2859..2116988068 100644 --- a/gtsam/geometry/Unit3.h +++ b/gtsam/geometry/Unit3.h @@ -39,7 +39,7 @@ namespace gtsam { /// Represents a 3D point on a unit sphere. -class GTSAM_EXPORT Unit3 { +class Unit3 { private: @@ -94,11 +94,11 @@ class GTSAM_EXPORT Unit3 { } /// Named constructor from Point3 with optional Jacobian - static Unit3 FromPoint3(const Point3& point, // + GTSAM_EXPORT static Unit3 FromPoint3(const Point3& point, // OptionalJacobian<2, 3> H = boost::none); /// Random direction, using boost::uniform_on_sphere - static Unit3 Random(boost::mt19937 & rng); + GTSAM_EXPORT static Unit3 Random(boost::mt19937 & rng); /// @} @@ -108,7 +108,7 @@ class GTSAM_EXPORT Unit3 { friend std::ostream& operator<<(std::ostream& os, const Unit3& pair); /// The print fuction - void print(const std::string& s = std::string()) const; + GTSAM_EXPORT void print(const std::string& s = std::string()) const; /// The equals function with tolerance bool equals(const Unit3& s, double tol = 1e-9) const { @@ -125,16 +125,16 @@ class GTSAM_EXPORT Unit3 { * 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(OptionalJacobian<6, 2> H = boost::none) const; + GTSAM_EXPORT const Matrix32& basis(OptionalJacobian<6, 2> H = boost::none) const; /// Return skew-symmetric associated with 3D point on unit sphere - Matrix3 skew() const; + GTSAM_EXPORT Matrix3 skew() const; /// Return unit-norm Point3 - Point3 point3(OptionalJacobian<3, 2> H = boost::none) const; + GTSAM_EXPORT Point3 point3(OptionalJacobian<3, 2> H = boost::none) const; /// Return unit-norm Vector - Vector3 unitVector(OptionalJacobian<3, 2> H = boost::none) const; + GTSAM_EXPORT Vector3 unitVector(OptionalJacobian<3, 2> H = boost::none) const; /// Return scaled direction as Point3 friend Point3 operator*(double s, const Unit3& d) { @@ -142,20 +142,20 @@ class GTSAM_EXPORT Unit3 { } /// Return dot product with q - double dot(const Unit3& q, OptionalJacobian<1,2> H1 = boost::none, // + GTSAM_EXPORT 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 /// @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; + GTSAM_EXPORT 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, // + GTSAM_EXPORT 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; + GTSAM_EXPORT double distance(const Unit3& q, OptionalJacobian<1, 2> H = boost::none) const; /// Cross-product between two Unit3s Unit3 cross(const Unit3& q) const { @@ -188,10 +188,10 @@ class GTSAM_EXPORT Unit3 { }; /// The retract function - Unit3 retract(const Vector2& v, OptionalJacobian<2,2> H = boost::none) const; + GTSAM_EXPORT Unit3 retract(const Vector2& v, OptionalJacobian<2,2> H = boost::none) const; /// The local coordinates function - Vector2 localCoordinates(const Unit3& s) const; + GTSAM_EXPORT Vector2 localCoordinates(const Unit3& s) const; /// @} diff --git a/gtsam/geometry/triangulation.h b/gtsam/geometry/triangulation.h index 851c5e4d3e..ed61c75b59 100644 --- a/gtsam/geometry/triangulation.h +++ b/gtsam/geometry/triangulation.h @@ -386,7 +386,7 @@ struct GTSAM_EXPORT TriangulationParameters { /** * TriangulationResult is an optional point, along with the reasons why it is invalid. */ -class GTSAM_EXPORT TriangulationResult: public boost::optional { +class TriangulationResult: public boost::optional { enum Status { VALID, DEGENERATE, BEHIND_CAMERA, OUTLIER, FAR_POINT }; diff --git a/gtsam/inference/Key.h b/gtsam/inference/Key.h index ae3f3844b2..8b13f0b4ca 100644 --- a/gtsam/inference/Key.h +++ b/gtsam/inference/Key.h @@ -58,7 +58,7 @@ static const gtsam::KeyFormatter MultiRobotKeyFormatter = struct StreamedKey { const Key &key_; explicit StreamedKey(const Key &key) : key_(key) {} - friend std::ostream &operator<<(std::ostream &, const StreamedKey &); + GTSAM_EXPORT friend std::ostream &operator<<(std::ostream &, const StreamedKey &); }; /** @@ -72,8 +72,8 @@ struct StreamedKey { class key_formatter { public: explicit key_formatter(KeyFormatter v) : formatter_(v) {} - friend std::ostream &operator<<(std::ostream &, const key_formatter &); - friend std::ostream &operator<<(std::ostream &, const StreamedKey &); + GTSAM_EXPORT friend std::ostream &operator<<(std::ostream &, const key_formatter &); + GTSAM_EXPORT friend std::ostream &operator<<(std::ostream &, const StreamedKey &); private: KeyFormatter formatter_; diff --git a/gtsam/linear/IterativeSolver.h b/gtsam/linear/IterativeSolver.h index f0fbfbfd20..758d2aec97 100644 --- a/gtsam/linear/IterativeSolver.h +++ b/gtsam/linear/IterativeSolver.h @@ -41,7 +41,7 @@ class VectorValues; /** * parameters for iterative linear solvers */ -class GTSAM_EXPORT IterativeOptimizationParameters { +class IterativeOptimizationParameters { public: @@ -63,27 +63,27 @@ class GTSAM_EXPORT IterativeOptimizationParameters { inline Verbosity verbosity() const { return verbosity_; } - std::string getVerbosity() const; - void setVerbosity(const std::string &s); + GTSAM_EXPORT std::string getVerbosity() const; + GTSAM_EXPORT void setVerbosity(const std::string &s); /* matlab interface */ - void print() const; + GTSAM_EXPORT void print() const; /* virtual print function */ - virtual void print(std::ostream &os) const; + GTSAM_EXPORT virtual void print(std::ostream &os) const; /* for serialization */ friend std::ostream& operator<<(std::ostream &os, const IterativeOptimizationParameters &p); - static Verbosity verbosityTranslator(const std::string &s); - static std::string verbosityTranslator(Verbosity v); + GTSAM_EXPORT static Verbosity verbosityTranslator(const std::string &s); + GTSAM_EXPORT static std::string verbosityTranslator(Verbosity v); }; /** * Base class for Iterative Solvers like SubgraphSolver */ -class GTSAM_EXPORT IterativeSolver { +class IterativeSolver { public: typedef boost::shared_ptr shared_ptr; IterativeSolver() { @@ -92,12 +92,12 @@ class GTSAM_EXPORT IterativeSolver { } /* interface to the nonlinear optimizer, without metadata, damping and initial estimate */ - VectorValues optimize(const GaussianFactorGraph &gfg, + GTSAM_EXPORT VectorValues optimize(const GaussianFactorGraph &gfg, boost::optional = boost::none, boost::optional&> lambda = boost::none); /* interface to the nonlinear optimizer, without initial estimate */ - VectorValues optimize(const GaussianFactorGraph &gfg, const KeyInfo &keyInfo, + GTSAM_EXPORT VectorValues optimize(const GaussianFactorGraph &gfg, const KeyInfo &keyInfo, const std::map &lambda); /* interface to the nonlinear optimizer that the subclasses have to implement */ diff --git a/gtsam/nonlinear/ISAM2Result.h b/gtsam/nonlinear/ISAM2Result.h index 3953c760b2..e45b17e4ac 100644 --- a/gtsam/nonlinear/ISAM2Result.h +++ b/gtsam/nonlinear/ISAM2Result.h @@ -38,7 +38,7 @@ namespace gtsam { * converging, and about how much work was required for the update. See member * variables for details and information about each entry. */ -struct GTSAM_EXPORT ISAM2Result { +struct ISAM2Result { /** The nonlinear error of all of the factors, \a including new factors and * variables added during the current call to ISAM2::update(). This error is * calculated using the following variable values: diff --git a/gtsam/nonlinear/ISAM2UpdateParams.h b/gtsam/nonlinear/ISAM2UpdateParams.h index 884ac7922a..44519e3aab 100644 --- a/gtsam/nonlinear/ISAM2UpdateParams.h +++ b/gtsam/nonlinear/ISAM2UpdateParams.h @@ -29,7 +29,7 @@ namespace gtsam { * This struct is used by ISAM2::update() to pass additional parameters to * give the user a fine-grained control on how factors and relinearized, etc. */ -struct GTSAM_EXPORT ISAM2UpdateParams { +struct ISAM2UpdateParams { ISAM2UpdateParams() = default; /** Indices of factors to remove from system (default: empty) */ diff --git a/gtsam/nonlinear/LinearContainerFactor.h b/gtsam/nonlinear/LinearContainerFactor.h index 928b59e773..8a1f600ffe 100644 --- a/gtsam/nonlinear/LinearContainerFactor.h +++ b/gtsam/nonlinear/LinearContainerFactor.h @@ -23,7 +23,7 @@ namespace gtsam { * This factor does have the ability to perform relinearization under small-angle and * linearity assumptions if a linearization point is added. */ -class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { +class LinearContainerFactor : public NonlinearFactor { protected: GaussianFactor::shared_ptr factor_; @@ -33,7 +33,7 @@ class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { LinearContainerFactor() {} /** direct copy constructor */ - LinearContainerFactor(const GaussianFactor::shared_ptr& factor, const boost::optional& linearizationPoint); + GTSAM_EXPORT LinearContainerFactor(const GaussianFactor::shared_ptr& factor, const boost::optional& linearizationPoint); // Some handy typedefs typedef NonlinearFactor Base; @@ -44,13 +44,13 @@ class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { typedef boost::shared_ptr shared_ptr; /** Primary constructor: store a linear factor with optional linearization point */ - LinearContainerFactor(const JacobianFactor& factor, const Values& linearizationPoint = Values()); + GTSAM_EXPORT LinearContainerFactor(const JacobianFactor& factor, const Values& linearizationPoint = Values()); /** Primary constructor: store a linear factor with optional linearization point */ - LinearContainerFactor(const HessianFactor& factor, const Values& linearizationPoint = Values()); + GTSAM_EXPORT LinearContainerFactor(const HessianFactor& factor, const Values& linearizationPoint = Values()); /** Constructor from shared_ptr */ - LinearContainerFactor(const GaussianFactor::shared_ptr& factor, const Values& linearizationPoint = Values()); + GTSAM_EXPORT LinearContainerFactor(const GaussianFactor::shared_ptr& factor, const Values& linearizationPoint = Values()); // Access @@ -59,10 +59,10 @@ class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { // Testable /** print */ - void print(const std::string& s = "", const KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter) const; + GTSAM_EXPORT void print(const std::string& s = "", const KeyFormatter& keyFormatter = gtsam::DefaultKeyFormatter) const; /** Check if two factors are equal */ - bool equals(const NonlinearFactor& f, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const NonlinearFactor& f, double tol = 1e-9) const; // NonlinearFactor @@ -74,10 +74,10 @@ class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { * * @return nonlinear error if linearizationPoint present, zero otherwise */ - double error(const Values& c) const; + GTSAM_EXPORT double error(const Values& c) const; /** get the dimension of the factor: rows of linear factor */ - size_t dim() const; + GTSAM_EXPORT size_t dim() const; /** Extract the linearization point used in recalculating error */ const boost::optional& linearizationPoint() const { return linearizationPoint_; } @@ -98,17 +98,17 @@ class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { * TODO: better approximation of relinearization * TODO: switchable modes for approximation technique */ - GaussianFactor::shared_ptr linearize(const Values& c) const; + GTSAM_EXPORT GaussianFactor::shared_ptr linearize(const Values& c) const; /** * Creates an anti-factor directly */ - GaussianFactor::shared_ptr negateToGaussian() const; + GTSAM_EXPORT GaussianFactor::shared_ptr negateToGaussian() const; /** * Creates the equivalent anti-factor as another LinearContainerFactor. */ - NonlinearFactor::shared_ptr negateToNonlinear() const; + GTSAM_EXPORT NonlinearFactor::shared_ptr negateToNonlinear() const; /** * Creates a shared_ptr clone of the factor - needs to be specialized to allow @@ -127,31 +127,31 @@ class GTSAM_EXPORT LinearContainerFactor : public NonlinearFactor { /** * Simple checks whether this is a Jacobian or Hessian factor */ - bool isJacobian() const; - bool isHessian() const; + GTSAM_EXPORT bool isJacobian() const; + GTSAM_EXPORT bool isHessian() const; /** Casts to JacobianFactor */ - boost::shared_ptr toJacobian() const; + GTSAM_EXPORT boost::shared_ptr toJacobian() const; /** Casts to HessianFactor */ - boost::shared_ptr toHessian() const; + GTSAM_EXPORT boost::shared_ptr toHessian() const; /** * Utility function for converting linear graphs to nonlinear graphs * consisting of LinearContainerFactors. */ - static NonlinearFactorGraph ConvertLinearGraph(const GaussianFactorGraph& linear_graph, + GTSAM_EXPORT static NonlinearFactorGraph ConvertLinearGraph(const GaussianFactorGraph& linear_graph, const Values& linearizationPoint = Values()); #ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 - static NonlinearFactorGraph convertLinearGraph(const GaussianFactorGraph& linear_graph, + GTSAM_EXPORT static NonlinearFactorGraph convertLinearGraph(const GaussianFactorGraph& linear_graph, const Values& linearizationPoint = Values()) { return ConvertLinearGraph(linear_graph, linearizationPoint); } #endif protected: - void initializeLinearizationPoint(const Values& linearizationPoint); + GTSAM_EXPORT void initializeLinearizationPoint(const Values& linearizationPoint); private: diff --git a/gtsam/nonlinear/NonlinearOptimizerParams.h b/gtsam/nonlinear/NonlinearOptimizerParams.h index 3018a14b9f..180f4fb84d 100644 --- a/gtsam/nonlinear/NonlinearOptimizerParams.h +++ b/gtsam/nonlinear/NonlinearOptimizerParams.h @@ -31,7 +31,7 @@ namespace gtsam { /** The common parameters for Nonlinear optimizers. Most optimizers * deriving from NonlinearOptimizer also subclass the parameters. */ -class GTSAM_EXPORT NonlinearOptimizerParams { +class NonlinearOptimizerParams { public: /** See NonlinearOptimizerParams::verbosity */ enum Verbosity { @@ -52,7 +52,7 @@ class GTSAM_EXPORT NonlinearOptimizerParams { virtual ~NonlinearOptimizerParams() { } - virtual void print(const std::string& str = "") const; + GTSAM_EXPORT virtual void print(const std::string& str = "") const; size_t getMaxIterations() const { return maxIterations; } double getRelativeErrorTol() const { return relativeErrorTol; } @@ -68,8 +68,8 @@ class GTSAM_EXPORT NonlinearOptimizerParams { verbosity = verbosityTranslator(src); } - static Verbosity verbosityTranslator(const std::string &s) ; - static std::string verbosityTranslator(Verbosity value) ; + GTSAM_EXPORT static Verbosity verbosityTranslator(const std::string &s) ; + GTSAM_EXPORT static std::string verbosityTranslator(Verbosity value) ; /** See NonlinearOptimizerParams::linearSolverType */ enum LinearSolverType { @@ -144,10 +144,10 @@ class GTSAM_EXPORT NonlinearOptimizerParams { } private: - std::string linearSolverTranslator(LinearSolverType linearSolverType) const; - LinearSolverType linearSolverTranslator(const std::string& linearSolverType) const; - std::string orderingTypeTranslator(Ordering::OrderingType type) const; - Ordering::OrderingType orderingTypeTranslator(const std::string& type) const; + GTSAM_EXPORT std::string linearSolverTranslator(LinearSolverType linearSolverType) const; + GTSAM_EXPORT LinearSolverType linearSolverTranslator(const std::string& linearSolverType) const; + GTSAM_EXPORT std::string orderingTypeTranslator(Ordering::OrderingType type) const; + GTSAM_EXPORT Ordering::OrderingType orderingTypeTranslator(const std::string& type) const; }; // For backward compatibility: diff --git a/gtsam/nonlinear/Values.h b/gtsam/nonlinear/Values.h index 1048cd73a7..4b0fceaf93 100644 --- a/gtsam/nonlinear/Values.h +++ b/gtsam/nonlinear/Values.h @@ -422,7 +422,7 @@ namespace gtsam { }; /* ************************************************************************* */ - class GTSAM_EXPORT ValuesKeyAlreadyExists : public std::exception { + class ValuesKeyAlreadyExists : public std::exception { protected: const Key key_; ///< The key that already existed @@ -440,11 +440,11 @@ namespace gtsam { Key key() const throw() { return key_; } /// The message to be displayed to the user - virtual const char* what() const throw(); + GTSAM_EXPORT virtual const char* what() const throw(); }; /* ************************************************************************* */ - class GTSAM_EXPORT ValuesKeyDoesNotExist : public std::exception { + class ValuesKeyDoesNotExist : public std::exception { protected: const char* operation_; ///< The operation that attempted to access the key const Key key_; ///< The key that does not exist @@ -463,11 +463,11 @@ namespace gtsam { Key key() const throw() { return key_; } /// The message to be displayed to the user - virtual const char* what() const throw(); + GTSAM_EXPORT virtual const char* what() const throw(); }; /* ************************************************************************* */ - class GTSAM_EXPORT ValuesIncorrectType : public std::exception { + class ValuesIncorrectType : public std::exception { protected: const Key key_; ///< The key requested const std::type_info& storedTypeId_; @@ -494,11 +494,11 @@ namespace gtsam { const std::type_info& requestedTypeId() const { return requestedTypeId_; } /// The message to be displayed to the user - virtual const char* what() const throw(); + GTSAM_EXPORT virtual const char* what() const throw(); }; /* ************************************************************************* */ - class GTSAM_EXPORT DynamicValuesMismatched : public std::exception { + class DynamicValuesMismatched : public std::exception { public: DynamicValuesMismatched() throw() {} @@ -511,7 +511,7 @@ namespace gtsam { }; /* ************************************************************************* */ - class GTSAM_EXPORT NoMatchFoundForFixed: public std::exception { + class NoMatchFoundForFixed: public std::exception { protected: const size_t M1_, N1_; @@ -528,7 +528,7 @@ namespace gtsam { virtual ~NoMatchFoundForFixed() throw () { } - virtual const char* what() const throw (); + GTSAM_EXPORT virtual const char* what() const throw (); }; /* ************************************************************************* */ diff --git a/gtsam/symbolic/SymbolicBayesNet.h b/gtsam/symbolic/SymbolicBayesNet.h index ab89a4dbab..ca87b2bbc4 100644 --- a/gtsam/symbolic/SymbolicBayesNet.h +++ b/gtsam/symbolic/SymbolicBayesNet.h @@ -27,7 +27,7 @@ namespace gtsam { /** Symbolic Bayes Net * \nosubgrouping */ - class GTSAM_EXPORT SymbolicBayesNet : public FactorGraph { + class SymbolicBayesNet : public FactorGraph { public: @@ -61,14 +61,14 @@ namespace gtsam { /// @{ /** Check equality */ - bool equals(const This& bn, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const This& bn, double tol = 1e-9) const; /// @} /// @name Standard Interface /// @{ - void saveGraph(const std::string &s, const KeyFormatter& keyFormatter = DefaultKeyFormatter) const; + GTSAM_EXPORT void saveGraph(const std::string &s, const KeyFormatter& keyFormatter = DefaultKeyFormatter) const; /// @} diff --git a/gtsam/symbolic/SymbolicBayesTree.h b/gtsam/symbolic/SymbolicBayesTree.h index e28f287640..5f7bdde7e3 100644 --- a/gtsam/symbolic/SymbolicBayesTree.h +++ b/gtsam/symbolic/SymbolicBayesTree.h @@ -30,7 +30,7 @@ namespace gtsam { /* ************************************************************************* */ /// A clique in a SymbolicBayesTree - class GTSAM_EXPORT SymbolicBayesTreeClique : + class SymbolicBayesTreeClique : public BayesTreeCliqueBase { public: @@ -45,7 +45,7 @@ namespace gtsam { /* ************************************************************************* */ /// A Bayes tree that represents the connectivity between variables but is not associated with any /// probability functions. - class GTSAM_EXPORT SymbolicBayesTree : + class SymbolicBayesTree : public BayesTree { private: @@ -59,7 +59,7 @@ namespace gtsam { SymbolicBayesTree() {} /** check equality */ - bool equals(const This& other, double tol = 1e-9) const; + GTSAM_EXPORT bool equals(const This& other, double tol = 1e-9) const; private: /** Serialization function */ diff --git a/gtsam_unstable/geometry/Event.h b/gtsam_unstable/geometry/Event.h index b221641175..fc186857fe 100644 --- a/gtsam_unstable/geometry/Event.h +++ b/gtsam_unstable/geometry/Event.h @@ -22,6 +22,7 @@ #include #include #include +#include namespace gtsam { @@ -60,10 +61,10 @@ class Event { } /** print with optional string */ - GTSAM_EXPORT void print(const std::string& s = "") const; + GTSAM_UNSTABLE_EXPORT void print(const std::string& s = "") const; /** equals with an tolerance */ - GTSAM_EXPORT bool equals(const Event& other, double tol = 1e-9) const; + GTSAM_UNSTABLE_EXPORT bool equals(const Event& other, double tol = 1e-9) const; /// Updates a with tangent space delta inline Event retract(const Vector4& v) const { diff --git a/gtsam_unstable/geometry/Similarity3.h b/gtsam_unstable/geometry/Similarity3.h index a3d80c1d00..bf4937ed44 100644 --- a/gtsam_unstable/geometry/Similarity3.h +++ b/gtsam_unstable/geometry/Similarity3.h @@ -21,6 +21,7 @@ #include #include #include +#include namespace gtsam { @@ -49,59 +50,59 @@ class Similarity3: public LieGroup { /// @{ /// Default constructor - GTSAM_EXPORT Similarity3(); + GTSAM_UNSTABLE_EXPORT Similarity3(); /// Construct pure scaling - GTSAM_EXPORT Similarity3(double s); + GTSAM_UNSTABLE_EXPORT Similarity3(double s); /// Construct from GTSAM types - GTSAM_EXPORT Similarity3(const Rot3& R, const Point3& t, double s); + GTSAM_UNSTABLE_EXPORT Similarity3(const Rot3& R, const Point3& t, double s); /// Construct from Eigen types - GTSAM_EXPORT Similarity3(const Matrix3& R, const Vector3& t, double s); + GTSAM_UNSTABLE_EXPORT Similarity3(const Matrix3& R, const Vector3& t, double s); /// Construct from matrix [R t; 0 s^-1] - GTSAM_EXPORT Similarity3(const Matrix4& T); + GTSAM_UNSTABLE_EXPORT Similarity3(const Matrix4& T); /// @} /// @name Testable /// @{ /// Compare with tolerance - GTSAM_EXPORT bool equals(const Similarity3& sim, double tol) const; + GTSAM_UNSTABLE_EXPORT bool equals(const Similarity3& sim, double tol) const; /// Exact equality - GTSAM_EXPORT bool operator==(const Similarity3& other) const; + GTSAM_UNSTABLE_EXPORT bool operator==(const Similarity3& other) const; /// Print with optional string - GTSAM_EXPORT void print(const std::string& s) const; + GTSAM_UNSTABLE_EXPORT void print(const std::string& s) const; - GTSAM_EXPORT friend std::ostream &operator<<(std::ostream &os, const Similarity3& p); + GTSAM_UNSTABLE_EXPORT friend std::ostream &operator<<(std::ostream &os, const Similarity3& p); /// @} /// @name Group /// @{ /// Return an identity transform - GTSAM_EXPORT static Similarity3 identity(); + GTSAM_UNSTABLE_EXPORT static Similarity3 identity(); /// Composition - GTSAM_EXPORT Similarity3 operator*(const Similarity3& T) const; + GTSAM_UNSTABLE_EXPORT Similarity3 operator*(const Similarity3& T) const; /// Return the inverse - GTSAM_EXPORT Similarity3 inverse() const; + GTSAM_UNSTABLE_EXPORT Similarity3 inverse() const; /// @} /// @name Group action on Point3 /// @{ /// Action on a point p is s*(R*p+t) - GTSAM_EXPORT Point3 transformFrom(const Point3& p, // + GTSAM_UNSTABLE_EXPORT Point3 transformFrom(const Point3& p, // OptionalJacobian<3, 7> H1 = boost::none, // OptionalJacobian<3, 3> H2 = boost::none) const; /** syntactic sugar for transformFrom */ - GTSAM_EXPORT Point3 operator*(const Point3& p) const; + GTSAM_UNSTABLE_EXPORT Point3 operator*(const Point3& p) const; /// @} /// @name Lie Group @@ -110,12 +111,12 @@ class Similarity3: public LieGroup { /** Log map at the identity * \f$ [R_x,R_y,R_z, t_x, t_y, t_z, \lambda] \f$ */ - GTSAM_EXPORT static Vector7 Logmap(const Similarity3& s, // + GTSAM_UNSTABLE_EXPORT static Vector7 Logmap(const Similarity3& s, // OptionalJacobian<7, 7> Hm = boost::none); /** Exponential map at the identity */ - GTSAM_EXPORT static Similarity3 Expmap(const Vector7& v, // + GTSAM_UNSTABLE_EXPORT static Similarity3 Expmap(const Vector7& v, // OptionalJacobian<7, 7> Hm = boost::none); /// Chart at the origin @@ -136,17 +137,17 @@ class Similarity3: public LieGroup { * @return 4*4 element of Lie algebra that can be exponentiated * TODO(frank): rename to Hat, make part of traits */ - GTSAM_EXPORT static Matrix4 wedge(const Vector7& xi); + GTSAM_UNSTABLE_EXPORT static Matrix4 wedge(const Vector7& xi); /// Project from one tangent space to another - GTSAM_EXPORT Matrix7 AdjointMap() const; + GTSAM_UNSTABLE_EXPORT Matrix7 AdjointMap() const; /// @} /// @name Standard interface /// @{ /// Calculate 4*4 matrix group equivalent - GTSAM_EXPORT const Matrix4 matrix() const; + GTSAM_UNSTABLE_EXPORT const Matrix4 matrix() const; /// Return a GTSAM rotation const Rot3& rotation() const { @@ -165,7 +166,7 @@ class Similarity3: public LieGroup { /// Convert to a rigid body pose (R, s*t) /// TODO(frank): why is this here? Red flag! Definitely don't have it as a cast. - GTSAM_EXPORT operator Pose3() const; + GTSAM_UNSTABLE_EXPORT operator Pose3() const; /// Dimensionality of tangent space = 7 DOF - used to autodetect sizes inline static size_t Dim() { From 38e43ce0af2141c5395d6944fb1a06f54e0e508a Mon Sep 17 00:00:00 2001 From: Sandro Berchier Date: Sun, 28 Jul 2019 16:01:49 -0400 Subject: [PATCH 137/160] Added non-default const. for AHRS pre-int. and params --- gtsam/navigation/AHRSFactor.h | 22 +++++++++++++++++ gtsam/navigation/PreintegratedRotation.h | 7 ++++++ gtsam/navigation/tests/testAHRSFactor.cpp | 29 +++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/gtsam/navigation/AHRSFactor.h b/gtsam/navigation/AHRSFactor.h index 8ed695622a..c00fb8caf8 100644 --- a/gtsam/navigation/AHRSFactor.h +++ b/gtsam/navigation/AHRSFactor.h @@ -55,6 +55,28 @@ class GTSAM_EXPORT PreintegratedAhrsMeasurements : public PreintegratedRotation resetIntegration(); } + /** + * Non-Default constructor, initialize with measurements + * @param p: Parameters for AHRS pre-integration + * @param bias_hat: Current estimate of acceleration and rotation rate biases + * @param deltaTij: Delta time in pre-integration + * @param deltaRij: Delta rotation in pre-integration + * @param delRdelBiasOmega: Jacobian of rotation wrt. to gyro bias + * @param preint_meas_cov: Pre-integration covariance + */ + PreintegratedAhrsMeasurements( + const boost::shared_ptr& p, + const Vector3& bias_hat, + double deltaTij, + const Rot3& deltaRij, + const Matrix3& delRdelBiasOmega, + const Matrix3& preint_meas_cov) : + biasHat_(bias_hat), + PreintegratedRotation(p, deltaTij, deltaRij, delRdelBiasOmega), + preintMeasCov_(preint_meas_cov) { + p_->gyroscopeCovariance = p->getGyroscopeCovariance(); + } + const Params& p() const { return *boost::static_pointer_cast(p_);} const Vector3& biasHat() const { return biasHat_; } const Matrix3& preintMeasCov() const { return preintMeasCov_; } diff --git a/gtsam/navigation/PreintegratedRotation.h b/gtsam/navigation/PreintegratedRotation.h index bf2f5c0c8e..0d68a6e18c 100644 --- a/gtsam/navigation/PreintegratedRotation.h +++ b/gtsam/navigation/PreintegratedRotation.h @@ -35,6 +35,13 @@ struct GTSAM_EXPORT PreintegratedRotationParams { PreintegratedRotationParams() : gyroscopeCovariance(I_3x3) {} + PreintegratedRotationParams(Matrix3 gyroscope_covariance, + boost::optional omega_coriolis) + : gyroscopeCovariance(gyroscope_covariance) { + if (omega_coriolis) + omegaCoriolis.reset(omega_coriolis.get()); + } + virtual ~PreintegratedRotationParams() {} virtual void print(const std::string& s) const; diff --git a/gtsam/navigation/tests/testAHRSFactor.cpp b/gtsam/navigation/tests/testAHRSFactor.cpp index 208d0e7098..a5fae42c63 100644 --- a/gtsam/navigation/tests/testAHRSFactor.cpp +++ b/gtsam/navigation/tests/testAHRSFactor.cpp @@ -119,6 +119,35 @@ TEST( AHRSFactor, PreintegratedMeasurements ) { DOUBLES_EQUAL(expectedDeltaT2, actual2.deltaTij(), 1e-6); } +//****************************************************************************** +TEST( AHRSFactor, PreintegratedAhrsMeasurementsConstructor ) { + // Linearization point + Matrix3 gyroscopeCovariance = Matrix3::Ones()*0.4; + Vector3 omegaCoriolis(0.1, 0.5, 0.9); + PreintegratedRotationParams params(gyroscopeCovariance, omegaCoriolis); + Vector3 bias(1.0,2.0,3.0); ///< Current estimate of angular rate bias + Rot3 deltaRij(Rot3::RzRyRx(M_PI / 12.0, M_PI / 6.0, M_PI / 4.0)); + double deltaTij = 0.02; + Matrix3 delRdelBiasOmega = Matrix3::Ones()*0.5; + Matrix3 preintMeasCov = Matrix3::Ones()*0.2; + gtsam::PreintegratedAhrsMeasurements test_pim( + boost::make_shared(params), + bias, + deltaTij, + deltaRij, + delRdelBiasOmega, + preintMeasCov); + EXPECT(assert_equal(gyroscopeCovariance, + test_pim.p().getGyroscopeCovariance(), 1e-6)); + EXPECT(assert_equal(omegaCoriolis, + test_pim.p().getOmegaCoriolis().get(), 1e-6)); + EXPECT(assert_equal(bias, test_pim.biasHat(), 1e-6)); + DOUBLES_EQUAL(deltaTij, test_pim.deltaTij(), 1e-6); + EXPECT(assert_equal(deltaRij, Rot3(test_pim.deltaRij()), 1e-6)); + EXPECT(assert_equal(delRdelBiasOmega, test_pim.delRdelBiasOmega(), 1e-6)); + EXPECT(assert_equal(preintMeasCov, test_pim.preintMeasCov(), 1e-6)); +} + /* ************************************************************************* */ TEST(AHRSFactor, Error) { // Linearization point From 04b9bf23e536d482f6d1feea968ebe956bca0896 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 30 Jul 2019 11:41:14 -0400 Subject: [PATCH 138/160] Remove redundant gyroCov param update in ctor --- gtsam/navigation/AHRSFactor.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gtsam/navigation/AHRSFactor.h b/gtsam/navigation/AHRSFactor.h index c00fb8caf8..02384e23dc 100644 --- a/gtsam/navigation/AHRSFactor.h +++ b/gtsam/navigation/AHRSFactor.h @@ -73,9 +73,7 @@ class GTSAM_EXPORT PreintegratedAhrsMeasurements : public PreintegratedRotation const Matrix3& preint_meas_cov) : biasHat_(bias_hat), PreintegratedRotation(p, deltaTij, deltaRij, delRdelBiasOmega), - preintMeasCov_(preint_meas_cov) { - p_->gyroscopeCovariance = p->getGyroscopeCovariance(); - } + preintMeasCov_(preint_meas_cov) {} const Params& p() const { return *boost::static_pointer_cast(p_);} const Vector3& biasHat() const { return biasHat_; } From 56cc2b037fb43bcdf5fd3f946cdd760d1361c2fd Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 30 Jul 2019 11:43:08 -0400 Subject: [PATCH 139/160] Use const& for input param --- gtsam/navigation/PreintegratedRotation.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtsam/navigation/PreintegratedRotation.h b/gtsam/navigation/PreintegratedRotation.h index 0d68a6e18c..71ef5d08fb 100644 --- a/gtsam/navigation/PreintegratedRotation.h +++ b/gtsam/navigation/PreintegratedRotation.h @@ -35,8 +35,8 @@ struct GTSAM_EXPORT PreintegratedRotationParams { PreintegratedRotationParams() : gyroscopeCovariance(I_3x3) {} - PreintegratedRotationParams(Matrix3 gyroscope_covariance, - boost::optional omega_coriolis) + PreintegratedRotationParams(const Matrix3& gyroscope_covariance, + boost::optional omega_coriolis) : gyroscopeCovariance(gyroscope_covariance) { if (omega_coriolis) omegaCoriolis.reset(omega_coriolis.get()); From 162c9ab472d34931931d92950c512ef64d5d2241 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 30 Jul 2019 11:44:33 -0400 Subject: [PATCH 140/160] Remove outdated comment --- gtsam/navigation/tests/testAHRSFactor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/gtsam/navigation/tests/testAHRSFactor.cpp b/gtsam/navigation/tests/testAHRSFactor.cpp index a5fae42c63..c89019cff7 100644 --- a/gtsam/navigation/tests/testAHRSFactor.cpp +++ b/gtsam/navigation/tests/testAHRSFactor.cpp @@ -121,7 +121,6 @@ TEST( AHRSFactor, PreintegratedMeasurements ) { //****************************************************************************** TEST( AHRSFactor, PreintegratedAhrsMeasurementsConstructor ) { - // Linearization point Matrix3 gyroscopeCovariance = Matrix3::Ones()*0.4; Vector3 omegaCoriolis(0.1, 0.5, 0.9); PreintegratedRotationParams params(gyroscopeCovariance, omegaCoriolis); From 3e83f00788d11faeeb929c36b9eeaa7b4b684683 Mon Sep 17 00:00:00 2001 From: Toni Date: Tue, 30 Jul 2019 11:44:58 -0400 Subject: [PATCH 141/160] Rename test_pim to actualPim --- gtsam/navigation/tests/testAHRSFactor.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gtsam/navigation/tests/testAHRSFactor.cpp b/gtsam/navigation/tests/testAHRSFactor.cpp index c89019cff7..d32553b94a 100644 --- a/gtsam/navigation/tests/testAHRSFactor.cpp +++ b/gtsam/navigation/tests/testAHRSFactor.cpp @@ -129,22 +129,22 @@ TEST( AHRSFactor, PreintegratedAhrsMeasurementsConstructor ) { double deltaTij = 0.02; Matrix3 delRdelBiasOmega = Matrix3::Ones()*0.5; Matrix3 preintMeasCov = Matrix3::Ones()*0.2; - gtsam::PreintegratedAhrsMeasurements test_pim( - boost::make_shared(params), + PreintegratedAhrsMeasurements actualPim( + boost::make_shared(params), bias, deltaTij, deltaRij, delRdelBiasOmega, preintMeasCov); EXPECT(assert_equal(gyroscopeCovariance, - test_pim.p().getGyroscopeCovariance(), 1e-6)); + actualPim.p().getGyroscopeCovariance(), 1e-6)); EXPECT(assert_equal(omegaCoriolis, - test_pim.p().getOmegaCoriolis().get(), 1e-6)); - EXPECT(assert_equal(bias, test_pim.biasHat(), 1e-6)); - DOUBLES_EQUAL(deltaTij, test_pim.deltaTij(), 1e-6); - EXPECT(assert_equal(deltaRij, Rot3(test_pim.deltaRij()), 1e-6)); - EXPECT(assert_equal(delRdelBiasOmega, test_pim.delRdelBiasOmega(), 1e-6)); - EXPECT(assert_equal(preintMeasCov, test_pim.preintMeasCov(), 1e-6)); + actualPim.p().getOmegaCoriolis().get(), 1e-6)); + EXPECT(assert_equal(bias, actualPim.biasHat(), 1e-6)); + DOUBLES_EQUAL(deltaTij, actualPim.deltaTij(), 1e-6); + EXPECT(assert_equal(deltaRij, Rot3(actualPim.deltaRij()), 1e-6)); + EXPECT(assert_equal(delRdelBiasOmega, actualPim.delRdelBiasOmega(), 1e-6)); + EXPECT(assert_equal(preintMeasCov, actualPim.preintMeasCov(), 1e-6)); } /* ************************************************************************* */ From 05f24ed320da1e0f1fd011abb5f58642999383fb Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 31 Jul 2019 16:43:22 -0700 Subject: [PATCH 142/160] cosmic distro is obsolete now --- package_scripts/prepare_ubuntu_pkgs_for_ppa.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh b/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh index 40eeb16411..ba8dd7c375 100755 --- a/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh +++ b/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh @@ -14,7 +14,7 @@ set -e # List of distributions to create PPA packages for: -LST_DISTROS=(xenial bionic cosmic disco) +LST_DISTROS=(xenial bionic disco) # Checks # -------------------------------- From 214dca3aa5233313dd6f38b57e796f299ed7f1ca Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Wed, 7 Aug 2019 13:36:39 -0400 Subject: [PATCH 143/160] Fixed warning --- gtsam/navigation/AHRSFactor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam/navigation/AHRSFactor.h b/gtsam/navigation/AHRSFactor.h index 02384e23dc..1418ab687d 100644 --- a/gtsam/navigation/AHRSFactor.h +++ b/gtsam/navigation/AHRSFactor.h @@ -71,8 +71,8 @@ class GTSAM_EXPORT PreintegratedAhrsMeasurements : public PreintegratedRotation const Rot3& deltaRij, const Matrix3& delRdelBiasOmega, const Matrix3& preint_meas_cov) : - biasHat_(bias_hat), PreintegratedRotation(p, deltaTij, deltaRij, delRdelBiasOmega), + biasHat_(bias_hat), preintMeasCov_(preint_meas_cov) {} const Params& p() const { return *boost::static_pointer_cast(p_);} From 0eef77ff368ddc30e37aa1f3a7a97f526ba1fa92 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 8 Aug 2019 11:52:33 -0400 Subject: [PATCH 144/160] Made code a bit more efficient in case of offset --- gtsam/slam/SmartFactorBase.h | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/gtsam/slam/SmartFactorBase.h b/gtsam/slam/SmartFactorBase.h index 6e55eb50cf..34f9b9e9f1 100644 --- a/gtsam/slam/SmartFactorBase.h +++ b/gtsam/slam/SmartFactorBase.h @@ -75,15 +75,12 @@ class SmartFactorBase: public NonlinearFactor { */ ZVector measured_; - /// @name Pose of the camera in the body frame - boost::optional body_P_sensor_; ///< Pose of the camera in the body frame - /// @} + boost::optional body_P_sensor_; ///< Pose of the camera in the body frame // Cache for Fblocks, to avoid a malloc ever time we re-linearize mutable FBlocks Fs; -public: - + public: EIGEN_MAKE_ALIGNED_OPERATOR_NEW /// shorthand for a smart pointer to a factor @@ -200,17 +197,20 @@ class SmartFactorBase: public NonlinearFactor { return e && Base::equals(p, tol) && areMeasurementsEqual; } - /// Compute reprojection errors [h(x)-z] = [cameras.project(p)-z] and derivatives - template - Vector unwhitenedError(const Cameras& cameras, const POINT& point, - boost::optional Fs = boost::none, // + /// Compute reprojection errors [h(x)-z] = [cameras.project(p)-z] and + /// derivatives + template + Vector unwhitenedError( + const Cameras& cameras, const POINT& point, + boost::optional Fs = boost::none, // boost::optional E = boost::none) const { Vector ue = cameras.reprojectionError(point, measured_, Fs, E); - if(body_P_sensor_ && Fs){ - for(size_t i=0; i < Fs->size(); i++){ - Pose3 w_Pose_body = (cameras[i].pose()).compose(body_P_sensor_->inverse()); + if (body_P_sensor_ && Fs) { + const Pose3 sensor_P_body = body_P_sensor_->inverse(); + for (size_t i = 0; i < Fs->size(); i++) { + const Pose3 w_Pose_body = cameras[i].pose() * sensor_P_body; Matrix J(6, 6); - Pose3 world_P_body = w_Pose_body.compose(*body_P_sensor_, J); + const Pose3 world_P_body = w_Pose_body.compose(*body_P_sensor_, J); Fs->at(i) = Fs->at(i) * J; } } From 20736b6f14ad0245d36efdc7784b72955dd1a468 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 8 Aug 2019 11:53:05 -0400 Subject: [PATCH 145/160] deprecated SmartProjectionFactor constructor with offset --- gtsam.h | 3 + gtsam/slam/SmartProjectionFactor.h | 37 +++++++--- gtsam/slam/SmartProjectionPoseFactor.h | 29 ++++++-- .../slam/tests/testSmartProjectionFactor.cpp | 67 ++----------------- .../tests/testSmartProjectionPoseFactor.cpp | 58 ++++++++-------- 5 files changed, 86 insertions(+), 108 deletions(-) diff --git a/gtsam.h b/gtsam.h index 826f8472ec..0ac2d4ad12 100644 --- a/gtsam.h +++ b/gtsam.h @@ -2499,6 +2499,9 @@ virtual class SmartProjectionPoseFactor: gtsam::NonlinearFactor { SmartProjectionPoseFactor(const gtsam::noiseModel::Base* noise, const CALIBRATION* K, const gtsam::Pose3& body_P_sensor); + SmartProjectionPoseFactor(const gtsam::noiseModel::Base* noise, + const CALIBRATION* K, + const gtsam::SmartProjectionParams& params); SmartProjectionPoseFactor(const gtsam::noiseModel::Base* noise, const CALIBRATION* K, const gtsam::Pose3& body_P_sensor, diff --git a/gtsam/slam/SmartProjectionFactor.h b/gtsam/slam/SmartProjectionFactor.h index cbeed77c50..15d632cda2 100644 --- a/gtsam/slam/SmartProjectionFactor.h +++ b/gtsam/slam/SmartProjectionFactor.h @@ -79,15 +79,15 @@ class SmartProjectionFactor: public SmartFactorBase { /** * Constructor - * @param body_P_sensor pose of the camera in the body frame - * @param params internal parameters of the smart factors + * @param sharedNoiseModel isotropic noise model for the 2D feature measurements + * @param params parameters for the smart projection factors */ - SmartProjectionFactor(const SharedNoiseModel& sharedNoiseModel, - const boost::optional body_P_sensor = boost::none, - const SmartProjectionParams& params = SmartProjectionParams()) : - Base(sharedNoiseModel, body_P_sensor), params_(params), // - result_(TriangulationResult::Degenerate()) { - } + SmartProjectionFactor( + const SharedNoiseModel& sharedNoiseModel, + const SmartProjectionParams& params = SmartProjectionParams()) + : Base(sharedNoiseModel), + params_(params), + result_(TriangulationResult::Degenerate()) {} /** Virtual destructor */ virtual ~SmartProjectionFactor() { @@ -443,7 +443,26 @@ class SmartProjectionFactor: public SmartFactorBase { /** return the farPoint state */ bool isFarPoint() const { return result_.farPoint(); } -private: +#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 + /// @name Deprecated + /// @{ + // It does not make sense to optimize for a camera where the pose would not be + // the actual pose of the camera. An unfortunate consequence of deprecating + // this constructor means that we cannot optimize for calibration when the + // camera is offset from the body pose. That would need a new factor with + // (body) pose and calibration as variables. However, that use case is + // unlikely: when a global offset is know, calibration is typically known. + SmartProjectionFactor( + const SharedNoiseModel& sharedNoiseModel, + const boost::optional body_P_sensor, + const SmartProjectionParams& params = SmartProjectionParams()) + : Base(sharedNoiseModel, body_P_sensor), + params_(params), + result_(TriangulationResult::Degenerate()) {} + /// @} +#endif + + private: /// Serialization function friend class boost::serialization::access; diff --git a/gtsam/slam/SmartProjectionPoseFactor.h b/gtsam/slam/SmartProjectionPoseFactor.h index be15841957..b5be46258a 100644 --- a/gtsam/slam/SmartProjectionPoseFactor.h +++ b/gtsam/slam/SmartProjectionPoseFactor.h @@ -66,16 +66,31 @@ class SmartProjectionPoseFactor: public SmartProjectionFactor< /** * Constructor - * @param Isotropic measurement noise + * @param sharedNoiseModel isotropic noise model for the 2D feature measurements * @param K (fixed) calibration, assumed to be the same for all cameras - * @param body_P_sensor pose of the camera in the body frame - * @param params internal parameters of the smart factors + * @param params parameters for the smart projection factors */ - SmartProjectionPoseFactor(const SharedNoiseModel& sharedNoiseModel, + SmartProjectionPoseFactor( + const SharedNoiseModel& sharedNoiseModel, const boost::shared_ptr K, - const boost::optional body_P_sensor = boost::none, - const SmartProjectionParams& params = SmartProjectionParams()) : - Base(sharedNoiseModel, body_P_sensor, params), K_(K) { + const SmartProjectionParams& params = SmartProjectionParams()) + : Base(sharedNoiseModel, params), K_(K) { + } + + /** + * Constructor + * @param sharedNoiseModel isotropic noise model for the 2D feature measurements + * @param K (fixed) calibration, assumed to be the same for all cameras + * @param body_P_sensor pose of the camera in the body frame (optional) + * @param params parameters for the smart projection factors + */ + SmartProjectionPoseFactor( + const SharedNoiseModel& sharedNoiseModel, + const boost::shared_ptr K, + const boost::optional body_P_sensor, + const SmartProjectionParams& params = SmartProjectionParams()) + : SmartProjectionPoseFactor(sharedNoiseModel, K, params) { + this->body_P_sensor_ = body_P_sensor; } /** Virtual destructor */ diff --git a/gtsam/slam/tests/testSmartProjectionFactor.cpp b/gtsam/slam/tests/testSmartProjectionFactor.cpp index 336c7d2ea4..c6d1a5b2cf 100644 --- a/gtsam/slam/tests/testSmartProjectionFactor.cpp +++ b/gtsam/slam/tests/testSmartProjectionFactor.cpp @@ -71,7 +71,7 @@ TEST(SmartProjectionFactor, Constructor) { TEST(SmartProjectionFactor, Constructor2) { using namespace vanilla; params.setRankTolerance(rankTol); - SmartFactor factor1(unit2, boost::none, params); + SmartFactor factor1(unit2, params); } /* ************************************************************************* */ @@ -85,7 +85,7 @@ TEST(SmartProjectionFactor, Constructor3) { TEST(SmartProjectionFactor, Constructor4) { using namespace vanilla; params.setRankTolerance(rankTol); - SmartFactor factor1(unit2, boost::none, params); + SmartFactor factor1(unit2, params); factor1.add(measurement1, c1); } @@ -777,7 +777,7 @@ TEST(SmartProjectionFactor, implicitJacobianFactor ) { params.setEnableEPI(useEPI); SmartFactor::shared_ptr explicitFactor( - new SmartFactor(unit2, boost::none, params)); + new SmartFactor(unit2, params)); explicitFactor->add(level_uv, c1); explicitFactor->add(level_uv_right, c2); @@ -789,7 +789,7 @@ TEST(SmartProjectionFactor, implicitJacobianFactor ) { // Implicit Schur version params.setLinearizationMode(gtsam::IMPLICIT_SCHUR); SmartFactor::shared_ptr implicitFactor( - new SmartFactor(unit2, boost::none, params)); + new SmartFactor(unit2, params)); implicitFactor->add(level_uv, c1); implicitFactor->add(level_uv_right, c2); GaussianFactor::shared_ptr gaussianImplicitSchurFactor = @@ -810,65 +810,6 @@ TEST(SmartProjectionFactor, implicitJacobianFactor ) { EXPECT(assert_equal(yActual, yExpected, 1e-7)); } - -/* *************************************************************************/ -TEST(SmartProjectionFactor, smartFactorWithSensorBodyTransform) { - using namespace vanilla; - - // create arbitrary body_T_sensor (transforms from sensor to body) - Pose3 body_T_sensor = Pose3(Rot3::Ypr(-M_PI / 2, 0., -M_PI / 2), Point3(1, 1, 1)); - - // These are the poses we want to estimate, from camera measurements - const Pose3 sensor_T_body = body_T_sensor.inverse(); - Pose3 wTb1 = cam1.pose() * sensor_T_body; - Pose3 wTb2 = cam2.pose() * sensor_T_body; - Pose3 wTb3 = cam3.pose() * sensor_T_body; - - // three landmarks ~5 meters infront of camera - Point3 landmark1(5, 0.5, 1.2), landmark2(5, -0.5, 1.2), landmark3(5, 0, 3.0); - - Point2Vector measurements_cam1, measurements_cam2, measurements_cam3; - - // Project three landmarks into three cameras - projectToMultipleCameras(cam1, cam2, cam3, landmark1, measurements_cam1); - projectToMultipleCameras(cam1, cam2, cam3, landmark2, measurements_cam2); - projectToMultipleCameras(cam1, cam2, cam3, landmark3, measurements_cam3); - - // Create smart factors - KeyVector views {1, 2, 3}; - - SmartProjectionParams params; - params.setRankTolerance(1.0); - params.setDegeneracyMode(IGNORE_DEGENERACY); - params.setEnableEPI(false); - - SmartFactor smartFactor1(unit2, body_T_sensor, params); - smartFactor1.add(measurements_cam1, views); - - SmartFactor smartFactor2(unit2, body_T_sensor, params); - smartFactor2.add(measurements_cam2, views); - - SmartFactor smartFactor3(unit2, body_T_sensor, params); - smartFactor3.add(measurements_cam3, views); - - const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); - - // Put all factors in factor graph, adding priors - NonlinearFactorGraph graph; - graph.push_back(smartFactor1); - graph.push_back(smartFactor2); - graph.push_back(smartFactor3); - - // Check errors at ground truth poses - Values gtValues; - gtValues.insert(1, cam1); - gtValues.insert(2, cam2); - gtValues.insert(3, cam3); - double actualError = graph.error(gtValues); - double expectedError = 0.0; - DOUBLES_EQUAL(expectedError, actualError, 1e-7); -} - /* ************************************************************************* */ BOOST_CLASS_EXPORT_GUID(gtsam::noiseModel::Constrained, "gtsam_noiseModel_Constrained"); BOOST_CLASS_EXPORT_GUID(gtsam::noiseModel::Diagonal, "gtsam_noiseModel_Diagonal"); diff --git a/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp b/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp index 0197ba1b07..f833941bcc 100644 --- a/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp +++ b/gtsam/slam/tests/testSmartProjectionPoseFactor.cpp @@ -62,7 +62,7 @@ TEST( SmartProjectionPoseFactor, Constructor2) { using namespace vanillaPose; SmartProjectionParams params; params.setRankTolerance(rankTol); - SmartFactor factor1(model, sharedK, boost::none, params); + SmartFactor factor1(model, sharedK, params); } /* ************************************************************************* */ @@ -77,7 +77,7 @@ TEST( SmartProjectionPoseFactor, Constructor4) { using namespace vanillaPose; SmartProjectionParams params; params.setRankTolerance(rankTol); - SmartFactor factor1(model, sharedK, boost::none, params); + SmartFactor factor1(model, sharedK, params); factor1.add(measurement1, x1); } @@ -569,18 +569,18 @@ TEST( SmartProjectionPoseFactor, jacobianSVD ) { params.setLinearizationMode(gtsam::JACOBIAN_SVD); params.setDegeneracyMode(gtsam::IGNORE_DEGENERACY); params.setEnableEPI(false); - SmartFactor factor1(model, sharedK, boost::none, params); + SmartFactor factor1(model, sharedK, params); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor3->add(measurements_cam3, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -630,15 +630,15 @@ TEST( SmartProjectionPoseFactor, landmarkDistance ) { params.setEnableEPI(false); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor3->add(measurements_cam3, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -694,19 +694,19 @@ TEST( SmartProjectionPoseFactor, dynamicOutlierRejection ) { params.setDynamicOutlierRejectionThreshold(dynamicOutlierRejectionThreshold); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor3->add(measurements_cam3, views); SmartFactor::shared_ptr smartFactor4( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor4->add(measurements_cam4, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -749,15 +749,15 @@ TEST( SmartProjectionPoseFactor, jacobianQ ) { params.setLinearizationMode(gtsam::JACOBIAN_Q); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor3->add(measurements_cam3, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -854,15 +854,15 @@ TEST( SmartProjectionPoseFactor, CheckHessian) { params.setRankTolerance(10); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK, boost::none, params)); // HESSIAN, by default + new SmartFactor(model, sharedK, params)); // HESSIAN, by default smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK, boost::none, params)); // HESSIAN, by default + new SmartFactor(model, sharedK, params)); // HESSIAN, by default smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedK, boost::none, params)); // HESSIAN, by default + new SmartFactor(model, sharedK, params)); // HESSIAN, by default smartFactor3->add(measurements_cam3, views); NonlinearFactorGraph graph; @@ -934,11 +934,11 @@ TEST( SmartProjectionPoseFactor, 3poses_2land_rotation_only_smart_projection_fac params.setDegeneracyMode(gtsam::HANDLE_INFINITY); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK2, boost::none, params)); + new SmartFactor(model, sharedK2, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK2, boost::none, params)); + new SmartFactor(model, sharedK2, params)); smartFactor2->add(measurements_cam2, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -992,15 +992,15 @@ TEST( SmartProjectionPoseFactor, 3poses_rotation_only_smart_projection_factor ) params.setDegeneracyMode(gtsam::ZERO_ON_DEGENERACY); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedK, boost::none, params)); + new SmartFactor(model, sharedK, params)); smartFactor3->add(measurements_cam3, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -1187,7 +1187,7 @@ TEST( SmartProjectionPoseFactor, ConstructorWithCal3Bundler) { using namespace bundlerPose; SmartProjectionParams params; params.setDegeneracyMode(gtsam::ZERO_ON_DEGENERACY); - SmartFactor factor(model, sharedBundlerK, boost::none, params); + SmartFactor factor(model, sharedBundlerK, params); factor.add(measurement1, x1); } @@ -1276,15 +1276,15 @@ TEST( SmartProjectionPoseFactor, Cal3BundlerRotationOnly ) { params.setDegeneracyMode(gtsam::ZERO_ON_DEGENERACY); SmartFactor::shared_ptr smartFactor1( - new SmartFactor(model, sharedBundlerK, boost::none, params)); + new SmartFactor(model, sharedBundlerK, params)); smartFactor1->add(measurements_cam1, views); SmartFactor::shared_ptr smartFactor2( - new SmartFactor(model, sharedBundlerK, boost::none, params)); + new SmartFactor(model, sharedBundlerK, params)); smartFactor2->add(measurements_cam2, views); SmartFactor::shared_ptr smartFactor3( - new SmartFactor(model, sharedBundlerK, boost::none, params)); + new SmartFactor(model, sharedBundlerK, params)); smartFactor3->add(measurements_cam3, views); const SharedDiagonal noisePrior = noiseModel::Isotropic::Sigma(6, 0.10); @@ -1345,7 +1345,7 @@ TEST(SmartProjectionPoseFactor, serialize) { using namespace gtsam::serializationTestHelpers; SmartProjectionParams params; params.setRankTolerance(rankTol); - SmartFactor factor(model, sharedK, boost::none, params); + SmartFactor factor(model, sharedK, params); EXPECT(equalsObj(factor)); EXPECT(equalsXML(factor)); From 66ca63575f576ab7a3d3378eb330db49fba36f23 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco-Claraco Date: Wed, 14 Aug 2019 02:35:50 +0200 Subject: [PATCH 146/160] Generate PPA packages for Ubuntu 19.10 (eoan) --- package_scripts/prepare_ubuntu_pkgs_for_ppa.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh b/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh index ba8dd7c375..553e1884df 100755 --- a/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh +++ b/package_scripts/prepare_ubuntu_pkgs_for_ppa.sh @@ -14,7 +14,7 @@ set -e # List of distributions to create PPA packages for: -LST_DISTROS=(xenial bionic disco) +LST_DISTROS=(xenial bionic disco eoan) # Checks # -------------------------------- From 2f8238a9566a4ce31dba4f004e4ec3f95cde0281 Mon Sep 17 00:00:00 2001 From: Alice Anderson Date: Wed, 28 Aug 2019 17:28:10 -0700 Subject: [PATCH 147/160] Fixed spelling for Welsch and added to gtsam.h --- gtsam/linear/NoiseModel.cpp | 16 ++++++++-------- gtsam/linear/NoiseModel.h | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gtsam/linear/NoiseModel.cpp b/gtsam/linear/NoiseModel.cpp index 4d9c7883b9..fac9f9ecd1 100644 --- a/gtsam/linear/NoiseModel.cpp +++ b/gtsam/linear/NoiseModel.cpp @@ -808,22 +808,22 @@ Tukey::shared_ptr Tukey::Create(double c, const ReweightScheme reweight) { } /* ************************************************************************* */ -// Welsh +// Welsch /* ************************************************************************* */ -Welsh::Welsh(double c, const ReweightScheme reweight) : Base(reweight), c_(c), csquared_(c * c) {} +Welsch::Welsch(double c, const ReweightScheme reweight) : Base(reweight), c_(c), csquared_(c * c) {} -void Welsh::print(const std::string &s="") const { - std::cout << s << ": Welsh (" << c_ << ")" << std::endl; +void Welsch::print(const std::string &s="") const { + std::cout << s << ": Welsch (" << c_ << ")" << std::endl; } -bool Welsh::equals(const Base &expected, double tol) const { - const Welsh* p = dynamic_cast(&expected); +bool Welsch::equals(const Base &expected, double tol) const { + const Welsch* p = dynamic_cast(&expected); if (p == NULL) return false; return std::abs(c_ - p->c_) < tol; } -Welsh::shared_ptr Welsh::Create(double c, const ReweightScheme reweight) { - return shared_ptr(new Welsh(c, reweight)); +Welsch::shared_ptr Welsch::Create(double c, const ReweightScheme reweight) { + return shared_ptr(new Welsch(c, reweight)); } /* ************************************************************************* */ diff --git a/gtsam/linear/NoiseModel.h b/gtsam/linear/NoiseModel.h index e495921c2d..3476869055 100644 --- a/gtsam/linear/NoiseModel.h +++ b/gtsam/linear/NoiseModel.h @@ -852,15 +852,15 @@ namespace gtsam { } }; - /// Welsh implements the "Welsh" robust error model (Zhang97ivc) - class GTSAM_EXPORT Welsh : public Base { + /// Welsch implements the "Welsch" robust error model (Zhang97ivc) + class GTSAM_EXPORT Welsch : public Base { protected: double c_, csquared_; public: - typedef boost::shared_ptr shared_ptr; + typedef boost::shared_ptr shared_ptr; - Welsh(double c = 2.9846, const ReweightScheme reweight = Block); + Welsch(double c = 2.9846, const ReweightScheme reweight = Block); double weight(double error) const { double xc2 = (error*error)/csquared_; return std::exp(-xc2); From 87c336fce363fc7f7c87db5985ae6d8aa3800db2 Mon Sep 17 00:00:00 2001 From: Alice Anderson Date: Thu, 29 Aug 2019 08:15:00 -0700 Subject: [PATCH 148/160] Added Welsch model to gtsam.h --- gtsam.h | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gtsam.h b/gtsam.h index 0ac2d4ad12..bf35755809 100644 --- a/gtsam.h +++ b/gtsam.h @@ -252,9 +252,9 @@ class FactorIndices { bool isDebugVersion(); #include -class IndexPair { - IndexPair(); - IndexPair(size_t i, size_t j); +class IndexPair { + IndexPair(); + IndexPair(size_t i, size_t j); size_t i() const; size_t j() const; }; @@ -1388,6 +1388,15 @@ virtual class Tukey: gtsam::noiseModel::mEstimator::Base { void serializable() const; }; +virtual class Welsch: gtsam::noiseModel::mEstimator::Base { + Welsch(double k); + static gtsam::noiseModel::mEstimator::Welsch* Create(double k); + + // enabling serialization functionality + void serializable() const; +}; + + }///\namespace mEstimator virtual class Robust : gtsam::noiseModel::Base { From f72322c4a4a1c9689726bb160f5557640e5fb22b Mon Sep 17 00:00:00 2001 From: Alice Anderson Date: Mon, 2 Sep 2019 14:11:56 -0700 Subject: [PATCH 149/160] Added old code back wrapped in deprecated flag --- gtsam/linear/NoiseModel.cpp | 18 ++++++++++++++++++ gtsam/linear/NoiseModel.h | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/gtsam/linear/NoiseModel.cpp b/gtsam/linear/NoiseModel.cpp index fac9f9ecd1..0adb9910ad 100644 --- a/gtsam/linear/NoiseModel.cpp +++ b/gtsam/linear/NoiseModel.cpp @@ -826,6 +826,24 @@ Welsch::shared_ptr Welsch::Create(double c, const ReweightScheme reweight) { return shared_ptr(new Welsch(c, reweight)); } +#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 +Welsh::Welsh(double c, const ReweightScheme reweight) : Base(reweight), c_(c), csquared_(c * c) {} + +void Welsh::print(const std::string &s="") const { + std::cout << s << ": Welsh (" << c_ << ")" << std::endl; +} + +bool Welsh::equals(const Base &expected, double tol) const { + const Welsh* p = dynamic_cast(&expected); + if (p == NULL) return false; + return std::abs(c_ - p->c_) < tol; +} + +Welsh::shared_ptr Welsh::Create(double c, const ReweightScheme reweight) { + return shared_ptr(new Welsh(c, reweight)); +} +#endif + /* ************************************************************************* */ // GemanMcClure /* ************************************************************************* */ diff --git a/gtsam/linear/NoiseModel.h b/gtsam/linear/NoiseModel.h index 3476869055..aa89be9825 100644 --- a/gtsam/linear/NoiseModel.h +++ b/gtsam/linear/NoiseModel.h @@ -878,6 +878,38 @@ namespace gtsam { ar & BOOST_SERIALIZATION_NVP(c_); } }; +#ifdef GTSAM_ALLOW_DEPRECATED_SINCE_V4 + /// @name Deprecated + /// @{ + // Welsh implements the "Welsch" robust error model (Zhang97ivc) + // This was misspelled in previous versions of gtsam and should be + // removed in the future. + class GTSAM_EXPORT Welsh : public Base { + protected: + double c_, csquared_; + + public: + typedef boost::shared_ptr shared_ptr; + + Welsh(double c = 2.9846, const ReweightScheme reweight = Block); + double weight(double error) const { + double xc2 = (error*error)/csquared_; + return std::exp(-xc2); + } + void print(const std::string &s) const; + bool equals(const Base& expected, double tol=1e-8) const; + static shared_ptr Create(double k, const ReweightScheme reweight = Block) ; + + private: + /** Serialization function */ + friend class boost::serialization::access; + template + void serialize(ARCHIVE & ar, const unsigned int /*version*/) { + ar & BOOST_SERIALIZATION_BASE_OBJECT_NVP(Base); + ar & BOOST_SERIALIZATION_NVP(c_); + } + }; +#endif /// GemanMcClure implements the "Geman-McClure" robust error model /// (Zhang97ivc). From 02ee55f3e6d1802afa14f20ad8624c1e4b857bd2 Mon Sep 17 00:00:00 2001 From: Jose Luis Blanco Date: Fri, 6 Sep 2019 18:02:15 +0200 Subject: [PATCH 150/160] Fix missing "t" typo --- doc/ImuFactor.lyx | 2 +- doc/ImuFactor.pdf | Bin 89298 -> 198168 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ImuFactor.lyx b/doc/ImuFactor.lyx index d9cd35584b..0922a3e9c7 100644 --- a/doc/ImuFactor.lyx +++ b/doc/ImuFactor.lyx @@ -398,7 +398,7 @@ The local coordinates and note that \begin_inset Formula \[ -\frac{d\Phi_{R_{0}}\left(\omega t\right)}{dt}\biggr\vert_{t=0}=\frac{dR_{0}\exp\left(\Skew{\omega t}\right)}{dt}\biggr\vert_{t=0}=R_{0}\Skew{\omega} +\frac{d\Phi_{R_{0}}\left(\omega t\right)}{dt}\biggr\vert_{t=0}=\frac{dR_{0}\exp\left(\Skew{\omega t}\right)}{dt}\biggr\vert_{t=0}=R_{0}\Skew{\omega t} \] \end_inset diff --git a/doc/ImuFactor.pdf b/doc/ImuFactor.pdf index f5a25f54f702531731d50f4b6f7122c2b4920320..0b13c1f5948de38aa6454732ca20367cc630c62c 100644 GIT binary patch literal 198168 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°)K_7Ssz~QBDJ}B)#uzY5^42$9mV#~j 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 From 4949d6988cc918f67188291c7fc0edc74671c03d Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Sat, 7 Sep 2019 15:48:30 -0400 Subject: [PATCH 151/160] Fix compilation of PartialPriorFactor --- gtsam_unstable/slam/PartialPriorFactor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gtsam_unstable/slam/PartialPriorFactor.h b/gtsam_unstable/slam/PartialPriorFactor.h index fa06d47a3d..c71ee7abd7 100644 --- a/gtsam_unstable/slam/PartialPriorFactor.h +++ b/gtsam_unstable/slam/PartialPriorFactor.h @@ -121,7 +121,7 @@ namespace gtsam { // access const Vector& prior() const { return prior_; } - const std::vector& mask() const { return mask_; } + const std::vector& mask() const { return mask_; } const Matrix& H() const { return H_; } protected: From b61636e2f711971e875df3aca63ce69907bce9c5 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Sat, 7 Sep 2019 16:10:49 -0400 Subject: [PATCH 152/160] Fix segfault in SubgraphBuilder --- gtsam/linear/SubgraphBuilder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gtsam/linear/SubgraphBuilder.cpp b/gtsam/linear/SubgraphBuilder.cpp index 22ad89cd88..34c4c0440d 100644 --- a/gtsam/linear/SubgraphBuilder.cpp +++ b/gtsam/linear/SubgraphBuilder.cpp @@ -127,7 +127,8 @@ static vector UniqueSampler(const vector &weight, /* sampling and cache results */ vector samples = iidSampler(localWeights, n - count); - for (const size_t &index : samples) { + const auto samplesSize = samples.size(); + for (size_t index = 0; index < samplesSize; index++) { if (touched[index] == false) { touched[index] = true; samples.push_back(index); From cb80f27ee8a7af8eada3b9dfb214cee46b99c390 Mon Sep 17 00:00:00 2001 From: Fan Jiang Date: Sat, 7 Sep 2019 17:30:18 -0400 Subject: [PATCH 153/160] Restoring the correct behavior of UniqueSampler --- gtsam/linear/SubgraphBuilder.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gtsam/linear/SubgraphBuilder.cpp b/gtsam/linear/SubgraphBuilder.cpp index 34c4c0440d..a999b3a719 100644 --- a/gtsam/linear/SubgraphBuilder.cpp +++ b/gtsam/linear/SubgraphBuilder.cpp @@ -107,7 +107,7 @@ static vector UniqueSampler(const vector &weight, const size_t m = weight.size(); if (n > m) throw std::invalid_argument("UniqueSampler: invalid input size"); - vector samples; + vector results; size_t count = 0; vector touched(m, false); @@ -127,16 +127,15 @@ static vector UniqueSampler(const vector &weight, /* sampling and cache results */ vector samples = iidSampler(localWeights, n - count); - const auto samplesSize = samples.size(); - for (size_t index = 0; index < samplesSize; index++) { + for (const size_t &index : samples) { if (touched[index] == false) { touched[index] = true; - samples.push_back(index); + results.push_back(index); if (++count >= n) break; } } } - return samples; + return results; } /****************************************************************************/ From 7dc1811d5477e25d76e6e91e01aefd9a2c04e369 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Thu, 12 Sep 2019 14:56:12 -0400 Subject: [PATCH 154/160] add cmake flag to include eigen-unsupported module --- CMakeLists.txt | 1 + gtsam/3rdparty/CMakeLists.txt | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 738a434f24..9983fabcce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,6 +293,7 @@ endif() ### See: http://eigen.tuxfamily.org/bz/show_bug.cgi?id=704 (Householder QR MKL selection) ### http://eigen.tuxfamily.org/bz/show_bug.cgi?id=705 (Fix MKL LLT return code) option(GTSAM_USE_SYSTEM_EIGEN "Find and use system-installed Eigen. If 'off', use the one bundled with GTSAM" OFF) +option(GTSAM_WITH_EIGEN_UNSUPPORTED "Install Eigen's unsupported modules" ON) # Switch for using system Eigen or GTSAM-bundled Eigen if(GTSAM_USE_SYSTEM_EIGEN) diff --git a/gtsam/3rdparty/CMakeLists.txt b/gtsam/3rdparty/CMakeLists.txt index 9a99bdba85..ed18b7aad2 100644 --- a/gtsam/3rdparty/CMakeLists.txt +++ b/gtsam/3rdparty/CMakeLists.txt @@ -16,6 +16,21 @@ if(NOT GTSAM_USE_SYSTEM_EIGEN) endif() endforeach(eigen_dir) + if(GTSAM_WITH_EIGEN_UNSUPPORTED) + message("-- Installing Eigen Unsupported modules") + # do the same for the unsupported eigen folder + file(GLOB_RECURSE unsupported_eigen_headers "${CMAKE_CURRENT_SOURCE_DIR}/Eigen/unsupported/Eigen/*.h") + + file(GLOB unsupported_eigen_dir_headers_all "Eigen/unsupported/Eigen/*") + foreach(unsupported_eigen_dir ${unsupported_eigen_dir_headers_all}) + get_filename_component(filename ${unsupported_eigen_dir} NAME) + if (NOT ((${filename} MATCHES "CMakeLists.txt") OR (${filename} MATCHES "src") OR (${filename} MATCHES "CXX11") OR (${filename} MATCHES ".svn"))) + list(APPEND unsupported_eigen_headers "${CMAKE_CURRENT_SOURCE_DIR}/Eigen/unsupported/Eigen/${filename}") + install(FILES Eigen/unsupported/Eigen/${filename} DESTINATION include/gtsam/3rdparty/Eigen/unsupported/Eigen) + endif() + endforeach(unsupported_eigen_dir) + endif() + # Add to project source set(eigen_headers ${eigen_headers} PARENT_SCOPE) # set(unsupported_eigen_headers ${unsupported_eigen_headers} PARENT_SCOPE) @@ -24,6 +39,13 @@ if(NOT GTSAM_USE_SYSTEM_EIGEN) install(DIRECTORY Eigen/Eigen DESTINATION include/gtsam/3rdparty/Eigen FILES_MATCHING PATTERN "*.h") + + if(GTSAM_WITH_EIGEN_UNSUPPORTED) + install(DIRECTORY Eigen/unsupported/Eigen + DESTINATION include/gtsam/3rdparty/Eigen/unsupported/ + FILES_MATCHING PATTERN "*.h") + endif() + endif() option(GTSAM_BUILD_METIS_EXECUTABLES "Build metis library executables" OFF) From a1583e08b74e7025fe35e651c40b52fc8ee4bb91 Mon Sep 17 00:00:00 2001 From: Frank Dellaert Date: Thu, 12 Sep 2019 15:01:01 -0400 Subject: [PATCH 155/160] Switched default to OFF --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9983fabcce..b0457cb1c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,7 +293,7 @@ endif() ### See: http://eigen.tuxfamily.org/bz/show_bug.cgi?id=704 (Householder QR MKL selection) ### http://eigen.tuxfamily.org/bz/show_bug.cgi?id=705 (Fix MKL LLT return code) option(GTSAM_USE_SYSTEM_EIGEN "Find and use system-installed Eigen. If 'off', use the one bundled with GTSAM" OFF) -option(GTSAM_WITH_EIGEN_UNSUPPORTED "Install Eigen's unsupported modules" ON) +option(GTSAM_WITH_EIGEN_UNSUPPORTED "Install Eigen's unsupported modules" OFF) # Switch for using system Eigen or GTSAM-bundled Eigen if(GTSAM_USE_SYSTEM_EIGEN) From 5e387d1a7ca2a9313a4532dc052b4d779a64f879 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 16 Sep 2019 16:03:59 -0400 Subject: [PATCH 156/160] update to README to include link to READMEs of MATLAB and Python wrappers --- README.md | 4 ++++ cython/README.md | 20 ++++++++++--------- matlab/{README-gtsam-toolbox.md => README.md} | 4 +--- 3 files changed, 16 insertions(+), 12 deletions(-) rename matlab/{README-gtsam-toolbox.md => README.md} (98%) diff --git a/README.md b/README.md index 68bae879e1..381b56ba3d 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,10 @@ GTSAM 4 will introduce several new features, most notably Expressions and a pyth Also, GTSAM 4 introduces traits, a C++ technique that allows optimizing with non-GTSAM types. That opens the door to retiring geometric types such as Point2 and Point3 to pure Eigen types, which we will also do. A significant change which will not trigger a compile error is that zero-initializing of Point2 and Point3 will be deprecated, so please be aware that this might render functions using their default constructor incorrect. +## Wrappers + +We provide support for [MATLAB](matlab/README.md) and [Python](cython/README.md) wrappers for GTSAM. Please refer to the linked documents for more details. + ## The Preintegrated IMU Factor GTSAM includes a state of the art IMU handling scheme based on diff --git a/cython/README.md b/cython/README.md index 6dcdd7c1ce..bc6e346d9d 100644 --- a/cython/README.md +++ b/cython/README.md @@ -1,6 +1,8 @@ +# Python Wrapper + This is the Cython/Python wrapper around the GTSAM C++ library. -# INSTALL +## Install - if you want to build the gtsam python library for a specific python version (eg 2.7), use the `-DGTSAM_PYTHON_VERSION=2.7` option when running `cmake` otherwise the default interpreter will be used. - If the interpreter is inside an environment (such as an anaconda environment or virtualenv environment) then the environment should be active while building gtsam. @@ -27,7 +29,7 @@ export PYTHONPATH=$PYTHONPATH: - if you run `setup.py` from the build directory rather than the installation directory, the script will warn you with the message: `setup.py is being run from an unexpected location`. Before `make install` is run, not all the components of the package have been copied across, so running `setup.py` from the build directory would result in an incomplete package. -# UNIT TESTS +## Unit Tests The Cython toolbox also has a small set of unit tests located in the test directory. To run them: @@ -37,11 +39,11 @@ test directory. To run them: python -m unittest discover ``` -# WRITING YOUR OWN SCRIPTS +## Writing Your Own Scripts See the tests for examples. -## Some important notes: +### Some Important Notes: - Vector/Matrix: + GTSAM expects double-precision floating point vectors and matrices. @@ -66,7 +68,7 @@ Examples: noiseGaussian = dynamic_cast_noiseModel_Gaussian_noiseModel_Base(noiseBase) ``` -# WRAPPING YOUR OWN PROJECT THAT USES GTSAM +## Wrapping Your Own Project That Uses GTSAM - Set PYTHONPATH to include ${GTSAM_CYTHON_INSTALL_PATH} + so that it can find gtsam Cython header: gtsam/gtsam.pxd @@ -88,8 +90,8 @@ wrap_and_install_library_cython("your_project_interface.h" #Optional: install_cython_scripts and install_cython_files. See GtsamCythonWrap.cmake. ``` -KNOWN ISSUES -============ +## KNOWN ISSUES + - Doesn't work with python3 installed from homebrew - size-related issue: can only wrap up to a certain number of classes: up to mEstimator! - Guess: 64 vs 32b? disutils Compiler flags? @@ -99,7 +101,7 @@ KNOWN ISSUES - support these constructors by default and declare "delete" for special classes? -# TODO +### TODO - [ ] allow duplication of parent' functions in child classes. Not allowed for now due to conflicts in Cython. - [ ] a common header for boost shared_ptr? (Or wait until everything is switched to std::shared_ptr in gtsam?) @@ -107,7 +109,7 @@ KNOWN ISSUES - [ ] Wrap fixed-size Matrices/Vectors? -# Completed/Cancelled: +### Completed/Cancelled: - [x] Fix Python tests: don't use " import * ": Bad style!!! (18-03-17 19:50) - [x] Unit tests for cython wrappers @done (18-03-17 18:45) -- simply compare generated files diff --git a/matlab/README-gtsam-toolbox.md b/matlab/README.md similarity index 98% rename from matlab/README-gtsam-toolbox.md rename to matlab/README.md index 66a02e9694..86e7d9fe07 100644 --- a/matlab/README-gtsam-toolbox.md +++ b/matlab/README.md @@ -1,6 +1,4 @@ -# GTSAM - Georgia Tech Smoothing and Mapping Library - -## MATLAB wrapper +# MATLAB Wrapper http://borg.cc.gatech.edu/projects/gtsam From ff1f37c26fc439913cd7564cac8bed8bdf40a4d3 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 18 Sep 2019 15:24:09 -0400 Subject: [PATCH 157/160] replaced fabs with c++11 std::abs --- gtsam/base/Matrix.h | 2 +- gtsam/base/Vector.cpp | 10 +++++----- gtsam/base/deprecated/LieScalar.h | 2 +- gtsam/base/tests/testVector.cpp | 2 +- gtsam/discrete/DecisionTree-inl.h | 2 +- gtsam/geometry/Cal3Bundler.cpp | 6 +++--- gtsam/geometry/Cal3DS2.cpp | 6 +++--- gtsam/geometry/Cal3DS2_Base.cpp | 6 +++--- gtsam/geometry/Cal3Unified.cpp | 8 ++++---- gtsam/geometry/Cal3_S2.cpp | 10 +++++----- gtsam/geometry/Cal3_S2Stereo.cpp | 2 +- gtsam/geometry/OrientedPlane3.h | 2 +- gtsam/geometry/Point2.cpp | 4 ++-- gtsam/geometry/Point3.cpp | 6 +++--- gtsam/geometry/Pose2.cpp | 4 ++-- gtsam/geometry/Pose3.cpp | 2 +- gtsam/geometry/Rot2.cpp | 8 ++++---- gtsam/geometry/StereoPoint2.h | 4 ++-- gtsam/geometry/Unit3.cpp | 2 +- gtsam/geometry/tests/testEssentialMatrix.cpp | 4 ++-- gtsam/linear/NoiseModel.cpp | 6 +++--- gtsam/linear/NoiseModel.h | 8 ++++---- gtsam/navigation/ManifoldPreintegration.cpp | 2 +- gtsam/navigation/PreintegratedRotation.cpp | 2 +- gtsam/navigation/TangentPreintegration.cpp | 2 +- gtsam/nonlinear/DoglegOptimizerImpl.h | 4 ++-- gtsam/nonlinear/LevenbergMarquardtOptimizer.cpp | 4 ++-- gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h | 2 +- gtsam/nonlinear/tests/testExpression.cpp | 2 +- gtsam/slam/BetweenFactor.h | 2 +- gtsam_unstable/dynamics/FullIMUFactor.h | 2 +- gtsam_unstable/dynamics/IMUFactor.h | 2 +- gtsam_unstable/dynamics/Pendulum.h | 8 ++++---- gtsam_unstable/dynamics/PoseRTV.cpp | 2 +- gtsam_unstable/dynamics/VelocityConstraint3.h | 2 +- gtsam_unstable/examples/SmartRangeExample_plaza1.cpp | 2 +- gtsam_unstable/examples/SmartRangeExample_plaza2.cpp | 2 +- gtsam_unstable/geometry/Pose3Upright.cpp | 2 +- gtsam_unstable/geometry/SimPolygon2D.cpp | 8 ++++---- gtsam_unstable/geometry/SimWall2D.cpp | 4 ++-- gtsam_unstable/linear/ActiveSetSolver-inl.h | 2 +- gtsam_unstable/nonlinear/FixedLagSmoother.cpp | 2 +- gtsam_unstable/slam/AHRS.cpp | 2 +- gtsam_unstable/slam/RelativeElevationFactor.cpp | 2 +- 44 files changed, 84 insertions(+), 84 deletions(-) diff --git a/gtsam/base/Matrix.h b/gtsam/base/Matrix.h index 8060ae7f4d..2910ce74c9 100644 --- a/gtsam/base/Matrix.h +++ b/gtsam/base/Matrix.h @@ -90,7 +90,7 @@ bool equal_with_abs_tol(const Eigen::DenseBase& A, const Eigen::DenseBas for(size_t j=0; j tol) + else if(std::abs(A(i,j) - B(i,j)) > tol) return false; } return true; diff --git a/gtsam/base/Vector.cpp b/gtsam/base/Vector.cpp index 856b559c37..ac03c0f53f 100644 --- a/gtsam/base/Vector.cpp +++ b/gtsam/base/Vector.cpp @@ -84,7 +84,7 @@ bool equal_with_abs_tol(const Vector& vec1, const Vector& vec2, double tol) { for(size_t i=0; i tol) + if(std::abs(vec1[i] - vec2[i]) > tol) return false; } return true; @@ -97,7 +97,7 @@ bool equal_with_abs_tol(const SubVector& vec1, const SubVector& vec2, double tol for(size_t i=0; i tol) + if(std::abs(vec1[i] - vec2[i]) > tol) return false; } return true; @@ -145,14 +145,14 @@ bool linear_dependent(const Vector& vec1, const Vector& vec2, double tol) { bool flag = false; double scale = 1.0; size_t m = vec1.size(); for(size_t i=0; itol&&fabs(vec2[i])tol)) + if((std::abs(vec1[i])>tol && std::abs(vec2[i])tol)) return false; if(vec1[i] == 0 && vec2[i] == 0) continue; if (!flag) { scale = vec1[i] / vec2[i]; flag = true ; } - else if (fabs(vec1[i] - vec2[i]*scale) > tol) return false; + else if (std::abs(vec1[i] - vec2[i]*scale) > tol) return false; } return flag; } @@ -213,7 +213,7 @@ double weightedPseudoinverse(const Vector& a, const Vector& weights, // Check once for zero entries of a. TODO: really needed ? vector isZero; - for (size_t i = 0; i < m; ++i) isZero.push_back(fabs(a[i]) < 1e-9); + for (size_t i = 0; i < m; ++i) isZero.push_back(std::abs(a[i]) < 1e-9); // If there is a valid (a!=0) constraint (sigma==0) return the first one for (size_t i = 0; i < m; ++i) { diff --git a/gtsam/base/deprecated/LieScalar.h b/gtsam/base/deprecated/LieScalar.h index 4e9bfb7dbb..6c9a5f766d 100644 --- a/gtsam/base/deprecated/LieScalar.h +++ b/gtsam/base/deprecated/LieScalar.h @@ -53,7 +53,7 @@ namespace gtsam { std::cout << name << ": " << d_ << std::endl; } bool equals(const LieScalar& expected, double tol = 1e-5) const { - return fabs(expected.d_ - d_) <= tol; + return std::abs(expected.d_ - d_) <= tol; } /// @} diff --git a/gtsam/base/tests/testVector.cpp b/gtsam/base/tests/testVector.cpp index c315b9c273..bd715e3cb2 100644 --- a/gtsam/base/tests/testVector.cpp +++ b/gtsam/base/tests/testVector.cpp @@ -165,7 +165,7 @@ TEST(Vector, weightedPseudoinverse ) // verify EXPECT(assert_equal(expected,actual)); - EXPECT(fabs(expPrecision-precision) < 1e-5); + EXPECT(std::abs(expPrecision-precision) < 1e-5); } /* ************************************************************************* */ diff --git a/gtsam/discrete/DecisionTree-inl.h b/gtsam/discrete/DecisionTree-inl.h index 2ad9fc1e32..2efd069cc0 100644 --- a/gtsam/discrete/DecisionTree-inl.h +++ b/gtsam/discrete/DecisionTree-inl.h @@ -79,7 +79,7 @@ namespace gtsam { bool equals(const Node& q, double tol) const { const Leaf* other = dynamic_cast (&q); if (!other) return false; - return fabs(double(this->constant_ - other->constant_)) < tol; + return std::abs(double(this->constant_ - other->constant_)) < tol; } /** print */ diff --git a/gtsam/geometry/Cal3Bundler.cpp b/gtsam/geometry/Cal3Bundler.cpp index 4ad1dffa2b..223bcc2421 100644 --- a/gtsam/geometry/Cal3Bundler.cpp +++ b/gtsam/geometry/Cal3Bundler.cpp @@ -59,9 +59,9 @@ void Cal3Bundler::print(const std::string& s) const { /* ************************************************************************* */ bool Cal3Bundler::equals(const Cal3Bundler& K, double tol) const { - if (fabs(f_ - K.f_) > tol || fabs(k1_ - K.k1_) > tol - || fabs(k2_ - K.k2_) > tol || fabs(u0_ - K.u0_) > tol - || fabs(v0_ - K.v0_) > tol) + if (std::abs(f_ - K.f_) > tol || std::abs(k1_ - K.k1_) > tol + || std::abs(k2_ - K.k2_) > tol || std::abs(u0_ - K.u0_) > tol + || std::abs(v0_ - K.v0_) > tol) return false; return true; } diff --git a/gtsam/geometry/Cal3DS2.cpp b/gtsam/geometry/Cal3DS2.cpp index 044d47de1c..070d16c6cc 100644 --- a/gtsam/geometry/Cal3DS2.cpp +++ b/gtsam/geometry/Cal3DS2.cpp @@ -30,9 +30,9 @@ void Cal3DS2::print(const std::string& s_) const { /* ************************************************************************* */ bool Cal3DS2::equals(const Cal3DS2& K, double tol) const { - if (fabs(fx_ - K.fx_) > tol || fabs(fy_ - K.fy_) > tol || fabs(s_ - K.s_) > tol || - fabs(u0_ - K.u0_) > tol || fabs(v0_ - K.v0_) > tol || fabs(k1_ - K.k1_) > tol || - fabs(k2_ - K.k2_) > tol || fabs(p1_ - K.p1_) > tol || fabs(p2_ - K.p2_) > tol) + if (std::abs(fx_ - K.fx_) > tol || std::abs(fy_ - K.fy_) > tol || std::abs(s_ - K.s_) > tol || + std::abs(u0_ - K.u0_) > tol || std::abs(v0_ - K.v0_) > tol || std::abs(k1_ - K.k1_) > tol || + std::abs(k2_ - K.k2_) > tol || std::abs(p1_ - K.p1_) > tol || std::abs(p2_ - K.p2_) > tol) return false; return true; } diff --git a/gtsam/geometry/Cal3DS2_Base.cpp b/gtsam/geometry/Cal3DS2_Base.cpp index 2071b87921..6c03883cef 100644 --- a/gtsam/geometry/Cal3DS2_Base.cpp +++ b/gtsam/geometry/Cal3DS2_Base.cpp @@ -49,9 +49,9 @@ void Cal3DS2_Base::print(const std::string& s_) const { /* ************************************************************************* */ bool Cal3DS2_Base::equals(const Cal3DS2_Base& K, double tol) const { - if (fabs(fx_ - K.fx_) > tol || fabs(fy_ - K.fy_) > tol || fabs(s_ - K.s_) > tol || - fabs(u0_ - K.u0_) > tol || fabs(v0_ - K.v0_) > tol || fabs(k1_ - K.k1_) > tol || - fabs(k2_ - K.k2_) > tol || fabs(p1_ - K.p1_) > tol || fabs(p2_ - K.p2_) > tol) + if (std::abs(fx_ - K.fx_) > tol || std::abs(fy_ - K.fy_) > tol || std::abs(s_ - K.s_) > tol || + std::abs(u0_ - K.u0_) > tol || std::abs(v0_ - K.v0_) > tol || std::abs(k1_ - K.k1_) > tol || + std::abs(k2_ - K.k2_) > tol || std::abs(p1_ - K.p1_) > tol || std::abs(p2_ - K.p2_) > tol) return false; return true; } diff --git a/gtsam/geometry/Cal3Unified.cpp b/gtsam/geometry/Cal3Unified.cpp index 8b7c1abf46..b1b9c37221 100644 --- a/gtsam/geometry/Cal3Unified.cpp +++ b/gtsam/geometry/Cal3Unified.cpp @@ -43,10 +43,10 @@ void Cal3Unified::print(const std::string& s) const { /* ************************************************************************* */ bool Cal3Unified::equals(const Cal3Unified& K, double tol) const { - if (fabs(fx_ - K.fx_) > tol || fabs(fy_ - K.fy_) > tol || fabs(s_ - K.s_) > tol || - fabs(u0_ - K.u0_) > tol || fabs(v0_ - K.v0_) > tol || fabs(k1_ - K.k1_) > tol || - fabs(k2_ - K.k2_) > tol || fabs(p1_ - K.p1_) > tol || fabs(p2_ - K.p2_) > tol || - fabs(xi_ - K.xi_) > tol) + if (std::abs(fx_ - K.fx_) > tol || std::abs(fy_ - K.fy_) > tol || std::abs(s_ - K.s_) > tol || + std::abs(u0_ - K.u0_) > tol || std::abs(v0_ - K.v0_) > tol || std::abs(k1_ - K.k1_) > tol || + std::abs(k2_ - K.k2_) > tol || std::abs(p1_ - K.p1_) > tol || std::abs(p2_ - K.p2_) > tol || + std::abs(xi_ - K.xi_) > tol) return false; return true; } diff --git a/gtsam/geometry/Cal3_S2.cpp b/gtsam/geometry/Cal3_S2.cpp index ca4e88eb6a..b3d1be4b68 100644 --- a/gtsam/geometry/Cal3_S2.cpp +++ b/gtsam/geometry/Cal3_S2.cpp @@ -64,15 +64,15 @@ void Cal3_S2::print(const std::string& s) const { /* ************************************************************************* */ bool Cal3_S2::equals(const Cal3_S2& K, double tol) const { - if (fabs(fx_ - K.fx_) > tol) + if (std::abs(fx_ - K.fx_) > tol) return false; - if (fabs(fy_ - K.fy_) > tol) + if (std::abs(fy_ - K.fy_) > tol) return false; - if (fabs(s_ - K.s_) > tol) + if (std::abs(s_ - K.s_) > tol) return false; - if (fabs(u0_ - K.u0_) > tol) + if (std::abs(u0_ - K.u0_) > tol) return false; - if (fabs(v0_ - K.v0_) > tol) + if (std::abs(v0_ - K.v0_) > tol) return false; return true; } diff --git a/gtsam/geometry/Cal3_S2Stereo.cpp b/gtsam/geometry/Cal3_S2Stereo.cpp index 414fe6711c..9b5aea4eda 100644 --- a/gtsam/geometry/Cal3_S2Stereo.cpp +++ b/gtsam/geometry/Cal3_S2Stereo.cpp @@ -30,7 +30,7 @@ void Cal3_S2Stereo::print(const std::string& s) const { /* ************************************************************************* */ bool Cal3_S2Stereo::equals(const Cal3_S2Stereo& other, double tol) const { - if (fabs(b_ - other.b_) > tol) return false; + if (std::abs(b_ - other.b_) > tol) return false; return K_.equals(other.K_,tol); } diff --git a/gtsam/geometry/OrientedPlane3.h b/gtsam/geometry/OrientedPlane3.h index 596118385f..61d8a30d2e 100644 --- a/gtsam/geometry/OrientedPlane3.h +++ b/gtsam/geometry/OrientedPlane3.h @@ -78,7 +78,7 @@ class GTSAM_EXPORT OrientedPlane3 { /// The equals function with tolerance bool equals(const OrientedPlane3& s, double tol = 1e-9) const { - return (n_.equals(s.n_, tol) && (fabs(d_ - s.d_) < tol)); + return (n_.equals(s.n_, tol) && (std::abs(d_ - s.d_) < tol)); } /// @} diff --git a/gtsam/geometry/Point2.cpp b/gtsam/geometry/Point2.cpp index 74b9a2bec8..3d4bb753ea 100644 --- a/gtsam/geometry/Point2.cpp +++ b/gtsam/geometry/Point2.cpp @@ -27,7 +27,7 @@ namespace gtsam { double norm2(const Point2& p, OptionalJacobian<1,2> H) { double r = std::sqrt(p.x() * p.x() + p.y() * p.y()); if (H) { - if (fabs(r) > 1e-10) + if (std::abs(r) > 1e-10) *H << p.x() / r, p.y() / r; else *H << 1, 1; // really infinity, why 1 ? @@ -59,7 +59,7 @@ void Point2::print(const string& s) const { /* ************************************************************************* */ bool Point2::equals(const Point2& q, double tol) const { - return (fabs(x() - q.x()) < tol && fabs(y() - q.y()) < tol); + return (std::abs(x() - q.x()) < tol && std::abs(y() - q.y()) < tol); } /* ************************************************************************* */ diff --git a/gtsam/geometry/Point3.cpp b/gtsam/geometry/Point3.cpp index 8df5f56074..8aa339a898 100644 --- a/gtsam/geometry/Point3.cpp +++ b/gtsam/geometry/Point3.cpp @@ -24,8 +24,8 @@ namespace gtsam { #ifndef GTSAM_TYPEDEF_POINTS_TO_VECTORS bool Point3::equals(const Point3 &q, double tol) const { - return (fabs(x() - q.x()) < tol && fabs(y() - q.y()) < tol && - fabs(z() - q.z()) < tol); + return (std::abs(x() - q.x()) < tol && std::abs(y() - q.y()) < tol && + std::abs(z() - q.z()) < tol); } void Point3::print(const string& s) const { @@ -98,7 +98,7 @@ double distance3(const Point3 &p1, const Point3 &q, OptionalJacobian<1, 3> H1, double norm3(const Point3 &p, OptionalJacobian<1, 3> H) { double r = sqrt(p.x() * p.x() + p.y() * p.y() + p.z() * p.z()); if (H) { - if (fabs(r) > 1e-10) + if (std::abs(r) > 1e-10) *H << p.x() / r, p.y() / r, p.z() / r; else *H << 1, 1, 1; // really infinity, why 1 ? diff --git a/gtsam/geometry/Pose2.cpp b/gtsam/geometry/Pose2.cpp index 4e5085cfe7..9c41a76c89 100644 --- a/gtsam/geometry/Pose2.cpp +++ b/gtsam/geometry/Pose2.cpp @@ -142,7 +142,7 @@ Matrix3 Pose2::adjointMap(const Vector3& v) { Matrix3 Pose2::ExpmapDerivative(const Vector3& v) { double alpha = v[2]; Matrix3 J; - if (fabs(alpha) > 1e-5) { + if (std::abs(alpha) > 1e-5) { // Chirikjian11book2, pg. 36 /* !!!Warning!!! Compare Iserles05an, formula 2.42 and Chirikjian11book2 pg.26 * Iserles' right-trivialization dexpR is actually the left Jacobian J_l in Chirikjian's notation @@ -174,7 +174,7 @@ Matrix3 Pose2::LogmapDerivative(const Pose2& p) { Vector3 v = Logmap(p); double alpha = v[2]; Matrix3 J; - if (fabs(alpha) > 1e-5) { + if (std::abs(alpha) > 1e-5) { double alphaInv = 1/alpha; double halfCotHalfAlpha = 0.5*sin(alpha)/(1-cos(alpha)); double v1 = v[0], v2 = v[1]; diff --git a/gtsam/geometry/Pose3.cpp b/gtsam/geometry/Pose3.cpp index e720fe0b91..01611d7397 100644 --- a/gtsam/geometry/Pose3.cpp +++ b/gtsam/geometry/Pose3.cpp @@ -221,7 +221,7 @@ static Matrix3 computeQforExpmapDerivative(const Vector6& xi) { #else // The closed-form formula in Barfoot14tro eq. (102) double phi = w.norm(); - if (fabs(phi)>1e-5) { + if (std::abs(phi)>1e-5) { const double sinPhi = sin(phi), cosPhi = cos(phi); const double phi2 = phi * phi, phi3 = phi2 * phi, phi4 = phi3 * phi, phi5 = phi4 * phi; // Invert the sign of odd-order terms to have the right Jacobian diff --git a/gtsam/geometry/Rot2.cpp b/gtsam/geometry/Rot2.cpp index 9f3ed35b07..04ed16774f 100644 --- a/gtsam/geometry/Rot2.cpp +++ b/gtsam/geometry/Rot2.cpp @@ -25,7 +25,7 @@ namespace gtsam { /* ************************************************************************* */ Rot2 Rot2::fromCosSin(double c, double s) { - if (fabs(c * c + s * s - 1.0) > 1e-9) { + if (std::abs(c * c + s * s - 1.0) > 1e-9) { double norm_cs = sqrt(c*c + s*s); c = c/norm_cs; s = s/norm_cs; @@ -46,13 +46,13 @@ void Rot2::print(const string& s) const { /* ************************************************************************* */ bool Rot2::equals(const Rot2& R, double tol) const { - return fabs(c_ - R.c_) <= tol && fabs(s_ - R.s_) <= tol; + return std::abs(c_ - R.c_) <= tol && std::abs(s_ - R.s_) <= tol; } /* ************************************************************************* */ Rot2& Rot2::normalize() { double scale = c_*c_ + s_*s_; - if(fabs(scale-1.0)>1e-10) { + if(std::abs(scale-1.0)>1e-10) { scale = pow(scale, -0.5); c_ *= scale; s_ *= scale; @@ -115,7 +115,7 @@ Point2 Rot2::unrotate(const Point2& p, /* ************************************************************************* */ Rot2 Rot2::relativeBearing(const Point2& d, OptionalJacobian<1, 2> H) { double x = d.x(), y = d.y(), d2 = x * x + y * y, n = sqrt(d2); - if(fabs(n) > 1e-5) { + if(std::abs(n) > 1e-5) { if (H) { *H << -y / d2, x / d2; } diff --git a/gtsam/geometry/StereoPoint2.h b/gtsam/geometry/StereoPoint2.h index e0f3c34242..9eef01577d 100644 --- a/gtsam/geometry/StereoPoint2.h +++ b/gtsam/geometry/StereoPoint2.h @@ -62,8 +62,8 @@ class GTSAM_EXPORT StereoPoint2 { /** equals */ bool equals(const StereoPoint2& q, double tol = 1e-9) const { - return (fabs(uL_ - q.uL_) < tol && fabs(uR_ - q.uR_) < tol - && fabs(v_ - q.v_) < tol); + return (std::abs(uL_ - q.uL_) < tol && std::abs(uR_ - q.uR_) < tol + && std::abs(v_ - q.v_) < tol); } /// @} diff --git a/gtsam/geometry/Unit3.cpp b/gtsam/geometry/Unit3.cpp index f661f819db..533ee500ef 100755 --- a/gtsam/geometry/Unit3.cpp +++ b/gtsam/geometry/Unit3.cpp @@ -68,7 +68,7 @@ Unit3 Unit3::Random(boost::mt19937 & rng) { /* ************************************************************************* */ // Get the axis of rotation with the minimum projected length of the point static Point3 CalculateBestAxis(const Point3& n) { - double mx = fabs(n.x()), my = fabs(n.y()), mz = fabs(n.z()); + double mx = std::abs(n.x()), my = std::abs(n.y()), mz = std::abs(n.z()); if ((mx <= my) && (mx <= mz)) { return Point3(1.0, 0.0, 0.0); } else if ((my <= mx) && (my <= mz)) { diff --git a/gtsam/geometry/tests/testEssentialMatrix.cpp b/gtsam/geometry/tests/testEssentialMatrix.cpp index 7ba8851155..c923e398ba 100644 --- a/gtsam/geometry/tests/testEssentialMatrix.cpp +++ b/gtsam/geometry/tests/testEssentialMatrix.cpp @@ -218,10 +218,10 @@ TEST (EssentialMatrix, epipoles) { } // check rank 2 constraint - CHECK(fabs(S(2))<1e-10); + CHECK(std::abs(S(2))<1e-10); // check epipoles not at infinity - CHECK(fabs(U(2,2))>1e-10 && fabs(V(2,2))>1e-10); + CHECK(std::abs(U(2,2))>1e-10 && std::abs(V(2,2))>1e-10); // Check epipoles diff --git a/gtsam/linear/NoiseModel.cpp b/gtsam/linear/NoiseModel.cpp index 4d9c7883b9..b5b2594723 100644 --- a/gtsam/linear/NoiseModel.cpp +++ b/gtsam/linear/NoiseModel.cpp @@ -847,7 +847,7 @@ void GemanMcClure::print(const std::string &s="") const { bool GemanMcClure::equals(const Base &expected, double tol) const { const GemanMcClure* p = dynamic_cast(&expected); if (p == NULL) return false; - return fabs(c_ - p->c_) < tol; + return std::abs(c_ - p->c_) < tol; } GemanMcClure::shared_ptr GemanMcClure::Create(double c, const ReweightScheme reweight) { @@ -879,7 +879,7 @@ void DCS::print(const std::string &s="") const { bool DCS::equals(const Base &expected, double tol) const { const DCS* p = dynamic_cast(&expected); if (p == NULL) return false; - return fabs(c_ - p->c_) < tol; + return std::abs(c_ - p->c_) < tol; } DCS::shared_ptr DCS::Create(double c, const ReweightScheme reweight) { @@ -903,7 +903,7 @@ void L2WithDeadZone::print(const std::string &s="") const { bool L2WithDeadZone::equals(const Base &expected, double tol) const { const L2WithDeadZone* p = dynamic_cast(&expected); if (p == NULL) return false; - return fabs(k_ - p->k_) < tol; + return std::abs(k_ - p->k_) < tol; } L2WithDeadZone::shared_ptr L2WithDeadZone::Create(double k, const ReweightScheme reweight) { diff --git a/gtsam/linear/NoiseModel.h b/gtsam/linear/NoiseModel.h index e495921c2d..d65aa5e67a 100644 --- a/gtsam/linear/NoiseModel.h +++ b/gtsam/linear/NoiseModel.h @@ -750,7 +750,7 @@ namespace gtsam { Fair(double c = 1.3998, const ReweightScheme reweight = Block); double weight(double error) const { - return 1.0 / (1.0 + fabs(error) / c_); + return 1.0 / (1.0 + std::abs(error) / c_); } void print(const std::string &s) const; bool equals(const Base& expected, double tol=1e-8) const; @@ -832,7 +832,7 @@ namespace gtsam { Tukey(double c = 4.6851, const ReweightScheme reweight = Block); double weight(double error) const { - if (std::fabs(error) <= c_) { + if (std::abs(error) <= c_) { double xc2 = error*error/csquared_; return (1.0-xc2)*(1.0-xc2); } @@ -952,14 +952,14 @@ namespace gtsam { L2WithDeadZone(double k, const ReweightScheme reweight = Block); double residual(double error) const { - const double abs_error = fabs(error); + const double abs_error = std::abs(error); return (abs_error < k_) ? 0.0 : 0.5*(k_-abs_error)*(k_-abs_error); } double weight(double error) const { // note that this code is slightly uglier than above, because there are three distinct // cases to handle (left of deadzone, deadzone, right of deadzone) instead of the two // cases (deadzone, non-deadzone) above. - if (fabs(error) <= k_) return 0.0; + if (std::abs(error) <= k_) return 0.0; else if (error > k_) return (-k_+error)/error; else return (k_+error)/error; } diff --git a/gtsam/navigation/ManifoldPreintegration.cpp b/gtsam/navigation/ManifoldPreintegration.cpp index cc88d91014..165acdaf0e 100644 --- a/gtsam/navigation/ManifoldPreintegration.cpp +++ b/gtsam/navigation/ManifoldPreintegration.cpp @@ -46,7 +46,7 @@ void ManifoldPreintegration::resetIntegration() { //------------------------------------------------------------------------------ bool ManifoldPreintegration::equals(const ManifoldPreintegration& other, double tol) const { - return p_->equals(*other.p_, tol) && fabs(deltaTij_ - other.deltaTij_) < tol + return p_->equals(*other.p_, tol) && std::abs(deltaTij_ - other.deltaTij_) < tol && biasHat_.equals(other.biasHat_, tol) && deltaXij_.equals(other.deltaXij_, tol) && equal_with_abs_tol(delRdelBiasOmega_, other.delRdelBiasOmega_, tol) diff --git a/gtsam/navigation/PreintegratedRotation.cpp b/gtsam/navigation/PreintegratedRotation.cpp index ec91d69b83..8c29d85ddb 100644 --- a/gtsam/navigation/PreintegratedRotation.cpp +++ b/gtsam/navigation/PreintegratedRotation.cpp @@ -65,7 +65,7 @@ bool PreintegratedRotation::equals(const PreintegratedRotation& other, double tol) const { return this->matchesParamsWith(other) && deltaRij_.equals(other.deltaRij_, tol) - && fabs(deltaTij_ - other.deltaTij_) < tol + && std::abs(deltaTij_ - other.deltaTij_) < tol && equal_with_abs_tol(delRdelBiasOmega_, other.delRdelBiasOmega_, tol); } diff --git a/gtsam/navigation/TangentPreintegration.cpp b/gtsam/navigation/TangentPreintegration.cpp index 0ac57d05c7..4229d4f0ce 100644 --- a/gtsam/navigation/TangentPreintegration.cpp +++ b/gtsam/navigation/TangentPreintegration.cpp @@ -41,7 +41,7 @@ void TangentPreintegration::resetIntegration() { //------------------------------------------------------------------------------ bool TangentPreintegration::equals(const TangentPreintegration& other, double tol) const { - return p_->equals(*other.p_, tol) && fabs(deltaTij_ - other.deltaTij_) < tol + return p_->equals(*other.p_, tol) && std::abs(deltaTij_ - other.deltaTij_) < tol && biasHat_.equals(other.biasHat_, tol) && equal_with_abs_tol(preintegrated_, other.preintegrated_, tol) && equal_with_abs_tol(preintegrated_H_biasAcc_, diff --git a/gtsam/nonlinear/DoglegOptimizerImpl.h b/gtsam/nonlinear/DoglegOptimizerImpl.h index 9a70678780..8ce9b361ed 100644 --- a/gtsam/nonlinear/DoglegOptimizerImpl.h +++ b/gtsam/nonlinear/DoglegOptimizerImpl.h @@ -177,7 +177,7 @@ typename DoglegOptimizerImpl::IterationResult DoglegOptimizerImpl::Iterate( gttic(adjust_delta); // Compute gain ratio. Here we take advantage of the invariant that the // Bayes' net error at zero is equal to the nonlinear error - const double rho = fabs(f_error - result.f_error) < 1e-15 || fabs(M_error - new_M_error) < 1e-15 ? + const double rho = std::abs(f_error - result.f_error) < 1e-15 || std::abs(M_error - new_M_error) < 1e-15 ? 0.5 : (f_error - result.f_error) / (M_error - new_M_error); @@ -191,7 +191,7 @@ typename DoglegOptimizerImpl::IterationResult DoglegOptimizerImpl::Iterate( if(mode == ONE_STEP_PER_ITERATION || mode == SEARCH_REDUCE_ONLY) stay = false; // If not searching, just return with the new delta else if(mode == SEARCH_EACH_ITERATION) { - if(fabs(newDelta - delta) < 1e-15 || lastAction == DECREASED_DELTA) + if(std::abs(newDelta - delta) < 1e-15 || lastAction == DECREASED_DELTA) stay = false; // Searching, but Newton's solution is within trust region so keep the same trust region else { stay = true; // Searching and increased delta, so try again to increase delta diff --git a/gtsam/nonlinear/LevenbergMarquardtOptimizer.cpp b/gtsam/nonlinear/LevenbergMarquardtOptimizer.cpp index b9579661d4..c85891af2d 100644 --- a/gtsam/nonlinear/LevenbergMarquardtOptimizer.cpp +++ b/gtsam/nonlinear/LevenbergMarquardtOptimizer.cpp @@ -201,9 +201,9 @@ bool LevenbergMarquardtOptimizer::tryLambda(const GaussianFactorGraph& linear, double minAbsoluteTolerance = params_.relativeErrorTol * currentState->error; // if the change is small we terminate - if (fabs(costChange) < minAbsoluteTolerance) { + if (std::abs(costChange) < minAbsoluteTolerance) { if (verbose) - cout << "fabs(costChange)=" << fabs(costChange) + cout << "abs(costChange)=" << std::abs(costChange) << " minAbsoluteTolerance=" << minAbsoluteTolerance << " (relativeErrorTol=" << params_.relativeErrorTol << ")" << endl; stopSearchingLambda = true; diff --git a/gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h b/gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h index 0486c1f296..abf6b257aa 100644 --- a/gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h +++ b/gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h @@ -107,7 +107,7 @@ double lineSearch(const S &system, const V currentValues, const W &gradient) { newStep - resphi * (newStep - minStep); if ((maxStep - minStep) - < tau * (std::fabs(testStep) + std::fabs(newStep))) { + < tau * (std::abs(testStep) + std::abs(newStep))) { return 0.5 * (minStep + maxStep); } diff --git a/gtsam/nonlinear/tests/testExpression.cpp b/gtsam/nonlinear/tests/testExpression.cpp index 67e0d1f246..8fcf84a113 100644 --- a/gtsam/nonlinear/tests/testExpression.cpp +++ b/gtsam/nonlinear/tests/testExpression.cpp @@ -121,7 +121,7 @@ class Class : public Point3 { return norm3(*this, H); } bool equals(const Class &q, double tol) const { - return (fabs(x() - q.x()) < tol && fabs(y() - q.y()) < tol && fabs(z() - q.z()) < tol); + return (std::abs(x() - q.x()) < tol && std::abs(y() - q.y()) < tol && std::abs(z() - q.z()) < tol); } void print(const string& s) const { cout << s << *this << endl;} }; diff --git a/gtsam/slam/BetweenFactor.h b/gtsam/slam/BetweenFactor.h index 9c412eb191..f949514e39 100644 --- a/gtsam/slam/BetweenFactor.h +++ b/gtsam/slam/BetweenFactor.h @@ -146,7 +146,7 @@ namespace gtsam { /** Syntactic sugar for constrained version */ BetweenConstraint(const VALUE& measured, Key key1, Key key2, double mu = 1000.0) : BetweenFactor(key1, key2, measured, - noiseModel::Constrained::All(traits::GetDimension(measured), fabs(mu))) + noiseModel::Constrained::All(traits::GetDimension(measured), std::abs(mu))) {} private: diff --git a/gtsam_unstable/dynamics/FullIMUFactor.h b/gtsam_unstable/dynamics/FullIMUFactor.h index 1aa85b6fe8..1c5ade5b6c 100644 --- a/gtsam_unstable/dynamics/FullIMUFactor.h +++ b/gtsam_unstable/dynamics/FullIMUFactor.h @@ -61,7 +61,7 @@ class FullIMUFactor : public NoiseModelFactor2 { return f && Base::equals(e) && equal_with_abs_tol(accel_, f->accel_, tol) && equal_with_abs_tol(gyro_, f->gyro_, tol) && - fabs(dt_ - f->dt_) < tol; + std::abs(dt_ - f->dt_) < tol; } void print(const std::string& s="", const gtsam::KeyFormatter& formatter = gtsam::DefaultKeyFormatter) const { diff --git a/gtsam_unstable/dynamics/IMUFactor.h b/gtsam_unstable/dynamics/IMUFactor.h index 5ed079acb2..bb0a354eee 100644 --- a/gtsam_unstable/dynamics/IMUFactor.h +++ b/gtsam_unstable/dynamics/IMUFactor.h @@ -54,7 +54,7 @@ class IMUFactor : public NoiseModelFactor2 { return f && Base::equals(e) && equal_with_abs_tol(accel_, f->accel_, tol) && equal_with_abs_tol(gyro_, f->gyro_, tol) && - fabs(dt_ - f->dt_) < tol; + std::abs(dt_ - f->dt_) < tol; } void print(const std::string& s="", const gtsam::KeyFormatter& formatter = gtsam::DefaultKeyFormatter) const { diff --git a/gtsam_unstable/dynamics/Pendulum.h b/gtsam_unstable/dynamics/Pendulum.h index 9ec10e39ff..209456a621 100644 --- a/gtsam_unstable/dynamics/Pendulum.h +++ b/gtsam_unstable/dynamics/Pendulum.h @@ -37,7 +37,7 @@ class PendulumFactor1: public NoiseModelFactor3 { ///Constructor. k1: q_{k+1}, k: q_k, velKey: velocity variable depending on the chosen method, h: time step PendulumFactor1(Key k1, Key k, Key velKey, double h, double mu = 1000.0) - : Base(noiseModel::Constrained::All(1, fabs(mu)), k1, k, velKey), h_(h) {} + : Base(noiseModel::Constrained::All(1, std::abs(mu)), k1, k, velKey), h_(h) {} /// @return a deep copy of this factor virtual gtsam::NonlinearFactor::shared_ptr clone() const { @@ -85,7 +85,7 @@ class PendulumFactor2: public NoiseModelFactor3 { ///Constructor. vk1: v_{k+1}, vk: v_k, qkey: q's key depending on the chosen method, h: time step PendulumFactor2(Key vk1, Key vk, Key qkey, double h, double r = 1.0, double g = 9.81, double mu = 1000.0) - : Base(noiseModel::Constrained::All(1, fabs(mu)), vk1, vk, qkey), h_(h), g_(g), r_(r) {} + : Base(noiseModel::Constrained::All(1, std::abs(mu)), vk1, vk, qkey), h_(h), g_(g), r_(r) {} /// @return a deep copy of this factor virtual gtsam::NonlinearFactor::shared_ptr clone() const { @@ -135,7 +135,7 @@ class PendulumFactorPk: public NoiseModelFactor3 { ///Constructor PendulumFactorPk(Key pKey, Key qKey, Key qKey1, double h, double m = 1.0, double r = 1.0, double g = 9.81, double alpha = 0.0, double mu = 1000.0) - : Base(noiseModel::Constrained::All(1, fabs(mu)), pKey, qKey, qKey1), + : Base(noiseModel::Constrained::All(1, std::abs(mu)), pKey, qKey, qKey1), h_(h), m_(m), r_(r), g_(g), alpha_(alpha) {} /// @return a deep copy of this factor @@ -191,7 +191,7 @@ class PendulumFactorPk1: public NoiseModelFactor3 { ///Constructor PendulumFactorPk1(Key pKey1, Key qKey, Key qKey1, double h, double m = 1.0, double r = 1.0, double g = 9.81, double alpha = 0.0, double mu = 1000.0) - : Base(noiseModel::Constrained::All(1, fabs(mu)), pKey1, qKey, qKey1), + : Base(noiseModel::Constrained::All(1, std::abs(mu)), pKey1, qKey, qKey1), h_(h), m_(m), r_(r), g_(g), alpha_(alpha) {} /// @return a deep copy of this factor diff --git a/gtsam_unstable/dynamics/PoseRTV.cpp b/gtsam_unstable/dynamics/PoseRTV.cpp index c8c46ee7ba..cf21c315b9 100644 --- a/gtsam_unstable/dynamics/PoseRTV.cpp +++ b/gtsam_unstable/dynamics/PoseRTV.cpp @@ -99,7 +99,7 @@ PoseRTV PoseRTV::flyingDynamics( double yaw2 = r2.yaw(); double pitch2 = r2.pitch(); double forward_accel = -thrust * sin(pitch2); // r2, pitch (in global frame?) controls forward force - double loss_lift = lift*fabs(sin(pitch2)); + double loss_lift = lift*std::abs(sin(pitch2)); Rot3 yaw_correction_bn = Rot3::Yaw(yaw2); Point3 forward(forward_accel, 0.0, 0.0); Vector Acc_n = diff --git a/gtsam_unstable/dynamics/VelocityConstraint3.h b/gtsam_unstable/dynamics/VelocityConstraint3.h index f98879e410..721d0265b2 100644 --- a/gtsam_unstable/dynamics/VelocityConstraint3.h +++ b/gtsam_unstable/dynamics/VelocityConstraint3.h @@ -27,7 +27,7 @@ class VelocityConstraint3 : public NoiseModelFactor3 { ///TODO: comment VelocityConstraint3(Key key1, Key key2, Key velKey, double dt, double mu = 1000.0) - : Base(noiseModel::Constrained::All(1, fabs(mu)), key1, key2, velKey), dt_(dt) {} + : Base(noiseModel::Constrained::All(1, std::abs(mu)), key1, key2, velKey), dt_(dt) {} virtual ~VelocityConstraint3() {} /// @return a deep copy of this factor diff --git a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp index 24bab3feb1..9dbeeac89b 100644 --- a/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp +++ b/gtsam_unstable/examples/SmartRangeExample_plaza1.cpp @@ -198,7 +198,7 @@ int main(int argc, char** argv) { rangeNoise); // Throw out obvious outliers based on current landmark estimates Vector error = factor.unwhitenedError(landmarkEstimates); - if (k <= 200 || fabs(error[0]) < 5) + if (k <= 200 || std::abs(error[0]) < 5) newFactors.push_back(factor); } totalCount += 1; diff --git a/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp b/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp index a83eb06acc..a63a0ba20a 100644 --- a/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp +++ b/gtsam_unstable/examples/SmartRangeExample_plaza2.cpp @@ -167,7 +167,7 @@ int main(int argc, char** argv) { RangeFactor factor(i, symbol('L', j), range, rangeNoise); // Throw out obvious outliers based on current landmark estimates Vector error = factor.unwhitenedError(landmarkEstimates); - if (k <= 200 || fabs(error[0]) < 5) + if (k <= 200 || std::abs(error[0]) < 5) newFactors.push_back(factor); k = k + 1; countK = countK + 1; diff --git a/gtsam_unstable/geometry/Pose3Upright.cpp b/gtsam_unstable/geometry/Pose3Upright.cpp index 4237170f01..2a2afa4769 100644 --- a/gtsam_unstable/geometry/Pose3Upright.cpp +++ b/gtsam_unstable/geometry/Pose3Upright.cpp @@ -44,7 +44,7 @@ void Pose3Upright::print(const std::string& s) const { /* ************************************************************************* */ bool Pose3Upright::equals(const Pose3Upright& x, double tol) const { - return T_.equals(x.T_, tol) && fabs(z_ - x.z_) < tol; + return T_.equals(x.T_, tol) && std::abs(z_ - x.z_) < tol; } /* ************************************************************************* */ diff --git a/gtsam_unstable/geometry/SimPolygon2D.cpp b/gtsam_unstable/geometry/SimPolygon2D.cpp index 00bd28ec7d..eb732f2c57 100644 --- a/gtsam_unstable/geometry/SimPolygon2D.cpp +++ b/gtsam_unstable/geometry/SimPolygon2D.cpp @@ -77,7 +77,7 @@ bool SimPolygon2D::contains(const Point2& c) const { Point2 dab = ab.b() - ab.a(); Point2 dac = c - ab.a(); double cross = dab.x() * dac.y() - dab.y() * dac.x(); - if (fabs(cross) < 1e-6) // check for on one of the edges + if (std::abs(cross) < 1e-6) // check for on one of the edges return true; bool side = cross > 0; // save the first side found @@ -241,14 +241,14 @@ double SimPolygon2D::randomDistance(double mu, double sigma, double min_dist) { boost::variate_generator > gen_d(rng, norm_dist); double d = -10.0; for (size_t i=0; i min_dist) return d; } cout << "Non viable distance: " << d << " with mu = " << mu << " sigma = " << sigma << " min_dist = " << min_dist << endl; throw runtime_error("Failed to find a viable distance"); - return fabs(norm_dist(rng)); + return std::abs(norm_dist(rng)); } /* ***************************************************************** */ @@ -313,7 +313,7 @@ Pose2 SimPolygon2D::randomFreePose(double boundary_size, const vector pt) cons } // handle vertical case to avoid calculating slope - if (fabs(Ba.x() - Bb.x()) < 1e-5) { + if (std::abs(Ba.x() - Bb.x()) < 1e-5) { if (debug) cout << "vertical line" << endl; if (Ba.x() < len && Ba.x() > 0.0) { if (pt) *pt = transform.transformFrom(Point2(Ba.x(), 0.0)); @@ -88,7 +88,7 @@ bool SimWall2D::intersects(const SimWall2D& B, boost::optional pt) cons // find x-intercept double slope = (high.y() - low.y())/(high.x() - low.x()); if (debug) cout << "slope " << slope << endl; - double xint = (low.x() < high.x()) ? low.x() + fabs(low.y())/slope : high.x() - fabs(high.y())/slope; + double xint = (low.x() < high.x()) ? low.x() + std::abs(low.y())/slope : high.x() - std::abs(high.y())/slope; if (debug) cout << "xintercept " << xint << endl; if (xint > 0.0 && xint < len) { if (pt) *pt = transform.transformFrom(Point2(xint, 0.0)); diff --git a/gtsam_unstable/linear/ActiveSetSolver-inl.h b/gtsam_unstable/linear/ActiveSetSolver-inl.h index 18dc07aec5..602012090a 100644 --- a/gtsam_unstable/linear/ActiveSetSolver-inl.h +++ b/gtsam_unstable/linear/ActiveSetSolver-inl.h @@ -252,7 +252,7 @@ Template InequalityFactorGraph This::identifyActiveConstraints( double error = workingFactor->error(initialValues); // Safety guard. This should not happen unless users provide a bad init if (error > 0) throw InfeasibleInitialValues(); - if (fabs(error) < 1e-7) + if (std::abs(error) < 1e-7) workingFactor->activate(); else workingFactor->inactivate(); diff --git a/gtsam_unstable/nonlinear/FixedLagSmoother.cpp b/gtsam_unstable/nonlinear/FixedLagSmoother.cpp index 9c04f0eecc..34a23982f2 100644 --- a/gtsam_unstable/nonlinear/FixedLagSmoother.cpp +++ b/gtsam_unstable/nonlinear/FixedLagSmoother.cpp @@ -38,7 +38,7 @@ void FixedLagSmoother::print(const std::string& s, const KeyFormatter& keyFormat /* ************************************************************************* */ bool FixedLagSmoother::equals(const FixedLagSmoother& rhs, double tol) const { - return std::fabs(smootherLag_ - rhs.smootherLag_) < tol + return std::abs(smootherLag_ - rhs.smootherLag_) < tol && std::equal(timestampKeyMap_.begin(), timestampKeyMap_.end(), rhs.timestampKeyMap_.begin()); } diff --git a/gtsam_unstable/slam/AHRS.cpp b/gtsam_unstable/slam/AHRS.cpp index 0a3fa02839..ef03eb2c2d 100644 --- a/gtsam_unstable/slam/AHRS.cpp +++ b/gtsam_unstable/slam/AHRS.cpp @@ -152,7 +152,7 @@ bool AHRS::isAidingAvailable(const Mechanization_bRn2& mech, double mu_f = f_.norm() - ge; // accelerometer same magnitude as local gravity ? double mu_u = u_.norm(); // gyro says we are not maneuvering ? - return (fabs(mu_f)<0.5 && mu_u < 5.0 / 180.0 * M_PI); + return (std::abs(mu_f)<0.5 && mu_u < 5.0 / 180.0 * M_PI); } /* ************************************************************************* */ diff --git a/gtsam_unstable/slam/RelativeElevationFactor.cpp b/gtsam_unstable/slam/RelativeElevationFactor.cpp index db077994fe..32e8731cd8 100644 --- a/gtsam_unstable/slam/RelativeElevationFactor.cpp +++ b/gtsam_unstable/slam/RelativeElevationFactor.cpp @@ -38,7 +38,7 @@ Vector RelativeElevationFactor::evaluateError(const Pose3& pose, const Point3& p /* ************************************************************************* */ bool RelativeElevationFactor::equals(const NonlinearFactor& expected, double tol) const { const This *e = dynamic_cast (&expected); - return e != NULL && Base::equals(*e, tol) && fabs(this->measured_ - e->measured_) < tol; + return e != NULL && Base::equals(*e, tol) && std::abs(this->measured_ - e->measured_) < tol; } /* ************************************************************************* */ From 60f5ee7cf1196cfc4960a68b3786be133df7e955 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 18 Sep 2019 16:13:09 -0400 Subject: [PATCH 158/160] added test for negative error in Huber Robust noise model --- gtsam/linear/tests/testNoiseModel.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gtsam/linear/tests/testNoiseModel.cpp b/gtsam/linear/tests/testNoiseModel.cpp index 6b130bea0c..f8ce1fecae 100644 --- a/gtsam/linear/tests/testNoiseModel.cpp +++ b/gtsam/linear/tests/testNoiseModel.cpp @@ -452,19 +452,24 @@ TEST(NoiseModel, WhitenInPlace) /* * These tests are responsible for testing the weight functions for the m-estimators in GTSAM. * The weight function is related to the analytic derivative of the residual function. See - * http://research.microsoft.com/en-us/um/people/zhang/INRIA/Publis/Tutorial-Estim/node24.html + * https://members.loria.fr/MOBerger/Enseignement/Master2/Documents/ZhangIVC-97-01.pdf * for details. This weight function is required when optimizing cost functions with robust * penalties using iteratively re-weighted least squares. */ TEST(NoiseModel, robustFunctionHuber) { - const double k = 5.0, error1 = 1.0, error2 = 10.0; + const double k = 5.0, error1 = 1.0, error2 = 10.0, error3 = -10.0, error4 = -1.0; const mEstimator::Huber::shared_ptr huber = mEstimator::Huber::Create(k); const double weight1 = huber->weight(error1), - weight2 = huber->weight(error2); + weight2 = huber->weight(error2), + weight3 = huber->weight(error3), + weight4 = huber->weight(error4); DOUBLES_EQUAL(1.0, weight1, 1e-8); DOUBLES_EQUAL(0.5, weight2, 1e-8); + // Test negative value to ensure we take absolute value of error. + DOUBLES_EQUAL(0.5, weight3, 1e-8); + DOUBLES_EQUAL(1.0, weight4, 1e-8); } TEST(NoiseModel, robustFunctionGemanMcClure) From ba22688ca066c81c361e7daacaf2e2cba6420990 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 18 Sep 2019 18:16:17 -0400 Subject: [PATCH 159/160] compare absolute value of error against k for Huber noise model --- gtsam/linear/NoiseModel.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gtsam/linear/NoiseModel.h b/gtsam/linear/NoiseModel.h index aa89be9825..d4a955fd45 100644 --- a/gtsam/linear/NoiseModel.h +++ b/gtsam/linear/NoiseModel.h @@ -629,7 +629,7 @@ namespace gtsam { /** * The mEstimator name space contains all robust error functions. * It mirrors the exposition at - * http://research.microsoft.com/en-us/um/people/zhang/INRIA/Publis/Tutorial-Estim/node24.html + * https://members.loria.fr/MOBerger/Enseignement/Master2/Documents/ZhangIVC-97-01.pdf * which talks about minimizing \sum \rho(r_i), where \rho is a residual function of choice. * * To illustrate, let's consider the least-squares (L2), L1, and Huber estimators as examples: @@ -681,7 +681,7 @@ namespace gtsam { /* * This method is responsible for returning the weight function for a given amount of error. * The weight function is related to the analytic derivative of the residual function. See - * http://research.microsoft.com/en-us/um/people/zhang/INRIA/Publis/Tutorial-Estim/node24.html + * https://members.loria.fr/MOBerger/Enseignement/Master2/Documents/ZhangIVC-97-01.pdf * for details. This method is required when optimizing cost functions with robust penalties * using iteratively re-weighted least squares. */ @@ -776,7 +776,7 @@ namespace gtsam { Huber(double k = 1.345, const ReweightScheme reweight = Block); double weight(double error) const { - return (error < k_) ? (1.0) : (k_ / fabs(error)); + return (std::abs(error) < k_) ? (1.0) : (k_ / fabs(error)); } void print(const std::string &s) const; bool equals(const Base& expected, double tol=1e-8) const; From 813f169d4a24c9b858b96f833dc6ad690bc2d5f8 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Wed, 18 Sep 2019 20:01:24 -0400 Subject: [PATCH 160/160] improved code for Huber robust noise model --- gtsam/linear/NoiseModel.h | 3 ++- gtsam/linear/tests/testNoiseModel.cpp | 12 ++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/gtsam/linear/NoiseModel.h b/gtsam/linear/NoiseModel.h index d4a955fd45..5aa7740262 100644 --- a/gtsam/linear/NoiseModel.h +++ b/gtsam/linear/NoiseModel.h @@ -776,7 +776,8 @@ namespace gtsam { Huber(double k = 1.345, const ReweightScheme reweight = Block); double weight(double error) const { - return (std::abs(error) < k_) ? (1.0) : (k_ / fabs(error)); + double absError = std::abs(error); + return (absError < k_) ? (1.0) : (k_ / absError); } void print(const std::string &s) const; bool equals(const Base& expected, double tol=1e-8) const; diff --git a/gtsam/linear/tests/testNoiseModel.cpp b/gtsam/linear/tests/testNoiseModel.cpp index f8ce1fecae..85dc4735dd 100644 --- a/gtsam/linear/tests/testNoiseModel.cpp +++ b/gtsam/linear/tests/testNoiseModel.cpp @@ -461,15 +461,11 @@ TEST(NoiseModel, robustFunctionHuber) { const double k = 5.0, error1 = 1.0, error2 = 10.0, error3 = -10.0, error4 = -1.0; const mEstimator::Huber::shared_ptr huber = mEstimator::Huber::Create(k); - const double weight1 = huber->weight(error1), - weight2 = huber->weight(error2), - weight3 = huber->weight(error3), - weight4 = huber->weight(error4); - DOUBLES_EQUAL(1.0, weight1, 1e-8); - DOUBLES_EQUAL(0.5, weight2, 1e-8); + DOUBLES_EQUAL(1.0, huber->weight(error1), 1e-8); + DOUBLES_EQUAL(0.5, huber->weight(error2), 1e-8); // Test negative value to ensure we take absolute value of error. - DOUBLES_EQUAL(0.5, weight3, 1e-8); - DOUBLES_EQUAL(1.0, weight4, 1e-8); + DOUBLES_EQUAL(0.5, huber->weight(error3), 1e-8); + DOUBLES_EQUAL(1.0, huber->weight(error4), 1e-8); } TEST(NoiseModel, robustFunctionGemanMcClure)