ampsci
High-precision calculations for one- and two-valence atomic systems
SixJTable.hpp
1#pragma once
2#include "Angular/Wigner369j.hpp"
3#include "Wavefunction/DiracSpinor.hpp" // for 'magic' 6J symbols
4#include <algorithm>
5#include <cassert>
6#include <cstdint>
7#include <cstring>
8#include <iostream>
9#include <unordered_map>
10#include <vector>
11
12// XXX Note: This is significantly faster if implemented in header file, not
13// sepperate cpp file. Seems due to inlineing of 'get' function
14
15namespace Angular {
16
17//=============================================================================
18// If given an integer (k), returns 2*k
19// If given a DiracSpinor, F, returns 2*j [F.twoj()]
20template <class A>
21int twojk(const A &a) {
22 if constexpr (std::is_same_v<A, DiracSpinor>) {
23 return a.twoj();
24 } else if constexpr (std::is_same_v<A, int>) {
25 return 2 * a;
26 } else {
27 static_assert(std::is_same_v<A, std::size_t>);
28 return 2 * static_cast<int>(a);
29 }
30}
31
32//! "Magic" 6j, for integers and DiracSpinors. Pass int (for k), or DiracSpinor for j.
33//! e.g.: (F,F,k,F,F,k) -> {j,j,k,j,j,k}. Do NOT *2! Do NOT pass j or 2j directly!
34template <class A, class B, class C, class D, class E, class F>
35double SixJ(const A &a, const B &b, const C &c, const D &d, const E &e,
36 const F &f) {
37 return sixj_2(twojk(a), twojk(b), twojk(c), twojk(d), twojk(e), twojk(f));
38}
39
40//==============================================================================
41
42//! Lookup table for Wigner 6J symbols.
43/*! @details
44Note: functions all called with 2*j and 2*k (ensure j integer)
45Makes use of symmetry (but not completely, not every stores symbol is unique).
46*/
47class SixJTable {
48private:
49 std::unordered_map<uint64_t, double> m_data{};
50 int m_max_2j_k{-1};
51 static auto s(int i) { return static_cast<uint8_t>(i); };
52
53public:
54 //! Default contructor: creates empty table
55 SixJTable() = default;
56
57 //! On construction, calculates + stores all possible 6j symbols up to
58 //! maximum value of 2j (or k)
59 /*! @details
60 Typically, max_2jk is 2*max_2j, where max_2j is 2* max j of set
61 of orbitals. You may call this function several times; if the new max_2jk is
62 larger, it will extend the table. If it is smaller, does nothing.
63 */
64 SixJTable(int max_2j_k) { fill(max_2j_k); }
65
66 //! Returns maximum element (k or 2j) of tables [max(k) = 2*max(j) = max(2j)]
67 int max_2jk() const { return m_max_2j_k; }
68
69 //! Returns number of non-zero symbols stored
70 std::size_t size() const { return m_data.size(); }
71
72 //----------------------------------------------------------------------------
73 //! Return 6j symbol {a/2,b/2,c/2,d/2,e/2,f/2}. Note: takes in 2*j/2*k as int
74 //! @details Note: If requesting a 6J symbol beyond what is stored, will
75 //! return 0 (without warning)
76 inline double get_2(int a, int b, int c, int d, int e, int f) const {
77 if (Angular::sixj_zeroQ(a, b, c, d, e, f))
78 return 0.0;
79 const auto it = m_data.find(normal_order(a, b, c, d, e, f));
80 return (it == m_data.cend()) ? 0.0 : it->second;
81 }
82
83 //! Return "magic" 6j. Pass in either integer (for k), or DiracSpinor, F.
84 //! e.g.: (F,F,k,F,F,k) -> {j,j,k,j,j,k}. Do NOT *2!
85 //! @details Note: If requesting a 6J symbol beyond what is stored, will
86 //! return 0 (without warning)
87 template <class A, class B, class C, class D, class E, class F>
88 double get(const A &a, const B &b, const C &c, const D &d, const E &e,
89 const F &f) const {
90 return get_2(twojk(a), twojk(b), twojk(c), twojk(d), twojk(e), twojk(f));
91 }
92
93 //----------------------------------------------------------------------------
94 //! Checks if given 6j symbol is in table (note: may not be in table because
95 //! it's zero)
96 bool contains(int a, int b, int c, int d, int e, int f) const {
97 const auto it = m_data.find(normal_order(a, b, c, d, e, f));
98 return (it != m_data.cend());
99 }
100
101 //----------------------------------------------------------------------------
102 //! Fill the table. max_2jk is maximum (2*j) or k that appears in the symbols
103 void fill(int max_2j_k) {
104
105 if (max_2j_k <= m_max_2j_k)
106 return;
107
108 // Calculate all new *unique* 6J symbols:
109 // Take advantage of symmetries, to only calc those that are needed.
110 // We define a = min(a,b,c,d,e,f), b=min(b,d,e,f) => unique 6j symbol
111 // in "normal" order
112 const auto max_2k = 2 * max_2j_k;
113 for (int a = 0; a <= max_2k; ++a) {
114 auto a0 = a; // std::max(min_2jk, a);
115 for (int b = a0; b <= max_2k; ++b) {
116 for (int c = b; c <= max_2k; ++c) {
117 for (int d = a0; d <= max_2k; ++d) { // note: different!
118 for (int e = b; e <= max_2k; ++e) {
119 for (int f = b; f <= max_2k; ++f) {
120 if (Angular::sixj_zeroQ(a, b, c, d, e, f))
121 continue;
122 if (contains(a, b, c, d, e, f))
123 continue;
124 const auto sj = Angular::sixj_2(a, b, c, d, e, f);
125 if (std::abs(sj) > 1.0e-16) {
126 m_data[normal_order(a, b, c, d, e, f)] = sj;
127 }
128 }
129 }
130 }
131 }
132 }
133 }
134
135 // update max 2k
136 m_max_2j_k = max_2j_k;
137 }
138
139private:
140 //----------------------------------------------------------------------------
141 inline static auto make_key(uint8_t a, uint8_t b, uint8_t c, uint8_t d,
142 uint8_t e, uint8_t f) {
143 static_assert(sizeof(uint64_t) >= 6 * sizeof(uint8_t));
144 uint64_t key = 0;
145 const auto pk = reinterpret_cast<uint8_t *>(&key);
146 std::memcpy(pk, &a, sizeof(uint8_t));
147 std::memcpy(pk + 1, &b, sizeof(uint8_t));
148 std::memcpy(pk + 2, &c, sizeof(uint8_t));
149 std::memcpy(pk + 3, &d, sizeof(uint8_t));
150 std::memcpy(pk + 4, &e, sizeof(uint8_t));
151 std::memcpy(pk + 5, &f, sizeof(uint8_t));
152 return key;
153 }
154 inline static auto make_key(int a, int b, int c, int d, int e, int f) {
155 return make_key(s(a), s(b), s(c), s(d), s(e), s(f));
156 }
157
158 //----------------------------------------------------------------------------
159 inline static auto normal_order_level2(int a, int b, int c, int d, int e,
160 int f) {
161 // note: 'a' must be minimum!
162 // assert(a == std::min({a, b, c, d, e, f})); // remove
163 // {a,b,c|d,e,f} = {a,c,b|d,f,e} = {a,e,f|d,b,c} = {a,f,e|d,c,b}
164 const auto min_bcef = std::min({b, c, e, f});
165
166 if (min_bcef == b) {
167 return make_key(s(a), s(b), s(c), s(d), s(e), s(f));
168 } else if (min_bcef == c) {
169 return make_key(s(a), s(c), s(b), s(d), s(f), s(e));
170 } else if (min_bcef == e) {
171 return make_key(s(a), s(e), s(f), s(d), s(b), s(c));
172 } else if (min_bcef == f) {
173 return make_key(s(a), s(f), s(e), s(d), s(c), s(b));
174 }
175 assert(false && "Fatal error 170: unreachable");
176 }
177
178 //----------------------------------------------------------------------------
179 static uint64_t normal_order(int a, int b, int c, int d, int e, int f) {
180 // returns unique "normal ordering" of {a,b,c,d,e,f}->{i,j,k,l,m,n}
181
182 // auto t11 = make_key(a, b, c, d, e, f);
183 // auto t21 = make_key(a, c, b, d, f, e);
184 // auto t31 = make_key(b, a, c, e, d, f);
185 // auto t41 = make_key(b, c, a, e, f, d);
186 // auto t51 = make_key(c, b, a, f, e, d);
187 // auto t61 = make_key(c, a, b, f, d, e);
188 // auto t12 = make_key(a, e, f, d, b, c);
189 // auto t22 = make_key(a, f, e, d, c, b);
190 // auto t32 = make_key(b, d, f, e, a, c);
191 // auto t42 = make_key(b, f, d, e, c, a);
192 // auto t52 = make_key(c, e, d, f, b, a);
193 // auto t62 = make_key(c, d, e, f, a, b);
194 // auto t13 = make_key(d, b, f, a, e, c);
195 // auto t23 = make_key(d, c, e, a, f, b);
196 // auto t33 = make_key(e, a, f, b, d, c);
197 // auto t43 = make_key(e, c, d, b, f, a);
198 // auto t53 = make_key(f, a, e, c, d, b);
199 // auto t63 = make_key(f, b, d, c, e, a);
200 // auto t14 = make_key(d, e, c, a, b, f);
201 // auto t24 = make_key(d, f, b, a, c, e);
202 // auto t34 = make_key(e, d, c, b, a, f);
203 // auto t44 = make_key(e, f, a, b, c, d);
204 // auto t54 = make_key(f, e, a, c, b, d);
205 // auto t64 = make_key(f, d, b, c, a, e);
206 //
207 // return std::min({t11, t21, t31, t41, t51, t61, t12, t22,
208 // t32, t42, t52, t62, t13, t23, t33, t43,
209 // t53, t63, t14, t24, t34, t44, t54, t64});
210 // returns unique "normal ordering" of {a,b,c,d,e,f}->{i,j,k,l,m,n}
211 // where i = min{a,b,c,d,e,f}, j = min{b,c,e,f}
212
213 // nb: This is not quite correct, and leads to storing more 6J's than
214 // required. However, so long as we actually calculate all of them, it turns
215 // out this is faster.
216
217 const auto min = std::min({a, b, c, d, e, f});
218 // {a,b,c|d,e,f} = {b,a,c|e,d,f} = {c,a,b|f,d,e}
219 // = {d,e,c|a,b,f} = {e,d,c|b,a,f} = {f,a,e|c,d,b}
220 // at next level, use also:
221 // {a,b,c|d,e,f} = {a,c,b|d,f,e} = {a,e,f|d,b,c} = {a,f,e|d,c,b}
222 if (min == a) {
223 return normal_order_level2(a, b, c, d, e, f);
224 } else if (min == b) {
225 return normal_order_level2(b, a, c, e, d, f);
226 } else if (min == c) {
227 return normal_order_level2(c, a, b, f, d, e);
228 } else if (min == d) {
229 return normal_order_level2(d, e, c, a, b, f);
230 } else if (min == e) {
231 return normal_order_level2(e, d, c, b, a, f);
232 } else if (min == f) {
233 return normal_order_level2(f, a, e, c, d, b);
234 }
235 assert(false && "Fatal error 193: unreachable");
236 }
237};
238
239} // namespace Angular
Lookup table for Wigner 6J symbols.
Definition SixJTable.hpp:47
int max_2jk() const
Returns maximum element (k or 2j) of tables [max(k) = 2*max(j) = max(2j)].
Definition SixJTable.hpp:67
bool contains(int a, int b, int c, int d, int e, int f) const
Checks if given 6j symbol is in table (note: may not be in table because it's zero)
Definition SixJTable.hpp:96
void fill(int max_2j_k)
Fill the table. max_2jk is maximum (2*j) or k that appears in the symbols.
Definition SixJTable.hpp:103
SixJTable()=default
Default contructor: creates empty table.
std::size_t size() const
Returns number of non-zero symbols stored.
Definition SixJTable.hpp:70
double get_2(int a, int b, int c, int d, int e, int f) const
Return 6j symbol {a/2,b/2,c/2,d/2,e/2,f/2}. Note: takes in 2*j/2*k as int.
Definition SixJTable.hpp:76
double get(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f) const
Return "magic" 6j. Pass in either integer (for k), or DiracSpinor, F. e.g.: (F,F,k,...
Definition SixJTable.hpp:88
SixJTable(int max_2j_k)
On construction, calculates + stores all possible 6j symbols up to maximum value of 2j (or k)
Definition SixJTable.hpp:64
Angular provides functions and classes for calculating and storing angular factors (3,...
Definition CkTable.cpp:7
double sixj_2(int two_j1, int two_j2, int two_j3, int two_j4, int two_j5, int two_j6)
6j symbol {j1 j2 j3 \ j4 j5 j6} - [takes 2*j as int]
Definition Wigner369j.hpp:341
double SixJ(const A &a, const B &b, const C &c, const D &d, const E &e, const F &f)
"Magic" 6j, for integers and DiracSpinors. Pass int (for k), or DiracSpinor for j....
Definition SixJTable.hpp:35
bool sixj_zeroQ(int a, int b, int c, int d, int e, int f)
Checks triangle conditions for 6j symbols (for 2*j)
Definition Wigner369j.hpp:295
double f(RaB r, PrincipalQN n, DiracQN k, Zeff z, AlphaFS a)
Upper radial component.
Definition DiracHydrogen.cpp:71
constexpr double c
speed of light in a.u. (=1/alpha)
Definition PhysConst_constants.hpp:63
T min(T first, Args... rest)
Returns minimum of any number of parameters (variadic function)
Definition Maths.hpp:34