votca 2026-dev
Loading...
Searching...
No Matches
davidsonsolver.cc
Go to the documentation of this file.
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
24#include "votca/xtp/eigen.h"
25
26namespace votca {
27namespace xtp {
28
30
32 const std::chrono::time_point<std::chrono::system_clock> &start) const {
34 << TimeStamp() << "-----------------------------------" << std::flush;
35 std::chrono::time_point<std::chrono::system_clock> end =
36 std::chrono::system_clock::now();
37 std::chrono::duration<double> elapsed_time = end - start;
38 XTP_LOG(Log::error, log_) << TimeStamp() << "- Davidson ran for "
39 << elapsed_time.count() << "secs." << std::flush;
41 << TimeStamp() << "-----------------------------------" << std::flush;
42}
43
45 //. search space exceeding the system size
46 if (max_search_space_ > operator_size) {
48 << TimeStamp() << " == Warning : Max search space ("
49 << max_search_space_ << ") larger than system size (" << operator_size
50 << ")" << std::flush;
51
52 max_search_space_ = operator_size;
54 << TimeStamp() << " == Warning : Max search space set to "
55 << operator_size << std::flush;
56
58 << TimeStamp()
59 << " == Warning : If problems appear, try asking for less than "
60 << Index(operator_size / 10) << " eigenvalues" << std::flush;
61 }
62}
63
64void DavidsonSolver::printOptions(Index operator_size) const {
65
67 << TimeStamp() << " Davidson Solver using " << OPENMP::getMaxThreads()
68 << " threads." << std::flush;
70 << TimeStamp() << " Tolerance : " << tol_ << std::flush;
71
72 switch (this->davidson_correction_) {
73 case CORR::DPR:
75 << TimeStamp() << " DPR Correction" << std::flush;
76 break;
77 case CORR::OLSEN:
79 << TimeStamp() << " Olsen Correction" << std::flush;
80 break;
81 }
82
83 XTP_LOG(Log::error, log_) << TimeStamp() << " Matrix size : " << operator_size
84 << 'x' << operator_size << std::flush;
85}
86
89 const DavidsonSolver::ProjectedSpace &proj, Index neigen) const {
90
91 Index converged_roots = proj.root_converged.head(neigen).count();
92 double percent_converged = 100 * double(converged_roots) / double(neigen);
94 << TimeStamp()
95 << boost::format(" %1$4d %2$12d \t %3$4.2e \t %4$5.2f%% converged") %
96 i_iter_ % proj.search_space() %
97 rep.res_norm().head(neigen).maxCoeff() % percent_converged
98 << std::flush;
99}
100
102 if (mt == "HAM") {
104 } else if (mt == "SYMM") {
106 } else {
107 throw std::runtime_error(mt + " is not a valid Davidson matrix type");
108 }
109}
110
111void DavidsonSolver::set_correction(std::string method) {
112 if (method == "DPR") {
114 } else if (method == "OLSEN") {
116 } else {
117 throw std::runtime_error(method +
118 " is not a valid Davidson correction method");
119 }
120}
121
122void DavidsonSolver::set_tolerance(std::string tol) {
123 if (tol == "loose") {
124 this->tol_ = 1E-3;
125 } else if (tol == "normal") {
126 this->tol_ = 1E-4;
127 } else if (tol == "strict") {
128 this->tol_ = 1E-5;
129 } else if (tol == "lapack") {
130 this->tol_ = 1E-9;
131 } else {
132 throw std::runtime_error(tol + " is not a valid Davidson tolerance");
133 }
134}
135
136void DavidsonSolver::set_size_update(std::string update_size) {
137
138 if (update_size == "min") {
140 } else if (update_size == "safe") {
142 } else if (update_size == "max") {
144 } else {
145 throw std::runtime_error(update_size + " is not a valid Davidson update");
146 }
147}
148
150 Index size_update;
151 switch (this->davidson_update_) {
152 case UPDATE::MIN:
153 size_update = neigen;
154 break;
155 case UPDATE::SAFE:
156 if (neigen < 20) {
157 size_update = static_cast<Index>(1.5 * double(neigen));
158 } else {
159 size_update = neigen + 10;
160 }
161 break;
162 case UPDATE::MAX:
163 size_update = 2 * neigen;
164 break;
165 default:
166 size_update = 2 * neigen;
167 break;
168 }
169 return size_update;
170}
171
172ArrayXl DavidsonSolver::argsort(const Eigen::VectorXd &V) const {
173 /* \brief return the index of the sorted vector */
174 ArrayXl idx = ArrayXl::LinSpaced(V.rows(), 0, V.rows() - 1);
175 std::sort(idx.data(), idx.data() + idx.size(),
176 [&](Index i1, Index i2) { return V[i1] < V[i2]; });
177 return idx;
178}
179
181 Index size_initial_guess) const {
182
183 Eigen::MatrixXd guess =
184 Eigen::MatrixXd::Zero(Adiag_.size(), size_initial_guess);
186
187 switch (this->matrix_type_) {
189 /* \brief Initialize the guess eigenvector so that they 'target' the
190 * smallest diagonal elements */
191 for (Index j = 0; j < size_initial_guess; j++) {
192 guess(idx(j), j) = 1.0;
193 }
194 break;
195
196 case MATRIX_TYPE::HAM:
197 /* Initialize the guess eigenvector so that they 'target' the lowest
198 * positive diagonal elements */
199 Index ind0 = Adiag_.size() / 2;
200 for (Index j = 0; j < size_initial_guess; j++) {
201 guess(idx(ind0 + j), j) = 1.0;
202 }
203 break;
204 }
205 return guess;
206}
208 const ProjectedSpace &proj) const {
209 // get the ritz vectors
210 switch (this->matrix_type_) {
211 case MATRIX_TYPE::SYMM: {
212 return getRitz(proj);
213 }
214 case MATRIX_TYPE::HAM: {
215 return getHarmonicRitz(proj);
216 }
217 }
218 return RitzEigenPair();
219}
220
222 const DavidsonSolver::ProjectedSpace &proj) const {
223
225 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(proj.T);
226 if (es.info() != Eigen::ComputationInfo::Success) {
227 std::cerr << "A\n" << proj.T << std::endl;
228 throw std::runtime_error("Small hermitian eigenvalue problem failed.");
229 }
230 // we only need enough pairs for either extension of space or restart
231 Index needed_pairs =
232 std::min(proj.T.cols(), std::max(restart_size_, proj.size_update));
233 rep.lambda = es.eigenvalues().head(needed_pairs);
234 rep.U = es.eigenvectors().leftCols(needed_pairs);
235
236 rep.q = proj.V * rep.U; // Ritz vectors
237 rep.res = proj.AV * rep.U - rep.q * rep.lambda.asDiagonal(); // residues
238 return rep;
239}
240
242 const ProjectedSpace &proj) const {
243
244 /* Compute the Harmonic Ritz vector following
245 * Computing Interior Eigenvalues of Large Matrices
246 * Ronald B Morgan
247 * LINEAR ALGEBRA AND ITS APPLICATIONS 154-156:289-309 (1991)
248 * https://cpb-us-w2.wpmucdn.com/sites.baylor.edu/dist/e/71/files/2015/05/InterEvals-1vgdz91.pdf
249 */
250
251 RitzEigenPair rep;
252 bool return_eigenvectors = true;
253 Eigen::GeneralizedEigenSolver<Eigen::MatrixXd> ges(proj.T, proj.B,
254 return_eigenvectors);
255 if (ges.info() != Eigen::ComputationInfo::Success) {
256 std::cerr << "A\n" << proj.T << std::endl;
257 std::cerr << "B\n" << proj.B << std::endl;
258 throw std::runtime_error("Small generalized eigenvalue problem failed.");
259 }
260
261 std::vector<std::pair<Index, Index>> complex_pairs;
262 for (Index i = 0; i < ges.eigenvalues().size(); i++) {
263 if (ges.eigenvalues()(i).imag() != 0) {
264 bool found_partner = false;
265 for (auto &pair : complex_pairs) {
266 if (pair.second > -1) {
267 continue;
268 } else {
269 bool are_pair = (std::abs(ges.eigenvalues()(pair.first).real() -
270 ges.eigenvalues()(i).real()) < 1e-9) &&
271 (std::abs(ges.eigenvalues()(pair.first).imag() +
272 ges.eigenvalues()(i).imag()) < 1e-9);
273 if (are_pair) {
274 pair.second = i;
275 found_partner = true;
276 }
277 }
278 }
279
280 if (!found_partner) {
281 complex_pairs.emplace_back(i, -1);
282 }
283 }
284 }
285
286 for (const auto &pair : complex_pairs) {
287 if (pair.second < 0) {
288 throw std::runtime_error("Eigenvalue:" + std::to_string(pair.first) +
289 " is complex but has no partner.");
290 }
291 }
292 if (!complex_pairs.empty()) {
294 << TimeStamp() << " Found " << complex_pairs.size()
295 << " complex pairs in eigenvalue problem" << std::flush;
296 }
297 Eigen::VectorXd eigenvalues =
298 Eigen::VectorXd::Zero(ges.eigenvalues().size() - complex_pairs.size());
299 Eigen::MatrixXd eigenvectors =
300 Eigen::MatrixXd::Zero(ges.eigenvectors().rows(),
301 ges.eigenvectors().cols() - complex_pairs.size());
302
303 Index j = 0;
304 for (Index i = 0; i < ges.eigenvalues().size(); i++) {
305 bool is_second_in_complex_pair =
306 std::find_if(complex_pairs.begin(), complex_pairs.end(),
307 [&](const std::pair<Index, Index> &pair) {
308 return pair.second == i;
309 }) != complex_pairs.end();
310 if (is_second_in_complex_pair) {
311 continue;
312 } else {
313 eigenvalues(j) = ges.eigenvalues()(i).real();
314 eigenvectors.col(j) = ges.eigenvectors().col(i).real();
315 eigenvectors.col(j).normalize();
316 j++;
317 }
318 }
319 // we only need enough pairs for either extension of space or restart
320 Index needed_pairs =
321 std::min(proj.T.cols(), std::max(restart_size_, proj.size_update));
322 // Dropping one member of each complex pair can leave fewer candidates than
323 // needed (e.g. right after a restart, when the space has restart_size_
324 // columns). Then add the imaginary parts of the pairs' eigenvectors: with
325 // the real parts they span the real invariant subspaces of the pairs.
326 if (j < needed_pairs) {
327 const Index missing =
328 std::min(needed_pairs - j, Index(complex_pairs.size()));
329 eigenvalues.conservativeResize(j + missing);
330 eigenvectors.conservativeResize(Eigen::NoChange, j + missing);
331 for (Index p = 0; p < missing; ++p) {
332 const Index i = complex_pairs[p].first;
333 eigenvalues(j) = ges.eigenvalues()(i).real();
334 eigenvectors.col(j) = ges.eigenvectors().col(i).imag();
335 eigenvectors.col(j).normalize();
336 j++;
337 }
338 }
339 ArrayXl idx =
340 DavidsonSolver::argsort(eigenvalues).reverse().head(needed_pairs);
341 // we need the largest values, because this is the inverse value, so
342 // reverse list
343
345 rep.lambda = (rep.U.transpose() * proj.T * rep.U).diagonal();
346 rep.q = proj.V * rep.U; // Ritz vectors
347 rep.res = proj.AV * rep.U - rep.q * rep.lambda.asDiagonal(); // residues
348 return rep;
349}
350
352 Index neigen, Index size_initial_guess) const {
354
355 // initial vector basis
356 proj.V = DavidsonSolver::setupInitialEigenvectors(size_initial_guess);
357
358 // update variables
360 proj.root_converged = ArrayXb::Constant(proj.size_update, false);
361 return proj;
362}
363
366 Index neigen) const {
367 // there can be fewer Ritz pairs than size_update while the search space is
368 // still small (an initial guess with dependent columns dropped); roots
369 // without a Ritz pair are not converged
370 const Index n = std::min<Index>(proj.size_update, rep.res_norm().size());
371 proj.root_converged = ArrayXb::Constant(proj.size_update, false);
372 proj.root_converged.head(n) = (rep.res_norm().head(n) < tol_);
373 return proj.root_converged.head(neigen).all();
374}
375
378 DavidsonSolver::ProjectedSpace &proj) const {
379
380 // correction vectors only for roots with a Ritz pair (see
381 // checkConvergence)
382 const Index n = std::min<Index>(proj.size_update, rep.lambda.size());
383 Index nupdate = (proj.root_converged.head(n) == false).count();
384 Index oldsize = proj.V.cols();
385 proj.V.conservativeResize(Eigen::NoChange, oldsize + nupdate);
386
387 Index k = 0;
388 for (Index j = 0; j < n; j++) {
389 // skip the roots that have already converged
390 if (proj.root_converged[j]) {
391 continue;
392 }
393 // residue vector
394 Eigen::VectorXd w =
395 computeCorrectionVector(rep.q.col(j), rep.lambda(j), rep.res.col(j));
396
397 // append the correction vector to the search space
398 proj.V.col(oldsize + k) = w.normalized();
399 k++;
400 }
401 const Index added = orthogonalize(proj.V, nupdate);
402 if (added == 0) {
403 throw std::runtime_error(
404 "Davidson: no new search direction, all corrections are linearly "
405 "dependent on the search space");
406 }
407 return added;
408}
409
410Eigen::MatrixXd DavidsonSolver::extract_vectors(const Eigen::MatrixXd &V,
411 const ArrayXl &idx) const {
412 Eigen::MatrixXd W = Eigen::MatrixXd::Zero(V.rows(), idx.size());
413 for (Index i = 0; i < idx.size(); i++) {
414 W.col(i) = V.col(idx(i));
415 }
416 return W;
417}
418
420 const Eigen::VectorXd &qj, double lambdaj,
421 const Eigen::VectorXd &Aqj) const {
422
423 /* compute correction vector with either DPR or OLSEN CORRECTION
424 * For details on the method see :
425 * Systematic Study of Selected Diagonalization Methods
426 * for Configuration Interaction Matrices
427 * M.L. Leininger et al .
428 * Journal of Computational Chemistry Vol 22, No. 13 1574-1589 (2001)
429 */
430 Eigen::VectorXd correction;
431 switch (this->davidson_correction_) {
432 case CORR::DPR: {
433 correction = dpr(Aqj, lambdaj);
434 break;
435 }
436 case CORR::OLSEN: {
437 correction = olsen(Aqj, qj, lambdaj);
438 break;
439 }
440 }
441 // make sure no nan values are there, instead we set them to zero
442 return correction.unaryExpr(
443 [](double v) { return std::isfinite(v) ? v : 0.0; });
444}
445
446Eigen::VectorXd DavidsonSolver::dpr(const Eigen::VectorXd &r,
447 double lambda) const {
448 /* \brief Compute the diagonal preconditoned residue :
449 \delta = -r/(D - lambda)
450 with |D - lambda| floored, so that a diagonal element at the Ritz value
451 gives a large but finite component instead of inf/nan
452 */
453 constexpr double floor = 1e-8;
454 return -r.array() / (Adiag_.array() - lambda).unaryExpr([](double d) {
455 return (std::abs(d) < floor) ? (d < 0 ? -floor : floor) : d;
456 });
457}
458
459Eigen::VectorXd DavidsonSolver::olsen(const Eigen::VectorXd &r,
460 const Eigen::VectorXd &x,
461 double lambda) const {
462 /* \brief Compute the olsen correction :
463 \delta = (D-\lambda)^{-1} (-r + \epsilon x)
464 */
465 Eigen::VectorXd delta = DavidsonSolver::dpr(r, lambda);
466 double num = -x.transpose() * delta;
467 double denom = -x.transpose() * dpr(x, lambda);
468 if (!(std::abs(denom) > 1e-14 * std::abs(num)) || !std::isfinite(denom)) {
469 return delta; // no usable Olsen term: DPR
470 }
471 double eps = num / denom;
472 delta += eps * x;
473 return delta;
474}
475
476Index DavidsonSolver::orthogonalize(Eigen::MatrixXd &V, Index nupdate) const {
477 return DavidsonSolver::gramschmidt(V, V.cols() - nupdate);
478}
479
480// Orthonormalises the columns from nstart on against the first nstart
481// (assumed orthonormal) and against each other, classical Gram-Schmidt twice.
482// Columns that are linearly dependent (less than 1e-10 of their norm left)
483// are dropped; returns the number of columns kept from nstart on.
484Index DavidsonSolver::gramschmidt(Eigen::MatrixXd &Q, Index nstart) const {
485 const Index nupdate = Q.cols() - nstart;
486 if (nupdate <= 0) {
487 return 0;
488 }
489 const Eigen::VectorXd norms = Q.rightCols(nupdate).colwise().norm();
490 auto project_out_old = [&](Index first, Index n) {
491 if (nstart > 0 && n > 0) {
492 Q.middleCols(first, n) -=
493 Q.leftCols(nstart) *
494 (Q.leftCols(nstart).transpose() * Q.middleCols(first, n));
495 }
496 };
497 // against the existing vectors, as blocks
498 project_out_old(nstart, nupdate);
499 project_out_old(nstart, nupdate);
500 // against each other, dropping dependent vectors
501 Index kept = 0;
502 for (Index j = 0; j < nupdate; ++j) {
503 Eigen::VectorXd v = Q.col(nstart + j);
504 for (Index pass = 0; pass < 2 && kept > 0; ++pass) {
505 v -= Q.middleCols(nstart, kept) *
506 (Q.middleCols(nstart, kept).transpose() * v);
507 }
508 const double norm = v.norm();
509 if (!(norm > 1e-10 * norms(j))) {
510 continue;
511 }
512 Q.col(nstart + kept) = v / norm;
513 ++kept;
514 }
515 Q.conservativeResize(Eigen::NoChange, nstart + kept);
516 // the normalisation can amplify what is left of the existing vectors
517 project_out_old(nstart, kept);
518 Q.rightCols(kept).colwise().normalize();
519 if (kept < nupdate) {
521 << TimeStamp() << " Davidson: dropped " << nupdate - kept
522 << " linearly dependent search vector(s)" << std::flush;
523 }
524 return kept;
525}
526
527Eigen::MatrixXd DavidsonSolver::qr(const Eigen::MatrixXd &A) const {
528
529 Index nrows = A.rows();
530 Index ncols = A.cols();
531 ncols = std::min(nrows, ncols);
532 Eigen::MatrixXd I = Eigen::MatrixXd::Identity(nrows, ncols);
533 Eigen::HouseholderQR<Eigen::MatrixXd> qr(A);
534 return qr.householderQ() * I;
535}
536
539 Index newvectors) const {
540 Eigen::MatrixXd newV =
541 Eigen::MatrixXd(proj.V.rows(), newvectors + restart_size_);
542 newV.rightCols(newvectors) = proj.V.rightCols(newvectors);
544
545 newV.leftCols(restart_size_) = rep.q.leftCols(restart_size_);
546 proj.AV *= rep.U.leftCols(restart_size_); // corresponds to replacing
547 // V with q.leftCols
548 } else {
549 Eigen::MatrixXd orthonormal =
550 DavidsonSolver::qr(rep.U.leftCols(restart_size_));
551 newV.leftCols(restart_size_) =
552 proj.V.leftCols(proj.V.cols() - newvectors) * orthonormal;
553 proj.AV *= orthonormal;
554
555 proj.AAV *= orthonormal;
556 proj.B = newV.leftCols(restart_size_).transpose() * proj.AAV;
557 }
558 proj.T = newV.leftCols(restart_size_).transpose() * proj.AV;
559 proj.V = newV;
560}
561
563 const DavidsonSolver::RitzEigenPair &rep, Index neigen) {
564
566 XTP_LOG(Log::error, log_) << TimeStamp() << " Davidson converged after "
567 << i_iter_ << " iterations." << std::flush;
568 info_ = Eigen::ComputationInfo::Success;
569}
570
572 const DavidsonSolver::RitzEigenPair &rep, const ArrayXb &root_converged,
573 Index neigen) {
574
576
577 double percent_converged = 0;
578
579 for (Index i = 0; i < neigen; i++) {
580 if (!root_converged[i]) {
581 eigenvalues_(i) = 0;
582 eigenvectors_.col(i).setZero();
583 } else {
584 percent_converged += 1.;
585 }
586 }
587 percent_converged /= double(neigen);
588 percent_converged *= 100.;
590 << TimeStamp() << "- Warning : Davidson "
591 << boost::format("%1$5.2f%%") % percent_converged << " converged after "
592 << i_iter_ << " iterations." << std::flush;
593 info_ = Eigen::ComputationInfo::NoConvergence;
594}
595
597 Index neigen) {
598 // store the eigenvalues/eigenvectors
599 this->eigenvalues_ = rep.lambda.head(neigen);
600 this->eigenvectors_ = rep.q.leftCols(neigen);
601 this->eigenvectors_.colwise().normalize();
602}
603
604} // namespace xtp
605} // namespace votca
Index getSizeUpdate(Index neigen) const
RitzEigenPair getRitzEigenPairs(const ProjectedSpace &proj) const
bool checkConvergence(const RitzEigenPair &rep, ProjectedSpace &proj, Index neigen) const
void restart(const RitzEigenPair &rep, ProjectedSpace &proj, Index newtestvectors) const
ProjectedSpace initProjectedSpace(Index neigen, Index size_initial_guess) const
Index orthogonalize(Eigen::MatrixXd &V, Index nupdate) const
void printOptions(Index operator_size) const
ArrayXl argsort(const Eigen::VectorXd &V) const
Index extendProjection(const RitzEigenPair &rep, ProjectedSpace &proj) const
Eigen::VectorXd eigenvalues() const
void checkOptions(Index operator_size)
void set_matrix_type(std::string mt)
Eigen::MatrixXd setupInitialEigenvectors(Index size_initial_guess) const
void set_correction(std::string method)
void set_size_update(std::string update_size)
Index gramschmidt(Eigen::MatrixXd &A, Index nstart) const
RitzEigenPair getHarmonicRitz(const ProjectedSpace &proj) const
void set_tolerance(std::string tol)
Eigen::MatrixXd eigenvectors() const
Eigen::VectorXd dpr(const Eigen::VectorXd &r, double lambda) const
Eigen::VectorXd computeCorrectionVector(const Eigen::VectorXd &qj, double lambdaj, const Eigen::VectorXd &Aqj) const
Eigen::MatrixXd extract_vectors(const Eigen::MatrixXd &V, const ArrayXl &idx) const
void printTiming(const std::chrono::time_point< std::chrono::system_clock > &start) const
void storeEigenPairs(const RitzEigenPair &rep, Index neigen)
Eigen::Array< bool, Eigen::Dynamic, 1 > ArrayXb
RitzEigenPair getRitz(const ProjectedSpace &proj) const
void storeConvergedData(const RitzEigenPair &rep, Index neigen)
Eigen::ComputationInfo info_
void printIterationData(const RitzEigenPair &rep, const ProjectedSpace &proj, Index neigen) const
Eigen::VectorXd olsen(const Eigen::VectorXd &r, const Eigen::VectorXd &x, double lambda) const
Eigen::MatrixXd qr(const Eigen::MatrixXd &A) const
void storeNotConvergedData(const RitzEigenPair &rep, const ArrayXb &root_converged, Index neigen)
Logger is used for thread-safe output of messages.
Definition logger.h:164
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
Index getMaxThreads()
Definition eigen.h:145
Charge transport classes.
Definition ERIs.h:28
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
Eigen::Array< votca::Index, Eigen::Dynamic, 1 > ArrayXl
Definition eigen.h:39