High-precision calculations for one- and two-valence atomic systems
Ladder.ipp
1#pragma once
2#include "Angular/include.hpp"
3#include "Coulomb/include.hpp"
4#include "Wavefunction/DiracSpinor.hpp"
5#include <type_traits>
6// namespace MBPT
7
8//==============================================================================
9// Calculate energy shift (either ladder, or sigma2)
10// nb: set lk to qk to det de(2)
11template <typename Qintegrals, typename QorLintegrals>
12double de_valence(const DiracSpinor &v, const Qintegrals &qk,
13 const QorLintegrals &lk, const std::vector<DiracSpinor> &core,
14 const std::vector<DiracSpinor> &excited) {
15
16 // XXX Fix this!
17 // static_assert(std::is_same_v<Qintegrals, Coulomb::YkTable> ||
18 // std::is_base_of_v<Coulomb::CoulombTable, Qintegrals>,
19 // "Qintegrals must be YkTable or CoulombTable
20 // (QkTable/LkTable)");
21 // static_assert(
22 // std::is_same_v<QorLintegrals, Coulomb::YkTable> ||
23 // std::is_base_of_v<Coulomb::CoulombTable, QorLintegrals>,
24 // "QorLintegrals must be YkTable or CoulombTable (QkTable/LkTable)");
25
26 const bool LisQ = [&]() {
27 if constexpr (std::is_same_v<Qintegrals, QorLintegrals>)
28 return &qk == &lk;
29 else
30 return false;
31 }();
32
33 auto de_v = 0.0;
34
35 // nb: always put 'v' in correct* position. This ensures that actual state v
36 // is used when evaluating Q/W integrals [as opposed to the excited basis
37 // version of v]. Note: only matters when using YkTable for integrals
38 // *correct == 1st or 3rd in Q, 1st or 4th in P
39
40#pragma omp parallel for reduction(+ : de_v)
41 for (auto in = 0ul; in < excited.size(); ++in) {
42 const auto &n = excited[in];
43 for (const auto &a : core) {
44
45 // Diagrams (a) + (b)
46 for (const auto &m : excited) {
47 const auto inv_de = 1.0 / (v.en() + a.en() - m.en() - n.en());
48 const auto [k0, kI] = Coulomb::k_minmax_Q(v, a, m, n);
49 for (int k = k0; k <= kI; k += 2) {
50
51 const auto Q_kvamn = qk.Q(k, v, a, m, n);
52 const auto L_kvamn = LisQ ? Q_kvamn : lk.Q(k, m, n, v, a);
53 const auto P_kvamn = lk.P(k, n, m, a, v);
54
55 // diagram (a)
56 de_v += Q_kvamn * L_kvamn * inv_de / (2 * k + 1);
57 // diagram (b) [exchange]
58 de_v += Q_kvamn * P_kvamn * inv_de / (2 * k + 1);
59 } // k
60 } // m
61
62 // Diagrams (c) + (d)
63 for (const auto &b : core) {
64 const auto inv_de = 1.0 / (v.en() + n.en() - a.en() - b.en());
65 const auto [k0, kI] = Coulomb::k_minmax_Q(v, n, a, b);
66 for (int k = k0; k <= kI; k += 2) {
67
68 const auto Q_kvnab = qk.Q(k, v, n, a, b);
69 const auto L_kvnab = LisQ ? Q_kvnab : lk.Q(k, v, n, a, b);
70 const auto P_kvnab = lk.P(k, v, n, a, b);
71
72 // diagram (c)
73 de_v += Q_kvnab * L_kvnab * inv_de / (2 * k + 1);
74 // diagram (d) [exchange]
75 de_v += Q_kvnab * P_kvnab * inv_de / (2 * k + 1);
76 }
77 }
78 }
79 }
80
81 return de_v / v.twojp1();
82}
83
84//------------------------------------------------------------------------------
85// Ladder (or MBPT2) valence energy with the exchange moved onto the FIRST
86// (Coulomb) integral:
87// de = sum_{amn,k} W^k_{vamn} L^k_{mnva} / Δε / [k] / [j_v] (a)+(b)
88// + sum_{anb,k} W^k_{vnab} L^k_{vnab} / Δε / [k] / [j_v] (c)+(d)
89// Here W = Q + P is the antisymmetrised Coulomb integral (qk.W), and the ladder
90// L enters as the bare (direct) second integral (lk.Q). This is an alternative
91// to de_valence(), which instead antisymmetrises the second (ladder) integral
92// via lk.P. The two agree (the exchange symmetry holds under the full sum),
93// because L^k vanishes outside the Coulomb k-range and gates the sum.
94// Putting the ladder in the FIRST slot does NOT work, since the ladder lacks
95// that symmetry.
96// nb: no screening/eta here (W lumps direct+exchange with unit weight).
97template <typename Qintegrals, typename Lintegrals>
98double de_valence_w(const DiracSpinor &v, const Qintegrals &qk,
99 const Lintegrals &lk, const std::vector<DiracSpinor> &core,
100 const std::vector<DiracSpinor> &excited,
101 const Angular::SixJTable *sj) {
102
103 auto de_v = 0.0;
104#pragma omp parallel for reduction(+ : de_v)
105 for (auto in = 0ul; in < excited.size(); ++in) {
106 const auto &n = excited[in];
107 for (const auto &a : core) {
108
109 // Diagrams (a)+(b): W^k_{vamn} L^k_{mnva}
110 for (const auto &m : excited) {
111 const auto inv_de = 1.0 / (v.en() + a.en() - m.en() - n.en());
112 const auto [k0, kI] = Coulomb::k_minmax_W(v, a, m, n);
113 for (int k = k0; k <= kI; ++k) {
114 const auto W_kvamn = qk.W(k, v, a, m, n, sj);
115 if (W_kvamn == 0.0)
116 continue;
117 const auto L_kmnva = lk.Q(k, m, n, v, a);
118 de_v += W_kvamn * L_kmnva * inv_de / (2 * k + 1);
119 }
120 }
121
122 // Diagrams (c)+(d): W^k_{vnab} L^k_{vnab}
123 for (const auto &b : core) {
124 const auto inv_de = 1.0 / (v.en() + n.en() - a.en() - b.en());
125 const auto [k0, kI] = Coulomb::k_minmax_W(v, n, a, b);
126 for (int k = k0; k <= kI; ++k) {
127 const auto W_kvnab = qk.W(k, v, n, a, b, sj);
128 if (W_kvnab == 0.0)
129 continue;
130 const auto L_kvnab = lk.Q(k, v, n, a, b);
131 de_v += W_kvnab * L_kvnab * inv_de / (2 * k + 1);
132 }
133 }
134 }
135 }
136
137 return de_v / v.twojp1();
138}
139
140//------------------------------------------------------------------------------
141// Calculate energy shift (either ladder, or sigma2) for CORE
142// lk may be regular Coulomb integrals [in which case this returns MBPT(2)
143// correction], or Ladder diagrams [in which case this returns the ladder
144// diagram correction]
145template <typename Qintegrals, typename QorLintegrals>
146double de_core(const Qintegrals &qk, const QorLintegrals &lk,
147 const std::vector<DiracSpinor> &core,
148 const std::vector<DiracSpinor> &excited) {
149
150 // XXX Fix this
151 // static_assert(std::is_same_v<Qintegrals, Coulomb::YkTable> ||
152 // std::is_base_of_v<Coulomb::CoulombTable, QorLintegrals>,
153 // "Qintegrals must be YkTable or CoulombTable
154 // (QkTable/LkTable)");
155
156 const bool LisQ = [&]() {
157 if constexpr (std::is_same_v<Qintegrals, QorLintegrals>)
158 return &qk == &lk;
159 else
160 return false;
161 }();
162
163 auto de_c = 0.0;
164#pragma omp parallel for reduction(+ : de_c)
165 for (auto in = 0ul; in < excited.size(); ++in) {
166 const auto &n = excited[in];
167 for (const auto &m : excited) {
168 for (const auto &a : core) {
169 for (const auto &b : core) {
170 const auto inv_de = 1.0 / (a.en() + b.en() - m.en() - n.en());
171 const auto [k0, kI] = Coulomb::k_minmax_Q(a, b, m, n);
172 for (int k = k0; k <= kI; k += 2) {
173 const auto Q_kabmn = qk.Q(k, a, b, m, n);
174 const auto L_kmnab = LisQ ? Q_kabmn : lk.Q(k, m, n, a, b);
175 const auto P_kmnab = lk.P(k, m, n, a, b);
176 de_c += 0.5 * Q_kabmn * (L_kmnab + P_kmnab) * inv_de / (2 * k + 1);
177 }
178 }
179 }
180 }
181 }
182 return de_c;
183}
Lookup table for Wigner 6j symbols.
Definition SixJTable.hpp:82
Stores radial Dirac spinor: F_nk = (f, g)
Definition DiracSpinor.hpp:44
int twojp1() const
2j+1
Definition DiracSpinor.hpp:99
double en() const
Single-particle energy, not including rest energy.
Definition DiracSpinor.hpp:132
std::pair< int, int > k_minmax_W(const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d)
Returns min and max k (multipolarity) allowed for W^k_abcd. DOES NOT contain parity rules (6j only) -...
Definition CoulombIntegrals.cpp:492
std::pair< int, int > k_minmax_Q(const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d)
Returns min and max k (multipolarity) allowed for Q^k_abcd. Parity rule is included,...
Definition CoulombIntegrals.hpp:146
double de_valence_w(const DiracSpinor &v, const Qintegrals &qk, const Lintegrals &lk, const std::vector< DiracSpinor > &core, const std::vector< DiracSpinor > &excited, const Angular::SixJTable *sj=nullptr)
Ladder (or MBPT2) valence energy, antisymmetrising the FIRST integral.
double de_core(const Qintegrals &qk, const QorLintegrals &lk, const std::vector< DiracSpinor > &core, const std::vector< DiracSpinor > &excited)
Second-order (or ladder) correction to the core energy.
double de_valence(const DiracSpinor &v, const Qintegrals &qk, const QorLintegrals &lk, const std::vector< DiracSpinor > &core, const std::vector< DiracSpinor > &excited)
Second-order (or ladder) correction to the valence energy.