votca 2024.2-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
26using boost::format;
27using std::flush;
28
29namespace votca {
30namespace xtp {
31
33
35 const std::chrono::time_point<std::chrono::system_clock> &start) const {
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;
44 << TimeStamp() << "-----------------------------------" << std::flush;
45}
46
48 //. search space exceeding the system size
49 if (max_search_space_ > operator_size) {
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;
57 << TimeStamp() << " == Warning : Max search space set to "
58 << operator_size << std::flush;
59
61 << TimeStamp()
62 << " == Warning : If problems appear, try asking for less than "
63 << Index(operator_size / 10) << " eigenvalues" << std::flush;
64 }
65}
66
67void DavidsonSolver::printOptions(Index operator_size) const {
68
70 << TimeStamp() << " Davidson Solver using " << OPENMP::getMaxThreads()
71 << " threads." << std::flush;
73 << TimeStamp() << " Tolerance : " << tol_ << std::flush;
74
75 switch (this->davidson_correction_) {
76 case CORR::DPR:
78 << TimeStamp() << " DPR Correction" << std::flush;
79 break;
80 case CORR::OLSEN:
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
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);
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
105 if (mt == "HAM") {
107 } else if (mt == "SYMM") {
109 } else {
110 throw std::runtime_error(mt + " is not a valid Davidson matrix type");
111 }
112}
113
114void DavidsonSolver::set_correction(std::string method) {
115 if (method == "DPR") {
117 } else if (method == "OLSEN") {
119 } else {
120 throw std::runtime_error(method +
121 " is not a valid Davidson correction method");
122 }
123}
124
125void 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
139void DavidsonSolver::set_size_update(std::string update_size) {
140
141 if (update_size == "min") {
143 } else if (update_size == "safe") {
145 } else if (update_size == "max") {
147 } else {
148 throw std::runtime_error(update_size + " is not a valid Davidson update");
149 }
150}
151
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
175ArrayXl 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
184 Index size_initial_guess) const {
185
186 Eigen::MatrixXd guess =
187 Eigen::MatrixXd::Zero(Adiag_.size(), size_initial_guess);
189
190 switch (this->matrix_type_) {
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}
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
225 const DavidsonSolver::ProjectedSpace &proj) const {
226
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
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()) {
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
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
338 Index neigen, Index size_initial_guess) const {
340
341 // initial vector basis
342 proj.V = DavidsonSolver::setupInitialEigenvectors(size_initial_guess);
343
344 // update variables
346 proj.root_converged = ArrayXb::Constant(proj.size_update, false);
347 return proj;
348}
349
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
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
383Eigen::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
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
419Eigen::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
427Eigen::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
441void DavidsonSolver::orthogonalize(Eigen::MatrixXd &V, Index nupdate) const {
442 DavidsonSolver::gramschmidt(V, V.cols() - nupdate);
443}
444
445void 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
483Eigen::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
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);
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
519 const DavidsonSolver::RitzEigenPair &rep, Index neigen) {
520
522 XTP_LOG(Log::error, log_) << TimeStamp() << " Davidson converged after "
523 << i_iter_ << " iterations." << std::flush;
524 info_ = Eigen::ComputationInfo::Success;
525}
526
528 const DavidsonSolver::RitzEigenPair &rep, const ArrayXb &root_converged,
529 Index neigen) {
530
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.;
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
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
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
base class for all analysis tools
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
Eigen::Array< votca::Index, Eigen::Dynamic, 1 > ArrayXl
Definition eigen.h:36