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