ampsci
c++ program for high-precision atomic structure calculations of single-valence 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 
15 namespace Angular {
16 
17 //==============================================================================
18 
20 
24 class SixJTable {
25 private:
26  std::unordered_map<uint64_t, double> m_data{};
27  int m_max_2j_k{-1};
28  static auto s(int i) { return static_cast<uint8_t>(i); };
29 
30 public:
32  SixJTable() = default;
33 
36 
41  SixJTable(int max_2j_k) { fill(max_2j_k); }
42 
44  int max_2jk() const { return m_max_2j_k; }
45 
47  std::size_t size() const { return m_data.size(); }
48 
49  //----------------------------------------------------------------------------
53  inline double get_2(int a, int b, int c, int d, int e, int f) const {
54  if (Angular::sixj_zeroQ(a, b, c, d, e, f))
55  return 0.0;
56  const auto it = m_data.find(normal_order(a, b, c, d, e, f));
57  return (it == m_data.cend()) ? 0.0 : it->second;
58  }
59 
64  template <class A, class B, class C, class D, class E, class F>
65  double get(const A &a, const B &b, const C &c, const D &d, const E &e,
66  const F &f) const {
67  return get_2(twojk(a), twojk(b), twojk(c), twojk(d), twojk(e), twojk(f));
68  }
69 
70  //----------------------------------------------------------------------------
73  bool contains(int a, int b, int c, int d, int e, int f) const {
74  const auto it = m_data.find(normal_order(a, b, c, d, e, f));
75  return (it != m_data.cend());
76  }
77 
78  //----------------------------------------------------------------------------
80  void fill(int max_2j_k) {
81 
82  if (max_2j_k <= m_max_2j_k)
83  return;
84 
85  // Calculate all new *unique* 6J symbols:
86  // Take advantage of symmetries, to only calc those that are needed.
87  // We define a = min(a,b,c,d,e,f), b=min(b,d,e,f) => unique 6j symbol
88  // in "normal" order
89  const auto max_2k = 2 * max_2j_k;
90  for (int a = 0; a <= max_2k; ++a) {
91  auto a0 = a; // std::max(min_2jk, a);
92  for (int b = a0; b <= max_2k; ++b) {
93  for (int c = b; c <= max_2k; ++c) {
94  for (int d = a0; d <= max_2k; ++d) { // note: different!
95  for (int e = b; e <= max_2k; ++e) {
96  for (int f = b; f <= max_2k; ++f) {
97  if (Angular::sixj_zeroQ(a, b, c, d, e, f))
98  continue;
99  if (contains(a, b, c, d, e, f))
100  continue;
101  const auto sj = Angular::sixj_2(a, b, c, d, e, f);
102  if (std::abs(sj) > 1.0e-16) {
103  m_data[normal_order(a, b, c, d, e, f)] = sj;
104  }
105  }
106  }
107  }
108  }
109  }
110  }
111 
112  // update max 2k
113  m_max_2j_k = max_2j_k;
114  }
115 
116 private:
117  //----------------------------------------------------------------------------
118  inline static auto make_key(uint8_t a, uint8_t b, uint8_t c, uint8_t d,
119  uint8_t e, uint8_t f) {
120  static_assert(sizeof(uint64_t) >= 6 * sizeof(uint8_t));
121  uint64_t key = 0;
122  const auto pk = reinterpret_cast<uint8_t *>(&key);
123  std::memcpy(pk, &a, sizeof(uint8_t));
124  std::memcpy(pk + 1, &b, sizeof(uint8_t));
125  std::memcpy(pk + 2, &c, sizeof(uint8_t));
126  std::memcpy(pk + 3, &d, sizeof(uint8_t));
127  std::memcpy(pk + 4, &e, sizeof(uint8_t));
128  std::memcpy(pk + 5, &f, sizeof(uint8_t));
129  return key;
130  }
131  inline static auto make_key(int a, int b, int c, int d, int e, int f) {
132  return make_key(s(a), s(b), s(c), s(d), s(e), s(f));
133  }
134 
135  //----------------------------------------------------------------------------
136  inline static auto normal_order_level2(int a, int b, int c, int d, int e,
137  int f) {
138  // note: 'a' must be minimum!
139  // assert(a == std::min({a, b, c, d, e, f})); // remove
140  // {a,b,c|d,e,f} = {a,c,b|d,f,e} = {a,e,f|d,b,c} = {a,f,e|d,c,b}
141  const auto min_bcef = std::min({b, c, e, f});
142 
143  if (min_bcef == b) {
144  return make_key(s(a), s(b), s(c), s(d), s(e), s(f));
145  } else if (min_bcef == c) {
146  return make_key(s(a), s(c), s(b), s(d), s(f), s(e));
147  } else if (min_bcef == e) {
148  return make_key(s(a), s(e), s(f), s(d), s(b), s(c));
149  } else if (min_bcef == f) {
150  return make_key(s(a), s(f), s(e), s(d), s(c), s(b));
151  }
152  assert(false && "Fatal error 170: unreachable");
153  }
154 
155  //----------------------------------------------------------------------------
156  static uint64_t normal_order(int a, int b, int c, int d, int e, int f) {
157  // returns unique "normal ordering" of {a,b,c,d,e,f}->{i,j,k,l,m,n}
158 
159  // auto t11 = make_key(a, b, c, d, e, f);
160  // auto t21 = make_key(a, c, b, d, f, e);
161  // auto t31 = make_key(b, a, c, e, d, f);
162  // auto t41 = make_key(b, c, a, e, f, d);
163  // auto t51 = make_key(c, b, a, f, e, d);
164  // auto t61 = make_key(c, a, b, f, d, e);
165  // auto t12 = make_key(a, e, f, d, b, c);
166  // auto t22 = make_key(a, f, e, d, c, b);
167  // auto t32 = make_key(b, d, f, e, a, c);
168  // auto t42 = make_key(b, f, d, e, c, a);
169  // auto t52 = make_key(c, e, d, f, b, a);
170  // auto t62 = make_key(c, d, e, f, a, b);
171  // auto t13 = make_key(d, b, f, a, e, c);
172  // auto t23 = make_key(d, c, e, a, f, b);
173  // auto t33 = make_key(e, a, f, b, d, c);
174  // auto t43 = make_key(e, c, d, b, f, a);
175  // auto t53 = make_key(f, a, e, c, d, b);
176  // auto t63 = make_key(f, b, d, c, e, a);
177  // auto t14 = make_key(d, e, c, a, b, f);
178  // auto t24 = make_key(d, f, b, a, c, e);
179  // auto t34 = make_key(e, d, c, b, a, f);
180  // auto t44 = make_key(e, f, a, b, c, d);
181  // auto t54 = make_key(f, e, a, c, b, d);
182  // auto t64 = make_key(f, d, b, c, a, e);
183  //
184  // return std::min({t11, t21, t31, t41, t51, t61, t12, t22,
185  // t32, t42, t52, t62, t13, t23, t33, t43,
186  // t53, t63, t14, t24, t34, t44, t54, t64});
187  // returns unique "normal ordering" of {a,b,c,d,e,f}->{i,j,k,l,m,n}
188  // where i = min{a,b,c,d,e,f}, j = min{b,c,e,f}
189 
190  // nb: This is not quite correct, and leads to storing more 6J's than
191  // required. However, so long as we actually calculate all of them, it turns
192  // out this is faster.
193 
194  const auto min = std::min({a, b, c, d, e, f});
195  // {a,b,c|d,e,f} = {b,a,c|e,d,f} = {c,a,b|f,d,e}
196  // = {d,e,c|a,b,f} = {e,d,c|b,a,f} = {f,a,e|c,d,b}
197  // at next level, use also:
198  // {a,b,c|d,e,f} = {a,c,b|d,f,e} = {a,e,f|d,b,c} = {a,f,e|d,c,b}
199  if (min == a) {
200  return normal_order_level2(a, b, c, d, e, f);
201  } else if (min == b) {
202  return normal_order_level2(b, a, c, e, d, f);
203  } else if (min == c) {
204  return normal_order_level2(c, a, b, f, d, e);
205  } else if (min == d) {
206  return normal_order_level2(d, e, c, a, b, f);
207  } else if (min == e) {
208  return normal_order_level2(e, d, c, b, a, f);
209  } else if (min == f) {
210  return normal_order_level2(f, a, e, c, d, b);
211  }
212  assert(false && "Fatal error 193: unreachable");
213  }
214 
215  //----------------------------------------------------------------------------
216  // If given an integer (k), returns 2*k
217  // If given a DiracSpinor, F, returns 2*j [F.twoj()]
218  template <class A>
219  static int twojk(const A &a) {
220  if constexpr (std::is_same_v<A, DiracSpinor>) {
221  return a.twoj();
222  } else {
223  static_assert(std::is_same_v<A, int>);
224  return 2 * a;
225  }
226  }
227 };
228 
229 } // namespace Angular
Lookup table for Wigner 6J symbols.
Definition: SixJTable.hpp:24
int max_2jk() const
Returns maximum element (k or 2j) of tables [max(k) = 2*max(j) = max(2j)].
Definition: SixJTable.hpp:44
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:73
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:80
SixJTable()=default
Default contructor: creates empty table.
std::size_t size() const
Returns number of non-zero symbols stored.
Definition: SixJTable.hpp:47
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:53
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:65
SixJTable(int max_2j_k)
On construction, calculates + stores all possible 6j symbols up to maximum value of 2j (or k)
Definition: SixJTable.hpp:41
Angular provides functions and classes for calculating and storing angular factors (3,...
Definition: Angular.hpp:37
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:236
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:190
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:17
T min(T first, Args... rest)
Returns minimum of any number of parameters (variadic function)
Definition: Maths.hpp:34