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, value_type threshold) {
191 // compute max
192 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 (it->value > max_value) {
196 max_value = it->value;
197 }
198 }
199 // scale threshold
200 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 (it->value > threshold_scaled) {
206 rank += 1;
207 }
208 }
209 return rank;
210}
211
212template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
213std::list<SVD<value_type>> svd_system(size_t nrows, size_t ncols, const array::Span<value_type>& m,
214 real_type_t<value_type> threshold, bool hermitian, bool reduce_to_rank) {
215 std::list<SVD<value_type>> svds;
216 assert(m.size() == nrows * ncols);
217 if (m.empty())
218 return {};
219 if (hermitian) {
220 assert(nrows == ncols);
221 // dispatches to ?syev/?heev or to Eigen::SelfAdjointEigenSolver depending on the scalar type
222 svds = eigensolver_hermitian<value_type>(nrows, std::span<const value_type>{m.data(), m.size()});
223 for (auto s = svds.begin(); s != svds.end();)
224 if (s->value > threshold)
225 s = svds.erase(s);
226 else
227 ++s;
228 } else {
229 // The general (non-hermitian) decomposition goes through Eigen for every scalar type: the
230 // subspace matrices are small, and ?gesdd/?gesvd (svd_lapacke_gesdd/svd_lapacke_gesvd above)
231 // offer nothing here that would justify differing results between precisions.
232 svds = svd_eigen_jacobi<value_type>(nrows, ncols, m, threshold);
233 // return svd_eigen_bdcsvd<value_type>(nrows, ncols, m, threshold);
234 }
235
236 // reduce to rank
237 if (reduce_to_rank) {
238 int rank = get_rank(svds, threshold);
239 for (int i = ncols; i > rank; i--) {
240 svds.pop_back();
241 }
242 }
243 return svds;
244}
245
246template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
247std::list<SVD<value_type>> svd_system(size_t nrows, size_t ncols, const array::Span<value_type>& m,
248 real_type_t<value_type> threshold, bool hermitian, bool reduce_to_rank) {
249 assert(false); // Complex not implemented here
250 return {};
251}
252
253template <typename value_type>
254void printMatrix(const std::vector<value_type>& m, size_t rows, size_t cols, std::string title, std::ostream& s) {
255 s << title << "\n"
256 << Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>>(m.data(), rows, cols) << std::endl;
257}
258
259template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
260void eigenproblem(std::vector<value_type>& eigenvectors, std::vector<value_type>& eigenvalues,
261 const std::vector<value_type>& matrix, const std::vector<value_type>& metric, size_t dimension,
262 bool hermitian, real_type_t<value_type> svdThreshold, int verbosity) {
263 assert(false); // Complex not implemented here
264}
265
266template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
267void eigenproblem(std::vector<value_type>& eigenvectors, std::vector<value_type>& eigenvalues,
268 const std::vector<value_type>& matrix, const std::vector<value_type>& metric, size_t dimension,
269 bool hermitian, real_type_t<value_type> svdThreshold, int verbosity,
270 std::vector<std::pair<std::size_t, value_type>>* imag_eval_parts) {
271 // let ADL pick up the overloads of extended- and arbitrary-precision scalar types
272 using std::abs;
273 using std::sqrt;
274 // Tolerances calibrated for double precision, rescaled to the precision actually in use
275 const value_type zero_tol = precision_scaled<value_type>(1e-10); // a quantity that ought to vanish
276 const value_type null_eigenvalue_tol = precision_scaled<value_type>(1e-12); // an eigenvalue that vanishes
277 const value_type null_metric_eigenvalue_tol = precision_scaled<value_type>(1e-14); // a singular metric direction
278 using MatrixT = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
279 using ComplexMatrixT = Eigen::Matrix<std::complex<value_type>, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor>;
280 using MatrixRowMajT = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
281 using VectorT = Eigen::Vector<value_type, Eigen::Dynamic>;
282 using ComplexVectorT = Eigen::Vector<std::complex<value_type>, Eigen::Dynamic>;
283
284 auto prof = molpro::Profiler::single();
285 prof->start("itsolv::eigenproblem");
286 Eigen::Map<const MatrixRowMajT> HrowMajor(
287 matrix.data(), dimension, dimension);
288 MatrixT H(dimension, dimension);
289 H = HrowMajor;
290 Eigen::Map<const MatrixT> S(metric.data(), dimension, dimension);
291 ComplexMatrixT subspaceEigenvectors;
292 ComplexVectorT subspaceEigenvalues;
293
294 // initialisation of variables
295 VectorT metricEvals(dimension);
296 MatrixT metricEvecs(dimension, dimension);
297 int rank = 0;
298
299 // Perform an eigenvalue decomposition of the metric
300 // Note: Since the metric must necessarily be hermitian (and due to its real-valuedness in this
301 // function therefore symmetric), we can use the hermitian eigensolver for this
302 int success = eigensolver_hermitian<value_type>(std::span<const value_type>{metric.data(), metric.size()},
303 {metricEvecs.data(), dimension * dimension},
304 {metricEvals.data(), dimension}, dimension);
305 if (success != 0) {
306 throw std::runtime_error("Eigensolver did not converge");
307 }
308 rank = get_rank<value_type>(std::span<const value_type>{metricEvals.data(), dimension}, svdThreshold);
309
310 if (verbosity > 1 && rank < S.cols())
311 molpro::cout << "SVD rank " << rank << " in subspace of dimension " << S.cols() << std::endl;
312 if (verbosity > 2 && rank < S.cols())
313 molpro::cout << "singular values " << metricEvals.transpose() << std::endl;
314
315 // Transform H into a symmetrically orthogonalized basis via (S^{-1/2})^\dagger H S^{-1/2}
316 // taking into account the possibility of rank-deficiency of S (aka: zero SV)
317 // Note that since S is hermitian and positive (semi-)definite, its SVD is equal to its
318 // eigendecomposition
319 auto svmh = metricEvals.tail(rank);
320 for (auto k = 0; k < rank; k++) {
321 assert(abs(svmh(k)) <= svdThreshold || svmh(k) >= 0); // metric is supposed to be positive (semi-)definite
322 svmh(k) = svmh(k) > null_metric_eigenvalue_tol ? 1 / sqrt(svmh(k)) : 0;
323 }
324 auto Hbar =
325 svmh.asDiagonal() * metricEvecs.rightCols(rank).adjoint() * H * metricEvecs.rightCols(rank) * svmh.asDiagonal();
326
327 // Perform an eigendecomposition of the transformed matrix
328 Eigen::EigenSolver<MatrixT> s(Hbar);
329 subspaceEigenvalues = s.eigenvalues();
330 if (s.eigenvalues().imag().norm() < zero_tol) {
331 // real eigenvalues
332 subspaceEigenvalues = subspaceEigenvalues.real();
333 subspaceEigenvectors = s.eigenvectors();
334 // complex eigenvectors need to be rotated
335 // assume that they come in consecutive pairs
336 for (int i = 0; i < subspaceEigenvectors.cols() - 1; i++) {
337 if (subspaceEigenvectors.col(i).imag().norm() <= zero_tol) {
338 continue;
339 }
340
341 const int j = i + 1;
342 if (abs(subspaceEigenvalues(i) - subspaceEigenvalues(j)) >= zero_tol or
343 subspaceEigenvectors.col(j).imag().norm() <= zero_tol) {
344 continue;
345 }
346
347 // For a real-valued matrix, eigenvectors can always be chosen to be real. If we have a complex eigenvector,
348 // it's complex conjugate must also be an eigenvector with the same eigenvalue. We can combine these two
349 // vectors as either u + u^* = 2 Re(u) or i*(u - u^*) = -2 Im(u).
350 // In other words, the real and imaginary part of u are the corresponding real-valued eigenvectors.
351 subspaceEigenvectors.col(j) = subspaceEigenvectors.col(i).imag() / subspaceEigenvectors.col(i).imag().norm();
352 subspaceEigenvectors.col(i) = subspaceEigenvectors.col(i).real() / subspaceEigenvectors.col(i).real().norm();
353 }
354
355 // Convert eigenvectors back into original basis (minus singular dimensions)
356 subspaceEigenvectors = metricEvecs.rightCols(rank) * svmh.asDiagonal() * subspaceEigenvectors;
357 } else {
358 // complex eigenvalues
359#ifdef __INTEL_COMPILER
360 molpro::cout << "Hbar\n" << Hbar << std::endl;
361 molpro::cout << "Eigenvalues\n" << s.eigenvalues() << std::endl;
362 molpro::cout << "Eigenvectors\n" << s.eigenvectors() << std::endl;
363 throw std::runtime_error("Intel compiler does not support working with complex eigen3 entities properly");
364#endif
365
366 // Convert eigenvectors back into original basis (minus singular dimensions)
367 subspaceEigenvectors = metricEvecs.rightCols(rank) * svmh.asDiagonal() * s.eigenvectors();
368 }
369
370 // Determine order of eigenvalues such that they come in non-descending order of their real part
371 // (and non-descending order of imaginary part, in case of equal real parts)
372 Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> perm(subspaceEigenvalues.size());
373 perm.setIdentity();
374 std::ranges::sort(
375 perm.indices(),
376 [](const std::complex<value_type>& lhs, const std::complex<value_type>& rhs) {
377 if (lhs.real() != rhs.real()) {
378 return lhs.real() < rhs.real();
379 }
380
381 if (abs(lhs.imag()) != abs(rhs.imag())) {
382 // This fixes the order of distinct complex eigenvalue pairs that share the same real part
383 return abs(lhs.imag()) < abs(rhs.imag());
384 }
385
386 // This fixes the order within a complex eigenvalue pair
387 return lhs.imag() < rhs.imag();
388 },
389 [&subspaceEigenvalues](auto idx) { return subspaceEigenvalues[idx]; });
390
391 // Apply determined order to eigenvalues and -vectors
392 subspaceEigenvectors = subspaceEigenvectors * perm;
393 subspaceEigenvalues = perm.transpose() * subspaceEigenvalues;
394
395
396 // TODO: Need to address the case of near-zero eigenvalues (as below for non-hermitian case) and clean-up
397 // non-hermitian case
398
399 if (!hermitian) {
400 for (auto repeat = 0; repeat < 1; ++repeat)
401 for (Eigen::Index k = 0; k < subspaceEigenvectors.cols(); k++) {
402 if (abs(subspaceEigenvalues(k)) < null_eigenvalue_tol) {
403 // special case of zero eigenvalue -- make some real non-zero vector definitely in the null space
404 subspaceEigenvectors.col(k).real() += value_type(0.3256897) * subspaceEigenvectors.col(k).imag();
405 subspaceEigenvectors.col(k).imag().setZero();
406 }
407
408 auto ovl = subspaceEigenvectors.col(k).dot(S * subspaceEigenvectors.col(k));
409 // S is supposed to be positive (semi-)definite implying that ovl must be a non-negative real number
410 assert(abs(ovl.imag()) < zero_tol);
411 assert(ovl.real() > 0);
412 subspaceEigenvectors.col(k) /= sqrt(ovl.real());
413 }
414 }
415
416 // Fix indeterminate phase of eigenvectors by requiring the max component to be positive
417 for (std::size_t i = 0; i < subspaceEigenvectors.cols(); ++i) {
418 const auto &col = subspaceEigenvectors.col(i);
419 auto it = std::ranges::max_element(col, std::less<>{}, [](auto val) { return abs(val); });
420 auto idx = std::distance(col.begin(), it);
421 if (subspaceEigenvectors.col(i)[idx].real() < 0) {
422 subspaceEigenvectors.col(i) *= -1;
423 }
424 }
425
426 if (imag_eval_parts) {
427 // Complex eigenvalues are tolerable -> process them to be able to represent everything
428 // by real-valued vectors
429 imag_eval_parts->clear();
430
431 for (Eigen::Index root = 0; root < Hbar.cols(); ++root) {
432 if (subspaceEigenvalues(root).imag() == 0) {
433 continue;
434 }
435
436 // Complex-valued eigenvalues must appear as complex conjugate pairs
437 assert(root + 1 < subspaceEigenvalues.size());
438 assert(abs(std::conj(subspaceEigenvalues(root)) - subspaceEigenvalues(root + 1)) < zero_tol);
439
440 imag_eval_parts->emplace_back(root, subspaceEigenvalues(root).imag());
441 imag_eval_parts->emplace_back(root + 1, -subspaceEigenvalues(root).imag());
442
443 // Set the eigenvalue pair to their real-part only (imaginary part is tracked separately in imag_eval_parts)
444 subspaceEigenvalues(root) = subspaceEigenvalues(root + 1) = subspaceEigenvalues(root).real();
445
446 // Pretend the real and imaginary part were separate eigenvectors (this is required in order
447 // to represent all data without the need for using complex numbers).
448 // However, as the eigenvalues are not degenerate, the real and imaginary parts of the eigenvectors
449 // are in fact NOT eigenvectors themselves.
450 // If the true eigenvectors are required, they can easily be recovered from the real and imaginary
451 // parts we store here.
452 subspaceEigenvectors.col(root + 1) = subspaceEigenvectors.col(root).imag();
453 subspaceEigenvectors.col(root) = subspaceEigenvectors.col(root).real();
454
455 // Skip the second eigenvalue in the pair of complex conjugate eigenvalues
456 ++root;
457 }
458 }
459
460 if ((subspaceEigenvectors - subspaceEigenvectors.real()).norm() > zero_tol or
461 (subspaceEigenvalues - subspaceEigenvalues.real()).norm() > zero_tol) {
462 throw std::runtime_error("unexpected complex solution found");
463 }
464
465 eigenvectors.resize(dimension * Hbar.cols());
466 eigenvalues.resize(Hbar.cols());
467
468 Eigen::Map<MatrixT>(eigenvectors.data(), dimension, Hbar.cols()) =
469 subspaceEigenvectors.real();
470 Eigen::Map<VectorT> ev(eigenvalues.data(), Hbar.cols());
471 ev = subspaceEigenvalues.real();
472
473 prof->stop();
474}
475
476template <typename value_type, typename std::enable_if_t<is_complex<value_type>{}, int>>
477void solve_LinearEquations(std::vector<value_type>& solution, std::vector<value_type>& eigenvalues,
478 const std::vector<value_type>& matrix, const std::vector<value_type>& metric,
479 const std::vector<value_type>& rhs, const size_t dimension, size_t nroot,
480 real_type_t<value_type> augmented_hessian, real_type_t<value_type> svdThreshold,
481 int verbosity) {
482 assert(false); // Complex not implemented here
483}
484
485template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
486void solve_LinearEquations(std::vector<value_type>& solution, std::vector<value_type>& eigenvalues,
487 const std::vector<value_type>& matrix, const std::vector<value_type>& metric,
488 const std::vector<value_type>& rhs, const size_t dimension, size_t nroot,
489 real_type_t<value_type> augmented_hessian, real_type_t<value_type> svdThreshold,
490 int verbosity) {
491 const Eigen::Index nX = dimension;
492 solution.resize(nX * nroot);
493 // std::cout << "augmented_hessian "<<augmented_hessian<<std::endl;
494 if (augmented_hessian > 0) { // Augmented hessian
495 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> subspaceMatrix;
496 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> subspaceOverlap;
497 subspaceMatrix.conservativeResize(nX + 1, nX + 1);
498 subspaceOverlap.conservativeResize(nX + 1, nX + 1);
499 // both arrive row-major, as subspace::Matrix stores them and as the straight-solve
500 // branch below already reads them
501 using row_major_type = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
502 subspaceMatrix.block(0, 0, nX, nX) = Eigen::Map<const row_major_type>(matrix.data(), nX, nX);
503 subspaceOverlap.block(0, 0, nX, nX) = Eigen::Map<const row_major_type>(metric.data(), nX, nX);
504 eigenvalues.resize(nroot);
505 for (size_t root = 0; root < nroot; root++) {
506 for (Eigen::Index i = 0; i < nX; i++) {
507 subspaceMatrix(i, nX) = subspaceMatrix(nX, i) = -augmented_hessian * rhs[i * nroot + root];
508 subspaceOverlap(i, nX) = subspaceOverlap(nX, i) = 0;
509 }
510 subspaceMatrix(nX, nX) = 0;
511 subspaceOverlap(nX, nX) = 1;
512 // std::cout << "subspace augmented hessian subspaceMatrix\n"<<subspaceMatrix<<std::endl;
513 // std::cout << "subspace augmented hessian subspaceOverlap\n"<<subspaceOverlap<<std::endl;
514
515 Eigen::GeneralizedEigenSolver<Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>> s(subspaceMatrix,
516 subspaceOverlap);
517 auto eval = s.eigenvalues();
518 auto evec = s.eigenvectors();
519 Eigen::Index imax = 0;
520 for (Eigen::Index i = 0; i < nX + 1; i++)
521 if (eval(i).real() < eval(imax).real())
522 imax = i;
523 eigenvalues[root] = eval(imax).real();
524 auto Solution = evec.col(imax).real().head(nX) / (augmented_hessian * evec.real()(nX, imax));
525 for (auto k = 0; k < nX; k++)
526 solution[k + nX * root] = Solution(k);
527 // std::cout << "subspace augmented hessian solution\n"<<Solution<<std::endl;
528 }
529 } else { // straight solution of linear equations
530 Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> subspaceMatrixR(
531 matrix.data(), nX, nX);
532 Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>> RHS_R(rhs.data(), nX,
533 nroot);
534 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> subspaceMatrix = subspaceMatrixR;
535 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> RHS = RHS_R;
536 Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> Solution;
537// std::cout << "solve_LinearEquations RHS_R\n"<<RHS_R<<std::endl;
538// for (size_t i=0; i<RHS_R.cols()*RHS_R.rows(); ++i)
539// std::cout << " "<<RHS_R.data()[i];
540// std::cout << std::endl;
541// std::cout << "solve_LinearEquations RHS\n"<<RHS<<std::endl;
542// for (size_t i=0; i<RHS.cols()*RHS.rows(); ++i)
543// std::cout << " "<<RHS.data()[i];
544// std::cout << std::endl;
545 Solution = subspaceMatrix.householderQr().solve(RHS);
546 // std::cout << "subspace linear equations solution\n"<<Solution<<std::endl;
547 for (size_t root = 0; root < nroot; root++)
548 for (auto k = 0; k < nX; k++)
549 solution[k + nX * root] = Solution(k, root);
550 }
551}
552
553template <typename value_type, typename std::enable_if_t<!is_complex<value_type>{}, std::nullptr_t>>
554void solve_DIIS(std::vector<value_type>& solution, const std::vector<value_type>& matrix, const size_t dimension,
555 real_type_t<value_type> svdThreshold, int verbosity) {
556 // let ADL pick up the overloads of extended- and arbitrary-precision scalar types
557 using std::abs;
558 using std::isnan;
559 using VectorT = Eigen::Vector<value_type, Eigen::Dynamic>;
560 using MatrixT = Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>;
561 auto nAug = dimension + 1;
562 // auto nQ = dimension - 1;
563 solution.resize(dimension);
564 // if (nQ > 0) {
565 VectorT Rhs(nAug), Coeffs(nAug);
566 MatrixT BAug(nAug, nAug);
567 // Eigen::Matrix<value_type, Eigen::Dynamic, 1> Rhs(nQ), Coeffs(nQ);
568 // Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic> B(nQ, nQ);
569 //
570 Eigen::Map<const Eigen::Matrix<value_type, Eigen::Dynamic, Eigen::Dynamic>> subspaceMatrix(matrix.data(), dimension,
571 dimension);
572 BAug.block(0, 0, dimension, dimension) = subspaceMatrix;
573 for (size_t i = 0; i < dimension; ++i) {
574 BAug(dimension, i) = BAug(i, dimension) = -1;
575 Rhs(i) = 0;
576 }
577 BAug(dimension, dimension) = 0;
578 Rhs(dimension) = -1;
579 //
580 // molpro::cout << "BAug:" << std::endl << BAug << std::endl;
581 // molpro::cout << "Rhs:" << std::endl << Rhs << std::endl;
582
583 // invert the system, determine extrapolation coefficients.
584 Eigen::JacobiSVD<MatrixT> svd(BAug, Eigen::ComputeThinU | Eigen::ComputeThinV);
585
586 // std::cout << "svd thresholds " << svdThreshold << "," << svd.singularValues().maxCoeff() << std::endl;
587 // std::cout << "singular values " << svd.singularValues().transpose() << std::endl;
588 svd.setThreshold(svdThreshold * svd.singularValues().maxCoeff() * 0);
589 // molpro::cout << "svdThreshold "<<svdThreshold<<std::endl;
590 // molpro::cout << "U\n"<<svd.matrixU()<<std::endl;
591 // molpro::cout << "V\n"<<svd.matrixV()<<std::endl;
592 // molpro::cout << "singularValues\n"<<svd.singularValues()<<std::endl;
593 Coeffs = svd.solve(Rhs).head(dimension);
594 // Coeffs = BAug.fullPivHouseholderQr().solve(Rhs);
595 // molpro::cout << "Coeffs "<<Coeffs.transpose()<<std::endl;
596 if (verbosity > 1)
597 molpro::cout << "Combination of iteration vectors: " << Coeffs.transpose() << std::endl;
598 for (size_t k = 0; k < (size_t)Coeffs.rows(); k++) {
599 if (isnan(abs(Coeffs(k)))) {
600 molpro::cout << "B:" << std::endl << BAug << std::endl;
601 molpro::cout << "Rhs:" << std::endl << Rhs << std::endl;
602 molpro::cout << "Combination of iteration vectors: " << Coeffs.transpose() << std::endl;
603 throw std::overflow_error("NaN detected in DIIS submatrix solution");
604 }
605 solution[k] = Coeffs(k);
606 }
607}
608} // namespace molpro::linalg::itsolv
609
610
612
626template <typename value_type, typename value_type_abs>
627auto redundant_parameters(const subspace::Matrix<value_type>& overlap, const size_t oR, const size_t nR,
628 const value_type_abs svd_thresh, Logger& logger) {
629 auto prof = molpro::Profiler::single();
630 prof->start("itsolv::svd_system");
631 logger.trace("redundant_parameters()");
632 auto redundant_params = std::vector<int>{};
633 auto rspace_indices = std::vector<int>(nR);
634 std::iota(std::begin(rspace_indices), std::end(rspace_indices), 0);
635 auto svd = svd_system(overlap.rows(), overlap.cols(),
636 array::Span(const_cast<value_type*>(overlap.data().data()), overlap.size()), svd_thresh, true);
637 prof->stop();
638 prof->start("find redundant parameters");
639 for (const auto& singular_system : svd) {
640 if (!rspace_indices.empty()) {
641 auto rspace_contribution = std::vector<value_type_abs>{};
642 for (auto i : rspace_indices)
643 rspace_contribution.push_back(std::abs(singular_system.v.at(oR + i)));
644 auto it_min = std::max_element(std::begin(rspace_contribution), std::end(rspace_contribution));
645 auto imin = std::distance(std::begin(rspace_contribution), it_min);
646 redundant_params.push_back(rspace_indices[imin]);
647 rspace_indices.erase(std::begin(rspace_indices) + imin);
648 std::stringstream ss;
649 ss << std::setprecision(3) << "redundant parameter found, i = " << redundant_params.back()
650 << ", svd.value = " << singular_system.value
651 << ", svd.v[i] = " << singular_system.v[oR + redundant_params.back()];
652 logger.info(ss.str());
653 }
654 }
655 prof->stop();
656 return redundant_params;
657}
658
659}
660
661#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
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:627
4-parameter interpolation of a 1-dimensional function given two points for which function values and ...
Definition: helper.h:14
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:477
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)
Definition: helper-implementation.h:260
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
template size_t get_rank< value_type >(std::span< const value_type > eigenvalues, value_type threshold)
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:554
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:213
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:254
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
Stores a singular value and corresponding left and right singular vectors.
Definition: helper.h:24
value_type value
Definition: helper.h:26