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
|