ARPACK-Eigen
SymEigsSolver.h
1 #ifndef SYM_EIGS_SOLVER_H
2 #define SYM_EIGS_SOLVER_H
3 
4 #include <Eigen/Core>
5 #include <Eigen/Eigenvalues>
6 
7 #include <vector>
8 #include <algorithm>
9 #include <cmath>
10 #include <utility>
11 #include <stdexcept>
12 
13 #include "SelectionRule.h"
14 #include "UpperHessenbergQR.h"
15 #include "MatOp/DenseGenMatProd.h"
16 #include "MatOp/DenseSymShiftSolve.h"
17 
18 
24 
135 template < typename Scalar = double,
136  int SelectionRule = LARGEST_MAGN,
137  typename OpType = DenseGenMatProd<double> >
139 {
140 private:
141  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> Matrix;
142  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;
143  typedef Eigen::Array<Scalar, Eigen::Dynamic, 1> Array;
144  typedef Eigen::Array<bool, Eigen::Dynamic, 1> BoolArray;
145  typedef Eigen::Map<const Matrix> MapMat;
146  typedef Eigen::Map<const Vector> MapVec;
147  typedef Eigen::SelfAdjointEigenSolver<Matrix> EigenSolver;
148  typedef std::pair<Scalar, int> SortPair;
149 
150 protected:
151  OpType *op; // object to conduct matrix operation,
152  // e.g. matrix-vector product
153 
154 private:
155  const int dim_n; // dimension of matrix A
156 
157 protected:
158  const int nev; // number of eigenvalues requested
159 
160 private:
161  const int ncv; // number of ritz values
162  int nmatop; // number of matrix operations called
163  int niter; // number of restarting iterations
164 
165  Matrix fac_V; // V matrix in the Arnoldi factorization
166  Matrix fac_H; // H matrix in the Arnoldi factorization
167  Vector fac_f; // residual in the Arnoldi factorization
168 
169 protected:
170  Vector ritz_val; // ritz values
171 
172 private:
173  Matrix ritz_vec; // ritz vectors
174  BoolArray ritz_conv; // indicator of the convergence of ritz values
175 
176  const Scalar prec; // precision parameter used to test convergence
177  // prec = epsilon^(2/3)
178  // epsilon is the machine precision,
179  // e.g. ~= 1e-16 for the "double" type
180 
181  // Arnoldi factorization starting from step-k
182  void factorize_from(int from_k, int to_m, const Vector &fk)
183  {
184  if(to_m <= from_k) return;
185 
186  fac_f = fk;
187 
188  Vector v(dim_n), w(dim_n);
189  Scalar beta = 0.0, Hii = 0.0;
190  // Keep the upperleft k x k submatrix of H and set other elements to 0
191  fac_H.rightCols(ncv - from_k).setZero();
192  fac_H.block(from_k, 0, ncv - from_k, from_k).setZero();
193  for(int i = from_k; i <= to_m - 1; i++)
194  {
195  beta = fac_f.norm();
196  v.noalias() = fac_f / beta;
197  fac_V.col(i) = v; // The (i+1)-th column
198  fac_H(i, i - 1) = beta;
199 
200  op->perform_op(v.data(), w.data());
201  nmatop++;
202 
203  Hii = v.dot(w);
204  fac_H(i - 1, i) = beta;
205  fac_H(i, i) = Hii;
206 
207  fac_f.noalias() = w - beta * fac_V.col(i - 1) - Hii * v;
208  // Correct f if it is not orthogonal to V
209  // Typically the largest absolute value occurs in
210  // the first element, i.e., <v1, f>, so we use this
211  // to test the orthogonality
212  Scalar v1f = fac_f.dot(fac_V.col(0));
213  if(v1f > prec || v1f < -prec)
214  {
215  Vector Vf(i + 1);
216  Vf.tail(i) = fac_V.block(0, 1, dim_n, i).transpose() * fac_f;
217  Vf[0] = v1f;
218  fac_f -= fac_V.leftCols(i + 1) * Vf;
219  }
220  }
221  }
222 
223  // Implicitly restarted Arnoldi factorization
224  void restart(int k)
225  {
226  if(k >= ncv)
227  return;
228 
229  TridiagQR<Scalar> decomp;
230  Vector em(ncv);
231  em.setZero();
232  em[ncv - 1] = 1;
233 
234  for(int i = k; i < ncv; i++)
235  {
236  // QR decomposition of H-mu*I, mu is the shift
237  fac_H.diagonal().array() -= ritz_val[i];
238  decomp.compute(fac_H);
239 
240  // V -> VQ
241  decomp.apply_YQ(fac_V);
242  // H -> Q'HQ
243  // Since QR = H - mu * I, we have H = QR + mu * I
244  // and therefore Q'HQ = RQ + mu * I
245  fac_H = decomp.matrix_RQ();
246  fac_H.diagonal().array() += ritz_val[i];
247  // em -> Q'em
248  decomp.apply_QtY(em);
249  }
250 
251  Vector fk = fac_f * em[k - 1] + fac_V.col(k) * fac_H(k, k - 1);
252  factorize_from(k, ncv, fk);
253  retrieve_ritzpair();
254  }
255 
256  // Calculate the number of converged Ritz values
257  int num_converged(Scalar tol)
258  {
259  // thresh = tol * max(prec, abs(theta)), theta for ritz value
260  Array thresh = tol * ritz_val.head(nev).array().abs().max(prec);
261  Array resid = ritz_vec.template bottomRows<1>().transpose().array().abs() * fac_f.norm();
262  // Converged "wanted" ritz values
263  ritz_conv = (resid < thresh);
264 
265  return ritz_conv.cast<int>().sum();
266  }
267 
268  // Return the adjusted nev for restarting
269  int nev_adjusted(int nconv)
270  {
271  int nev_new = nev;
272 
273  // Adjust nev_new, according to dsaup2.f line 677~684 in ARPACK
274  nev_new = nev + std::min(nconv, (ncv - nev) / 2);
275  if(nev == 1 && ncv >= 6)
276  nev_new = ncv / 2;
277  else if(nev == 1 && ncv > 2)
278  nev_new = 2;
279 
280  return nev_new;
281  }
282 
283  // Retrieve and sort ritz values and ritz vectors
284  void retrieve_ritzpair()
285  {
286  EigenSolver eig(fac_H);
287  Vector evals = eig.eigenvalues();
288  Matrix evecs = eig.eigenvectors();
289 
290  std::vector<SortPair> pairs(ncv);
291  EigenvalueComparator<Scalar, SelectionRule> comp;
292  for(int i = 0; i < ncv; i++)
293  {
294  pairs[i].first = evals[i];
295  pairs[i].second = i;
296  }
297  std::sort(pairs.begin(), pairs.end(), comp);
298  // For BOTH_ENDS, the eigenvalues are sorted according
299  // to the LARGEST_ALGE rule, so we need to move those smallest
300  // values to the left
301  // The order would be
302  // Largest => Smallest => 2nd largest => 2nd smallest => ...
303  // We keep this order since the first k values will always be
304  // the wanted collection, no matter k is nev_updated (used in restart())
305  // or is nev (used in sort_ritzpair())
306  if(SelectionRule == BOTH_ENDS)
307  {
308  std::vector<SortPair> pairs_copy(pairs);
309  for(int i = 0; i < ncv; i++)
310  {
311  // If i is even, pick values from the left (large values)
312  // If i is odd, pick values from the right (small values)
313  if(i % 2 == 0)
314  pairs[i] = pairs_copy[i / 2];
315  else
316  pairs[i] = pairs_copy[ncv - 1 - i / 2];
317  }
318  }
319 
320  // Copy the ritz values and vectors to ritz_val and ritz_vec, respectively
321  for(int i = 0; i < ncv; i++)
322  {
323  ritz_val[i] = pairs[i].first;
324  }
325  for(int i = 0; i < nev; i++)
326  {
327  ritz_vec.col(i) = evecs.col(pairs[i].second);
328  }
329  }
330 
331 protected:
332  // Sort the first nev Ritz pairs in decreasing magnitude order
333  // This is used to return the final results
334  virtual void sort_ritzpair()
335  {
336  std::vector<SortPair> pairs(nev);
337  EigenvalueComparator<Scalar, LARGEST_MAGN> comp;
338  for(int i = 0; i < nev; i++)
339  {
340  pairs[i].first = ritz_val[i];
341  pairs[i].second = i;
342  }
343  std::sort(pairs.begin(), pairs.end(), comp);
344 
345  Matrix new_ritz_vec(ncv, nev);
346  BoolArray new_ritz_conv(nev);
347 
348  for(int i = 0; i < nev; i++)
349  {
350  ritz_val[i] = pairs[i].first;
351  new_ritz_vec.col(i) = ritz_vec.col(pairs[i].second);
352  new_ritz_conv[i] = ritz_conv[pairs[i].second];
353  }
354 
355  ritz_vec.swap(new_ritz_vec);
356  ritz_conv.swap(new_ritz_conv);
357  }
358 
359 public:
377  SymEigsSolver(OpType *op_, int nev_, int ncv_) :
378  op(op_),
379  dim_n(op->rows()),
380  nev(nev_),
381  ncv(ncv_ > dim_n ? dim_n : ncv_),
382  nmatop(0),
383  niter(0),
384  prec(std::pow(std::numeric_limits<Scalar>::epsilon(), Scalar(2.0 / 3)))
385  {
386  if(nev_ < 1 || nev_ > dim_n - 1)
387  throw std::invalid_argument("nev must satisfy 1 <= nev <= n - 1, n is the size of matrix");
388 
389  if(ncv_ <= nev_ || ncv_ > dim_n)
390  throw std::invalid_argument("ncv must satisfy nev < ncv <= n, n is the size of matrix");
391  }
392 
402  void init(const Scalar *init_resid)
403  {
404  // Reset all matrices/vectors to zero
405  fac_V.resize(dim_n, ncv);
406  fac_H.resize(ncv, ncv);
407  fac_f.resize(dim_n);
408  ritz_val.resize(ncv);
409  ritz_vec.resize(ncv, nev);
410  ritz_conv.resize(nev);
411 
412  fac_V.setZero();
413  fac_H.setZero();
414  fac_f.setZero();
415  ritz_val.setZero();
416  ritz_vec.setZero();
417  ritz_conv.setZero();
418 
419  nmatop = 0;
420  niter = 0;
421 
422  Vector v(dim_n);
423  std::copy(init_resid, init_resid + dim_n, v.data());
424  Scalar vnorm = v.norm();
425  if(vnorm < prec)
426  throw std::invalid_argument("initial residual vector cannot be zero");
427  v /= vnorm;
428 
429  Vector w(dim_n);
430  op->perform_op(v.data(), w.data());
431  nmatop++;
432 
433  fac_H(0, 0) = v.dot(w);
434  fac_f = w - v * fac_H(0, 0);
435  fac_V.col(0) = v;
436  }
437 
445  void init()
446  {
447  Vector init_resid = Vector::Random(dim_n);
448  init_resid.array() -= 0.5;
449  init(init_resid.data());
450  }
451 
460  int compute(int maxit = 1000, Scalar tol = 1e-10)
461  {
462  // The m-step Arnoldi factorization
463  factorize_from(1, ncv, fac_f);
464  retrieve_ritzpair();
465  // Restarting
466  int i, nconv, nev_adj;
467  for(i = 0; i < maxit; i++)
468  {
469  nconv = num_converged(tol);
470  if(nconv >= nev)
471  break;
472 
473  nev_adj = nev_adjusted(nconv);
474  restart(nev_adj);
475  }
476  // Sorting results
477  sort_ritzpair();
478 
479  niter += i + 1;
480 
481  return std::min(nev, nconv);
482  }
483 
487  int num_iterations() { return niter; }
488 
492  int num_operations() { return nmatop; }
493 
501  Vector eigenvalues()
502  {
503  int nconv = ritz_conv.cast<int>().sum();
504  Vector res(nconv);
505 
506  if(!nconv)
507  return res;
508 
509  int j = 0;
510  for(int i = 0; i < nev; i++)
511  {
512  if(ritz_conv[i])
513  {
514  res[j] = ritz_val[i];
515  j++;
516  }
517  }
518 
519  return res;
520  }
521 
529  Matrix eigenvectors()
530  {
531  int nconv = ritz_conv.cast<int>().sum();
532  Matrix res(dim_n, nconv);
533 
534  if(!nconv)
535  return res;
536 
537  Matrix ritz_vec_conv(ncv, nconv);
538  int j = 0;
539  for(int i = 0; i < nev; i++)
540  {
541  if(ritz_conv[i])
542  {
543  ritz_vec_conv.col(j) = ritz_vec.col(i);
544  j++;
545  }
546  }
547 
548  res.noalias() = fac_V * ritz_vec_conv;
549 
550  return res;
551  }
552 };
553 
554 
555 
556 
557 
683 template <typename Scalar = double,
684  int SelectionRule = LARGEST_MAGN,
685  typename OpType = DenseSymShiftSolve<double> >
686 class SymEigsShiftSolver: public SymEigsSolver<Scalar, SelectionRule, OpType>
687 {
688 private:
689  typedef Eigen::Array<Scalar, Eigen::Dynamic, 1> Array;
690 
691  Scalar sigma;
692 
693  // First transform back the ritz values, and then sort
694  void sort_ritzpair()
695  {
696  Array ritz_val_org = Scalar(1.0) / this->ritz_val.head(this->nev).array() + sigma;
697  this->ritz_val.head(this->nev) = ritz_val_org;
699  }
700 public:
719  SymEigsShiftSolver(OpType *op_, int nev_, int ncv_, Scalar sigma_) :
720  SymEigsSolver<Scalar, SelectionRule, OpType>(op_, nev_, ncv_),
721  sigma(sigma_)
722  {
723  this->op->set_shift(sigma);
724  }
725 };
726 
727 #endif // SYM_EIGS_SOLVER_H
void compute(ConstGenericMatrix &mat)
SymEigsSolver(OpType *op_, int nev_, int ncv_)
int num_operations()
SymEigsShiftSolver(OpType *op_, int nev_, int ncv_, Scalar sigma_)
int compute(int maxit=1000, Scalar tol=1e-10)
void apply_YQ(GenericMatrix Y)
Matrix eigenvectors()
int num_iterations()
Matrix matrix_RQ()
void init(const Scalar *init_resid)
Vector eigenvalues()
void apply_QtY(Vector &Y)