5 #include <boost/property_tree/ptree.hpp>
16 int verbosityIn) : numEigs(numEigsIn),
17 blockSize(blockSizeIn),
18 solverTol(solverTolIn),
19 eigRelTol(eigRelTolIn),
20 eigAbsTol(eigAbsTolIn),
22 verbosity(verbosityIn)
26 assert(eigRelTol>=0.0);
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))
39 Eigen::MatrixXd LOBPCG::Orthonormalizer::Compute(Eigen::Ref<const Eigen::MatrixXd>
const& V)
41 Eigen::MatrixXd output(V);
43 ComputeInPlace(output,B->Apply(V));
45 ComputeInPlace(output,V);
50 Eigen::MatrixXd LOBPCG::Orthonormalizer::Compute(Eigen::Ref<const Eigen::MatrixXd>
const& V, Eigen::Ref<const Eigen::MatrixXd>
const& BVin)
52 Eigen::MatrixXd output(V);
53 ComputeInPlace(output, BVin);
57 bool LOBPCG::Orthonormalizer::ComputeInPlace(Eigen::Ref<Eigen::MatrixXd> V)
60 return ComputeInPlace(V, B->Apply(V));
62 return ComputeInPlace(V,V);
66 bool LOBPCG::Orthonormalizer::ComputeInPlace(Eigen::Ref<Eigen::MatrixXd> V, Eigen::Ref<const Eigen::MatrixXd>
const& BVin)
71 auto cholFact = (V.transpose()*BV).eval().selfadjointView<Eigen::Lower>().llt();
72 if(cholFact.info() != Eigen::Success)
75 VBV_Chol = cholFact.matrixL();
76 V = VBV_Chol.triangularView<Eigen::Lower>().solve(V.transpose()).transpose();
79 BV = VBV_Chol.triangularView<Eigen::Lower>().solve(BV.transpose()).transpose();
87 Eigen::MatrixXd LOBPCG::Orthonormalizer::InverseVBV()
const
89 return VBV_Chol.triangularView<Eigen::Lower>().solve(Eigen::MatrixXd::Identity(vDim,vDim));
93 LOBPCG::Constraints::Constraints(std::shared_ptr<LinearOperator>
const& B,
94 Eigen::Ref<const Eigen::MatrixXd>
const& constMat) : Y(constMat)
97 BY =
B->Apply(constMat);
102 YBY_llt = (constMat.transpose() * BY).eval().selfadjointView<Eigen::Lower>().llt();
105 void LOBPCG::Constraints::ApplyInPlace(Eigen::Ref<Eigen::MatrixXd> x)
107 Eigen::MatrixXd YBX = BY.transpose()*x;
108 x -= Y*YBY_llt.solve(YBX);
112 Eigen::MatrixXd
const& constMat,
113 std::shared_ptr<LinearOperator> B,
114 std::shared_ptr<LinearOperator> M)
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)));
125 std::cout <<
"Solving ";
127 std::cout <<
"standard ";
129 std::cout <<
"generalized ";
131 std::cout <<
"eigenvalue problem." << std::endl;
134 std::cout <<
" matrix size = " <<
A->rows() <<
" x " <<
A->cols() << std::endl;
143 eigVecs = RandomGenerator::GetUniform(dim,numEigs);
146 }
else if(
eigVecs.cols()>numEigs){
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);
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;
163 Bnorm =
B->Apply(gaussMat).norm() / gaussMatNorm;
169 Eigen::VectorXd blockVals;
170 Eigen::MatrixXd blockVecs;
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;
181 for(numComputed=0; numComputed<numEigs; numComputed+=blockSize){
184 constraints.block(0,numComputed+constMat.cols()-blockSize,dim,blockSize) =
eigVecs.block(0,numComputed-blockSize,dim,blockSize);
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);
194 std::tie(blockVals, blockVecs) = ComputeBlock(
A,
195 eigVecs.block(0,numComputed,dim,blockSize),
196 constraints.leftCols(numComputed+constMat.cols()),
200 eigVals.segment(numComputed, blockSize) = blockVals;
201 eigVecs.block(0,numComputed, dim, blockSize) = blockVecs;
206 eigVals.conservativeResize(numComputed);
215 for(numToKeep=1; numToKeep<numEigs; ++numToKeep){
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)
234 assert(X0.cols()==blockSize);
235 assert(X0.rows()==
A->rows());
236 assert(
A->rows()==
A->cols());
239 assert(
B->rows()==
A->rows());
240 assert(
B->rows()==
B->cols());
244 assert(
M->rows()==
A->rows());
245 assert(
M->rows()==
M->cols());
248 if(constMat.rows()>0){
249 assert(constMat.rows()==
A->rows());
252 Eigen::MatrixXd X = X0;
255 unsigned int numActive = blockSize;
258 std::vector<bool> isActive(blockSize,
true);
261 std::shared_ptr<Constraints> consts;
262 if(constMat.rows()>0){
263 consts = std::make_shared<Constraints>(
B, constMat);
264 consts->ApplyInPlace(X);
268 Orthonormalizer bOrtho(
B);
269 bOrtho.ComputeInPlace(X);
279 Eigen::MatrixXd AX =
A->Apply(X);
281 Eigen::MatrixXd XAX = X.transpose() * AX;
282 XAX = 0.5*(XAX+XAX.transpose()).eval();
284 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ritzSolver(XAX);
285 Eigen::VectorXd subEigVals = ritzSolver.eigenvalues();
286 Eigen::MatrixXd subEigVecs = ritzSolver.eigenvectors();
293 X = (X*subEigVecs).eval();
294 AX = (AX*subEigVecs).eval();
296 BX = (BX*subEigVecs).eval();
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());
307 Eigen::VectorXd convCrit, oldConvCrit;
308 convCrit = Eigen::VectorXd::Constant(X.cols(), std::numeric_limits<double>::infinity());
310 for(
unsigned it=0; it<maxIts; ++it){
315 consts->ApplyInPlace(X);
318 std::cout <<
"Iteration " << it << std::endl;
321 resids.leftCols(numActive) = AX.leftCols(numActive) - BX.leftCols(numActive)*subEigVals.head(numActive).asDiagonal();
324 residNorms = resids.colwise().norm();
327 oldConvCrit = convCrit;
328 convCrit = residNorms.array() / ((Anorm + Bnorm*subEigVals.array())*X.colwise().norm().transpose().array());
332 for(
unsigned int i=0; i<residNorms.size(); ++i){
335 if((convCrit(i)<solverTol)||(std::abs(convCrit(i)-oldConvCrit(i))<1
e-14)){
336 isActive.at(i) =
false;
338 numActive += int(isActive.at(i));
342 std::cout <<
" numActive = " << numActive << std::endl;
345 std::cout <<
" eigenvalues = " << subEigVals.transpose() << std::endl;
348 std::cout <<
" residNorms = " << residNorms.transpose() << std::endl;
349 std::cout <<
" convergence criteria = " << convCrit.transpose() << std::endl;
358 return std::make_pair(subEigVals, X);
381 resids.leftCols(numActive) =
M->Apply(resids.leftCols(numActive));
385 consts->ApplyInPlace(resids.leftCols(numActive));
387 resids.leftCols(numActive) -= (X * BX.transpose() * resids.leftCols(numActive)).eval();
390 bOrtho.ComputeInPlace(resids.leftCols(numActive));
396 BR = resids.leftCols(numActive);
399 AR.leftCols(numActive) =
A->Apply(resids.leftCols(numActive));
404 bool success = bOrtho.ComputeInPlace(P.leftCols(numActive),BP.leftCols(numActive));
409 AP.leftCols(numActive) = bOrtho.VBV_Chol.triangularView<Eigen::Lower>().solve(AP.leftCols(numActive).transpose()).transpose();
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();
425 Eigen::MatrixXd XBX, RBR, XBR;
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);
433 Eigen::MatrixXd gramA, gramB;
436 gramA.resize(blockSize+2*numActive,blockSize+2*numActive);
437 gramB.resize(blockSize+2*numActive,blockSize+2*numActive);
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);
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;
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);
468 gramA.resize(blockSize+numActive,blockSize+numActive);
469 gramB.resize(blockSize+numActive,blockSize+numActive);
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;
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);
482 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> ritzSolver(gramA, gramB);
483 assert(ritzSolver.info()==Eigen::Success);
486 subEigVals = ritzSolver.eigenvalues().tail(blockSize);
487 if(subEigVals.size()>1)
488 assert(subEigVals(0)<subEigVals(1));
490 subEigVecs = ritzSolver.eigenvectors().rightCols(blockSize);
496 Eigen::Ref<const Eigen::MatrixXd> eigX = subEigVecs.topRows(blockSize);
497 Eigen::Ref<const Eigen::MatrixXd> eigR = subEigVecs.block(blockSize,0,numActive,subEigVecs.cols());
499 Eigen::MatrixXd pp = resids.leftCols(numActive) * eigR;
500 Eigen::MatrixXd app = AR.leftCols(numActive)*eigR;
501 Eigen::MatrixXd bpp = BR.leftCols(numActive)*eigR;
504 Eigen::Ref<const Eigen::MatrixXd> eigP = subEigVecs.bottomRows(numActive);
506 pp += P.leftCols(numActive)*eigP;
507 app += AP.leftCols(numActive)*eigP;
508 bpp += BP.leftCols(numActive)*eigP;
511 X = (X*eigX + pp).eval();
512 AX = (AX*eigX + app).eval();
513 BX = (BX*eigX + bpp).eval();
521 std::cerr <<
"WARNING: LOBPCG reached maximum iterations." << std::endl;
528 return std::make_pair(subEigVals, X);
532 void LOBPCG::InitializeVectors(Eigen::MatrixXd
const& X0)
535 assert(X0.rows()==
eigVecs.rows());
541 LOBPCG& LOBPCG::reset(
int dim)
544 solverTol = dim * std::sqrt(std::numeric_limits<double>::epsilon());
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...
LOBPCG & compute(std::shared_ptr< LinearOperator > const &A, std::shared_ptr< LinearOperator > B=nullptr, std::shared_ptr< LinearOperator > M=nullptr)
LOBPCG(int numEigsIn, double eigRelTolIn=0.0, double eigAbsTolIn=0.0, int blockSizeIn=-1, double solverTolIn=-1, int maxItsIn=-1, int verbosityIn=0)
auto get(const nlohmann::detail::iteration_proxy_value< IteratorType > &i) -> decltype(i.key())