pairinteraction
A Rydberg Interaction Calculator
SystemPair.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2024 Pairinteraction Developers
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
5
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
33namespace pairinteraction {
34template <typename Scalar>
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
42template <typename Scalar>
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
51
52 GreenTensor<Scalar> green_tensor;
53
54 // Normalize the distance vector, return zero green tensor if the distance is infinity
55 Eigen::Map<const Eigen::Vector3<real_t>> vector_map(distance_vector.data(),
56 distance_vector.size());
57 real_t distance = vector_map.norm();
58 SPDLOG_DEBUG("Interatomic distance: {}", distance);
59 if (distance == std::numeric_limits<real_t>::infinity()) {
60 return green_tensor;
61 }
62 Eigen::Vector3<real_t> vector_normalized = vector_map / distance;
63
64 // Dyadic green function of dipole-dipole interaction
65 if (interaction_order >= 3) {
67 3 * vector_normalized * vector_normalized.transpose();
68 entries /= std::pow(distance, 3);
69
70 green_tensor.set_entries(1, 1, entries.template cast<Scalar>());
71 }
72
73 // Dyadic green function of dipole-quadrupole interaction
74 if (interaction_order >= 4) {
75 Eigen::Matrix<real_t, 3, 9> entries = Eigen::Matrix<real_t, 3, 9>::Zero();
76 for (Eigen::Index q = 0; q < 3; ++q) {
77 Eigen::Index row = q;
78 for (Eigen::Index j = 0; j < 3; ++j) {
79 for (Eigen::Index i = 0; i < 3; ++i) {
80 Eigen::Index col = 3 * j + i;
81 entries(row, col) +=
82 15 * vector_normalized[q] * vector_normalized[j] * vector_normalized[i];
83 if (i == j) {
84 entries(row, col) += -3 * vector_normalized[q];
85 }
86 if (i == q) {
87 entries(row, col) += -3 * vector_normalized[j];
88 }
89 if (j == q) {
90 entries(row, col) += -3 * vector_normalized[i];
91 }
92 }
93 }
94 }
95 entries /= std::pow(distance, 4);
96
97 green_tensor.set_entries(1, 2, entries.template cast<Scalar>());
98 }
99
100 // Dyadic green function of quadrupole-dipole interaction
101 if (interaction_order >= 4) {
102 Eigen::Matrix<real_t, 9, 3> entries = Eigen::Matrix<real_t, 9, 3>::Zero();
103 for (Eigen::Index q = 0; q < 3; ++q) {
104 for (Eigen::Index j = 0; j < 3; ++j) {
105 Eigen::Index row = 3 * q + j;
106 for (Eigen::Index i = 0; i < 3; ++i) {
107 Eigen::Index col = i;
108 entries(row, col) +=
109 -15 * vector_normalized[q] * vector_normalized[j] * vector_normalized[i];
110 if (i == j) {
111 entries(row, col) += 3 * vector_normalized[q];
112 }
113 if (i == q) {
114 entries(row, col) += 3 * vector_normalized[j];
115 }
116 if (j == q) {
117 entries(row, col) += 3 * vector_normalized[i];
118 }
119 }
120 }
121 }
122 entries /= std::pow(distance, 4);
123
124 green_tensor.set_entries(2, 1, entries.template cast<Scalar>());
125 }
126
127 // Dyadic green function of quadrupole-quadrupole interaction
128 if (interaction_order >= 5) {
129 SPDLOG_WARN("Quadrupole-quadrupole interaction is considered but "
130 "not dipole-octupole interaction although this interaction would be "
131 "of the same order. We plan to implement dipole-octupole interaction "
132 "in the future.");
133
134 Eigen::Matrix<real_t, 9, 9> entries = Eigen::Matrix<real_t, 9, 9>::Zero();
135 for (Eigen::Index q = 0; q < 3; ++q) {
136 for (Eigen::Index j = 0; j < 3; ++j) {
137 Eigen::Index row = 3 * q + j;
138 for (Eigen::Index i = 0; i < 3; ++i) {
139 for (Eigen::Index k = 0; k < 3; ++k) {
140 Eigen::Index col = 3 * i + k;
141 entries(row, col) += 105 * vector_normalized[q] * vector_normalized[j] *
142 vector_normalized[i] * vector_normalized[k];
143 if (i == j) {
144 entries(row, col) += -15 * vector_normalized[q] * vector_normalized[k];
145 }
146 if (i == q) {
147 entries(row, col) += -15 * vector_normalized[j] * vector_normalized[k];
148 }
149 if (j == q) {
150 entries(row, col) += -15 * vector_normalized[i] * vector_normalized[k];
151 }
152 if (k == q) {
153 entries(row, col) += -15 * vector_normalized[j] * vector_normalized[i];
154 }
155 if (k == j) {
156 entries(row, col) += -15 * vector_normalized[q] * vector_normalized[i];
157 }
158 if (k == i) {
159 entries(row, col) += -15 * vector_normalized[q] * vector_normalized[j];
160 }
161 if (q == k && i == j) {
162 entries(row, col) += 3;
163 }
164 if (i == k && j == q) {
165 entries(row, col) += 3;
166 }
167 if (j == k && i == q) {
168 entries(row, col) += 3;
169 }
170 }
171 }
172 }
173 }
174 entries /= std::pow(distance, 5);
175
176 green_tensor.set_entries(2, 2, entries.template cast<Scalar>());
177 }
178
179 return green_tensor;
180}
181
182template <typename Scalar>
183OperatorMatrices<Scalar>
185 const std::shared_ptr<const BasisAtom<Scalar>> &basis1,
186 const std::shared_ptr<const BasisAtom<Scalar>> &basis2) {
188
189 if (!green_tensor.get_entries(1, 1).empty() || !green_tensor.get_entries(1, 2).empty()) {
190 op.d1.push_back(
191 -OperatorAtom<Scalar>(basis1, OperatorType::ELECTRIC_DIPOLE, 1).get_matrix());
192 op.d1.push_back(
193 OperatorAtom<Scalar>(basis1, OperatorType::ELECTRIC_DIPOLE, 0).get_matrix());
194 op.d1.push_back(
195 -OperatorAtom<Scalar>(basis1, OperatorType::ELECTRIC_DIPOLE, -1).get_matrix());
196 }
197
198 if (!green_tensor.get_entries(1, 1).empty() || !green_tensor.get_entries(2, 1).empty()) {
199 op.d2.push_back(
200 OperatorAtom<Scalar>(basis2, OperatorType::ELECTRIC_DIPOLE, -1).get_matrix());
201 op.d2.push_back(
202 OperatorAtom<Scalar>(basis2, OperatorType::ELECTRIC_DIPOLE, 0).get_matrix());
203 op.d2.push_back(
204 OperatorAtom<Scalar>(basis2, OperatorType::ELECTRIC_DIPOLE, 1).get_matrix());
205 }
206
207 if (!green_tensor.get_entries(2, 2).empty() || !green_tensor.get_entries(2, 1).empty()) {
208 op.q1.push_back(
210 op.q1.push_back(
212 op.q1.push_back(
214 op.q1.push_back(
216 op.q1.push_back(
218 op.q1.push_back(
220 }
221
222 if (!green_tensor.get_entries(2, 2).empty() || !green_tensor.get_entries(1, 2).empty()) {
223 op.q2.push_back(
225 op.q2.push_back(
227 op.q2.push_back(
229 op.q2.push_back(
231 op.q2.push_back(
233 op.q2.push_back(
235 }
236
237 return op;
238}
239
240template <typename Scalar>
241SystemPair<Scalar>::SystemPair(std::shared_ptr<const basis_t> basis)
242 : System<SystemPair<Scalar>>(std::move(basis)) {}
243
244template <typename Scalar>
246 this->hamiltonian_requires_construction = true;
247 if (value < 3 || value > 5) {
248 throw std::invalid_argument("The order must be 3, 4, or 5.");
249 }
250 interaction_order = value;
251 return *this;
252}
253
254template <typename Scalar>
255SystemPair<Scalar> &SystemPair<Scalar>::set_distance_vector(const std::array<real_t, 3> &vector) {
256 this->hamiltonian_requires_construction = true;
257
258 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
260 std::abs(distance_vector[1]) > numerical_precision) {
261 throw std::invalid_argument(
262 "The distance vector must not have a y-component if the scalar type is real.");
263 }
264
265 distance_vector = vector;
266
267 return *this;
268}
269
270template <typename Scalar>
272 auto basis = this->hamiltonian->get_basis();
273 auto basis1 = basis->get_basis1();
274 auto basis2 = basis->get_basis2();
275
276 auto green_tensor = construct_green_tensor<Scalar>(distance_vector, interaction_order);
277 auto op = construct_operator_matrices(green_tensor, basis1, basis2);
278
279 // Construct the unperturbed Hamiltonian
280 this->hamiltonian = std::make_unique<OperatorPair<Scalar>>(basis, OperatorType::ENERGY);
281 this->hamiltonian_is_diagonal = true;
282 bool sort_by_quantum_number_f = basis->has_quantum_number_f();
283 bool sort_by_quantum_number_m = basis->has_quantum_number_m();
284 bool sort_by_parity = basis->has_parity();
285
286 // Dipole-dipole interaction
287 for (const auto &entry : green_tensor.get_entries(1, 1)) {
288 if (auto ce = std::get_if<typename GreenTensor<Scalar>::ConstantEntry>(&entry)) {
289 this->hamiltonian->get_matrix() += ce->val() *
290 utils::calculate_tensor_product(basis, basis, op.d1[ce->row()], op.d2[ce->col()]);
291 if (ce->row() != ce->col()) {
292 sort_by_quantum_number_m = false;
293 }
294 this->hamiltonian_is_diagonal = false;
295 sort_by_quantum_number_f = false;
296 } else {
297 throw std::invalid_argument("Omega-dependent entries are not yet supported.");
298 }
299 }
300
301 // Dipole-quadrupole interaction
302 for (const auto &entry : green_tensor.get_entries(1, 2)) {
303 if (auto ce = std::get_if<typename GreenTensor<Scalar>::ConstantEntry>(&entry)) {
304 this->hamiltonian->get_matrix() += ce->val() *
305 utils::calculate_tensor_product(basis, basis, op.d1[ce->row()], op.q2[ce->col()]);
306 if (ce->row() != ce->col() - 1) {
307 sort_by_quantum_number_m = false;
308 }
309 this->hamiltonian_is_diagonal = false;
310 sort_by_quantum_number_f = false;
311 } else {
312 throw std::invalid_argument("Omega-dependent entries are not yet supported.");
313 }
314 }
315
316 // Quadrupole-dipole interaction
317 for (const auto &entry : green_tensor.get_entries(2, 1)) {
318 if (auto ce = std::get_if<typename GreenTensor<Scalar>::ConstantEntry>(&entry)) {
319 this->hamiltonian->get_matrix() += ce->val() *
320 utils::calculate_tensor_product(basis, basis, op.q1[ce->row()], op.d2[ce->col()]);
321 if (ce->row() - 1 != ce->col()) {
322 sort_by_quantum_number_m = false;
323 }
324 this->hamiltonian_is_diagonal = false;
325 sort_by_quantum_number_f = false;
326 } else {
327 throw std::invalid_argument("Omega-dependent entries are not yet supported.");
328 }
329 }
330
331 // Quadrupole-quadrupole interaction
332 for (const auto &entry : green_tensor.get_entries(2, 2)) {
333 if (auto ce = std::get_if<typename GreenTensor<Scalar>::ConstantEntry>(&entry)) {
334 this->hamiltonian->get_matrix() += ce->val() *
335 utils::calculate_tensor_product(basis, basis, op.q1[ce->row()], op.q2[ce->col()]);
336 if (ce->row() != ce->col()) {
337 sort_by_quantum_number_m = false;
338 }
339 this->hamiltonian_is_diagonal = false;
340 sort_by_quantum_number_f = false;
341 } else {
342 throw std::invalid_argument("Omega-dependent entries are not yet supported.");
343 }
344 }
345
346 // Store which labels can be used to block-diagonalize the Hamiltonian
347 this->blockdiagonalizing_labels.clear();
348 if (sort_by_quantum_number_f) {
349 this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_F);
350 }
351 if (sort_by_quantum_number_m) {
352 this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_M);
353 }
354 if (sort_by_parity) {
355 this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_PARITY);
356 }
357}
358
359// Explicit instantiations
360template class SystemPair<double>;
361template class SystemPair<std::complex<double>>;
362} // namespace pairinteraction
Class for creating a basis of atomic kets.
Definition: BasisAtom.hpp:40
const std::vector< Entry > & get_entries(int kappa1, int kappa2) const
void set_entries(int kappa1, int kappa2, const Eigen::MatrixX< Scalar > &tensor_in_cartesian_coordinates)
Definition: GreenTensor.cpp:65
SystemPair(std::shared_ptr< const basis_t > basis)
Definition: SystemPair.cpp:241
typename traits::CrtpTraits< Type >::real_t real_t
Definition: SystemPair.hpp:49
Type & set_interaction_order(int value)
Definition: SystemPair.cpp:245
Type & set_distance_vector(const std::array< real_t, 3 > &vector)
Definition: SystemPair.cpp:255
std::shared_ptr< const basis_t > get_basis() const
Definition: System.cpp:76
Matrix< Type, 3, 1 > Vector3
Matrix< Type, 3, 3 > Matrix3
Eigen::SparseMatrix< Scalar, Eigen::RowMajor > calculate_tensor_product(const std::shared_ptr< const BasisPair< Scalar > > &basis_initial, const std::shared_ptr< const BasisPair< Scalar > > &basis_final, const Eigen::SparseMatrix< Scalar, Eigen::RowMajor > &matrix1, const Eigen::SparseMatrix< Scalar, Eigen::RowMajor > &matrix2)
Definition: tensor.cpp:20
GreenTensor< Scalar > construct_green_tensor(const std::array< typename traits::NumTraits< Scalar >::real_t, 3 > &distance_vector, int interaction_order)
Definition: SystemPair.cpp:43
OperatorMatrices< Scalar > construct_operator_matrices(const GreenTensor< Scalar > &green_tensor, const std::shared_ptr< const BasisAtom< Scalar > > &basis1, const std::shared_ptr< const BasisAtom< Scalar > > &basis2)
Definition: SystemPair.cpp:184
std::vector< Eigen::SparseMatrix< Scalar, Eigen::RowMajor > > q2
Definition: SystemPair.cpp:39
std::vector< Eigen::SparseMatrix< Scalar, Eigen::RowMajor > > q1
Definition: SystemPair.cpp:38
std::vector< Eigen::SparseMatrix< Scalar, Eigen::RowMajor > > d1
Definition: SystemPair.cpp:36
std::vector< Eigen::SparseMatrix< Scalar, Eigen::RowMajor > > d2
Definition: SystemPair.cpp:37
Helper struct to extract types from a numerical type.
Definition: traits.hpp:35