LCOV - code coverage report
Current view: top level - src/basis - BasisAtomCreator.test.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 305 305 100.0 %
Date: 2026-10-14 13:16:16 Functions: 10 10 100.0 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2024 PairInteraction Developers
       2             : // SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : #include "pairinteraction/basis/BasisAtomCreator.hpp"
       5             : 
       6             : #include "pairinteraction/basis/BasisAtom.hpp"
       7             : #include "pairinteraction/database/Database.hpp"
       8             : #include "pairinteraction/diagonalize/DiagonalizerEigen.hpp"
       9             : #include "pairinteraction/enums/OperatorType.hpp"
      10             : #include "pairinteraction/enums/SorterType.hpp"
      11             : #include "pairinteraction/ket/KetAtom.hpp"
      12             : #include "pairinteraction/ket/KetAtomCreator.hpp"
      13             : #include "pairinteraction/system/SystemAtom.hpp"
      14             : 
      15             : #include <cmath>
      16             : #include <doctest/doctest.h>
      17             : #include <stdexcept>
      18             : #include <vector>
      19             : 
      20             : namespace pairinteraction {
      21             : 
      22             : constexpr double VOLT_PER_CM_IN_ATOMIC_UNITS = 1 / 5.14220675112e9;
      23             : 
      24           1 : DOCTEST_TEST_CASE("create a basis for strontium 88") {
      25           1 :     Database &database = Database::get_global_instance();
      26           1 :     auto basis = BasisAtomCreator<double>(database)
      27           2 :                      .set_species("Sr88_sqdt")
      28           2 :                      .restrict_quantum_number("n", 60, 60)
      29           2 :                      .restrict_quantum_number("l", 0, 2)
      30           2 :                      .restrict_quantum_number("s", 0, 0)
      31           1 :                      .create();
      32          19 :     for (const auto &ket : *basis) {
      33           9 :         DOCTEST_CHECK(ket->get_species() == "Sr88_sqdt");
      34           9 :     }
      35           1 : }
      36             : 
      37           1 : DOCTEST_TEST_CASE("create a basis for strontium 87") {
      38           1 :     Database &database = Database::get_global_instance();
      39           1 :     auto basis = BasisAtomCreator<double>(database)
      40           2 :                      .set_species("Sr87_mqdt")
      41           2 :                      .restrict_quantum_number("nu", 59, 61)
      42           2 :                      .restrict_quantum_number("l", 0, 0)
      43           1 :                      .create();
      44         161 :     for (const auto &ket : *basis) {
      45          80 :         DOCTEST_CHECK(ket->get_species() == "Sr87_mqdt");
      46          80 :     }
      47           1 : }
      48             : 
      49           1 : DOCTEST_TEST_CASE("create a basis from kets") {
      50           1 :     Database &database = Database::get_global_instance();
      51           1 :     auto ket1 = KetAtomCreator(database)
      52           2 :                     .set_species("Sr88_sqdt")
      53           2 :                     .set_quantum_number("n", 59)
      54           2 :                     .set_quantum_number("l", 0)
      55           2 :                     .set_quantum_number("j", 0)
      56           2 :                     .set_quantum_number("m", 0)
      57           1 :                     .create();
      58           1 :     auto ket2 = KetAtomCreator(database)
      59           2 :                     .set_species("Sr88_sqdt")
      60           2 :                     .set_quantum_number("n", 60)
      61           2 :                     .set_quantum_number("l", 0)
      62           2 :                     .set_quantum_number("j", 0)
      63           2 :                     .set_quantum_number("m", 0)
      64           1 :                     .create();
      65           1 :     auto ket3 = KetAtomCreator(database)
      66           2 :                     .set_species("Sr88_sqdt")
      67           2 :                     .set_quantum_number("n", 61)
      68           2 :                     .set_quantum_number("l", 0)
      69           2 :                     .set_quantum_number("j", 0)
      70           2 :                     .set_quantum_number("m", 0)
      71           1 :                     .create();
      72             :     auto basis =
      73           1 :         BasisAtomCreator<double>(database).add_ket(ket1).add_ket(ket2).add_ket(ket3).create();
      74           7 :     for (const auto &ket : *basis) {
      75           3 :         DOCTEST_CHECK(ket->get_species() == "Sr88_sqdt");
      76           3 :     }
      77           1 : }
      78             : 
      79           1 : DOCTEST_TEST_CASE("create a basis and sort it according to parity and m") {
      80           1 :     Database &database = Database::get_global_instance();
      81           1 :     auto basis_unsorted = BasisAtomCreator<double>(database)
      82           2 :                               .set_species("Rb")
      83           2 :                               .restrict_quantum_number("n", 60, 60)
      84           2 :                               .restrict_quantum_number("l", 0, 3)
      85           2 :                               .restrict_quantum_number("m", -0.5, 0.5)
      86           1 :                               .create();
      87             : 
      88             :     // Sort the basis by parity and the m quantum number
      89           1 :     auto sorter = basis_unsorted->get_sorter({SorterType::PARITY, SorterType::QUANTUM_NUMBER_M});
      90           1 :     auto basis = basis_unsorted->transformed(sorter);
      91             : 
      92             :     // Check if the basis is properly sorted
      93           1 :     double parity = -1;
      94           1 :     auto quantum_number_m = std::numeric_limits<double>::lowest();
      95          15 :     for (size_t i = 0; i < basis->get_number_of_states(); ++i) {
      96          14 :         DOCTEST_MESSAGE("State ", i, ": Parity = ", basis->get_quantum_number("parity", i),
      97             :                         ", M = ", basis->get_quantum_number("m", i));
      98          14 :         DOCTEST_CHECK(basis->get_quantum_number("parity", i) >= parity);
      99          14 :         if (basis->get_quantum_number("parity", i) != parity) {
     100           1 :             parity = basis->get_quantum_number("parity", i);
     101           1 :             quantum_number_m = std::numeric_limits<double>::lowest();
     102             :         }
     103          14 :         DOCTEST_CHECK(basis->get_quantum_number("m", i) >= quantum_number_m);
     104          14 :         quantum_number_m = basis->get_quantum_number("m", i);
     105             :     }
     106             : 
     107             :     // Check that the blocks are correctly determined
     108           1 :     auto blocks = basis->get_indices_of_blocks({SorterType::PARITY, SorterType::QUANTUM_NUMBER_M});
     109           1 :     std::vector<size_t> expected_start = {0, 4, 8, 11};
     110             : 
     111           1 :     DOCTEST_CHECK(blocks.size() == expected_start.size());
     112             : 
     113           1 :     size_t idx = 0;
     114           5 :     for (const auto &block : blocks) {
     115           4 :         DOCTEST_MESSAGE("Block ", idx, " starts at ", block.start);
     116           4 :         DOCTEST_CHECK(block.start == expected_start[idx]);
     117           4 :         idx++;
     118             :     }
     119             : 
     120             :     // Transforming by the identity matrix leaves the coefficients unchanged
     121           1 :     size_t dim = basis->get_number_of_states();
     122             :     Eigen::SparseMatrix<double, Eigen::RowMajor> matrix(static_cast<long>(dim),
     123           1 :                                                         static_cast<long>(dim));
     124           1 :     matrix.setIdentity();
     125           1 :     auto transformed = basis->transformed(matrix);
     126           1 :     DOCTEST_CHECK(transformed->get_coefficients().isApprox(basis->get_coefficients()));
     127           1 : }
     128             : 
     129           1 : DOCTEST_TEST_CASE("a basis is canonical if its coefficients are the identity matrix") {
     130           1 :     auto &database = Database::get_global_instance();
     131             : 
     132           1 :     auto basis = BasisAtomCreator<double>(database)
     133           2 :                      .set_species("Rb")
     134           2 :                      .restrict_quantum_number("n", 60, 60)
     135           2 :                      .restrict_quantum_number("l", 0, 1)
     136           1 :                      .create();
     137             : 
     138           1 :     DOCTEST_CHECK(basis->is_canonical());
     139             : 
     140           1 :     auto dim = static_cast<long>(basis->get_number_of_states());
     141             : 
     142             :     // Explicitly stored zeros must not affect the result
     143           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> identity_with_zeros(dim, dim);
     144           1 :     identity_with_zeros.reserve(Eigen::VectorXi::Constant(dim, 2));
     145           9 :     for (long i = 0; i < dim; ++i) {
     146           8 :         identity_with_zeros.insert(i, i) = 1;
     147           8 :         identity_with_zeros.insert(i, (i + 1) % dim) = 0;
     148             :     }
     149           1 :     identity_with_zeros.makeCompressed();
     150           1 :     DOCTEST_CHECK(identity_with_zeros.nonZeros() > dim);
     151           1 :     DOCTEST_CHECK(basis->copy_with_coefficients(identity_with_zeros)->is_canonical());
     152             : 
     153             :     // A basis whose states are a non-trivial superposition of kets is not canonical
     154           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> non_identity(dim, dim);
     155           1 :     non_identity.setIdentity();
     156           1 :     non_identity.coeffRef(0, 0) = 0.5;
     157           1 :     DOCTEST_CHECK_FALSE(basis->copy_with_coefficients(non_identity)->is_canonical());
     158             : 
     159             :     // Sorting the basis makes it non-canonical
     160           1 :     auto sorted = basis->transformed(basis->get_sorter({SorterType::PARITY}));
     161           1 :     DOCTEST_CHECK_FALSE(sorted->is_canonical());
     162           1 : }
     163             : 
     164           1 : DOCTEST_TEST_CASE("blocks can only be obtained if the states are sorted") {
     165           1 :     Database &database = Database::get_global_instance();
     166           1 :     auto basis_unsorted = BasisAtomCreator<double>(database)
     167           2 :                               .set_species("Rb")
     168           2 :                               .restrict_quantum_number("n", 60, 60)
     169           2 :                               .restrict_quantum_number("l", 0, 3)
     170           2 :                               .restrict_quantum_number("m", -0.5, 0.5)
     171           1 :                               .create();
     172             : 
     173             :     // In the unsorted basis, states that share the same labels are scattered over the basis. They
     174             :     // would end up in several blocks and couplings between them would be lost.
     175           3 :     DOCTEST_CHECK_THROWS_AS(
     176             :         basis_unsorted->get_indices_of_blocks({SorterType::PARITY, SorterType::QUANTUM_NUMBER_M}),
     177             :         std::invalid_argument);
     178             : 
     179           1 :     auto basis = basis_unsorted->transformed(
     180           1 :         basis_unsorted->get_sorter({SorterType::PARITY, SorterType::QUANTUM_NUMBER_M}));
     181             : 
     182             :     // After sorting, the states of equal parity and m are contiguous ...
     183           1 :     DOCTEST_CHECK_NOTHROW(
     184             :         basis->get_indices_of_blocks({SorterType::PARITY, SorterType::QUANTUM_NUMBER_M}));
     185             : 
     186             :     // ... and so are the states of equal parity because the parity is the primary criterion
     187           1 :     DOCTEST_CHECK_NOTHROW(basis->get_indices_of_blocks({SorterType::PARITY}));
     188             : 
     189             :     // The states of equal m are not contiguous, however, because m only breaks ties between states
     190             :     // of equal parity
     191           3 :     DOCTEST_CHECK_THROWS_AS(basis->get_indices_of_blocks({SorterType::QUANTUM_NUMBER_M}),
     192             :                             std::invalid_argument);
     193             : 
     194             :     // If the states are not labeled at all, no blocks can be obtained either
     195           1 :     auto dim = static_cast<long>(basis->get_number_of_states());
     196           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> identity(dim, dim);
     197           1 :     identity.setIdentity();
     198           1 :     auto unlabeled = basis->copy_with_coefficients(identity);
     199           3 :     DOCTEST_CHECK_THROWS_AS(unlabeled->get_indices_of_blocks({SorterType::PARITY}),
     200             :                             std::invalid_argument);
     201           3 :     DOCTEST_CHECK_THROWS_AS(unlabeled->get_indices_of_blocks({SorterType::QUANTUM_NUMBER_F}),
     202             :                             std::invalid_argument);
     203           1 : }
     204             : 
     205           3 : DOCTEST_TEST_CASE("calculation of matrix elements") {
     206           3 :     auto &database = Database::get_global_instance();
     207             : 
     208           3 :     auto ket_s = KetAtomCreator(database)
     209           6 :                      .set_species("Rb")
     210           6 :                      .set_quantum_number("n", 60)
     211           6 :                      .set_quantum_number("l", 0)
     212           6 :                      .set_quantum_number("j", 0.5)
     213           6 :                      .set_quantum_number("m", 0.5)
     214           3 :                      .create();
     215             : 
     216           3 :     auto ket_p = KetAtomCreator(database)
     217           6 :                      .set_species("Rb")
     218           6 :                      .set_quantum_number("n", 60)
     219           6 :                      .set_quantum_number("l", 1)
     220           6 :                      .set_quantum_number("j", 0.5)
     221           6 :                      .set_quantum_number("m", 0.5)
     222           3 :                      .create();
     223             : 
     224           3 :     auto basis = BasisAtomCreator<double>(database)
     225           6 :                      .set_species("Rb")
     226           6 :                      .restrict_quantum_number("n", 59, 61)
     227           6 :                      .restrict_quantum_number("l", 0, 1)
     228           6 :                      .restrict_quantum_number("m", 0.5, 0.5)
     229           3 :                      .create();
     230             : 
     231           3 :     SystemAtom<double> system(basis);
     232             : 
     233           4 :     auto get_corresponding_state_index = [&database](const auto &b,
     234           4 :                                                      const std::shared_ptr<const KetAtom> &ket) {
     235           4 :         auto basis_ket = BasisAtomCreator<double>(database).add_ket(ket).create();
     236           4 :         Eigen::MatrixXd overlaps =
     237           8 :             Eigen::MatrixXd(b->get_matrix_elements(basis_ket, OperatorType::IDENTITY, 0))
     238             :                 .cwiseAbs();
     239           4 :         Eigen::Index idx = 0;
     240           4 :         overlaps.row(0).maxCoeff(&idx);
     241           4 :         return idx;
     242           4 :     };
     243             : 
     244           3 :     DOCTEST_SUBCASE("calculate energy") {
     245           1 :         auto basis_ket_s = BasisAtomCreator<double>(database).add_ket(ket_s).create();
     246             : 
     247           1 :         auto m1 = basis_ket_s->get_matrix_elements(basis_ket_s, OperatorType::ENERGY, 0);
     248           1 :         DOCTEST_CHECK(m1.rows() == 1);
     249           1 :         DOCTEST_CHECK(m1.cols() == 1);
     250           1 :         double energy1 = m1.coeff(0, 0);
     251             : 
     252           1 :         auto m2 = basis->get_matrix_elements(basis_ket_s, OperatorType::ENERGY, 0);
     253           1 :         DOCTEST_CHECK(m2.rows() == 1);
     254           1 :         DOCTEST_CHECK(m2.cols() == basis->get_number_of_states());
     255           1 :         double energy2 = m2.coeff(0, static_cast<int>(get_corresponding_state_index(basis, ket_s)));
     256             : 
     257           1 :         double reference = ket_s->get_energy();
     258           1 :         DOCTEST_CHECK(std::abs(energy1 - reference) < 1e-11);
     259           1 :         DOCTEST_CHECK(std::abs(energy2 - reference) < 1e-11);
     260           4 :     }
     261             : 
     262           3 :     DOCTEST_SUBCASE("calculate electric dipole matrix element") {
     263           1 :         auto basis_ket_p = BasisAtomCreator<double>(database).add_ket(ket_p).create();
     264             : 
     265           1 :         auto m = basis->get_matrix_elements(basis_ket_p, OperatorType::ELECTRIC_DIPOLE, 0);
     266           1 :         DOCTEST_CHECK(m.rows() == 1);
     267           1 :         DOCTEST_CHECK(m.cols() == basis->get_number_of_states());
     268           1 :         double dipole = m.coeff(0, static_cast<int>(get_corresponding_state_index(basis, ket_s)));
     269             : 
     270           1 :         DOCTEST_CHECK(std::abs(dipole - 1247.60438) < 1e-3);
     271           4 :     }
     272             : 
     273           3 :     DOCTEST_SUBCASE("calculate electric dipole matrix element with and without an induced dipole") {
     274             :         {
     275           1 :             auto state = basis->get_state(get_corresponding_state_index(basis, ket_s));
     276             : 
     277           1 :             auto m = state->get_matrix_elements(state, OperatorType::ELECTRIC_DIPOLE, 0);
     278           1 :             DOCTEST_CHECK(m.rows() == 1);
     279           1 :             DOCTEST_CHECK(m.cols() == 1);
     280           1 :             double dipole = m.coeff(0, 0);
     281             : 
     282           1 :             DOCTEST_CHECK(std::abs(dipole - 0) < 1e-6);
     283           1 :         }
     284             : 
     285             :         {
     286           1 :             system.set_electric_field({0, 0, VOLT_PER_CM_IN_ATOMIC_UNITS});
     287           1 :             system.diagonalize(DiagonalizerEigen<double>());
     288           1 :             auto eigenbasis = system.get_eigenbasis();
     289           1 :             auto state = eigenbasis->get_state(get_corresponding_state_index(eigenbasis, ket_s));
     290             : 
     291           1 :             auto m = state->get_matrix_elements(state, OperatorType::ELECTRIC_DIPOLE, 0);
     292           1 :             DOCTEST_CHECK(m.rows() == 1);
     293           1 :             DOCTEST_CHECK(m.cols() == 1);
     294           1 :             double dipole = m.coeff(0, 0);
     295             : 
     296           1 :             DOCTEST_CHECK(std::abs(dipole - 135.04130) < 1e-3);
     297           1 :         }
     298           3 :     }
     299           3 : }
     300             : 
     301           1 : DOCTEST_TEST_CASE("conserved quantum numbers are detected despite non-normalized coefficients") {
     302             :     // The diagonalizers prune small entries of the eigenvectors so that the columns of the
     303             :     // resulting transformation matrix are normalized only up to a defect of the order of rtol^2.
     304             :     // The detection of conserved quantum numbers must not be spoiled by this defect. Otherwise,
     305             :     // the detection would fail for a large rtol or a low floating point precision.
     306           1 :     Database &database = Database::get_global_instance();
     307           1 :     auto unsorted_basis = BasisAtomCreator<double>(database)
     308           2 :                               .set_species("Rb")
     309           2 :                               .restrict_quantum_number("n", 60, 60)
     310           2 :                               .restrict_quantum_number("l", 0, 3)
     311           1 :                               .create();
     312             :     auto basis =
     313           1 :         unsorted_basis->transformed(unsorted_basis->get_sorter({SorterType::QUANTUM_NUMBER_M}));
     314           1 :     DOCTEST_REQUIRE(basis->has_quantum_number("m"));
     315             : 
     316           1 :     auto dim = static_cast<Eigen::Index>(basis->get_number_of_states());
     317           1 :     constexpr double defect = 1e-2; // the squared norm of each column is 1 - defect
     318           1 :     const double scale = std::sqrt(1 - defect);
     319           1 :     const double sqrt_half = std::sqrt(0.5);
     320             : 
     321             :     // Superimpose pairs of states that have the same m, mimicking eigenvectors that are confined
     322             :     // to a block of constant m, and scale the columns so that they are not exactly normalized
     323           1 :     std::vector<Eigen::Triplet<double>> triplets;
     324          21 :     for (Eigen::Index i = 0; i < dim;) {
     325          20 :         auto idx = static_cast<size_t>(i);
     326          39 :         if (i + 1 < dim &&
     327          39 :             basis->get_quantum_number("m", idx) == basis->get_quantum_number("m", idx + 1)) {
     328          12 :             triplets.emplace_back(i, i, scale * sqrt_half);
     329          12 :             triplets.emplace_back(i + 1, i, scale * sqrt_half);
     330          12 :             triplets.emplace_back(i, i + 1, scale * sqrt_half);
     331          12 :             triplets.emplace_back(i + 1, i + 1, -scale * sqrt_half);
     332          12 :             i += 2;
     333             :         } else {
     334           8 :             triplets.emplace_back(i, i, scale);
     335           8 :             i += 1;
     336             :         }
     337             :     }
     338           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> transformation(dim, dim);
     339           1 :     transformation.setFromTriplets(triplets.begin(), triplets.end());
     340             : 
     341             :     // Because the transformation does not mix different m, m is still well-defined
     342           1 :     auto transformed = basis->transformed(transformation);
     343           1 :     DOCTEST_CHECK(transformed->has_quantum_number("m"));
     344          33 :     for (size_t i = 0; i < transformed->get_number_of_states(); ++i) {
     345          32 :         DOCTEST_CHECK(transformed->get_quantum_number("m", i) == basis->get_quantum_number("m", i));
     346             :     }
     347             : 
     348             :     // If, in contrast, states of different m are superimposed, m is not well-defined anymore
     349           1 :     Eigen::Index other = 0;
     350           4 :     while (other < dim &&
     351           4 :            basis->get_quantum_number("m", 0) ==
     352           4 :                basis->get_quantum_number("m", static_cast<size_t>(other))) {
     353           1 :         ++other;
     354             :     }
     355           1 :     DOCTEST_REQUIRE(other < dim);
     356           1 :     triplets.clear();
     357          33 :     for (Eigen::Index i = 0; i < dim; ++i) {
     358          32 :         if (i != 0 && i != other) {
     359          30 :             triplets.emplace_back(i, i, scale);
     360             :         }
     361             :     }
     362           1 :     triplets.emplace_back(0, 0, scale * sqrt_half);
     363           1 :     triplets.emplace_back(other, 0, scale * sqrt_half);
     364           1 :     triplets.emplace_back(0, other, scale * sqrt_half);
     365           1 :     triplets.emplace_back(other, other, -scale * sqrt_half);
     366           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> mixing_transformation(dim, dim);
     367           1 :     mixing_transformation.setFromTriplets(triplets.begin(), triplets.end());
     368             : 
     369           1 :     auto mixed = basis->transformed(mixing_transformation);
     370           1 :     DOCTEST_CHECK_FALSE(mixed->has_quantum_number("m"));
     371           3 :     DOCTEST_CHECK_THROWS_AS(mixed->get_quantum_number("m", 0), std::invalid_argument);
     372             : 
     373             :     // An explicitly stored zero that couples a state to a state without a well-defined m must not
     374             :     // spoil the m of the state
     375           1 :     Eigen::Index target = 0;
     376           3 :     while (target == 0 || target == other) {
     377           2 :         ++target;
     378             :     }
     379           1 :     DOCTEST_REQUIRE(target < dim);
     380           1 :     triplets.clear();
     381          33 :     for (Eigen::Index i = 0; i < dim; ++i) {
     382          32 :         triplets.emplace_back(i, i, 1);
     383             :     }
     384           1 :     triplets.emplace_back(0, target, 0);
     385           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> transformation_with_zero(dim, dim);
     386           1 :     transformation_with_zero.setFromTriplets(triplets.begin(), triplets.end());
     387           1 :     DOCTEST_REQUIRE(transformation_with_zero.nonZeros() == dim + 1);
     388             : 
     389           1 :     auto transformed_with_zero = mixed->transformed(transformation_with_zero);
     390           1 :     DOCTEST_CHECK(transformed_with_zero->get_quantum_number("m", static_cast<size_t>(target)) ==
     391             :                   basis->get_quantum_number("m", static_cast<size_t>(target)));
     392           1 : }
     393             : 
     394           1 : DOCTEST_TEST_CASE("energies of the states of a basis") {
     395           1 :     Database &database = Database::get_global_instance();
     396           1 :     auto basis = BasisAtomCreator<double>(database)
     397           2 :                      .set_species("Rb")
     398           2 :                      .restrict_quantum_number("n", 60, 60)
     399           2 :                      .restrict_quantum_number("l", 0, 1)
     400           1 :                      .create();
     401           1 :     auto dim = static_cast<Eigen::Index>(basis->get_number_of_states());
     402             : 
     403             :     // The states of a canonical basis have the energies of the kets, by which they are sorted
     404           1 :     DOCTEST_CHECK(basis->has_energy());
     405           1 :     DOCTEST_CHECK(basis->is_sorted_by_energy());
     406           9 :     for (size_t i = 0; i < basis->get_number_of_states(); ++i) {
     407           8 :         DOCTEST_CHECK(basis->get_energy(i) == basis->get_ket(i)->get_energy());
     408           8 :         DOCTEST_CHECK(basis->get_state(i)->get_energy(0) == basis->get_ket(i)->get_energy());
     409             :     }
     410             : 
     411             :     // Sorting permutes the energies along with the states
     412           1 :     auto sorter = basis->get_sorter({SorterType::QUANTUM_NUMBER_M});
     413           1 :     auto sorted = basis->transformed(sorter);
     414           1 :     DOCTEST_CHECK(sorted->has_energy());
     415           1 :     DOCTEST_CHECK_FALSE(sorted->is_sorted_by_energy());
     416           9 :     for (Eigen::Index i = 0; i < dim; ++i) {
     417           8 :         DOCTEST_CHECK(sorted->get_energy(static_cast<size_t>(i)) ==
     418             :                       basis->get_energy(static_cast<size_t>(sorter.indices()[i])));
     419             :     }
     420             : 
     421             :     // In general, transformed states do not have well-defined energies
     422           1 :     Eigen::SparseMatrix<double, Eigen::RowMajor> identity(dim, dim);
     423           1 :     identity.setIdentity();
     424           1 :     auto transformed = basis->transformed(identity);
     425           1 :     DOCTEST_CHECK_FALSE(transformed->has_energy());
     426           1 :     DOCTEST_CHECK_FALSE(transformed->is_sorted_by_energy());
     427           1 :     DOCTEST_CHECK_THROWS_AS(transformed->get_energy(0), std::invalid_argument);
     428             : 
     429             :     // Unless the energies of the transformed states are explicitly specified
     430           1 :     std::vector<double> energies(basis->get_number_of_states());
     431           9 :     for (size_t i = 0; i < energies.size(); ++i) {
     432           8 :         energies[i] = basis->get_energy(i);
     433             :     }
     434           1 :     auto transformed_with_energies = basis->transformed(identity, energies);
     435           1 :     DOCTEST_CHECK(transformed_with_energies->is_sorted_by_energy());
     436           9 :     for (size_t i = 0; i < energies.size(); ++i) {
     437           8 :         DOCTEST_CHECK(transformed_with_energies->get_energy(i) == energies[i]);
     438             :     }
     439           3 :     DOCTEST_CHECK_THROWS_AS(basis->transformed(identity, {energies[0]}), std::invalid_argument);
     440             : 
     441             :     // Arbitrary coefficients do not have a well-defined energy
     442           1 :     auto copied = basis->copy_with_coefficients(basis->get_coefficients());
     443           1 :     DOCTEST_CHECK_THROWS_AS(copied->get_energy(0), std::invalid_argument);
     444           1 :     DOCTEST_CHECK_FALSE(copied->has_energy());
     445           1 : }
     446             : 
     447             : } // namespace pairinteraction

Generated by: LCOV version 1.16