iterative-solver 0.0
helper-implementation.h
1#ifndef LINEARALGEBRA_SRC_MOLPRO_LINALG_ITERATIVESOLVER_HELPER_IMPLEMENTATION_H_
2#define LINEARALGEBRA_SRC_MOLPRO_LINALG_ITERATIVESOLVER_HELPER_IMPLEMENTATION_H_
3#include <Eigen/Dense>
4
5#include <molpro/Profiler.h>
6#include <molpro/lapacke.h>
7#include <molpro/linalg/itsolv/helper-dispatch.h>
8#include <molpro/linalg/itsolv/helper.h>
9
10#include "Logger.h"
11#include "subspace/Matrix.h"
12
13#include <algorithm>
14#include <cassert>
15#include <cmath>
16#include <complex>
17#include <cstddef>
18#include <iomanip>
19#include <list>
20#include <numeric>
21#include <span>
22#include <type_traits>
23
24namespace molpro::linalg::itsolv {
25
26template <typename value_type>
27std::list<SVD<value_type>> svd_eigen_jacobi(size_t nrows, size_t ncols, const array::Span<value_type>& m,
28 real_type_t<value_type> threshold) {
29 auto mat = Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>>(m.data(), nrows, ncols);
30#if EIGEN_VERSION_AT_LEAST(3, 4, 90)
31 // Cast to unsigned int to avoid -Wdeprecated-enum-enum-conversion: the two
32 // Eigen flags belong to different enum types but are meant to be ORed here.
33 auto svd = Eigen::JacobiSVD<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>>(
34 mat, static_cast<unsigned int>(Eigen::ComputeThinV) |
35 static_cast<unsigned int>(Eigen::NoQRPreconditioner));
36#else
37 auto svd = Eigen::JacobiSVD<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>, Eigen::NoQRPreconditioner>(
38 mat, Eigen::ComputeThinV);
39#endif
40 auto svd_system = std::list<SVD<value_type>>{};
41 auto sv = svd.singularValues();
42 for (int i = int(ncols) - 1; i >= 0; --i) {
43 if (std::abs(sv(i)) < threshold) { // TODO: This seems to discard values ABOVE the threshold, not below it. it's
44 auto t = SVD<value_type>{}; // also not scaling this threshold relative to the max singular value - find out why
45 t.value = sv(i);
46 t.v.reserve(ncols);
47 for (size_t j = 0; j < ncols; ++j) {
48 t.v.emplace_back(svd.matrixV()(j, i));
49 }
50 svd_system.emplace_back(std::move(t));
51 }
52 }
53 return svd_system;
54}
55
56template <typename value_type>
57std::list<SVD<value_type>> svd_eigen_bdcsvd(size_t nrows, size_t ncols, const array::Span<value_type>& m,
58 real_type_t<value_type> threshold) {
59 auto mat = Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>>(m.data(), nrows, ncols);
60 auto svd = Eigen::BDCSVD<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>>(mat, Eigen::ComputeThinV);
61 auto svd_system = std::list<SVD<value_type>>{};
62 auto sv = svd.singularValues();
63 for (int i = int(ncols) - 1; i >= 0; --i) {
64 if (std::abs(sv(i)) < threshold) {
65 auto t = SVD<value_type>{};
66 t.value = sv(i);
67 t.v.reserve(ncols);
68 for (size_t j = 0; j < ncols; ++j) {
69 t.v.emplace_back(svd.matrixV()(j, i));
70 }
71 svd_system.emplace_back(std::move(t));
72 }
73 }
74 return svd_system;
75}
76
77#ifdef HAVE_LAPACKE
84template <typename value_type>
85std::list<SVD<value_type>> svd_lapacke_gesdd(size_t nrows, size_t ncols, const array::Span<value_type>& mat,
86 real_type_t<value_type> threshold) {
87 static_assert(has_lapack_kernel_v<value_type>, "LAPACK has no ?gesdd kernel for this scalar type");
88 const size_t sdim = std::min(nrows, ncols);
89 // ?gesdd destroys its input, so hand it a copy
90 std::vector<value_type> a(mat.begin(), mat.end());
91 std::vector<real_type_t<value_type>> sv(sdim);
92 std::vector<value_type> u(nrows * nrows), v(ncols * ncols);
93 const auto info =
94 lapack::gesdd(LAPACK_ROW_MAJOR, 'A', lapack_int(nrows), lapack_int(ncols), a.data(), lapack_int(ncols), sv.data(),
95 u.data(), lapack_int(nrows), v.data(), lapack_int(ncols));
96 if (info != 0)
97 throw std::runtime_error("?gesdd (singular value decomposition) failed with info = " + std::to_string(info));
98 auto svd_system = std::list<SVD<value_type>>{};
99 for (int i = int(ncols) - 1; i >= 0; --i) {
100 if (std::abs(sv[i]) < threshold) {
101 auto t = SVD<value_type>{};
102 t.value = sv[i];
103 t.v.reserve(ncols);
104 for (size_t j = 0; j < ncols; ++j) {
105 t.v.emplace_back(v[i * ncols + j]);
106 }
107 svd_system.emplace_back(std::move(t));
108 }
109 }
110 return svd_system;
111}
112
114template <typename value_type>
115std::list<SVD<value_type>> svd_lapacke_gesvd(size_t nrows, size_t ncols, const array::Span<value_type>& mat,
116 real_type_t<value_type> threshold) {
117 static_assert(has_lapack_kernel_v<value_type>, "LAPACK has no ?gesvd kernel for this scalar type");
118 const size_t sdim = std::min(nrows, ncols);
119 // ?gesvd destroys its input, so hand it a copy
120 std::vector<value_type> a(mat.begin(), mat.end());
121 std::vector<real_type_t<value_type>> sv(sdim);
122 std::vector<value_type> u(nrows * nrows), v(ncols * ncols);
123 std::vector<real_type_t<value_type>> superb(sdim > 0 ? sdim - 1 : 0);
124 const auto info = lapack::gesvd(LAPACK_ROW_MAJOR, 'N', 'A', lapack_int(nrows), lapack_int(ncols), a.data(),
125 lapack_int(ncols), sv.data(), u.data(), lapack_int(nrows), v.data(),
126 lapack_int(ncols), superb.data());
127 if (info != 0)
128 throw std::runtime_error("?gesvd (singular value decomposition) failed with info = " + std::to_string(info));
129 auto svd_system = std::list<SVD<value_type>>{};
130 for (int i = int(ncols) - 1; i >= 0; --i) {
131 if (std::abs(sv[i]) < threshold) {
132 auto t = SVD<value_type>{};
133 t.value = sv[i];
134 t.v.reserve(ncols);
135 for (size_t j = 0; j < ncols; ++j) {
136 t.v.emplace_back(v[i * ncols + j]);
137 }
138 svd_system.emplace_back(std::move(t));
139 }
140 }
141 return svd_system;
142}
143
144#endif
145
153inline int eigensolver_lapacke_dsyev(std::span<const double> matrix, std::span<double> eigenvectors,
154 std::span<double> eigenvalues, const size_t dimension) {
155 return eigensolver_hermitian<double>(matrix, eigenvectors, eigenvalues, dimension);
156}
157
159inline std::list<SVD<double>> eigensolver_lapacke_dsyev(size_t dimension, std::span<const double> matrix) {
160 return eigensolver_hermitian<double>(dimension, matrix);
161}
162
170template <typename value_type>
171size_t get_rank(std::span<const value_type> eigenvalues, value_type threshold) {
172 if (eigenvalues.size() == 0) {
173 return 0;
174 }
175 value_type max = *max_element(eigenvalues.begin(), eigenvalues.end());
176 value_type threshold_scaled = threshold * max;
177 size_t count =
178 std::count_if(eigenvalues.begin(), eigenvalues.end(), [&](auto const& val) { return val >= threshold_scaled; });
179 return count;
180}
181
189template <typename value_type>
190size_t get_rank(std::list<SVD<value_type>> svd_system, real_type_t<value_type> threshold) {
191 // a singular value, and the eigenvalue of a hermitian matrix, is real even for a complex problem
192 real_type_t<value_type> max_value = 0;
193 typename std::list<SVD<value_type>>::iterator it;
194 for (it = svd_system.begin(); it != svd_system.end(); it++) {
195 if (real_part(it->value) > max_value) {
196 max_value = real_part(it->value);
197 }
198 }
199 // scale threshold
200 const real_type_t<value_type> threshold_scaled = threshold * max_value;
201
202 size_t rank = 0;
203 // get rank
204 for (it = svd_system.begin(); it != svd_system.end(); it++) {
205 if (real_part(it->value) > threshold_scaled) {
206 rank += 1;
207 }
208 }
209 return rank;
210}
211
212namespace detail {
213
221template <typename value_type>
222std::list<SVD<value_type>> svd_system_impl(size_t nrows, size_t ncols, const array::Span<value_type>& m,
223 real_type_t<value_type> threshold, bool hermitian, bool reduce_to_rank) {
224 std::list<SVD<value_type>> svds;
225 assert(m.size() == nrows * ncols);
226 if (m.empty())
227 return {};
228 if (hermitian) {
229 assert(nrows == ncols);
230 // m arrives row-major, as subspace::Matrix stores it, while eigensolver_hermitian reads its input
231 // column-major. For a complex hermitian matrix the two differ by a conjugation, so transpose it.
232 using matrix_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
233 using row_major_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
234 const matrix_type a = Eigen::Map<const row_major_type>(m.data(), nrows, ncols);
235 svds = eigensolver_hermitian<value_type>(nrows, std::span<const value_type>{a.data(), size_t(a.size())});
236 for (auto s = svds.begin(); s != svds.end();)
237 if (real_part(s->value) > threshold)
238 s = svds.erase(s);
239 else
240 ++s;
241 } else {
242 // The general (non-hermitian) decomposition goes through Eigen for every scalar type: the
243 // subspace matrices are small, and ?gesdd/?gesvd (svd_lapacke_gesdd/svd_lapacke_gesvd above)
244 // offer nothing here that would justify differing results between precisions.
245 svds = svd_eigen_jacobi<value_type>(nrows, ncols, m, threshold);
246 // return svd_eigen_bdcsvd<value_type>(nrows, ncols, m, threshold);
247 }
248
249 // reduce to rank
250 if (reduce_to_rank) {
251 int rank = get_rank(svds, threshold);
252 for (int i = ncols; i > rank; i--) {
253 svds.pop_back();
254 }
255 }
256 return svds;
257}
258
259} // namespace detail
260
261template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
262std::list<SVD<value_type>> svd_system(size_t nrows, size_t ncols, const array::Span<value_type>& m,
263 real_type_t<value_type> threshold, bool hermitian, bool reduce_to_rank) {
264 return detail::svd_system_impl<value_type>(nrows, ncols, m, threshold, hermitian, reduce_to_rank);
265}
266
267template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
268std::list<SVD<value_type>> svd_system(size_t nrows, size_t ncols, const array::Span<value_type>& m,
269 real_type_t<value_type> threshold, bool hermitian, bool reduce_to_rank) {
270 return detail::svd_system_impl<value_type>(nrows, ncols, m, threshold, hermitian, reduce_to_rank);
271}
272
273template <typename value_type>
274void printMatrix(const std::vector<value_type>& m, size_t rows, size_t cols, std::string title, std::ostream& s) {
275 s << title << "\n"
276 << Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>>(m.data(), rows, cols) << std::endl;
277}
278
279namespace detail {
280
287template <typename real_type>
289 bool operator()(const std::complex<real_type>& lhs, const std::complex<real_type>& rhs) const {
290 using std::abs;
291 if (lhs.real() != rhs.real()) {
292 return lhs.real() < rhs.real();
293 }
294 if (abs(lhs.imag()) != abs(rhs.imag())) {
295 // This fixes the order of distinct complex eigenvalue pairs that share the same real part
296 return abs(lhs.imag()) < abs(rhs.imag());
297 }
298 // This fixes the order within a complex eigenvalue pair
299 return lhs.imag() < rhs.imag();
300 }
301};
302
304template <typename value_type>
306 using matrix_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
310 int rank = 0;
311};
312
324template <typename value_type>
326 const std::vector<value_type>& metric, size_t dimension,
327 real_type_t<value_type> svdThreshold, int verbosity) {
328 using std::abs;
329 using std::sqrt;
330 using real_t = real_type_t<value_type>;
331 using matrix_type = typename orthogonalised_subspace<value_type>::matrix_type;
332 using row_major_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
333 // a metric eigenvalue below this marks a singular direction; calibrated for double precision
334 const real_t null_metric_eigenvalue_tol = precision_scaled<value_type>(1e-14);
335
337 const matrix_type H = Eigen::Map<const row_major_type>(matrix.data(), dimension, dimension);
338 result.S = Eigen::Map<const row_major_type>(metric.data(), dimension, dimension);
339
340 // Perform an eigenvalue decomposition of the metric. The metric is necessarily hermitian, so the
341 // hermitian eigensolver applies; it reads its input column-major, which is why S was materialised
342 // above rather than mapped straight onto the caller's row-major buffer.
343 Eigen::Vector<real_t, Eigen::Dynamic> metricEvals(dimension);
344 matrix_type metricEvecs(dimension, dimension);
345 const int success = eigensolver_hermitian<value_type>(
346 std::span<const value_type>{result.S.data(), size_t(result.S.size())},
347 {metricEvecs.data(), dimension * dimension}, {metricEvals.data(), dimension}, dimension);
348 if (success != 0) {
349 throw std::runtime_error("Eigensolver did not converge");
350 }
351 result.rank = int(get_rank<real_t>(std::span<const real_t>{metricEvals.data(), dimension}, svdThreshold));
352 const int rank = result.rank;
353
354 if (verbosity > 1 && rank < int(dimension))
355 molpro::cout << "SVD rank " << rank << " in subspace of dimension " << dimension << std::endl;
356 if (verbosity > 2 && rank < int(dimension))
357 molpro::cout << "singular values " << metricEvals.transpose() << std::endl;
358
359 // Transform H into a symmetrically orthogonalized basis via (S^{-1/2})^\dagger H S^{-1/2}
360 // taking into account the possibility of rank-deficiency of S (aka: zero SV).
361 // The eigenvalues come out in ascending order, so the retained directions are the trailing block.
362 Eigen::Vector<value_type, Eigen::Dynamic> svmh(rank);
363 for (int k = 0; k < rank; k++) {
364 const real_t lambda = metricEvals(dimension - rank + k);
365 assert(abs(lambda) <= svdThreshold || lambda >= 0); // metric is supposed to be positive (semi-)definite
366 svmh(k) = lambda > null_metric_eigenvalue_tol ? value_type(1 / sqrt(lambda)) : value_type(0);
367 }
368 const auto retained = metricEvecs.rightCols(rank);
369 result.Hbar = svmh.asDiagonal() * retained.adjoint() * H * retained * svmh.asDiagonal();
370 result.transformation = retained * svmh.asDiagonal();
371 return result;
372}
373
374} // namespace detail
375
376template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
377void eigenproblem(std::vector<value_type>& eigenvectors, std::vector<value_type>& eigenvalues,
378 const std::vector<value_type>& matrix, const std::vector<value_type>& metric, size_t dimension,
379 bool hermitian, real_type_t<value_type> svdThreshold, int verbosity,
380 std::vector<std::pair<std::size_t, value_type>>* imag_eval_parts) {
381 using std::abs;
382 using std::sqrt;
383 using real_t = real_type_t<value_type>;
384 using matrix_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
385 using vector_type = Eigen::Vector<value_type, Eigen::Dynamic>;
386 // A complex scalar represents a complex eigenvalue directly, so, unlike the real overload, this one
387 // never has to split an eigenvalue into a real part and a separately tracked imaginary part.
388 if (imag_eval_parts)
389 imag_eval_parts->clear();
390 const real_t null_eigenvalue_tol = precision_scaled<value_type>(1e-12);
391
392 auto prof = molpro::Profiler::single();
393 prof->start("itsolv::eigenproblem");
394 const auto subspace = detail::orthogonalise_subspace<value_type>(matrix, metric, dimension, svdThreshold, verbosity);
395 const int rank = subspace.rank;
396 eigenvectors.resize(dimension * rank);
397 eigenvalues.resize(rank);
398 if (rank == 0) {
399 prof->stop();
400 return;
401 }
402
403 // Perform an eigendecomposition of the transformed matrix. Hbar is not self-adjoint in general, so
404 // its spectrum is complex -- which is exactly what the scalar type of this overload can hold.
405 Eigen::ComplexEigenSolver<matrix_type> s(subspace.Hbar);
406 if (s.info() != Eigen::Success) {
407 throw std::runtime_error("Eigensolver of the subspace matrix did not converge");
408 }
409 vector_type subspaceEigenvalues = s.eigenvalues();
410 // Convert eigenvectors back into original basis (minus singular dimensions)
411 matrix_type subspaceEigenvectors = subspace.transformation * s.eigenvectors();
412
413 // Determine order of eigenvalues such that they come in non-descending order of their real part
414 // (and non-descending order of imaginary part, in case of equal real parts)
415 Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> perm(subspaceEigenvalues.size());
416 perm.setIdentity();
417 std::ranges::sort(perm.indices(), detail::eigenvalue_order<real_t>{},
418 [&subspaceEigenvalues](auto idx) { return subspaceEigenvalues[idx]; });
419 subspaceEigenvectors = subspaceEigenvectors * perm;
420 subspaceEigenvalues = perm.transpose() * subspaceEigenvalues;
421
422 if (!hermitian) {
423 // The eigenvectors of a self-adjoint Hbar are orthonormal, so back-transforming them already
424 // yields an S-orthonormal set; otherwise they have to be normalised explicitly.
425 for (Eigen::Index k = 0; k < subspaceEigenvectors.cols(); k++) {
426 const auto ovl = subspaceEigenvectors.col(k).dot(subspace.S * subspaceEigenvectors.col(k));
427 // S is supposed to be positive (semi-)definite implying that ovl must be a non-negative real number
428 assert(abs(ovl.imag()) < precision_scaled<value_type>(1e-10));
429 if (ovl.real() > null_eigenvalue_tol)
430 subspaceEigenvectors.col(k) /= value_type(sqrt(ovl.real()));
431 }
432 }
433
434 // Fix indeterminate phase of eigenvectors by requiring the max component to be real and positive
435 for (Eigen::Index i = 0; i < subspaceEigenvectors.cols(); ++i) {
436 Eigen::Index pivot = 0;
437 for (Eigen::Index j = 1; j < subspaceEigenvectors.rows(); ++j)
438 if (abs(subspaceEigenvectors(j, i)) > abs(subspaceEigenvectors(pivot, i)))
439 pivot = j;
440 const real_t magnitude = abs(subspaceEigenvectors(pivot, i));
441 if (magnitude > 0)
442 subspaceEigenvectors.col(i) *= conjugate(subspaceEigenvectors(pivot, i)) / value_type(magnitude);
443 }
444
445 Eigen::Map<matrix_type>(eigenvectors.data(), dimension, rank) = subspaceEigenvectors;
446 Eigen::Map<vector_type>(eigenvalues.data(), rank) = subspaceEigenvalues;
447 prof->stop();
448}
449
450template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
451void eigenproblem(std::vector<value_type>& eigenvectors, std::vector<value_type>& eigenvalues,
452 const std::vector<value_type>& matrix, const std::vector<value_type>& metric, size_t dimension,
453 bool hermitian, real_type_t<value_type> svdThreshold, int verbosity,
454 std::vector<std::pair<std::size_t, value_type>>* imag_eval_parts) {
455 // let ADL pick up the overloads of extended- and arbitrary-precision scalar types
456 using std::abs;
457 using std::sqrt;
458 // Tolerances calibrated for double precision, rescaled to the precision actually in use
459 const value_type zero_tol = precision_scaled<value_type>(1e-10); // a quantity that ought to vanish
460 const value_type null_eigenvalue_tol = precision_scaled<value_type>(1e-12); // an eigenvalue that vanishes
461 using MatrixT = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
462 using ComplexMatrixT = Eigen::Matrix<std::complex<value_type>, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
463 using VectorT = Eigen::Vector<value_type, Eigen::Dynamic>;
464 using ComplexVectorT = Eigen::Vector<std::complex<value_type>, Eigen::Dynamic>;
465
466 auto prof = molpro::Profiler::single();
467 prof->start("itsolv::eigenproblem");
468 const auto subspace = detail::orthogonalise_subspace<value_type>(matrix, metric, dimension, svdThreshold, verbosity);
469 const int rank = subspace.rank;
470 const auto& S = subspace.S;
471 const auto& Hbar = subspace.Hbar;
472 ComplexMatrixT subspaceEigenvectors;
473 ComplexVectorT subspaceEigenvalues;
474 if (rank == 0) {
475 eigenvectors.clear();
476 eigenvalues.clear();
477 prof->stop();
478 return;
479 }
480
481 // Perform an eigendecomposition of the transformed matrix
482 Eigen::EigenSolver<MatrixT> s(Hbar);
483 subspaceEigenvalues = s.eigenvalues();
484 if (s.eigenvalues().imag().norm() < zero_tol) {
485 // real eigenvalues
486 subspaceEigenvalues = subspaceEigenvalues.real();
487 subspaceEigenvectors = s.eigenvectors();
488 // complex eigenvectors need to be rotated
489 // assume that they come in consecutive pairs
490 for (int i = 0; i < subspaceEigenvectors.cols() - 1; i++) {
491 if (subspaceEigenvectors.col(i).imag().norm() <= zero_tol) {
492 continue;
493 }
494
495 const int j = i + 1;
496 if (abs(subspaceEigenvalues(i) - subspaceEigenvalues(j)) >= zero_tol or
497 subspaceEigenvectors.col(j).imag().norm() <= zero_tol) {
498 continue;
499 }
500
501 // For a real-valued matrix, eigenvectors can always be chosen to be real. If we have a complex eigenvector,
502 // it's complex conjugate must also be an eigenvector with the same eigenvalue. We can combine these two
503 // vectors as either u + u^* = 2 Re(u) or i*(u - u^*) = -2 Im(u).
504 // In other words, the real and imaginary part of u are the corresponding real-valued eigenvectors.
505 subspaceEigenvectors.col(j) = subspaceEigenvectors.col(i).imag() / subspaceEigenvectors.col(i).imag().norm();
506 subspaceEigenvectors.col(i) = subspaceEigenvectors.col(i).real() / subspaceEigenvectors.col(i).real().norm();
507 }
508
509 // Convert eigenvectors back into original basis (minus singular dimensions)
510 subspaceEigenvectors = subspace.transformation * subspaceEigenvectors;
511 } else {
512 // complex eigenvalues
513#ifdef __INTEL_COMPILER
514 molpro::cout << "Hbar\n" << Hbar << std::endl;
515 molpro::cout << "Eigenvalues\n" << s.eigenvalues() << std::endl;
516 molpro::cout << "Eigenvectors\n" << s.eigenvectors() << std::endl;
517 throw std::runtime_error("Intel compiler does not support working with complex eigen3 entities properly");
518#endif
519
520 // Convert eigenvectors back into original basis (minus singular dimensions)
521 subspaceEigenvectors = subspace.transformation * s.eigenvectors();
522 }
523
524 // Determine order of eigenvalues such that they come in non-descending order of their real part
525 // (and non-descending order of imaginary part, in case of equal real parts)
526 Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> perm(subspaceEigenvalues.size());
527 perm.setIdentity();
528 std::ranges::sort(perm.indices(), detail::eigenvalue_order<real_type_t<value_type>>{},
529 [&subspaceEigenvalues](auto idx) { return subspaceEigenvalues[idx]; });
530
531 // Apply determined order to eigenvalues and -vectors
532 subspaceEigenvectors = subspaceEigenvectors * perm;
533 subspaceEigenvalues = perm.transpose() * subspaceEigenvalues;
534
535
536 // TODO: Need to address the case of near-zero eigenvalues (as below for non-hermitian case) and clean-up
537 // non-hermitian case
538
539 if (!hermitian) {
540 for (auto repeat = 0; repeat < 1; ++repeat)
541 for (Eigen::Index k = 0; k < subspaceEigenvectors.cols(); k++) {
542 if (abs(subspaceEigenvalues(k)) < null_eigenvalue_tol) {
543 // special case of zero eigenvalue -- make some real non-zero vector definitely in the null space
544 subspaceEigenvectors.col(k).real() += value_type(0.3256897) * subspaceEigenvectors.col(k).imag();
545 subspaceEigenvectors.col(k).imag().setZero();
546 }
547
548 auto ovl = subspaceEigenvectors.col(k).dot(S * subspaceEigenvectors.col(k));
549 // S is supposed to be positive (semi-)definite implying that ovl must be a non-negative real number
550 assert(abs(ovl.imag()) < zero_tol);
551 assert(ovl.real() > 0);
552 subspaceEigenvectors.col(k) /= sqrt(ovl.real());
553 }
554 }
555
556 // Fix indeterminate phase of eigenvectors by requiring the max component to be positive
557 for (std::size_t i = 0; i < subspaceEigenvectors.cols(); ++i) {
558 const auto &col = subspaceEigenvectors.col(i);
559 auto it = std::ranges::max_element(col, std::less<>{}, [](auto val) { return abs(val); });
560 auto idx = std::distance(col.begin(), it);
561 if (subspaceEigenvectors.col(i)[idx].real() < 0) {
562 subspaceEigenvectors.col(i) *= -1;
563 }
564 }
565
566 if (imag_eval_parts) {
567 // Complex eigenvalues are tolerable -> process them to be able to represent everything
568 // by real-valued vectors
569 imag_eval_parts->clear();
570
571 for (Eigen::Index root = 0; root < Hbar.cols(); ++root) {
572 if (subspaceEigenvalues(root).imag() == 0) {
573 continue;
574 }
575
576 // Complex-valued eigenvalues must appear as complex conjugate pairs
577 assert(root + 1 < subspaceEigenvalues.size());
578 assert(abs(std::conj(subspaceEigenvalues(root)) - subspaceEigenvalues(root + 1)) < zero_tol);
579
580 imag_eval_parts->emplace_back(root, subspaceEigenvalues(root).imag());
581 imag_eval_parts->emplace_back(root + 1, -subspaceEigenvalues(root).imag());
582
583 // Set the eigenvalue pair to their real-part only (imaginary part is tracked separately in imag_eval_parts)
584 subspaceEigenvalues(root) = subspaceEigenvalues(root + 1) = subspaceEigenvalues(root).real();
585
586 // Pretend the real and imaginary part were separate eigenvectors (this is required in order
587 // to represent all data without the need for using complex numbers).
588 // However, as the eigenvalues are not degenerate, the real and imaginary parts of the eigenvectors
589 // are in fact NOT eigenvectors themselves.
590 // If the true eigenvectors are required, they can easily be recovered from the real and imaginary
591 // parts we store here.
592 subspaceEigenvectors.col(root + 1) = subspaceEigenvectors.col(root).imag();
593 subspaceEigenvectors.col(root) = subspaceEigenvectors.col(root).real();
594
595 // Skip the second eigenvalue in the pair of complex conjugate eigenvalues
596 ++root;
597 }
598 }
599
600 if ((subspaceEigenvectors - subspaceEigenvectors.real()).norm() > zero_tol or
601 (subspaceEigenvalues - subspaceEigenvalues.real()).norm() > zero_tol) {
602 throw std::runtime_error("unexpected complex solution found");
603 }
604
605 eigenvectors.resize(dimension * Hbar.cols());
606 eigenvalues.resize(Hbar.cols());
607
608 Eigen::Map<MatrixT>(eigenvectors.data(), dimension, Hbar.cols()) =
609 subspaceEigenvectors.real();
610 Eigen::Map<VectorT> ev(eigenvalues.data(), Hbar.cols());
611 ev = subspaceEigenvalues.real();
612
613 prof->stop();
614}
615
616namespace detail {
617
624template <typename value_type>
625void solve_LinearEquations_impl(std::vector<value_type>& solution, std::vector<value_type>& eigenvalues,
626 const std::vector<value_type>& matrix, const std::vector<value_type>& metric,
627 const std::vector<value_type>& rhs, const size_t dimension, size_t nroot,
628 real_type_t<value_type> augmented_hessian, real_type_t<value_type> svdThreshold,
629 int verbosity) {
630 const Eigen::Index nX = dimension;
631 solution.resize(nX * nroot);
632 // std::cout << "augmented_hessian "<<augmented_hessian<<std::endl;
633 if (augmented_hessian > 0) { // Augmented hessian
634 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> subspaceMatrix;
635 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> subspaceOverlap;
636 subspaceMatrix.conservativeResize(nX + 1, nX + 1);
637 subspaceOverlap.conservativeResize(nX + 1, nX + 1);
638 // both arrive row-major, as subspace::Matrix stores them; reading them column-major would
639 // transpose, which is invisible for a real symmetric matrix but conjugates a hermitian one
640 using row_major_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
641 subspaceMatrix.block(0, 0, nX, nX) = Eigen::Map<const row_major_type>(matrix.data(), nX, nX);
642 subspaceOverlap.block(0, 0, nX, nX) = Eigen::Map<const row_major_type>(metric.data(), nX, nX);
643 eigenvalues.resize(nroot);
644 for (size_t root = 0; root < nroot; root++) {
645 for (Eigen::Index i = 0; i < nX; i++) {
646 // rhs is a row-major dimension x nroot matrix, as the straight-solve branch below also reads it
647 subspaceMatrix(i, nX) = -augmented_hessian * rhs[i * nroot + root];
648 // the augmented matrix is hermitian, which for a real element type means simply symmetric
649 subspaceMatrix(nX, i) = conjugate(subspaceMatrix(i, nX));
650 subspaceOverlap(i, nX) = subspaceOverlap(nX, i) = 0;
651 }
652 subspaceMatrix(nX, nX) = 0;
653 subspaceOverlap(nX, nX) = 1;
654 // std::cout << "subspace augmented hessian subspaceMatrix\n"<<subspaceMatrix<<std::endl;
655 // std::cout << "subspace augmented hessian subspaceOverlap\n"<<subspaceOverlap<<std::endl;
656
657 if constexpr (is_complex<value_type>{}) {
658 // Eigen's GeneralizedEigenSolver is real-only, so reduce to a standard problem the same way
659 // eigenproblem() does: the augmented overlap is hermitian and positive semi-definite.
660 using matrix_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
661 std::vector<value_type> augmented_matrix(subspaceMatrix.size()), augmented_overlap(subspaceOverlap.size());
662 // orthogonalise_subspace() reads its arguments row-major
663 Eigen::Map<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>(
664 augmented_matrix.data(), nX + 1, nX + 1) = subspaceMatrix;
665 Eigen::Map<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>(
666 augmented_overlap.data(), nX + 1, nX + 1) = subspaceOverlap;
667 const auto augmented = detail::orthogonalise_subspace<value_type>(augmented_matrix, augmented_overlap,
668 nX + 1, svdThreshold, verbosity);
669 if (augmented.rank == 0)
670 throw std::runtime_error("augmented Hessian: the augmented overlap matrix has no non-singular direction");
671 Eigen::ComplexEigenSolver<matrix_type> s(augmented.Hbar);
672 if (s.info() != Eigen::Success)
673 throw std::runtime_error("augmented Hessian: eigensolver did not converge");
674 auto eval = s.eigenvalues();
675 Eigen::Index imax = 0;
676 for (Eigen::Index i = 0; i < eval.size(); i++)
677 if (eval(i).real() < eval(imax).real())
678 imax = i;
679 eigenvalues[root] = eval(imax);
680 const Eigen::Vector<value_type, Eigen::Dynamic> evec = augmented.transformation * s.eigenvectors().col(imax);
681 const value_type scale = value_type(augmented_hessian) * evec(nX);
682 for (Eigen::Index k = 0; k < nX; k++)
683 solution[k + nX * root] = evec(k) / scale;
684 } else {
685 Eigen::GeneralizedEigenSolver<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>> s(subspaceMatrix,
686 subspaceOverlap);
687 auto eval = s.eigenvalues();
688 auto evec = s.eigenvectors();
689 Eigen::Index imax = 0;
690 for (Eigen::Index i = 0; i < nX + 1; i++)
691 if (eval(i).real() < eval(imax).real())
692 imax = i;
693 eigenvalues[root] = eval(imax).real();
694 auto Solution = evec.col(imax).real().head(nX) / (augmented_hessian * evec.real()(nX, imax));
695 for (auto k = 0; k < nX; k++)
696 solution[k + nX * root] = Solution(k);
697 // std::cout << "subspace augmented hessian solution\n"<<Solution<<std::endl;
698 }
699 }
700 } else { // straight solution of linear equations
701 Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> subspaceMatrixR(
702 matrix.data(), nX, nX);
703 Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> RHS_R(rhs.data(), nX,
704 nroot);
705 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> subspaceMatrix = subspaceMatrixR;
706 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> RHS = RHS_R;
707 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> Solution;
708// std::cout << "solve_LinearEquations RHS_R\n"<<RHS_R<<std::endl;
709// for (size_t i=0; i<RHS_R.cols()*RHS_R.rows(); ++i)
710// std::cout << " "<<RHS_R.data()[i];
711// std::cout << std::endl;
712// std::cout << "solve_LinearEquations RHS\n"<<RHS<<std::endl;
713// for (size_t i=0; i<RHS.cols()*RHS.rows(); ++i)
714// std::cout << " "<<RHS.data()[i];
715// std::cout << std::endl;
716 Solution = subspaceMatrix.householderQr().solve(RHS);
717 // std::cout << "subspace linear equations solution\n"<<Solution<<std::endl;
718 for (size_t root = 0; root < nroot; root++)
719 for (auto k = 0; k < nX; k++)
720 solution[k + nX * root] = Solution(k, root);
721 }
722}
723
724} // namespace detail
725
726template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
727void solve_LinearEquations(std::vector<value_type>& solution, std::vector<value_type>& eigenvalues,
728 const std::vector<value_type>& matrix, const std::vector<value_type>& metric,
729 const std::vector<value_type>& rhs, const size_t dimension, size_t nroot,
730 real_type_t<value_type> augmented_hessian, real_type_t<value_type> svdThreshold,
731 int verbosity) {
732 detail::solve_LinearEquations_impl<value_type>(solution, eigenvalues, matrix, metric, rhs, dimension, nroot,
733 augmented_hessian, svdThreshold, verbosity);
734}
735
736template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
737void solve_LinearEquations(std::vector<value_type>& solution, std::vector<value_type>& eigenvalues,
738 const std::vector<value_type>& matrix, const std::vector<value_type>& metric,
739 const std::vector<value_type>& rhs, const size_t dimension, size_t nroot,
740 real_type_t<value_type> augmented_hessian, real_type_t<value_type> svdThreshold,
741 int verbosity) {
742 detail::solve_LinearEquations_impl<value_type>(solution, eigenvalues, matrix, metric, rhs, dimension, nroot,
743 augmented_hessian, svdThreshold, verbosity);
744}
745
746namespace detail {
747
754template <typename value_type>
755void solve_DIIS_impl(std::vector<value_type>& solution, const std::vector<value_type>& matrix, const size_t dimension,
756 real_type_t<value_type> svdThreshold, int verbosity) {
757 // let ADL pick up the overloads of extended- and arbitrary-precision scalar types
758 using std::abs;
759 using std::isnan;
760 using VectorT = Eigen::Vector<value_type, Eigen::Dynamic>;
761 using MatrixT = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>;
762 auto nAug = dimension + 1;
763 // auto nQ = dimension - 1;
764 solution.resize(dimension);
765 // if (nQ > 0) {
766 VectorT Rhs(nAug), Coeffs(nAug);
767 MatrixT BAug(nAug, nAug);
768 // Eigen::Matrix<value_type, Eigen::Dynamic, 1> Rhs(nQ), Coeffs(nQ);
769 // Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> B(nQ, nQ);
770 //
771 Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>> subspaceMatrix(matrix.data(), dimension,
772 dimension);
773 BAug.block(0, 0, dimension, dimension) = subspaceMatrix;
774 for (size_t i = 0; i < dimension; ++i) {
775 BAug(dimension, i) = BAug(i, dimension) = -1;
776 Rhs(i) = 0;
777 }
778 BAug(dimension, dimension) = 0;
779 Rhs(dimension) = -1;
780 //
781 // molpro::cout << "BAug:" << std::endl << BAug << std::endl;
782 // molpro::cout << "Rhs:" << std::endl << Rhs << std::endl;
783
784 // invert the system, determine extrapolation coefficients.
785 Eigen::JacobiSVD<MatrixT> svd(BAug, Eigen::ComputeThinU | Eigen::ComputeThinV);
786
787 // std::cout << "svd thresholds " << svdThreshold << "," << svd.singularValues().maxCoeff() << std::endl;
788 // std::cout << "singular values " << svd.singularValues().transpose() << std::endl;
789 svd.setThreshold(svdThreshold * svd.singularValues().maxCoeff() * 0);
790 // molpro::cout << "svdThreshold "<<svdThreshold<<std::endl;
791 // molpro::cout << "U\n"<<svd.matrixU()<<std::endl;
792 // molpro::cout << "V\n"<<svd.matrixV()<<std::endl;
793 // molpro::cout << "singularValues\n"<<svd.singularValues()<<std::endl;
794 Coeffs = svd.solve(Rhs).head(dimension);
795 // Coeffs = BAug.fullPivHouseholderQr().solve(Rhs);
796 // molpro::cout << "Coeffs "<<Coeffs.transpose()<<std::endl;
797 if (verbosity > 1)
798 molpro::cout << "Combination of iteration vectors: " << Coeffs.transpose() << std::endl;
799 for (size_t k = 0; k < (size_t)Coeffs.rows(); k++) {
800 if (isnan(abs(Coeffs(k)))) {
801 molpro::cout << "B:" << std::endl << BAug << std::endl;
802 molpro::cout << "Rhs:" << std::endl << Rhs << std::endl;
803 molpro::cout << "Combination of iteration vectors: " << Coeffs.transpose() << std::endl;
804 throw std::overflow_error("NaN detected in DIIS submatrix solution");
805 }
806 solution[k] = Coeffs(k);
807 }
808}
809
810} // namespace detail
811
812template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
813void solve_DIIS(std::vector<value_type>& solution, const std::vector<value_type>& matrix, const size_t dimension,
814 real_type_t<value_type> svdThreshold, int verbosity) {
815 detail::solve_DIIS_impl<value_type>(solution, matrix, dimension, svdThreshold, verbosity);
816}
817
818template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
819void solve_DIIS(std::vector<value_type>& solution, const std::vector<value_type>& matrix, const size_t dimension,
820 real_type_t<value_type> svdThreshold, int verbosity) {
821 detail::solve_DIIS_impl<value_type>(solution, matrix, dimension, svdThreshold, verbosity);
822}
823
824} // namespace molpro::linalg::itsolv
825
826
828
842template <typename value_type, typename value_type_abs>
843auto redundant_parameters(const subspace::Matrix<value_type>& overlap, const size_t oR, const size_t nR,
844 const value_type_abs svd_thresh, Logger& logger) {
845 auto prof = molpro::Profiler::single();
846 prof->start("itsolv::svd_system");
847 logger.trace("redundant_parameters()");
848 auto redundant_params = std::vector<int>{};
849 auto rspace_indices = std::vector<int>(nR);
850 std::iota(std::begin(rspace_indices), std::end(rspace_indices), 0);
851 auto svd = svd_system(overlap.rows(), overlap.cols(),
852 array::Span(const_cast<value_type*>(overlap.data().data()), overlap.size()), svd_thresh, true);
853 prof->stop();
854 prof->start("find redundant parameters");
855 for (const auto& singular_system : svd) {
856 if (!rspace_indices.empty()) {
857 auto rspace_contribution = std::vector<value_type_abs>{};
858 for (auto i : rspace_indices)
859 rspace_contribution.push_back(std::abs(singular_system.v.at(oR + i)));
860 auto it_min = std::max_element(std::begin(rspace_contribution), std::end(rspace_contribution));
861 auto imin = std::distance(std::begin(rspace_contribution), it_min);
862 redundant_params.push_back(rspace_indices[imin]);
863 rspace_indices.erase(std::begin(rspace_indices) + imin);
864 std::stringstream ss;
865 ss << std::setprecision(3) << "redundant parameter found, i = " << redundant_params.back()
866 << ", svd.value = " << singular_system.value
867 << ", svd.v[i] = " << singular_system.v[oR + redundant_params.back()];
868 logger.info(ss.str());
869 }
870 }
871 prof->stop();
872 return redundant_params;
873}
874
875}
876
877#endif // LINEARALGEBRA_SRC_MOLPRO_LINALG_ITERATIVESOLVER_HELPER_IMPLEMENTATION_H_
Non-owning container taking a pointer to the data buffer and its size and exposing routines for itera...
Definition: Span.h:31
bool empty() const
Definition: Span.h:79
iterator begin()
Definition: Span.h:69
size_type size() const
Definition: Span.h:77
iterator end()
Definition: Span.h:73
iterator data()
Definition: Span.h:66
Definition: Logger.h:442
void info(std::string_view message, Ts &&...args) const
Definition: Logger.h:505
void trace(std::string_view message, Ts &&...args) const
Definition: Logger.h:495
static std::shared_ptr< Profiler > single()
Definition: helper-dispatch.h:140
void solve_DIIS_impl(std::vector< value_type > &solution, const std::vector< value_type > &matrix, const size_t dimension, real_type_t< value_type > svdThreshold, int verbosity)
Implementation of solve_DIIS(), shared by its real and complex overloads.
Definition: helper-implementation.h:755
std::list< SVD< value_type > > svd_system_impl(size_t nrows, size_t ncols, const array::Span< value_type > &m, real_type_t< value_type > threshold, bool hermitian, bool reduce_to_rank)
Implementation of svd_system(), shared by its real and complex overloads.
Definition: helper-implementation.h:222
void solve_LinearEquations_impl(std::vector< value_type > &solution, std::vector< value_type > &eigenvalues, const std::vector< value_type > &matrix, const std::vector< value_type > &metric, const std::vector< value_type > &rhs, const size_t dimension, size_t nroot, real_type_t< value_type > augmented_hessian, real_type_t< value_type > svdThreshold, int verbosity)
Implementation of solve_LinearEquations(), shared by its real and complex overloads.
Definition: helper-implementation.h:625
orthogonalised_subspace< value_type > orthogonalise_subspace(const std::vector< value_type > &matrix, const std::vector< value_type > &metric, size_t dimension, real_type_t< value_type > svdThreshold, int verbosity)
Reduce H C = S C E to the standard eigenproblem Hbar U = U E, whose solution is C = X U.
Definition: helper-implementation.h:325
auto redundant_parameters(const subspace::Matrix< value_type > &overlap, const size_t oR, const size_t nR, const value_type_abs svd_thresh, Logger &logger)
Deduces a set of parameters that are redundant due to linear dependencies.
Definition: helper-implementation.h:843
4-parameter interpolation of a 1-dimensional function given two points for which function values and ...
Definition: helper.h:14
void eigenproblem(std::vector< value_type > &eigenvectors, std::vector< value_type > &eigenvalues, const std::vector< value_type > &matrix, const std::vector< value_type > &metric, size_t dimension, bool hermitian, real_type_t< value_type > svdThreshold, int verbosity, std::vector< std::pair< std::size_t, value_type > > *imag_eval_parts=nullptr)
Definition: helper-implementation.h:377
std::list< SVD< value_type > > svd_eigen_bdcsvd(size_t nrows, size_t ncols, const array::Span< value_type > &m, real_type_t< value_type > threshold)
Definition: helper-implementation.h:57
void solve_LinearEquations(std::vector< value_type > &solution, std::vector< value_type > &eigenvalues, const std::vector< value_type > &matrix, const std::vector< value_type > &metric, const std::vector< value_type > &rhs, size_t dimension, size_t nroot, real_type_t< value_type > augmented_hessian, real_type_t< value_type > svdThreshold, int verbosity)
Definition: helper-implementation.h:727
int eigensolver_lapacke_dsyev(std::span< const double > matrix, std::span< double > eigenvectors, std::span< double > eigenvalues, const size_t dimension)
Eigen-decomposition of a real symmetric matrix in double precision.
Definition: helper-implementation.h:153
void solve_DIIS(std::vector< value_type > &solution, const std::vector< value_type > &matrix, size_t dimension, real_type_t< value_type > svdThreshold, int verbosity=0)
Definition: helper-implementation.h:813
std::list< SVD< value_type > > svd_eigen_jacobi(size_t nrows, size_t ncols, const array::Span< value_type > &m, real_type_t< value_type > threshold)
Definition: helper-implementation.h:27
std::list< SVD< value_type > > svd_system(size_t nrows, size_t ncols, const array::Span< value_type > &m, real_type_t< value_type > threshold, bool hermitian=false, bool reduce_to_rank=false)
Performs singular value decomposition and returns SVD objects for singular values less than threshold...
Definition: helper-implementation.h:262
size_t get_rank(std::span< const value_type > eigenvalues, value_type threshold)
Definition: helper-implementation.h:171
void printMatrix(const std::vector< value_type > &, size_t rows, size_t cols, std::string title="", std::ostream &s=molpro::cout)
Definition: helper-implementation.h:274
real_type_t< T > real_part(const T &x)
The real part of a scalar; the value itself for a real type.
Definition: scalar_traits.h:58
T conjugate(const T &x)
Complex conjugate, staying within the scalar type; the identity for a real type.
Definition: scalar_traits.h:47
typename real_type< T >::type real_type_t
The real type underlying T, i.e. T itself for a real type and U for std::complex<U>.
Definition: scalar_traits.h:43
Definition: scalar_traits.h:19
Stores a singular value and corresponding left and right singular vectors.
Definition: helper.h:27
value_type value
Definition: helper.h:29
Orders eigenvalues by non-descending real part, then by imaginary part.
Definition: helper-implementation.h:288
bool operator()(const std::complex< real_type > &lhs, const std::complex< real_type > &rhs) const
Definition: helper-implementation.h:289
A generalised eigenproblem reduced to a standard one by symmetric orthogonalisation of the metric.
Definition: helper-implementation.h:305
Eigen::Matrix< value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor > matrix_type
Definition: helper-implementation.h:306
matrix_type S
the metric, column-major
Definition: helper-implementation.h:307
int rank
the number of directions of the metric that were kept
Definition: helper-implementation.h:310
matrix_type transformation
X, of dimension x rank, with X^dagger S X = I.
Definition: helper-implementation.h:308
matrix_type Hbar
X^dagger H X, of rank x rank.
Definition: helper-implementation.h:309