wigner_symbols/
internal.rs

1//! Contents of this module are subject to change.
2
3use std::cmp::Ordering;
4use std::ops::Range;
5use rug::{Integer, Rational};
6use super::{SignedSqrt, Wigner3jm, Wigner6j, Wigner9j, Wigner12jSecond};
7
8#[inline]
9pub fn sort2<T: Ord>(a: T, b: T) -> (T, T) {
10    if b < a {
11        (b, a)
12    } else {
13        (a, b)
14    }
15}
16
17#[inline]
18pub fn sort3<T: Ord>(a: T, b: T, c: T) -> (T, T, T) {
19    let (a, b) = sort2(a, b);
20    if c < a {
21        (c, a, b)
22    } else if c < b {
23        (a, c, b)
24    } else {
25        (a, b, c)
26    }
27}
28
29#[inline]
30pub fn sort4<T: Ord>(a: T, b: T, c: T, d: T) -> (T, T, T, T) {
31    let (a, b) = sort2(a, b);
32    let (c, d) = sort2(c, d);
33    if c < a {
34        if d < a {
35            (c, d, a, b)
36        } else if d < b {
37            (c, a, d, b)
38        } else {
39            (c, a, b, d)
40        }
41    } else if c < b {
42        if d < b {
43            (a, c, d, b)
44        } else {
45            (a, c, b, d)
46        }
47    } else {
48        (a, b, c, d)
49    }
50}
51
52/// Reinterpret ordering as a sign:
53///
54/// ```text
55/// Less => -1
56/// Equal => 0
57/// Greater => +1
58/// ```
59#[inline]
60pub fn ordering_to_i32(ordering: Ordering) -> i32 {
61    match ordering {
62        Ordering::Less => -1,
63        Ordering::Equal => 0,
64        Ordering::Greater => 1,
65    }
66}
67
68/// Calculate the binomial coefficient `C(n, k)`.
69#[inline]
70pub fn binomial(n: i32, k: i32) -> Integer {
71    Integer::from(n).binomial(k as u32)
72}
73
74/// Calculate the falling factorial, i.e. the product of the integers `[n, n - k)`.
75#[inline]
76pub fn falling_factorial(n: i32, k: i32) -> Integer {
77    let mut r = Integer::from(1);
78    for i in n - k .. n {
79        r *= Integer::from(i + 1);
80    }
81    r
82}
83
84/// Calculate the factorial `n!`.
85#[inline]
86pub fn factorial(n: i32) -> Integer {
87    Integer::factorial(n as u32).into()
88}
89
90#[inline]
91pub fn phase(phi: i32) -> i32 {
92    if phi % 2 == 0 {
93        1
94    } else {
95        -1
96    }
97}
98
99/// Check `|j1 − j2| ≤ j3 ≤ j1 + j2` and `j1 + j2 + j3 ∈ ℤ`.
100#[inline]
101pub fn triangle_condition(tj1: i32, tj2: i32, tj3: i32) -> bool {
102    let d = tj1 + tj2 - tj3;
103    d >= 0 && d % 2 == 0 && tj3 - (tj1 - tj2).abs() >= 0
104}
105
106/// Calculate the Wigner 3-jm symbol times `(−1) ^ (j1 − j2 − m3)`.
107pub fn wigner_3jm_raw_c(this: Wigner3jm) -> SignedSqrt {
108    let Wigner3jm { tj1, tm1, tj2, tm2, tj3, tm3 } = this;
109    let jmr1 = (tj1 + tm1) % 2;
110    let jmr2 = (tj2 + tm2) % 2;
111    if
112        tm1 + tm2 + tm3 == 0 &&
113        tm1.abs() <= tj1 &&
114        tm2.abs() <= tj2 &&
115        tm3.abs() <= tj3 &&
116        jmr1 == 0 &&
117        jmr2 == 0 &&
118        triangle_condition(tj1, tj2, tj3)
119    {
120        wigner_3jm_raw(this)
121    } else {
122        Default::default()
123    }
124}
125
126/// Calculate the Wigner 3-jm symbol times `(−1) ^ (j1 − j2 − m3)`.
127/// The selection rules are not checked.
128pub fn wigner_3jm_raw(this: Wigner3jm) -> SignedSqrt {
129    let Wigner3jm { tj1, tm1, tj2, tm2, tj3, tm3 } = this;
130    let jjj1 = (tj1 - tj2 + tj3) / 2;
131    let jjj2 = (tj2 - tj3 + tj1) / 2;
132    let jjj3 = (tj3 - tj1 + tj2) / 2;
133    let jjj  = (tj1 + tj2 + tj3) / 2 + 1;
134    let jm1 = (tj1 + tm1) / 2;
135    let jm2 = (tj2 + tm2) / 2;
136    let jsm1 = (tj1 - tm1) / 2;
137    let jm3  = (tj3 + tm3) / 2;
138    let kmin = sort3(0, tj1 - tj3 + tm2, tj2 - tj3 - tm1).2 / 2;
139    let kmax = sort3(jjj2, jsm1, jm2).0;
140    let z1 = Rational::from((
141        binomial(tj1, jjj1) * binomial(tj2, jjj2) * binomial(tj3, jjj3),
142        binomial(tj1, jm1) * binomial(tj2, jm2) * binomial(tj3, jm3),
143    )) * triangular_factor_raw(jjj, jjj1, jjj2, jjj3);
144    let z2 = if kmin > kmax {
145        Integer::default()
146    } else {
147        let c0 = Integer::from(phase(kmin))
148            * binomial(jjj2, kmin)
149            * binomial(jjj1, jsm1 - kmin)
150            * binomial(jjj3, jm2 - kmin);
151        let mut s = c0.clone();
152        let mut c = c0;
153        for k in kmin + 1 .. kmax + 1 {
154            c = -c
155                * Integer::from(jjj2 - k + 1) / Integer::from(k)
156                * Integer::from(jsm1 - k + 1) / Integer::from(jjj1 - (jsm1 - k))
157                * Integer::from(jm2 - k + 1) / Integer::from(jjj3 - (jm2  - k));
158            s += &c;
159        }
160        s
161    };
162    SignedSqrt::new(z2, z1)
163}
164
165/// Calculate the Wigner 6-j symbol.  The selection rules are not checked.
166pub fn wigner_6j_raw(this: Wigner6j) -> SignedSqrt {
167    let Wigner6j { tj1, tj2, tj3, tj4, tj5, tj6 } = this;
168    let z1 =
169        triangular_factor(tj1, tj5, tj6)
170        * triangular_factor(tj4, tj2, tj6)
171        * triangular_factor(tj4, tj5, tj3)
172        / triangular_factor(tj1, tj2, tj3);
173    let z2 = tetrahedral_sum(tj1, tj5, tj6, tj4, tj2, tj3);
174    SignedSqrt::new(z2, z1)
175}
176
177/// Calculate the Wigner 9-j symbol.  The selection rules are not checked.
178pub fn wigner_9j_raw(this: Wigner9j) -> SignedSqrt {
179    let Wigner9j { tj1, tj2, tj3, tj4, tj5, tj6, tj7, tj8, tj9 } = this;
180    let tkmin = sort3(
181        (tj8 - tj4).abs(),
182        (tj2 - tj6).abs(),
183        (tj1 - tj9).abs(),
184    ).2;
185    let tkmax = sort3(
186        tj8 + tj4,
187        tj2 + tj6,
188        tj1 + tj9,
189    ).0;
190    let z2 = (0 .. (tkmax - tkmin) / 2 + 1).map(|i| {
191        let tk = tkmin + i * 2;
192        Integer::from(phase(tk) * (tk + 1))
193            * tetrahedral_sum(tj1, tj2, tj3, tj6, tj9, tk)
194            * tetrahedral_sum(tj6, tj4, tj5, tj8, tj2, tk)
195            * tetrahedral_sum(tj8, tj9, tj7, tj1, tj4, tk)
196    }).sum();
197    let z1 =
198        triangular_factor(tj1, tj2, tj3) *
199        triangular_factor(tj4, tj5, tj6) *
200        triangular_factor(tj7, tj8, tj9) *
201        triangular_factor(tj1, tj4, tj7) *
202        triangular_factor(tj2, tj5, tj8) *
203        triangular_factor(tj3, tj6, tj9);
204    SignedSqrt::new(z2, z1)
205}
206
207/// Calculate the Wigner 12-j symbol of second type.  The selection rules are not checked.
208pub fn wigner_12j_second_raw(this: Wigner12jSecond) -> SignedSqrt {
209    let Wigner12jSecond { tj1, tj2, tj3, tj4, tj5, tj6, tj7, tj8, tj9, tj10, tj11, tj12 } = this;
210    let tkmin = sort4(
211        (tj1 - tj3).abs(),
212        (tj2 - tj4).abs(),
213        (tj9 - tj10).abs(),
214        (tj11 - tj12).abs(),
215    ).2;
216    let tkmax = sort4(
217        tj1 + tj3,
218        tj2 + tj4,
219        tj9 + tj10,
220        tj11 + tj12,
221    ).0;
222
223    let z2: Integer = (0 .. (tkmax - tkmin) / 2 + 1).map(|i| {
224        let tk = tkmin + i * 2;
225        Integer::from(tk + 1)
226            * tetrahedral_sum(tj9, tj1, tj5, tj3, tj10, tk)
227            * tetrahedral_sum(tj3, tj12, tj6, tj11, tj1, tk)
228            * tetrahedral_sum(tj4, tj10, tj7, tj9, tj2, tk)
229            * tetrahedral_sum(tj11, tj2, tj7, tj4, tj12, tk)
230    }).sum();
231
232    let z1 =
233        triangular_factor(tj9, tj1, tj5)
234        * triangular_factor(tj3, tj10, tj5)
235        * triangular_factor(tj3, tj12, tj6)
236        * triangular_factor(tj11, tj1, tj6)
237        * triangular_factor(tj4, tj10, tj7)
238        * triangular_factor(tj9, tj2, tj7)
239        * triangular_factor(tj11, tj2, tj8)
240        * triangular_factor(tj4, tj12, tj8);
241
242    SignedSqrt::new(Integer::from(phase(tj5 - tj6 - tj7 + tj8)) * z2, z1)
243}
244
245
246/// Calculate the triangular factor:
247///
248/// ```text
249/// Δ(j1, j2, j3) = (−j1 + j2 _ j3)! (j1 − j2 + j3)! (j1 + j2 − j3)!
250///               / (j1 + j2 + j3 + 1)!
251/// ```
252///
253#[inline]
254pub fn triangular_factor(tj1: i32, tj2: i32, tj3: i32) -> Rational {
255    let jjja = (tj3 - tj1 + tj2) / 2;
256    let jjjb = (tj1 - tj2 + tj3) / 2;
257    let jjjc = (tj2 - tj3 + tj1) / 2;
258    let jjj = (tj1 + tj2 + tj3) / 2 + 1;
259    triangular_factor_raw(jjj, jjja, jjjb, jjjc)
260}
261
262/// Calculate `ja! jb! jc! / jd!`.
263#[inline]
264pub fn triangular_factor_raw(jd: i32, ja: i32, jb: i32, jc: i32) -> Rational {
265    let (ju, jv, jw) = sort3(ja, jb, jc);
266    Rational::from((
267        factorial(ju) * factorial(jv),
268        falling_factorial(jd, jd - jw),
269    ))
270}
271
272/// Calculate the symbol in the paper by L. Wei that is enclosed in square
273/// brackets:
274///
275/// ```text
276/// ⎡j11 j12 j13⎤
277/// ⎣j21 j22 j23⎦
278/// ```
279///
280/// This is essentially a Wigner 6-j symbol without the triangular factors,
281/// although the ordering of the arguments is a bit funky here.
282pub fn tetrahedral_sum(
283    tja: i32,
284    tje: i32,
285    tjf: i32,
286    tjd: i32,
287    tjb: i32,
288    tjc: i32,
289) -> Integer
290{
291    let jjja = (tjc - tja + tjb) / 2;
292    let jjjb = (tja - tjb + tjc) / 2;
293    let jjjc = (tjb - tjc + tja) / 2;
294    let jabc = (tja + tjb + tjc) / 2;
295    let jaef = (tja + tje + tjf) / 2;
296    let jdbf = (tjd + tjb + tjf) / 2;
297    let jdec = (tjd + tje + tjc) / 2;
298    let kmin = *[jabc, jdec, jdbf, jaef].iter().max().unwrap();
299    let kmax = *[
300        tja + tjd + tjb + tje,
301        tjb + tje + tjc + tjf,
302        tja + tjd + tjc + tjf,
303    ].iter().max().unwrap() / 2;
304    (kmin .. kmax + 1).map(|k| {
305        Integer::from(phase(k))
306            * binomial(k + 1, k - jabc)
307            * binomial(jjja, k - jaef)
308            * binomial(jjjb, k - jdbf)
309            * binomial(jjjc, k - jdec)
310    }).sum()
311}
312
313#[inline]
314pub fn intersect_ranges(a: Range<i32>, b: Range<i32>) -> Range<i32> {
315    a.start.max(b.start) .. a.end.min(b.end)
316}
317
318pub struct Step<I> {
319    pub iter: I,
320    pub step: usize,
321}
322
323impl<I: Iterator> Iterator for Step<I> {
324    type Item = I::Item;
325    #[inline]
326    fn next(&mut self) -> Option<Self::Item> {
327        let item = self.iter.next();
328        if item.is_some() && self.step >= 2 {
329            self.iter.nth(self.step - 2);
330        }
331        item
332    }
333}
334
335#[inline]
336pub fn get_triangular_tjs(tj_max: i32, tj1: i32, tj2: i32) -> Step<Range<i32>> {
337    Step {
338        iter: (tj1 - tj2).abs() .. tj_max.min(tj1 + tj2) + 1,
339        step: 2,
340    }
341}
342
343#[inline]
344pub fn get_bitriangular_tjs(
345    tj_max: i32,
346    tj1: i32,
347    tj2: i32,
348    tj3: i32,
349    tj4: i32,
350) -> Step<Range<i32>>
351{
352    Step {
353        iter: if (tj1 + tj2 + tj3 + tj4) % 2 != 0 {
354            0 .. 0
355        } else {
356            intersect_ranges(
357                get_triangular_tjs(tj_max, tj1, tj2).iter,
358                get_triangular_tjs(tj_max, tj3, tj4).iter,
359            )
360        },
361        step: 2,
362    }
363}
364
365/// Get all projection quantum numbers that lie within the multiplet of `j`.
366#[inline]
367pub fn get_tms(tj: i32) -> Step<Range<i32>> {
368    Step { iter: -tj .. tj + 1, step: 2 }
369}
370
371/// Get all possible arguments of the Wigner 3-j symbol that satisfy the
372/// selection rules up to a maximum of `j_max`.
373pub fn get_3tjms(
374    tj_max: i32,
375    callback: &mut dyn FnMut(Wigner3jm),
376) {
377    for tj1 in 0 .. tj_max + 1 {
378    for tj2 in 0 .. tj_max + 1 {
379    for tj3 in get_triangular_tjs(tj_max, tj1, tj2) {
380    for tm1 in get_tms(tj1) {
381    for tm2 in get_tms(tj2) {
382        let tm3 = -(tm1 + tm2);
383        if tm3.abs() > tj3 {
384            continue;
385        }
386        callback(Wigner3jm { tj1, tm1, tj2, tm2, tj3, tm3 });
387    }
388    }
389    }
390    }
391    }
392}
393
394/// Get all possible arguments of the Wigner 6-j symbol that satisfy the
395/// selection rules up to a maximum of `j_max`.
396pub fn get_6tjs(
397    tj_max: i32,
398    callback: &mut dyn FnMut(Wigner6j),
399) {
400    for tj1 in 0 .. tj_max + 1 {
401    for tj2 in 0 .. tj_max + 1 {
402    for tj3 in get_triangular_tjs(tj_max, tj1, tj2) {
403    for tj4 in 0 .. tj_max + 1 {
404    for tj5 in get_triangular_tjs(tj_max, tj4, tj3) {
405    for tj6 in get_bitriangular_tjs(tj_max, tj1, tj5, tj4, tj2) {
406        callback(Wigner6j { tj1, tj2, tj3, tj4, tj5, tj6 });
407    }
408    }
409    }
410    }
411    }
412    }
413}
414
415/// Get all possible arguments of the Wigner 9-j symbol that satisfy the
416/// selection rules up to a maximum of `j_max`.
417pub fn get_9tjs(
418    tj_max: i32,
419    callback: &mut dyn FnMut(Wigner9j),
420) {
421    for tj1 in 0 .. tj_max + 1 {
422    for tj2 in 0 .. tj_max + 1 {
423    for tj3 in get_triangular_tjs(tj_max, tj1, tj2) {
424    for tj4 in 0 .. tj_max + 1 {
425    for tj5 in 0 .. tj_max + 1 {
426    for tj6 in get_triangular_tjs(tj_max, tj4, tj5) {
427    for tj7 in get_triangular_tjs(tj_max, tj1, tj4) {
428    for tj8 in get_triangular_tjs(tj_max, tj2, tj5) {
429    for tj9 in get_bitriangular_tjs(tj_max, tj7, tj8, tj3, tj6) {
430        callback(Wigner9j { tj1, tj2, tj3, tj4, tj5, tj6, tj7, tj8, tj9 });
431    }
432    }
433    }
434    }
435    }
436    }
437    }
438    }
439    }
440}
441
442/// Get all possible arguments of the second type of the Wigner 12-j symbol that satisfy the
443/// selection rules up to a maximum of `j_max`.
444pub fn get_12tjs_second(
445    tj_max: i32,
446    callback: &mut dyn FnMut(Wigner12jSecond),
447) {
448    for tj1 in 0 .. tj_max + 1 {
449    for tj2 in 0 .. tj_max + 1 {
450    for tj3 in 0 .. tj_max + 1 {
451    for tj5 in 0 .. tj_max + 1 {
452    for tj6 in 0 .. tj_max + 1 {
453    for tj9 in get_triangular_tjs(tj_max, tj1, tj5) {
454    for tj10 in get_triangular_tjs(tj_max, tj3, tj5) {
455    for tj11 in get_triangular_tjs(tj_max, tj1, tj6) {
456    for tj12 in get_triangular_tjs(tj_max, tj3, tj6) {
457    for tj7 in get_triangular_tjs(tj_max, tj2, tj9) {
458    for tj8 in get_triangular_tjs(tj_max, tj2, tj11) {
459    for tj4 in get_bitriangular_tjs(tj_max, tj7, tj10, tj8, tj12) {
460        callback(Wigner12jSecond { tj1, tj2, tj3, tj4, tj5, tj6, tj7, tj8, tj9, tj10, tj11, tj12 });
461    }
462    }
463    }
464    }
465    }
466    }
467    }
468    }
469    }
470    }
471    }
472    }
473}