Merge pull request #1877 from borglab/conjugate-gradient-system

release/4.3a0
Varun Agrawal 2024-10-16 10:21:46 -04:00 committed by GitHub
commit 5786073072
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 63 additions and 69 deletions

View File

@ -16,11 +16,11 @@
* @date Jun 11, 2012 * @date Jun 11, 2012
*/ */
#include <gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h>
#include <gtsam/nonlinear/internal/NonlinearOptimizerState.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/linear/GaussianFactorGraph.h> #include <gtsam/linear/GaussianFactorGraph.h>
#include <gtsam/linear/VectorValues.h> #include <gtsam/linear/VectorValues.h>
#include <gtsam/nonlinear/NonlinearConjugateGradientOptimizer.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/nonlinear/internal/NonlinearOptimizerState.h>
#include <cmath> #include <cmath>
@ -34,8 +34,8 @@ typedef internal::NonlinearOptimizerState State;
* @param values a linearization point * @param values a linearization point
* Can be moved to NonlinearFactorGraph.h if desired * Can be moved to NonlinearFactorGraph.h if desired
*/ */
static VectorValues gradientInPlace(const NonlinearFactorGraph &nfg, static VectorValues gradientInPlace(const NonlinearFactorGraph& nfg,
const Values &values) { const Values& values) {
// Linearize graph // Linearize graph
GaussianFactorGraph::shared_ptr linear = nfg.linearize(values); GaussianFactorGraph::shared_ptr linear = nfg.linearize(values);
return linear->gradientAtZero(); return linear->gradientAtZero();
@ -71,7 +71,8 @@ NonlinearConjugateGradientOptimizer::System::advance(const State& current,
GaussianFactorGraph::shared_ptr NonlinearConjugateGradientOptimizer::iterate() { GaussianFactorGraph::shared_ptr NonlinearConjugateGradientOptimizer::iterate() {
const auto [newValues, dummy] = nonlinearConjugateGradient<System, Values>( const auto [newValues, dummy] = nonlinearConjugateGradient<System, Values>(
System(graph_), state_->values, params_, true /* single iteration */); System(graph_), state_->values, params_, true /* single iteration */);
state_.reset(new State(newValues, graph_.error(newValues), state_->iterations + 1)); state_.reset(
new State(newValues, graph_.error(newValues), state_->iterations + 1));
// NOTE(frank): We don't linearize this system, so we must return null here. // NOTE(frank): We don't linearize this system, so we must return null here.
return nullptr; return nullptr;
@ -82,9 +83,9 @@ const Values& NonlinearConjugateGradientOptimizer::optimize() {
System system(graph_); System system(graph_);
const auto [newValues, iterations] = const auto [newValues, iterations] =
nonlinearConjugateGradient(system, state_->values, params_, false); nonlinearConjugateGradient(system, state_->values, params_, false);
state_.reset(new State(std::move(newValues), graph_.error(newValues), iterations)); state_.reset(
new State(std::move(newValues), graph_.error(newValues), iterations));
return state_->values; return state_->values;
} }
} /* namespace gtsam */ } /* namespace gtsam */

View File

@ -24,30 +24,27 @@
namespace gtsam { namespace gtsam {
/** An implementation of the nonlinear CG method using the template below */ /** An implementation of the nonlinear CG method using the template below */
class GTSAM_EXPORT NonlinearConjugateGradientOptimizer : public NonlinearOptimizer { class GTSAM_EXPORT NonlinearConjugateGradientOptimizer
: public NonlinearOptimizer {
/* a class for the nonlinearConjugateGradient template */ /* a class for the nonlinearConjugateGradient template */
class System { class System {
public: public:
typedef Values State; typedef Values State;
typedef VectorValues Gradient; typedef VectorValues Gradient;
typedef NonlinearOptimizerParams Parameters; typedef NonlinearOptimizerParams Parameters;
protected: protected:
const NonlinearFactorGraph &graph_; const NonlinearFactorGraph &graph_;
public: public:
System(const NonlinearFactorGraph &graph) : System(const NonlinearFactorGraph &graph) : graph_(graph) {}
graph_(graph) {
}
double error(const State &state) const; double error(const State &state) const;
Gradient gradient(const State &state) const; Gradient gradient(const State &state) const;
State advance(const State &current, const double alpha, State advance(const State &current, const double alpha,
const Gradient &g) const; const Gradient &g) const;
}; };
public: public:
typedef NonlinearOptimizer Base; typedef NonlinearOptimizer Base;
typedef NonlinearOptimizerParams Parameters; typedef NonlinearOptimizerParams Parameters;
typedef std::shared_ptr<NonlinearConjugateGradientOptimizer> shared_ptr; typedef std::shared_ptr<NonlinearConjugateGradientOptimizer> shared_ptr;
@ -55,59 +52,52 @@ public:
protected: protected:
Parameters params_; Parameters params_;
const NonlinearOptimizerParams& _params() const override { const NonlinearOptimizerParams &_params() const override { return params_; }
return params_;
}
public:
public:
/// Constructor /// Constructor
NonlinearConjugateGradientOptimizer( NonlinearConjugateGradientOptimizer(const NonlinearFactorGraph &graph,
const NonlinearFactorGraph &graph, const Values &initialValues, const Values &initialValues,
const Parameters &params = Parameters()); const Parameters &params = Parameters());
/// Destructor /// Destructor
~NonlinearConjugateGradientOptimizer() override { ~NonlinearConjugateGradientOptimizer() override {}
}
/** /**
* Perform a single iteration, returning GaussianFactorGraph corresponding to * Perform a single iteration, returning GaussianFactorGraph corresponding to
* the linearized factor graph. * the linearized factor graph.
*/ */
GaussianFactorGraph::shared_ptr iterate() override; GaussianFactorGraph::shared_ptr iterate() override;
/** /**
* Optimize for the maximum-likelihood estimate, returning a the optimized * Optimize for the maximum-likelihood estimate, returning a the optimized
* variable assignments. * variable assignments.
*/ */
const Values& optimize() override; const Values &optimize() override;
}; };
/** Implement the golden-section line search algorithm */ /** Implement the golden-section line search algorithm */
template<class S, class V, class W> template <class S, class V, class W>
double lineSearch(const S &system, const V currentValues, const W &gradient) { double lineSearch(const S &system, const V currentValues, const W &gradient) {
/* normalize it such that it becomes a unit vector */ /* normalize it such that it becomes a unit vector */
const double g = gradient.norm(); const double g = gradient.norm();
// perform the golden section search algorithm to decide the the optimal step size // perform the golden section search algorithm to decide the the optimal step
// detail refer to http://en.wikipedia.org/wiki/Golden_section_search // size detail refer to http://en.wikipedia.org/wiki/Golden_section_search
const double phi = 0.5 * (1.0 + std::sqrt(5.0)), resphi = 2.0 - phi, tau = const double phi = 0.5 * (1.0 + std::sqrt(5.0)), resphi = 2.0 - phi,
1e-5; tau = 1e-5;
double minStep = -1.0 / g, maxStep = 0, newStep = minStep double minStep = -1.0 / g, maxStep = 0,
+ (maxStep - minStep) / (phi + 1.0); newStep = minStep + (maxStep - minStep) / (phi + 1.0);
V newValues = system.advance(currentValues, newStep, gradient); V newValues = system.advance(currentValues, newStep, gradient);
double newError = system.error(newValues); double newError = system.error(newValues);
while (true) { while (true) {
const bool flag = (maxStep - newStep > newStep - minStep) ? true : false; const bool flag = (maxStep - newStep > newStep - minStep) ? true : false;
const double testStep = const double testStep = flag ? newStep + resphi * (maxStep - newStep)
flag ? newStep + resphi * (maxStep - newStep) : : newStep - resphi * (newStep - minStep);
newStep - resphi * (newStep - minStep);
if ((maxStep - minStep) if ((maxStep - minStep) < tau * (std::abs(testStep) + std::abs(newStep))) {
< tau * (std::abs(testStep) + std::abs(newStep))) {
return 0.5 * (minStep + maxStep); return 0.5 * (minStep + maxStep);
} }
@ -136,19 +126,21 @@ double lineSearch(const S &system, const V currentValues, const W &gradient) {
} }
/** /**
* Implement the nonlinear conjugate gradient method using the Polak-Ribiere formula suggested in * Implement the nonlinear conjugate gradient method using the Polak-Ribiere
* formula suggested in
* http://en.wikipedia.org/wiki/Nonlinear_conjugate_gradient_method. * http://en.wikipedia.org/wiki/Nonlinear_conjugate_gradient_method.
* *
* The S (system) class requires three member functions: error(state), gradient(state) and * The S (system) class requires three member functions: error(state),
* advance(state, step-size, direction). The V class denotes the state or the solution. * gradient(state) and advance(state, step-size, direction). The V class denotes
* the state or the solution.
* *
* The last parameter is a switch between gradient-descent and conjugate gradient * The last parameter is a switch between gradient-descent and conjugate
* gradient
*/ */
template<class S, class V> template <class S, class V>
std::tuple<V, int> nonlinearConjugateGradient(const S &system, std::tuple<V, int> nonlinearConjugateGradient(
const V &initial, const NonlinearOptimizerParams &params, const S &system, const V &initial, const NonlinearOptimizerParams &params,
const bool singleIteration, const bool gradientDescent = false) { const bool singleIteration, const bool gradientDescent = false) {
// GTSAM_CONCEPT_MANIFOLD_TYPE(V) // GTSAM_CONCEPT_MANIFOLD_TYPE(V)
size_t iteration = 0; size_t iteration = 0;
@ -158,7 +150,7 @@ std::tuple<V, int> nonlinearConjugateGradient(const S &system,
if (currentError <= params.errorTol) { if (currentError <= params.errorTol) {
if (params.verbosity >= NonlinearOptimizerParams::ERROR) { if (params.verbosity >= NonlinearOptimizerParams::ERROR) {
std::cout << "Exiting, as error = " << currentError << " < " std::cout << "Exiting, as error = " << currentError << " < "
<< params.errorTol << std::endl; << params.errorTol << std::endl;
} }
return {initial, iteration}; return {initial, iteration};
} }
@ -186,9 +178,9 @@ std::tuple<V, int> nonlinearConjugateGradient(const S &system,
prevGradient = currentGradient; prevGradient = currentGradient;
currentGradient = system.gradient(currentValues); currentGradient = system.gradient(currentValues);
// Polak-Ribiere: beta = g'*(g_n-g_n-1)/g_n-1'*g_n-1 // Polak-Ribiere: beta = g'*(g_n-g_n-1)/g_n-1'*g_n-1
const double beta = std::max(0.0, const double beta =
currentGradient.dot(currentGradient - prevGradient) std::max(0.0, currentGradient.dot(currentGradient - prevGradient) /
/ prevGradient.dot(prevGradient)); prevGradient.dot(prevGradient));
direction = currentGradient + (beta * direction); direction = currentGradient + (beta * direction);
} }
@ -206,20 +198,21 @@ std::tuple<V, int> nonlinearConjugateGradient(const S &system,
// Maybe show output // Maybe show output
if (params.verbosity >= NonlinearOptimizerParams::ERROR) if (params.verbosity >= NonlinearOptimizerParams::ERROR)
std::cout << "iteration: " << iteration << ", currentError: " << currentError << std::endl; std::cout << "iteration: " << iteration
} while (++iteration < params.maxIterations && !singleIteration << ", currentError: " << currentError << std::endl;
&& !checkConvergence(params.relativeErrorTol, params.absoluteErrorTol, } while (++iteration < params.maxIterations && !singleIteration &&
params.errorTol, prevError, currentError, params.verbosity)); !checkConvergence(params.relativeErrorTol, params.absoluteErrorTol,
params.errorTol, prevError, currentError,
params.verbosity));
// Printing if verbose // Printing if verbose
if (params.verbosity >= NonlinearOptimizerParams::ERROR if (params.verbosity >= NonlinearOptimizerParams::ERROR &&
&& iteration >= params.maxIterations) iteration >= params.maxIterations)
std::cout std::cout << "nonlinearConjugateGradient: Terminating because reached "
<< "nonlinearConjugateGradient: Terminating because reached maximum iterations" "maximum iterations"
<< std::endl; << std::endl;
return {currentValues, iteration}; return {currentValues, iteration};
} }
} // \ namespace gtsam } // namespace gtsam