MUQ  0.4.3
LOBPCG.cpp
Go to the documentation of this file.
2 
4 
5 #include <boost/property_tree/ptree.hpp>
6 
7 using namespace muq::Modeling;
8 using namespace muq::Utilities;
9 
10 LOBPCG::LOBPCG(int numEigsIn,
11  double eigRelTolIn,
12  double eigAbsTolIn,
13  int blockSizeIn,
14  double solverTolIn,
15  int maxItsIn,
16  int verbosityIn) : numEigs(numEigsIn),
17  blockSize(blockSizeIn),
18  solverTol(solverTolIn),
19  eigRelTol(eigRelTolIn),
20  eigAbsTol(eigAbsTolIn),
21  maxIts(maxItsIn),
22  verbosity(verbosityIn)
23 {
24  assert(numEigs>0);
25  assert(blockSize>0);
26  assert(eigRelTol>=0.0);
27 }
28 
29 
30 LOBPCG::LOBPCG(boost::property_tree::ptree const& opts) : LOBPCG(opts.get<int>("NumEigs"),
31  opts.get("RelativeTolerance",0.0),
32  opts.get("AbsoluteTolerance",0.0),
33  opts.get("BlockSize", 1),
34  opts.get("SolverTolerance", -1.0),
35  opts.get("MaxIts",-1),
36  opts.get("Verbosity",0))
37 {}
38 
39 Eigen::MatrixXd LOBPCG::Orthonormalizer::Compute(Eigen::Ref<const Eigen::MatrixXd> const& V)
40 {
41  Eigen::MatrixXd output(V);
42  if(B!=nullptr){
43  ComputeInPlace(output,B->Apply(V));
44  }else{
45  ComputeInPlace(output,V);
46  }
47  return output;
48 }
49 
50 Eigen::MatrixXd LOBPCG::Orthonormalizer::Compute(Eigen::Ref<const Eigen::MatrixXd> const& V, Eigen::Ref<const Eigen::MatrixXd> const& BVin)
51 {
52  Eigen::MatrixXd output(V);
53  ComputeInPlace(output, BVin);
54  return output;
55 }
56 
57 bool LOBPCG::Orthonormalizer::ComputeInPlace(Eigen::Ref<Eigen::MatrixXd> V)
58 {
59  if(B!=nullptr){
60  return ComputeInPlace(V, B->Apply(V));
61  }else{
62  return ComputeInPlace(V,V);
63  }
64 }
65 
66 bool LOBPCG::Orthonormalizer::ComputeInPlace(Eigen::Ref<Eigen::MatrixXd> V, Eigen::Ref<const Eigen::MatrixXd> const& BVin)
67 {
68  vDim = V.cols();
69  BV = BVin;
70 
71  auto cholFact = (V.transpose()*BV).eval().selfadjointView<Eigen::Lower>().llt();
72  if(cholFact.info() != Eigen::Success)
73  return false;
74 
75  VBV_Chol = cholFact.matrixL();
76  V = VBV_Chol.triangularView<Eigen::Lower>().solve(V.transpose()).transpose();
77 
78  if(B!=nullptr){
79  BV = VBV_Chol.triangularView<Eigen::Lower>().solve(BV.transpose()).transpose();
80  }else{
81  BV = V;
82  }
83 
84  return true;
85 }
86 
87 Eigen::MatrixXd LOBPCG::Orthonormalizer::InverseVBV() const
88 {
89  return VBV_Chol.triangularView<Eigen::Lower>().solve(Eigen::MatrixXd::Identity(vDim,vDim));
90 }
91 
92 
93 LOBPCG::Constraints::Constraints(std::shared_ptr<LinearOperator> const& B,
94  Eigen::Ref<const Eigen::MatrixXd> const& constMat) : Y(constMat)
95 {
96  if(B!=nullptr){
97  BY = B->Apply(constMat);
98  }else{
99  BY = constMat;
100  }
101 
102  YBY_llt = (constMat.transpose() * BY).eval().selfadjointView<Eigen::Lower>().llt();
103 }
104 
105 void LOBPCG::Constraints::ApplyInPlace(Eigen::Ref<Eigen::MatrixXd> x)
106 {
107  Eigen::MatrixXd YBX = BY.transpose()*x;
108  x -= Y*YBY_llt.solve(YBX);
109 }
110 
111 LOBPCG& LOBPCG::compute(std::shared_ptr<LinearOperator> const& A,
112  Eigen::MatrixXd const& constMat,
113  std::shared_ptr<LinearOperator> B,
114  std::shared_ptr<LinearOperator> M)
115 {
116 
117  const int dim = A->rows();
118  if(numEigs>int(std::floor(0.2*dim))){
119  std::cerr << "ERROR: LOBPCG can fail when more than 20\% of the eigenvalues are computed." << std::endl;
120  assert(numEigs>int(std::ceil(0.2*dim)));
121  }
122 
123  // Initial message
124  if(verbosity>0){
125  std::cout << "Solving ";
126  if(B==nullptr){
127  std::cout << "standard ";
128  }else{
129  std::cout << "generalized ";
130  }
131  std::cout << "eigenvalue problem." << std::endl;
132 
133  if(verbosity>1)
134  std::cout << " matrix size = " << A->rows() << " x " << A->cols() << std::endl;
135  }
136 
137  // If this is the first call to compute, initialize everything
138  reset(dim);
139 
140  // Initialize the eigenvalue and eigenvector matrices if they haven't been initialized yet
141  if(eigVecs.rows()==0){
142  eigVecs.resize(dim, numEigs);
143  eigVecs = RandomGenerator::GetUniform(dim,numEigs);
144  eigVals.resize(numEigs);
145 
146  }else if(eigVecs.cols()>numEigs){
147  numEigs = eigVecs.cols();
148 
149  }else if(eigVecs.cols()<numEigs){
150  unsigned int oldSize = eigVecs.cols();
151  eigVecs.conservativeResize(dim, numEigs);
152  eigVecs.rightCols(numEigs - oldSize) = RandomGenerator::GetUniform(dim,numEigs-oldSize);
153  }
154 
155  // Get an estimate of matrix norms
156  { // TODO: Move this into a function
157  const int numNormTest = std::min(numEigs,5);
158  Eigen::MatrixXd gaussMat = RandomGenerator::GetNormal(dim, numNormTest);
159  double gaussMatNorm = gaussMat.norm();
160  Anorm = A->Apply(gaussMat).norm() / gaussMatNorm;
161 
162  if(B!=nullptr){
163  Bnorm = B->Apply(gaussMat).norm() / gaussMatNorm;
164  }else{
165  Bnorm = 1.0;
166  }
167  }
168 
169  Eigen::VectorXd blockVals;
170  Eigen::MatrixXd blockVecs;
171 
172  Eigen::MatrixXd constraints;
173  unsigned int maxBlocks = std::ceil(float(numEigs)/float(blockSize));
174  if(constMat.cols()+numEigs-blockSize > 0)
175  constraints.resize(dim, constMat.cols()+maxBlocks*blockSize-blockSize);
176  if(constMat.cols()>0)
177  constraints.leftCols(constMat.cols()) = constMat;
178 
179  // Loop over the blocks needed to compute the eigenvalues
180  int numComputed;
181  for(numComputed=0; numComputed<numEigs; numComputed+=blockSize){
182 
183  if(numComputed>0)
184  constraints.block(0,numComputed+constMat.cols()-blockSize,dim,blockSize) = eigVecs.block(0,numComputed-blockSize,dim,blockSize);
185 
186  // Make sure the eigenvalue and eigenvector matrices have enough space. Intialize any new vectors to random values
187  if(numComputed + blockSize > eigVecs.cols()){
188  int numNew = numComputed + blockSize - eigVecs.cols();
189  eigVals.conservativeResize(numComputed + blockSize);
190  eigVecs.conservativeResize(dim, numComputed + blockSize);
191  eigVecs.rightCols(numNew) = RandomGenerator::GetUniform(dim, numNew);
192  }
193 
194  std::tie(blockVals, blockVecs) = ComputeBlock(A,
195  eigVecs.block(0,numComputed,dim,blockSize), // initial guess
196  constraints.leftCols(numComputed+constMat.cols()), // Make sure this block produces vectors that are perpendicular to the previously computed vectors
197  B, // RHS matrix
198  M); // preconditioner
199 
200  eigVals.segment(numComputed, blockSize) = blockVals;
201  eigVecs.block(0,numComputed, dim, blockSize) = blockVecs;
202 
203  // Check to see if we've capture the dominant eigenvalues
204  if(numComputed>0){
205  if( (eigVals(numComputed-1)< eigRelTol*eigVals(0))||(eigVals(numComputed-1)<eigAbsTol)){
206  eigVals.conservativeResize(numComputed);
207  eigVecs = eigVecs.leftCols(numComputed).eval();
208  break;
209  }
210  }
211 
212  }
213 
214  int numToKeep;
215  for(numToKeep=1; numToKeep<numEigs; ++numToKeep){
216  if((eigVals(numToKeep)< eigRelTol*eigVals(0))||(eigVals(numToKeep)<eigAbsTol))
217  break;
218  }
219  if(numToKeep<eigVecs.cols()){
220  eigVals = eigVals.head(numToKeep).eval();
221  eigVecs = eigVecs.leftCols(numToKeep).eval();
222  }
223 
224  return *this;
225 }
226 
227 std::pair<Eigen::VectorXd, Eigen::MatrixXd> LOBPCG::ComputeBlock(std::shared_ptr<LinearOperator> const& A,
228  Eigen::Ref<const Eigen::MatrixXd> const& X0,
229  Eigen::Ref<const Eigen::MatrixXd> const& constMat,
230  std::shared_ptr<LinearOperator> B,
231  std::shared_ptr<LinearOperator> M)
232 {
233  // Make sure X0 has the right shape
234  assert(X0.cols()==blockSize);
235  assert(X0.rows()==A->rows());
236  assert(A->rows()==A->cols());
237 
238  if(B!=nullptr){
239  assert(B->rows()==A->rows());
240  assert(B->rows()==B->cols());
241  }
242 
243  if(M!=nullptr){
244  assert(M->rows()==A->rows());
245  assert(M->rows()==M->cols());
246  }
247 
248  if(constMat.rows()>0){
249  assert(constMat.rows()==A->rows());
250  }
251 
252  Eigen::MatrixXd X = X0;
253 
254  // To start with, all of the indices are active
255  unsigned int numActive = blockSize;
256 
257  // A vector full of booleans measuring if each eigenvalue is fixed or not
258  std::vector<bool> isActive(blockSize,true);
259 
260  // Get ready to apply constraints (e.g., build Y, BY, )
261  std::shared_ptr<Constraints> consts;
262  if(constMat.rows()>0){
263  consts = std::make_shared<Constraints>(B, constMat);
264  consts->ApplyInPlace(X);
265  }
266 
267  // Make the current vectors orthogonal wrt B
268  Orthonormalizer bOrtho(B);
269  bOrtho.ComputeInPlace(X);
270 
271  Eigen::MatrixXd BX;
272  if(B != nullptr){
273  BX = bOrtho.GetBV();
274  }else{
275  BX = X;
276  }
277 
278  // Compute the initial Ritz vectors by solving the reduced eigenproblem.
279  Eigen::MatrixXd AX = A->Apply(X);
280 
281  Eigen::MatrixXd XAX = X.transpose() * AX;
282  XAX = 0.5*(XAX+XAX.transpose()).eval();
283 
284  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ritzSolver(XAX);
285  Eigen::VectorXd subEigVals = ritzSolver.eigenvalues();
286  Eigen::MatrixXd subEigVecs = ritzSolver.eigenvectors();
287 
288  auto swaps = GetSortSwaps(subEigVals);
289  SortVec(swaps,subEigVals);
290  SortCols(swaps, subEigVecs);
291 
292 
293  X = (X*subEigVecs).eval();
294  AX = (AX*subEigVecs).eval();
295  if(B!=nullptr){
296  BX = (BX*subEigVecs).eval();
297  }else{
298  BX = X;
299  }
300 
301  //
302  Eigen::MatrixXd resids(X.rows(),X.cols());
303  Eigen::VectorXd residNorms;
304  Eigen::MatrixXd AR, P, BP, AP;
305  AR.resize(X.rows(),X.cols());
306 
307  Eigen::VectorXd convCrit, oldConvCrit;
308  convCrit = Eigen::VectorXd::Constant(X.cols(), std::numeric_limits<double>::infinity());
309 
310  for(unsigned it=0; it<maxIts; ++it){
311 
312  bool useP = it!=0;
313 
314  if(consts!=nullptr)
315  consts->ApplyInPlace(X);
316 
317  if(verbosity>0)
318  std::cout << "Iteration " << it << std::endl;
319 
320  // Compute the residuals
321  resids.leftCols(numActive) = AX.leftCols(numActive) - BX.leftCols(numActive)*subEigVals.head(numActive).asDiagonal();
322 
323  // Compute the norm of the residuals
324  residNorms = resids.colwise().norm();
325 
326  // Compute the convergence criteria
327  oldConvCrit = convCrit;
328  convCrit = residNorms.array() / ((Anorm + Bnorm*subEigVals.array())*X.colwise().norm().transpose().array());
329 
330  // Figure out how many indices are active
331  numActive = 0;
332  for(unsigned int i=0; i<residNorms.size(); ++i){
333 
334  // Fix a vector if we've converged or haven't improved
335  if((convCrit(i)<solverTol)||(std::abs(convCrit(i)-oldConvCrit(i))<1e-14)){
336  isActive.at(i) = false;
337  }
338  numActive += int(isActive.at(i));
339  }
340 
341  if(verbosity>2)
342  std::cout << " numActive = " << numActive << std::endl;
343 
344  if(verbosity>2)
345  std::cout << " eigenvalues = " << subEigVals.transpose() << std::endl;
346 
347  if(verbosity>1){
348  std::cout << " residNorms = " << residNorms.transpose() << std::endl;
349  std::cout << " convergence criteria = " << convCrit.transpose() << std::endl;
350  }
351 
352  // If we've converged for all the vectors, break
353  if(numActive==0){
354  auto swaps = GetSortSwaps(subEigVals);
355  SortVec(swaps, subEigVals);//.head(numActive));
356  SortCols(swaps, X);//.leftCols(numActive));
357 
358  return std::make_pair(subEigVals, X);
359  }
360 
361 
362  // Sort everything so the first columns corresponds to the largest residuals
363  auto swaps = GetSortSwaps(residNorms,isActive);
364 
365  SortVec(swaps, residNorms);
366  SortVec(swaps, isActive);
367  SortVec(swaps, subEigVals);//.head(numActive));
368  SortCols(swaps, X);//.leftCols(numActive));
369  SortCols(swaps, AX);//.leftCols(numActive));
370  SortCols(swaps, BX);//.leftCols(numActive));
371  SortCols(swaps, resids);
372 
373  if(it!=0){
374  SortCols(swaps, P);//.leftCols(numActive));
375  SortCols(swaps, AP);//.leftCols(numActive));
376  SortCols(swaps, BP);//.leftCols(numActive));
377  }
378 
379  // Apply the preconditioner
380  if(M!=nullptr)
381  resids.leftCols(numActive) = M->Apply(resids.leftCols(numActive));
382 
383  // Apply the constraints
384  if(consts!=nullptr)
385  consts->ApplyInPlace(resids.leftCols(numActive));
386 
387  resids.leftCols(numActive) -= (X * BX.transpose() * resids.leftCols(numActive)).eval();
388 
389  // Orthonormalize residuals wrt B-inner product
390  bOrtho.ComputeInPlace(resids.leftCols(numActive));
391 
392  Eigen::MatrixXd BR;
393  if(B != nullptr){
394  BR = bOrtho.GetBV();
395  } else {
396  BR = resids.leftCols(numActive);
397  }
398 
399  AR.leftCols(numActive) = A->Apply(resids.leftCols(numActive));
400 
401  if(useP){
402 
403  // Orthonormalize P
404  bool success = bOrtho.ComputeInPlace(P.leftCols(numActive),BP.leftCols(numActive));
405  if(!success){
406  useP = false;
407  }else{
408 
409  AP.leftCols(numActive) = bOrtho.VBV_Chol.triangularView<Eigen::Lower>().solve(AP.leftCols(numActive).transpose()).transpose();
410 
411  if(B!=nullptr){
412  BP = B->Apply(P);
413  }else{
414  BP = P;
415  }
416  } // else if(!success)
417  }
418 
419  Eigen::MatrixXd XAR = X.transpose()*AR.leftCols(numActive);
420  Eigen::MatrixXd RAR = resids.leftCols(numActive).transpose()*AR.leftCols(numActive);
421  RAR = 0.5*(RAR+RAR.transpose()).eval();
422  Eigen::MatrixXd XAX = X.transpose()*AX;
423  XAX = 0.5*(XAX+XAX.transpose()).eval();
424 
425  Eigen::MatrixXd XBX, RBR, XBR;
426 
427  XBX = BX.transpose() * X;
428  XBX = 0.5*(XBX+XBX.transpose()).eval();
429  RBR = BR.transpose() * resids.leftCols(numActive);
430  XAX = 0.5*(RBR+RBR.transpose()).eval();
431  XBR = X.transpose() * BR.leftCols(numActive);
432 
433  Eigen::MatrixXd gramA, gramB;
434 
435  if(useP){
436  gramA.resize(blockSize+2*numActive,blockSize+2*numActive);
437  gramB.resize(blockSize+2*numActive,blockSize+2*numActive);
438 
439  Eigen::MatrixXd XAP = X.transpose() * AP.leftCols(numActive);
440  Eigen::MatrixXd RAP = resids.leftCols(numActive).transpose()*AP.leftCols(numActive);
441  Eigen::MatrixXd PAP = P.leftCols(numActive).transpose()*AP.leftCols(numActive);
442  PAP = 0.5*(PAP+PAP.transpose()).eval();
443  Eigen::MatrixXd XBP = X.transpose()*BP.leftCols(numActive);
444  Eigen::MatrixXd RBP = resids.leftCols(numActive).transpose()*BP.leftCols(numActive);
445 
446  gramA.block(0,0,blockSize,blockSize) = subEigVals.asDiagonal();
447  gramA.block(0,blockSize,blockSize,numActive) = XAR;
448  gramA.block(0,blockSize+numActive,blockSize,numActive) = XAP;
449  gramA.block(blockSize,0,numActive,blockSize) = XAR.transpose();
450  gramA.block(blockSize,blockSize,numActive,numActive) = RAR;
451  gramA.block(blockSize,blockSize+numActive,numActive,numActive) = RAP;
452  gramA.block(blockSize+numActive,0,numActive,blockSize) = XAP.transpose();
453  gramA.block(blockSize+numActive,blockSize,numActive,numActive) = RAP.transpose();
454  gramA.block(blockSize+numActive,blockSize+numActive,numActive,numActive) = PAP;
455 
456  gramB.block(0,0,blockSize,blockSize) = Eigen::MatrixXd::Identity(blockSize,blockSize);
457  gramB.block(0,blockSize,blockSize,numActive) = XBR;
458  gramB.block(0,blockSize+numActive,blockSize,numActive) = XBP;
459  gramB.block(blockSize,0,numActive,blockSize) = XBR.transpose();
460  gramB.block(blockSize,blockSize,numActive,numActive) = Eigen::MatrixXd::Identity(numActive,numActive);
461  gramB.block(blockSize,blockSize+numActive,numActive,numActive) = RBP;
462  gramB.block(blockSize+numActive,0,numActive,blockSize) = XBP.transpose();
463  gramB.block(blockSize+numActive,blockSize,numActive,numActive) = RBP.transpose();
464  gramB.block(blockSize+numActive,blockSize+numActive,numActive,numActive) = Eigen::MatrixXd::Identity(numActive,numActive);
465 
466  }else{
467 
468  gramA.resize(blockSize+numActive,blockSize+numActive);
469  gramB.resize(blockSize+numActive,blockSize+numActive);
470 
471  gramA.block(0,0,blockSize,blockSize) = subEigVals.asDiagonal();
472  gramA.block(0,blockSize,blockSize,numActive) = XAR;
473  gramA.block(blockSize,0,numActive,blockSize) = XAR.transpose();
474  gramA.block(blockSize,blockSize,numActive,numActive) = RAR;
475 
476  gramB.block(0,0,blockSize,blockSize) = Eigen::MatrixXd::Identity(blockSize,blockSize);
477  gramB.block(0,blockSize,blockSize,numActive) = XBR;
478  gramB.block(blockSize,0,numActive,blockSize) = XBR.transpose();
479  gramB.block(blockSize,blockSize,numActive,numActive) = Eigen::MatrixXd::Identity(numActive,numActive);
480  }
481 
482  Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> ritzSolver(gramA, gramB);
483  assert(ritzSolver.info()==Eigen::Success);
484 
485  // Get and sort the ritz values
486  subEigVals = ritzSolver.eigenvalues().tail(blockSize);
487  if(subEigVals.size()>1)
488  assert(subEigVals(0)<subEigVals(1));
489 
490  subEigVecs = ritzSolver.eigenvectors().rightCols(blockSize);
491 
492  swaps = GetSortSwaps(subEigVals);
493  SortVec(swaps,subEigVals);
494  SortCols(swaps, subEigVecs);
495 
496  Eigen::Ref<const Eigen::MatrixXd> eigX = subEigVecs.topRows(blockSize);
497  Eigen::Ref<const Eigen::MatrixXd> eigR = subEigVecs.block(blockSize,0,numActive,subEigVecs.cols());
498 
499  Eigen::MatrixXd pp = resids.leftCols(numActive) * eigR;
500  Eigen::MatrixXd app = AR.leftCols(numActive)*eigR;
501  Eigen::MatrixXd bpp = BR.leftCols(numActive)*eigR;
502 
503  if(useP){
504  Eigen::Ref<const Eigen::MatrixXd> eigP = subEigVecs.bottomRows(numActive);
505 
506  pp += P.leftCols(numActive)*eigP;
507  app += AP.leftCols(numActive)*eigP;
508  bpp += BP.leftCols(numActive)*eigP;
509  }
510 
511  X = (X*eigX + pp).eval();
512  AX = (AX*eigX + app).eval();
513  BX = (BX*eigX + bpp).eval();
514 
515  P = pp;
516  AP = app;
517  BP = bpp;
518 
519  }
520 
521  std::cerr << "WARNING: LOBPCG reached maximum iterations." << std::endl;
522 
523  // Sort the results so that the eigenvalues are descending
524  swaps = GetSortSwaps(subEigVals);
525  SortVec(swaps, subEigVals);//.head(numActive));
526  SortCols(swaps, X);//.leftCols(numActive));
527 
528  return std::make_pair(subEigVals, X);
529 }
530 
531 
532 void LOBPCG::InitializeVectors(Eigen::MatrixXd const& X0)
533 {
534  if(eigVecs.rows()>0)
535  assert(X0.rows()==eigVecs.rows());
536 
537  eigVals.resize(X0.cols());
538  eigVecs = X0;
539 }
540 
541 LOBPCG& LOBPCG::reset(int dim)
542 {
543  if(solverTol<0.0)
544  solverTol = dim * std::sqrt(std::numeric_limits<double>::epsilon());
545 
546  if(maxIts<0)
547  maxIts = 500;
548 
549  return *this;
550 }
static std::vector< std::pair< int, int > > GetSortSwaps(Eigen::Ref< const Eigen::VectorXd > const &residNorms, std::vector< bool > const &isActive)
std::shared_ptr< LinearOperator > A
static void SortVec(std::vector< std::pair< int, int >> const &swapInds, Eigen::Ref< Eigen::VectorXd > matrix)
std::shared_ptr< LinearOperator > M
static void SortCols(std::vector< std::pair< int, int >> const &swapInds, Eigen::Ref< Eigen::MatrixXd > matrix)
std::shared_ptr< LinearOperator > B
The Locally Optimal Block Preconditioned Conjugate Gradient Method (LOBPCG) method for matrix-free co...
Definition: LOBPCG.h:21
LOBPCG & compute(std::shared_ptr< LinearOperator > const &A, std::shared_ptr< LinearOperator > B=nullptr, std::shared_ptr< LinearOperator > M=nullptr)
Definition: LOBPCG.h:64
LOBPCG(int numEigsIn, double eigRelTolIn=0.0, double eigAbsTolIn=0.0, int blockSizeIn=-1, double solverTolIn=-1, int maxItsIn=-1, int verbosityIn=0)
Definition: LOBPCG.cpp:10
auto get(const nlohmann::detail::iteration_proxy_value< IteratorType > &i) -> decltype(i.key())
Definition: json.h:3956