Completed and tested power method with unittest

release/4.3a0
jingwuOUO 2020-09-14 01:58:44 -04:00
parent 2afbaaaf44
commit e02633c745
2 changed files with 149 additions and 43 deletions

View File

@ -24,14 +24,17 @@
#include <Eigen/Core> #include <Eigen/Core>
#include <Eigen/Sparse> #include <Eigen/Sparse>
#include <Eigen/Array>
#include <map> #include <map>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <algorithm>
namespace gtsam { namespace gtsam {
using Sparse = Eigen::SparseMatrix<double>;
/* ************************************************************************* */ /* ************************************************************************* */
/// MINIMUM EIGENVALUE COMPUTATIONS /// MINIMUM EIGENVALUE COMPUTATIONS
@ -39,46 +42,63 @@ namespace gtsam {
* 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
* nontrivial function, perform_op(x,y), that computes and returns the product * nontrivial function, perform_op(x,y), that computes and returns the product
* y = (A + sigma*I) x */ * y = (A + sigma*I) x */
struct MatrixProdFunctor { struct PowerFunctor {
// Const reference to an externally-held matrix whose minimum-eigenvalue we // Const reference to an externally-held matrix whose minimum-eigenvalue we
// want to compute // want to compute
const Sparse &A_; const Sparse &A_;
// Spectral shift const int m_n_; // dimension of Matrix A
double sigma_; const int m_nev_; // number of eigenvalues required
const int m_n_; // a Polyak momentum term
const int m_nev_; double beta_;
const int m_ncv_;
Vector ritz_val_;
Matrix ritz_vectors_;
// const int m_ncv_; // dimention of orthonormal basis subspace
size_t m_niter_; // number of iterations
private:
Vector ritz_val_; // all ritz eigenvalues
Matrix ritz_vectors_; // all ritz eigenvectors
Vector ritz_conv_; // store whether the ritz eigenpair converged
Vector sorted_ritz_val_; // sorted converged eigenvalue
Matrix sorted_ritz_vectors_; // sorted converged eigenvectors
public:
// Constructor // Constructor
explicit MatrixProdFunctor(const Sparse &A, int nev, int ncv, explicit PowerFunctor(const Sparse &A, int nev, int ncv,
double sigma = 0) double beta = 0)
: A_(A), : A_(A),
m_n_(A.rows()), m_n_(A.rows()),
m_nev_(nev), m_nev_(nev),
m_ncv_(ncv > m_n_ ? m_n_ : ncv), // m_ncv_(ncv > m_n_ ? m_n_ : ncv),
sigma_(sigma) {} beta_(beta),
m_niter_(0)
{}
int rows() const { return A_.rows(); } int rows() const { return A_.rows(); }
int cols() const { return A_.cols(); } int cols() const { return A_.cols(); }
// Matrix-vector multiplication operation // Matrix-vector multiplication operation
void perform_op(const double *x, double *y) const { Vector perform_op(const Vector& x1, const Vector& x0) const {
// Wrap the raw arrays as Eigen Vector types
Eigen::Map<const Vector> X(x, rows());
Eigen::Map<Vector> Y(y, rows());
// Do the multiplication using wrapped Eigen vectors // Do the multiplication using wrapped Eigen vectors
Y = A_ * X + sigma_ * X; Vector x2 = A_ * x1 - beta_ * x0;
x2.normalize();
return x2;
}
Vector perform_op(int i) const {
// Do the multiplication using wrapped Eigen vectors
Vector x1 = ritz_vectors_.col(i-1);
Vector x2 = ritz_vectors_.col(i-2);
return perform_op(x1, x2);
} }
long next_long_rand(long seed) { long next_long_rand(long seed) {
const unsigned int m_a = 16807; const unsigned int m_a = 16807;
const unsigned long m_max = 2147483647L; const unsigned long m_max = 2147483647L;
long m_rand = m_n_ ? (m_n_ & m_max) : 1; // long m_rand = m_n_ ? (m_n_ & m_max) : 1;
unsigned long lo, hi; unsigned long lo, hi;
lo = m_a * (long)(seed & 0xFFFF); lo = m_a * (long)(seed & 0xFFFF);
@ -103,62 +123,119 @@ struct MatrixProdFunctor {
long m_rand = next_long_rand(m_rand); long m_rand = next_long_rand(m_rand);
res[i] = double(m_rand) / double(m_max) - double(0.5); res[i] = double(m_rand) / double(m_max) - double(0.5);
} }
res.normalize();
return res; return res;
} }
void init(Vector data) { void init(const Vector x0, const Vector x00) {
ritz_val_.resize(m_ncv_); // initialzie ritz eigen values
ritz_val_.resize(m_n_);
ritz_val_.setZero(); ritz_val_.setZero();
ritz_vectors_.resize(m_ncv_, m_nev_);
// initialzie the ritz converged vector
ritz_conv_.resize(m_n_);
ritz_conv_.setZero();
// initialzie ritz eigen vectors
ritz_vectors_.resize(m_n_, m_n_);
ritz_vectors_.setZero(); ritz_vectors_.setZero();
Eigen::Map<Matrix> v0(init_resid.data(), m_n_); ritz_vectors_.col(0) = perform_op(x0, x00);
ritz_vectors_.col(1) = perform_op(ritz_vectors_.col(0), x0);
// setting beta
Vector init_resid = ritz_vectors_.col(0);
const double up = init_resid.transpose() * A_ * init_resid;
const double down = init_resid.transpose().dot(init_resid);
const double mu = up/down;
beta_ = mu*mu/4;
} }
void init() { void init() {
Vector init_resid = random_vec(m_n_); Vector x0 = random_vec(m_n_);
init(init_resid); Vector x00 = random_vec(m_n_);
init(x0, x00);
} }
bool converged(double tol, const Vector x) { bool converged(double tol, int i) {
Vector x = ritz_vectors_.col(i);
double theta = x.transpose() * A_ * x; double theta = x.transpose() * A_ * x;
// store the ritz eigen value
ritz_val_(i) = theta;
// update beta
beta_ = std::max(beta_, theta * theta / 4);
Vector diff = A_ * x - theta * x; Vector diff = A_ * x - theta * x;
double error = diff.lpNorm<1>(); double error = diff.lpNorm<1>();
if (error < tol) ritz_conv_(i) = 1;
return error < tol; return error < tol;
} }
int num_converged(double tol) { int num_converged(double tol) {
int converge = 0; int num_converge = 0;
for (int i=0; i<ritz_vectors_.cols(); i++) { for (int i=0; i<ritz_vectors_.cols(); i++) {
if (converged(tol, ritz_vectors_.col(i))) converged += 1; if (converged(tol, i)) {
num_converge += 1;
}
if (i>0 && i<ritz_vectors_.cols()-1) {
ritz_vectors_.col(i+1) = perform_op(i+1);
}
}
return num_converge;
}
size_t num_iterations() {
return m_niter_;
}
void sort_eigenpair() {
std::vector<std::pair<double, int>> pairs;
for(int i=0; i<ritz_conv_.size(); ++i) {
if (ritz_conv_(i)) pairs.push_back({ritz_val_(i), i});
}
std::sort(pairs.begin(), pairs.end(), [](const std::pair<double, int>& left, const std::pair<double, int>& right) {
return left.first < right.first;
});
// initialzie sorted ritz eigenvalues and eigenvectors
size_t num_converged = pairs.size();
sorted_ritz_val_.resize(num_converged);
sorted_ritz_val_.setZero();
sorted_ritz_vectors_.resize(m_n_, num_converged);
sorted_ritz_vectors_.setZero();
// fill sorted ritz eigenvalues and eigenvectors with sorted index
for(size_t j=0; j<num_converged; ++j) {
sorted_ritz_val_(j) = pairs[j].first;
sorted_ritz_vectors_.col(j) = ritz_vectors_.col(pairs[j].second);
} }
return converged;
} }
int compute(int maxit, double tol) { int compute(int maxit, double tol) {
// Starting // Starting
int i, nconv = 0, nev_adj; int i = 0;
for (i = 0; i < maxit; i++) { int nconv = 0;
for (; i < maxit; i++) {
m_niter_ +=1;
nconv = num_converged(tol); nconv = num_converged(tol);
if (nconv >= m_nev_) break; if (nconv >= m_nev_) break;
nev_adj = nev_adjusted(nconv);
restart(nev_adj);
} }
// Sorting results
sort_ritzpair(sort_rule);
m_niter += i + 1; // sort the result
m_info = (nconv >= m_nev_) ? SUCCESSFUL : NOT_CONVERGING; sort_eigenpair();
return std::min(m_nev_, nconv); return std::min(m_nev_, nconv);
} }
Vector eigenvalues() { Vector eigenvalues() {
return ritz_val_; return sorted_ritz_val_;
} }
Matrix eigenvectors() { Matrix eigenvectors() {
return ritz_vectors_; return sorted_ritz_vectors_;
} }
}; };

View File

@ -18,16 +18,45 @@
* @brief Check eigenvalue and eigenvector computed by power method * @brief Check eigenvalue and eigenvector computed by power method
*/ */
#include <gtsam/sfm/PowerMethod.h>
#include <gtsam/sfm/ShonanAveraging.h>
#include <CppUnitLite/TestHarness.h> #include <CppUnitLite/TestHarness.h>
#include <gtsam/slam/PowerMethod.h>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <iostream>
#include <random>
using namespace std; using namespace std;
using namespace gtsam; using namespace gtsam;
ShonanAveraging3 fromExampleName(
const std::string &name,
ShonanAveraging3::Parameters parameters = ShonanAveraging3::Parameters()) {
string g2oFile = findExampleDataFile(name);
return ShonanAveraging3(g2oFile, parameters);
}
static const ShonanAveraging3 kShonan = fromExampleName("toyExample.g2o");
/* ************************************************************************* */ /* ************************************************************************* */
TEST(PowerMethod, initialize) { TEST(PowerMethod, initialize) {
gtsam::Sparse A; gtsam::Sparse A(6, 6);
MatrixProdFunctor(A, 1, A.rows()); A.coeffRef(0, 0) = 6;
PowerFunctor pf(A, 1, A.rows());
pf.init();
pf.compute(20, 1e-4);
EXPECT_LONGS_EQUAL(6, pf.eigenvectors().cols());
EXPECT_LONGS_EQUAL(6, pf.eigenvectors().rows());
const Vector6 x1 = (Vector(6) << 1.0, 0.0, 0.0, 0.0, 0.0, 0.0).finished();
Vector6 actual = pf.eigenvectors().col(0);
actual(0) = abs(actual(0));
EXPECT(assert_equal(x1, actual));
const double ev1 = 6.0;
EXPECT_DOUBLES_EQUAL(ev1, pf.eigenvalues()(0), 1e-5);
} }
/* ************************************************************************* */ /* ************************************************************************* */