LCOV - code coverage report
Current view: top level - src/system - SystemPair.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 132 163 81.0 %
Date: 2026-06-19 12:50:25 Functions: 20 20 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/system/SystemPair.hpp"
       5             : 
       6             : #include "pairinteraction/basis/BasisAtom.hpp"
       7             : #include "pairinteraction/basis/BasisPair.hpp"
       8             : #include "pairinteraction/database/Database.hpp"
       9             : #include "pairinteraction/enums/OperatorType.hpp"
      10             : #include "pairinteraction/enums/Parity.hpp"
      11             : #include "pairinteraction/enums/TransformationType.hpp"
      12             : #include "pairinteraction/ket/KetAtom.hpp"
      13             : #include "pairinteraction/ket/KetPair.hpp"
      14             : #include "pairinteraction/system/GreenTensorInterpolator.hpp"
      15             : #include "pairinteraction/system/SystemAtom.hpp"
      16             : #include "pairinteraction/utils/Range.hpp"
      17             : #include "pairinteraction/utils/eigen_assertion.hpp"
      18             : #include "pairinteraction/utils/eigen_compat.hpp"
      19             : #include "pairinteraction/utils/operator.hpp"
      20             : #include "pairinteraction/utils/spherical.hpp"
      21             : #include "pairinteraction/utils/streamed.hpp"
      22             : #include "pairinteraction/utils/tensor.hpp"
      23             : #include "pairinteraction/utils/traits.hpp"
      24             : 
      25             : #include <Eigen/SparseCore>
      26             : #include <array>
      27             : #include <cmath>
      28             : #include <complex>
      29             : #include <limits>
      30             : #include <memory>
      31             : #include <spdlog/spdlog.h>
      32             : #include <vector>
      33             : 
      34             : namespace pairinteraction {
      35             : template <typename Scalar>
      36             : struct OperatorMatrices {
      37             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> d1;
      38             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> d2;
      39             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> q1;
      40             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> q2;
      41             : };
      42             : 
      43             : template <typename Scalar>
      44         546 : GreenTensorInterpolator<Scalar> construct_green_tensor_interpolator(
      45             :     const std::array<typename traits::NumTraits<Scalar>::real_t, 3> &distance_vector,
      46             :     int interaction_order) {
      47             :     // https://doi.org/10.1103/PhysRevA.96.062509
      48             :     // https://doi.org/10.1103/PhysRevA.82.010901
      49             :     // https://en.wikipedia.org/wiki/Table_of_spherical_harmonics
      50             : 
      51             :     using real_t = typename traits::NumTraits<Scalar>::real_t;
      52             : 
      53         546 :     GreenTensorInterpolator<Scalar> green_tensor_interpolator;
      54             : 
      55             :     // Normalize the distance vector, return zero green tensor if the distance is infinity
      56        1092 :     Eigen::Map<const Eigen::Vector3<real_t>> vector_map(distance_vector.data(),
      57         546 :                                                         distance_vector.size());
      58         546 :     real_t distance = vector_map.norm();
      59         546 :     SPDLOG_DEBUG("Interatomic distance: {}", distance);
      60         546 :     if (!std::isfinite(distance)) {
      61          22 :         return green_tensor_interpolator;
      62             :     }
      63         524 :     Eigen::Vector3<real_t> unitvec = vector_map / distance;
      64             : 
      65             :     // Dyadic green function of dipole-dipole interaction
      66         524 :     if (interaction_order >= 3) {
      67         524 :         Eigen::Matrix3<Scalar> entries =
      68         524 :             Eigen::Matrix3<real_t>::Identity() - 3 * unitvec * unitvec.transpose();
      69             : 
      70         524 :         green_tensor_interpolator.create_entries_from_cartesian(
      71        1048 :             1, 1, (entries / std::pow(distance, 3)).template cast<Scalar>());
      72             :     }
      73             : 
      74             :     // Dyadic green function of dipole-quadrupole interaction
      75         524 :     if (interaction_order >= 4) {
      76          20 :         Eigen::Matrix<real_t, 3, 9> entries = Eigen::Matrix<real_t, 3, 9>::Zero();
      77          80 :         for (Eigen::Index q = 0; q < 3; ++q) {
      78          60 :             Eigen::Index row = q;
      79         240 :             for (Eigen::Index j = 0; j < 3; ++j) {
      80         720 :                 for (Eigen::Index i = 0; i < 3; ++i) {
      81         540 :                     Eigen::Index col = 3 * j + i;
      82         540 :                     real_t v = 15 * unitvec[q] * unitvec[j] * unitvec[i];
      83         540 :                     if (i == j) v += -3 * unitvec[q];
      84         540 :                     if (i == q) v += -3 * unitvec[j];
      85         540 :                     if (j == q) v += -3 * unitvec[i];
      86         540 :                     entries(row, col) += v;
      87             :                 }
      88             :             }
      89             :         }
      90             : 
      91          20 :         green_tensor_interpolator.create_entries_from_cartesian(
      92          40 :             1, 2, (entries / std::pow(distance, 4)).template cast<Scalar>());
      93             :     }
      94             : 
      95             :     // Dyadic green function of quadrupole-dipole interaction
      96         524 :     if (interaction_order >= 4) {
      97          20 :         Eigen::Matrix<real_t, 9, 3> entries = Eigen::Matrix<real_t, 9, 3>::Zero();
      98          80 :         for (Eigen::Index q = 0; q < 3; ++q) {
      99         240 :             for (Eigen::Index j = 0; j < 3; ++j) {
     100         180 :                 Eigen::Index row = 3 * q + j;
     101         720 :                 for (Eigen::Index i = 0; i < 3; ++i) {
     102         540 :                     Eigen::Index col = i;
     103         540 :                     real_t v = -15 * unitvec[q] * unitvec[j] * unitvec[i];
     104         540 :                     if (i == j) v += 3 * unitvec[q];
     105         540 :                     if (i == q) v += 3 * unitvec[j];
     106         540 :                     if (j == q) v += 3 * unitvec[i];
     107         540 :                     entries(row, col) += v;
     108             :                 }
     109             :             }
     110             :         }
     111             : 
     112          20 :         green_tensor_interpolator.create_entries_from_cartesian(
     113          40 :             2, 1, (entries / std::pow(distance, 4)).template cast<Scalar>());
     114             :     }
     115             : 
     116             :     // Dyadic green function of quadrupole-quadrupole interaction
     117         524 :     if (interaction_order >= 5) {
     118           0 :         SPDLOG_WARN("Quadrupole-quadrupole interaction is considered but "
     119             :                     "not dipole-octupole interaction although this interaction would be "
     120             :                     "of the same order. We plan to implement dipole-octupole interaction "
     121             :                     "in the future.");
     122             : 
     123           0 :         Eigen::Matrix<real_t, 9, 9> entries = Eigen::Matrix<real_t, 9, 9>::Zero();
     124           0 :         for (Eigen::Index q = 0; q < 3; ++q) {
     125           0 :             for (Eigen::Index j = 0; j < 3; ++j) {
     126           0 :                 Eigen::Index row = 3 * q + j;
     127           0 :                 for (Eigen::Index i = 0; i < 3; ++i) {
     128           0 :                     for (Eigen::Index k = 0; k < 3; ++k) {
     129           0 :                         Eigen::Index col = 3 * i + k;
     130           0 :                         real_t v = 105 * unitvec[q] * unitvec[j] * unitvec[i] * unitvec[k];
     131           0 :                         if (i == j) v += -15 * unitvec[q] * unitvec[k];
     132           0 :                         if (i == q) v += -15 * unitvec[j] * unitvec[k];
     133           0 :                         if (j == q) v += -15 * unitvec[i] * unitvec[k];
     134           0 :                         if (k == q) v += -15 * unitvec[j] * unitvec[i];
     135           0 :                         if (k == j) v += -15 * unitvec[q] * unitvec[i];
     136           0 :                         if (k == i) v += -15 * unitvec[q] * unitvec[j];
     137           0 :                         if (q == k && i == j) v += 3;
     138           0 :                         if (i == k && j == q) v += 3;
     139           0 :                         if (j == k && i == q) v += 3;
     140           0 :                         entries(row, col) += v;
     141             :                     }
     142             :                 }
     143             :             }
     144             :         }
     145             : 
     146           0 :         green_tensor_interpolator.create_entries_from_cartesian(
     147           0 :             2, 2, (entries / std::pow(distance, 5)).template cast<Scalar>());
     148             :     }
     149             : 
     150         524 :     return green_tensor_interpolator;
     151           0 : }
     152             : 
     153             : template <typename Scalar>
     154             : OperatorMatrices<Scalar>
     155         566 : construct_operator_matrices(const GreenTensorInterpolator<Scalar> &green_tensor_interpolator,
     156             :                             const std::shared_ptr<const BasisAtom<Scalar>> &basis1,
     157             :                             const std::shared_ptr<const BasisAtom<Scalar>> &basis2) {
     158             :     // Helper function for constructing matrices of spherical harmonics operators
     159        1168 :     auto get_matrices = [](auto basis, OperatorType type, std::initializer_list<int> m,
     160             :                            bool conjugate) {
     161        1168 :         std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> matrices;
     162        1168 :         matrices.reserve(m.size());
     163        1168 :         int factor = conjugate ? -1 : 1;
     164       22192 :         std::transform(m.begin(), m.end(), std::back_inserter(matrices), [&](int q) {
     165        3504 :             auto matrix_elements = (std::pow(factor, q) *
     166             :                                     basis->get_database().get_matrix_elements_in_canonical_basis(
     167             :                                         basis, basis, type, factor * q))
     168             :                                        .eval();
     169        5184 :             return (basis->get_coefficients().adjoint() * matrix_elements *
     170        3504 :                     basis->get_coefficients())
     171        8688 :                 .eval();
     172        3504 :         });
     173        2336 :         return matrices;
     174           0 :     };
     175             : 
     176         566 :     OperatorMatrices<Scalar> op;
     177             : 
     178             :     // Operator matrices for Rydberg-Rydberg interaction
     179         588 :     if (!green_tensor_interpolator.get_spherical_entries(1, 1).empty() ||
     180          22 :         !green_tensor_interpolator.get_spherical_entries(1, 2).empty()) {
     181         544 :         op.d1 = get_matrices(basis1, OperatorType::ELECTRIC_DIPOLE, {-1, 0, +1}, true);
     182             :     }
     183         588 :     if (!green_tensor_interpolator.get_spherical_entries(1, 1).empty() ||
     184          22 :         !green_tensor_interpolator.get_spherical_entries(2, 1).empty()) {
     185         544 :         op.d2 = get_matrices(basis2, OperatorType::ELECTRIC_DIPOLE, {-1, 0, +1}, false);
     186             :     }
     187        1132 :     if (!green_tensor_interpolator.get_spherical_entries(2, 2).empty() ||
     188         566 :         !green_tensor_interpolator.get_spherical_entries(2, 1).empty()) {
     189          20 :         op.q1 = get_matrices(basis1, OperatorType::ELECTRIC_QUADRUPOLE, {-2, -1, 0, +1, +2}, true);
     190          20 :         op.q1.push_back(get_matrices(basis1, OperatorType::ELECTRIC_QUADRUPOLE_ZERO, {0}, true)[0]);
     191             :     }
     192        1132 :     if (!green_tensor_interpolator.get_spherical_entries(2, 2).empty() ||
     193         566 :         !green_tensor_interpolator.get_spherical_entries(1, 2).empty()) {
     194          20 :         op.q2 = get_matrices(basis2, OperatorType::ELECTRIC_QUADRUPOLE, {-2, -1, 0, +1, +2}, false);
     195          20 :         op.q2.push_back(
     196          40 :             get_matrices(basis2, OperatorType::ELECTRIC_QUADRUPOLE_ZERO, {0}, false)[0]);
     197             :     }
     198             : 
     199        1132 :     return op;
     200           0 : }
     201             : 
     202             : template <typename Scalar>
     203         567 : SystemPair<Scalar>::SystemPair(std::shared_ptr<const basis_t> basis)
     204         567 :     : System<SystemPair<Scalar>>(std::move(basis)) {}
     205             : 
     206             : template <typename Scalar>
     207          94 : SystemPair<Scalar> &SystemPair<Scalar>::set_interaction_order(int value) {
     208          94 :     this->hamiltonian_requires_construction = true;
     209             : 
     210          94 :     if (value < 3 || value > 5) {
     211           0 :         throw std::invalid_argument("The order must be 3, 4, or 5.");
     212             :     }
     213             : 
     214          94 :     if (green_tensor_interpolator) {
     215           0 :         throw std::invalid_argument(
     216             :             "Cannot set interaction order if a user-defined green tensor interpolator is set.");
     217             :     }
     218             : 
     219          94 :     interaction_order = value;
     220             : 
     221          94 :     return *this;
     222             : }
     223             : 
     224             : template <typename Scalar>
     225         525 : SystemPair<Scalar> &SystemPair<Scalar>::set_distance_vector(const std::array<real_t, 3> &vector) {
     226         525 :     this->hamiltonian_requires_construction = true;
     227             : 
     228         276 :     if (!traits::NumTraits<Scalar>::is_complex_v && vector[1] != 0) {
     229           1 :         throw std::invalid_argument(
     230             :             "The distance vector must not have a y-component if the scalar type is real.");
     231             :     }
     232             : 
     233         524 :     if (green_tensor_interpolator) {
     234           0 :         throw std::invalid_argument(
     235             :             "Cannot set distance vector if a user-defined green tensor interpolator is set.");
     236             :     }
     237             : 
     238         524 :     distance_vector = vector;
     239             : 
     240         524 :     return *this;
     241             : }
     242             : 
     243             : template <typename Scalar>
     244          20 : SystemPair<Scalar> &SystemPair<Scalar>::set_green_tensor_interpolator(
     245             :     const std::shared_ptr<const GreenTensorInterpolator<Scalar>> &green_tensor_interpolator) {
     246          20 :     this->hamiltonian_requires_construction = true;
     247             : 
     248          40 :     if (std::isfinite(distance_vector[0]) && std::isfinite(distance_vector[1]) &&
     249          20 :         std::isfinite(distance_vector[2])) {
     250           0 :         throw std::invalid_argument(
     251             :             "Cannot set green tensor interpolator if a finite distance vector is set.");
     252             :     }
     253             : 
     254          20 :     this->green_tensor_interpolator = green_tensor_interpolator;
     255             : 
     256          20 :     return *this;
     257             : }
     258             : 
     259             : template <typename Scalar>
     260         566 : void SystemPair<Scalar>::construct_hamiltonian() const {
     261         566 :     auto basis1 = this->basis->get_basis1();
     262         566 :     auto basis2 = this->basis->get_basis2();
     263             : 
     264         566 :     std::shared_ptr<const GreenTensorInterpolator<Scalar>> green_tensor_interpolator_ptr;
     265         566 :     if (green_tensor_interpolator) {
     266          20 :         green_tensor_interpolator_ptr = green_tensor_interpolator;
     267             :     } else {
     268         546 :         green_tensor_interpolator_ptr = std::make_shared<const GreenTensorInterpolator<Scalar>>(
     269         546 :             construct_green_tensor_interpolator<Scalar>(distance_vector, interaction_order));
     270             :     }
     271             : 
     272         566 :     auto op = construct_operator_matrices(*green_tensor_interpolator_ptr, basis1, basis2);
     273             : 
     274             :     // Construct the unperturbed Hamiltonian in the canonical pair basis
     275         566 :     this->matrix = utils::get_energies_in_canonical_basis(this->basis);
     276             : 
     277         566 :     this->hamiltonian_is_diagonal = false;
     278         566 :     bool sort_by_quantum_number_f = this->basis->has_quantum_number_f();
     279         566 :     bool sort_by_quantum_number_m = this->basis->has_quantum_number_m();
     280         566 :     bool sort_by_parity = this->basis->has_parity();
     281             : 
     282             :     // Add Rydberg-Rydberg interaction via Green tensor
     283             :     // H_RR = Σ_{ij} D_1,left[i] * G_{ij} * D_2,right[j]
     284             :     // where D_1,left uses conjugated convention and
     285             :     // D_2,right uses normal convention.
     286             : 
     287             :     // Helper function for adding Rydberg-Rydberg interaction
     288        2830 :     auto add_interaction = [this, &sort_by_quantum_number_f, &sort_by_quantum_number_m](
     289             :                                const auto &entries, const auto &op1, const auto &op2, int delta) {
     290        4106 :         for (const auto &entry : entries) {
     291        1842 :             if (std::holds_alternative<
     292        1842 :                     typename GreenTensorInterpolator<Scalar>::OmegaDependentEntry>(entry)) {
     293           0 :                 throw std::logic_error(
     294             :                     "Green tensor with omega dependent entries is currently not supported.");
     295             :             }
     296             : 
     297             :             const auto &constant_entry =
     298        1842 :                 std::get<typename GreenTensorInterpolator<Scalar>::ConstantEntry>(entry);
     299        1842 :             this->matrix += constant_entry.val() *
     300        1842 :                 utils::calculate_tensor_product_in_canonical_basis(this->basis, this->basis,
     301        1842 :                                                                    op1[constant_entry.row()],
     302        1842 :                                                                    op2[constant_entry.col()]);
     303             : 
     304        1842 :             sort_by_quantum_number_f = false;
     305        1842 :             if (constant_entry.row() != constant_entry.col() + delta) {
     306          90 :                 sort_by_quantum_number_m = false;
     307             :             }
     308             :         }
     309             :     };
     310             : 
     311             :     // Dipole-dipole interaction
     312         566 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(1, 1), op.d1, op.d2, 0);
     313             : 
     314             :     // Dipole-quadrupole interaction
     315         566 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(1, 2), op.d1, op.q2, -1);
     316             : 
     317             :     // Quadrupole-dipole interaction
     318         566 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(2, 1), op.q1, op.d2, +1);
     319             : 
     320             :     // Quadrupole-quadrupole interaction
     321         566 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(2, 2), op.q1, op.q2, 0);
     322             : 
     323             :     // Transform from the canonical basis into the actual basis
     324         566 :     this->matrix =
     325         566 :         this->basis->get_coefficients().adjoint() * this->matrix * this->basis->get_coefficients();
     326             : 
     327             :     // Store which labels can be used to block-diagonalize the Hamiltonian
     328         566 :     this->blockdiagonalizing_labels.clear();
     329         566 :     if (sort_by_quantum_number_f) {
     330           0 :         this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_F);
     331             :     }
     332         566 :     if (sort_by_quantum_number_m) {
     333         542 :         this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_M);
     334             :     }
     335         566 :     if (sort_by_parity) {
     336           0 :         this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_PARITY);
     337             :     }
     338         566 : }
     339             : 
     340             : // Explicit instantiations
     341             : template class SystemPair<double>;
     342             : template class SystemPair<std::complex<double>>;
     343             : } // namespace pairinteraction

Generated by: LCOV version 1.16