pairinteraction
A Rydberg Interaction Calculator
SystemAtom.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
12
13#include <Eigen/Dense>
14#include <algorithm>
15#include <limits>
16#include <memory>
17#include <set>
18#include <spdlog/spdlog.h>
19
20namespace pairinteraction {
21template <typename Scalar>
22SystemAtom<Scalar>::SystemAtom(std::shared_ptr<const basis_t> basis)
23 : System<SystemAtom<Scalar>>(std::move(basis)) {}
24
25template <typename Scalar>
26SystemAtom<Scalar> &SystemAtom<Scalar>::set_electric_field(const std::array<real_t, 3> &field) {
27 this->hamiltonian_requires_construction = true;
28
29 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
30 if (!traits::NumTraits<Scalar>::is_complex_v && std::abs(field[1]) > numerical_precision) {
31 throw std::invalid_argument(
32 "The field must not have a y-component if the scalar type is real.");
33 }
34
35 electric_field = field;
36
37 return *this;
38}
39
40template <typename Scalar>
41SystemAtom<Scalar> &SystemAtom<Scalar>::set_magnetic_field(const std::array<real_t, 3> &field) {
42 this->hamiltonian_requires_construction = true;
43
44 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
45 if (!traits::NumTraits<Scalar>::is_complex_v && std::abs(field[1]) > numerical_precision) {
46 throw std::invalid_argument(
47 "The field must not have a y-component if the scalar type is real.");
48 }
49
50 magnetic_field = field;
51
52 return *this;
53}
54
55template <typename Scalar>
57 this->hamiltonian_requires_construction = true;
58 diamagnetism_enabled = enable;
59 return *this;
60}
61
62template <typename Scalar>
64SystemAtom<Scalar>::set_ion_distance_vector(const std::array<real_t, 3> &vector) {
65 this->hamiltonian_requires_construction = true;
66
67 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
68 if (!traits::NumTraits<Scalar>::is_complex_v && std::abs(vector[1]) > numerical_precision) {
69 throw std::invalid_argument(
70 "The distance vector must not have a y-component if the scalar type is real.");
71 }
72
73 ion_distance_vector = vector;
74
75 return *this;
76}
77
78template <typename Scalar>
80 this->hamiltonian_requires_construction = true;
81 ion_charge = charge;
82 return *this;
83}
84
85template <typename Scalar>
87 this->hamiltonian_requires_construction = true;
88 if (value < 2 || value > 3) {
89 throw std::invalid_argument("The order of the Rydberg-ion interaction must be 2 or 3");
90 }
91 ion_interaction_order = value;
92 return *this;
93}
94
95template <typename Scalar>
97 auto basis = this->hamiltonian->get_basis();
98
99 constexpr real_t numerical_precision = 100 * std::numeric_limits<real_t>::epsilon();
100
101 // Construct the unperturbed Hamiltonian
102 this->hamiltonian = std::make_unique<OperatorAtom<Scalar>>(basis, OperatorType::ENERGY);
103 this->hamiltonian_is_diagonal = true;
104 bool sort_by_quantum_number_f = true;
105 bool sort_by_quantum_number_m = true;
106 bool sort_by_parity = true;
107
108 // Add the interaction with the field of an ion (see
109 // https://en.wikipedia.org/wiki/Multipole_expansion#Spherical_form for details)
110
111 Eigen::Map<const Eigen::Vector3<real_t>> vector_map(ion_distance_vector.data(),
112 ion_distance_vector.size());
113 real_t distance = vector_map.norm();
114 SPDLOG_DEBUG("Distance to the ion: {}", distance);
115
116 // Dipole order
117 if (distance != std::numeric_limits<real_t>::infinity() && ion_interaction_order >= 2) {
118 // Calculate sqrt(4pi/3) * r * Y_1,q with q = -1, 0, 1 for the first three elements. Take
119 // the conjugate of the result and scale it by 1/distance^2.
120 Eigen::Vector3<Scalar> vector_dipole_order =
121 spherical::get_transformator<Scalar>(1) * vector_map / distance;
122 vector_dipole_order = vector_dipole_order.conjugate() / std::pow(distance, 2);
123
124 for (int q = -1; q <= 1; ++q) {
125 if (std::abs(vector_dipole_order[q + 1]) > numerical_precision) {
126 *this->hamiltonian -= ion_charge * vector_dipole_order[q + 1] *
128 this->hamiltonian_is_diagonal = false;
129 sort_by_quantum_number_f = false;
130 sort_by_parity = false;
131 sort_by_quantum_number_m &= (q == 0);
132 }
133 }
134 }
135
136 // Quadrupole order (the last entry of vector_quadrupole_order would correspond to
137 // ELECTRIC_QUADRUPOLE_ZERO and does not contribute to a *traceless* quadrupole)
138 if (distance != std::numeric_limits<real_t>::infinity() && ion_interaction_order >= 3) {
139 // Calculate sqrt(4pi/5) * r^2 * Y_2,q / 3 with q = -2, -1, 0, 1, 2 for the first five
140 // elements and (x^2 + y^2 + z^2) / 6 as the last element. Take the conjugate of the
141 // result and scale it by 1/distance^3.
142 Eigen::Vector<Scalar, 6> vector_quadrupole_order = spherical::get_transformator<Scalar>(2) *
143 Eigen::KroneckerProduct(vector_map / distance, vector_map / distance);
144 vector_quadrupole_order = 3 * vector_quadrupole_order.conjugate() / std::pow(distance, 3);
145
146 for (int q = -2; q <= 2; ++q) {
147 if (std::abs(vector_quadrupole_order[q + 2]) > numerical_precision) {
148 *this->hamiltonian -= ion_charge * vector_quadrupole_order[q + 2] *
149 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, q);
150 this->hamiltonian_is_diagonal = false;
151 sort_by_quantum_number_f = false;
152 sort_by_quantum_number_m &= (q == 0);
153 }
154 }
155 }
156
157 // Add external fields (see https://arxiv.org/abs/1612.08053 for details)
158
159 // Transform the electric field to spherical coordinates and take the conjugate
160 Eigen::Vector3<Scalar> electric_field_spherical = spherical::get_transformator<Scalar>(1) *
161 Eigen::Map<const Eigen::Vector3<real_t>>(electric_field.data(), electric_field.size());
162 electric_field_spherical = electric_field_spherical.conjugate();
163
164 // Transform the magnetic field to spherical coordinates and take the conjugate
165 Eigen::Vector3<Scalar> magnetic_field_spherical = spherical::get_transformator<Scalar>(1) *
166 Eigen::Map<const Eigen::Vector3<real_t>>(magnetic_field.data(), magnetic_field.size());
167 magnetic_field_spherical = magnetic_field_spherical.conjugate();
168
169 // Stark effect: - \vec{d} \vec{E} = - d_{1,0} E_{0} + d_{1,1} E_{-} + d_{1,-1} E_{+}
170 // = - d_{1,0} E_{0} - d_{1,1} E_{+}^* - d_{1,-1} E_{-}^*
171 // with the electric dipole operator: d_{1,q} = - e r sqrt{4 pi / 3} Y_{1,q}(\theta, \phi)
172 // where electric_field_spherical=[E_{-}^*, E_{0}, E_{+}^*]
173 for (int q = -1; q <= 1; ++q) {
174 if (std::abs(electric_field_spherical[q + 1]) > numerical_precision) {
175 *this->hamiltonian -= electric_field_spherical[q + 1] *
176 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_DIPOLE, q);
177 this->hamiltonian_is_diagonal = false;
178 sort_by_quantum_number_f = false;
179 sort_by_parity = false;
180 sort_by_quantum_number_m &= (q == 0);
181 }
182 }
183
184 // Zeeman effect: - \vec{\mu} \vec{B} = - \mu_{1,0} B_{0} + \mu_{1,1} B_{-} + \mu_{1,-1} B_{+}
185 // = - \mu_{1,0} B_{0} - \mu_{1,1} B_{+}^* - \mu_{1,-1} B_{-}^*
186 // with the magnetic dipole operator: \vec{\mu} = - \mu_B / \hbar (g_l \vec{l} + g_s \vec{s})
187 // where magnetic_field_spherical=[B_{-}^*, B_{0}, B_{+}^*]
188 for (int q = -1; q <= 1; ++q) {
189 if (std::abs(magnetic_field_spherical[q + 1]) > numerical_precision) {
190 *this->hamiltonian -= magnetic_field_spherical[q + 1] *
191 OperatorAtom<Scalar>(basis, OperatorType::MAGNETIC_DIPOLE, q);
192 this->hamiltonian_is_diagonal = false;
193 sort_by_quantum_number_f = false;
194 sort_by_quantum_number_m &= (q == 0);
195 }
196 }
197
198 // Diamagnetism: 1 / (8 m_e) abs(\vec{d} \times \vec{B})^2
199 // = (1/12) \left[ B_0^2 (q0 - d_{2,0}) + B_+ B_- (-2 q0 - d_{2,0})
200 // + \sqrt{3} B_0 B_- d_{2,1} + \sqrt{3} B_0 B_+ d_{2,-1}
201 // - \sqrt{3/2} B_-^2 d_{2,2} - \sqrt{3/2} B_+^2 d_{2,-2} \right]
202 // with the operator: q0 = e^2 r^2 sqrt{4 pi} Y_{0,0} = e^2 r^2
203 // and the electric quadrupole operator: d_{2,q} = e^2 r^2 sqrt{4 pi / 5} Y_{2,q}(\theta, \phi)
204 // where magnetic_field_spherical=[B_{-}^*, B_{0}, B_{+}^*]
205 if (diamagnetism_enabled) {
206 if (std::abs(magnetic_field_spherical[1]) > numerical_precision) {
207 *this->hamiltonian += static_cast<real_t>(1 / 12.) *
208 static_cast<Scalar>(std::pow(magnetic_field_spherical[1], 2)) *
209 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE_ZERO, 0);
210 *this->hamiltonian -= static_cast<real_t>(1 / 12.) *
211 static_cast<Scalar>(std::pow(magnetic_field_spherical[1], 2)) *
212 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, 0);
213 this->hamiltonian_is_diagonal = false;
214 sort_by_quantum_number_f = false;
215 }
216 if (std::abs(magnetic_field_spherical[0]) > numerical_precision &&
217 std::abs(magnetic_field_spherical[2]) > numerical_precision) {
218 *this->hamiltonian -= static_cast<real_t>(2 / 12.) * magnetic_field_spherical[0] *
219 magnetic_field_spherical[2] *
220 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE_ZERO, 0);
221 *this->hamiltonian -= static_cast<real_t>(1 / 12.) * magnetic_field_spherical[0] *
222 magnetic_field_spherical[2] *
223 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, 0);
224 this->hamiltonian_is_diagonal = false;
225 sort_by_quantum_number_f = false;
226 }
227 if (std::abs(magnetic_field_spherical[1]) > numerical_precision &&
228 std::abs(magnetic_field_spherical[2]) > numerical_precision) {
229 *this->hamiltonian -= static_cast<real_t>(std::sqrt(3.0) / 12.) *
230 magnetic_field_spherical[1] * magnetic_field_spherical[2] *
231 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, 1);
232 this->hamiltonian_is_diagonal = false;
233 sort_by_quantum_number_f = false;
234 sort_by_quantum_number_m = false;
235 }
236 if (std::abs(magnetic_field_spherical[1]) > numerical_precision &&
237 std::abs(magnetic_field_spherical[0]) > numerical_precision) {
238 *this->hamiltonian -= static_cast<real_t>(std::sqrt(3.0) / 12.) *
239 magnetic_field_spherical[1] * magnetic_field_spherical[0] *
240 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, -1);
241 this->hamiltonian_is_diagonal = false;
242 sort_by_quantum_number_f = false;
243 sort_by_quantum_number_m = false;
244 }
245 if (std::abs(magnetic_field_spherical[2]) > numerical_precision) {
246 *this->hamiltonian -= static_cast<real_t>(std::sqrt(1.5) / 12.) *
247 static_cast<Scalar>(std::pow(magnetic_field_spherical[2], 2)) *
248 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, 2);
249 this->hamiltonian_is_diagonal = false;
250 sort_by_quantum_number_f = false;
251 sort_by_quantum_number_m = false;
252 }
253 if (std::abs(magnetic_field_spherical[0]) > numerical_precision) {
254 *this->hamiltonian -= static_cast<real_t>(std::sqrt(1.5) / 12.) *
255 static_cast<Scalar>(std::pow(magnetic_field_spherical[0], 2)) *
256 OperatorAtom<Scalar>(basis, OperatorType::ELECTRIC_QUADRUPOLE, -2);
257 this->hamiltonian_is_diagonal = false;
258 sort_by_quantum_number_f = false;
259 sort_by_quantum_number_m = false;
260 }
261 }
262
263 // Store which labels can be used to block-diagonalize the Hamiltonian
264 this->blockdiagonalizing_labels.clear();
265 if (sort_by_quantum_number_f) {
266 this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_F);
267 }
268 if (sort_by_quantum_number_m) {
269 this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_QUANTUM_NUMBER_M);
270 }
271 if (sort_by_parity) {
272 this->blockdiagonalizing_labels.push_back(TransformationType::SORT_BY_PARITY);
273 }
274}
275
276// Explicit instantiations
277template class SystemAtom<double>;
278template class SystemAtom<std::complex<double>>;
279} // namespace pairinteraction
Type & set_ion_charge(real_t charge)
Definition: SystemAtom.cpp:79
typename traits::CrtpTraits< Type >::real_t real_t
Definition: SystemAtom.hpp:42
Type & set_diamagnetism_enabled(bool enable)
Definition: SystemAtom.cpp:56
SystemAtom(std::shared_ptr< const basis_t > basis)
Definition: SystemAtom.cpp:22
Type & set_electric_field(const std::array< real_t, 3 > &field)
Definition: SystemAtom.cpp:26
Type & set_magnetic_field(const std::array< real_t, 3 > &field)
Definition: SystemAtom.cpp:41
Type & set_ion_distance_vector(const std::array< real_t, 3 > &vector)
Definition: SystemAtom.cpp:64
Type & set_ion_interaction_order(int value)
Definition: SystemAtom.cpp:86
std::shared_ptr< const basis_t > get_basis() const
Definition: System.cpp:76
Matrix< Type, Size, 1 > Vector
Matrix< Type, 3, 1 > Vector3
Helper struct to extract types from a numerical type.
Definition: traits.hpp:35