High-precision calculations for one- and two-valence atomic systems
QkTable.ipp
1#pragma once
2#include "CoulombIntegrals.hpp"
3#include "IO/ChronoTimer.hpp"
4#include "IO/FRW_fileReadWrite.hpp"
5#include "Physics/AtomData.hpp"
6#include "QkTable.hpp"
7#include "fmt/format.hpp"
8#include "qip/Widgets.hpp"
9#include <algorithm>
10#include <cassert>
11#include <cstring> // for memcpy
12#include <numeric>
13#include <string_view>
14#include <type_traits>
15#include <vector>
16
17namespace Coulomb {
18
19//==============================================================================
20template <Symmetry S>
22 // const auto symm = to_string(S);
23 std::cout << "Stored non-zero two-body integrals (" << to_string(S)
24 << " symmetry): \n";
25 int k = 0;
26 auto total = 0ul;
27 for (auto &qk : m_data) {
28 fmt::print("k = {:2}: {}\n", k, qk.size());
29 // << " [" << qk.bucket_count()<< "]\n";
30 total += qk.size();
31 ++k;
32 }
33 std::cout << "Total: " << total << "\n";
34}
35
36template <Symmetry S>
37std::size_t CoulombTable<S>::count() const {
38 auto total = 0ul;
39 for (auto &qk : m_data) {
40 total += qk.size();
41 }
42 return total;
43}
44
45//==============================================================================
46template <Symmetry S>
48 Real value) {
49 add(k, NormalOrder(a, b, c, d), value);
50}
51//------------------------------------------------------------------------------
52template <Symmetry S>
53void CoulombTable<S>::add(int k, const DiracSpinor &a, const DiracSpinor &b,
54 const DiracSpinor &c, const DiracSpinor &d,
55 Real value) {
56 add(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index(), value);
57}
58//------------------------------------------------------------------------------
59template <Symmetry S>
60void CoulombTable<S>::add(int k, nk4Index index, Real value) {
61 const auto sk = std::size_t(k);
62 if (sk >= m_data.size()) {
63 m_data.resize(sk + 1);
64 }
65 m_data.at(sk).insert({index, value});
66}
67
68//==============================================================================
69template <Symmetry S>
70// Updates Q in table. If not present, adds new Q
72 Real value) {
73 update(k, NormalOrder(a, b, c, d), value);
74}
75
76//------------------------------------------------------------------------------
77template <Symmetry S>
78// Updates Q in table. If not present, adds new Q
79void CoulombTable<S>::update(int k, const DiracSpinor &a, const DiracSpinor &b,
80 const DiracSpinor &c, const DiracSpinor &d,
81 Real value) {
82 update(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index(), value);
83}
84
85//------------------------------------------------------------------------------
86template <Symmetry S>
87void CoulombTable<S>::update(int k, nk4Index index, Real value) {
88 const auto sk = std::size_t(k);
89 if (sk >= m_data.size()) {
90 m_data.resize(sk + 1);
91 }
92 m_data.at(sk).insert_or_assign(index, value);
93}
94//==============================================================================
95template <Symmetry S>
97 nkIndex d) const {
98 const auto sk = std::size_t(k);
99 if (sk >= m_data.size())
100 return false;
101 return m_data.at(sk).find(NormalOrder(a, b, c, d)) != m_data.at(sk).cend();
102}
103//------------------------------------------------------------------------------
104template <Symmetry S>
106 const DiracSpinor &b, const DiracSpinor &c,
107 const DiracSpinor &d) const {
108 return contains(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index());
109}
110//------------------------------------------------------------------------------
111template <Symmetry S>
112bool CoulombTable<S>::contains(int k, nk4Index index) const {
113 const auto sk = std::size_t(k);
114 if (sk >= m_data.size())
115 return false;
116 return m_data.at(sk).find(index) != m_data.at(sk).cend();
117}
118
119//==============================================================================
120//==============================================================================
121
122template <Symmetry S>
123double *CoulombTable<S>::get(int k, nk4Index index) {
124 const auto sk = std::size_t(k);
125 if (sk >= m_data.size())
126 return nullptr;
127 // check valid k? Probably faster to lookup in table
128 const auto map_it = m_data.at(sk).find(index);
129 if (map_it == m_data.at(sk).cend())
130 return nullptr;
131 return &(map_it->second);
132}
133
134//==============================================================================
135template <Symmetry S>
137 nkIndex d) const {
138 const auto sk = std::size_t(k);
139 if (sk >= m_data.size())
140 return 0.0;
141 // check valid k? Probably faster to lookup in table
142 const auto map_it = m_data.at(sk).find(NormalOrder(a, b, c, d));
143 if (map_it == m_data.at(sk).cend())
144 return 0.0;
145 return map_it->second;
146}
147//------------------------------------------------------------------------------
148template <Symmetry S>
149double CoulombTable<S>::Q(int k, const DiracSpinor &a, const DiracSpinor &b,
150 const DiracSpinor &c, const DiracSpinor &d) const {
151 return Q(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index());
152}
153//------------------------------------------------------------------------------
154template <Symmetry S>
155double CoulombTable<S>::Q(int k, nk4Index index) const {
156 const auto sk = std::size_t(k);
157 if (sk >= m_data.size())
158 return 0.0;
159 // check valid k? Probably faster to lookup in table
160 const auto map_it = m_data.at(sk).find(index);
161 if (map_it == m_data.at(sk).cend())
162 return 0.0;
163 return map_it->second;
164}
165
166//==============================================================================
167template <Symmetry S>
169 nkIndex d) const {
170 const auto tQk = Q(k, a, b, c, d);
171 if (tQk == 0.0)
172 return 0.0;
173 const auto s = Angular::neg1pow(k);
174
175 const auto [na, ka] = Angular::index_to_nk(a);
176 const auto [nb, kb] = Angular::index_to_nk(b);
177 const auto [nc, kc] = Angular::index_to_nk(c);
178 const auto [nd, kd] = Angular::index_to_nk(d);
179
180 const auto tCkac = Angular::tildeCk_kk(k, ka, kc);
181 const auto tCkbd = Angular::tildeCk_kk(k, kb, kd);
182 return tQk / (s * tCkac * tCkbd);
183}
184//------------------------------------------------------------------------------
185template <Symmetry S>
186double CoulombTable<S>::R(int k, const DiracSpinor &a, const DiracSpinor &b,
187 const DiracSpinor &c, const DiracSpinor &d) const {
188 return R(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index());
189}
190
191//==============================================================================
192template <Symmetry S>
194 const Angular::SixJTable *const sj) const {
195 double Pk_abcd{0.0};
196
197 const auto ka = Angular::nkindex_to_kappa(a);
198 const auto kb = Angular::nkindex_to_kappa(b);
199 const auto kc = Angular::nkindex_to_kappa(c);
200 const auto kd = Angular::nkindex_to_kappa(d);
201
202 const auto tja = Angular::twoj_k(ka);
203 const auto tjb = Angular::twoj_k(kb);
204 const auto tjc = Angular::twoj_k(kc);
205 const auto tjd = Angular::twoj_k(kd);
206
207 // 6j(s) Triads: {a,c,k}, {k,b,d}, {c,b,l}, {d,a,l}
208 if (Angular::triangle(tja, tjc, 2 * k) == 0 ||
209 Angular::triangle(2 * k, tjb, tjd) == 0)
210 return 0.0;
211
212 // We may store non-Coulomb integrals in this table (for example, Breit)
213 // Then, the parity selection rule doesn't apply,
214 // and we don't know what the actual rule is, so use whole range
215 const auto [lmin, lmax] = [&]() -> std::pair<int, int> {
216 if constexpr (S == Symmetry::Qk)
217 return k_minmax_Q(ka, kb, kd, kc);
218 else
219 return {0, max_k()};
220 }();
221 const int dl = (S == Symmetry::Qk) ? 2 : 1;
222
223 for (int l = lmin; l <= lmax; l += dl) {
224 const auto ql = this->Q(l, a, b, d, c); // exchange
225 if (ql == 0.0)
226 continue;
227 const auto sixj = sj ? sj->get_2(tja, tjc, 2 * k, tjb, tjd, 2 * l) :
228 Angular::sixj_2(tja, tjc, 2 * k, tjb, tjd, 2 * l);
229 Pk_abcd += sixj * ql;
230 }
231 Pk_abcd *= double(2 * k + 1);
232 return Pk_abcd;
233}
234
235//------------------------------------------------------------------------------
236
237template <Symmetry S>
238double CoulombTable<S>::P(int k, const DiracSpinor &a, const DiracSpinor &b,
239 const DiracSpinor &c, const DiracSpinor &d,
240 const Angular::SixJTable *const sj) const {
241
242 return P(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index(), sj);
243}
244
245//==============================================================================
246template <Symmetry S>
247double CoulombTable<S>::P2(int k, const DiracSpinor &a, const DiracSpinor &b,
248 const DiracSpinor &c, const DiracSpinor &d,
249 const Angular::SixJTable &sj,
250 const std::vector<double> &fk) const {
251 double Pk_abcd{0.0};
252
253 // 6j(s) Triads: {a,c,k}, {k,b,d}, {c,b,l}, {d,a,l}
254 if (Coulomb::triangle(a, c, k) == 0 || Coulomb::triangle(k, b, d) == 0)
255 return 0.0;
256
257 // We may store non-Coulomb integrals in this table (for example, Breit)
258 // Then, the parity selection rule doesn't apply,
259 // and we don't know what the actual rule is, so use whole range
260 const auto [lmin, lmax] = [&]() -> std::pair<int, int> {
261 if constexpr (S == Symmetry::Qk)
262 return k_minmax_Q(a, b, d, c);
263 else
264 return {0, max_k()};
265 }();
266 const int dl = (S == Symmetry::Qk) ? 2 : 1;
267
268 for (int l = lmin; l <= lmax; l += dl) {
269 const auto ql = this->Q(l, a, b, d, c); // exchange
270 if (ql == 0.0)
271 continue;
272 const auto sixj = sj.get(a, c, k, b, d, l);
273
274 // include effective Coulomb screening:
275 const auto f_scr_l = (l < (int)fk.size()) ? fk[std::size_t(l)] : 1.0;
276
277 Pk_abcd += f_scr_l * sixj * ql;
278 }
279 Pk_abcd *= double(2 * k + 1);
280 return Pk_abcd;
281}
282
283//==============================================================================
284template <Symmetry S>
285double CoulombTable<S>::W(int k, const DiracSpinor &a, const DiracSpinor &b,
286 const DiracSpinor &c, const DiracSpinor &d,
287 const Angular::SixJTable *const sj) const {
288 return W(k, a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index(), sj);
289}
290
291template <Symmetry S>
293 const Angular::SixJTable *const sj) const {
294 return Q(k, a, b, c, d) + P(k, a, b, c, d, sj);
295}
296
297//==============================================================================
298template <Symmetry S>
300 const DiracSpinor &c, const DiracSpinor &d, int tma,
301 int tmb, int tmc, int tmd) const {
302
303 if (tmc - tma != tmb - tmd)
304 return 0.0;
305 const int twoq = tmc - tma;
306
307 //
308 double g = 0.0;
309 const auto [k0, ki] = [&]() -> std::pair<int, int> {
310 if constexpr (S == Symmetry::Qk)
311 return k_minmax_Q(a, b, c, d);
312 else
313 return {0, max_k()};
314 }();
315 const int dk = (S == Symmetry::Qk) ? 2 : 1;
316
317 for (int k = k0; k <= ki; k += dk) {
318 if (std::abs(twoq) > 2 * k)
319 continue;
320
321 // const auto A =
322 // Angular::neg1pow_2(twoq) *
323 // Angular::Ck_kk_mmq(k, a.kappa(), c.kappa(), tma, tmc, -twoq) *
324 // Angular::Ck_kk_mmq(k, b.kappa(), d.kappa(), tmb, tmd, twoq);
325 // if (A != 0.0) {
326 // g += A * R(k, a, b, c, d);
327 // }
328
329 const auto tjs1 =
330 Angular::threej_2(a.twoj(), 2 * k, c.twoj(), -tma, -twoq, tmc);
331 const auto tjs2 =
332 Angular::threej_2(b.twoj(), 2 * k, d.twoj(), -tmb, twoq, tmd);
333 const auto s = Angular::neg1pow_2(2 * k + tmb - tma + twoq);
334 g += s * tjs1 * tjs2 * Q(k, a, b, c, d);
335 }
336 return g;
337}
338
339//==============================================================================
340//==============================================================================
341
342//==============================================================================
343template <>
344inline nk4Index
346 nkIndex d) {
347
348 // abcd -> ijkl, with i = min(a,b,c,d)
349
350 // abcd = adcb
351 // badc = bcda
352 // cbad = cdab
353 // dabc = dcba
354 // nb: there must be a more efficient way of doing this!
355
356 // const auto tmp1 = FormIndex(a, b, c, d);
357 // const auto tmp2 = FormIndex(a, d, c, b);
358 // const auto tmp3 = FormIndex(b, a, d, c);
359 // const auto tmp4 = FormIndex(b, c, d, a);
360 // const auto tmp5 = FormIndex(c, b, a, d);
361 // const auto tmp6 = FormIndex(c, d, a, b);
362 // const auto tmp7 = FormIndex(d, a, b, c);
363 // const auto tmp8 = FormIndex(d, c, b, a);
364 // return std::min({tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8});
365
366 nk4Index out = FormIndex(a, b, c, d);
367 const auto min = std::min({a, b, c, d});
368 if (a == min) {
369 const auto tmp = FormIndex(a, d, c, b);
370 if (tmp < out)
371 out = tmp;
372 }
373 if (b == min) {
374 const auto tmp = std::min(FormIndex(b, a, d, c), FormIndex(b, c, d, a));
375 if (tmp < out)
376 out = tmp;
377 }
378 if (c == min) {
379 const auto tmp = std::min(FormIndex(c, b, a, d), FormIndex(c, d, a, b));
380 if (tmp < out)
381 out = tmp;
382 }
383 if (d == min) {
384 const auto tmp = std::min(FormIndex(d, a, b, c), FormIndex(d, c, b, a));
385 if (tmp < out)
386 out = tmp;
387 }
388 return out;
389}
390//------------------------------------------------------------------------------
391template <>
392inline nk4Index
393CoulombTable<Symmetry::Wk>::NormalOrder_impl(nkIndex a, nkIndex b, nkIndex c,
394 nkIndex d) {
395 const auto tmp1 = FormIndex(a, b, c, d);
396 const auto tmp2 = FormIndex(b, a, d, c);
397 const auto tmp3 = FormIndex(c, d, a, b);
398 const auto tmp4 = FormIndex(d, c, b, a);
399 return std::min({tmp1, tmp2, tmp3, tmp4});
400}
401
402//------------------------------------------------------------------------------
403template <>
404inline nk4Index
405CoulombTable<Symmetry::Lk>::NormalOrder_impl(nkIndex a, nkIndex b, nkIndex c,
406 nkIndex d) {
407 const auto tmp1 = FormIndex(a, b, c, d);
408 const auto tmp2 = FormIndex(b, a, d, c);
409 return std::min({tmp1, tmp2});
410}
411
412//------------------------------------------------------------------------------
413template <>
414inline nk4Index
415CoulombTable<Symmetry::none>::NormalOrder_impl(nkIndex a, nkIndex b, nkIndex c,
416 nkIndex d) {
417 return FormIndex(a, b, c, d);
418}
419
420//==============================================================================
421template <Symmetry S>
423 nkIndex d) const {
424 return NormalOrder_impl(a, b, c, d);
425}
426//------------------------------------------------------------------------------
427template <Symmetry S>
430 const DiracSpinor &c, const DiracSpinor &d) const {
431 return NormalOrder(a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index());
432}
433
434//==============================================================================
435template <Symmetry S>
437 nkIndex d) const {
438 return FormIndex(a, b, c, d);
439}
440//------------------------------------------------------------------------------
441template <Symmetry S>
443 const DiracSpinor &b,
444 const DiracSpinor &c,
445 const DiracSpinor &d) const {
446 return CurrentOrder(a.nk_index(), b.nk_index(), c.nk_index(), d.nk_index());
447}
448
449//==============================================================================
450template <Symmetry S>
452 nkIndex d) {
453 // create a nk4Index from four small nkIndex, by laying out in memory
454
455 // static_assert(sizeof(nk4Index) == 4 * sizeof(nkIndex));
456 // nk4Index big_index;
457 // const auto p_index = reinterpret_cast<nkIndex *>(&big_index);
458 // std::memcpy(p_index, &d, sizeof(nkIndex));
459 // std::memcpy(p_index + 1, &c, sizeof(nkIndex));
460 // std::memcpy(p_index + 2, &b, sizeof(nkIndex));
461 // std::memcpy(p_index + 3, &a, sizeof(nkIndex));
462 // return big_index;
463
464 // this seems slightly faster...though variance large
465 static_assert(sizeof(nk4Index) == 4 * sizeof(nkIndex));
466 static_assert(sizeof(nkIndex) * 8 == 16);
467 return ((nk4Index)a << 48) + ((nk4Index)b << 32) + ((nk4Index)c << 16) +
468 (nk4Index)d;
469}
470
471//==============================================================================
472template <Symmetry S>
473std::array<nkIndex, 4>
475 static_assert(sizeof(nk4Index) == sizeof(std::array<nkIndex, 4>));
476 std::array<nkIndex, 4> set;
477 std::memcpy(&set, &index, sizeof(nk4Index));
478 // note: this returns {d, c, b, a}, not {a, b, c, d}
479 // A warning: this might depend on "endiness"
480 // It's OK, this function is never used???
481 // If there's an issue, a unit test will fail, so it's "safe"
482 std::swap(set[0], set[3]);
483 std::swap(set[1], set[2]);
484 return set;
485}
486
487//==============================================================================
488template <Symmetry S>
489std::array<std::size_t, 4>
490CoulombTable<S>::count_non_zero_integrals(const std::vector<DiracSpinor> &basis,
491 std::size_t k_cutoff,
492 double eF) const {
493 static_assert(S == Symmetry::Qk);
494
495 // -1 account for parity selection rule
496 // (should be same as 2*max_l, without the -1)
497 const auto tmp_max_k =
498 std::size_t(std::max(DiracSpinor::max_tj(basis), 1) - 1);
499 const auto max_k =
500 (k_cutoff <= 0) ? tmp_max_k : std::min(tmp_max_k, std::size_t(k_cutoff));
501
502 std::vector<std::size_t> count_non_zero_k_tot(max_k + 1);
503 std::vector<std::size_t> count_non_zero_k_Excited(max_k + 1);
504 std::vector<std::size_t> count_non_zero_k_MBPT(max_k + 1);
505
506 const auto select_Q_Excited = [eF](int, const DiracSpinor &s,
507 const DiracSpinor &t, const DiracSpinor &u,
508 const DiracSpinor &v) {
509 auto num = Coulomb::number_below_Fermi(s, t, u, v, eF);
510 return num == 0;
511 };
512
513 const auto select_Q_MBPT = [eF](int, const DiracSpinor &s,
514 const DiracSpinor &t, const DiracSpinor &u,
515 const DiracSpinor &v) {
516 auto num = Coulomb::number_below_Fermi(s, t, u, v, eF);
517 return num == 1 || num == 2;
518 };
519
520#pragma omp parallel for schedule(dynamic, 1)
521 for (auto k = 0ul; k <= max_k; ++k) {
522 const auto ik = static_cast<int>(k);
523 for (const auto &a : basis) {
524 for (const auto &b : basis) {
525 for (const auto &c : basis) {
526 if (!Angular::Ck_kk_SR(ik, a.kappa(), c.kappa()))
527 continue;
528 for (const auto &d : basis) {
529 // due to symmetry, only calculate each 'unique' integral once
530 if (NormalOrder(a, b, c, d) == CurrentOrder(a, b, c, d)) {
531 if (Angular::Ck_kk_SR(ik, b.kappa(), d.kappa())) {
532 ++count_non_zero_k_tot[k];
533 if (eF != 0.0) {
534 if (select_Q_Excited(0, a, b, c, d)) {
535 ++count_non_zero_k_Excited[k];
536 }
537 if (select_Q_MBPT(0, a, b, c, d)) {
538 ++count_non_zero_k_MBPT[k];
539 }
540 }
541 }
542 }
543 }
544 }
545 }
546 }
547 }
548 // return count_non_zero_k;
549
550 const auto count_non_zero_tot = std::accumulate(
551 count_non_zero_k_tot.begin(), count_non_zero_k_tot.end(), 0ul);
552
553 const auto count_non_zero_Excited = std::accumulate(
554 count_non_zero_k_Excited.begin(), count_non_zero_k_Excited.end(), 0ul);
555
556 const auto count_non_zero_MBPT = std::accumulate(
557 count_non_zero_k_MBPT.begin(), count_non_zero_k_MBPT.end(), 0ul);
558
559 return {count_non_zero_tot, count_non_zero_Excited, count_non_zero_MBPT,
560 max_k};
561}
562
563//==============================================================================
564//==============================================================================
565template <Symmetry S>
566void CoulombTable<S>::fill(const std::vector<DiracSpinor> &basis,
567 const YkTable &yk, int k_cut, bool print) {
568 static_assert(S == Symmetry::Qk);
569 IO::ChronoTimer t("", print);
570
571 /*
572 In order to make best use of CPU and Memory, we "fill" the QK table in a
573 strange 4-step manner.
574 1. Count the number of non-zero Q's for each k
575 2. Use info from aboe to 'reserve()' space in the map
576 3. Fill all the non-zero Q entries in the map with 0. Note that this adds new
577 elements into the map, so cannot be done in parallel. Since we store a
578 different map for each k, we may parallelise over k (though this is not super
579 efficient)
580 4. Now that we have a fully sized map, we can update each of its values in
581 parallel
582 Note that we make use of the symmetry to ensure we do not access any element
583 from more than 1 thread (also, allowing us to not do any more calculations
584 than necisary)
585 */
586
587 // -1 account for parity selection rule
588 // (should be same as 2*max_l, without the -1)
589 const auto tmp_max_k =
590 std::size_t(std::max(DiracSpinor::max_tj(basis), 1) - 1);
591
592 const auto max_k =
593 (k_cut <= 0) ? tmp_max_k : std::min(tmp_max_k, std::size_t(k_cut));
594
595 if (m_data.size() < max_k + 1)
596 m_data.resize(max_k + 1);
597
598 // 1) Count non-zero Q integrals (each k). Use this to 'reserve' map space=
599 std::vector<std::size_t> count_non_zero_k(max_k + 1);
600#pragma omp parallel for
601 for (auto k = 0ul; k <= max_k; ++k) {
602 const auto ik = static_cast<int>(k);
603 for (const auto &a : basis) {
604 for (const auto &b : basis) {
605 for (const auto &c : basis) {
606 if (!Angular::Ck_kk_SR(ik, a.kappa(), c.kappa()))
607 continue;
608 for (const auto &d : basis) {
609 // due to symmetry, only calculate each 'unique' integral once
610 if (NormalOrder(a, b, c, d) == CurrentOrder(a, b, c, d)) {
611 if (Angular::Ck_kk_SR(ik, b.kappa(), d.kappa())) {
612 ++count_non_zero_k[k];
613 }
614 }
615 }
616 }
617 }
618 }
619 }
620
621 // If table already holds every required integral: skip.
622 // Pass basis to also check orbital coverage (full fill -> coverage implies
623 // completeness; the count alone is fooled by a different basis with a
624 // coincidentally-larger per-k count but missing angular-momentum channels).
625 if (already_filled(count_non_zero_k, basis)) {
626 return;
627 }
628
629 // 2) Reserve space in each sub-map
630#pragma omp parallel for
631 for (auto ik = 0ul; ik <= max_k; ++ik) {
632 m_data[ik].reserve(count_non_zero_k[ik]);
633 }
634 const auto n_existing = count();
635
636 // 3) Create space in map (set each element to zero).
637#pragma omp parallel for schedule(dynamic, 1)
638 for (auto k = 0ul; k <= max_k; ++k) {
639 for (const auto &a : basis) {
640 for (const auto &c : basis) {
641 if (!Angular::Ck_kk_SR(int(k), a.kappa(), c.kappa()))
642 continue;
643 for (const auto &b : basis) {
644 for (const auto &d : basis) {
645 if (!Angular::Ck_kk_SR(int(k), b.kappa(), d.kappa()))
646 continue;
647 const auto normal_index = NormalOrder(a, b, c, d);
648 if (normal_index == CurrentOrder(a, b, c, d)) {
649 m_data[k].insert({normal_index, 0.0});
650 }
651 }
652 }
653 }
654 }
655 }
656
657 // 4) Fill the pre-constructed map with values, in parallel. Since we are
658 // not adding any new elements to map, and since we are guarenteed to only
659 // access each map element once, we can do this part in parallel.
660 if (print)
661 fmt::print("Calculating {}{} integrals\n",
662 n_existing == 0 ? "" : "(subset of) ", count());
663 t.start();
664 qip::ProgressBar prog(int(basis.size()), print);
665#pragma omp parallel for schedule(dynamic, 1)
666 for (auto ia = 0ul; ia < basis.size(); ++ia) {
667 const auto &a = basis[ia];
668 for (const auto &b : basis) {
669 for (const auto &c : basis) {
670 for (const auto &d : basis) {
671
672 const auto normal_index = NormalOrder(a, b, c, d);
673 if (normal_index != CurrentOrder(a, b, c, d))
674 continue;
675
676 const auto [kmin, kmax] = k_minmax_Q(a, b, c, d);
677 for (int k = kmin; k <= kmax && k <= int(max_k); k += 2) {
678 auto it = m_data[std::size_t(k)].find(normal_index);
679 assert(it != m_data[std::size_t(k)].end());
680 double *ptr = &it->second;
681 if (*ptr == 0.0) {
682 // only calculate if not already in table
683 // This saves some time, but surprisingly little!
684 *ptr = yk.Q(k, a, b, c, d);
685 }
686 }
687 }
688 }
689 }
690 prog.update();
691 }
692
693 if (print)
694 summary();
695}
696
697//==============================================================================
698template <Symmetry S>
699void CoulombTable<S>::fill_if(const std::vector<DiracSpinor> &basis,
700 const YkTable &yk,
701 const SelectionRules &SelectionFunction,
702 int k_cut, bool print) {
703 static_assert(S == Symmetry::Qk);
704 IO::ChronoTimer t("", print);
705
706 /*
707 Same as above, but only calculate integrals satisfying the SelectionFunction
708 */
709
710 const auto tmp_max_k =
711 std::size_t(std::max(DiracSpinor::max_tj(basis), 1) - 1);
712
713 const auto max_k =
714 (k_cut <= 0) ? tmp_max_k : std::min(tmp_max_k, std::size_t(k_cut));
715
716 if (m_data.size() < max_k + 1)
717 m_data.resize(max_k + 1);
718
719 // 1) Count non-zero Q integrals (each k). Use this to 'reserve' map space
720 std::vector<std::size_t> count_non_zero_k(max_k + 1);
721#pragma omp parallel for
722 for (auto k = 0ul; k <= max_k; ++k) {
723 const auto ik = static_cast<int>(k);
724 for (const auto &a : basis) {
725 for (const auto &b : basis) {
726 for (const auto &c : basis) {
727 if (!Angular::Ck_kk_SR(ik, a.kappa(), c.kappa()))
728 continue;
729 for (const auto &d : basis) {
730 // due to symmetry, only calculate each 'unique' integral once
731 if (NormalOrder(a, b, c, d) == CurrentOrder(a, b, c, d)) {
732 if (Angular::Ck_kk_SR(ik, b.kappa(), d.kappa()) &&
733 SelectionFunction(ik, a, b, c, d)) {
734 ++count_non_zero_k[k];
735 }
736 }
737 }
738 }
739 }
740 }
741 }
742
743 // 2) Reserve space in each sub-map
744#pragma omp parallel for
745 for (auto ik = 0ul; ik <= max_k; ++ik) {
746 m_data[ik].reserve(count_non_zero_k[ik]);
747 }
748 const auto n_existing = count();
749
750 // 3) Create space in map (set each element to zero).
751#pragma omp parallel for
752 for (auto k = 0ul; k <= max_k; ++k) {
753 for (const auto &a : basis) {
754 for (const auto &c : basis) {
755 if (!Angular::Ck_kk_SR(int(k), a.kappa(), c.kappa()))
756 continue;
757 for (const auto &b : basis) {
758 for (const auto &d : basis) {
759 if (!Angular::Ck_kk_SR(int(k), b.kappa(), d.kappa()))
760 continue;
761 if (!SelectionFunction(int(k), a, b, c, d))
762 continue;
763 const auto normal_index = NormalOrder(a, b, c, d);
764 if (normal_index == CurrentOrder(a, b, c, d)) {
765 m_data[k].insert({normal_index, 0.0});
766 }
767 }
768 }
769 }
770 }
771 }
772
773 // 4) Fill the pre-constructed map with values, in parallel. Since we are
774 // not adding any new elements to map, and since we are guarenteed to only
775 // access each map element once, we can do this part in parallel.
776 if (print)
777 fmt::print("Calculating {}{} integrals\n",
778 n_existing == 0 ? "" : "(subset of) ", count());
779 t.start();
780 qip::ProgressBar prog(int(basis.size()), print);
781#pragma omp parallel for schedule(dynamic, 1)
782 for (auto ia = 0ul; ia < basis.size(); ++ia) {
783 const auto &a = basis[ia];
784 for (const auto &b : basis) {
785 for (const auto &c : basis) {
786 for (const auto &d : basis) {
787
788 const auto normal_index = NormalOrder(a, b, c, d);
789 if (normal_index != CurrentOrder(a, b, c, d))
790 continue;
791
792 const auto [kmin, kmax] = k_minmax_Q(a, b, c, d);
793 for (int k = kmin; k <= kmax && k <= int(max_k); k += 2) {
794 if (!SelectionFunction(k, a, b, c, d))
795 continue;
796 auto it = m_data[std::size_t(k)].find(normal_index);
797 assert(it != m_data[std::size_t(k)].end());
798 double *ptr = &it->second;
799 if (*ptr == 0.0) {
800 *ptr = yk.Q(k, a, b, c, d);
801 }
802 }
803 }
804 }
805 }
806 prog.update();
807 }
808
809 if (print)
810 summary();
811}
812
813//==============================================================================
814template <Symmetry S>
815void CoulombTable<S>::fill(const std::vector<DiracSpinor> &basis,
816 const CoulombFunction &Fk,
817 const SelectionRules &Fk_SR, int k_cut, bool print) {
818 IO::ChronoTimer t("", print);
819
820 /*
821 In order to make best use of CPU and Memory, we "fill" the QK table in a
822 strange 4-step manner.
823 1. Count the number of non-zero Q's for each k
824 2. Use info from aboe to 'reserve()' space in the map
825 3. Fill all the non-zero Q entries in the map with 0. Note that this adds new
826 elements into the map, so cannot be done in parallel. Since we store a
827 different map for each k, we may parallelise over k (though this is not super
828 efficient)
829 4. Now that we have a fully sized map, we can update each of its values in
830 parallel
831 Note that we make use of the symmetry to ensure we do not access any element
832 from more than 1 thread (also, allowing us to not do any more calculations
833 than necisary)
834 */
835
836 const auto tmp_max_k = std::size_t(DiracSpinor::max_tj(basis));
837 // May have different parity rule, so don't -1 here
838
839 const auto max_k =
840 (k_cut <= 0) ? tmp_max_k : std::min(tmp_max_k, std::size_t(k_cut));
841
842 if (m_data.size() < max_k + 1)
843 m_data.resize(max_k + 1);
844
845 // 1) Count non-zero Q integrals (each k). Use this to 'reserve' map space
846 std::vector<std::size_t> count_non_zero_k(max_k + 1);
847#pragma omp parallel for
848 for (auto k = 0ul; k <= max_k; ++k) {
849 const auto ik = static_cast<int>(k);
850 for (const auto &a : basis) {
851 for (const auto &b : basis) {
852 for (const auto &c : basis) {
853 for (const auto &d : basis) {
854 // due to symmetry, only calculate each 'unique' integral once
855 if (NormalOrder(a, b, c, d) == CurrentOrder(a, b, c, d)) {
856 if (Fk_SR(ik, a, b, c, d)) {
857 ++count_non_zero_k[k];
858 }
859 }
860 }
861 }
862 }
863 }
864 }
865
866 // No early-skip: step 4 only computes entries not already present.
867
868 // 2) Reserve space in each sub-map
869#pragma omp parallel for
870 for (auto ik = 0ul; ik <= max_k; ++ik) {
871 m_data[ik].reserve(count_non_zero_k[ik]);
872 }
873 const auto n_existing = count();
874
875 // 3) Create space in map (set each element to zero).
876#pragma omp parallel for
877 for (auto k = 0ul; k <= max_k; ++k) {
878 for (const auto &a : basis) {
879 for (const auto &b : basis) {
880 for (const auto &c : basis) {
881 for (const auto &d : basis) {
882 if (Fk_SR(int(k), a, b, c, d)) {
883 const auto normal_index = NormalOrder(a, b, c, d);
884 if (normal_index == CurrentOrder(a, b, c, d)) {
885 m_data[k].insert({normal_index, 0.0});
886 }
887 }
888 }
889 }
890 }
891 }
892 }
893
894 // 4) Fill the pre-constructed map with values, in parallel. Since we are
895 // not adding any new elements to map, and since we are guarenteed to only
896 // access each map element once, we can do this part in parallel.
897 if (print)
898 fmt::print("Calculating {}{} integrals\n",
899 n_existing == 0 ? "" : "(subset of) ", count());
900 t.start();
901
902 qip::ProgressBar prog(int(basis.size()), print);
903#pragma omp parallel for schedule(dynamic, 1)
904 for (std::size_t ia = 0; ia < basis.size(); ++ia) {
905
906 const auto &a = basis[ia];
907 for (const auto &b : basis) {
908 for (const auto &c : basis) {
909 for (const auto &d : basis) {
910
911 const auto normal_index = NormalOrder(a, b, c, d);
912 if (normal_index != CurrentOrder(a, b, c, d))
913 continue;
914
915 for (int k = 0; k <= int(max_k); ++k) {
916
917 if (!Fk_SR(k, a, b, c, d))
918 continue;
919 auto it = m_data[std::size_t(k)].find(normal_index);
920 assert(it != m_data[std::size_t(k)].end());
921 double *ptr = &it->second;
922 if (*ptr == 0.0) {
923 // only calculate if not already in table
924 *ptr = Fk(k, a, b, c, d);
925 }
926 }
927 }
928 }
929 }
930
931 prog.update();
932 }
933
934 // if (print)
935 // summary();
936}
937
938//==============================================================================
939template <Symmetry S>
941 const std::vector<DiracSpinor> &basis, const CoulombFunction &Fk, double damp,
942 bool print,
943 const std::function<bool(const DiracSpinor &, const DiracSpinor &,
944 const DiracSpinor &, const DiracSpinor &)> &filter) {
945 IO::ChronoTimer t("", print);
946
947 const auto max_k = m_data.size() - 1;
948
949 // zero mean no damping (default)
950 // damp must be between [0, 1) - cannot equal 1 (meaningless)
951 assert(damp != 1.0); // also: catches accidentally passing 'true' (print)
952 const auto x = std::clamp(damp, 0.0, 1.0);
953 const auto y = 1.0 - damp;
954
955 // Fill the pre-constructed map with values, in parallel. Since we are
956 // not adding any new elements to map, and since we are guarenteed to only
957 // access each map element once, we can do this part in parallel.
958
959 if (print) {
960 fmt::print("Updating {}{} integrals\n", filter ? "(subset of) " : "",
961 count());
962 }
963 qip::ProgressBar prog(int(basis.size()), print);
964#pragma omp parallel for schedule(dynamic, 1)
965 for (std::size_t ia = 0; ia < basis.size(); ++ia) {
966
967 const auto &a = basis[ia];
968 for (const auto &b : basis) {
969 for (const auto &c : basis) {
970 for (const auto &d : basis) {
971
972 // Now, we can check symmetries, selectrion rules etc.
973 // just be seeing if this one is in the current table!
974 const auto c_index = CurrentOrder(a, b, c, d);
975 const auto n_index = NormalOrder(a, b, c, d);
976 if (c_index != n_index)
977 continue;
978
979 // Skip (leave unchanged) entries the filter rejects - before any
980 // per-k lookup or recalculation.
981 if (filter && !filter(a, b, c, d))
982 continue;
983
984 for (int k = 0; k <= int(max_k); ++k) {
985 double *ptr = get(k, n_index);
986 // if not null, means already in table: so update it
987 if (ptr != nullptr) {
988 *ptr = (*ptr == 0.0 || x == 0.0) ?
989 Fk(k, a, b, c, d) :
990 y * Fk(k, a, b, c, d) + x * *ptr;
991 }
992 }
993 }
994 }
995 }
996
997 prog.update();
998 }
999}
1000
1001//==============================================================================
1002// Plain (key, value) pair for bulk (single-call) file read/write of the table
1003// data. Byte-layout matches the (older) element-by-element format, [key][value]
1004// pairs in sequence, so files are fully compatible (checked by static_asserts).
1005struct KeyValueBlock {
1006 nk4Index key;
1007 Real value;
1008};
1009static_assert(std::is_trivially_copyable_v<KeyValueBlock>);
1010static_assert(sizeof(KeyValueBlock) == sizeof(nk4Index) + sizeof(Real));
1011
1012//==============================================================================
1013template <Symmetry S>
1014void CoulombTable<S>::write(const std::string &fname, bool verbose) const {
1015 if (fname == "false" || fname == "")
1016 return;
1017 if (verbose)
1018 std::cout << "Writing " << count() << " integrals to file: " << fname
1019 << ".." << std::flush;
1020 std::fstream f;
1021 const auto rw = IO::FRW::write;
1022 IO::FRW::open_binary(f, fname, rw);
1023
1024 auto size = m_data.size();
1025 rw_binary(f, rw, size);
1026 std::vector<KeyValueBlock> block;
1027 for (const auto &Q_k : m_data) {
1028 auto size_k = Q_k.size();
1029 rw_binary(f, rw, size_k);
1030 // Bulk write: copy block to contiguous buffer, write in single call
1031 // (element-by-element stream writes dominate the cost otherwise)
1032 block.clear();
1033 block.reserve(size_k);
1034 for (const auto &[key, value] : Q_k) {
1035 block.push_back({key, value});
1036 }
1037 f.write(reinterpret_cast<const char *>(block.data()),
1038 std::streamsize(block.size() * sizeof(KeyValueBlock)));
1039 }
1040 if (verbose)
1041 std::cout << "\n" << std::flush;
1042}
1043
1044//==============================================================================
1045template <Symmetry S>
1046bool CoulombTable<S>::read(const std::string &fname, bool verbose,
1047 bool parse_basis_string) {
1048 if (fname == "false" || fname.empty())
1049 return false;
1050
1051 std::fstream f;
1052 const auto rw = IO::FRW::read;
1053 IO::FRW::open_binary(f, fname, rw);
1054
1055 // store max_n for each l: allows us to reconstruct the basis string
1056 // This adds noticable overhead, so only done if parse_basis_string requested
1057 std::vector<int> max_n_l;
1058
1059 if (!f.good())
1060 return false;
1061
1062 if (verbose)
1063 std::cout << "Reading: " << fname << "\n" << std::flush;
1064
1065 std::size_t size{0};
1066 rw_binary(f, rw, size);
1067 m_data.resize(size);
1068 if (m_data.size() == 0)
1069 return false;
1070 std::vector<KeyValueBlock> block;
1071 for (std::size_t i = 0; i < m_data.size(); ++i) {
1072 auto &Q_k = m_data[i];
1073 std::size_t size_k{0};
1074 rw_binary(f, rw, size_k);
1075 Q_k.reserve(size_k);
1076
1077 // Bulk read: whole k-block in a single call, then insert from memory
1078 // (element-by-element stream reads dominate the cost otherwise)
1079 block.resize(size_k);
1080 f.read(reinterpret_cast<char *>(block.data()),
1081 std::streamsize(size_k * sizeof(KeyValueBlock)));
1082 for (const auto &[key, value] : block) {
1083 Q_k[key] = value;
1084
1085 // determine basis string (max n for each l)
1086 if (parse_basis_string) {
1087 const auto indexes = UnFormIndex(key);
1088 for (const auto index : indexes) {
1089 const auto [n, ki] = Angular::nkindex_to_n_kindex(index);
1090 const auto l = std::size_t(Angular::kindex_to_l(ki));
1091 if (max_n_l.size() < l + 1)
1092 max_n_l.resize(l + 1);
1093 if (n > max_n_l.at(l))
1094 max_n_l.at(l) = n;
1095 }
1096 }
1097 }
1098 }
1099
1100 if (verbose) {
1101 // convert list to basis string (max n for each l)
1102 int last_n = 0;
1103 int l = 0;
1104 std::string basis_str;
1105 for (const auto &n : max_n_l) {
1106 if (n != last_n)
1107 basis_str += std::to_string(n);
1108 basis_str += AtomData::l_symbol(l);
1109 last_n = n;
1110 l++;
1111 }
1112 if (!basis_str.empty())
1113 basis_str = "[" + basis_str + "] ";
1114
1115 std::cout << "Read " << count() << " integrals " << basis_str
1116 << "from file: " << fname << "\n"
1117 << std::flush;
1118 }
1119
1120 return true;
1121}
1122
1123} // namespace Coulomb
Lookup table for Wigner 6j symbols.
Definition SixJTable.hpp:82
double get_2(int a, int b, int c, int d, int e, int f) const
Returns 6j symbol {a/2, b/2, c/2; d/2, e/2, f/2}.
Definition SixJTable.hpp:114
double get(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f) const
"Magic" table lookup: pass integers (for k) or DiracSpinors (for j).
Definition SixJTable.hpp:130
Base class template to store Coulomb integrals, and similar. 3 specific cases (by template instantiat...
Definition QkTable.hpp:118
Real P2(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d, const Angular::SixJTable &sj, const std::vector< double > &fk) const
Exchange integral P^k_abcd with effective Coulomb screening.
Definition QkTable.ipp:247
double * get(int k, nk4Index index)
Directly gets one of the stored elements, given normal-ordered nk4Index.
Definition QkTable.ipp:123
void fill_if(const std::vector< DiracSpinor > &basis, const YkTable &yk, const SelectionRules &SelectionFunction, int k_cut=-1, bool print=true)
Fill table with Coulomb Q integrals satisfying a selection rule.
Definition QkTable.ipp:699
Real P(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d, const Angular::SixJTable *const sj=nullptr) const
Exchange integral P^k_abcd = (2k+1) sum_l {6j} Q^l_abdc.
Definition QkTable.ipp:238
std::array< std::size_t, 4 > count_non_zero_integrals(const std::vector< DiracSpinor > &basis, std::size_t max_k=99, double eF=0.0) const
Definition QkTable.ipp:490
void update(const std::vector< DiracSpinor > &basis, const CoulombFunction &Fk, double damp, bool print=true, const std::function< bool(const DiracSpinor &, const DiracSpinor &, const DiracSpinor &, const DiracSpinor &)> &filter={})
Re-calculate all existing table entries using a CoulombFunction.
Definition QkTable.ipp:940
void write(const std::string &fname, bool verbose=true) const
Writes coulomb integrals to disk.
Definition QkTable.ipp:1014
bool contains(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d) const
Checks if given {k,a,b,c,d} is in the table.
Definition QkTable.ipp:105
nk4Index NormalOrder(const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d) const
Creates single 'nk4Index' corresponding to 'NormalOrder' symmetry of {a,b,c,d}.
Definition QkTable.ipp:429
nk4Index CurrentOrder(const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d) const
Creates single 'nk4Index', WITHOUT accounting for 'NormalOrder'. Can be used to check if {a,...
Definition QkTable.ipp:442
static nk4Index FormIndex(nkIndex a, nkIndex b, nkIndex c, nkIndex d)
Converts given set of nkIndex's (in any order) to nk4Index.
Definition QkTable.ipp:451
Real R(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d) const
Returns 'R', defined via: R := Q / (angular_coef)
Definition QkTable.ipp:186
std::array< nkIndex, 4 > UnFormIndex(const nk4Index &index) const
Breaks nk4Index back into {ia,ib,ic,id}. Not often used.
Definition QkTable.ipp:474
void fill(const std::vector< DiracSpinor > &basis, const YkTable &yk, int k_cut=-1, bool print=true)
Fill table with all non-zero Coulomb Q integrals from a YkTable.
Definition QkTable.ipp:566
bool read(const std::string &fname, bool verbose=true, bool parse_basis_string=false)
Reads coulomb integrals to disk. Returns false if none read in. If parse_basis_string is true,...
Definition QkTable.ipp:1046
void summary() const
For testing: prints details of coulomb integrals stored.
Definition QkTable.ipp:21
Real g(const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d, int tma, int tmb, int tmc, int tmd) const
Full matrix element g_abcd with explicit magnetic quantum numbers.
Definition QkTable.ipp:299
void add(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d, Real value)
adds a new value. Note: does nothing if {k,a,b,c,d} already exists
Definition QkTable.ipp:53
Real Q(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d) const
Retrieve a stored Q. If not present, returns 0. (Returns exactly as stored in table....
Definition QkTable.ipp:149
Real W(int k, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d, const Angular::SixJTable *const sj=nullptr) const
Antisymmetrised integral W^k_abcd = Q^k_abcd + P^k_abcd.
Definition QkTable.ipp:285
std::size_t count() const
Returns number of stored integrals.
Definition QkTable.ipp:37
Calculates + stores Hartree Y functions + Angular (w/ look-up), taking advantage of symmetry.
Definition YkTable.hpp:46
double Q(const int k, const DiracSpinor &Fa, const DiracSpinor &Fb, const DiracSpinor &Fc, const DiracSpinor &Fd) const
Calculates Qk using the existing yk integrals. Note: Ck tables must include all required values,...
Definition YkTable.cpp:129
Stores radial Dirac spinor: F_nk = (f, g)
Definition DiracSpinor.hpp:55
static int max_tj(const std::vector< DiracSpinor > &orbs)
Returns maximum (2j) found in {orbs}.
Definition DiracSpinor.cpp:354
Index nk_index() const
(n,kappa) index
Definition DiracSpinor.hpp:143
int twoj() const
2j (twice the total angular momentum)
Definition DiracSpinor.hpp:121
Class that uses std::chrono to easily time code.
Definition ChronoTimer.hpp:35
void start()
Starts (or resumes) timing.
Definition ChronoTimer.hpp:100
Thread-safe progress bar for OpenMP parallel loops.
Definition Widgets.hpp:194
void update()
Atomically increment progress counter and print updated bar.
Definition Widgets.hpp:223
std::pair< int, int > index_to_nk(std::uint64_t nk_index)
Returns {n, kappa} given nk_index.
Definition Wigner369j.hpp:191
double threej_2(int two_j1, int two_j2, int two_j3, int two_m1, int two_m2, int two_m3)
Wigner 3j symbol. Inputs are 2*j (or 2*l) as integers.
Definition Wigner369j.hpp:277
double sixj_2(int two_j1, int two_j2, int two_j3, int two_j4, int two_j5, int two_j6)
Wigner 6j symbol {j1 j2 j3 | j4 j5 j6}. Inputs are 2*j as integers.
Definition Wigner369j.hpp:431
int nkindex_to_kappa(std::uint64_t nk_index)
Returns kappa, given nk_index.
Definition Wigner369j.hpp:209
constexpr int neg1pow(int a)
Evaluates (-1)^{a} (for integer a)
Definition Wigner369j.hpp:237
std::pair< int, std::uint64_t > nkindex_to_n_kindex(std::uint64_t nk_index)
Returns {n, kappa_index} given nk_index.
Definition Wigner369j.hpp:201
double tildeCk_kk(int k, int ka, int kb)
Tilde variant of the C^k reduced ME: (-1)^{ja+1/2} * <ka||C^k||kb>
Definition Wigner369j.hpp:557
bool Ck_kk_SR(int k, int ka, int kb)
Selection rule check for C^k: returns true if <ka||C^k||kb> may be non-zero.
Definition Wigner369j.hpp:537
constexpr int kindex_to_l(std::uint64_t i)
returns l, given kappa_index
Definition Wigner369j.hpp:116
constexpr int neg1pow_2(int two_a)
Evaluates (-1)^{two_a/2} (for integer a; two_a is even)
Definition Wigner369j.hpp:239
constexpr int triangle(int j1, int j2, int J)
Returns 1 if triangle rule is satisfied. nb: works with j OR twoj!
Definition Wigner369j.hpp:249
constexpr int twoj_k(int ka)
returns 2j given kappa
Definition Wigner369j.hpp:46
std::string l_symbol(int l)
l (int) to symbol (e.g., 0->'s', 1->'p')
Definition AtomData.cpp:111
Functions (+classes) for computing Coulomb integrals.
Definition CoulombBreit.cpp:13
double Real
Data type used to store integrals.
Definition QkTable.hpp:37
std::function< bool(int, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d)> SelectionRules
Function type for determining Coulomb(-like) integral selection rules. Takes k and 4 DiracSpinors,...
Definition QkTable.hpp:70
std::string_view to_string(Symmetry s)
Convert Symmetry to string.
Definition QkTable.hpp:22
int number_below_Fermi(const DiracSpinor &i, const DiracSpinor &j, const DiracSpinor &k, const DiracSpinor &l, double eFermi)
Returns number of orbitals that are below Fermi level. Used for Qk selection.
Definition CoulombIntegrals.cpp:452
uint64_t nk4Index
index type for set of 4 orbitals {nk,nk,nk,nk} -> nk4Index [max n: 256]
Definition QkTable.hpp:41
double Pk_abcd(const int k, const DiracSpinor &Fa, const DiracSpinor &Fb, const DiracSpinor &Fc, const DiracSpinor &Fd)
Exchange only version of W (W-Q): W = Q + P [see Qk above].
Definition CoulombIntegrals.cpp:361
uint16_t nkIndex
index type for each {nk} (orbital) [max n: 256]
Definition QkTable.hpp:45
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
std::function< double(int, const DiracSpinor &a, const DiracSpinor &b, const DiracSpinor &c, const DiracSpinor &d)> CoulombFunction
Function type for calculating Coulomb(-like) integrals. Takes k and 4 DiracSpinors,...
Definition QkTable.hpp:64
void open_binary(std::fstream &stream, const std::string &fname, RoW row)
Opens a binary fstream for reading or writing according to row. update opens existing file for random...
Definition FRW_fileReadWrite.hpp:109