votca 2025.1-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") % i_iter_ %
96 proj.search_space() % rep.res_norm().head(neigen).maxCoeff() %
97 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 ArrayXl idx =
323 DavidsonSolver::argsort(eigenvalues).reverse().head(needed_pairs);
324 // we need the largest values, because this is the inverse value, so
325 // reverse list
326
328 rep.lambda = (rep.U.transpose() * proj.T * rep.U).diagonal();
329 rep.q = proj.V * rep.U; // Ritz vectors
330 rep.res = proj.AV * rep.U - rep.q * rep.lambda.asDiagonal(); // residues
331 return rep;
332}
333
335 Index neigen, Index size_initial_guess) const {
337
338 // initial vector basis
339 proj.V = DavidsonSolver::setupInitialEigenvectors(size_initial_guess);
340
341 // update variables
343 proj.root_converged = ArrayXb::Constant(proj.size_update, false);
344 return proj;
345}
346
349 Index neigen) const {
350 proj.root_converged = (rep.res_norm().head(proj.size_update) < tol_);
351 return proj.root_converged.head(neigen).all();
352}
353
356 DavidsonSolver::ProjectedSpace &proj) const {
357
358 Index nupdate = (proj.root_converged == false).count();
359 Index oldsize = proj.V.cols();
360 proj.V.conservativeResize(Eigen::NoChange, oldsize + nupdate);
361
362 Index k = 0;
363 for (Index j = 0; j < proj.size_update; j++) {
364 // skip the roots that have already converged
365 if (proj.root_converged[j]) {
366 continue;
367 }
368 // residue vector
369 Eigen::VectorXd w =
370 computeCorrectionVector(rep.q.col(j), rep.lambda(j), rep.res.col(j));
371
372 // append the correction vector to the search space
373 proj.V.col(oldsize + k) = w.normalized();
374 k++;
375 }
376 orthogonalize(proj.V, nupdate);
377 return nupdate;
378}
379
380Eigen::MatrixXd DavidsonSolver::extract_vectors(const Eigen::MatrixXd &V,
381 const ArrayXl &idx) const {
382 Eigen::MatrixXd W = Eigen::MatrixXd::Zero(V.rows(), idx.size());
383 for (Index i = 0; i < idx.size(); i++) {
384 W.col(i) = V.col(idx(i));
385 }
386 return W;
387}
388
390 const Eigen::VectorXd &qj, double lambdaj,
391 const Eigen::VectorXd &Aqj) const {
392
393 /* compute correction vector with either DPR or OLSEN CORRECTION
394 * For details on the method see :
395 * Systematic Study of Selected Diagonalization Methods
396 * for Configuration Interaction Matrices
397 * M.L. Leininger et al .
398 * Journal of Computational Chemistry Vol 22, No. 13 1574-1589 (2001)
399 */
400 Eigen::VectorXd correction;
401 switch (this->davidson_correction_) {
402 case CORR::DPR: {
403 correction = dpr(Aqj, lambdaj);
404 break;
405 }
406 case CORR::OLSEN: {
407 correction = olsen(Aqj, qj, lambdaj);
408 break;
409 }
410 }
411 // make sure no nan values are there, instead we set them to zero
412 return correction.unaryExpr(
413 [](double v) { return std::isfinite(v) ? v : 0.0; });
414}
415
416Eigen::VectorXd DavidsonSolver::dpr(const Eigen::VectorXd &r,
417 double lambda) const {
418 /* \brief Compute the diagonal preconditoned residue :
419 \delta = -r/(D - lambda)
420 */
421 return (-r.array() / (Adiag_.array() - lambda));
422}
423
424Eigen::VectorXd DavidsonSolver::olsen(const Eigen::VectorXd &r,
425 const Eigen::VectorXd &x,
426 double lambda) const {
427 /* \brief Compute the olsen correction :
428 \delta = (D-\lambda)^{-1} (-r + \epsilon x)
429 */
430 Eigen::VectorXd delta = DavidsonSolver::dpr(r, lambda);
431 double num = -x.transpose() * delta;
432 double denom = -x.transpose() * dpr(x, lambda);
433 double eps = num / denom;
434 delta += eps * x;
435 return delta;
436}
437
438void DavidsonSolver::orthogonalize(Eigen::MatrixXd &V, Index nupdate) const {
439 DavidsonSolver::gramschmidt(V, V.cols() - nupdate);
440}
441
442void DavidsonSolver::gramschmidt(Eigen::MatrixXd &Q, Index nstart) const {
443 Index nupdate = Q.cols() - nstart;
444 Eigen::VectorXd norms = Q.rightCols(nupdate).colwise().norm();
445 // orthogonalize with respect to already existing vectors
446 if (nstart > 0) {
447 Q.rightCols(nupdate) -=
448 Q.leftCols(nstart) *
449 (Q.leftCols(nstart).transpose() * Q.rightCols(nupdate));
450 Q.rightCols(nupdate).colwise().normalize();
451 }
452 // orthogonalize vectors to each other
453 for (Index j = nstart + 1; j < Q.cols(); ++j) {
454 Index range = j - nstart;
455 Q.col(j) -= Q.middleCols(nstart, range) *
456 (Q.middleCols(nstart, range).transpose() * Q.col(j));
457 Q.col(j).normalize();
458 }
459 // repeat again two is enough GS
460 // http://stoppels.blog/posts/orthogonalization-performance
461 if (nstart > 0) {
462 Q.rightCols(nupdate) -=
463 Q.leftCols(nstart) *
464 (Q.leftCols(nstart).transpose() * Q.rightCols(nupdate));
465 Q.rightCols(nupdate).colwise().normalize();
466 }
467
468 for (Index j = nstart + 1; j < Q.cols(); ++j) {
469 Index range = j - nstart;
470 Q.col(j) -= Q.middleCols(nstart, range) *
471 (Q.middleCols(nstart, range).transpose() * Q.col(j));
472 if (Q.col(j).norm() <= 1E-12 * norms(range)) {
473 // info_ = Eigen::ComputationInfo::NumericalIssue;
474 throw std::runtime_error("Linear dependencies in Gram-Schmidt.");
475 }
476 Q.col(j).normalize();
477 }
478}
479
480Eigen::MatrixXd DavidsonSolver::qr(const Eigen::MatrixXd &A) const {
481
482 Index nrows = A.rows();
483 Index ncols = A.cols();
484 ncols = std::min(nrows, ncols);
485 Eigen::MatrixXd I = Eigen::MatrixXd::Identity(nrows, ncols);
486 Eigen::HouseholderQR<Eigen::MatrixXd> qr(A);
487 return qr.householderQ() * I;
488}
489
492 Index newvectors) const {
493 Eigen::MatrixXd newV =
494 Eigen::MatrixXd(proj.V.rows(), newvectors + restart_size_);
495 newV.rightCols(newvectors) = proj.V.rightCols(newvectors);
497
498 newV.leftCols(restart_size_) = rep.q.leftCols(restart_size_);
499 proj.AV *= rep.U.leftCols(restart_size_); // corresponds to replacing
500 // V with q.leftCols
501 } else {
502 Eigen::MatrixXd orthonormal =
503 DavidsonSolver::qr(rep.U.leftCols(restart_size_));
504 newV.leftCols(restart_size_) =
505 proj.V.leftCols(proj.V.cols() - newvectors) * orthonormal;
506 proj.AV *= orthonormal;
507
508 proj.AAV *= orthonormal;
509 proj.B = newV.leftCols(restart_size_).transpose() * proj.AAV;
510 }
511 proj.T = newV.leftCols(restart_size_).transpose() * proj.AV;
512 proj.V = newV;
513}
514
516 const DavidsonSolver::RitzEigenPair &rep, Index neigen) {
517
519 XTP_LOG(Log::error, log_) << TimeStamp() << " Davidson converged after "
520 << i_iter_ << " iterations." << std::flush;
521 info_ = Eigen::ComputationInfo::Success;
522}
523
525 const DavidsonSolver::RitzEigenPair &rep, const ArrayXb &root_converged,
526 Index neigen) {
527
529
530 double percent_converged = 0;
531
532 for (Index i = 0; i < neigen; i++) {
533 if (!root_converged[i]) {
534 eigenvalues_(i) = 0;
535 eigenvectors_.col(i).setZero();
536 } else {
537 percent_converged += 1.;
538 }
539 }
540 percent_converged /= double(neigen);
541 percent_converged *= 100.;
543 << TimeStamp() << "- Warning : Davidson "
544 << boost::format("%1$5.2f%%") % percent_converged << " converged after "
545 << i_iter_ << " iterations." << std::flush;
546 info_ = Eigen::ComputationInfo::NoConvergence;
547}
548
550 Index neigen) {
551 // store the eigenvalues/eigenvectors
552 this->eigenvalues_ = rep.lambda.head(neigen);
553 this->eigenvectors_ = rep.q.leftCols(neigen);
554 this->eigenvectors_.colwise().normalize();
555}
556
557} // namespace xtp
558} // 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
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)
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
void gramschmidt(Eigen::MatrixXd &A, Index nstart) 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)
void orthogonalize(Eigen::MatrixXd &V, Index nupdate) const
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:128
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:36