Skip to content

Commit d7ef6b5

Browse files
committed
Compute a full array of Clebsch-Gordan coefficients in parallel
1 parent 6b97f8b commit d7ef6b5

File tree

7 files changed

+99
-1
lines changed

7 files changed

+99
-1
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ lazy_static = "1"
1919
smallvec = "1"
2020
parking_lot = "0.12"
2121
lru = "0.7"
22+
rayon = "1"
2223

2324
[dev-dependencies]
2425
approx = "0.5"

MANIFEST.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
recursive-include src *
22
include Cargo.*
33
include pyproject.toml
4+
recursive-include benchmarks *
45

56
recursive-exclude .github *
6-
recursive-exclude benchmarks *
77

88
recursive-exclude python/tests *
99
recursive-exclude python/wigners.egg-info *

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ legacy_tox_ini = """
1212
[testenv]
1313
deps =
1414
discover
15+
numpy
1516
1617
commands =
1718
discover -p "*.py" -s python/tests

python/tests/wigner.py

+16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import math
22
import unittest
33

4+
import numpy as np
45
import wigners
56

67

@@ -29,3 +30,18 @@ def test_clebsch_gordan(self):
2930
wigners.clebsch_gordan(j1=1, m1=1, j2=1, m2=0, j3=2, m3=1),
3031
1 / math.sqrt(2.0),
3132
)
33+
34+
def test_clebsch_gordan_array(self):
35+
j1 = 12
36+
j2 = 15
37+
j3 = 8
38+
39+
expected = np.zeros((2 * j1 + 1, 2 * j2 + 1, 2 * j3 + 1), dtype=np.float64)
40+
for m1 in range(-j1, j1 + 1):
41+
for m2 in range(-j2, j2 + 1):
42+
for m3 in range(-j3, j3 + 1):
43+
expected[j1 + m1, j2 + m2, j3 + m3] = wigners.clebsch_gordan(
44+
j1, m1, j2, m2, j3, m3
45+
)
46+
47+
self.assertTrue(np.all(wigners.clebsch_gordan_array(j1, j2, j3) == expected))

python/wigners/__init__.py

+40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import ctypes
3+
import numpy as np
34

45
__lib = ctypes.CDLL(os.path.join(os.path.dirname(__file__), "_wigners.so"))
56

@@ -24,17 +25,56 @@
2425
__lib.clebsch_gordan.restype = ctypes.c_double
2526

2627

28+
__lib.clebsch_gordan_array_c.argtypes = [
29+
ctypes.c_uint32,
30+
ctypes.c_uint32,
31+
ctypes.c_uint32,
32+
ctypes.POINTER(ctypes.c_double),
33+
]
34+
__lib.clebsch_gordan_array_c.restype = None
35+
36+
2737
__lib.clear_wigner_3j_cache.argtypes = []
2838
__lib.clear_wigner_3j_cache.restype = None
2939

3040

3141
def wigner_3j(j1: int, j2: int, j3: int, m1: int, m2: int, m3: int) -> float:
42+
"""
43+
Compute a single Wigner 3j coefficient, corresponding to:
44+
45+
.. code-block::
46+
47+
| j1 j2 j3 |
48+
| m1 m2 m3 |
49+
"""
3250
return __lib.wigner_3j(j1, j2, j3, m1, m2, m3)
3351

3452

3553
def clebsch_gordan(j1: int, m1: int, j2: int, m2: int, j3: int, m3: int) -> float:
54+
"""
55+
Compute a single Clebsch-Gordan coefficient, corresponding to:
56+
57+
.. code-block::
58+
59+
<j1 m1 j2 m2 | j3 m3>
60+
"""
3661
return __lib.clebsch_gordan(j1, m1, j2, m2, j3, m3)
3762

3863

64+
def clebsch_gordan_array(j1: int, j2: int, j3: int) -> np.ndarray:
65+
"""
66+
Compute a full array of Clebsch-Gordan coefficient for the three given
67+
``j``.
68+
69+
The result is a 3-dimensional array with shape ``(2 * j1 + 1, 2 * j2 + 1, 2
70+
* j3 + 1)``.
71+
"""
72+
array = np.zeros((2 * j1 + 1, 2 * j2 + 1, 2 * j3 + 1), dtype=np.float64)
73+
ptr = array.ctypes.data_as(ctypes.POINTER(ctypes.c_double))
74+
__lib.clebsch_gordan_array_c(j1, j2, j3, ptr, array.size)
75+
return array
76+
77+
3978
def clear_wigner_3j_cache():
79+
"""Clear the LRU cache of Wigner 3j symbols"""
4080
return __lib.clear_wigner_3j_cache()

setup.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ zip_safe = False
2727
packages = find:
2828
package_dir =
2929
= python
30+
install_requires =
31+
numpy
32+
3033

3134
[options.packages.find]
3235
where = python

src/wigner_3j.rs

+37
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use parking_lot::Mutex;
22

33
use lru::LruCache;
4+
use rayon::prelude::*;
45

56
use crate::primes::{factorial, PrimeFactorization};
67
use crate::rational::Rational;
@@ -117,6 +118,42 @@ pub extern fn clebsch_gordan(j1: u32, m1: i32, j2: u32, m2: i32, j3: u32, m3: i3
117118
}
118119
}
119120

121+
122+
/// Compute the full array of Clebsch-Gordan coefficients for the three given
123+
/// `j`.
124+
///
125+
/// Data will be written to `output`, which can be interpreted as a row-major
126+
/// 3-dimensional array with shape `(2 * j1 + 1, 2 * j2 + 1, 2 * j3 + 1)`.
127+
pub fn clebsch_gordan_array(j1: u32, j2: u32, j3: u32, output: &mut [f64]) {
128+
let j1_size = 2 * j1 + 1;
129+
let j2_size = 2 * j2 + 1;
130+
let j3_size = 2 * j3 + 1;
131+
132+
let size = (j1_size * j2_size * j3_size) as usize;
133+
if output.len() != size {
134+
panic!(
135+
"invalid output size, expected to have space for {} entries, but got {}",
136+
size, output.len()
137+
);
138+
}
139+
140+
output.par_iter_mut().enumerate().for_each(|(i, o)| {
141+
let i = i as u32;
142+
let m1 = ((i / j3_size) / j2_size) as i32 - j1 as i32;
143+
let m2 = ((i / j3_size) % j2_size) as i32 - j2 as i32;
144+
let m3 = (i % j3_size) as i32 - j3 as i32;
145+
146+
*o = clebsch_gordan(j1, m1, j2, m2, j3, m3);
147+
})
148+
}
149+
150+
/// Same function as `clebsch_gordan_array`, but can be called directly from C
151+
#[no_mangle]
152+
pub unsafe extern fn clebsch_gordan_array_c(j1: u32, j2: u32, j3: u32, data: *mut f64, len: u64) {
153+
let slice = std::slice::from_raw_parts_mut(data, len as usize);
154+
clebsch_gordan_array(j1, j2, j3, slice);
155+
}
156+
120157
/// check the triangle condition on j1, j2, j3, i.e. `|j1 - j2| <= j3 <= j1 + j2`
121158
fn triangle_condition(j1: u32, j2: u32, j3: u32) -> bool {
122159
return (j3 <= j1 + j2) && (j1 <= j2 + j3) && (j2 <= j3 + j1);

0 commit comments

Comments
 (0)