1 /*
2 * Copyright 2009-2020 The VOTCA Development Team (http://www.votca.org)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18 // Standard includes
19 #include <iostream>
20 #include <stdexcept>
21
22 // Local VOTCA includes
23 #include "votca/xtp/davidsonsolver.h"
24 #include "votca/xtp/eigen.h"
25
26 using boost::format;
27 using std::flush;
28
29 namespace votca {
30 namespace xtp {
31
DavidsonSolver(Logger & log)32 DavidsonSolver::DavidsonSolver(Logger &log) : log_(log) {}
33
printTiming(const std::chrono::time_point<std::chrono::system_clock> & start) const34 void DavidsonSolver::printTiming(
35 const std::chrono::time_point<std::chrono::system_clock> &start) const {
36 XTP_LOG(Log::error, log_)
37 << TimeStamp() << "-----------------------------------" << std::flush;
38 std::chrono::time_point<std::chrono::system_clock> end =
39 std::chrono::system_clock::now();
40 std::chrono::duration<double> elapsed_time = end - start;
41 XTP_LOG(Log::error, log_) << TimeStamp() << "- Davidson ran for "
42 << elapsed_time.count() << "secs." << std::flush;
43 XTP_LOG(Log::error, log_)
44 << TimeStamp() << "-----------------------------------" << std::flush;
45 }
46
checkOptions(Index operator_size)47 void DavidsonSolver::checkOptions(Index operator_size) {
48 //. search space exceeding the system size
49 if (max_search_space_ > operator_size) {
50 XTP_LOG(Log::error, log_)
51 << TimeStamp() << " == Warning : Max search space ("
52 << max_search_space_ << ") larger than system size (" << operator_size
53 << ")" << std::flush;
54
55 max_search_space_ = operator_size;
56 XTP_LOG(Log::error, log_)
57 << TimeStamp() << " == Warning : Max search space set to "
58 << operator_size << std::flush;
59
60 XTP_LOG(Log::error, log_)
61 << TimeStamp()
62 << " == Warning : If problems appear, try asking for less than "
63 << Index(operator_size / 10) << " eigenvalues" << std::flush;
64 }
65 }
66
printOptions(Index operator_size) const67 void DavidsonSolver::printOptions(Index operator_size) const {
68
69 XTP_LOG(Log::error, log_)
70 << TimeStamp() << " Davidson Solver using " << OPENMP::getMaxThreads()
71 << " threads." << std::flush;
72 XTP_LOG(Log::error, log_)
73 << TimeStamp() << " Tolerance : " << tol_ << std::flush;
74
75 switch (this->davidson_correction_) {
76 case CORR::DPR:
77 XTP_LOG(Log::error, log_)
78 << TimeStamp() << " DPR Correction" << std::flush;
79 break;
80 case CORR::OLSEN:
81 XTP_LOG(Log::error, log_)
82 << TimeStamp() << " Olsen Correction" << std::flush;
83 break;
84 }
85
86 XTP_LOG(Log::error, log_) << TimeStamp() << " Matrix size : " << operator_size
87 << 'x' << operator_size << std::flush;
88 }
89
printIterationData(const DavidsonSolver::RitzEigenPair & rep,const DavidsonSolver::ProjectedSpace & proj,Index neigen) const90 void DavidsonSolver::printIterationData(
91 const DavidsonSolver::RitzEigenPair &rep,
92 const DavidsonSolver::ProjectedSpace &proj, Index neigen) const {
93
94 Index converged_roots = proj.root_converged.head(neigen).count();
95 double percent_converged = 100 * double(converged_roots) / double(neigen);
96 XTP_LOG(Log::error, log_)
97 << TimeStamp()
98 << format(" %1$4d %2$12d \t %3$4.2e \t %4$5.2f%% converged") % i_iter_ %
99 proj.search_space() % rep.res_norm().head(neigen).maxCoeff() %
100 percent_converged
101 << std::flush;
102 }
103
set_matrix_type(std::string mt)104 void DavidsonSolver::set_matrix_type(std::string mt) {
105 if (mt == "HAM") {
106 this->matrix_type_ = MATRIX_TYPE::HAM;
107 } else if (mt == "SYMM") {
108 this->matrix_type_ = MATRIX_TYPE::SYMM;
109 } else {
110 throw std::runtime_error(mt + " is not a valid Davidson matrix type");
111 }
112 }
113
set_correction(std::string method)114 void DavidsonSolver::set_correction(std::string method) {
115 if (method == "DPR") {
116 this->davidson_correction_ = CORR::DPR;
117 } else if (method == "OLSEN") {
118 this->davidson_correction_ = CORR::OLSEN;
119 } else {
120 throw std::runtime_error(method +
121 " is not a valid Davidson correction method");
122 }
123 }
124
set_tolerance(std::string tol)125 void DavidsonSolver::set_tolerance(std::string tol) {
126 if (tol == "loose") {
127 this->tol_ = 1E-3;
128 } else if (tol == "normal") {
129 this->tol_ = 1E-4;
130 } else if (tol == "strict") {
131 this->tol_ = 1E-5;
132 } else if (tol == "lapack") {
133 this->tol_ = 1E-9;
134 } else {
135 throw std::runtime_error(tol + " is not a valid Davidson tolerance");
136 }
137 }
138
set_size_update(std::string update_size)139 void DavidsonSolver::set_size_update(std::string update_size) {
140
141 if (update_size == "min") {
142 this->davidson_update_ = UPDATE::MIN;
143 } else if (update_size == "safe") {
144 this->davidson_update_ = UPDATE::SAFE;
145 } else if (update_size == "max") {
146 this->davidson_update_ = UPDATE::MAX;
147 } else {
148 throw std::runtime_error(update_size + " is not a valid Davidson update");
149 }
150 }
151
getSizeUpdate(Index neigen) const152 Index DavidsonSolver::getSizeUpdate(Index neigen) const {
153 Index size_update;
154 switch (this->davidson_update_) {
155 case UPDATE::MIN:
156 size_update = neigen;
157 break;
158 case UPDATE::SAFE:
159 if (neigen < 20) {
160 size_update = static_cast<Index>(1.5 * double(neigen));
161 } else {
162 size_update = neigen + 10;
163 }
164 break;
165 case UPDATE::MAX:
166 size_update = 2 * neigen;
167 break;
168 default:
169 size_update = 2 * neigen;
170 break;
171 }
172 return size_update;
173 }
174
argsort(const Eigen::VectorXd & V) const175 ArrayXl DavidsonSolver::argsort(const Eigen::VectorXd &V) const {
176 /* \brief return the index of the sorted vector */
177 ArrayXl idx = ArrayXl::LinSpaced(V.rows(), 0, V.rows() - 1);
178 std::sort(idx.data(), idx.data() + idx.size(),
179 [&](Index i1, Index i2) { return V[i1] < V[i2]; });
180 return idx;
181 }
182
setupInitialEigenvectors(Index size_initial_guess) const183 Eigen::MatrixXd DavidsonSolver::setupInitialEigenvectors(
184 Index size_initial_guess) const {
185
186 Eigen::MatrixXd guess =
187 Eigen::MatrixXd::Zero(Adiag_.size(), size_initial_guess);
188 ArrayXl idx = DavidsonSolver::argsort(Adiag_);
189
190 switch (this->matrix_type_) {
191 case MATRIX_TYPE::SYMM:
192 /* \brief Initialize the guess eigenvector so that they 'target' the
193 * smallest diagonal elements */
194 for (Index j = 0; j < size_initial_guess; j++) {
195 guess(idx(j), j) = 1.0;
196 }
197 break;
198
199 case MATRIX_TYPE::HAM:
200 /* Initialize the guess eigenvector so that they 'target' the lowest
201 * positive diagonal elements */
202 Index ind0 = Adiag_.size() / 2;
203 for (Index j = 0; j < size_initial_guess; j++) {
204 guess(idx(ind0 + j), j) = 1.0;
205 }
206 break;
207 }
208 return guess;
209 }
getRitzEigenPairs(const ProjectedSpace & proj) const210 DavidsonSolver::RitzEigenPair DavidsonSolver::getRitzEigenPairs(
211 const ProjectedSpace &proj) const {
212 // get the ritz vectors
213 switch (this->matrix_type_) {
214 case MATRIX_TYPE::SYMM: {
215 return getRitz(proj);
216 }
217 case MATRIX_TYPE::HAM: {
218 return getHarmonicRitz(proj);
219 }
220 }
221 return RitzEigenPair();
222 }
223
getRitz(const DavidsonSolver::ProjectedSpace & proj) const224 DavidsonSolver::RitzEigenPair DavidsonSolver::getRitz(
225 const DavidsonSolver::ProjectedSpace &proj) const {
226
227 DavidsonSolver::RitzEigenPair rep;
228 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(proj.T);
229 if (es.info() != Eigen::ComputationInfo::Success) {
230 std::cerr << "A\n" << proj.T << std::endl;
231 throw std::runtime_error("Small hermitian eigenvalue problem failed.");
232 }
233 // we only need enough pairs for either extension of space or restart
234 Index needed_pairs =
235 std::min(proj.T.cols(), std::max(restart_size_, proj.size_update));
236 rep.lambda = es.eigenvalues().head(needed_pairs);
237 rep.U = es.eigenvectors().leftCols(needed_pairs);
238
239 rep.q = proj.V * rep.U; // Ritz vectors
240 rep.res = proj.AV * rep.U - rep.q * rep.lambda.asDiagonal(); // residues
241 return rep;
242 }
243
getHarmonicRitz(const ProjectedSpace & proj) const244 DavidsonSolver::RitzEigenPair DavidsonSolver::getHarmonicRitz(
245 const ProjectedSpace &proj) const {
246
247 /* Compute the Harmonic Ritz vector following
248 * Computing Interior Eigenvalues of Large Matrices
249 * Ronald B Morgan
250 * LINEAR ALGEBRA AND ITS APPLICATIONS 154-156:289-309 (1991)
251 * https://cpb-us-w2.wpmucdn.com/sites.baylor.edu/dist/e/71/files/2015/05/InterEvals-1vgdz91.pdf
252 */
253
254 RitzEigenPair rep;
255 bool return_eigenvectors = true;
256 Eigen::GeneralizedEigenSolver<Eigen::MatrixXd> ges(proj.T, proj.B,
257 return_eigenvectors);
258 if (ges.info() != Eigen::ComputationInfo::Success) {
259 std::cerr << "A\n" << proj.T << std::endl;
260 std::cerr << "B\n" << proj.B << std::endl;
261 throw std::runtime_error("Small generalized eigenvalue problem failed.");
262 }
263
264 std::vector<std::pair<Index, Index>> complex_pairs;
265 for (Index i = 0; i < ges.eigenvalues().size(); i++) {
266 if (ges.eigenvalues()(i).imag() != 0) {
267 bool found_partner = false;
268 for (auto &pair : complex_pairs) {
269 if (pair.second > -1) {
270 continue;
271 } else {
272 bool are_pair = (std::abs(ges.eigenvalues()(pair.first).real() -
273 ges.eigenvalues()(i).real()) < 1e-9) &&
274 (std::abs(ges.eigenvalues()(pair.first).imag() +
275 ges.eigenvalues()(i).imag()) < 1e-9);
276 if (are_pair) {
277 pair.second = i;
278 found_partner = true;
279 }
280 }
281 }
282
283 if (!found_partner) {
284 complex_pairs.emplace_back(i, -1);
285 }
286 }
287 }
288
289 for (const auto &pair : complex_pairs) {
290 if (pair.second < 0) {
291 throw std::runtime_error("Eigenvalue:" + std::to_string(pair.first) +
292 " is complex but has no partner.");
293 }
294 }
295 if (!complex_pairs.empty()) {
296 XTP_LOG(Log::warning, log_)
297 << TimeStamp() << " Found " << complex_pairs.size()
298 << " complex pairs in eigenvalue problem" << std::flush;
299 }
300 Eigen::VectorXd eigenvalues =
301 Eigen::VectorXd::Zero(ges.eigenvalues().size() - complex_pairs.size());
302 Eigen::MatrixXd eigenvectors =
303 Eigen::MatrixXd::Zero(ges.eigenvectors().rows(),
304 ges.eigenvectors().cols() - complex_pairs.size());
305
306 Index j = 0;
307 for (Index i = 0; i < ges.eigenvalues().size(); i++) {
308 bool is_second_in_complex_pair =
309 std::find_if(complex_pairs.begin(), complex_pairs.end(),
310 [&](const std::pair<Index, Index> &pair) {
311 return pair.second == i;
312 }) != complex_pairs.end();
313 if (is_second_in_complex_pair) {
314 continue;
315 } else {
316 eigenvalues(j) = ges.eigenvalues()(i).real();
317 eigenvectors.col(j) = ges.eigenvectors().col(i).real();
318 eigenvectors.col(j).normalize();
319 j++;
320 }
321 }
322 // we only need enough pairs for either extension of space or restart
323 Index needed_pairs =
324 std::min(proj.T.cols(), std::max(restart_size_, proj.size_update));
325 ArrayXl idx =
326 DavidsonSolver::argsort(eigenvalues).reverse().head(needed_pairs);
327 // we need the largest values, because this is the inverse value, so
328 // reverse list
329
330 rep.U = DavidsonSolver::extract_vectors(eigenvectors, idx);
331 rep.lambda = (rep.U.transpose() * proj.T * rep.U).diagonal();
332 rep.q = proj.V * rep.U; // Ritz vectors
333 rep.res = proj.AV * rep.U - rep.q * rep.lambda.asDiagonal(); // residues
334 return rep;
335 }
336
initProjectedSpace(Index neigen,Index size_initial_guess) const337 DavidsonSolver::ProjectedSpace DavidsonSolver::initProjectedSpace(
338 Index neigen, Index size_initial_guess) const {
339 DavidsonSolver::ProjectedSpace proj;
340
341 // initial vector basis
342 proj.V = DavidsonSolver::setupInitialEigenvectors(size_initial_guess);
343
344 // update variables
345 proj.size_update = DavidsonSolver::getSizeUpdate(neigen);
346 proj.root_converged = ArrayXb::Constant(proj.size_update, false);
347 return proj;
348 }
349
checkConvergence(const DavidsonSolver::RitzEigenPair & rep,DavidsonSolver::ProjectedSpace & proj,Index neigen) const350 bool DavidsonSolver::checkConvergence(const DavidsonSolver::RitzEigenPair &rep,
351 DavidsonSolver::ProjectedSpace &proj,
352 Index neigen) const {
353 proj.root_converged = (rep.res_norm().head(proj.size_update) < tol_);
354 return proj.root_converged.head(neigen).all();
355 }
356
extendProjection(const DavidsonSolver::RitzEigenPair & rep,DavidsonSolver::ProjectedSpace & proj) const357 Index DavidsonSolver::extendProjection(
358 const DavidsonSolver::RitzEigenPair &rep,
359 DavidsonSolver::ProjectedSpace &proj) const {
360
361 Index nupdate = (proj.root_converged == false).count();
362 Index oldsize = proj.V.cols();
363 proj.V.conservativeResize(Eigen::NoChange, oldsize + nupdate);
364
365 Index k = 0;
366 for (Index j = 0; j < proj.size_update; j++) {
367 // skip the roots that have already converged
368 if (proj.root_converged[j]) {
369 continue;
370 }
371 // residue vector
372 Eigen::VectorXd w =
373 computeCorrectionVector(rep.q.col(j), rep.lambda(j), rep.res.col(j));
374
375 // append the correction vector to the search space
376 proj.V.col(oldsize + k) = w.normalized();
377 k++;
378 }
379 orthogonalize(proj.V, nupdate);
380 return nupdate;
381 }
382
extract_vectors(const Eigen::MatrixXd & V,const ArrayXl & idx) const383 Eigen::MatrixXd DavidsonSolver::extract_vectors(const Eigen::MatrixXd &V,
384 const ArrayXl &idx) const {
385 Eigen::MatrixXd W = Eigen::MatrixXd::Zero(V.rows(), idx.size());
386 for (Index i = 0; i < idx.size(); i++) {
387 W.col(i) = V.col(idx(i));
388 }
389 return W;
390 }
391
computeCorrectionVector(const Eigen::VectorXd & qj,double lambdaj,const Eigen::VectorXd & Aqj) const392 Eigen::VectorXd DavidsonSolver::computeCorrectionVector(
393 const Eigen::VectorXd &qj, double lambdaj,
394 const Eigen::VectorXd &Aqj) const {
395
396 /* compute correction vector with either DPR or OLSEN CORRECTION
397 * For details on the method see :
398 * Systematic Study of Selected Diagonalization Methods
399 * for Configuration Interaction Matrices
400 * M.L. Leininger et al .
401 * Journal of Computational Chemistry Vol 22, No. 13 1574-1589 (2001)
402 */
403 Eigen::VectorXd correction;
404 switch (this->davidson_correction_) {
405 case CORR::DPR: {
406 correction = dpr(Aqj, lambdaj);
407 break;
408 }
409 case CORR::OLSEN: {
410 correction = olsen(Aqj, qj, lambdaj);
411 break;
412 }
413 }
414 // make sure no nan values are there, instead we set them to zero
415 return correction.unaryExpr(
416 [](double v) { return std::isfinite(v) ? v : 0.0; });
417 }
418
dpr(const Eigen::VectorXd & r,double lambda) const419 Eigen::VectorXd DavidsonSolver::dpr(const Eigen::VectorXd &r,
420 double lambda) const {
421 /* \brief Compute the diagonal preconditoned residue :
422 \delta = -r/(D - lambda)
423 */
424 return (-r.array() / (Adiag_.array() - lambda));
425 }
426
olsen(const Eigen::VectorXd & r,const Eigen::VectorXd & x,double lambda) const427 Eigen::VectorXd DavidsonSolver::olsen(const Eigen::VectorXd &r,
428 const Eigen::VectorXd &x,
429 double lambda) const {
430 /* \brief Compute the olsen correction :
431 \delta = (D-\lambda)^{-1} (-r + \epsilon x)
432 */
433 Eigen::VectorXd delta = DavidsonSolver::dpr(r, lambda);
434 double num = -x.transpose() * delta;
435 double denom = -x.transpose() * dpr(x, lambda);
436 double eps = num / denom;
437 delta += eps * x;
438 return delta;
439 }
440
orthogonalize(Eigen::MatrixXd & V,Index nupdate) const441 void DavidsonSolver::orthogonalize(Eigen::MatrixXd &V, Index nupdate) const {
442 DavidsonSolver::gramschmidt(V, V.cols() - nupdate);
443 }
444
gramschmidt(Eigen::MatrixXd & Q,Index nstart) const445 void DavidsonSolver::gramschmidt(Eigen::MatrixXd &Q, Index nstart) const {
446 Index nupdate = Q.cols() - nstart;
447 Eigen::VectorXd norms = Q.rightCols(nupdate).colwise().norm();
448 // orthogonalize with respect to already existing vectors
449 if (nstart > 0) {
450 Q.rightCols(nupdate) -=
451 Q.leftCols(nstart) *
452 (Q.leftCols(nstart).transpose() * Q.rightCols(nupdate));
453 Q.rightCols(nupdate).colwise().normalize();
454 }
455 // orthogonalize vectors to each other
456 for (Index j = nstart + 1; j < Q.cols(); ++j) {
457 Index range = j - nstart;
458 Q.col(j) -= Q.middleCols(nstart, range) *
459 (Q.middleCols(nstart, range).transpose() * Q.col(j));
460 Q.col(j).normalize();
461 }
462 // repeat again two is enough GS
463 // http://stoppels.blog/posts/orthogonalization-performance
464 if (nstart > 0) {
465 Q.rightCols(nupdate) -=
466 Q.leftCols(nstart) *
467 (Q.leftCols(nstart).transpose() * Q.rightCols(nupdate));
468 Q.rightCols(nupdate).colwise().normalize();
469 }
470
471 for (Index j = nstart + 1; j < Q.cols(); ++j) {
472 Index range = j - nstart;
473 Q.col(j) -= Q.middleCols(nstart, range) *
474 (Q.middleCols(nstart, range).transpose() * Q.col(j));
475 if (Q.col(j).norm() <= 1E-12 * norms(range)) {
476 // info_ = Eigen::ComputationInfo::NumericalIssue;
477 throw std::runtime_error("Linear dependencies in Gram-Schmidt.");
478 }
479 Q.col(j).normalize();
480 }
481 }
482
qr(const Eigen::MatrixXd & A) const483 Eigen::MatrixXd DavidsonSolver::qr(const Eigen::MatrixXd &A) const {
484
485 Index nrows = A.rows();
486 Index ncols = A.cols();
487 ncols = std::min(nrows, ncols);
488 Eigen::MatrixXd I = Eigen::MatrixXd::Identity(nrows, ncols);
489 Eigen::HouseholderQR<Eigen::MatrixXd> qr(A);
490 return qr.householderQ() * I;
491 }
492
restart(const DavidsonSolver::RitzEigenPair & rep,DavidsonSolver::ProjectedSpace & proj,Index newvectors) const493 void DavidsonSolver::restart(const DavidsonSolver::RitzEigenPair &rep,
494 DavidsonSolver::ProjectedSpace &proj,
495 Index newvectors) const {
496 Eigen::MatrixXd newV =
497 Eigen::MatrixXd(proj.V.rows(), newvectors + restart_size_);
498 newV.rightCols(newvectors) = proj.V.rightCols(newvectors);
499 if (matrix_type_ == MATRIX_TYPE::SYMM) {
500
501 newV.leftCols(restart_size_) = rep.q.leftCols(restart_size_);
502 proj.AV *= rep.U.leftCols(restart_size_); // corresponds to replacing
503 // V with q.leftCols
504 } else {
505 Eigen::MatrixXd orthonormal =
506 DavidsonSolver::qr(rep.U.leftCols(restart_size_));
507 newV.leftCols(restart_size_) =
508 proj.V.leftCols(proj.V.cols() - newvectors) * orthonormal;
509 proj.AV *= orthonormal;
510
511 proj.AAV *= orthonormal;
512 proj.B = newV.leftCols(restart_size_).transpose() * proj.AAV;
513 }
514 proj.T = newV.leftCols(restart_size_).transpose() * proj.AV;
515 proj.V = newV;
516 }
517
storeConvergedData(const DavidsonSolver::RitzEigenPair & rep,Index neigen)518 void DavidsonSolver::storeConvergedData(
519 const DavidsonSolver::RitzEigenPair &rep, Index neigen) {
520
521 DavidsonSolver::storeEigenPairs(rep, neigen);
522 XTP_LOG(Log::error, log_) << TimeStamp() << " Davidson converged after "
523 << i_iter_ << " iterations." << std::flush;
524 info_ = Eigen::ComputationInfo::Success;
525 }
526
storeNotConvergedData(const DavidsonSolver::RitzEigenPair & rep,const ArrayXb & root_converged,Index neigen)527 void DavidsonSolver::storeNotConvergedData(
528 const DavidsonSolver::RitzEigenPair &rep, const ArrayXb &root_converged,
529 Index neigen) {
530
531 DavidsonSolver::storeEigenPairs(rep, neigen);
532
533 double percent_converged = 0;
534
535 for (Index i = 0; i < neigen; i++) {
536 if (!root_converged[i]) {
537 eigenvalues_(i) = 0;
538 eigenvectors_.col(i).setZero();
539 } else {
540 percent_converged += 1.;
541 }
542 }
543 percent_converged /= double(neigen);
544 percent_converged *= 100.;
545 XTP_LOG(Log::error, log_)
546 << TimeStamp() << "- Warning : Davidson "
547 << format("%1$5.2f%%") % percent_converged << " converged after "
548 << i_iter_ << " iterations." << std::flush;
549 info_ = Eigen::ComputationInfo::NoConvergence;
550 }
551
storeEigenPairs(const DavidsonSolver::RitzEigenPair & rep,Index neigen)552 void DavidsonSolver::storeEigenPairs(const DavidsonSolver::RitzEigenPair &rep,
553 Index neigen) {
554 // store the eigenvalues/eigenvectors
555 this->eigenvalues_ = rep.lambda.head(neigen);
556 this->eigenvectors_ = rep.q.leftCols(neigen);
557 this->eigenvectors_.colwise().normalize();
558 }
559
560 } // namespace xtp
561 } // namespace votca
562