2010-10-14 12:54:38 +08:00
|
|
|
/* ----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
* 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
|
|
|
|
|
|
|
|
|
|
* -------------------------------------------------------------------------- */
|
|
|
|
|
|
2009-09-09 12:43:04 +08:00
|
|
|
/**
|
2011-10-14 11:23:14 +08:00
|
|
|
* @file NonlinearOptimizer-inl.h
|
2009-09-13 12:13:03 +08:00
|
|
|
* This is a template definition file, include it where needed (only!)
|
|
|
|
|
* so that the appropriate code is generated and link errors avoided.
|
2009-09-09 12:43:04 +08:00
|
|
|
* @brief: Encapsulates nonlinear optimization state
|
2011-09-07 09:26:28 +08:00
|
|
|
* @author Frank Dellaert
|
2011-10-14 11:23:14 +08:00
|
|
|
* @date Sep 7, 2009
|
2009-09-09 12:43:04 +08:00
|
|
|
*/
|
|
|
|
|
|
2009-09-13 12:13:03 +08:00
|
|
|
#pragma once
|
|
|
|
|
|
2009-09-09 12:43:04 +08:00
|
|
|
#include <iostream>
|
|
|
|
|
#include <boost/tuple/tuple.hpp>
|
2011-08-25 04:51:54 +08:00
|
|
|
#include <gtsam/base/cholesky.h>
|
2010-08-20 01:23:19 +08:00
|
|
|
#include <gtsam/nonlinear/NonlinearOptimizer.h>
|
2009-09-09 12:43:04 +08:00
|
|
|
|
2010-01-16 09:16:59 +08:00
|
|
|
#define INSTANTIATE_NONLINEAR_OPTIMIZER(G,C) \
|
|
|
|
|
template class NonlinearOptimizer<G,C>;
|
|
|
|
|
|
2009-09-09 12:43:04 +08:00
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
namespace gtsam {
|
|
|
|
|
|
|
|
|
|
/* ************************************************************************* */
|
2010-10-26 06:23:57 +08:00
|
|
|
template<class G, class C, class L, class S, class W>
|
|
|
|
|
NonlinearOptimizer<G, C, L, S, W>::NonlinearOptimizer(shared_graph graph,
|
|
|
|
|
shared_values values, shared_ordering ordering, shared_parameters parameters) :
|
2010-12-10 02:31:25 +08:00
|
|
|
graph_(graph), values_(values), error_(graph->error(*values)), ordering_(ordering),
|
2011-02-11 00:51:56 +08:00
|
|
|
parameters_(parameters), iterations_(0),
|
|
|
|
|
dimensions_(new vector<size_t>(values->dims(*ordering))),
|
|
|
|
|
structure_(new VariableIndex(*graph->symbolic(*values, *ordering))) {
|
2010-10-26 06:23:57 +08:00
|
|
|
if (!graph) throw std::invalid_argument(
|
|
|
|
|
"NonlinearOptimizer constructor: graph = NULL");
|
|
|
|
|
if (!values) throw std::invalid_argument(
|
|
|
|
|
"NonlinearOptimizer constructor: values = NULL");
|
|
|
|
|
if (!ordering) throw std::invalid_argument(
|
|
|
|
|
"NonlinearOptimizer constructor: ordering = NULL");
|
|
|
|
|
}
|
2010-10-23 13:47:29 +08:00
|
|
|
|
2011-02-11 00:01:29 +08:00
|
|
|
/* ************************************************************************* */
|
2011-02-11 00:51:56 +08:00
|
|
|
// FIXME: remove this constructor
|
2010-10-26 06:23:57 +08:00
|
|
|
template<class G, class C, class L, class S, class W>
|
2011-02-11 00:01:29 +08:00
|
|
|
NonlinearOptimizer<G, C, L, S, W>::NonlinearOptimizer(shared_graph graph,
|
|
|
|
|
shared_values values, shared_ordering ordering,
|
|
|
|
|
shared_solver spcg_solver, shared_parameters parameters) :
|
|
|
|
|
graph_(graph), values_(values), error_(graph->error(*values)), ordering_(ordering),
|
|
|
|
|
parameters_(parameters), iterations_(0),
|
|
|
|
|
dimensions_(new vector<size_t>(values->dims(*ordering))),
|
|
|
|
|
spcg_solver_(spcg_solver) {
|
2010-10-26 06:23:57 +08:00
|
|
|
if (!graph) throw std::invalid_argument(
|
|
|
|
|
"NonlinearOptimizer constructor: graph = NULL");
|
|
|
|
|
if (!values) throw std::invalid_argument(
|
|
|
|
|
"NonlinearOptimizer constructor: values = NULL");
|
2011-02-11 00:01:29 +08:00
|
|
|
if (!spcg_solver) throw std::invalid_argument(
|
|
|
|
|
"NonlinearOptimizer constructor: spcg_solver = NULL");
|
2009-09-09 12:43:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ************************************************************************* */
|
|
|
|
|
// One iteration of Gauss Newton
|
|
|
|
|
/* ************************************************************************* */
|
2010-01-22 16:13:54 +08:00
|
|
|
template<class G, class C, class L, class S, class W>
|
2010-10-26 06:23:57 +08:00
|
|
|
NonlinearOptimizer<G, C, L, S, W> NonlinearOptimizer<G, C, L, S, W>::iterate() const {
|
|
|
|
|
|
|
|
|
|
Parameters::verbosityLevel verbosity = parameters_->verbosity_ ;
|
|
|
|
|
|
2011-02-11 00:01:29 +08:00
|
|
|
// FIXME: get rid of spcg solver
|
2011-02-11 00:51:56 +08:00
|
|
|
shared_solver solver;
|
|
|
|
|
if (spcg_solver_) { // special case for SPCG
|
|
|
|
|
spcg_solver_->replaceFactors(linearize());
|
|
|
|
|
solver = spcg_solver_;
|
|
|
|
|
} else { // normal case
|
|
|
|
|
solver = createSolver();
|
|
|
|
|
}
|
2010-10-26 06:23:57 +08:00
|
|
|
|
2011-02-11 00:01:29 +08:00
|
|
|
VectorValues delta = *solver->optimize();
|
2009-09-09 12:43:04 +08:00
|
|
|
|
|
|
|
|
// maybe show output
|
2010-10-26 06:23:57 +08:00
|
|
|
if (verbosity >= Parameters::DELTA) delta.print("delta");
|
2009-09-09 12:43:04 +08:00
|
|
|
|
2010-10-23 03:29:15 +08:00
|
|
|
// take old values and update it
|
|
|
|
|
shared_values newValues(new C(values_->expmap(delta, *ordering_)));
|
2009-09-09 12:43:04 +08:00
|
|
|
|
|
|
|
|
// maybe show output
|
2010-10-26 06:23:57 +08:00
|
|
|
if (verbosity >= Parameters::VALUES) newValues->print("newValues");
|
2009-09-09 12:43:04 +08:00
|
|
|
|
2011-02-11 00:01:29 +08:00
|
|
|
NonlinearOptimizer newOptimizer = newValues_(newValues);
|
2010-01-16 13:08:29 +08:00
|
|
|
|
2010-10-26 06:23:57 +08:00
|
|
|
if (verbosity >= Parameters::ERROR) cout << "error: " << newOptimizer.error_ << endl;
|
2010-01-16 13:08:29 +08:00
|
|
|
return newOptimizer;
|
2009-09-09 12:43:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ************************************************************************* */
|
2010-01-22 16:13:54 +08:00
|
|
|
template<class G, class C, class L, class S, class W>
|
2010-10-26 06:23:57 +08:00
|
|
|
NonlinearOptimizer<G, C, L, S, W> NonlinearOptimizer<G, C, L, S, W>::gaussNewton() const {
|
2010-01-22 16:13:54 +08:00
|
|
|
static W writer(error_);
|
|
|
|
|
|
2010-10-26 06:23:57 +08:00
|
|
|
if (error_ < parameters_->sumError_ ) {
|
|
|
|
|
if ( parameters_->verbosity_ >= Parameters::ERROR)
|
|
|
|
|
cout << "Exiting, as error = " << error_
|
|
|
|
|
<< " < sumError (" << parameters_->sumError_ << ")" << endl;
|
2010-03-08 01:51:46 +08:00
|
|
|
return *this;
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-09 12:43:04 +08:00
|
|
|
// linearize, solve, update
|
2010-10-26 06:23:57 +08:00
|
|
|
NonlinearOptimizer next = iterate();
|
2009-09-09 12:43:04 +08:00
|
|
|
|
2010-01-22 16:13:54 +08:00
|
|
|
writer.write(next.error_);
|
|
|
|
|
|
2009-09-09 12:43:04 +08:00
|
|
|
// check convergence
|
2010-10-26 06:23:57 +08:00
|
|
|
bool converged = gtsam::check_convergence(*parameters_, error_, next.error_);
|
|
|
|
|
|
|
|
|
|
// return converged state or iterate
|
|
|
|
|
if (converged) return next;
|
|
|
|
|
else return next.gaussNewton();
|
|
|
|
|
}
|
|
|
|
|
|
2009-09-09 12:43:04 +08:00
|
|
|
/* ************************************************************************* */
|
2010-11-09 13:58:31 +08:00
|
|
|
// Iteratively try to do tempered Gauss-Newton steps until we succeed.
|
2009-09-09 12:43:04 +08:00
|
|
|
// Form damped system with given lambda, and return a new, more optimistic
|
2010-11-09 13:58:31 +08:00
|
|
|
// optimizer if error decreased or iterate with a larger lambda if not.
|
2009-09-09 12:43:04 +08:00
|
|
|
// TODO: in theory we can't infinitely recurse, but maybe we should put a max.
|
2011-09-06 05:28:26 +08:00
|
|
|
// Reminder: the parameters are Graph type $G$, Values class type $T$,
|
|
|
|
|
// linear system class $L$, the non linear solver type $S$, and the writer type $W$
|
|
|
|
|
template<class G, class T, class L, class S, class W>
|
|
|
|
|
NonlinearOptimizer<G, T, L, S, W> NonlinearOptimizer<G, T, L, S, W>::try_lambda(const L& linearSystem) {
|
2009-09-09 12:43:04 +08:00
|
|
|
|
2010-10-26 06:23:57 +08:00
|
|
|
const Parameters::verbosityLevel verbosity = parameters_->verbosity_ ;
|
|
|
|
|
const Parameters::LambdaMode lambdaMode = parameters_->lambdaMode_ ;
|
|
|
|
|
const double factor = parameters_->lambdaFactor_ ;
|
2011-09-06 05:28:26 +08:00
|
|
|
double lambda = parameters_->lambda_ ;
|
2010-10-26 06:23:57 +08:00
|
|
|
|
|
|
|
|
if( lambdaMode >= Parameters::CAUTIOUS) throw runtime_error("CAUTIOUS mode not working yet, please use BOUNDED.");
|
2010-03-11 00:27:38 +08:00
|
|
|
|
2010-11-09 13:58:31 +08:00
|
|
|
double next_error = error_;
|
2010-12-30 19:15:34 +08:00
|
|
|
shared_values next_values = values_;
|
2010-11-09 13:58:31 +08:00
|
|
|
|
2011-09-06 05:28:26 +08:00
|
|
|
// Keep increasing lambda until we make make progress
|
2010-11-09 13:58:31 +08:00
|
|
|
while(true) {
|
|
|
|
|
if (verbosity >= Parameters::TRYLAMBDA) cout << "trying lambda = " << lambda << endl;
|
|
|
|
|
|
|
|
|
|
// add prior-factors
|
2011-02-11 00:01:29 +08:00
|
|
|
// TODO: replace this dampening with a backsubstitution approach
|
2011-09-06 05:28:26 +08:00
|
|
|
typename L::shared_ptr dampedSystem(new L(linearSystem));
|
2010-11-16 07:01:50 +08:00
|
|
|
{
|
|
|
|
|
double sigma = 1.0 / sqrt(lambda);
|
2011-09-06 05:28:26 +08:00
|
|
|
dampedSystem->reserve(dampedSystem->size() + dimensions_->size());
|
2010-11-16 07:01:50 +08:00
|
|
|
// for each of the variables, add a prior
|
|
|
|
|
for(Index j=0; j<dimensions_->size(); ++j) {
|
|
|
|
|
size_t dim = (*dimensions_)[j];
|
|
|
|
|
Matrix A = eye(dim);
|
|
|
|
|
Vector b = zero(dim);
|
|
|
|
|
SharedDiagonal model = noiseModel::Isotropic::Sigma(dim,sigma);
|
2011-03-08 23:32:44 +08:00
|
|
|
typename L::sharedFactor prior(new JacobianFactor(j, A, b, model));
|
2011-09-06 05:28:26 +08:00
|
|
|
dampedSystem->push_back(prior);
|
2010-11-16 07:01:50 +08:00
|
|
|
}
|
|
|
|
|
}
|
2011-09-06 05:28:26 +08:00
|
|
|
if (verbosity >= Parameters::DAMPED) dampedSystem->print("damped");
|
2010-11-09 13:58:31 +08:00
|
|
|
|
2011-09-06 05:28:26 +08:00
|
|
|
// Create a new solver using the damped linear system
|
2011-02-11 00:51:56 +08:00
|
|
|
// FIXME: remove spcg specific code
|
2011-09-06 05:28:26 +08:00
|
|
|
if (spcg_solver_) spcg_solver_->replaceFactors(dampedSystem);
|
2011-06-14 00:55:31 +08:00
|
|
|
shared_solver solver = (spcg_solver_) ? spcg_solver_ : shared_solver(
|
2011-09-06 05:28:26 +08:00
|
|
|
new S(dampedSystem, structure_, parameters_->useQR_));
|
|
|
|
|
|
|
|
|
|
// Try solving
|
2011-08-25 04:51:54 +08:00
|
|
|
try {
|
|
|
|
|
VectorValues delta = *solver->optimize();
|
|
|
|
|
if (verbosity >= Parameters::TRYDELTA) delta.print("delta");
|
2010-11-09 13:58:31 +08:00
|
|
|
|
2011-08-25 04:51:54 +08:00
|
|
|
// update values
|
2011-09-06 05:28:26 +08:00
|
|
|
shared_values newValues(new T(values_->expmap(delta, *ordering_)));
|
2010-11-09 13:58:31 +08:00
|
|
|
|
2011-08-25 04:51:54 +08:00
|
|
|
// create new optimization state with more adventurous lambda
|
|
|
|
|
double error = graph_->error(*newValues);
|
2010-11-09 13:58:31 +08:00
|
|
|
|
2011-08-25 04:51:54 +08:00
|
|
|
if (verbosity >= Parameters::TRYLAMBDA) cout << "next error = " << error << endl;
|
2010-11-09 13:58:31 +08:00
|
|
|
|
2011-08-25 04:51:54 +08:00
|
|
|
if( error <= error_ ) {
|
|
|
|
|
next_values = newValues;
|
|
|
|
|
next_error = error;
|
|
|
|
|
lambda /= factor;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// Either we're not cautious, or the same lambda was worse than the current error.
|
|
|
|
|
// The more adventurous lambda was worse too, so make lambda more conservative
|
|
|
|
|
// and keep the same values.
|
|
|
|
|
if(lambdaMode >= Parameters::BOUNDED && lambda >= 1.0e5) {
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
lambda *= factor;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch(const NegativeMatrixException& e) {
|
2011-09-09 09:56:41 +08:00
|
|
|
if(verbosity >= Parameters::LAMBDA)
|
|
|
|
|
cout << "Negative matrix, increasing lambda" << endl;
|
2011-08-25 04:51:54 +08:00
|
|
|
// Either we're not cautious, or the same lambda was worse than the current error.
|
|
|
|
|
// The more adventurous lambda was worse too, so make lambda more conservative
|
|
|
|
|
// and keep the same values.
|
|
|
|
|
if(lambdaMode >= Parameters::BOUNDED && lambda >= 1.0e5) {
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
lambda *= factor;
|
|
|
|
|
}
|
|
|
|
|
} catch(...) {
|
|
|
|
|
throw;
|
2010-11-09 13:58:31 +08:00
|
|
|
}
|
|
|
|
|
} // end while
|
|
|
|
|
|
|
|
|
|
return newValuesErrorLambda_(next_values, next_error, lambda);
|
2009-09-09 12:43:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ************************************************************************* */
|
|
|
|
|
// One iteration of Levenberg Marquardt
|
2011-09-06 05:28:26 +08:00
|
|
|
// Reminder: the parameters are Graph type $G$, Values class type $T$,
|
|
|
|
|
// linear system class $L$, the non linear solver type $S$, and the writer type $W$
|
|
|
|
|
template<class G, class T, class L, class S, class W>
|
|
|
|
|
NonlinearOptimizer<G, T, L, S, W> NonlinearOptimizer<G, T, L, S, W>::iterateLM() {
|
2010-10-26 06:23:57 +08:00
|
|
|
|
|
|
|
|
const Parameters::verbosityLevel verbosity = parameters_->verbosity_ ;
|
|
|
|
|
const double lambda = parameters_->lambda_ ;
|
2009-09-09 12:43:04 +08:00
|
|
|
|
2010-10-22 02:54:00 +08:00
|
|
|
// show output
|
2010-10-26 06:23:57 +08:00
|
|
|
if (verbosity >= Parameters::VALUES) values_->print("values");
|
|
|
|
|
if (verbosity >= Parameters::ERROR) cout << "error: " << error_ << endl;
|
|
|
|
|
if (verbosity >= Parameters::LAMBDA) cout << "lambda = " << lambda << endl;
|
2009-09-09 12:43:04 +08:00
|
|
|
|
|
|
|
|
// linearize all factors once
|
2011-03-30 03:51:50 +08:00
|
|
|
boost::shared_ptr<L> linear(new L(*graph_->linearize(*values_, *ordering_)));
|
2010-10-26 06:23:57 +08:00
|
|
|
|
|
|
|
|
if (verbosity >= Parameters::LINEAR) linear->print("linear");
|
2009-09-09 12:43:04 +08:00
|
|
|
|
|
|
|
|
// try lambda steps with successively larger lambda until we achieve descent
|
2010-10-16 09:55:47 +08:00
|
|
|
if (verbosity >= Parameters::LAMBDA) cout << "Trying Lambda for the first time" << endl;
|
2010-10-26 06:23:57 +08:00
|
|
|
|
|
|
|
|
return try_lambda(*linear);
|
2009-09-09 12:43:04 +08:00
|
|
|
}
|
|
|
|
|
|
2010-10-26 06:23:57 +08:00
|
|
|
/* ************************************************************************* */
|
2011-09-06 05:28:26 +08:00
|
|
|
// Reminder: the parameters are Graph type $G$, Values class type $T$,
|
|
|
|
|
// linear system class $L$, the non linear solver type $S$, and the writer type $W$
|
|
|
|
|
template<class G, class T, class L, class S, class W>
|
|
|
|
|
NonlinearOptimizer<G, T, L, S, W> NonlinearOptimizer<G, T, L, S, W>::levenbergMarquardt() {
|
2010-10-01 22:47:28 +08:00
|
|
|
|
2011-09-06 05:28:26 +08:00
|
|
|
// Initialize
|
2010-11-09 13:58:31 +08:00
|
|
|
bool converged = false;
|
2010-10-26 06:23:57 +08:00
|
|
|
const Parameters::verbosityLevel verbosity = parameters_->verbosity_ ;
|
2010-04-29 05:35:01 +08:00
|
|
|
|
2010-03-05 06:46:27 +08:00
|
|
|
// check if we're already close enough
|
2010-10-26 06:23:57 +08:00
|
|
|
if (error_ < parameters_->sumError_) {
|
|
|
|
|
if ( verbosity >= Parameters::ERROR )
|
|
|
|
|
cout << "Exiting, as sumError = " << error_ << " < " << parameters_->sumError_ << endl;
|
2010-03-05 06:46:27 +08:00
|
|
|
return *this;
|
2010-03-08 01:51:46 +08:00
|
|
|
}
|
2010-03-05 06:46:27 +08:00
|
|
|
|
2011-03-11 13:28:53 +08:00
|
|
|
// for the case that maxIterations_ = 0
|
2010-12-03 11:23:35 +08:00
|
|
|
iterations_ = 1;
|
2011-03-11 13:28:53 +08:00
|
|
|
if (iterations_ >= parameters_->maxIterations_)
|
|
|
|
|
return *this;
|
|
|
|
|
|
2011-09-06 05:28:26 +08:00
|
|
|
// Iterative loop that implements Levenberg-Marquardt
|
2010-11-09 13:58:31 +08:00
|
|
|
while (true) {
|
|
|
|
|
double previous_error = error_;
|
|
|
|
|
// do one iteration of LM
|
|
|
|
|
NonlinearOptimizer next = iterateLM();
|
|
|
|
|
error_ = next.error_;
|
|
|
|
|
values_ = next.values_;
|
|
|
|
|
parameters_ = next.parameters_;
|
|
|
|
|
|
|
|
|
|
// check convergence
|
|
|
|
|
// TODO: move convergence checks here and incorporate in verbosity levels
|
|
|
|
|
// TODO: build into iterations somehow as an instance variable
|
|
|
|
|
converged = gtsam::check_convergence(*parameters_, previous_error, error_);
|
|
|
|
|
|
2010-12-03 11:23:35 +08:00
|
|
|
if(iterations_ >= parameters_->maxIterations_ || converged == true) {
|
2010-11-09 13:58:31 +08:00
|
|
|
if (verbosity >= Parameters::VALUES) values_->print("final values");
|
|
|
|
|
if (verbosity >= Parameters::ERROR) cout << "final error: " << error_ << endl;
|
|
|
|
|
if (verbosity >= Parameters::LAMBDA) cout << "final lambda = " << lambda() << endl;
|
|
|
|
|
return *this;
|
|
|
|
|
}
|
2010-12-03 11:23:35 +08:00
|
|
|
iterations_++;
|
2010-10-01 22:47:28 +08:00
|
|
|
}
|
2010-10-26 06:23:57 +08:00
|
|
|
}
|
2009-09-09 12:43:04 +08:00
|
|
|
}
|