LCOV - code coverage report
Current view: top level - src/system - SystemPair.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 127 171 74.3 %
Date: 2026-03-03 11:25:29 Functions: 22 24 91.7 %

          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/enums/OperatorType.hpp"
       9             : #include "pairinteraction/enums/Parity.hpp"
      10             : #include "pairinteraction/enums/TransformationType.hpp"
      11             : #include "pairinteraction/ket/KetAtom.hpp"
      12             : #include "pairinteraction/ket/KetPair.hpp"
      13             : #include "pairinteraction/operator/OperatorAtom.hpp"
      14             : #include "pairinteraction/operator/OperatorPair.hpp"
      15             : #include "pairinteraction/system/GreenTensorInterpolator.hpp"
      16             : #include "pairinteraction/system/SystemAtom.hpp"
      17             : #include "pairinteraction/utils/Range.hpp"
      18             : #include "pairinteraction/utils/eigen_assertion.hpp"
      19             : #include "pairinteraction/utils/eigen_compat.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 <complex>
      28             : #include <limits>
      29             : #include <memory>
      30             : #include <spdlog/spdlog.h>
      31             : #include <vector>
      32             : 
      33             : namespace pairinteraction {
      34             : template <typename Scalar>
      35             : struct OperatorMatrices {
      36             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> d1;
      37             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> d2;
      38             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> q1;
      39             :     std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> q2;
      40             : };
      41             : 
      42             : template <typename Scalar>
      43         527 : GreenTensorInterpolator<Scalar> construct_green_tensor_interpolator(
      44             :     const std::array<typename traits::NumTraits<Scalar>::real_t, 3> &distance_vector,
      45             :     int interaction_order) {
      46             :     // https://doi.org/10.1103/PhysRevA.96.062509
      47             :     // https://doi.org/10.1103/PhysRevA.82.010901
      48             :     // https://en.wikipedia.org/wiki/Table_of_spherical_harmonics
      49             : 
      50             :     using real_t = typename traits::NumTraits<Scalar>::real_t;
      51             : 
      52         527 :     GreenTensorInterpolator<Scalar> green_tensor_interpolator;
      53             : 
      54             :     // Normalize the distance vector, return zero green tensor if the distance is infinity
      55        1054 :     Eigen::Map<const Eigen::Vector3<real_t>> vector_map(distance_vector.data(),
      56         527 :                                                         distance_vector.size());
      57         527 :     real_t distance = vector_map.norm();
      58         527 :     SPDLOG_DEBUG("Interatomic distance: {}", distance);
      59         527 :     if (!std::isfinite(distance)) {
      60          20 :         return green_tensor_interpolator;
      61             :     }
      62         507 :     Eigen::Vector3<real_t> unitvec = vector_map / distance;
      63             : 
      64             :     // Dyadic green function of dipole-dipole interaction
      65         507 :     if (interaction_order >= 3) {
      66         507 :         Eigen::Matrix3<Scalar> entries =
      67         507 :             Eigen::Matrix3<real_t>::Identity() - 3 * unitvec * unitvec.transpose();
      68             : 
      69         507 :         green_tensor_interpolator.create_entries_from_cartesian(
      70        1014 :             1, 1, (entries / std::pow(distance, 3)).template cast<Scalar>());
      71             :     }
      72             : 
      73             :     // Dyadic green function of dipole-quadrupole interaction
      74         507 :     if (interaction_order >= 4) {
      75          20 :         Eigen::Matrix<real_t, 3, 9> entries = Eigen::Matrix<real_t, 3, 9>::Zero();
      76          80 :         for (Eigen::Index q = 0; q < 3; ++q) {
      77          60 :             Eigen::Index row = q;
      78         240 :             for (Eigen::Index j = 0; j < 3; ++j) {
      79         720 :                 for (Eigen::Index i = 0; i < 3; ++i) {
      80         540 :                     Eigen::Index col = 3 * j + i;
      81         540 :                     real_t v = 15 * unitvec[q] * unitvec[j] * unitvec[i];
      82         540 :                     if (i == j) v += -3 * unitvec[q];
      83         540 :                     if (i == q) v += -3 * unitvec[j];
      84         540 :                     if (j == q) v += -3 * unitvec[i];
      85         540 :                     entries(row, col) += v;
      86             :                 }
      87             :             }
      88             :         }
      89             : 
      90          20 :         green_tensor_interpolator.create_entries_from_cartesian(
      91          40 :             1, 2, (entries / std::pow(distance, 4)).template cast<Scalar>());
      92             :     }
      93             : 
      94             :     // Dyadic green function of quadrupole-dipole interaction
      95         507 :     if (interaction_order >= 4) {
      96          20 :         Eigen::Matrix<real_t, 9, 3> entries = Eigen::Matrix<real_t, 9, 3>::Zero();
      97          80 :         for (Eigen::Index q = 0; q < 3; ++q) {
      98         240 :             for (Eigen::Index j = 0; j < 3; ++j) {
      99         180 :                 Eigen::Index row = 3 * q + j;
     100         720 :                 for (Eigen::Index i = 0; i < 3; ++i) {
     101         540 :                     Eigen::Index col = i;
     102         540 :                     real_t v = -15 * unitvec[q] * unitvec[j] * unitvec[i];
     103         540 :                     if (i == j) v += 3 * unitvec[q];
     104         540 :                     if (i == q) v += 3 * unitvec[j];
     105         540 :                     if (j == q) v += 3 * unitvec[i];
     106         540 :                     entries(row, col) += v;
     107             :                 }
     108             :             }
     109             :         }
     110             : 
     111          20 :         green_tensor_interpolator.create_entries_from_cartesian(
     112          40 :             2, 1, (entries / std::pow(distance, 4)).template cast<Scalar>());
     113             :     }
     114             : 
     115             :     // Dyadic green function of quadrupole-quadrupole interaction
     116         507 :     if (interaction_order >= 5) {
     117           0 :         SPDLOG_WARN("Quadrupole-quadrupole interaction is considered but "
     118             :                     "not dipole-octupole interaction although this interaction would be "
     119             :                     "of the same order. We plan to implement dipole-octupole interaction "
     120             :                     "in the future.");
     121             : 
     122           0 :         Eigen::Matrix<real_t, 9, 9> entries = Eigen::Matrix<real_t, 9, 9>::Zero();
     123           0 :         for (Eigen::Index q = 0; q < 3; ++q) {
     124           0 :             for (Eigen::Index j = 0; j < 3; ++j) {
     125           0 :                 Eigen::Index row = 3 * q + j;
     126           0 :                 for (Eigen::Index i = 0; i < 3; ++i) {
     127           0 :                     for (Eigen::Index k = 0; k < 3; ++k) {
     128           0 :                         Eigen::Index col = 3 * i + k;
     129           0 :                         real_t v = 105 * unitvec[q] * unitvec[j] * unitvec[i] * unitvec[k];
     130           0 :                         if (i == j) v += -15 * unitvec[q] * unitvec[k];
     131           0 :                         if (i == q) v += -15 * unitvec[j] * unitvec[k];
     132           0 :                         if (j == q) v += -15 * unitvec[i] * unitvec[k];
     133           0 :                         if (k == q) v += -15 * unitvec[j] * unitvec[i];
     134           0 :                         if (k == j) v += -15 * unitvec[q] * unitvec[i];
     135           0 :                         if (k == i) v += -15 * unitvec[q] * unitvec[j];
     136           0 :                         if (q == k && i == j) v += 3;
     137           0 :                         if (i == k && j == q) v += 3;
     138           0 :                         if (j == k && i == q) v += 3;
     139           0 :                         entries(row, col) += v;
     140             :                     }
     141             :                 }
     142             :             }
     143             :         }
     144             : 
     145           0 :         green_tensor_interpolator.create_entries_from_cartesian(
     146           0 :             2, 2, (entries / std::pow(distance, 5)).template cast<Scalar>());
     147             :     }
     148             : 
     149         507 :     return green_tensor_interpolator;
     150           0 : }
     151             : 
     152             : template <typename Scalar>
     153             : OperatorMatrices<Scalar>
     154         549 : construct_operator_matrices(const GreenTensorInterpolator<Scalar> &green_tensor_interpolator,
     155             :                             const std::shared_ptr<const BasisAtom<Scalar>> &basis1,
     156             :                             const std::shared_ptr<const BasisAtom<Scalar>> &basis2) {
     157             :     // Helper function for constructing matrices of spherical harmonics operators
     158        1138 :     auto get_matrices = [](auto basis, OperatorType type, std::initializer_list<int> m,
     159             :                            bool conjugate) {
     160        1138 :         std::vector<Eigen::SparseMatrix<Scalar, Eigen::RowMajor>> matrices;
     161        1138 :         matrices.reserve(m.size());
     162        1138 :         int factor = conjugate ? -1 : 1;
     163        7966 :         std::transform(m.begin(), m.end(), std::back_inserter(matrices), [&](int q) {
     164           0 :             return (std::pow(factor, q) *
     165        6828 :                     OperatorAtom<Scalar>(basis, type, factor * q).get_matrix())
     166        6828 :                 .eval();
     167             :         });
     168        2276 :         return matrices;
     169           0 :     };
     170             : 
     171         549 :     OperatorMatrices<Scalar> op;
     172             : 
     173             :     // Operator matrices for Rydberg-Rydberg interaction
     174         569 :     if (!green_tensor_interpolator.get_spherical_entries(1, 1).empty() ||
     175          20 :         !green_tensor_interpolator.get_spherical_entries(1, 2).empty()) {
     176         529 :         op.d1 = get_matrices(basis1, OperatorType::ELECTRIC_DIPOLE, {-1, 0, +1}, true);
     177             :     }
     178         569 :     if (!green_tensor_interpolator.get_spherical_entries(1, 1).empty() ||
     179          20 :         !green_tensor_interpolator.get_spherical_entries(2, 1).empty()) {
     180         529 :         op.d2 = get_matrices(basis2, OperatorType::ELECTRIC_DIPOLE, {-1, 0, +1}, false);
     181             :     }
     182        1098 :     if (!green_tensor_interpolator.get_spherical_entries(2, 2).empty() ||
     183         549 :         !green_tensor_interpolator.get_spherical_entries(2, 1).empty()) {
     184          20 :         op.q1 = get_matrices(basis1, OperatorType::ELECTRIC_QUADRUPOLE, {-2, -1, 0, +1, +2}, true);
     185          20 :         op.q1.push_back(get_matrices(basis1, OperatorType::ELECTRIC_QUADRUPOLE_ZERO, {0}, true)[0]);
     186             :     }
     187        1098 :     if (!green_tensor_interpolator.get_spherical_entries(2, 2).empty() ||
     188         549 :         !green_tensor_interpolator.get_spherical_entries(1, 2).empty()) {
     189          20 :         op.q2 = get_matrices(basis2, OperatorType::ELECTRIC_QUADRUPOLE, {-2, -1, 0, +1, +2}, false);
     190          20 :         op.q2.push_back(
     191          40 :             get_matrices(basis2, OperatorType::ELECTRIC_QUADRUPOLE_ZERO, {0}, false)[0]);
     192             :     }
     193             : 
     194        1098 :     return op;
     195           0 : }
     196             : 
     197             : // "overloaded" pattern for std::visit
     198             : template <class... Ts>
     199             : struct overloaded : Ts... {
     200             :     using Ts::operator()...;
     201             : };
     202             : template <class... Ts>
     203             : overloaded(Ts...) -> overloaded<Ts...>;
     204             : 
     205             : template <typename Scalar>
     206         549 : SystemPair<Scalar>::SystemPair(std::shared_ptr<const basis_t> basis)
     207         549 :     : System<SystemPair<Scalar>>(std::move(basis)) {}
     208             : 
     209             : template <typename Scalar>
     210          85 : SystemPair<Scalar> &SystemPair<Scalar>::set_interaction_order(int value) {
     211          85 :     this->hamiltonian_requires_construction = true;
     212             : 
     213          85 :     if (value < 3 || value > 5) {
     214           0 :         throw std::invalid_argument("The order must be 3, 4, or 5.");
     215             :     }
     216             : 
     217          85 :     if (green_tensor_interpolator) {
     218           0 :         throw std::invalid_argument(
     219             :             "Cannot set interaction order if a user-defined green tensor interpolator is set.");
     220             :     }
     221             : 
     222          85 :     interaction_order = value;
     223             : 
     224          85 :     return *this;
     225             : }
     226             : 
     227             : template <typename Scalar>
     228         507 : SystemPair<Scalar> &SystemPair<Scalar>::set_distance_vector(const std::array<real_t, 3> &vector) {
     229         507 :     this->hamiltonian_requires_construction = true;
     230             : 
     231         258 :     if (!traits::NumTraits<Scalar>::is_complex_v && distance_vector[1] != 0) {
     232           0 :         throw std::invalid_argument(
     233             :             "The distance vector must not have a y-component if the scalar type is real.");
     234             :     }
     235             : 
     236         507 :     if (green_tensor_interpolator) {
     237           0 :         throw std::invalid_argument(
     238             :             "Cannot set distance vector if a user-defined green tensor interpolator is set.");
     239             :     }
     240             : 
     241         507 :     distance_vector = vector;
     242             : 
     243         507 :     return *this;
     244             : }
     245             : 
     246             : template <typename Scalar>
     247          22 : SystemPair<Scalar> &SystemPair<Scalar>::set_green_tensor_interpolator(
     248             :     std::shared_ptr<const GreenTensorInterpolator<Scalar>> &green_tensor_interpolator) {
     249          22 :     this->hamiltonian_requires_construction = true;
     250             : 
     251          44 :     if (std::isfinite(distance_vector[0]) && std::isfinite(distance_vector[1]) &&
     252          22 :         std::isfinite(distance_vector[2])) {
     253           0 :         throw std::invalid_argument(
     254             :             "Cannot set green tensor interpolator if a finite distance vector is set.");
     255             :     }
     256             : 
     257          22 :     this->green_tensor_interpolator = green_tensor_interpolator;
     258             : 
     259          22 :     return *this;
     260             : }
     261             : 
     262             : template <typename Scalar>
     263         549 : void SystemPair<Scalar>::construct_hamiltonian() const {
     264         549 :     auto basis = this->hamiltonian->get_basis();
     265         549 :     auto basis1 = basis->get_basis1();
     266         549 :     auto basis2 = basis->get_basis2();
     267             : 
     268         549 :     std::shared_ptr<const GreenTensorInterpolator<Scalar>> green_tensor_interpolator_ptr;
     269         549 :     if (green_tensor_interpolator) {
     270          22 :         green_tensor_interpolator_ptr = green_tensor_interpolator;
     271             :     } else {
     272         527 :         green_tensor_interpolator_ptr = std::make_shared<const GreenTensorInterpolator<Scalar>>(
     273         527 :             construct_green_tensor_interpolator<Scalar>(distance_vector, interaction_order));
     274             :     }
     275             : 
     276         549 :     auto op = construct_operator_matrices(*green_tensor_interpolator_ptr, basis1, basis2);
     277             : 
     278             :     // Construct the unperturbed Hamiltonian
     279         549 :     this->hamiltonian = std::make_unique<OperatorPair<Scalar>>(basis, OperatorType::ENERGY);
     280         549 :     this->hamiltonian_is_diagonal = true;
     281         549 :     bool sort_by_quantum_number_f = basis->has_quantum_number_f();
     282         549 :     bool sort_by_quantum_number_m = basis->has_quantum_number_m();
     283         549 :     bool sort_by_parity = basis->has_parity();
     284             : 
     285             :     // Store the energies (they are needed in case of Rydberg-Rydberg interaction with an
     286             :     // OmegaDependentEntry)
     287         549 :     auto energies = this->hamiltonian->get_matrix().diagonal().real();
     288             : 
     289             :     // Helper function for adding Rydberg-Rydberg interaction
     290        2745 :     auto add_interaction = [this, &basis, &energies, &sort_by_quantum_number_f,
     291             :                             &sort_by_quantum_number_m](const auto &entries, const auto &op1,
     292             :                                                        const auto &op2, int delta) {
     293        4001 :         for (const auto &entry : entries) {
     294        1805 :             std::visit(
     295             :                 overloaded{
     296        1805 :                     [this, &basis, &sort_by_quantum_number_m, &op1, &op2,
     297             :                      &delta](const typename GreenTensorInterpolator<Scalar>::ConstantEntry &ce) {
     298        1805 :                         this->hamiltonian->get_matrix() += ce.val() *
     299        1805 :                             utils::calculate_tensor_product(basis, basis, op1[ce.row()],
     300        1805 :                                                             op2[ce.col()]);
     301        1805 :                         if (ce.row() != ce.col() + delta) {
     302          98 :                             sort_by_quantum_number_m = false;
     303             :                         }
     304             :                     },
     305             : 
     306           0 :                     [this, &basis, &energies, &sort_by_quantum_number_m, &op1, &op2, &delta](
     307             :                         const typename GreenTensorInterpolator<Scalar>::OmegaDependentEntry &oe) {
     308           0 :                         auto tensor_product = utils::calculate_tensor_product(
     309           0 :                             basis, basis, op1[oe.row()], op2[oe.col()]);
     310           0 :                         for (int k = 0; k < tensor_product.outerSize(); ++k) {
     311           0 :                             for (typename Eigen::SparseMatrix<
     312           0 :                                      Scalar, Eigen::RowMajor>::InnerIterator it(tensor_product, k);
     313           0 :                                  it; ++it) {
     314           0 :                                 it.valueRef() *= oe.val(energies(it.row()) - energies(it.col()));
     315             :                             }
     316             :                         }
     317           0 :                         this->hamiltonian->get_matrix() += tensor_product;
     318           0 :                         if (oe.row() != oe.col() + delta) {
     319           0 :                             sort_by_quantum_number_m = false;
     320             :                         }
     321           0 :                     }},
     322             :                 entry);
     323             : 
     324        1805 :             this->hamiltonian_is_diagonal = false;
     325        1805 :             sort_by_quantum_number_f = false;
     326             :         }
     327             :     };
     328             : 
     329             :     // Dipole-dipole interaction
     330         549 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(1, 1), op.d1, op.d2, 0);
     331             : 
     332             :     // Dipole-quadrupole interaction
     333         549 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(1, 2), op.d1, op.q2, -1);
     334             : 
     335             :     // Quadrupole-dipole interaction
     336         549 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(2, 1), op.q1, op.d2, +1);
     337             : 
     338             :     // Quadrupole-quadrupole interaction
     339         549 :     add_interaction(green_tensor_interpolator_ptr->get_spherical_entries(2, 2), op.q1, op.q2, 0);
     340             : 
     341             :     // Store which labels can be used to block-diagonalize the Hamiltonian
     342         549 :     this->blockdiagonalizing_labels.clear();
     343         549 :     if (sort_by_quantum_number_f) {
     344           0 :         this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_F);
     345             :     }
     346         549 :     if (sort_by_quantum_number_m) {
     347         522 :         this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_M);
     348             :     }
     349         549 :     if (sort_by_parity) {
     350           0 :         this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_PARITY);
     351             :     }
     352         549 : }
     353             : 
     354             : // Explicit instantiations
     355             : template class SystemPair<double>;
     356             : template class SystemPair<std::complex<double>>;
     357             : } // namespace pairinteraction

Generated by: LCOV version 1.16