pairinteraction
A Rydberg Interaction Calculator
SystemAtom.test.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
16
17#include <Eigen/Eigenvalues>
18#include <doctest/doctest.h>
19#include <fmt/ranges.h>
20
21namespace pairinteraction {
22
23constexpr double VOLT_PER_CM_IN_ATOMIC_UNITS = 1 / 5.14220675112e9;
24
25DOCTEST_TEST_CASE("construct and diagonalize a small Hamiltonian") {
26 auto &database = Database::get_global_instance();
27 auto diagonalizer = DiagonalizerEigen<double>();
28
29 auto ket1 = KetAtomCreator()
30 .set_species("Rb")
35 .create(database);
36 auto ket2 = KetAtomCreator()
37 .set_species("Rb")
42 .create(database);
43 auto basis = BasisAtomCreator<double>().append_ket(ket1).append_ket(ket2).create(database);
44
45 auto system = SystemAtom<double>(basis);
46 system.set_electric_field({0, 0, 0.0001});
47
48 Eigen::MatrixXd tmp = Eigen::MatrixXd(1e5 * system.get_matrix()).array().round() / 1e5;
49 std::vector<double> matrix_vector(tmp.data(), tmp.data() + tmp.size());
50 DOCTEST_MESSAGE(fmt::format("Constructed: {}", fmt::join(matrix_vector, ", ")));
51
52 system.diagonalize(diagonalizer);
53 tmp = Eigen::MatrixXd(1e5 * system.get_matrix()).array().round() / 1e5;
54 matrix_vector = std::vector<double>(tmp.data(), tmp.data() + tmp.size());
55 DOCTEST_MESSAGE(fmt::format("Diagonalized: {}", fmt::join(matrix_vector, ", ")));
56
57 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigensolver;
58 eigensolver.compute(system.get_matrix());
59 auto eigenenergies_eigen = eigensolver.eigenvalues();
60 auto eigenenergies_pairinteraction = system.get_eigenenergies();
61 for (int i = 0; i < eigenenergies_eigen.size(); ++i) {
62 DOCTEST_CHECK(std::abs(eigenenergies_eigen(i) - eigenenergies_pairinteraction(i)) < 1e-10);
63 }
64}
65
66DOCTEST_TEST_CASE("construct and diagonalize two Hamiltonians in parallel") {
67 auto &database = Database::get_global_instance();
68 auto diagonalizer = DiagonalizerEigen<std::complex<double>>();
69
71 .set_species("Rb")
73 .restrict_quantum_number_l(0, 1)
74 .create(database);
75
76 auto system1 = SystemAtom<std::complex<double>>(basis);
77 system1.set_electric_field({0, 0, 0.0001});
78
79 auto system2 = SystemAtom<std::complex<double>>(basis);
80 system2.set_electric_field({0, 0, 0.0002});
81
82 diagonalize<SystemAtom<std::complex<double>>>({system1, system2}, diagonalizer);
83
84 auto matrix1 = system1.get_matrix();
85 auto matrix2 = system2.get_matrix();
86 for (int i = 0; i < matrix1.rows(); ++i) {
87 for (int j = 0; j < matrix1.cols(); ++j) {
88 if (i != j) {
89 DOCTEST_CHECK(std::abs(matrix1.coeff(i, j)) < 1e-10);
90 DOCTEST_CHECK(std::abs(matrix2.coeff(i, j)) < 1e-10);
91 }
92 }
93 }
94}
95
96DOCTEST_TEST_CASE("construct and diagonalize multiple Hamiltonians in parallel" *
97 doctest::skip(true)) {
98 // TODO For a slow database, the fast parallelized construction of the tiny Hamiltonians seems
99 // to lead to the wrong Hamiltonians (visible in the warning "The floating point error (5e-324
100 // Hartree) is similar or larger than error estimated from the specified tolerance (0
101 // Hartree)."). This could be caused by an issue with thread safety or memory access.
102 int n = 10;
103
104 auto &database = Database::get_global_instance();
105 auto diagonalizer = DiagonalizerEigen<std::complex<double>>();
106
108 .set_species("Sr87_mqdt")
110 .restrict_quantum_number_l(0, 1)
111 .create(database);
112
113 std::vector<SystemAtom<std::complex<double>>> systems;
114 systems.reserve(n);
115 for (int i = 0; i < n; ++i) {
116 auto system = SystemAtom<std::complex<double>>(basis);
117 system.set_electric_field({0, 0, 0.0001 * i});
118 systems.push_back(std::move(system));
119 }
120
121 DOCTEST_MESSAGE("Basis size: ", basis->get_number_of_states());
122
123 diagonalize<SystemAtom<std::complex<double>>>(systems, diagonalizer);
124}
125
126DOCTEST_TEST_CASE("construct and diagonalize a Hamiltonian using different methods") {
127 auto &database = Database::get_global_instance();
128
130 .set_species("Rb")
132 .restrict_quantum_number_l(0, 1)
133 .create(database);
134
135 // Diagonalize using the Eigen library
136 auto system = SystemAtom<std::complex<double>>(basis);
139
140 Eigen::MatrixXcd matrix = system.get_matrix();
141 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXcd> eigensolver;
142 eigensolver.compute(matrix);
143 auto eigenenergies_eigen = eigensolver.eigenvalues();
144
145 // Create diagonalizers
146 std::vector<std::unique_ptr<DiagonalizerInterface<std::complex<double>>>> diagonalizers;
147 std::vector<double> rtols;
148 double eps{};
149 DOCTEST_SUBCASE("Double precision") {
150 diagonalizers.push_back(std::make_unique<DiagonalizerEigen<std::complex<double>>>());
151#ifdef WITH_LAPACKE
152 diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<std::complex<double>>>());
153 diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvr<std::complex<double>>>());
154#endif
155#ifdef WITH_MKL
156 diagonalizers.push_back(std::make_unique<DiagonalizerFeast<std::complex<double>>>(300));
157#endif
158 rtols = {1e-1, 1e-6, 1e-14};
159 eps = std::numeric_limits<double>::epsilon();
160 }
161
162 DOCTEST_SUBCASE("Single precision") {
163 diagonalizers.push_back(
164 std::make_unique<DiagonalizerEigen<std::complex<double>>>(FloatType::FLOAT32));
165#ifdef WITH_LAPACKE
166 diagonalizers.push_back(
167 std::make_unique<DiagonalizerLapackeEvd<std::complex<double>>>(FloatType::FLOAT32));
168 diagonalizers.push_back(
169 std::make_unique<DiagonalizerLapackeEvr<std::complex<double>>>(FloatType::FLOAT32));
170#endif
171#ifdef WITH_MKL
172 diagonalizers.push_back(
173 std::make_unique<DiagonalizerFeast<std::complex<double>>>(300, FloatType::FLOAT32));
174#endif
175 rtols = {1e-1, 1e-6};
176 eps = std::numeric_limits<float>::epsilon();
177 }
178
179 // Diagonalize using pairinteraction
180 for (double rtol_eigenenergies : rtols) {
181 double atol_eigenvectors =
182 std::max(0.5 * rtol_eigenenergies / std::sqrt(basis->get_number_of_states()), 4 * eps);
183 DOCTEST_MESSAGE("Precision: " << rtol_eigenenergies << " (rtol eigenenergies), "
184 << atol_eigenvectors << " (atol eigenvectors)");
185
186 for (const auto &diagonalizer : diagonalizers) {
187 auto system = SystemAtom<std::complex<double>>(basis);
191
192 // We specify a search interval because this is required if the FEAST routine is
193 // used. To avoid overflows, the interval ranges from half the smallest possible
194 // value to half the largest possible value.
195 system.diagonalize(*diagonalizer, std::numeric_limits<float>::lowest() / 2,
196 std::numeric_limits<float>::max() / 2, rtol_eigenenergies);
197 auto eigenenergies_pairinteraction = system.get_eigenenergies();
198 auto eigenvectors_pairinteraction = system.get_eigenbasis()->get_coefficients();
199
200 DOCTEST_CHECK(
201 (eigenenergies_eigen - eigenenergies_pairinteraction).array().abs().maxCoeff() <
202 rtol_eigenenergies * matrix.norm());
203
204 for (int i = 0; i < eigenvectors_pairinteraction.cols(); ++i) {
205 DOCTEST_CHECK(abs(1 - eigenvectors_pairinteraction.col(i).norm()) <
206 atol_eigenvectors * eigenvectors_pairinteraction.rows());
207 }
208 }
209 }
210}
211
212DOCTEST_TEST_CASE("construct and diagonalize a Hamiltonian with energy restrictions") {
213 double min_energy = 0.153355;
214 double max_energy = 0.153360;
215
216 auto &database = Database::get_global_instance();
217
218 auto basis = BasisAtomCreator<double>()
219 .set_species("Rb")
220 .restrict_quantum_number_n(58, 62)
221 .restrict_quantum_number_l(0, 1)
222 .create(database);
223
224 // Diagonalize using the Eigen library
225 auto system = SystemAtom<double>(basis);
226 system.set_electric_field(
228
229 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigensolver;
230 eigensolver.compute(system.get_matrix());
231 auto eigenenergies_all = eigensolver.eigenvalues();
232 std::vector<double> eigenenergies_eigen;
233 for (int i = 0; i < eigenenergies_all.size(); ++i) {
234 if (eigenenergies_all[i] > min_energy && eigenenergies_all[i] < max_energy) {
235 eigenenergies_eigen.push_back(eigenenergies_all[i]);
236 }
237 }
238
239 // Create diagonalizer
240 std::vector<std::unique_ptr<DiagonalizerInterface<double>>> diagonalizers;
241 diagonalizers.push_back(std::make_unique<DiagonalizerEigen<double>>(FloatType::FLOAT64));
242#ifdef WITH_LAPACKE
243 diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<double>>(FloatType::FLOAT64));
244 diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvr<double>>(FloatType::FLOAT64));
245#endif
246#ifdef WITH_MKL
247 diagonalizers.push_back(std::make_unique<DiagonalizerFeast<double>>(10, FloatType::FLOAT64));
248#endif
249
250 // Diagonalize using pairinteraction
251 for (const auto &diagonalizer : diagonalizers) {
252 auto system = SystemAtom<double>(basis);
253 system.set_electric_field(
255
256 system.diagonalize(*diagonalizer, min_energy, max_energy, 1e-6);
257 auto eigenenergies_pairinteraction = system.get_eigenenergies();
258
259 Eigen::MatrixXd tmp = (1e5 * eigenenergies_pairinteraction).array().round() / 1e5;
260 std::vector<double> eigenenergies_vector(tmp.data(), tmp.data() + tmp.size());
261 DOCTEST_MESSAGE(fmt::format("Eigenenergies: {}", fmt::join(eigenenergies_vector, ", ")));
262
263 DOCTEST_CHECK(eigenenergies_eigen.size() == 8);
264 DOCTEST_CHECK(eigenenergies_pairinteraction.size() == 8);
265 for (size_t i = 0; i < eigenenergies_eigen.size(); ++i) {
266 DOCTEST_CHECK(std::abs(eigenenergies_eigen[i] - eigenenergies_pairinteraction[i]) <
267 1e-10);
268 }
269 }
270}
271
272#ifdef WITH_MKL
273#include <Eigen/Dense>
274#include <mkl.h>
275DOCTEST_TEST_CASE("diagonalization with mkl") {
276 // We loop several times to check for memory errors
277 for (size_t i = 0; i < 10; ++i) {
278 // Create a symmetric matrix
279 int n = 100;
280 Eigen::MatrixXd matrix = Eigen::MatrixXd::Random(n, n);
281 matrix = (matrix + matrix.transpose()).eval();
282 Eigen::VectorXd eigenenergies(n);
283
284 // Diagonalize the matrix
285 int info =
286 LAPACKE_dsyev(LAPACK_COL_MAJOR, 'V', 'U', n, matrix.data(), n, eigenenergies.data());
287 DOCTEST_CHECK(info == 0);
288 }
289}
290#endif
291
292DOCTEST_TEST_CASE("handle it gracefully if no eigenenergies are within energy restrictions") {
293 double min_energy = -1;
294 double max_energy = -1;
295
296 auto &database = Database::get_global_instance();
297
298 auto basis = BasisAtomCreator<double>()
299 .set_species("Rb")
300 .restrict_quantum_number_n(58, 62)
301 .restrict_quantum_number_l(0, 1)
302 .create(database);
303
304 std::vector<std::unique_ptr<DiagonalizerInterface<double>>> diagonalizers;
305 diagonalizers.push_back(std::make_unique<DiagonalizerEigen<double>>());
306#ifdef WITH_LAPACKE
307 diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<double>>());
308#endif
309
310 for (const auto &diagonalizer : diagonalizers) {
311 auto system = SystemAtom<double>(basis);
312 system.set_electric_field(
314
315 system.diagonalize(*diagonalizer, min_energy, max_energy, 1e-6);
316 auto eigenenergies_pairinteraction = system.get_eigenenergies();
317
318 DOCTEST_CHECK(eigenenergies_pairinteraction.size() == 0);
319 }
320}
321
322} // namespace pairinteraction
Builder class for creating BasisAtom objects.
BasisAtomCreator< Scalar > & append_ket(const std::shared_ptr< const ket_t > &ket)
BasisAtomCreator< Scalar > & set_species(const std::string &value)
BasisAtomCreator< Scalar > & restrict_quantum_number_n(int min, int max)
BasisAtomCreator< Scalar > & restrict_quantum_number_nu(real_t min, real_t max)
static Database & get_global_instance()
Definition: Database.cpp:1068
Builder class for creating KetAtom objects.
KetAtomCreator & set_quantum_number_n(int value)
std::shared_ptr< const KetAtom > create(Database &database) const
KetAtomCreator & set_quantum_number_j(double value)
KetAtomCreator & set_quantum_number_l(double value)
KetAtomCreator & set_species(const std::string &value)
KetAtomCreator & set_quantum_number_m(double value)
Type & set_electric_field(const std::array< real_t, 3 > &field)
Definition: SystemAtom.cpp:26
const Eigen::SparseMatrix< scalar_t, Eigen::RowMajor > & get_matrix() const
Definition: System.cpp:110
System< Derived > & diagonalize(const DiagonalizerInterface< scalar_t > &diagonalizer, std::optional< real_t > min_eigenenergy={}, std::optional< real_t > max_eigenenergy={}, double rtol=1e-6)
Definition: System.cpp:184
Eigen::VectorX< real_t > get_eigenenergies() const
Definition: System.cpp:97
DOCTEST_TEST_CASE("create a basis for strontium 88")
constexpr double VOLT_PER_CM_IN_ATOMIC_UNITS