LCOV - code coverage report
Current view: top level - src/basis - Basis.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 234 249 94.0 %
Date: 2026-10-14 13:16:16 Functions: 120 144 83.3 %

          Line data    Source code
       1             : // SPDX-FileCopyrightText: 2024 PairInteraction Developers
       2             : // SPDX-License-Identifier: LGPL-3.0-or-later
       3             : 
       4             : #include "pairinteraction/basis/Basis.hpp"
       5             : 
       6             : #include "pairinteraction/basis/BasisAtom.hpp"
       7             : #include "pairinteraction/basis/BasisPair.hpp"
       8             : #include "pairinteraction/enums/SorterType.hpp"
       9             : #include "pairinteraction/ket/KetAtom.hpp"
      10             : #include "pairinteraction/ket/KetPair.hpp"
      11             : #include "pairinteraction/utils/TaskControl.hpp"
      12             : #include "pairinteraction/utils/eigen_assertion.hpp"
      13             : #include "pairinteraction/utils/eigen_compat.hpp"
      14             : 
      15             : #include <algorithm>
      16             : #include <cassert>
      17             : #include <cmath>
      18             : #include <cstdint>
      19             : #include <map>
      20             : #include <set>
      21             : #include <string>
      22             : #include <vector>
      23             : 
      24             : namespace pairinteraction {
      25             : 
      26             : template <typename Scalar>
      27             : class BasisAtom;
      28             : 
      29             : template <typename Derived>
      30         666 : void Basis<Derived>::perform_blocks_checks(const std::set<SorterType> &unique_labels) const {
      31             :     // Currently, since states in a basis dont store the energy, they cannot be sorted by energy.
      32         666 :     if (unique_labels.contains(SorterType::ENERGY)) {
      33           1 :         throw std::invalid_argument("Blocks cannot be obtained by the energy. Note that sorting "
      34             :                                     "a system by the energy is supported nevertheless.");
      35             :     }
      36             : 
      37             :     // Check if the states are labeled by the requested labels
      38         634 :     std::vector<const std::vector<real_t> *> quantum_numbers_of_labels;
      39         634 :     quantum_numbers_of_labels.reserve(unique_labels.size());
      40        1294 :     for (const auto &label : unique_labels) {
      41         621 :         quantum_numbers_of_labels.push_back(&get_quantum_numbers_of_states(label));
      42             :     }
      43             : 
      44             :     // Check if the states are sorted by the requested labels, i.e., if states that share the same
      45             :     // labels are contiguous. Otherwise, states belonging to the same block would be scattered over
      46             :     // several blocks and couplings between them would be lost.
      47             :     // The quantum numbers are (half-)integers, thus doubling them yields an exact integer
      48             :     using label_t = std::vector<int64_t>;
      49       37642 :     auto label_of = [&](Eigen::Index i) {
      50       36993 :         label_t label;
      51       36993 :         label.reserve(quantum_numbers_of_labels.size());
      52       75236 :         for (const auto *quantum_numbers : quantum_numbers_of_labels) {
      53       38250 :             label.push_back(std::llround(2 * (*quantum_numbers)[i]));
      54             :         }
      55       36880 :         return label;
      56           0 :     };
      57             : 
      58             :     // Collect the labels of the contiguous blocks of states
      59         649 :     std::vector<label_t> labels_of_blocks;
      60       37541 :     for (Eigen::Index i = 0; i < coefficients.cols(); ++i) {
      61       37040 :         label_t label = label_of(i);
      62       36884 :         if (labels_of_blocks.empty() || labels_of_blocks.back() != label) {
      63        1378 :             labels_of_blocks.push_back(label);
      64             :         }
      65             :     }
      66             : 
      67             :     // If the states are sorted, no two blocks share the same labels
      68         666 :     std::sort(labels_of_blocks.begin(), labels_of_blocks.end());
      69         647 :     if (std::adjacent_find(labels_of_blocks.begin(), labels_of_blocks.end()) !=
      70        1286 :         labels_of_blocks.end()) {
      71           2 :         throw std::invalid_argument("The states are not sorted by the requested labels.");
      72             :     }
      73         645 : }
      74             : 
      75             : template <typename Derived>
      76        2218 : Basis<Derived>::Basis(ketvec_t &&kets)
      77        4436 :     : kets(std::move(kets)), coefficients(static_cast<Eigen::Index>(this->kets.size()),
      78        6654 :                                           static_cast<Eigen::Index>(this->kets.size())) {
      79        2218 :     if (this->kets.empty()) {
      80           0 :         throw std::invalid_argument("The basis must contain at least one element.");
      81             :     }
      82        2218 :     make_canonical();
      83        2218 : }
      84             : 
      85             : template <typename Derived>
      86        2386 : void Basis<Derived>::make_canonical() {
      87        2386 :     auto n = static_cast<Eigen::Index>(kets.size());
      88        2386 :     if (coefficients.rows() != n || coefficients.cols() != n) {
      89           0 :         throw std::invalid_argument("The coefficient matrix must be of shape n x n, where n is "
      90             :                                     "the number of kets.");
      91             :     }
      92             : 
      93             :     // Make the i-th state equal to the i-th ket
      94        2386 :     coefficients.setIdentity();
      95             : 
      96             :     // Assign each state the quantum numbers of the ket it is equal to
      97        9544 :     for (const auto &[label, name] : Derived::sorter_type_to_quantum_number_name) {
      98        7158 :         std::vector<real_t> quantum_numbers;
      99        7158 :         quantum_numbers.reserve(kets.size());
     100     8020923 :         for (const auto &ket : kets) {
     101     8013765 :             quantum_numbers.push_back(ket->has_quantum_number(name)
     102    13232767 :                                           ? static_cast<real_t>(ket->get_quantum_number(name))
     103     5219002 :                                           : std::numeric_limits<real_t>::max());
     104             :         }
     105        7158 :         quantum_numbers_of_states[name] = std::move(quantum_numbers);
     106             :     }
     107             : 
     108             :     // Assign each state the energy of the ket it is equal to
     109        2386 :     energy_of_states.clear();
     110        2386 :     energy_of_states.reserve(kets.size());
     111     2673641 :     for (const auto &ket : kets) {
     112     2671255 :         energy_of_states.push_back(static_cast<real_t>(ket->get_energy()));
     113             :     }
     114        2386 : }
     115             : 
     116             : template <typename Derived>
     117        6235 : bool Basis<Derived>::has_quantum_number(const std::string &name) const {
     118        6235 :     auto it = quantum_numbers_of_states.find(name);
     119        6197 :     if (it == quantum_numbers_of_states.end()) {
     120           0 :         return false;
     121             :     }
     122      457486 :     return std::none_of(it->second.begin(), it->second.end(), [](real_t quantum_number) {
     123      451308 :         return quantum_number == std::numeric_limits<real_t>::max();
     124        6233 :     });
     125             : }
     126             : 
     127             : template <typename Derived>
     128        1602 : bool Basis<Derived>::has_energy() const {
     129        1602 :     return std::none_of(energy_of_states.begin(), energy_of_states.end(),
     130      192025 :                         [](real_t energy) { return energy == std::numeric_limits<real_t>::max(); });
     131             : }
     132             : 
     133             : template <typename Derived>
     134         505 : bool Basis<Derived>::is_sorted_by_energy() const {
     135         505 :     return has_energy() && std::is_sorted(energy_of_states.begin(), energy_of_states.end());
     136             : }
     137             : 
     138             : template <typename Derived>
     139        6084 : const Derived &Basis<Derived>::derived() const {
     140        6084 :     return static_cast<const Derived &>(*this);
     141             : }
     142             : 
     143             : template <typename Derived>
     144             : const std::vector<typename Basis<Derived>::real_t> &
     145        1885 : Basis<Derived>::get_quantum_numbers_of_states(SorterType label) const {
     146        1885 :     auto it = Derived::sorter_type_to_quantum_number_name.find(label);
     147        1846 :     if (it == Derived::sorter_type_to_quantum_number_name.end()) {
     148           0 :         throw std::invalid_argument(
     149             :             "The states of this basis cannot be labeled by the requested sorter type.");
     150             :     }
     151        1852 :     const std::string &name = it->second;
     152        1850 :     if (!has_quantum_number(name)) {
     153           2 :         throw std::invalid_argument(
     154             :             "States cannot be labeled and thus not sorted by the quantum number " + name + ".");
     155             :     }
     156        3800 :     return quantum_numbers_of_states.at(name);
     157             : }
     158             : 
     159             : template <typename Derived>
     160        2357 : const typename Basis<Derived>::ketvec_t &Basis<Derived>::get_kets() const {
     161        2357 :     return kets;
     162             : }
     163             : 
     164             : template <typename Derived>
     165             : const Eigen::SparseMatrix<typename Basis<Derived>::scalar_t, Eigen::RowMajor> &
     166       15495 : Basis<Derived>::get_coefficients() const {
     167       15495 :     return coefficients;
     168             : }
     169             : 
     170             : template <typename Derived>
     171        1129 : std::shared_ptr<const Derived> Basis<Derived>::copy_with_coefficients(
     172             :     const Eigen::SparseMatrix<scalar_t, Eigen::RowMajor> &values) const {
     173        1129 :     if (values.rows() != coefficients.rows()) {
     174           0 :         throw std::invalid_argument("Incompatible number of rows.");
     175             :     }
     176        1129 :     if (values.cols() != coefficients.cols()) {
     177           0 :         throw std::invalid_argument("Incompatible number of columns.");
     178             :     }
     179             : 
     180             :     // Create a copy of the current object and update the coefficients of the copy
     181        1129 :     auto result = std::make_shared<Derived>(derived());
     182             : 
     183        1129 :     result->coefficients = values;
     184             : 
     185        4516 :     for (auto &[name, quantum_numbers] : result->quantum_numbers_of_states) {
     186        3387 :         std::fill(quantum_numbers.begin(), quantum_numbers.end(),
     187        6774 :                   std::numeric_limits<real_t>::max());
     188             :     }
     189        1129 :     std::fill(result->energy_of_states.begin(), result->energy_of_states.end(),
     190        1129 :               std::numeric_limits<real_t>::max());
     191             : 
     192        2258 :     return result;
     193        1129 : }
     194             : 
     195             : template <typename Derived>
     196     5389722 : typename Basis<Derived>::real_t Basis<Derived>::get_quantum_number(const std::string &name,
     197             :                                                                    size_t state_index) const {
     198     5389722 :     auto it = quantum_numbers_of_states.find(name);
     199     5389722 :     if (it == quantum_numbers_of_states.end()) {
     200           0 :         throw std::invalid_argument("The states are not labeled by the quantum number " + name +
     201             :                                     ".");
     202             :     }
     203     5389722 :     real_t quantum_number = it->second.at(state_index);
     204     5389722 :     if (quantum_number == std::numeric_limits<real_t>::max()) {
     205           1 :         throw std::invalid_argument("The state does not have a well-defined quantum number " +
     206             :                                     name + ".");
     207             :     }
     208     5389721 :     return quantum_number;
     209             : }
     210             : 
     211             : template <typename Derived>
     212      112446 : typename Basis<Derived>::real_t Basis<Derived>::get_energy(size_t state_index) const {
     213      112446 :     real_t energy = energy_of_states.at(state_index);
     214      112446 :     if (energy == std::numeric_limits<real_t>::max()) {
     215           2 :         throw std::invalid_argument("The state does not have a well-defined energy.");
     216             :     }
     217      112444 :     return energy;
     218             : }
     219             : 
     220             : template <typename Derived>
     221        2624 : std::shared_ptr<const Derived> Basis<Derived>::get_state(size_t state_index) const {
     222             :     // Create a copy of the current object
     223        2624 :     auto restricted = std::make_shared<Derived>(derived());
     224             : 
     225             :     // Restrict the copy to the single requested state
     226        2624 :     restricted->coefficients = restricted->coefficients.col(state_index);
     227             : 
     228       10496 :     for (auto &[name, quantum_numbers] : restricted->quantum_numbers_of_states) {
     229        7872 :         quantum_numbers = {quantum_numbers_of_states.at(name)[state_index]};
     230             :     }
     231        2624 :     restricted->energy_of_states = {energy_of_states.at(state_index)};
     232             : 
     233        5248 :     return restricted;
     234        2624 : }
     235             : 
     236             : template <typename Derived>
     237             : std::shared_ptr<const typename Basis<Derived>::ket_t>
     238      186464 : Basis<Derived>::get_ket(size_t ket_index) const {
     239      186464 :     return kets[ket_index];
     240             : }
     241             : 
     242             : template <typename Derived>
     243           5 : typename Basis<Derived>::Iterator Basis<Derived>::begin() const {
     244           5 :     return kets.begin();
     245             : }
     246             : 
     247             : template <typename Derived>
     248           5 : typename Basis<Derived>::Iterator Basis<Derived>::end() const {
     249           5 :     return kets.end();
     250             : }
     251             : 
     252             : template <typename Derived>
     253          10 : Basis<Derived>::Iterator::Iterator(typename ketvec_t::const_iterator it) : it{std::move(it)} {}
     254             : 
     255             : template <typename Derived>
     256         497 : bool Basis<Derived>::Iterator::operator!=(const Iterator &other) const {
     257         497 :     return other.it != it;
     258             : }
     259             : 
     260             : template <typename Derived>
     261         492 : std::shared_ptr<const typename Basis<Derived>::ket_t> Basis<Derived>::Iterator::operator*() const {
     262         492 :     return *it;
     263             : }
     264             : 
     265             : template <typename Derived>
     266         492 : typename Basis<Derived>::Iterator &Basis<Derived>::Iterator::operator++() {
     267         492 :     ++it;
     268         492 :     return *this;
     269             : }
     270             : 
     271             : template <typename Derived>
     272        7626 : size_t Basis<Derived>::get_number_of_states() const {
     273        7626 :     return coefficients.cols();
     274             : }
     275             : 
     276             : template <typename Derived>
     277       49383 : size_t Basis<Derived>::get_number_of_kets() const {
     278       49383 :     return coefficients.rows();
     279             : }
     280             : 
     281             : template <typename Derived>
     282             : Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic>
     283           6 : Basis<Derived>::get_sorter(const std::vector<SorterType> &labels) const {
     284             :     // Note that sorting by the energy is rejected by get_sorter_without_checks
     285             : 
     286             :     // Initialize the sorter
     287           6 :     Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> sorter(coefficients.cols());
     288           6 :     sorter.setIdentity();
     289             : 
     290           6 :     get_sorter_without_checks(labels, sorter);
     291             : 
     292           6 :     return sorter;
     293           0 : }
     294             : 
     295             : template <typename Derived>
     296             : std::vector<IndicesOfBlock>
     297           7 : Basis<Derived>::get_indices_of_blocks(const std::vector<SorterType> &labels) const {
     298           7 :     std::set<SorterType> unique_labels(labels.begin(), labels.end());
     299           7 :     perform_blocks_checks(unique_labels);
     300             : 
     301             :     // Get the blocks
     302           3 :     IndicesOfBlocksCreator blocks_creator({0, static_cast<size_t>(coefficients.cols())});
     303           3 :     get_indices_of_blocks_without_checks(unique_labels, blocks_creator);
     304             : 
     305           6 :     return blocks_creator.create();
     306           7 : }
     307             : 
     308             : template <typename Derived>
     309         567 : void Basis<Derived>::get_sorter_without_checks(
     310             :     const std::vector<SorterType> &labels,
     311             :     Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> &sorter) const {
     312         567 :     constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
     313             : 
     314             :     // Currently, since states in a basis dont store the energy, they cannot be sorted by energy.
     315             :     // Checking this upfront also guarantees that only labels that refer to a quantum number are
     316             :     // encountered below.
     317         567 :     if (std::find(labels.begin(), labels.end(), SorterType::ENERGY) != labels.end()) {
     318           0 :         throw std::invalid_argument(
     319             :             "States in a basis do not store the energy and thus can not be sorted by it. "
     320             :             "Note that sorting a system by the energy is supported nevertheless.");
     321             :     }
     322             : 
     323         576 :     if (coefficients.cols() == 0) {
     324           0 :         return;
     325             :     }
     326             : 
     327         574 :     std::vector<const std::vector<real_t> *> quantum_numbers_of_labels;
     328         574 :     quantum_numbers_of_labels.reserve(labels.size());
     329        1200 :     for (const auto &label : labels) {
     330         621 :         quantum_numbers_of_labels.push_back(&get_quantum_numbers_of_states(label));
     331             :     }
     332             : 
     333         570 :     int *perm_begin = sorter.indices().data();
     334         564 :     int *perm_end = perm_begin + coefficients.cols();
     335             : 
     336             :     // Sort the vector based on the requested labels
     337         569 :     set_task_status("Sorting basis states...");
     338      146582 :     std::stable_sort(perm_begin, perm_end, [&](int a, int b) {
     339      264350 :         for (const auto *quantum_numbers : quantum_numbers_of_labels) {
     340      169636 :             if (std::abs((*quantum_numbers)[a] - (*quantum_numbers)[b]) > numerical_precision) {
     341       54768 :                 return (*quantum_numbers)[a] < (*quantum_numbers)[b];
     342             :             }
     343             :         }
     344       93528 :         return false; // Elements are equal
     345             :     });
     346         605 : }
     347             : 
     348             : template <typename Derived>
     349         568 : void Basis<Derived>::get_indices_of_blocks_without_checks(
     350             :     const std::set<SorterType> &unique_labels, IndicesOfBlocksCreator &blocks_creator) const {
     351         568 :     constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
     352             : 
     353         568 :     if (coefficients.cols() == 0) {
     354           0 :         return;
     355             :     }
     356             : 
     357         560 :     std::vector<const std::vector<real_t> *> quantum_numbers_of_labels;
     358         560 :     quantum_numbers_of_labels.reserve(unique_labels.size());
     359        1208 :     for (const auto &label : unique_labels) {
     360         625 :         quantum_numbers_of_labels.push_back(&get_quantum_numbers_of_states(label));
     361             :     }
     362             : 
     363             :     // A new block starts whenever a state differs from its predecessor in one of the labels
     364         587 :     set_task_status("Identifying basis blocks...");
     365       31657 :     for (int i = 1; i < coefficients.cols(); ++i) {
     366       66987 :         for (const auto *quantum_numbers : quantum_numbers_of_labels) {
     367       36467 :             if (std::abs((*quantum_numbers)[i] - (*quantum_numbers)[i - 1]) > numerical_precision) {
     368         736 :                 blocks_creator.add(i);
     369         735 :                 break;
     370             :             }
     371             :         }
     372             :     }
     373         548 : }
     374             : 
     375             : template <typename Derived>
     376         168 : std::shared_ptr<const Derived> Basis<Derived>::canonicalized() const {
     377         168 :     auto result = std::make_shared<Derived>(derived());
     378         168 :     size_t n = kets.size();
     379         168 :     result->coefficients.resize(n, n);
     380         168 :     result->make_canonical();
     381         336 :     return result;
     382         168 : }
     383             : 
     384             : template <typename Derived>
     385         112 : bool Basis<Derived>::is_canonical() const {
     386         112 :     constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
     387             : 
     388             :     // The basis is canonical if and only if its coefficient matrix is the identity matrix, i.e.,
     389             :     // if the i-th state is the i-th ket
     390         112 :     if (coefficients.rows() != coefficients.cols()) {
     391           0 :         return false;
     392             :     }
     393             : 
     394         112 :     Eigen::Index num_ones = 0;
     395        2064 :     for (Eigen::Index row = 0; row < coefficients.outerSize(); ++row) {
     396        1954 :         for (typename Eigen::SparseMatrix<scalar_t, Eigen::RowMajor>::InnerIterator it(coefficients,
     397             :                                                                                        row);
     398        3914 :              it; ++it) {
     399        3915 :             if (it.row() == it.col() &&
     400        2923 :                 std::abs(it.value() - static_cast<scalar_t>(1)) <= numerical_precision) {
     401        1952 :                 ++num_ones;
     402          10 :             } else if (std::abs(it.value()) > numerical_precision) {
     403           2 :                 return false;
     404             :             }
     405             :         }
     406             :     }
     407         110 :     return num_ones == coefficients.rows();
     408             : }
     409             : 
     410             : template <typename Derived>
     411        1256 : std::shared_ptr<const Derived> Basis<Derived>::transformed(
     412             :     const Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> &sorter) const {
     413             :     // Create a copy of the current object
     414        1256 :     auto transformed = std::make_shared<Derived>(derived());
     415             : 
     416        1260 :     if (coefficients.cols() == 0) {
     417           0 :         return transformed;
     418             :     }
     419             : 
     420             :     // Apply the sorting
     421        1248 :     set_task_status("Applying basis sorting...");
     422        1264 :     transformed->coefficients = coefficients * sorter;
     423             : 
     424        1258 :     set_task_status("Relabeling sorted basis states...");
     425        4828 :     for (auto &[name, transformed_quantum_numbers] : transformed->quantum_numbers_of_states) {
     426        3784 :         const auto &quantum_numbers = quantum_numbers_of_states.at(name);
     427        3761 :         transformed_quantum_numbers.resize(sorter.size());
     428      193312 :         for (int i = 0; i < sorter.size(); ++i) {
     429      189183 :             transformed_quantum_numbers[i] = quantum_numbers[sorter.indices()[i]];
     430             :         }
     431             :     }
     432        1260 :     transformed->energy_of_states.resize(sorter.size());
     433       65095 :     for (int i = 0; i < sorter.size(); ++i) {
     434       63635 :         transformed->energy_of_states[i] = energy_of_states[sorter.indices()[i]];
     435             :     }
     436             : 
     437        1235 :     return transformed;
     438        1261 : }
     439             : 
     440             : template <typename Derived>
     441             : std::shared_ptr<const Derived>
     442         916 : Basis<Derived>::transformed(const Eigen::SparseMatrix<scalar_t, Eigen::RowMajor> &transformation,
     443             :                             const std::vector<real_t> &energy_of_transformed_states) const {
     444        1790 :     if (!energy_of_transformed_states.empty() &&
     445         884 :         energy_of_transformed_states.size() != static_cast<size_t>(transformation.cols())) {
     446           1 :         throw std::invalid_argument(
     447             :             "The number of energies must match the number of transformed states.");
     448             :     }
     449             : 
     450         913 :     const real_t numerical_precision =
     451         897 :         100 * std::sqrt(coefficients.rows()) * std::numeric_limits<real_t>::epsilon();
     452             : 
     453             :     // Create a copy of the current object
     454         913 :     auto transformed = std::make_shared<Derived>(derived());
     455             : 
     456         924 :     if (coefficients.cols() == 0) {
     457           0 :         return transformed;
     458             :     }
     459             : 
     460             :     // Apply the transformation
     461         922 :     set_task_status("Applying basis transformation...");
     462         926 :     transformed->coefficients = coefficients * transformation;
     463             : 
     464         925 :     Eigen::SparseMatrix<real_t> probs = transformation.cwiseAbs2().transpose();
     465             :     // Drop explicitly stored zeros
     466         920 :     probs.prune(
     467      902887 :         [](Eigen::Index /*row*/, Eigen::Index /*col*/, const real_t &value) { return value != 0; });
     468         925 :     Eigen::VectorX<real_t> norm = probs * Eigen::VectorX<real_t>::Ones(probs.cols());
     469             : 
     470         920 :     set_task_status("Updating transformed quantum numbers...");
     471        3656 :     for (auto &[name, transformed_quantum_numbers] : transformed->quantum_numbers_of_states) {
     472        2753 :         const auto &quantum_numbers = quantum_numbers_of_states.at(name);
     473        5508 :         auto map = Eigen::Map<const Eigen::VectorX<real_t>>(
     474        2750 :             quantum_numbers.data(), static_cast<Eigen::Index>(quantum_numbers.size()));
     475        2734 :         Eigen::VectorX<real_t> val = probs * map;
     476        2776 :         Eigen::VectorX<real_t> sq = probs * map.cwiseAbs2();
     477        2771 :         transformed_quantum_numbers.resize(probs.rows());
     478             : 
     479      284180 :         for (size_t i = 0; i < transformed_quantum_numbers.size(); ++i) {
     480      281463 :             const real_t mean = val[i] / norm[i];
     481      281240 :             const real_t mean_sq = sq[i] / norm[i];
     482      281287 :             const real_t variance = std::abs(mean_sq - mean * mean);
     483      281506 :             if (variance < numerical_precision * std::max(static_cast<real_t>(1), mean_sq)) {
     484      157249 :                 transformed_quantum_numbers[i] = std::round(mean * 2) / 2;
     485             :             } else {
     486      124478 :                 transformed_quantum_numbers[i] = std::numeric_limits<real_t>::max();
     487             :             }
     488             :         }
     489             :     }
     490             : 
     491             :     // The transformed states only have well-defined energies if they are explicitly specified,
     492             :     // e.g., as eigenenergies
     493         923 :     if (energy_of_transformed_states.empty()) {
     494          48 :         transformed->energy_of_states.assign(static_cast<size_t>(transformation.cols()),
     495          48 :                                              std::numeric_limits<real_t>::max());
     496             :     } else {
     497         897 :         transformed->energy_of_states = energy_of_transformed_states;
     498             :     }
     499             : 
     500         909 :     return transformed;
     501         918 : }
     502             : 
     503             : // Explicit instantiations
     504             : template class Basis<BasisAtom<double>>;
     505             : template class Basis<BasisAtom<std::complex<double>>>;
     506             : template class Basis<BasisPair<double>>;
     507             : template class Basis<BasisPair<std::complex<double>>>;
     508             : } // namespace pairinteraction

Generated by: LCOV version 1.16