669 lines
23 KiB
C++
669 lines
23 KiB
C++
/* ----------------------------------------------------------------------------
|
|
|
|
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
|
|
* Atlanta, Georgia 30332-0415
|
|
* All Rights Reserved
|
|
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
|
|
|
|
* See LICENSE for the license information
|
|
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
/**
|
|
* @file testNonlinearOptimizer.cpp
|
|
* @brief Unit tests for NonlinearOptimizer class
|
|
* @author Frank Dellaert
|
|
*/
|
|
|
|
#include <tests/smallExample.h>
|
|
#include <gtsam/slam/BetweenFactor.h>
|
|
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
|
|
#include <gtsam/nonlinear/Values.h>
|
|
#include <gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h>
|
|
#include <gtsam/nonlinear/GaussNewtonOptimizer.h>
|
|
#include <gtsam/nonlinear/DoglegOptimizer.h>
|
|
#include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h>
|
|
#include <gtsam/linear/GaussianFactorGraph.h>
|
|
#include <gtsam/linear/NoiseModel.h>
|
|
#include <gtsam/inference/Symbol.h>
|
|
#include <gtsam/geometry/Pose2.h>
|
|
#include <gtsam/base/Matrix.h>
|
|
|
|
#include <CppUnitLite/TestHarness.h>
|
|
|
|
#include <boost/range/adaptor/map.hpp>
|
|
#include <boost/shared_ptr.hpp>
|
|
using boost::adaptors::map_values;
|
|
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
using namespace std;
|
|
using namespace gtsam;
|
|
|
|
const double tol = 1e-5;
|
|
|
|
using symbol_shorthand::X;
|
|
using symbol_shorthand::L;
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, paramsEquals )
|
|
{
|
|
// default constructors lead to two identical params
|
|
GaussNewtonParams gnParams1;
|
|
GaussNewtonParams gnParams2;
|
|
CHECK(gnParams1.equals(gnParams2));
|
|
|
|
// but the params become different if we change something in gnParams2
|
|
gnParams2.setVerbosity("DELTA");
|
|
CHECK(!gnParams1.equals(gnParams2));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, iterateLM )
|
|
{
|
|
// really non-linear factor graph
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
// config far from minimum
|
|
Point2 x0(3,0);
|
|
Values config;
|
|
config.insert(X(1), x0);
|
|
|
|
// normal iterate
|
|
GaussNewtonParams gnParams;
|
|
GaussNewtonOptimizer gnOptimizer(fg, config, gnParams);
|
|
gnOptimizer.iterate();
|
|
|
|
// LM iterate with lambda 0 should be the same
|
|
LevenbergMarquardtParams lmParams;
|
|
lmParams.lambdaInitial = 0.0;
|
|
LevenbergMarquardtOptimizer lmOptimizer(fg, config, lmParams);
|
|
lmOptimizer.iterate();
|
|
|
|
CHECK(assert_equal(gnOptimizer.values(), lmOptimizer.values(), 1e-9));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, optimize )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
// test error at minimum
|
|
Point2 xstar(0,0);
|
|
Values cstar;
|
|
cstar.insert(X(1), xstar);
|
|
DOUBLES_EQUAL(0.0,fg.error(cstar),0.0);
|
|
|
|
// test error at initial = [(1-cos(3))^2 + (sin(3))^2]*50 =
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
DOUBLES_EQUAL(199.0,fg.error(c0),1e-3);
|
|
|
|
// optimize parameters
|
|
Ordering ord;
|
|
ord.push_back(X(1));
|
|
|
|
// Gauss-Newton
|
|
GaussNewtonParams gnParams;
|
|
gnParams.ordering = ord;
|
|
Values actual1 = GaussNewtonOptimizer(fg, c0, gnParams).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual1),tol);
|
|
|
|
// Levenberg-Marquardt
|
|
LevenbergMarquardtParams lmParams;
|
|
lmParams.ordering = ord;
|
|
Values actual2 = LevenbergMarquardtOptimizer(fg, c0, lmParams).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual2),tol);
|
|
|
|
// Dogleg
|
|
DoglegParams dlParams;
|
|
dlParams.ordering = ord;
|
|
Values actual3 = DoglegOptimizer(fg, c0, dlParams).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual3),tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, SimpleLMOptimizer )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
Values actual = LevenbergMarquardtOptimizer(fg, c0).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual),tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, SimpleGNOptimizer )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
Values actual = GaussNewtonOptimizer(fg, c0).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual),tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, SimpleDLOptimizer )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
Values actual = DoglegOptimizer(fg, c0).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual),tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, optimization_method )
|
|
{
|
|
LevenbergMarquardtParams paramsQR;
|
|
paramsQR.linearSolverType = LevenbergMarquardtParams::MULTIFRONTAL_QR;
|
|
LevenbergMarquardtParams paramsChol;
|
|
paramsChol.linearSolverType = LevenbergMarquardtParams::MULTIFRONTAL_CHOLESKY;
|
|
|
|
NonlinearFactorGraph fg = example::createReallyNonlinearFactorGraph();
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
Values actualMFQR = LevenbergMarquardtOptimizer(fg, c0, paramsQR).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actualMFQR),tol);
|
|
|
|
Values actualMFChol = LevenbergMarquardtOptimizer(fg, c0, paramsChol).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actualMFChol),tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, Factorization )
|
|
{
|
|
Values config;
|
|
config.insert(X(1), Pose2(0.,0.,0.));
|
|
config.insert(X(2), Pose2(1.5,0.,0.));
|
|
|
|
NonlinearFactorGraph graph;
|
|
graph.addPrior(X(1), Pose2(0.,0.,0.), noiseModel::Isotropic::Sigma(3, 1e-10));
|
|
graph += BetweenFactor<Pose2>(X(1),X(2), Pose2(1.,0.,0.), noiseModel::Isotropic::Sigma(3, 1));
|
|
|
|
Ordering ordering;
|
|
ordering.push_back(X(1));
|
|
ordering.push_back(X(2));
|
|
|
|
LevenbergMarquardtParams params;
|
|
LevenbergMarquardtParams::SetLegacyDefaults(¶ms);
|
|
LevenbergMarquardtOptimizer optimizer(graph, config, ordering, params);
|
|
optimizer.iterate();
|
|
|
|
Values expected;
|
|
expected.insert(X(1), Pose2(0.,0.,0.));
|
|
expected.insert(X(2), Pose2(1.,0.,0.));
|
|
CHECK(assert_equal(expected, optimizer.values(), 1e-5));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, NullFactor) {
|
|
|
|
NonlinearFactorGraph fg = example::createReallyNonlinearFactorGraph();
|
|
|
|
// Add null factor
|
|
fg.push_back(NonlinearFactorGraph::sharedFactor());
|
|
|
|
// test error at minimum
|
|
Point2 xstar(0,0);
|
|
Values cstar;
|
|
cstar.insert(X(1), xstar);
|
|
DOUBLES_EQUAL(0.0,fg.error(cstar),0.0);
|
|
|
|
// test error at initial = [(1-cos(3))^2 + (sin(3))^2]*50 =
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
DOUBLES_EQUAL(199.0,fg.error(c0),1e-3);
|
|
|
|
// optimize parameters
|
|
Ordering ord;
|
|
ord.push_back(X(1));
|
|
|
|
// Gauss-Newton
|
|
Values actual1 = GaussNewtonOptimizer(fg, c0, ord).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual1),tol);
|
|
|
|
// Levenberg-Marquardt
|
|
Values actual2 = LevenbergMarquardtOptimizer(fg, c0, ord).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual2),tol);
|
|
|
|
// Dogleg
|
|
Values actual3 = DoglegOptimizer(fg, c0, ord).optimize();
|
|
DOUBLES_EQUAL(0,fg.error(actual3),tol);
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST_UNSAFE(NonlinearOptimizer, MoreOptimization) {
|
|
|
|
NonlinearFactorGraph fg;
|
|
fg.addPrior(0, Pose2(0, 0, 0), noiseModel::Isotropic::Sigma(3, 1));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(1, 0, M_PI / 2),
|
|
noiseModel::Isotropic::Sigma(3, 1));
|
|
fg += BetweenFactor<Pose2>(1, 2, Pose2(1, 0, M_PI / 2),
|
|
noiseModel::Isotropic::Sigma(3, 1));
|
|
|
|
Values init;
|
|
init.insert(0, Pose2(3, 4, -M_PI));
|
|
init.insert(1, Pose2(10, 2, -M_PI));
|
|
init.insert(2, Pose2(11, 7, -M_PI));
|
|
|
|
Values expected;
|
|
expected.insert(0, Pose2(0, 0, 0));
|
|
expected.insert(1, Pose2(1, 0, M_PI / 2));
|
|
expected.insert(2, Pose2(1, 1, M_PI));
|
|
|
|
VectorValues expectedGradient;
|
|
expectedGradient.insert(0,Z_3x1);
|
|
expectedGradient.insert(1,Z_3x1);
|
|
expectedGradient.insert(2,Z_3x1);
|
|
|
|
// Try LM and Dogleg
|
|
LevenbergMarquardtParams params = LevenbergMarquardtParams::LegacyDefaults();
|
|
{
|
|
LevenbergMarquardtOptimizer optimizer(fg, init, params);
|
|
|
|
// test convergence
|
|
Values actual = optimizer.optimize();
|
|
EXPECT(assert_equal(expected, actual));
|
|
|
|
// Check that the gradient is zero
|
|
GaussianFactorGraph::shared_ptr linear = optimizer.linearize();
|
|
EXPECT(assert_equal(expectedGradient,linear->gradientAtZero()));
|
|
}
|
|
EXPECT(assert_equal(expected, DoglegOptimizer(fg, init).optimize()));
|
|
|
|
// cout << "===================================================================================" << endl;
|
|
|
|
// Try LM with diagonal damping
|
|
Values initBetter;
|
|
initBetter.insert(0, Pose2(3,4,0));
|
|
initBetter.insert(1, Pose2(10,2,M_PI/3));
|
|
initBetter.insert(2, Pose2(11,7,M_PI/2));
|
|
|
|
{
|
|
params.diagonalDamping = true;
|
|
LevenbergMarquardtOptimizer optimizer(fg, initBetter, params);
|
|
|
|
// test the diagonal
|
|
GaussianFactorGraph::shared_ptr linear = optimizer.linearize();
|
|
VectorValues d = linear->hessianDiagonal();
|
|
VectorValues sqrtHessianDiagonal = d;
|
|
for (Vector& v : sqrtHessianDiagonal | map_values) v = v.cwiseSqrt();
|
|
GaussianFactorGraph damped = optimizer.buildDampedSystem(*linear, sqrtHessianDiagonal);
|
|
VectorValues expectedDiagonal = d + params.lambdaInitial * d;
|
|
EXPECT(assert_equal(expectedDiagonal, damped.hessianDiagonal()));
|
|
|
|
// test convergence (does not!)
|
|
Values actual = optimizer.optimize();
|
|
EXPECT(assert_equal(expected, actual));
|
|
|
|
// Check that the gradient is zero (it is not!)
|
|
linear = optimizer.linearize();
|
|
EXPECT(assert_equal(expectedGradient,linear->gradientAtZero()));
|
|
|
|
// Check that the gradient is zero for damped system (it is not!)
|
|
damped = optimizer.buildDampedSystem(*linear, sqrtHessianDiagonal);
|
|
VectorValues actualGradient = damped.gradientAtZero();
|
|
EXPECT(assert_equal(expectedGradient,actualGradient));
|
|
|
|
/* This block was made to test the original initial guess "init"
|
|
// Check errors at convergence and errors in direction of gradient (decreases!)
|
|
EXPECT_DOUBLES_EQUAL(46.02558,fg.error(actual),1e-5);
|
|
EXPECT_DOUBLES_EQUAL(44.742237,fg.error(actual.retract(-0.01*actualGradient)),1e-5);
|
|
|
|
// Check that solve yields gradient (it's not equal to gradient, as predicted)
|
|
VectorValues delta = damped.optimize();
|
|
double factor = actualGradient[0][0]/delta[0][0];
|
|
EXPECT(assert_equal(actualGradient,factor*delta));
|
|
|
|
// Still pointing downhill wrt actual gradient !
|
|
EXPECT_DOUBLES_EQUAL( 0.1056157,dot(-1*actualGradient,delta),1e-3);
|
|
|
|
// delta.print("This is the delta value computed by LM with diagonal damping");
|
|
|
|
// Still pointing downhill wrt expected gradient (IT DOES NOT! actually they are perpendicular)
|
|
EXPECT_DOUBLES_EQUAL( 0.0,dot(-1*expectedGradient,delta),1e-5);
|
|
|
|
// Check errors at convergence and errors in direction of solution (does not decrease!)
|
|
EXPECT_DOUBLES_EQUAL(46.0254859,fg.error(actual.retract(delta)),1e-5);
|
|
|
|
// Check errors at convergence and errors at a small step in direction of solution (does not decrease!)
|
|
EXPECT_DOUBLES_EQUAL(46.0255,fg.error(actual.retract(0.01*delta)),1e-3);
|
|
*/
|
|
}
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, Pose2OptimizationWithHuberNoOutlier) {
|
|
|
|
NonlinearFactorGraph fg;
|
|
fg.addPrior(0, Pose2(0,0,0), noiseModel::Isotropic::Sigma(3,1));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(1,1.1,M_PI/4),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(2.0),
|
|
noiseModel::Isotropic::Sigma(3,1)));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(1,0.9,M_PI/2),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(3.0),
|
|
noiseModel::Isotropic::Sigma(3,1)));
|
|
|
|
Values init;
|
|
init.insert(0, Pose2(0,0,0));
|
|
init.insert(1, Pose2(0.961187, 0.99965, 1.1781));
|
|
|
|
Values expected;
|
|
expected.insert(0, Pose2(0,0,0));
|
|
expected.insert(1, Pose2(0.961187, 0.99965, 1.1781));
|
|
|
|
LevenbergMarquardtParams lm_params;
|
|
|
|
auto gn_result = GaussNewtonOptimizer(fg, init).optimize();
|
|
auto lm_result = LevenbergMarquardtOptimizer(fg, init, lm_params).optimize();
|
|
auto dl_result = DoglegOptimizer(fg, init).optimize();
|
|
|
|
EXPECT(assert_equal(expected, gn_result, 3e-2));
|
|
EXPECT(assert_equal(expected, lm_result, 3e-2));
|
|
EXPECT(assert_equal(expected, dl_result, 3e-2));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, Point2LinearOptimizationWithHuber) {
|
|
|
|
NonlinearFactorGraph fg;
|
|
fg.addPrior(0, Point2(0,0), noiseModel::Isotropic::Sigma(2,0.01));
|
|
fg += BetweenFactor<Point2>(0, 1, Point2(1,1.8),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(1.0),
|
|
noiseModel::Isotropic::Sigma(2,1)));
|
|
fg += BetweenFactor<Point2>(0, 1, Point2(1,0.9),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(1.0),
|
|
noiseModel::Isotropic::Sigma(2,1)));
|
|
fg += BetweenFactor<Point2>(0, 1, Point2(1,90),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(1.0),
|
|
noiseModel::Isotropic::Sigma(2,1)));
|
|
|
|
Values init;
|
|
init.insert(0, Point2(1,1));
|
|
init.insert(1, Point2(1,0));
|
|
|
|
Values expected;
|
|
expected.insert(0, Point2(0,0));
|
|
expected.insert(1, Point2(1,1.85));
|
|
|
|
LevenbergMarquardtParams params;
|
|
|
|
auto gn_result = GaussNewtonOptimizer(fg, init).optimize();
|
|
auto lm_result = LevenbergMarquardtOptimizer(fg, init, params).optimize();
|
|
auto dl_result = DoglegOptimizer(fg, init).optimize();
|
|
|
|
EXPECT(assert_equal(expected, gn_result, 1e-4));
|
|
EXPECT(assert_equal(expected, lm_result, 1e-4));
|
|
EXPECT(assert_equal(expected, dl_result, 1e-4));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, Pose2OptimizationWithHuber) {
|
|
|
|
NonlinearFactorGraph fg;
|
|
fg.addPrior(0, Pose2(0,0, 0), noiseModel::Isotropic::Sigma(3,0.1));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(0,9, M_PI/2),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(0.2),
|
|
noiseModel::Isotropic::Sigma(3,1)));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(0, 11, M_PI/2),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(0.2),
|
|
noiseModel::Isotropic::Sigma(3,1)));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(0, 10, M_PI/2),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(0.2),
|
|
noiseModel::Isotropic::Sigma(3,1)));
|
|
fg += BetweenFactor<Pose2>(0, 1, Pose2(0,9, 0),
|
|
noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(0.2),
|
|
noiseModel::Isotropic::Sigma(3,1)));
|
|
|
|
Values init;
|
|
init.insert(0, Pose2(0, 0, 0));
|
|
init.insert(1, Pose2(0, 10, M_PI/4));
|
|
|
|
Values expected;
|
|
expected.insert(0, Pose2(0, 0, 0));
|
|
expected.insert(1, Pose2(0, 10, 1.45212));
|
|
|
|
LevenbergMarquardtParams params;
|
|
|
|
auto gn_result = GaussNewtonOptimizer(fg, init).optimize();
|
|
auto lm_result = LevenbergMarquardtOptimizer(fg, init, params).optimize();
|
|
auto dl_result = DoglegOptimizer(fg, init).optimize();
|
|
|
|
EXPECT(assert_equal(expected, gn_result, 1e-1));
|
|
EXPECT(assert_equal(expected, lm_result, 1e-1));
|
|
EXPECT(assert_equal(expected, dl_result, 1e-1));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, RobustMeanCalculation) {
|
|
|
|
NonlinearFactorGraph fg;
|
|
|
|
Values init;
|
|
|
|
Values expected;
|
|
|
|
auto huber = noiseModel::Robust::Create(noiseModel::mEstimator::Huber::Create(20),
|
|
noiseModel::Isotropic::Sigma(1, 1));
|
|
|
|
vector<double> pts{-10,-3,-1,1,3,10,1000};
|
|
for(auto pt : pts) {
|
|
fg.addPrior(0, pt, huber);
|
|
}
|
|
|
|
init.insert(0, 100.0);
|
|
expected.insert(0, 3.33333333);
|
|
|
|
DoglegParams params_dl;
|
|
params_dl.setRelativeErrorTol(1e-10);
|
|
|
|
auto gn_result = GaussNewtonOptimizer(fg, init).optimize();
|
|
auto lm_result = LevenbergMarquardtOptimizer(fg, init).optimize();
|
|
auto dl_result = DoglegOptimizer(fg, init, params_dl).optimize();
|
|
|
|
EXPECT(assert_equal(expected, gn_result, tol));
|
|
EXPECT(assert_equal(expected, lm_result, tol));
|
|
EXPECT(assert_equal(expected, dl_result, tol));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, disconnected_graph) {
|
|
Values expected;
|
|
expected.insert(X(1), Pose2(0.,0.,0.));
|
|
expected.insert(X(2), Pose2(1.5,0.,0.));
|
|
expected.insert(X(3), Pose2(3.0,0.,0.));
|
|
|
|
Values init;
|
|
init.insert(X(1), Pose2(0.,0.,0.));
|
|
init.insert(X(2), Pose2(0.,0.,0.));
|
|
init.insert(X(3), Pose2(0.,0.,0.));
|
|
|
|
NonlinearFactorGraph graph;
|
|
graph.addPrior(X(1), Pose2(0.,0.,0.), noiseModel::Isotropic::Sigma(3,1));
|
|
graph += BetweenFactor<Pose2>(X(1),X(2), Pose2(1.5,0.,0.), noiseModel::Isotropic::Sigma(3,1));
|
|
graph.addPrior(X(3), Pose2(3.,0.,0.), noiseModel::Isotropic::Sigma(3,1));
|
|
|
|
EXPECT(assert_equal(expected, LevenbergMarquardtOptimizer(graph, init).optimize()));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
#include <gtsam/linear/iterative.h>
|
|
|
|
class IterativeLM : public LevenbergMarquardtOptimizer {
|
|
/// Solver specific parameters
|
|
ConjugateGradientParameters cgParams_;
|
|
Values initial_;
|
|
|
|
public:
|
|
/// Constructor
|
|
IterativeLM(const NonlinearFactorGraph& graph, const Values& initialValues,
|
|
const ConjugateGradientParameters& p,
|
|
const LevenbergMarquardtParams& params =
|
|
LevenbergMarquardtParams::LegacyDefaults())
|
|
: LevenbergMarquardtOptimizer(graph, initialValues, params),
|
|
cgParams_(p),
|
|
initial_(initialValues) {}
|
|
|
|
/// Solve that uses conjugate gradient
|
|
VectorValues solve(const GaussianFactorGraph& gfg,
|
|
const NonlinearOptimizerParams& params) const override {
|
|
VectorValues zeros = initial_.zeroVectors();
|
|
return conjugateGradientDescent(gfg, zeros, cgParams_);
|
|
}
|
|
};
|
|
|
|
/* ************************************************************************* */
|
|
TEST(NonlinearOptimizer, subclass_solver) {
|
|
Values expected;
|
|
expected.insert(X(1), Pose2(0., 0., 0.));
|
|
expected.insert(X(2), Pose2(1.5, 0., 0.));
|
|
expected.insert(X(3), Pose2(3.0, 0., 0.));
|
|
|
|
Values init;
|
|
init.insert(X(1), Pose2(0., 0., 0.));
|
|
init.insert(X(2), Pose2(0., 0., 0.));
|
|
init.insert(X(3), Pose2(0., 0., 0.));
|
|
|
|
NonlinearFactorGraph graph;
|
|
graph.addPrior(X(1), Pose2(0., 0., 0.), noiseModel::Isotropic::Sigma(3, 1));
|
|
graph += BetweenFactor<Pose2>(X(1), X(2), Pose2(1.5, 0., 0.),
|
|
noiseModel::Isotropic::Sigma(3, 1));
|
|
graph.addPrior(X(3), Pose2(3., 0., 0.), noiseModel::Isotropic::Sigma(3, 1));
|
|
|
|
ConjugateGradientParameters p;
|
|
Values actual = IterativeLM(graph, init, p).optimize();
|
|
EXPECT(assert_equal(expected, actual, 1e-4));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, logfile )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
// Levenberg-Marquardt
|
|
LevenbergMarquardtParams lmParams;
|
|
static const string filename("testNonlinearOptimizer.log");
|
|
lmParams.logFile = filename;
|
|
LevenbergMarquardtOptimizer(fg, c0, lmParams).optimize();
|
|
|
|
// stringstream expected,actual;
|
|
// ifstream ifs(("../../gtsam/tests/" + filename).c_str());
|
|
// if(!ifs) throw std::runtime_error(filename);
|
|
// expected << ifs.rdbuf();
|
|
// ifs.close();
|
|
// ifstream ifs2(filename.c_str());
|
|
// if(!ifs2) throw std::runtime_error(filename);
|
|
// actual << ifs2.rdbuf();
|
|
// EXPECT(actual.str()==expected.str());
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, iterationHook_LM )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
// Levenberg-Marquardt
|
|
LevenbergMarquardtParams lmParams;
|
|
size_t lastIterCalled = 0;
|
|
lmParams.iterationHook = [&](size_t iteration, double oldError, double newError)
|
|
{
|
|
// Tests:
|
|
lastIterCalled = iteration;
|
|
EXPECT(newError<oldError);
|
|
|
|
// Example of evolution printout:
|
|
//std::cout << "iter: " << iteration << " error: " << oldError << " => " << newError <<"\n";
|
|
};
|
|
LevenbergMarquardtOptimizer(fg, c0, lmParams).optimize();
|
|
|
|
EXPECT(lastIterCalled>5);
|
|
}
|
|
/* ************************************************************************* */
|
|
TEST( NonlinearOptimizer, iterationHook_CG )
|
|
{
|
|
NonlinearFactorGraph fg(example::createReallyNonlinearFactorGraph());
|
|
|
|
Point2 x0(3,3);
|
|
Values c0;
|
|
c0.insert(X(1), x0);
|
|
|
|
// Levenberg-Marquardt
|
|
NonlinearConjugateGradientOptimizer::Parameters cgParams;
|
|
size_t lastIterCalled = 0;
|
|
cgParams.iterationHook = [&](size_t iteration, double oldError, double newError)
|
|
{
|
|
// Tests:
|
|
lastIterCalled = iteration;
|
|
EXPECT(newError<oldError);
|
|
|
|
// Example of evolution printout:
|
|
//std::cout << "iter: " << iteration << " error: " << oldError << " => " << newError <<"\n";
|
|
};
|
|
NonlinearConjugateGradientOptimizer(fg, c0, cgParams).optimize();
|
|
|
|
EXPECT(lastIterCalled>5);
|
|
}
|
|
|
|
|
|
/* ************************************************************************* */
|
|
//// Minimal traits example
|
|
struct MyType : public Vector3 {
|
|
using Vector3::Vector3;
|
|
};
|
|
|
|
namespace gtsam {
|
|
template <>
|
|
struct traits<MyType> {
|
|
static bool Equals(const MyType& a, const MyType& b, double tol) {
|
|
return (a - b).array().abs().maxCoeff() < tol;
|
|
}
|
|
static void Print(const MyType&, const string&) {}
|
|
static int GetDimension(const MyType&) { return 3; }
|
|
static MyType Retract(const MyType& a, const Vector3& b) { return a + b; }
|
|
static Vector3 Local(const MyType& a, const MyType& b) { return b - a; }
|
|
};
|
|
}
|
|
|
|
TEST(NonlinearOptimizer, Traits) {
|
|
NonlinearFactorGraph fg;
|
|
fg.addPrior(0, MyType(0, 0, 0), noiseModel::Isotropic::Sigma(3, 1));
|
|
|
|
Values init;
|
|
init.insert(0, MyType(0,0,0));
|
|
|
|
LevenbergMarquardtOptimizer optimizer(fg, init);
|
|
Values actual = optimizer.optimize();
|
|
EXPECT(assert_equal(init, actual));
|
|
}
|
|
|
|
/* ************************************************************************* */
|
|
int main() {
|
|
TestResult tr;
|
|
return TestRegistry::runAllTests(tr);
|
|
}
|
|
/* ************************************************************************* */
|