High-precision calculations for one- and two-valence atomic systems
AsymptoticSpinor.hpp
1#pragma once
2#include "Physics/PhysConst_constants.hpp"
3#include "qip/Maths.hpp"
4#include <array>
5#include <cassert>
6#include <cmath>
7#include <complex>
8#include <utility>
9
10namespace DiracODE {
11
12/*!
13 @brief
14 Performs asymptotic expansion for f and g at large r, up to order Nx in (1/r).
15
16 @details
17 Templated on energy type T: T=double for bound states, or
18 T=std::complex<double> for Green's function solutions at complex energy.
19 The expansion coefficients, lambda, and sigma extend analytically; the
20 principal branch of sqrt gives Re(lambda)>0, i.e. the decaying solution.
21
22 The branch may instead be chosen explicitly via @ref with_lambda
23 (e.g. lambda = i p for the oscillating en > 0 tail, see
24 @ref AsymptoticSpinorContinuum).
25 Everything else (sigma, the small-component amplitude,
26 and the 1/r coefficients) is derived from lambda.
27*/
28template <typename T = double, std::size_t Nx = 15>
30private:
31 // Selects the constructor that takes lambda explicitly (see with_lambda)
32 struct ExplicitLambda {};
33
34 int kappa;
35 double Zeff;
36 T en;
37 double alpha, m_mass, eps_target;
38 double kappa2, alpha2, c;
39 T lambda, sigma;
40 // Large/small component amplitudes: A_large = sqrt(1 + en alpha^2/(2m)),
41 // A_small = sqrt(-en/(2m)) alpha. Since lambda = sqrt(-2 m en) A_large,
42 // A_small is written in terms of lambda so both share one branch of sqrt(-en)
43 T A_large, A_small;
44 // bx must be first (ax depends on bx in initialisation), make_ax()
45 std::array<T, Nx> bx;
46 std::array<T, Nx> ax;
47
48 AsymptoticSpinor(ExplicitLambda, int in_kappa, double in_Zeff, T in_en,
49 T in_lambda, double in_alpha, double in_eps_target, double m)
50 : kappa(in_kappa),
51 Zeff(in_Zeff),
52 en(in_en),
53 alpha(in_alpha),
54 m_mass(m),
55 eps_target(in_eps_target),
56 kappa2(double(kappa * kappa)),
57 alpha2(alpha * alpha),
58 c(1.0 / alpha),
59 lambda(in_lambda),
60 sigma((m + en * alpha2) * (Zeff / lambda)),
61 A_large(std::sqrt(1.0 + 0.5 * en * alpha2 / m_mass)),
62 A_small(lambda * alpha / (2.0 * m_mass * A_large)),
63 bx(make_bx()),
64 ax(make_ax()) {}
65
66public:
67 AsymptoticSpinor(int in_kappa, double in_Zeff, T in_en,
68 double in_alpha = PhysConst::alpha,
69 double in_eps_target = 1.0e-14, double m = 1.0)
71 ExplicitLambda{}, in_kappa, in_Zeff, in_en,
72 std::sqrt(-in_en * (2.0 * m + in_en * in_alpha * in_alpha)), in_alpha,
73 in_eps_target, m) {
74 // assert(en < 0.0 && "Must have en<0 in AsymptoticSpinor");
75 }
76
77 /*!
78 @brief Constructs with an explicitly chosen lambda (exponent in
79 exp(-lambda r)), i.e. with a chosen branch of sqrt(-en(2m + en alpha^2)).
80 @details in_lambda must satisfy lambda^2 = -en(2m + en alpha^2); only its
81 sign is free. Used for the en > 0 tail, where lambda = +/- i p and the
82 principal branch is ambiguous (it sits on the branch cut).
83 */
84 static AsymptoticSpinor with_lambda(int in_kappa, double in_Zeff, T in_en,
85 T in_lambda,
86 double in_alpha = PhysConst::alpha,
87 double in_eps_target = 1.0e-14,
88 double m = 1.0) {
89 return AsymptoticSpinor(ExplicitLambda{}, in_kappa, in_Zeff, in_en,
90 in_lambda, in_alpha, in_eps_target, m);
91 }
92
93 /*!
94 @brief Returns {f(r), g(r)} via asymptotic expansion at large r.
95 @details
96 Large-r expansion of upper/lower radial components of the Dirac solution,
97 see Johnson (2007), Eqs. (2.170) -- (2.171).
98
99 f(r) = r^s exp(-yr) * { A(1 + O(1/r) + ...) + B(O(1/r) + ...)},
100
101 g(r) = r^s exp(-yr) * { -B(1 + O(1/r) + ...) + A(O(1/r) + ...)},
102
103 where s~1, y~1, A~1, B<<1.
104
105 The 1/r expansion inside the braces is truncated at order Nx. The series is
106 terminated early if the relative change drops below eps_target (typically
107 around order ~5).
108 */
109 std::pair<T, T> fg(double r) const {
110 // See Johnson (2007), Eqs. (2.170) -- (2.171)
111 // Notation difference:
112 // P(r) = f(r)
113 // Q(r) = -g(r)
114 // There appears to by typo in Eq. (2.171)
115
116 const T rfac = /*2.0 * */ std::pow(r, sigma) * std::exp(-lambda * r);
117 T fs{1.0};
118 T gs{0.0};
119 // Continue the expansion until reach eps, or Nx
120 for (std::size_t k = 0; k < Nx; k++) {
121 const auto rkp1 = qip::pow(r, int(k) + 1);
122 const auto df = ax[k] / rkp1;
123 const auto dg = bx[k] / rkp1;
124 fs += df;
125 gs += dg;
126 const auto eps = std::max(std::abs(df / fs), std::abs(dg / gs));
127 if (eps < eps_target) {
128 break;
129 }
130 }
131 // here: typo in Johnson, or not? Both work
132 return {rfac * (A_large * fs + A_small * gs),
133 rfac * (A_large * gs - A_small * fs)};
134 // -rfac * (A_large * fs - A_small * gs)};
135 }
136
137private:
138 std::array<T, Nx> make_bx() const {
139 // See Johnson (2007), Eqs. (2.172) -- (2.173)
140 std::array<T, Nx> tbx;
141 const auto Zalpha2 = Zeff * Zeff * alpha2;
142 tbx[0] = (kappa / m_mass + (Zeff / lambda)) * (0.5 * alpha);
143 for (std::size_t i = 1; i < Nx; i++) {
144 tbx[i] = (kappa2 - qip::pow<2>((double(i) - sigma)) - Zalpha2) *
145 tbx[i - 1] / (double(2 * i) * lambda);
146 }
147 return tbx;
148 }
149
150 std::array<T, Nx> make_ax() const {
151 // See Johnson (2007), Eq. (2.174)
152 // bx must already be initialised
153 std::array<T, Nx> tax;
154 const auto RenAlpha2 = m_mass + en * alpha2;
155 for (std::size_t i = 0; i < Nx; i++) {
156 tax[i] = (kappa * m_mass + (double(i + 1) - sigma) * RenAlpha2 -
157 Zeff * lambda * alpha2) *
158 (bx[i] * c) / (double(i + 1) * lambda);
159 }
160 return tax;
161 }
162};
163
164//==============================================================================
165/*!
166 @brief Pair of oscillating continuum spinor values at one radius: the regular
167 (F) and irregular (G) large-r Dirac-Coulomb solutions.
168 @details Each spinor is stored as {f, g} (large, small components).
169 F^C has large component ~ cos(theta), G^C has large component ~ sin(theta):
170 the two are a quarter-wave (90-degree) pair.
171*/
173 //! F^C = {fC, gC}, large ~ cos
174 double fC, gC;
175 //! G^C = {fG, gG}, large ~ sin
176 double fG, gG;
177};
178
179/*!
180 @brief Large-r Dirac-Coulomb oscillating tail spinors of a continuum (en > 0)
181 state, energy-normalised.
182 @details
183 The en > 0 continuation of @ref AsymptoticSpinor. For a bound state the decay
184 constant lambda = sqrt(-en(2 + en alpha^2/m)) is real and the spinor decays
185 as r^sigma exp(-lambda r). For en > 0, lambda -> i p, with the relativistic
186 momentum
187 \f[ p = \sqrt{\en(2 + \en\alpha^2/m)} = \sqrt{\en(\en + 2c^2)}/c , \f]
188 so the solution oscillates as exp(-i(pr + nu ln r)), with sigma = -i nu and
189 nu = (m + en alpha^2) Z_ion / p. The expansion coefficients obey the same
190 recurrence as the bound case, with complex values. The real and imaginary
191 parts of the resulting complex spinor are the two real, linearly independent
192 oscillating solutions
193 \f[
194 F^C \to \begin{pmatrix} A_L\cos\theta \\ -A_S\sin\theta\end{pmatrix},
195 \qquad
196 G^C \to \begin{pmatrix} -A_L\sin\theta \\ -A_S\cos\theta\end{pmatrix},
197 \qquad A_S = \beta A_L,
198 \f]
199 with beta = sqrt(en/(en + 2c^2)). They are scaled to the energy
200 normalisation A_L = sqrt(alpha/(pi beta)), so that the Wronskian is
201 W[F^C, G^C] = f^C g^G - f^G g^C = -alpha/pi exactly.
202
203 Implemented as the complex-energy @ref AsymptoticSpinor on the branch
204 lambda = +i p (chosen explicitly: the principal sqrt is ambiguous on the
205 branch cut), with the energy-normalisation scale applied on output.
206
207 Used to seed the inward integration of the irregular continuum solution
208 (@ref solveContinuumIrregular), as @ref AsymptoticSpinor seeds the decaying
209 bound solution.
210
211 @warning Valid only for en > 0 and at large r (where the 1/r expansion has
212 converged); for en < 0 use @ref AsymptoticSpinor.
213*/
214template <std::size_t Nx = 15>
216private:
217 using Complex = std::complex<double>;
218 // relativistic momentum p = sqrt(en(2m + en alpha^2))
219 double p;
220 // small/large amplitude ratio beta = sqrt(en/(en + 2mc^2))
221 double m_beta;
222 // energy-normalised large-component amplitude A_L = sqrt(alpha/(pi beta))
223 double m_amplitude_large;
224 // Scale taking the raw expansion (large-component envelope
225 // sqrt(1 + en alpha^2/(2m))) to the energy-normalised amplitude A_L
226 double m_scale;
227 // Complex expansion on the branch lambda = +i p, oscillating as
228 // exp(-i(pr + nu ln r)); F^C and G^C are its real and imaginary parts
230
231public:
232 AsymptoticSpinorContinuum(int kappa, double Zeff, double en,
233 double alpha = PhysConst::alpha,
234 double eps_target = 1.0e-14, double m = 1.0)
235 : p(std::sqrt(en * (2.0 * m + en * alpha * alpha))),
236 m_beta(std::sqrt(en * alpha * alpha / (2.0 * m + en * alpha * alpha))),
237 m_amplitude_large(std::sqrt(alpha / (M_PI * m_beta))),
238 m_scale(m_amplitude_large /
239 std::sqrt(1.0 + 0.5 * en * alpha * alpha / m)),
241 kappa, Zeff, Complex{en}, Complex{0.0, p}, alpha, eps_target, m)) {
242 assert(en > 0.0 && "Must have en>0 in AsymptoticSpinorContinuum");
243 }
244
245 //! Relativistic momentum p = sqrt(en(en+2c^2))/c.
246 double momentum() const { return p; }
247 //! Energy-normalised large-component amplitude A_L = sqrt(alpha/(pi*beta)).
248 double amplitude_large() const { return m_amplitude_large; }
249 //! Small/large amplitude ratio beta = sqrt(en/(en+2c^2)).
250 double beta() const { return m_beta; }
251
252 /*!
253 @brief Returns the two real oscillating tail spinors {F^C, G^C} at r.
254 @details
255 F^C (large ~ cos) and G^C (large ~ sin) are the real and imaginary parts
256 of the complex en > 0 asymptotic spinor, energy-normalised. The 1/r series
257 is truncated at order Nx, or when the relative change drops below the
258 eps_target supplied at construction.
259 */
260 ContinuumTailSpinors fg(double r) const {
261 const auto [f, g] = m_expansion.fg(r);
262 return {m_scale * f.real(), m_scale * g.real(), m_scale * f.imag(),
263 m_scale * g.imag()};
264 }
265};
266
267} // namespace DiracODE
Large-r Dirac-Coulomb oscillating tail spinors of a continuum (en > 0) state, energy-normalised.
Definition AsymptoticSpinor.hpp:215
double momentum() const
Relativistic momentum p = sqrt(en(en+2c^2))/c.
Definition AsymptoticSpinor.hpp:246
double amplitude_large() const
Energy-normalised large-component amplitude A_L = sqrt(alpha/(pi*beta)).
Definition AsymptoticSpinor.hpp:248
ContinuumTailSpinors fg(double r) const
Returns the two real oscillating tail spinors {F^C, G^C} at r.
Definition AsymptoticSpinor.hpp:260
double beta() const
Small/large amplitude ratio beta = sqrt(en/(en+2c^2)).
Definition AsymptoticSpinor.hpp:250
Performs asymptotic expansion for f and g at large r, up to order Nx in (1/r).
Definition AsymptoticSpinor.hpp:29
std::pair< T, T > fg(double r) const
Returns {f(r), g(r)} via asymptotic expansion at large r.
Definition AsymptoticSpinor.hpp:109
static AsymptoticSpinor with_lambda(int in_kappa, double in_Zeff, T in_en, T in_lambda, double in_alpha=PhysConst::alpha, double in_eps_target=1.0e-14, double m=1.0)
Constructs with an explicitly chosen lambda (exponent in exp(-lambda r)), i.e. with a chosen branch o...
Definition AsymptoticSpinor.hpp:84
Functions and classes used to solve the Dirac equation.
Definition AsymptoticSpinor.hpp:10
double fC
F^C = {fC, gC}, large ~ cos.
Definition AsymptoticSpinor.hpp:174
double fG
G^C = {fG, gG}, large ~ sin.
Definition AsymptoticSpinor.hpp:176
Pair of oscillating continuum spinor values at one radius: the regular (F) and irregular (G) large-r ...
Definition AsymptoticSpinor.hpp:172
constexpr double alpha
Fine-structure constant: alpha = 1/137.035 999 177(21) [CODATA 2022].
Definition PhysConst_constants.hpp:24
constexpr double c
speed of light in a.u. (=1/alpha)
Definition PhysConst_constants.hpp:63
constexpr auto pow(T x)
x^n for compile-time integer n, x any arithmetic type.
Definition Maths.hpp:98