pairinteraction
A Rydberg Interaction Calculator
DiagonalizerInterface.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2024 Pairinteraction Developers
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
5
9
10#include <Eigen/Dense>
11#include <Eigen/SparseCore>
12#include <optional>
13#include <spdlog/spdlog.h>
14
15namespace pairinteraction {
16template <typename Scalar>
18 : float_type(float_type) {}
19
20template <typename Scalar>
21template <typename ScalarLim>
24 double rtol) const {
25 using real_lim_t = typename traits::NumTraits<ScalarLim>::real_t;
26 int dim = matrix.rows();
27
28 assert(rtol > 0);
29 assert(rtol < 1);
30
31 // Subtract the mean of the diagonal elements from the diagonal
32 shift = matrix.diagonal().real().mean();
33 Eigen::MatrixX<ScalarLim> shifted_matrix =
34 (matrix - shift * Eigen::MatrixX<Scalar>::Identity(dim, dim)).template cast<ScalarLim>();
35
36 // Ensure the accuracy the results
37 double floating_point_error = 5 * std::numeric_limits<real_lim_t>::epsilon();
38
39 if (floating_point_error > rtol) {
40 SPDLOG_WARN(
41 "Because the floating point precision is too low, the eigenvalues cannot be calculated "
42 "accurately. The estimated floating point error ({} * ||H||) is larger than the "
43 "specified tolerance ({} * ||H||). Try to use a 'float_type' with higher precision or "
44 "a larger 'rtol'.",
45 floating_point_error, rtol);
46 }
47
48 // Note that this check is not as handwavy as it seems at first glance:
49 //
50 // Let us first analyze the error of the eigenvalues due to the limited floating point
51 // precision. Under the assumption that the diagonalization routine is backward stable and the
52 // we know that the calculated eigenvalues and eigenvectors correspond to the diagonalization of
53 // a matrix H+E, where H is the original matrix and E a small perturbation with
54 // ||E|| <= c*eps*||H||. Here, for an hermitian matrix, c is a modest constant and eps is the
55 // machine epsilon. Taking this result, we can use the Bauer-Fike theorem, simplified for an
56 // hermitian matrix H, to bound the error of the eigenvalues:
57 //
58 // |lambda - lambda_exact| <= c*eps*||H||,
59 //
60 // where lambda is the calculated eigenvalue and lambda_exact is the closest exact eigenvalue of
61 // H. Thus, to ensure that the relative tolerance rtol can be met, we need c*eps*||H|| <=
62 // rtol*||H||, which we check in the code above.
63 //
64 // If we know that for a user, it is permissible that |lambda - lambda_exact| ~ rtol*||H||, we
65 // assume that the eigenvectors are allowed to have an error as well. To obtain a reasonable
66 // bound for the strength of this error, we demand that the error of an eigenvalue, which would
67 // result from calculating the eigenvalue by transforming the Hamiltonian with the
68 // eigenvector v, should be smaller than rtol * ||H||. For v = v_exact + w, we obtain:
69 //
70 // |v^dag * H * v - lambda_exact| <= 2*||w||*||H||
71 //
72 // Thus, to keep this error below rtol * ||H||, we need ||w|| <= rtol/2.
73 //
74 // In the diagonalizer classes derived from the DiagonalizerInterface, we will make use of the
75 // fact that a small error of the eigenvectors is tolerable to increase the sparsity of the
76 // eigenvector matrix by setting every entry smaller than rtol/(2*sqrt(dim(H))) to zero.
77 // The factor 1/sqrt(dim(H)) ensures that the bound is met also if multiple entries are
78 // set to zero.
79
80 return shifted_matrix;
81}
82
83template <typename Scalar>
84template <typename RealLim>
87 real_t shift) const {
88 // Add the mean of the diagonal elements back to the eigenvalues
89 Eigen::VectorX<real_t> eigenvalues = shifted_eigenvalues.template cast<real_t>();
90 eigenvalues.array() += shift;
91 return eigenvalues;
92}
93
94template <typename Scalar>
96DiagonalizerInterface<Scalar>::eigh(const Eigen::SparseMatrix<Scalar, Eigen::RowMajor> &matrix,
97 std::optional<real_t> min_eigenvalue,
98 std::optional<real_t> max_eigenvalue, double rtol) const {
99 int dim = matrix.rows();
100
101 auto eigensys = eigh(matrix, rtol);
102
103 auto *it_begin =
104 std::lower_bound(eigensys.eigenvalues.data(), eigensys.eigenvalues.data() + dim,
105 min_eigenvalue.value_or(std::numeric_limits<real_t>::lowest() / 2));
106 auto *it_end =
107 std::upper_bound(eigensys.eigenvalues.data(), eigensys.eigenvalues.data() + dim,
108 max_eigenvalue.value_or(std::numeric_limits<real_t>::max() / 2));
109 eigensys.eigenvectors = eigensys.eigenvectors
110 .block(0, std::distance(eigensys.eigenvalues.data(), it_begin), dim,
111 std::distance(it_begin, it_end))
112 .eval();
113 eigensys.eigenvalues = eigensys.eigenvalues
114 .segment(std::distance(eigensys.eigenvalues.data(), it_begin),
115 std::distance(it_begin, it_end))
116 .eval();
117
118 return eigensys;
119}
120
121// Explicit instantiations
122template class DiagonalizerInterface<double>;
124
127 double rtol) const;
130 const Eigen::MatrixX<std::complex<double>> &matrix, double &shift, double rtol) const;
131
134 double shift) const;
136 const Eigen::VectorX<float> &shifted_eigenvalues, double shift) const;
137
140 double rtol) const;
143 const Eigen::MatrixX<std::complex<double>> &matrix, double &shift, double rtol) const;
144
147 double shift) const;
149 const Eigen::VectorX<double> &shifted_eigenvalues, double shift) const;
150} // namespace pairinteraction
Eigen::MatrixX< ScalarLim > subtract_mean(const Eigen::MatrixX< Scalar > &matrix, real_t &shift, double rtol) const
typename traits::NumTraits< Scalar >::real_t real_t
virtual EigenSystemH< Scalar > eigh(const Eigen::SparseMatrix< Scalar, Eigen::RowMajor > &matrix, double rtol) const =0
Eigen::VectorX< real_t > add_mean(const Eigen::VectorX< RealLim > &eigenvalues, real_t shift) const
Matrix< Type, Dynamic, Dynamic > MatrixX
Matrix< Type, Dynamic, 1 > VectorX