Merge pull request #533 from borglab/jing/powermethod
Added accelerated power method to compute eigenpair fastrelease/4.3a0
						commit
						d267368b28
					
				|  | @ -0,0 +1,176 @@ | ||||||
|  | /* ----------------------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  |  * GTSAM Copyright 2010-2019, 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   AcceleratedPowerMethod.h | ||||||
|  |  * @date   Sept 2020 | ||||||
|  |  * @author Jing Wu | ||||||
|  |  * @brief  accelerated power method for fast eigenvalue and eigenvector | ||||||
|  |  * computation | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gtsam/linear/PowerMethod.h> | ||||||
|  | 
 | ||||||
|  | namespace gtsam { | ||||||
|  | 
 | ||||||
|  | using Sparse = Eigen::SparseMatrix<double>; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * \brief Compute maximum Eigenpair with accelerated power method | ||||||
|  |  * | ||||||
|  |  * References : | ||||||
|  |  * 1) G. Golub and C. V. Loan, Matrix Computations, 3rd ed. Baltimore, Johns | ||||||
|  |  * Hopkins University Press, 1996, pp.405-411 | ||||||
|  |  * 2) Rosen, D. and Carlone, L., 2017, September. Computational | ||||||
|  |  * enhancements for certifiably correct SLAM. In Proceedings of the | ||||||
|  |  * International Conference on Intelligent Robots and Systems. | ||||||
|  |  * 3) Yulun Tian and Kasra Khosoussi and David M. Rosen and Jonathan P. How, | ||||||
|  |  * 2020, Aug, Distributed Certifiably Correct Pose-Graph Optimization, Arxiv | ||||||
|  |  * 4) C. de Sa, B. He, I. Mitliagkas, C. Ré, and P. Xu, “Accelerated | ||||||
|  |  * stochastic power iteration,” in Proc. Mach. Learn. Res., no. 84, 2018, pp. | ||||||
|  |  * 58–67 | ||||||
|  |  * | ||||||
|  |  * It performs the following iteration: \f$ x_{k+1} = A * x_k - \beta * | ||||||
|  |  * x_{k-1} \f$ where A is the aim matrix we want to get eigenpair of, x is the | ||||||
|  |  * Ritz vector | ||||||
|  |  * | ||||||
|  |  * Template argument Operator just needs multiplication operator | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | template <class Operator> | ||||||
|  | class AcceleratedPowerMethod : public PowerMethod<Operator> { | ||||||
|  | 
 | ||||||
|  |   double beta_ = 0;  // a Polyak momentum term
 | ||||||
|  | 
 | ||||||
|  |   Vector previousVector_;  // store previous vector
 | ||||||
|  | 
 | ||||||
|  |  public: | ||||||
|  |   /**
 | ||||||
|  |    * Constructor from aim matrix A (given as Matrix or Sparse), optional intial | ||||||
|  |    * vector as ritzVector | ||||||
|  |    */ | ||||||
|  |   explicit AcceleratedPowerMethod( | ||||||
|  |       const Operator &A, const boost::optional<Vector> initial = boost::none, | ||||||
|  |       double initialBeta = 0.0) | ||||||
|  |       : PowerMethod<Operator>(A, initial) { | ||||||
|  |     // initialize Ritz eigen vector and previous vector
 | ||||||
|  |     this->ritzVector_ = initial ? initial.get() : Vector::Random(this->dim_); | ||||||
|  |     this->ritzVector_.normalize(); | ||||||
|  |     previousVector_ = Vector::Zero(this->dim_); | ||||||
|  | 
 | ||||||
|  |     // initialize beta_
 | ||||||
|  |     beta_ = initialBeta; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Run accelerated power iteration to get ritzVector with beta and previous | ||||||
|  |    * two ritzVector x0 and x00, and return y = (A * x0 - \beta * x00) / || A * x0 | ||||||
|  |    * - \beta * x00 || | ||||||
|  |    */ | ||||||
|  |   Vector acceleratedPowerIteration (const Vector &x1, const Vector &x0, | ||||||
|  |                         const double beta) const { | ||||||
|  |     Vector y = this->A_ * x1 - beta * x0; | ||||||
|  |     y.normalize(); | ||||||
|  |     return y; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Run accelerated power iteration to get ritzVector with beta and previous | ||||||
|  |    * two ritzVector x0 and x00, and return y = (A * x0 - \beta * x00) / || A * x0 | ||||||
|  |    * - \beta * x00 || | ||||||
|  |    */ | ||||||
|  |   Vector acceleratedPowerIteration () const { | ||||||
|  |     Vector y = acceleratedPowerIteration(this->ritzVector_, previousVector_, beta_); | ||||||
|  |     return y; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Tuning the momentum beta using the Best Heavy Ball algorithm in Ref(3), T | ||||||
|  |    * is the iteration time to find beta with largest Rayleigh quotient | ||||||
|  |    */ | ||||||
|  |   double estimateBeta(const size_t T = 10) const { | ||||||
|  |     // set initial estimation of maxBeta
 | ||||||
|  |     Vector initVector = this->ritzVector_; | ||||||
|  |     const double up = initVector.dot( this->A_ * initVector ); | ||||||
|  |     const double down = initVector.dot(initVector); | ||||||
|  |     const double mu = up / down; | ||||||
|  |     double maxBeta = mu * mu / 4; | ||||||
|  |     size_t maxIndex; | ||||||
|  |     std::vector<double> betas; | ||||||
|  | 
 | ||||||
|  |     Matrix R = Matrix::Zero(this->dim_, 10); | ||||||
|  |     // run T times of iteration to find the beta that has the largest Rayleigh quotient
 | ||||||
|  |     for (size_t t = 0; t < T; t++) { | ||||||
|  |       // after each t iteration, reset the betas with the current maxBeta
 | ||||||
|  |       betas = {2 / 3 * maxBeta, 0.99 * maxBeta, maxBeta, 1.01 * maxBeta, | ||||||
|  |                1.5 * maxBeta}; | ||||||
|  |       // iterate through every beta value
 | ||||||
|  |       for (size_t k = 0; k < betas.size(); ++k) { | ||||||
|  |         // initialize x0 and x00 in each iteration of each beta
 | ||||||
|  |         Vector x0 = initVector; | ||||||
|  |         Vector x00 = Vector::Zero(this->dim_); | ||||||
|  |         // run 10 steps of accelerated power iteration with this beta
 | ||||||
|  |         for (size_t j = 1; j < 10; j++) { | ||||||
|  |           if (j < 2) { | ||||||
|  |             R.col(0) = acceleratedPowerIteration(x0, x00, betas[k]); | ||||||
|  |             R.col(1) = acceleratedPowerIteration(R.col(0), x0, betas[k]); | ||||||
|  |           } else { | ||||||
|  |             R.col(j) = acceleratedPowerIteration(R.col(j - 1), R.col(j - 2), | ||||||
|  |   betas[k]); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         // compute the Rayleigh quotient for the randomly sampled vector after
 | ||||||
|  |         // 10 steps of power accelerated iteration
 | ||||||
|  |         const Vector x = R.col(9); | ||||||
|  |         const double up = x.dot(this->A_ * x); | ||||||
|  |         const double down = x.dot(x); | ||||||
|  |         const double mu = up / down; | ||||||
|  |         // store the momentum with largest Rayleigh quotient and its according index of beta_
 | ||||||
|  |         if (mu * mu / 4 > maxBeta) { | ||||||
|  |           // save the max beta index
 | ||||||
|  |           maxIndex = k; | ||||||
|  |           maxBeta = mu * mu / 4; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     // set beta_ to momentum with largest Rayleigh quotient
 | ||||||
|  |     return betas[maxIndex]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Start the accelerated iteration, after performing the | ||||||
|  |    * accelerated iteration, calculate the ritz error, repeat this | ||||||
|  |    * operation until the ritz error converge. If converged return true, else | ||||||
|  |    * false. | ||||||
|  |    */ | ||||||
|  |   bool compute(size_t maxIterations, double tol) { | ||||||
|  |     // Starting
 | ||||||
|  |     bool isConverged = false; | ||||||
|  | 
 | ||||||
|  |     for (size_t i = 0; i < maxIterations && !isConverged; i++) { | ||||||
|  |       ++(this->nrIterations_); | ||||||
|  |       Vector tmp = this->ritzVector_; | ||||||
|  |       // update the ritzVector after accelerated power iteration
 | ||||||
|  |       this->ritzVector_ = acceleratedPowerIteration(); | ||||||
|  |       // update the previousVector with ritzVector
 | ||||||
|  |       previousVector_ = tmp; | ||||||
|  |       // update the ritzValue 
 | ||||||
|  |       this->ritzValue_ = this->ritzVector_.dot(this->A_ * this->ritzVector_); | ||||||
|  |       isConverged = this->converged(tol); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return isConverged; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace gtsam
 | ||||||
|  | @ -0,0 +1,152 @@ | ||||||
|  | /* ----------------------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  |  * GTSAM Copyright 2010-2019, 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   PowerMethod.h | ||||||
|  |  * @date   Sept 2020 | ||||||
|  |  * @author Jing Wu | ||||||
|  |  * @brief  Power method for fast eigenvalue and eigenvector | ||||||
|  |  * computation | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <gtsam/base/Matrix.h> | ||||||
|  | #include <gtsam/base/Vector.h> | ||||||
|  | 
 | ||||||
|  | #include <Eigen/Core> | ||||||
|  | #include <Eigen/Sparse> | ||||||
|  | #include <random> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | namespace gtsam { | ||||||
|  | 
 | ||||||
|  | using Sparse = Eigen::SparseMatrix<double>; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * \brief Compute maximum Eigenpair with power method | ||||||
|  |  * | ||||||
|  |  * References : | ||||||
|  |  * 1) G. Golub and C. V. Loan, Matrix Computations, 3rd ed. Baltimore, Johns | ||||||
|  |  * Hopkins University Press, 1996, pp.405-411 | ||||||
|  |  * 2) Rosen, D. and Carlone, L., 2017, September. Computational | ||||||
|  |  * enhancements for certifiably correct SLAM. In Proceedings of the | ||||||
|  |  * International Conference on Intelligent Robots and Systems. | ||||||
|  |  * 3) Yulun Tian and Kasra Khosoussi and David M. Rosen and Jonathan P. How, | ||||||
|  |  * 2020, Aug, Distributed Certifiably Correct Pose-Graph Optimization, Arxiv | ||||||
|  |  * 4) C. de Sa, B. He, I. Mitliagkas, C. Ré, and P. Xu, “Accelerated | ||||||
|  |  * stochastic power iteration,” in Proc. Mach. Learn. Res., no. 84, 2018, pp. | ||||||
|  |  * 58–67 | ||||||
|  |  * | ||||||
|  |  * It performs the following iteration: \f$ x_{k+1} = A * x_k  \f$ | ||||||
|  |  * where A is the aim matrix we want to get eigenpair of, x is the | ||||||
|  |  * Ritz vector | ||||||
|  |  * | ||||||
|  |  * Template argument Operator just needs multiplication operator | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | template <class Operator> | ||||||
|  | class PowerMethod { | ||||||
|  |  protected: | ||||||
|  |   /**
 | ||||||
|  |    * Const reference to an externally-held matrix whose minimum-eigenvalue we | ||||||
|  |    * want to compute | ||||||
|  |    */ | ||||||
|  |   const Operator &A_; | ||||||
|  | 
 | ||||||
|  |   const int dim_;  // dimension of Matrix A
 | ||||||
|  | 
 | ||||||
|  |   size_t nrIterations_;  // number of iterations
 | ||||||
|  | 
 | ||||||
|  |   double ritzValue_;   // Ritz eigenvalue
 | ||||||
|  |   Vector ritzVector_;  // Ritz eigenvector
 | ||||||
|  | 
 | ||||||
|  |  public: | ||||||
|  |   /// @name Standard Constructors
 | ||||||
|  |   /// @{
 | ||||||
|  | 
 | ||||||
|  |   /// Construct from the aim matrix and intial ritz vector
 | ||||||
|  |   explicit PowerMethod(const Operator &A, | ||||||
|  |                        const boost::optional<Vector> initial = boost::none) | ||||||
|  |       : A_(A), dim_(A.rows()), nrIterations_(0) { | ||||||
|  |     Vector x0; | ||||||
|  |     x0 = initial ? initial.get() : Vector::Random(dim_); | ||||||
|  |     x0.normalize(); | ||||||
|  | 
 | ||||||
|  |     // initialize Ritz eigen value
 | ||||||
|  |     ritzValue_ = 0.0; | ||||||
|  | 
 | ||||||
|  |     // initialize Ritz eigen vector
 | ||||||
|  |     ritzVector_ = powerIteration(x0); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Run power iteration to get ritzVector with previous ritzVector x, and | ||||||
|  |    * return A * x / || A * x || | ||||||
|  |    */ | ||||||
|  |   Vector powerIteration(const Vector &x) const { | ||||||
|  |     Vector y = A_ * x; | ||||||
|  |     y.normalize(); | ||||||
|  |     return y; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Run power iteration to get ritzVector with previous ritzVector x, and | ||||||
|  |    * return A * x / || A * x || | ||||||
|  |    */   | ||||||
|  |   Vector powerIteration() const { return powerIteration(ritzVector_); } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * After Perform power iteration on a single Ritz value, check if the Ritz | ||||||
|  |    * residual for the current Ritz pair is less than the required convergence | ||||||
|  |    * tol, return true if yes, else false | ||||||
|  |    */ | ||||||
|  |   bool converged(double tol) const { | ||||||
|  |     const Vector x = ritzVector_; | ||||||
|  |     // store the Ritz eigen value
 | ||||||
|  |     const double ritzValue = x.dot(A_ * x); | ||||||
|  |     const double error = (A_ * x - ritzValue * x).norm(); | ||||||
|  |     return error < tol; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Return the number of iterations
 | ||||||
|  |   size_t nrIterations() const { return nrIterations_; } | ||||||
|  | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Start the power/accelerated iteration, after performing the | ||||||
|  |    * power/accelerated iteration, calculate the ritz error, repeat this | ||||||
|  |    * operation until the ritz error converge. If converged return true, else | ||||||
|  |    * false. | ||||||
|  |    */ | ||||||
|  |   bool compute(size_t maxIterations, double tol) { | ||||||
|  |     // Starting
 | ||||||
|  |     bool isConverged = false; | ||||||
|  | 
 | ||||||
|  |     for (size_t i = 0; i < maxIterations && !isConverged; i++) { | ||||||
|  |       ++nrIterations_; | ||||||
|  |       // update the ritzVector after power iteration
 | ||||||
|  |       ritzVector_ = powerIteration(); | ||||||
|  |       // update the ritzValue 
 | ||||||
|  |       ritzValue_ = ritzVector_.dot(A_ * ritzVector_); | ||||||
|  |       isConverged = converged(tol); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return isConverged; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /// Return the eigenvalue
 | ||||||
|  |   double eigenvalue() const { return ritzValue_; } | ||||||
|  | 
 | ||||||
|  |   /// Return the eigenvector
 | ||||||
|  |   Vector eigenvector() const { return ritzVector_; } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | }  // namespace gtsam
 | ||||||
|  | @ -0,0 +1,67 @@ | ||||||
|  | /* ----------------------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  |  * GTSAM Copyright 2010-2019, 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 | ||||||
|  | 
 | ||||||
|  |  * -------------------------------------------------------------------------- */ | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * powerMethodExample.h | ||||||
|  |  * | ||||||
|  |  * @file   powerMethodExample.h | ||||||
|  |  * @date   Nov 2020 | ||||||
|  |  * @author Jing Wu | ||||||
|  |  * @brief  Create sparse and dense factor graph for | ||||||
|  |  * PowerMethod/AcceleratedPowerMethod | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <gtsam/inference/Symbol.h> | ||||||
|  | 
 | ||||||
|  | #include <iostream> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | namespace gtsam { | ||||||
|  | namespace linear { | ||||||
|  | namespace test { | ||||||
|  | namespace example { | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | inline GaussianFactorGraph createSparseGraph() { | ||||||
|  |   using symbol_shorthand::X; | ||||||
|  |   // Let's make a scalar synchronization graph with 4 nodes
 | ||||||
|  |   GaussianFactorGraph fg; | ||||||
|  |   auto model = noiseModel::Unit::Create(1); | ||||||
|  |   for (size_t j = 0; j < 3; j++) { | ||||||
|  |     fg.add(X(j), -I_1x1, X(j + 1), I_1x1, Vector1::Zero(), model); | ||||||
|  |   } | ||||||
|  |   fg.add(X(3), -I_1x1, X(0), I_1x1, Vector1::Zero(), model);  // extra row
 | ||||||
|  | 
 | ||||||
|  |   return fg; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | inline GaussianFactorGraph createDenseGraph() { | ||||||
|  |   using symbol_shorthand::X; | ||||||
|  |   // Let's make a scalar synchronization graph with 10 nodes
 | ||||||
|  |   GaussianFactorGraph fg; | ||||||
|  |   auto model = noiseModel::Unit::Create(1); | ||||||
|  |   // Iterate over nodes
 | ||||||
|  |   for (size_t j = 0; j < 10; j++) { | ||||||
|  |     // Each node has an edge with all the others
 | ||||||
|  |     for (size_t i = 1; i < 10; i++) | ||||||
|  |     fg.add(X(j), -I_1x1, X((j + i) % 10), I_1x1, Vector1::Zero(), model); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return fg; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | 
 | ||||||
|  | }  // namespace example
 | ||||||
|  | }  // namespace test
 | ||||||
|  | }  // namespace linear
 | ||||||
|  | }  // namespace gtsam
 | ||||||
|  | @ -0,0 +1,140 @@ | ||||||
|  | /* ----------------------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  |  * GTSAM Copyright 2010-2019, 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 | ||||||
|  | 
 | ||||||
|  |  * -------------------------------------------------------------------------- */ | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * testPowerMethod.cpp | ||||||
|  |  * | ||||||
|  |  * @file   testAcceleratedPowerMethod.cpp | ||||||
|  |  * @date   Sept 2020 | ||||||
|  |  * @author Jing Wu | ||||||
|  |  * @brief  Check eigenvalue and eigenvector computed by accelerated power method | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <CppUnitLite/TestHarness.h> | ||||||
|  | #include <gtsam/base/Matrix.h> | ||||||
|  | #include <gtsam/base/VectorSpace.h> | ||||||
|  | #include <gtsam/inference/Symbol.h> | ||||||
|  | #include <gtsam/linear/AcceleratedPowerMethod.h> | ||||||
|  | #include <gtsam/linear/GaussianFactorGraph.h> | ||||||
|  | #include <gtsam/linear/tests/powerMethodExample.h> | ||||||
|  | 
 | ||||||
|  | #include <Eigen/Core> | ||||||
|  | #include <Eigen/Dense> | ||||||
|  | #include <Eigen/Eigenvalues> | ||||||
|  | #include <iostream> | ||||||
|  | #include <random> | ||||||
|  | 
 | ||||||
|  | using namespace std; | ||||||
|  | using namespace gtsam; | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | TEST(AcceleratedPowerMethod, acceleratedPowerIteration) { | ||||||
|  |   // test power iteration, beta is set to 0
 | ||||||
|  |   Sparse A(6, 6); | ||||||
|  |   A.coeffRef(0, 0) = 6; | ||||||
|  |   A.coeffRef(1, 1) = 5; | ||||||
|  |   A.coeffRef(2, 2) = 4; | ||||||
|  |   A.coeffRef(3, 3) = 3; | ||||||
|  |   A.coeffRef(4, 4) = 2; | ||||||
|  |   A.coeffRef(5, 5) = 1; | ||||||
|  |   Vector initial = (Vector(6) << 0.24434602, 0.22829942, 0.70094486, 0.15463092, 0.55871359, | ||||||
|  |        0.2465342).finished(); | ||||||
|  |   const double ev1 = 6.0; | ||||||
|  | 
 | ||||||
|  |   // test accelerated power iteration
 | ||||||
|  |   AcceleratedPowerMethod<Sparse> apf(A, initial); | ||||||
|  |   apf.compute(100, 1e-5); | ||||||
|  |   EXPECT_LONGS_EQUAL(6, apf.eigenvector().rows()); | ||||||
|  | 
 | ||||||
|  |   Vector6 actual1 = apf.eigenvector(); | ||||||
|  |   const double ritzValue = actual1.dot(A * actual1); | ||||||
|  |   const double ritzResidual = (A * actual1 - ritzValue * actual1).norm(); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, ritzResidual, 1e-5); | ||||||
|  | 
 | ||||||
|  |   EXPECT_DOUBLES_EQUAL(ev1, apf.eigenvalue(), 1e-5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | TEST(AcceleratedPowerMethod, useFactorGraphSparse) { | ||||||
|  |   // Let's make a scalar synchronization graph with 4 nodes
 | ||||||
|  |   GaussianFactorGraph fg = gtsam::linear::test::example::createSparseGraph(); | ||||||
|  | 
 | ||||||
|  |   // Get eigenvalues and eigenvectors with Eigen
 | ||||||
|  |   auto L = fg.hessian(); | ||||||
|  |   Eigen::EigenSolver<Matrix> solver(L.first); | ||||||
|  | 
 | ||||||
|  |   // find the index of the max eigenvalue
 | ||||||
|  |   size_t maxIdx = 0; | ||||||
|  |   for (auto i = 0; i < solver.eigenvalues().rows(); ++i) { | ||||||
|  |     if (solver.eigenvalues()(i).real() >= solver.eigenvalues()(maxIdx).real()) | ||||||
|  |       maxIdx = i; | ||||||
|  |   } | ||||||
|  |   // Store the max eigenvalue and its according eigenvector
 | ||||||
|  |   const auto ev1 = solver.eigenvalues()(maxIdx).real(); | ||||||
|  | 
 | ||||||
|  |   Vector disturb = Vector4::Random(); | ||||||
|  |   disturb.normalize(); | ||||||
|  |   Vector initial = L.first.row(0); | ||||||
|  |   double magnitude = initial.norm(); | ||||||
|  |   initial += 0.03 * magnitude * disturb; | ||||||
|  |   AcceleratedPowerMethod<Matrix> apf(L.first, initial); | ||||||
|  |   apf.compute(100, 1e-5); | ||||||
|  |   // Check if the eigenvalue is the maximum eigen value
 | ||||||
|  |   EXPECT_DOUBLES_EQUAL(ev1, apf.eigenvalue(), 1e-8); | ||||||
|  | 
 | ||||||
|  |   // Check if the according ritz residual converged to the threshold
 | ||||||
|  |   Vector actual1 = apf.eigenvector(); | ||||||
|  |   const double ritzValue = actual1.dot(L.first * actual1); | ||||||
|  |   const double ritzResidual = (L.first * actual1 - ritzValue * actual1).norm(); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, ritzResidual, 1e-5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | TEST(AcceleratedPowerMethod, useFactorGraphDense) { | ||||||
|  |   // Let's make a scalar synchronization graph with 10 nodes
 | ||||||
|  |   GaussianFactorGraph fg = gtsam::linear::test::example::createDenseGraph(); | ||||||
|  | 
 | ||||||
|  |   // Get eigenvalues and eigenvectors with Eigen
 | ||||||
|  |   auto L = fg.hessian(); | ||||||
|  |   Eigen::EigenSolver<Matrix> solver(L.first); | ||||||
|  | 
 | ||||||
|  |   // find the index of the max eigenvalue
 | ||||||
|  |   size_t maxIdx = 0; | ||||||
|  |   for (auto i = 0; i < solver.eigenvalues().rows(); ++i) { | ||||||
|  |     if (solver.eigenvalues()(i).real() >= solver.eigenvalues()(maxIdx).real()) | ||||||
|  |       maxIdx = i; | ||||||
|  |   } | ||||||
|  |   // Store the max eigenvalue and its according eigenvector
 | ||||||
|  |   const auto ev1 = solver.eigenvalues()(maxIdx).real(); | ||||||
|  | 
 | ||||||
|  |   Vector disturb = Vector10::Random(); | ||||||
|  |   disturb.normalize(); | ||||||
|  |   Vector initial = L.first.row(0); | ||||||
|  |   double magnitude = initial.norm(); | ||||||
|  |   initial += 0.03 * magnitude * disturb; | ||||||
|  |   AcceleratedPowerMethod<Matrix> apf(L.first, initial); | ||||||
|  |   apf.compute(100, 1e-5); | ||||||
|  |   // Check if the eigenvalue is the maximum eigen value
 | ||||||
|  |   EXPECT_DOUBLES_EQUAL(ev1, apf.eigenvalue(), 1e-8); | ||||||
|  | 
 | ||||||
|  |   // Check if the according ritz residual converged to the threshold
 | ||||||
|  |   Vector actual1 = apf.eigenvector(); | ||||||
|  |   const double ritzValue = actual1.dot(L.first * actual1); | ||||||
|  |   const double ritzResidual = (L.first * actual1 - ritzValue * actual1).norm(); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, ritzResidual, 1e-5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | int main() { | ||||||
|  |   TestResult tr; | ||||||
|  |   return TestRegistry::runAllTests(tr); | ||||||
|  | } | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | @ -0,0 +1,124 @@ | ||||||
|  | /* ----------------------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  |  * GTSAM Copyright 2010-2019, 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 | ||||||
|  | 
 | ||||||
|  |  * -------------------------------------------------------------------------- */ | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * testPowerMethod.cpp | ||||||
|  |  * | ||||||
|  |  * @file   testPowerMethod.cpp | ||||||
|  |  * @date   Sept 2020 | ||||||
|  |  * @author Jing Wu | ||||||
|  |  * @brief  Check eigenvalue and eigenvector computed by power method | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <CppUnitLite/TestHarness.h> | ||||||
|  | #include <gtsam/base/Matrix.h> | ||||||
|  | #include <gtsam/base/VectorSpace.h> | ||||||
|  | #include <gtsam/inference/Symbol.h> | ||||||
|  | #include <gtsam/linear/GaussianFactorGraph.h> | ||||||
|  | #include <gtsam/linear/PowerMethod.h> | ||||||
|  | #include <gtsam/linear/tests/powerMethodExample.h> | ||||||
|  | 
 | ||||||
|  | #include <Eigen/Core> | ||||||
|  | #include <Eigen/Dense> | ||||||
|  | #include <Eigen/Eigenvalues> | ||||||
|  | #include <iostream> | ||||||
|  | #include <random> | ||||||
|  | 
 | ||||||
|  | using namespace std; | ||||||
|  | using namespace gtsam; | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | TEST(PowerMethod, powerIteration) { | ||||||
|  |   // test power iteration, beta is set to 0
 | ||||||
|  |   Sparse A(6, 6); | ||||||
|  |   A.coeffRef(0, 0) = 6; | ||||||
|  |   A.coeffRef(1, 1) = 5; | ||||||
|  |   A.coeffRef(2, 2) = 4; | ||||||
|  |   A.coeffRef(3, 3) = 3; | ||||||
|  |   A.coeffRef(4, 4) = 2; | ||||||
|  |   A.coeffRef(5, 5) = 1; | ||||||
|  |   Vector initial = (Vector(6) << 0.24434602, 0.22829942, 0.70094486, 0.15463092, 0.55871359, | ||||||
|  |        0.2465342).finished(); | ||||||
|  |   PowerMethod<Sparse> pf(A, initial); | ||||||
|  |   pf.compute(100, 1e-5); | ||||||
|  |   EXPECT_LONGS_EQUAL(6, pf.eigenvector().rows()); | ||||||
|  | 
 | ||||||
|  |   Vector6 actual1 = pf.eigenvector(); | ||||||
|  |   const double ritzValue = actual1.dot(A * actual1); | ||||||
|  |   const double ritzResidual = (A * actual1 - ritzValue * actual1).norm(); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, ritzResidual, 1e-5); | ||||||
|  | 
 | ||||||
|  |   const double ev1 = 6.0; | ||||||
|  |   EXPECT_DOUBLES_EQUAL(ev1, pf.eigenvalue(), 1e-5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | TEST(PowerMethod, useFactorGraphSparse) { | ||||||
|  |   // Let's make a scalar synchronization graph with 4 nodes
 | ||||||
|  |   GaussianFactorGraph fg = gtsam::linear::test::example::createSparseGraph(); | ||||||
|  | 
 | ||||||
|  |   // Get eigenvalues and eigenvectors with Eigen
 | ||||||
|  |   auto L = fg.hessian(); | ||||||
|  |   Eigen::EigenSolver<Matrix> solver(L.first); | ||||||
|  | 
 | ||||||
|  |   // find the index of the max eigenvalue
 | ||||||
|  |   size_t maxIdx = 0; | ||||||
|  |   for (auto i = 0; i < solver.eigenvalues().rows(); ++i) { | ||||||
|  |     if (solver.eigenvalues()(i).real() >= solver.eigenvalues()(maxIdx).real()) | ||||||
|  |       maxIdx = i; | ||||||
|  |   } | ||||||
|  |   // Store the max eigenvalue and its according eigenvector
 | ||||||
|  |   const auto ev1 = solver.eigenvalues()(maxIdx).real(); | ||||||
|  | 
 | ||||||
|  |   Vector initial = Vector4::Random(); | ||||||
|  |   PowerMethod<Matrix> pf(L.first, initial); | ||||||
|  |   pf.compute(100, 1e-5); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(ev1, pf.eigenvalue(), 1e-8); | ||||||
|  |   auto actual2 = pf.eigenvector(); | ||||||
|  |   const double ritzValue = actual2.dot(L.first * actual2); | ||||||
|  |   const double ritzResidual = (L.first * actual2 - ritzValue * actual2).norm(); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, ritzResidual, 1e-5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | TEST(PowerMethod, useFactorGraphDense) { | ||||||
|  |   // Let's make a scalar synchronization graph with 10 nodes
 | ||||||
|  |   GaussianFactorGraph fg = gtsam::linear::test::example::createDenseGraph(); | ||||||
|  | 
 | ||||||
|  |   // Get eigenvalues and eigenvectors with Eigen
 | ||||||
|  |   auto L = fg.hessian(); | ||||||
|  |   Eigen::EigenSolver<Matrix> solver(L.first); | ||||||
|  | 
 | ||||||
|  |   // find the index of the max eigenvalue
 | ||||||
|  |   size_t maxIdx = 0; | ||||||
|  |   for (auto i = 0; i < solver.eigenvalues().rows(); ++i) { | ||||||
|  |     if (solver.eigenvalues()(i).real() >= solver.eigenvalues()(maxIdx).real()) | ||||||
|  |       maxIdx = i; | ||||||
|  |   } | ||||||
|  |   // Store the max eigenvalue and its according eigenvector
 | ||||||
|  |   const auto ev1 = solver.eigenvalues()(maxIdx).real(); | ||||||
|  | 
 | ||||||
|  |   Vector initial = Vector10::Random(); | ||||||
|  |   PowerMethod<Matrix> pf(L.first, initial); | ||||||
|  |   pf.compute(100, 1e-5); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(ev1, pf.eigenvalue(), 1e-8); | ||||||
|  |   auto actual2 = pf.eigenvector(); | ||||||
|  |   const double ritzValue = actual2.dot(L.first * actual2); | ||||||
|  |   const double ritzResidual = (L.first * actual2 - ritzValue * actual2).norm(); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, ritzResidual, 1e-5); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | int main() { | ||||||
|  |   TestResult tr; | ||||||
|  |   return TestRegistry::runAllTests(tr); | ||||||
|  | } | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| #include <SymEigsSolver.h> | #include <SymEigsSolver.h> | ||||||
|  | #include <cmath> | ||||||
| #include <gtsam/linear/PCGSolver.h> | #include <gtsam/linear/PCGSolver.h> | ||||||
| #include <gtsam/linear/SubgraphPreconditioner.h> | #include <gtsam/linear/SubgraphPreconditioner.h> | ||||||
| #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h> | #include <gtsam/nonlinear/LevenbergMarquardtOptimizer.h> | ||||||
|  | @ -487,8 +488,88 @@ Sparse ShonanAveraging<d>::computeA(const Values &values) const { | ||||||
|   return Lambda - Q_; |   return Lambda - Q_; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | template <size_t d> | ||||||
|  | Sparse ShonanAveraging<d>::computeA(const Matrix &S) const { | ||||||
|  |   auto Lambda = computeLambda(S); | ||||||
|  |   return Lambda - Q_; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | // Perturb the initial initialVector by adding a spherically-uniformly
 | ||||||
|  | // distributed random vector with 0.03*||initialVector||_2 magnitude to
 | ||||||
|  | // initialVector
 | ||||||
|  | // ref : Part III. C, Rosen, D. and Carlone, L., 2017, September. Computational
 | ||||||
|  | // enhancements for certifiably correct SLAM. In Proceedings of the
 | ||||||
|  | // International Conference on Intelligent Robots and Systems.
 | ||||||
|  | static Vector perturb(const Vector &initialVector) { | ||||||
|  |   // generate a 0.03*||x_0||_2 as stated in David's paper
 | ||||||
|  |   int n = initialVector.rows(); | ||||||
|  |   Vector disturb = Vector::Random(n); | ||||||
|  |   disturb.normalize(); | ||||||
|  | 
 | ||||||
|  |   double magnitude = initialVector.norm(); | ||||||
|  |   Vector perturbedVector = initialVector + 0.03 * magnitude * disturb; | ||||||
|  |   perturbedVector.normalize(); | ||||||
|  |   return perturbedVector; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* ************************************************************************* */ | /* ************************************************************************* */ | ||||||
| /// MINIMUM EIGENVALUE COMPUTATIONS
 | /// MINIMUM EIGENVALUE COMPUTATIONS
 | ||||||
|  | // Alg.6 from paper Distributed Certifiably Correct Pose-Graph Optimization,
 | ||||||
|  | // it takes in the certificate matrix A as input, the maxIterations and the
 | ||||||
|  | // minEigenvalueNonnegativityTolerance is set to 1000 and 10e-4 ad default,
 | ||||||
|  | // there are two parts
 | ||||||
|  | // in this algorithm:
 | ||||||
|  | // (1) compute the maximum eigenpair (\lamda_dom, \vect{v}_dom) of A by power
 | ||||||
|  | // method. if \lamda_dom is less than zero, then return the eigenpair. (2)
 | ||||||
|  | // compute the maximum eigenpair (\theta, \vect{v}) of C = \lamda_dom * I - A by
 | ||||||
|  | // accelerated power method. Then return (\lamda_dom - \theta, \vect{v}).
 | ||||||
|  | static bool PowerMinimumEigenValue( | ||||||
|  |     const Sparse &A, const Matrix &S, double &minEigenValue, | ||||||
|  |     Vector *minEigenVector = 0, size_t *numIterations = 0, | ||||||
|  |     size_t maxIterations = 1000, | ||||||
|  |     double minEigenvalueNonnegativityTolerance = 10e-4) { | ||||||
|  | 
 | ||||||
|  |   // a. Compute dominant eigenpair of S using power method
 | ||||||
|  |   PowerMethod<Sparse> pmOperator(A); | ||||||
|  | 
 | ||||||
|  |   const bool pmConverged = pmOperator.compute( | ||||||
|  |       maxIterations, 1e-5); | ||||||
|  | 
 | ||||||
|  |   // Check convergence and bail out if necessary
 | ||||||
|  |   if (!pmConverged) return false; | ||||||
|  | 
 | ||||||
|  |   const double pmEigenValue = pmOperator.eigenvalue(); | ||||||
|  | 
 | ||||||
|  |   if (pmEigenValue < 0) { | ||||||
|  |     // The largest-magnitude eigenvalue is negative, and therefore also the
 | ||||||
|  |     // minimum eigenvalue, so just return this solution
 | ||||||
|  |     minEigenValue = pmEigenValue; | ||||||
|  |     if (minEigenVector) { | ||||||
|  |       *minEigenVector = pmOperator.eigenvector(); | ||||||
|  |       minEigenVector->normalize();  // Ensure that this is a unit vector
 | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const Sparse C = pmEigenValue * Matrix::Identity(A.rows(), A.cols()) - A; | ||||||
|  |   const boost::optional<Vector> initial = perturb(S.row(0)); | ||||||
|  |   AcceleratedPowerMethod<Sparse> apmShiftedOperator(C, initial); | ||||||
|  | 
 | ||||||
|  |   const bool minConverged = apmShiftedOperator.compute( | ||||||
|  |       maxIterations, minEigenvalueNonnegativityTolerance / pmEigenValue); | ||||||
|  | 
 | ||||||
|  |   if (!minConverged) return false; | ||||||
|  | 
 | ||||||
|  |   minEigenValue = pmEigenValue - apmShiftedOperator.eigenvalue(); | ||||||
|  |   if (minEigenVector) { | ||||||
|  |     *minEigenVector = apmShiftedOperator.eigenvector(); | ||||||
|  |     minEigenVector->normalize();  // Ensure that this is a unit vector
 | ||||||
|  |   } | ||||||
|  |   if (numIterations) *numIterations = apmShiftedOperator.nrIterations(); | ||||||
|  |   return true; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /** This is a lightweight struct used in conjunction with Spectra to compute
 | /** This is a lightweight struct used in conjunction with Spectra to compute
 | ||||||
|  * the minimum eigenvalue and eigenvector of a sparse matrix A; it has a single |  * the minimum eigenvalue and eigenvector of a sparse matrix A; it has a single | ||||||
|  | @ -634,13 +715,6 @@ static bool SparseMinimumEigenValue( | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* ************************************************************************* */ |  | ||||||
| template <size_t d> |  | ||||||
| Sparse ShonanAveraging<d>::computeA(const Matrix &S) const { |  | ||||||
|   auto Lambda = computeLambda(S); |  | ||||||
|   return Lambda - Q_; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /* ************************************************************************* */ | /* ************************************************************************* */ | ||||||
| template <size_t d> | template <size_t d> | ||||||
| double ShonanAveraging<d>::computeMinEigenValue(const Values &values, | double ShonanAveraging<d>::computeMinEigenValue(const Values &values, | ||||||
|  | @ -658,6 +732,23 @@ double ShonanAveraging<d>::computeMinEigenValue(const Values &values, | ||||||
|   return minEigenValue; |   return minEigenValue; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* ************************************************************************* */ | ||||||
|  | template <size_t d> | ||||||
|  | double ShonanAveraging<d>::computeMinEigenValueAP(const Values &values, | ||||||
|  |                                                 Vector *minEigenVector) const { | ||||||
|  |   assert(values.size() == nrUnknowns()); | ||||||
|  |   const Matrix S = StiefelElementMatrix(values); | ||||||
|  |   auto A = computeA(S); | ||||||
|  | 
 | ||||||
|  |   double minEigenValue = 0; | ||||||
|  |   bool success = PowerMinimumEigenValue(A, S, minEigenValue, minEigenVector); | ||||||
|  |   if (!success) { | ||||||
|  |     throw std::runtime_error( | ||||||
|  |         "PowerMinimumEigenValue failed to compute minimum eigenvalue."); | ||||||
|  |   } | ||||||
|  |   return minEigenValue; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* ************************************************************************* */ | /* ************************************************************************* */ | ||||||
| template <size_t d> | template <size_t d> | ||||||
| std::pair<double, Vector> ShonanAveraging<d>::computeMinEigenVector( | std::pair<double, Vector> ShonanAveraging<d>::computeMinEigenVector( | ||||||
|  |  | ||||||
|  | @ -26,6 +26,8 @@ | ||||||
| #include <gtsam/linear/VectorValues.h> | #include <gtsam/linear/VectorValues.h> | ||||||
| #include <gtsam/nonlinear/LevenbergMarquardtParams.h> | #include <gtsam/nonlinear/LevenbergMarquardtParams.h> | ||||||
| #include <gtsam/sfm/BinaryMeasurement.h> | #include <gtsam/sfm/BinaryMeasurement.h> | ||||||
|  | #include <gtsam/linear/PowerMethod.h> | ||||||
|  | #include <gtsam/linear/AcceleratedPowerMethod.h> | ||||||
| #include <gtsam/slam/dataset.h> | #include <gtsam/slam/dataset.h> | ||||||
| 
 | 
 | ||||||
| #include <Eigen/Sparse> | #include <Eigen/Sparse> | ||||||
|  | @ -250,6 +252,13 @@ class GTSAM_EXPORT ShonanAveraging { | ||||||
|   double computeMinEigenValue(const Values &values, |   double computeMinEigenValue(const Values &values, | ||||||
|                               Vector *minEigenVector = nullptr) const; |                               Vector *minEigenVector = nullptr) const; | ||||||
| 
 | 
 | ||||||
|  |   /**
 | ||||||
|  |    * Compute minimum eigenvalue with accelerated power method. | ||||||
|  |    * @param values: should be of type SOn | ||||||
|  |    */ | ||||||
|  |   double computeMinEigenValueAP(const Values &values, | ||||||
|  |                                 Vector *minEigenVector = nullptr) const; | ||||||
|  | 
 | ||||||
|   /// Project pxdN Stiefel manifold matrix S to Rot3^N
 |   /// Project pxdN Stiefel manifold matrix S to Rot3^N
 | ||||||
|   Values roundSolutionS(const Matrix &S) const; |   Values roundSolutionS(const Matrix &S) const; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -188,9 +188,14 @@ TEST(ShonanAveraging3, CheckWithEigen) { | ||||||
|   for (int i = 1; i < lambdas.size(); i++) |   for (int i = 1; i < lambdas.size(); i++) | ||||||
|       minEigenValue = min(lambdas(i), minEigenValue); |       minEigenValue = min(lambdas(i), minEigenValue); | ||||||
| 
 | 
 | ||||||
|  |   // Compute Eigenvalue with Accelerated Power method
 | ||||||
|  |   double lambdaAP = kShonan.computeMinEigenValueAP(Qstar3); | ||||||
|  | 
 | ||||||
|   // Actual check
 |   // Actual check
 | ||||||
|   EXPECT_DOUBLES_EQUAL(0, lambda, 1e-11); |   EXPECT_DOUBLES_EQUAL(0, lambda, 1e-11); | ||||||
|   EXPECT_DOUBLES_EQUAL(0, minEigenValue, 1e-11); |   EXPECT_DOUBLES_EQUAL(0, minEigenValue, 1e-11); | ||||||
|  |   EXPECT_DOUBLES_EQUAL(0, lambdaAP, 1e-11); | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|   // Construct test descent direction (as minEigenVector is not predictable
 |   // Construct test descent direction (as minEigenVector is not predictable
 | ||||||
|   // across platforms, being one from a basically flat 3d- subspace)
 |   // across platforms, being one from a basically flat 3d- subspace)
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue