LCOV - code coverage report
Current view: top level - src/system - SystemAtom.test.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 206 225 91.6 %
Date: 2025-06-06 09:03:58 Functions: 7 8 87.5 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2024 Pairinteraction Developers
       2             : // SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : #include "pairinteraction/system/SystemAtom.hpp"
       5             : 
       6             : #include "pairinteraction/basis/BasisAtom.hpp"
       7             : #include "pairinteraction/basis/BasisAtomCreator.hpp"
       8             : #include "pairinteraction/database/Database.hpp"
       9             : #include "pairinteraction/diagonalize/DiagonalizerEigen.hpp"
      10             : #include "pairinteraction/diagonalize/DiagonalizerFeast.hpp"
      11             : #include "pairinteraction/diagonalize/DiagonalizerLapackeEvd.hpp"
      12             : #include "pairinteraction/diagonalize/DiagonalizerLapackeEvr.hpp"
      13             : #include "pairinteraction/diagonalize/diagonalize.hpp"
      14             : #include "pairinteraction/enums/FloatType.hpp"
      15             : #include "pairinteraction/ket/KetAtom.hpp"
      16             : #include "pairinteraction/ket/KetAtomCreator.hpp"
      17             : 
      18             : #include <Eigen/Eigenvalues>
      19             : #include <doctest/doctest.h>
      20             : #include <fmt/ranges.h>
      21             : 
      22             : namespace pairinteraction {
      23             : 
      24             : constexpr double VOLT_PER_CM_IN_ATOMIC_UNITS = 1 / 5.14220675112e9;
      25             : constexpr double UM_IN_ATOMIC_UNITS = 1 / 5.29177210544e-5;
      26             : constexpr double HARTREE_IN_GHZ = 6579683.920501762;
      27             : 
      28           1 : DOCTEST_TEST_CASE("construct and diagonalize a small Hamiltonian") {
      29           1 :     auto &database = Database::get_global_instance();
      30           1 :     auto diagonalizer = DiagonalizerEigen<double>();
      31             : 
      32           1 :     auto ket1 = KetAtomCreator()
      33           2 :                     .set_species("Rb")
      34           1 :                     .set_quantum_number_n(60)
      35           1 :                     .set_quantum_number_l(0)
      36           1 :                     .set_quantum_number_j(0.5)
      37           1 :                     .set_quantum_number_m(0.5)
      38           1 :                     .create(database);
      39           1 :     auto ket2 = KetAtomCreator()
      40           2 :                     .set_species("Rb")
      41           1 :                     .set_quantum_number_n(60)
      42           1 :                     .set_quantum_number_l(1)
      43           1 :                     .set_quantum_number_j(0.5)
      44           1 :                     .set_quantum_number_m(0.5)
      45           1 :                     .create(database);
      46           1 :     auto basis = BasisAtomCreator<double>().append_ket(ket1).append_ket(ket2).create(database);
      47             : 
      48           1 :     auto system = SystemAtom<double>(basis);
      49           1 :     system.set_electric_field({0, 0, 0.0001});
      50             : 
      51           1 :     Eigen::MatrixXd tmp = Eigen::MatrixXd(1e5 * system.get_matrix()).array().round() / 1e5;
      52           1 :     std::vector<double> matrix_vector(tmp.data(), tmp.data() + tmp.size());
      53           3 :     DOCTEST_MESSAGE(fmt::format("Constructed: {}", fmt::join(matrix_vector, ", ")));
      54             : 
      55           1 :     system.diagonalize(diagonalizer);
      56           1 :     tmp = Eigen::MatrixXd(1e5 * system.get_matrix()).array().round() / 1e5;
      57           1 :     matrix_vector = std::vector<double>(tmp.data(), tmp.data() + tmp.size());
      58           3 :     DOCTEST_MESSAGE(fmt::format("Diagonalized: {}", fmt::join(matrix_vector, ", ")));
      59             : 
      60           1 :     Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigensolver;
      61           1 :     eigensolver.compute(system.get_matrix());
      62           1 :     auto eigenenergies_eigen = eigensolver.eigenvalues();
      63           1 :     auto eigenenergies_pairinteraction = system.get_eigenenergies();
      64           3 :     for (int i = 0; i < eigenenergies_eigen.size(); ++i) {
      65           2 :         DOCTEST_CHECK(std::abs(eigenenergies_eigen(i) - eigenenergies_pairinteraction(i)) < 1e-10);
      66             :     }
      67           1 : }
      68             : 
      69           1 : DOCTEST_TEST_CASE("construct and diagonalize two Hamiltonians in parallel") {
      70           1 :     auto &database = Database::get_global_instance();
      71           1 :     auto diagonalizer = DiagonalizerEigen<std::complex<double>>();
      72             : 
      73           1 :     auto basis = BasisAtomCreator<std::complex<double>>()
      74           2 :                      .set_species("Rb")
      75           1 :                      .restrict_quantum_number_n(59, 61)
      76           1 :                      .restrict_quantum_number_l(0, 1)
      77           1 :                      .create(database);
      78             : 
      79           1 :     auto system1 = SystemAtom<std::complex<double>>(basis);
      80           1 :     system1.set_electric_field({0, 0, 0.0001});
      81             : 
      82           1 :     auto system2 = SystemAtom<std::complex<double>>(basis);
      83           1 :     system2.set_electric_field({0, 0, 0.0002});
      84             : 
      85           1 :     diagonalize<SystemAtom<std::complex<double>>>({system1, system2}, diagonalizer);
      86             : 
      87           1 :     auto matrix1 = system1.get_matrix();
      88           1 :     auto matrix2 = system2.get_matrix();
      89          25 :     for (int i = 0; i < matrix1.rows(); ++i) {
      90         600 :         for (int j = 0; j < matrix1.cols(); ++j) {
      91         576 :             if (i != j) {
      92         552 :                 DOCTEST_CHECK(std::abs(matrix1.coeff(i, j)) < 1e-10);
      93         552 :                 DOCTEST_CHECK(std::abs(matrix2.coeff(i, j)) < 1e-10);
      94             :             }
      95             :         }
      96             :     }
      97           1 : }
      98             : 
      99           0 : DOCTEST_TEST_CASE("construct and diagonalize multiple Hamiltonians in parallel" *
     100             :                   doctest::skip(true)) {
     101             :     // TODO For a slow database, the fast parallelized construction of the tiny Hamiltonians seems
     102             :     // to lead to the wrong Hamiltonians (visible in the warning "The floating point error (5e-324
     103             :     // Hartree) is similar or larger than error estimated from the specified tolerance (0
     104             :     // Hartree)."). This could be caused by an issue with thread safety or memory access.
     105           0 :     int n = 10;
     106             : 
     107           0 :     auto &database = Database::get_global_instance();
     108           0 :     auto diagonalizer = DiagonalizerEigen<std::complex<double>>();
     109             : 
     110           0 :     auto basis = BasisAtomCreator<std::complex<double>>()
     111           0 :                      .set_species("Sr87_mqdt")
     112           0 :                      .restrict_quantum_number_nu(60, 61)
     113           0 :                      .restrict_quantum_number_l(0, 1)
     114           0 :                      .create(database);
     115             : 
     116           0 :     std::vector<SystemAtom<std::complex<double>>> systems;
     117           0 :     systems.reserve(n);
     118           0 :     for (int i = 0; i < n; ++i) {
     119           0 :         auto system = SystemAtom<std::complex<double>>(basis);
     120           0 :         system.set_electric_field({0, 0, 0.0001 * i});
     121           0 :         systems.push_back(std::move(system));
     122           0 :     }
     123             : 
     124           0 :     DOCTEST_MESSAGE("Basis size: ", basis->get_number_of_states());
     125             : 
     126           0 :     diagonalize<SystemAtom<std::complex<double>>>(systems, diagonalizer);
     127           0 : }
     128             : 
     129           2 : DOCTEST_TEST_CASE("construct and diagonalize a Hamiltonian using different methods") {
     130           2 :     auto &database = Database::get_global_instance();
     131             : 
     132           2 :     auto basis = BasisAtomCreator<std::complex<double>>()
     133           4 :                      .set_species("Rb")
     134           2 :                      .restrict_quantum_number_n(60, 61)
     135           2 :                      .restrict_quantum_number_l(0, 1)
     136           2 :                      .create(database);
     137             : 
     138             :     // Diagonalize using the Eigen library
     139           2 :     auto system = SystemAtom<std::complex<double>>(basis);
     140           2 :     system.set_electric_field({1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 2 * VOLT_PER_CM_IN_ATOMIC_UNITS,
     141             :                                3 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     142             : 
     143           2 :     Eigen::MatrixXcd matrix = system.get_matrix();
     144           2 :     Eigen::SelfAdjointEigenSolver<Eigen::MatrixXcd> eigensolver;
     145           2 :     eigensolver.compute(matrix);
     146           2 :     auto eigenenergies_eigen = eigensolver.eigenvalues();
     147             : 
     148             :     // Create diagonalizers
     149           2 :     std::vector<std::unique_ptr<DiagonalizerInterface<std::complex<double>>>> diagonalizers;
     150           2 :     std::vector<double> rtols;
     151           2 :     double eps{};
     152           2 :     DOCTEST_SUBCASE("Double precision") {
     153           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerEigen<std::complex<double>>>());
     154             : #ifdef WITH_LAPACKE
     155           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<std::complex<double>>>());
     156           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvr<std::complex<double>>>());
     157             : #endif
     158             : #ifdef WITH_MKL
     159           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerFeast<std::complex<double>>>(300));
     160             : #endif
     161           1 :         rtols = {1e-1, 1e-6, 1e-14};
     162           1 :         eps = std::numeric_limits<double>::epsilon();
     163           2 :     }
     164             : 
     165           2 :     DOCTEST_SUBCASE("Single precision") {
     166           1 :         diagonalizers.push_back(
     167           2 :             std::make_unique<DiagonalizerEigen<std::complex<double>>>(FloatType::FLOAT32));
     168             : #ifdef WITH_LAPACKE
     169           1 :         diagonalizers.push_back(
     170           2 :             std::make_unique<DiagonalizerLapackeEvd<std::complex<double>>>(FloatType::FLOAT32));
     171           1 :         diagonalizers.push_back(
     172           2 :             std::make_unique<DiagonalizerLapackeEvr<std::complex<double>>>(FloatType::FLOAT32));
     173             : #endif
     174             : #ifdef WITH_MKL
     175           1 :         diagonalizers.push_back(
     176           2 :             std::make_unique<DiagonalizerFeast<std::complex<double>>>(300, FloatType::FLOAT32));
     177             : #endif
     178           1 :         rtols = {1e-1, 1e-6};
     179           1 :         eps = std::numeric_limits<float>::epsilon();
     180           2 :     }
     181             : 
     182             :     // Diagonalize using pairinteraction
     183           7 :     for (double rtol_eigenenergies : rtols) {
     184             :         double atol_eigenvectors =
     185           5 :             std::max(0.5 * rtol_eigenenergies / std::sqrt(basis->get_number_of_states()), 4 * eps);
     186           5 :         DOCTEST_MESSAGE("Precision: " << rtol_eigenenergies << " (rtol eigenenergies), "
     187             :                                       << atol_eigenvectors << " (atol eigenvectors)");
     188             : 
     189          25 :         for (const auto &diagonalizer : diagonalizers) {
     190          20 :             auto system = SystemAtom<std::complex<double>>(basis);
     191          20 :             system.set_electric_field({1 * VOLT_PER_CM_IN_ATOMIC_UNITS,
     192             :                                        2 * VOLT_PER_CM_IN_ATOMIC_UNITS,
     193             :                                        3 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     194             : 
     195             :             // We specify a search interval because this is required if the FEAST routine is
     196             :             // used. To avoid overflows, the interval ranges from half the smallest possible
     197             :             // value to half the largest possible value.
     198          20 :             system.diagonalize(*diagonalizer, std::numeric_limits<float>::lowest() / 2,
     199          20 :                                std::numeric_limits<float>::max() / 2, rtol_eigenenergies);
     200          20 :             auto eigenenergies_pairinteraction = system.get_eigenenergies();
     201          20 :             auto eigenvectors_pairinteraction = system.get_eigenbasis()->get_coefficients();
     202             : 
     203          20 :             DOCTEST_CHECK(
     204             :                 (eigenenergies_eigen - eigenenergies_pairinteraction).array().abs().maxCoeff() <
     205             :                 rtol_eigenenergies * matrix.norm());
     206             : 
     207         340 :             for (int i = 0; i < eigenvectors_pairinteraction.cols(); ++i) {
     208         320 :                 DOCTEST_CHECK(abs(1 - eigenvectors_pairinteraction.col(i).norm()) <
     209             :                               atol_eigenvectors * eigenvectors_pairinteraction.rows());
     210             :             }
     211          20 :         }
     212             :     }
     213           2 : }
     214             : 
     215           1 : DOCTEST_TEST_CASE("construct and diagonalize a Hamiltonian with energy restrictions") {
     216           1 :     double min_energy = 0.153355;
     217           1 :     double max_energy = 0.153360;
     218             : 
     219           1 :     auto &database = Database::get_global_instance();
     220             : 
     221           1 :     auto basis = BasisAtomCreator<double>()
     222           2 :                      .set_species("Rb")
     223           1 :                      .restrict_quantum_number_n(58, 62)
     224           1 :                      .restrict_quantum_number_l(0, 1)
     225           1 :                      .create(database);
     226             : 
     227             :     // Diagonalize using the Eigen library
     228           1 :     auto system = SystemAtom<double>(basis);
     229           1 :     system.set_electric_field(
     230             :         {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     231             : 
     232           1 :     Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigensolver;
     233           1 :     eigensolver.compute(system.get_matrix());
     234           1 :     auto eigenenergies_all = eigensolver.eigenvalues();
     235           1 :     std::vector<double> eigenenergies_eigen;
     236          41 :     for (int i = 0; i < eigenenergies_all.size(); ++i) {
     237          40 :         if (eigenenergies_all[i] > min_energy && eigenenergies_all[i] < max_energy) {
     238           8 :             eigenenergies_eigen.push_back(eigenenergies_all[i]);
     239             :         }
     240             :     }
     241             : 
     242             :     // Create diagonalizer
     243           1 :     std::vector<std::unique_ptr<DiagonalizerInterface<double>>> diagonalizers;
     244           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerEigen<double>>(FloatType::FLOAT64));
     245             : #ifdef WITH_LAPACKE
     246           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<double>>(FloatType::FLOAT64));
     247           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvr<double>>(FloatType::FLOAT64));
     248             : #endif
     249             : #ifdef WITH_MKL
     250           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerFeast<double>>(10, FloatType::FLOAT64));
     251             : #endif
     252             : 
     253             :     // Diagonalize using pairinteraction
     254           5 :     for (const auto &diagonalizer : diagonalizers) {
     255           4 :         auto system = SystemAtom<double>(basis);
     256           4 :         system.set_electric_field(
     257             :             {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     258             : 
     259           4 :         system.diagonalize(*diagonalizer, min_energy, max_energy, 1e-6);
     260           4 :         auto eigenenergies_pairinteraction = system.get_eigenenergies();
     261             : 
     262           4 :         Eigen::MatrixXd tmp = (1e5 * eigenenergies_pairinteraction).array().round() / 1e5;
     263           4 :         std::vector<double> eigenenergies_vector(tmp.data(), tmp.data() + tmp.size());
     264          12 :         DOCTEST_MESSAGE(fmt::format("Eigenenergies: {}", fmt::join(eigenenergies_vector, ", ")));
     265             : 
     266           4 :         DOCTEST_CHECK(eigenenergies_eigen.size() == 8);
     267           4 :         DOCTEST_CHECK(eigenenergies_pairinteraction.size() == 8);
     268          36 :         for (size_t i = 0; i < eigenenergies_eigen.size(); ++i) {
     269          32 :             DOCTEST_CHECK(std::abs(eigenenergies_eigen[i] - eigenenergies_pairinteraction[i]) <
     270             :                           1e-10);
     271             :         }
     272           4 :     }
     273           1 : }
     274             : 
     275             : #ifdef WITH_MKL
     276             : #include <Eigen/Dense>
     277             : #include <mkl.h>
     278           1 : DOCTEST_TEST_CASE("diagonalization with mkl") {
     279             :     // We loop several times to check for memory errors
     280          11 :     for (size_t i = 0; i < 10; ++i) {
     281             :         // Create a symmetric matrix
     282          10 :         int n = 100;
     283          10 :         Eigen::MatrixXd matrix = Eigen::MatrixXd::Random(n, n);
     284          10 :         matrix = (matrix + matrix.transpose()).eval();
     285          10 :         Eigen::VectorXd eigenenergies(n);
     286             : 
     287             :         // Diagonalize the matrix
     288             :         int info =
     289          10 :             LAPACKE_dsyev(LAPACK_COL_MAJOR, 'V', 'U', n, matrix.data(), n, eigenenergies.data());
     290          10 :         DOCTEST_CHECK(info == 0);
     291          10 :     }
     292           1 : }
     293             : #endif
     294             : 
     295           1 : DOCTEST_TEST_CASE("handle it gracefully if no eigenenergies are within energy restrictions") {
     296           1 :     double min_energy = -1;
     297           1 :     double max_energy = -1;
     298             : 
     299           1 :     auto &database = Database::get_global_instance();
     300             : 
     301           1 :     auto basis = BasisAtomCreator<double>()
     302           2 :                      .set_species("Rb")
     303           1 :                      .restrict_quantum_number_n(58, 62)
     304           1 :                      .restrict_quantum_number_l(0, 1)
     305           1 :                      .create(database);
     306             : 
     307           1 :     std::vector<std::unique_ptr<DiagonalizerInterface<double>>> diagonalizers;
     308           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerEigen<double>>());
     309             : #ifdef WITH_LAPACKE
     310           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<double>>());
     311             : #endif
     312             : 
     313           3 :     for (const auto &diagonalizer : diagonalizers) {
     314           2 :         auto system = SystemAtom<double>(basis);
     315           2 :         system.set_electric_field(
     316             :             {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     317             : 
     318           2 :         system.diagonalize(*diagonalizer, min_energy, max_energy, 1e-6);
     319           2 :         auto eigenenergies_pairinteraction = system.get_eigenenergies();
     320             : 
     321           2 :         DOCTEST_CHECK(eigenenergies_pairinteraction.size() == 0);
     322           2 :     }
     323           1 : }
     324             : 
     325           1 : DOCTEST_TEST_CASE("atom ion interaction") {
     326           1 :     auto &database = Database::get_global_instance();
     327           1 :     DiagonalizerEigen<double> diagonalizer;
     328             : 
     329           1 :     auto ket = KetAtomCreator()
     330           2 :                    .set_species("Rb")
     331           1 :                    .set_quantum_number_n(60)
     332           1 :                    .set_quantum_number_l(1)
     333           1 :                    .set_quantum_number_j(0.5)
     334           1 :                    .set_quantum_number_m(0.5)
     335           1 :                    .create(database);
     336           1 :     double energy = ket->get_energy();
     337           1 :     double min_energy = energy - 50 / HARTREE_IN_GHZ;
     338           1 :     double max_energy = energy + 50 / HARTREE_IN_GHZ;
     339             : 
     340           1 :     auto basis = BasisAtomCreator<double>()
     341           2 :                      .set_species("Rb")
     342           1 :                      .restrict_quantum_number_n(58, 62)
     343           1 :                      .restrict_quantum_number_l(0, 3)
     344           1 :                      .restrict_quantum_number_m(0.5, 0.5)
     345           1 :                      .create(database);
     346             : 
     347           1 :     auto system3 = SystemAtom<double>(basis);
     348           1 :     system3.set_ion_interaction_order(3);
     349           1 :     system3.set_ion_distance_vector({0, 0, 10 * UM_IN_ATOMIC_UNITS});
     350           1 :     system3.diagonalize(diagonalizer, min_energy, max_energy, 1e-6);
     351           1 :     auto energies3 = system3.get_eigenenergies();
     352             : 
     353           1 :     auto system2 = SystemAtom<double>(basis);
     354           1 :     system2.set_ion_interaction_order(2);
     355           1 :     system2.set_ion_distance_vector({0, 0, 10 * UM_IN_ATOMIC_UNITS});
     356           1 :     system2.diagonalize(diagonalizer, min_energy, max_energy, 1e-6);
     357           1 :     auto energies2 = system2.get_eigenenergies();
     358             : 
     359             :     // Ensure that the quadrupole order has a significant effect
     360           1 :     size_t num_energies = std::min(energies2.size(), energies3.size());
     361          17 :     for (size_t i = 0; i < num_energies; ++i) {
     362          16 :         DOCTEST_CHECK(std::abs(energies3[i] - energies2[i]) * HARTREE_IN_GHZ > 1e-6);
     363             :     }
     364           1 : }
     365             : 
     366             : } // namespace pairinteraction

Generated by: LCOV version 1.16