LCOV - code coverage report
Current view: top level - src/system - SystemAtom.test.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 348 367 94.8 %
Date: 2026-10-14 13:16:16 Functions: 14 15 93.3 %

          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/enums/SorterType.hpp"
      16             : #include "pairinteraction/ket/KetAtom.hpp"
      17             : #include "pairinteraction/ket/KetAtomCreator.hpp"
      18             : 
      19             : #include <Eigen/Eigenvalues>
      20             : #include <cmath>
      21             : #include <doctest/doctest.h>
      22             : #include <fmt/ranges.h>
      23             : #include <stdexcept>
      24             : 
      25             : namespace pairinteraction {
      26             : 
      27             : constexpr double VOLT_PER_CM_IN_ATOMIC_UNITS = 1 / 5.14220675112e9;
      28             : constexpr double UM_IN_ATOMIC_UNITS = 1 / 5.29177210544e-5;
      29             : constexpr double HARTREE_IN_GHZ = 6579683.920501762;
      30             : 
      31           1 : DOCTEST_TEST_CASE("construct and diagonalize a small Hamiltonian") {
      32           1 :     auto &database = Database::get_global_instance();
      33           1 :     auto diagonalizer = DiagonalizerEigen<double>();
      34             : 
      35           1 :     auto ket1 = KetAtomCreator(database)
      36           2 :                     .set_species("Rb")
      37           2 :                     .set_quantum_number("n", 60)
      38           2 :                     .set_quantum_number("l", 0)
      39           2 :                     .set_quantum_number("j", 0.5)
      40           2 :                     .set_quantum_number("m", 0.5)
      41           1 :                     .create();
      42           1 :     auto ket2 = KetAtomCreator(database)
      43           2 :                     .set_species("Rb")
      44           2 :                     .set_quantum_number("n", 60)
      45           2 :                     .set_quantum_number("l", 1)
      46           2 :                     .set_quantum_number("j", 0.5)
      47           2 :                     .set_quantum_number("m", 0.5)
      48           1 :                     .create();
      49           1 :     auto basis = BasisAtomCreator<double>(database).add_ket(ket1).add_ket(ket2).create();
      50             : 
      51           1 :     auto system = SystemAtom<double>(basis);
      52           1 :     system.set_electric_field({0, 0, 0.0001});
      53             : 
      54           1 :     Eigen::MatrixXd tmp = Eigen::MatrixXd(1e5 * system.get_matrix()).array().round() / 1e5;
      55           1 :     std::vector<double> matrix_vector(tmp.data(), tmp.data() + tmp.size());
      56           3 :     DOCTEST_MESSAGE(fmt::format("Constructed: {}", fmt::join(matrix_vector, ", ")));
      57             : 
      58           1 :     system.diagonalize(diagonalizer);
      59           1 :     tmp = Eigen::MatrixXd(1e5 * system.get_matrix()).array().round() / 1e5;
      60           1 :     matrix_vector = std::vector<double>(tmp.data(), tmp.data() + tmp.size());
      61           3 :     DOCTEST_MESSAGE(fmt::format("Diagonalized: {}", fmt::join(matrix_vector, ", ")));
      62             : 
      63           1 :     Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigensolver;
      64           1 :     eigensolver.compute(system.get_matrix());
      65           1 :     auto eigenenergies_eigen = eigensolver.eigenvalues();
      66           1 :     auto eigenenergies_pairinteraction = system.get_eigenenergies();
      67           3 :     for (int i = 0; i < eigenenergies_eigen.size(); ++i) {
      68           2 :         DOCTEST_CHECK(std::abs(eigenenergies_eigen(i) - eigenenergies_pairinteraction(i)) < 1e-10);
      69             :     }
      70           1 : }
      71             : 
      72           1 : DOCTEST_TEST_CASE("construct and diagonalize two Hamiltonians in parallel") {
      73           1 :     auto &database = Database::get_global_instance();
      74           1 :     auto diagonalizer = DiagonalizerEigen<std::complex<double>>();
      75             : 
      76           1 :     auto basis = BasisAtomCreator<std::complex<double>>(database)
      77           2 :                      .set_species("Rb")
      78           2 :                      .restrict_quantum_number("n", 59, 61)
      79           2 :                      .restrict_quantum_number("l", 0, 1)
      80           1 :                      .create();
      81             : 
      82           1 :     auto system1 = SystemAtom<std::complex<double>>(basis);
      83           1 :     system1.set_electric_field({0, 0, 0.0001});
      84             : 
      85           1 :     auto system2 = SystemAtom<std::complex<double>>(basis);
      86           1 :     system2.set_electric_field({0, 0, 0.0002});
      87             : 
      88           1 :     diagonalize<SystemAtom<std::complex<double>>>({system1, system2}, diagonalizer);
      89             : 
      90           1 :     auto matrix1 = system1.get_matrix();
      91           1 :     auto matrix2 = system2.get_matrix();
      92          25 :     for (int i = 0; i < matrix1.rows(); ++i) {
      93         600 :         for (int j = 0; j < matrix1.cols(); ++j) {
      94         576 :             if (i != j) {
      95         552 :                 DOCTEST_CHECK(std::abs(matrix1.coeff(i, j)) < 1e-10);
      96         552 :                 DOCTEST_CHECK(std::abs(matrix2.coeff(i, j)) < 1e-10);
      97             :             }
      98             :         }
      99             :     }
     100           1 : }
     101             : 
     102           1 : DOCTEST_TEST_CASE("construct an atomic Hamiltonian in a non-canonical atomic basis") {
     103           1 :     auto &database = Database::get_global_instance();
     104             : 
     105           1 :     auto basis = BasisAtomCreator<double>(database)
     106           2 :                      .set_species("Rb")
     107           2 :                      .restrict_quantum_number("n", 60, 61)
     108           2 :                      .restrict_quantum_number("l", 0, 1)
     109           2 :                      .restrict_quantum_number("m", 0.5, 0.5)
     110           1 :                      .create();
     111           1 :     DOCTEST_REQUIRE(basis->get_number_of_states() >= 2);
     112             : 
     113           1 :     SystemAtom<double> reference_system(basis);
     114           1 :     reference_system.set_electric_field({0, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     115           1 :     const auto &reference_matrix = reference_system.get_matrix();
     116             : 
     117             :     Eigen::SparseMatrix<double, Eigen::RowMajor> transformation(
     118           1 :         static_cast<Eigen::Index>(basis->get_number_of_states()),
     119           2 :         static_cast<Eigen::Index>(basis->get_number_of_states()));
     120           1 :     transformation.setIdentity();
     121             : 
     122           1 :     double inverse_sqrt_two = 1 / std::sqrt(2.0);
     123           1 :     transformation.coeffRef(0, 0) = inverse_sqrt_two;
     124           1 :     transformation.coeffRef(1, 0) = inverse_sqrt_two;
     125           1 :     transformation.coeffRef(0, 1) = inverse_sqrt_two;
     126           1 :     transformation.coeffRef(1, 1) = -inverse_sqrt_two;
     127           1 :     transformation.makeCompressed();
     128             : 
     129           1 :     auto transformed_basis = basis->transformed(transformation);
     130           1 :     SystemAtom<double> transformed_system(transformed_basis);
     131           1 :     transformed_system.set_electric_field({0, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     132             : 
     133             :     Eigen::SparseMatrix<double, Eigen::RowMajor> expected_matrix =
     134           1 :         transformation.adjoint() * reference_matrix * transformation;
     135             : 
     136           1 :     DOCTEST_CHECK(transformed_system.get_matrix().isApprox(expected_matrix, 1e-11));
     137           1 : }
     138             : 
     139           0 : DOCTEST_TEST_CASE("construct and diagonalize multiple Hamiltonians in parallel" *
     140             :                   doctest::skip(true)) {
     141             :     // TODO For a slow database, the fast parallelized construction of the tiny Hamiltonians seems
     142             :     // to lead to the wrong Hamiltonians (visible in the warning "The floating point error (5e-324
     143             :     // Hartree) is similar or larger than error estimated from the specified tolerance (0
     144             :     // Hartree)."). This could be caused by an issue with thread safety or memory access.
     145           0 :     int n = 10;
     146             : 
     147           0 :     auto &database = Database::get_global_instance();
     148           0 :     auto diagonalizer = DiagonalizerEigen<std::complex<double>>();
     149             : 
     150           0 :     auto basis = BasisAtomCreator<std::complex<double>>(database)
     151           0 :                      .set_species("Sr87_mqdt")
     152           0 :                      .restrict_quantum_number("nu", 60, 61)
     153           0 :                      .restrict_quantum_number("l", 0, 1)
     154           0 :                      .create();
     155             : 
     156           0 :     std::vector<SystemAtom<std::complex<double>>> systems;
     157           0 :     systems.reserve(n);
     158           0 :     for (int i = 0; i < n; ++i) {
     159           0 :         auto system = SystemAtom<std::complex<double>>(basis);
     160           0 :         system.set_electric_field({0, 0, 0.0001 * i});
     161           0 :         systems.push_back(std::move(system));
     162           0 :     }
     163             : 
     164           0 :     DOCTEST_MESSAGE("Basis size: ", basis->get_number_of_states());
     165             : 
     166           0 :     diagonalize<SystemAtom<std::complex<double>>>(systems, diagonalizer);
     167           0 : }
     168             : 
     169           2 : DOCTEST_TEST_CASE("construct and diagonalize a Hamiltonian using different methods") {
     170           2 :     auto &database = Database::get_global_instance();
     171             : 
     172           2 :     auto basis = BasisAtomCreator<std::complex<double>>(database)
     173           4 :                      .set_species("Rb")
     174           4 :                      .restrict_quantum_number("n", 60, 61)
     175           4 :                      .restrict_quantum_number("l", 0, 1)
     176           2 :                      .create();
     177             : 
     178             :     // Diagonalize using the Eigen library
     179           2 :     auto system = SystemAtom<std::complex<double>>(basis);
     180           2 :     system.set_electric_field({1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 2 * VOLT_PER_CM_IN_ATOMIC_UNITS,
     181             :                                3 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     182             : 
     183           2 :     Eigen::MatrixXcd matrix = system.get_matrix();
     184           2 :     Eigen::SelfAdjointEigenSolver<Eigen::MatrixXcd> eigensolver;
     185           2 :     eigensolver.compute(matrix);
     186           2 :     auto eigenenergies_eigen = eigensolver.eigenvalues();
     187             : 
     188             :     // Create diagonalizers
     189           2 :     std::vector<std::unique_ptr<DiagonalizerInterface<std::complex<double>>>> diagonalizers;
     190           2 :     std::vector<double> rtols;
     191           2 :     double eps{};
     192           2 :     DOCTEST_SUBCASE("Double precision") {
     193           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerEigen<std::complex<double>>>());
     194             : #ifdef WITH_LAPACKE
     195           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<std::complex<double>>>());
     196           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvr<std::complex<double>>>());
     197             : #endif
     198             : #ifdef WITH_MKL
     199           1 :         diagonalizers.push_back(std::make_unique<DiagonalizerFeast<std::complex<double>>>(300));
     200             : #endif
     201           1 :         rtols = {1e-1, 1e-6, 1e-14};
     202           1 :         eps = std::numeric_limits<double>::epsilon();
     203           2 :     }
     204             : 
     205           2 :     DOCTEST_SUBCASE("Single precision") {
     206           1 :         diagonalizers.push_back(
     207           2 :             std::make_unique<DiagonalizerEigen<std::complex<double>>>(FloatType::FLOAT32));
     208             : #ifdef WITH_LAPACKE
     209           1 :         diagonalizers.push_back(
     210           2 :             std::make_unique<DiagonalizerLapackeEvd<std::complex<double>>>(FloatType::FLOAT32));
     211           1 :         diagonalizers.push_back(
     212           2 :             std::make_unique<DiagonalizerLapackeEvr<std::complex<double>>>(FloatType::FLOAT32));
     213             : #endif
     214             : #ifdef WITH_MKL
     215           1 :         diagonalizers.push_back(
     216           2 :             std::make_unique<DiagonalizerFeast<std::complex<double>>>(300, FloatType::FLOAT32));
     217             : #endif
     218           1 :         rtols = {1e-1, 1e-6};
     219           1 :         eps = std::numeric_limits<float>::epsilon();
     220           2 :     }
     221             : 
     222             :     // Diagonalize using pairinteraction
     223           7 :     for (double rtol_eigenenergies : rtols) {
     224             :         double atol_eigenvectors =
     225           5 :             std::max(0.5 * rtol_eigenenergies / std::sqrt(basis->get_number_of_states()), 5 * eps);
     226           5 :         DOCTEST_MESSAGE("Precision: " << rtol_eigenenergies << " (rtol eigenenergies), "
     227             :                                       << atol_eigenvectors << " (atol eigenvectors)");
     228             : 
     229          25 :         for (const auto &diagonalizer : diagonalizers) {
     230          20 :             auto system = SystemAtom<std::complex<double>>(basis);
     231          20 :             system.set_electric_field({1 * VOLT_PER_CM_IN_ATOMIC_UNITS,
     232             :                                        2 * VOLT_PER_CM_IN_ATOMIC_UNITS,
     233             :                                        3 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     234             : 
     235             :             // We specify a search interval because this is required if the FEAST routine is
     236             :             // used. To avoid overflows, the interval ranges from half the smallest possible
     237             :             // value to half the largest possible value.
     238          20 :             system.diagonalize(*diagonalizer, std::numeric_limits<float>::lowest() / 2,
     239          20 :                                std::numeric_limits<float>::max() / 2, rtol_eigenenergies);
     240          20 :             auto eigenenergies_pairinteraction = system.get_eigenenergies();
     241          20 :             auto eigenvectors_pairinteraction = system.get_eigenbasis()->get_coefficients();
     242             : 
     243          20 :             DOCTEST_CHECK(
     244             :                 (eigenenergies_eigen - eigenenergies_pairinteraction).array().abs().maxCoeff() <
     245             :                 rtol_eigenenergies * matrix.norm());
     246             : 
     247         340 :             for (int i = 0; i < eigenvectors_pairinteraction.cols(); ++i) {
     248         320 :                 DOCTEST_CHECK(abs(1 - eigenvectors_pairinteraction.col(i).norm()) <
     249             :                               atol_eigenvectors * eigenvectors_pairinteraction.rows());
     250             :             }
     251          20 :         }
     252             :     }
     253           2 : }
     254             : 
     255           1 : DOCTEST_TEST_CASE("construct and diagonalize a Hamiltonian with energy restrictions") {
     256           1 :     double min_energy = 0.153355;
     257           1 :     double max_energy = 0.153360;
     258             : 
     259           1 :     auto &database = Database::get_global_instance();
     260             : 
     261           1 :     auto basis = BasisAtomCreator<double>(database)
     262           2 :                      .set_species("Rb")
     263           2 :                      .restrict_quantum_number("n", 58, 62)
     264           2 :                      .restrict_quantum_number("l", 0, 1)
     265           1 :                      .create();
     266             : 
     267             :     // Diagonalize using the Eigen library
     268           1 :     auto system = SystemAtom<double>(basis);
     269           1 :     system.set_electric_field(
     270             :         {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     271             : 
     272           1 :     Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigensolver;
     273           1 :     eigensolver.compute(system.get_matrix());
     274           1 :     auto eigenenergies_all = eigensolver.eigenvalues();
     275           1 :     std::vector<double> eigenenergies_eigen;
     276          41 :     for (int i = 0; i < eigenenergies_all.size(); ++i) {
     277          40 :         if (eigenenergies_all[i] > min_energy && eigenenergies_all[i] < max_energy) {
     278           8 :             eigenenergies_eigen.push_back(eigenenergies_all[i]);
     279             :         }
     280             :     }
     281             : 
     282             :     // Create diagonalizer
     283           1 :     std::vector<std::unique_ptr<DiagonalizerInterface<double>>> diagonalizers;
     284           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerEigen<double>>(FloatType::FLOAT64));
     285             : #ifdef WITH_LAPACKE
     286           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<double>>(FloatType::FLOAT64));
     287           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvr<double>>(FloatType::FLOAT64));
     288             : #endif
     289             : #ifdef WITH_MKL
     290           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerFeast<double>>(10, FloatType::FLOAT64));
     291             : #endif
     292             : 
     293             :     // Diagonalize using pairinteraction
     294           5 :     for (const auto &diagonalizer : diagonalizers) {
     295           4 :         auto system = SystemAtom<double>(basis);
     296           4 :         system.set_electric_field(
     297             :             {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     298             : 
     299           4 :         system.diagonalize(*diagonalizer, min_energy, max_energy, 1e-6);
     300           4 :         auto eigenenergies_pairinteraction = system.get_eigenenergies();
     301             : 
     302           4 :         Eigen::MatrixXd tmp = (1e5 * eigenenergies_pairinteraction).array().round() / 1e5;
     303           4 :         std::vector<double> eigenenergies_vector(tmp.data(), tmp.data() + tmp.size());
     304          12 :         DOCTEST_MESSAGE(fmt::format("Eigenenergies: {}", fmt::join(eigenenergies_vector, ", ")));
     305             : 
     306           4 :         DOCTEST_CHECK(eigenenergies_eigen.size() == 8);
     307           4 :         DOCTEST_CHECK(eigenenergies_pairinteraction.size() == 8);
     308          36 :         for (size_t i = 0; i < eigenenergies_eigen.size(); ++i) {
     309          32 :             DOCTEST_CHECK(std::abs(eigenenergies_eigen[i] - eigenenergies_pairinteraction[i]) <
     310             :                           1e-10);
     311             :         }
     312           4 :     }
     313           1 : }
     314             : 
     315           2 : DOCTEST_TEST_CASE("the eigenbasis knows the eigenenergies") {
     316           2 :     auto &database = Database::get_global_instance();
     317           2 :     auto diagonalizer = DiagonalizerEigen<double>();
     318             : 
     319           2 :     DOCTEST_SUBCASE("diagonalized Hamiltonian") {
     320           1 :         auto basis = BasisAtomCreator<double>(database)
     321           2 :                          .set_species("Rb")
     322           2 :                          .restrict_quantum_number("n", 58, 62)
     323           2 :                          .restrict_quantum_number("l", 0, 1)
     324           1 :                          .create();
     325             : 
     326           3 :         for (bool restrict_energy : {false, true}) {
     327           2 :             auto system = SystemAtom<double>(basis);
     328           2 :             system.set_electric_field(
     329             :                 {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     330           2 :             if (restrict_energy) {
     331           1 :                 system.diagonalize(diagonalizer, 0.153355, 0.153360);
     332             :             } else {
     333           1 :                 system.diagonalize(diagonalizer);
     334             :             }
     335             : 
     336           2 :             auto eigenbasis = system.get_eigenbasis();
     337           2 :             auto eigenenergies = system.get_eigenenergies();
     338           2 :             DOCTEST_REQUIRE(eigenbasis->get_number_of_states() ==
     339             :                             static_cast<size_t>(eigenenergies.size()));
     340          50 :             for (Eigen::Index i = 0; i < eigenenergies.size(); ++i) {
     341          48 :                 DOCTEST_CHECK(eigenbasis->get_energy(static_cast<size_t>(i)) == eigenenergies[i]);
     342             :             }
     343           2 :         }
     344           3 :     }
     345             : 
     346           2 :     DOCTEST_SUBCASE("Hamiltonian that is diagonal without diagonalization") {
     347             :         // For a single fine structure level, the Zeeman Hamiltonian is diagonal in the canonical
     348             :         // basis
     349           1 :         auto basis = BasisAtomCreator<double>(database)
     350           2 :                          .set_species("Rb")
     351           2 :                          .restrict_quantum_number("n", 60, 60)
     352           2 :                          .restrict_quantum_number("l", 0, 0)
     353           1 :                          .create();
     354           1 :         auto system = SystemAtom<double>(basis);
     355           1 :         system.set_magnetic_field({0, 0, 1e-6});
     356             : 
     357           1 :         auto eigenbasis = system.get_eigenbasis();
     358           1 :         auto eigenenergies = system.get_eigenenergies();
     359           1 :         DOCTEST_REQUIRE(eigenenergies.size() == 2);
     360           3 :         for (Eigen::Index i = 0; i < eigenenergies.size(); ++i) {
     361           2 :             auto state_index = static_cast<size_t>(i);
     362           2 :             DOCTEST_CHECK(eigenbasis->get_energy(state_index) == eigenenergies[i]);
     363           2 :             DOCTEST_CHECK(eigenbasis->get_energy(state_index) !=
     364             :                           eigenbasis->get_ket(state_index)->get_energy());
     365             :         }
     366           3 :     }
     367           2 : }
     368             : 
     369             : #ifdef WITH_MKL
     370             : #include <Eigen/Dense>
     371             : #include <mkl.h>
     372           1 : DOCTEST_TEST_CASE("diagonalization with mkl") {
     373             :     // We loop several times to check for memory errors
     374          11 :     for (size_t i = 0; i < 10; ++i) {
     375             :         // Create a symmetric matrix
     376          10 :         int n = 100;
     377          10 :         Eigen::MatrixXd matrix = Eigen::MatrixXd::Random(n, n);
     378          10 :         matrix = (matrix + matrix.transpose()).eval();
     379          10 :         Eigen::VectorXd eigenenergies(n);
     380             : 
     381             :         // Diagonalize the matrix
     382             :         int info =
     383          10 :             LAPACKE_dsyev(LAPACK_COL_MAJOR, 'V', 'U', n, matrix.data(), n, eigenenergies.data());
     384          10 :         DOCTEST_CHECK(info == 0);
     385          10 :     }
     386           1 : }
     387             : #endif
     388             : 
     389           1 : DOCTEST_TEST_CASE("handle it gracefully if no eigenenergies are within energy restrictions") {
     390           1 :     double min_energy = -1;
     391           1 :     double max_energy = -1;
     392             : 
     393           1 :     auto &database = Database::get_global_instance();
     394             : 
     395           1 :     auto basis = BasisAtomCreator<double>(database)
     396           2 :                      .set_species("Rb")
     397           2 :                      .restrict_quantum_number("n", 58, 62)
     398           2 :                      .restrict_quantum_number("l", 0, 1)
     399           1 :                      .create();
     400             : 
     401           1 :     std::vector<std::unique_ptr<DiagonalizerInterface<double>>> diagonalizers;
     402           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerEigen<double>>());
     403             : #ifdef WITH_LAPACKE
     404           1 :     diagonalizers.push_back(std::make_unique<DiagonalizerLapackeEvd<double>>());
     405             : #endif
     406             : 
     407           3 :     for (const auto &diagonalizer : diagonalizers) {
     408           2 :         auto system = SystemAtom<double>(basis);
     409           2 :         system.set_electric_field(
     410             :             {1 * VOLT_PER_CM_IN_ATOMIC_UNITS, 0, 1 * VOLT_PER_CM_IN_ATOMIC_UNITS});
     411             : 
     412           2 :         system.diagonalize(*diagonalizer, min_energy, max_energy, 1e-6);
     413           2 :         auto eigenenergies_pairinteraction = system.get_eigenenergies();
     414             : 
     415           2 :         DOCTEST_CHECK(eigenenergies_pairinteraction.size() == 0);
     416           2 :     }
     417           1 : }
     418             : 
     419           1 : DOCTEST_TEST_CASE("atom ion interaction") {
     420           1 :     auto &database = Database::get_global_instance();
     421           1 :     DiagonalizerEigen<double> diagonalizer;
     422             : 
     423           1 :     auto ket = KetAtomCreator(database)
     424           2 :                    .set_species("Rb")
     425           2 :                    .set_quantum_number("n", 60)
     426           2 :                    .set_quantum_number("l", 1)
     427           2 :                    .set_quantum_number("j", 0.5)
     428           2 :                    .set_quantum_number("m", 0.5)
     429           1 :                    .create();
     430           1 :     double energy = ket->get_energy();
     431           1 :     double min_energy = energy - 50 / HARTREE_IN_GHZ;
     432           1 :     double max_energy = energy + 50 / HARTREE_IN_GHZ;
     433             : 
     434           1 :     auto basis = BasisAtomCreator<double>(database)
     435           2 :                      .set_species("Rb")
     436           2 :                      .restrict_quantum_number("n", 58, 62)
     437           2 :                      .restrict_quantum_number("l", 0, 3)
     438           2 :                      .restrict_quantum_number("m", 0.5, 0.5)
     439           1 :                      .create();
     440             : 
     441           1 :     auto system3 = SystemAtom<double>(basis);
     442           1 :     system3.set_ion_interaction_order(3);
     443           1 :     system3.set_ion_distance_vector({0, 0, 10 * UM_IN_ATOMIC_UNITS});
     444           1 :     system3.diagonalize(diagonalizer, min_energy, max_energy, 1e-6);
     445           1 :     auto energies3 = system3.get_eigenenergies();
     446             : 
     447           1 :     auto system2 = SystemAtom<double>(basis);
     448           1 :     system2.set_ion_interaction_order(2);
     449           1 :     system2.set_ion_distance_vector({0, 0, 10 * UM_IN_ATOMIC_UNITS});
     450           1 :     system2.diagonalize(diagonalizer, min_energy, max_energy, 1e-6);
     451           1 :     auto energies2 = system2.get_eigenenergies();
     452             : 
     453             :     // Ensure that the quadrupole order has a significant effect
     454           1 :     size_t num_energies = std::min(energies2.size(), energies3.size());
     455          17 :     for (size_t i = 0; i < num_energies; ++i) {
     456          16 :         DOCTEST_CHECK(std::abs(energies3[i] - energies2[i]) * HARTREE_IN_GHZ > 1e-6);
     457             :     }
     458           1 : }
     459             : 
     460           1 : DOCTEST_TEST_CASE("obtain the blocks of a Hamiltonian") {
     461           1 :     auto &database = Database::get_global_instance();
     462             : 
     463           1 :     auto basis = BasisAtomCreator<double>(database)
     464           2 :                      .set_species("Rb")
     465           2 :                      .restrict_quantum_number("n", 60, 60)
     466           2 :                      .restrict_quantum_number("l", 0, 1)
     467           1 :                      .create();
     468             : 
     469           1 :     auto system = SystemAtom<double>(basis);
     470           1 :     system.set_electric_field({0, 0, 0.0001});
     471             : 
     472             :     // The electric field conserves the quantum number m, thus the Hamiltonian can be
     473             :     // block-diagonalized by m
     474           1 :     system.transform(system.get_sorter({SorterType::QUANTUM_NUMBER_M}));
     475           1 :     auto blocks = system.get_indices_of_blocks({SorterType::QUANTUM_NUMBER_M});
     476           1 :     DOCTEST_CHECK(blocks.size() > 1);
     477             : 
     478           1 :     size_t expected_start = 0;
     479           5 :     for (const auto &block : blocks) {
     480           4 :         DOCTEST_MESSAGE("Block from ", block.start, " to ", block.end);
     481           4 :         DOCTEST_CHECK(block.start == expected_start);
     482           4 :         expected_start = block.end;
     483             :     }
     484           1 :     DOCTEST_CHECK(expected_start == basis->get_number_of_states());
     485             : 
     486             :     // Blocks are obtained to diagonalize the Hamiltonian block by block. Because energy blocks
     487             :     // would require an already diagonal Hamiltonian, they cannot be obtained ...
     488           3 :     DOCTEST_CHECK_THROWS_AS(system.get_indices_of_blocks({SorterType::ENERGY}),
     489             :                             std::invalid_argument);
     490             : 
     491             :     // ... whereas sorting by the energy is supported
     492           1 :     DOCTEST_CHECK_NOTHROW(system.get_sorter({SorterType::ENERGY}));
     493             : 
     494             :     // A repeated label cannot influence the order anymore and must be ignored instead of
     495             :     // terminating the program
     496           1 :     DOCTEST_CHECK_NOTHROW(system.get_sorter({SorterType::ENERGY, SorterType::ENERGY}));
     497           1 :     DOCTEST_CHECK_NOTHROW(
     498             :         system.get_sorter({SorterType::QUANTUM_NUMBER_M, SorterType::ENERGY, SorterType::ENERGY}));
     499           1 : }
     500             : 
     501           1 : DOCTEST_TEST_CASE("sort a Hamiltonian by several labels") {
     502           1 :     auto &database = Database::get_global_instance();
     503             : 
     504           1 :     auto basis = BasisAtomCreator<double>(database)
     505           2 :                      .set_species("Rb")
     506           2 :                      .restrict_quantum_number("n", 60, 60)
     507           2 :                      .restrict_quantum_number("l", 0, 1)
     508           1 :                      .create();
     509             : 
     510             :     // The first label is the primary sorting criterion, the following labels only break ties
     511             :     {
     512           1 :         auto system = SystemAtom<double>(basis);
     513           1 :         system.set_electric_field({0, 0, 0.0001});
     514           1 :         system.transform(system.get_sorter({SorterType::QUANTUM_NUMBER_M, SorterType::ENERGY}));
     515             : 
     516             :         // Sorting by m first means that states of equal m are contiguous ...
     517           1 :         auto blocks = system.get_indices_of_blocks({SorterType::QUANTUM_NUMBER_M});
     518           1 :         DOCTEST_CHECK(blocks.size() > 1);
     519             : 
     520             :         // ... and that within each block, the energies are ascending
     521           1 :         const auto &matrix = system.get_matrix();
     522           5 :         for (const auto &block : blocks) {
     523           8 :             for (auto i = static_cast<long>(block.start) + 1; i < static_cast<long>(block.end);
     524             :                  ++i) {
     525           4 :                 DOCTEST_CHECK(matrix.coeff(i - 1, i - 1) <= matrix.coeff(i, i));
     526             :             }
     527             :         }
     528           1 :     }
     529             : 
     530             :     // If the energy is the first label, the states are sorted by the energy globally
     531             :     {
     532           1 :         auto system = SystemAtom<double>(basis);
     533           1 :         system.set_electric_field({0, 0, 0.0001});
     534           1 :         system.transform(system.get_sorter({SorterType::ENERGY, SorterType::QUANTUM_NUMBER_M}));
     535             : 
     536           1 :         const auto &matrix = system.get_matrix();
     537           8 :         for (long i = 1; i < matrix.rows(); ++i) {
     538           7 :             DOCTEST_CHECK(matrix.coeff(i - 1, i - 1) <= matrix.coeff(i, i));
     539             :         }
     540           1 :     }
     541             : 
     542             :     // If the energy is enclosed by other labels, the labels before the energy take precedence over
     543             :     // the energy and the energy takes precedence over the labels after it
     544             :     {
     545           1 :         auto system = SystemAtom<double>(basis);
     546           1 :         system.set_electric_field({0, 0, 0.0001});
     547           1 :         system.transform(system.get_sorter(
     548             :             {SorterType::PARITY, SorterType::ENERGY, SorterType::QUANTUM_NUMBER_M}));
     549             : 
     550           1 :         auto sorted_basis = system.get_basis();
     551           1 :         size_t num_states = sorted_basis->get_number_of_states();
     552           1 :         const auto &matrix = system.get_matrix();
     553             : 
     554          42 :         auto parity = [&](size_t i) { return sorted_basis->get_quantum_number("parity", i); };
     555          24 :         auto energy = [&](size_t i) {
     556          24 :             return matrix.coeff(static_cast<long>(i), static_cast<long>(i));
     557           1 :         };
     558          24 :         auto quantum_number_m = [&](size_t i) { return sorted_basis->get_quantum_number("m", i); };
     559             : 
     560             :         // The parity is the primary criterion, thus it is ascending globally ...
     561           8 :         for (size_t i = 1; i < num_states; ++i) {
     562           7 :             DOCTEST_CHECK(parity(i - 1) <= parity(i));
     563             :         }
     564             : 
     565             :         // ... the energy is only ascending within a block of equal parity ...
     566           8 :         for (size_t i = 1; i < num_states; ++i) {
     567           7 :             if (parity(i - 1) == parity(i)) {
     568           6 :                 DOCTEST_CHECK(energy(i - 1) <= energy(i));
     569             :             }
     570             :         }
     571             : 
     572             :         // ... and the quantum number m only breaks ties between states of equal parity and energy
     573           1 :         size_t num_tie_breaks = 0;
     574           8 :         for (size_t i = 1; i < num_states; ++i) {
     575           7 :             if (parity(i - 1) == parity(i) && energy(i - 1) == energy(i)) {
     576           5 :                 DOCTEST_CHECK(quantum_number_m(i - 1) <= quantum_number_m(i));
     577           5 :                 ++num_tie_breaks;
     578             :             }
     579             :         }
     580             : 
     581             :         // Check that the basis contains degenerate states so that the tie breaking by m above is
     582             :         // not vacuous
     583           1 :         DOCTEST_CHECK(num_tie_breaks > 0);
     584             : 
     585             :         // If m was the primary criterion, m would be ascending globally instead
     586           1 :         bool m_ascending_globally = true;
     587           8 :         for (size_t i = 1; i < num_states; ++i) {
     588           7 :             if (quantum_number_m(i - 1) > quantum_number_m(i)) {
     589           2 :                 m_ascending_globally = false;
     590             :             }
     591             :         }
     592           1 :         DOCTEST_CHECK_FALSE(m_ascending_globally);
     593           1 :     }
     594           1 : }
     595             : 
     596             : } // namespace pairinteraction

Generated by: LCOV version 1.16