wignernj 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wignernj-0.4.1/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026 Susi Lehtola
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its contributors
16
+ may be used to endorse or promote products derived from this software
17
+ without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: wignernj
3
+ Version: 0.4.1
4
+ Summary: Exact Wigner 3j/6j/9j symbols and related coefficients via prime factorization
5
+ License-Expression: BSD-3-Clause
6
+ Classifier: Programming Language :: C
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3 :: Only
9
+ Classifier: Topic :: Scientific/Engineering :: Physics
10
+ Requires-Python: >=3.7
11
+ License-File: LICENSE
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest; extra == "test"
14
+ Requires-Dist: sympy; extra == "test"
15
+ Provides-Extra: numpy
16
+ Requires-Dist: numpy; extra == "numpy"
17
+ Dynamic: license-file
18
+ Dynamic: requires-python
@@ -0,0 +1,286 @@
1
+ # libwignernj
2
+
3
+ Exact evaluation of Wigner 3j, 6j, and 9j symbols, Clebsch-Gordan coefficients,
4
+ Racah W-coefficients, Fano X-coefficients, and Gaunt coefficients in C99.
5
+
6
+ All intermediate arithmetic is exact integer arithmetic using a prime-factorization
7
+ representation; floating-point conversion happens only at the final step. Results
8
+ are accurate to the last bit of the chosen output precision.
9
+
10
+ Algorithm: prime factorization of factorials (Dodds & Wiechers,
11
+ Comput. Phys. Commun. **4**, 268, 1972;
12
+ doi:[10.1016/0010-4655(72)90019-7](https://doi.org/10.1016/0010-4655(72)90019-7);
13
+ refined in subsequent work) combined with the multiword-integer
14
+ Racah sum of Johansson & Forssén, SIAM J. Sci. Comput. **38**(1),
15
+ A376–A384, 2016
16
+ (doi:[10.1137/15M1021908](https://doi.org/10.1137/15M1021908)).
17
+
18
+ Language interfaces: C (primary), C++ (header-only wrapper, links against `libwignernj`), Python (CPython extension),
19
+ Fortran 90 (iso_c_binding).
20
+
21
+ ## Argument convention
22
+
23
+ All angular momentum arguments are passed as **twice their value** so that
24
+ half-integers are represented exactly as odd integers:
25
+
26
+ ```
27
+ j = 3/2 → tj = 3
28
+ m = -1/2 → tm = -1
29
+ ```
30
+
31
+ This applies to the C, C++, and Fortran interfaces. The Python interface also
32
+ accepts plain floats (e.g. `0.5`) and `fractions.Fraction` objects.
33
+
34
+ ## Building
35
+
36
+ ```sh
37
+ cmake -B build && cmake --build build
38
+ ctest --test-dir build
39
+ ```
40
+
41
+ CMake options:
42
+
43
+ | Option | Default | Description |
44
+ |---|---|---|
45
+ | `BUILD_SHARED_LIBS` | `ON` | Shared library |
46
+ | `BUILD_FORTRAN` | `ON` | Fortran interface |
47
+ | `BUILD_TESTS` | `ON` | C/Fortran test suite |
48
+ | `BUILD_CXX_TESTS` | `ON` | C++ header tests |
49
+ | `BUILD_EXAMPLES` | `ON` | Build and run the language-binding example programs as ctest tests |
50
+ | `BUILD_LTO` | `ON` | Link-time optimisation; auto-disabled if the toolchain does not support it (and on MSVC, where it conflicts with `WINDOWS_EXPORT_ALL_SYMBOLS`) |
51
+ | `BUILD_PYTHON` | `OFF` | Python extension (dynamically linked against `libwignernj`) |
52
+ | `BUILD_QUADMATH` | `OFF` | libquadmath / IEEE 754 binary128 (`__float128`) interface |
53
+ | `BUILD_MPFR` | `OFF` | MPFR arbitrary-precision interface |
54
+ | `BUILD_FLINT` | `OFF` | Use FLINT/GMP/MPFR for the bigint backend (instead of the in-tree schoolbook with Karatsuba) — sub-quadratic multiplication at large *j* via Toom-Cook / Schönhage--Strassen |
55
+ | `BUILD_COVERAGE` | `OFF` | Build with `--coverage -O0` for lcov / Codecov (gcc, clang) |
56
+
57
+ A separate preprocessor switch `-DBIGINT_FORCE_PORTABLE` (passed via
58
+ `CMAKE_C_FLAGS`) forces the multiword-integer back-end onto its pure-C99
59
+ fallback path even on compilers that support `__uint128_t`; this is
60
+ exercised in the CI matrix to verify that the native and fallback code
61
+ paths produce bit-identical output.
62
+
63
+ ## C API
64
+
65
+ ```c
66
+ #include "wignernj.h"
67
+
68
+ /* Wigner 3j: ( j1 j2 j3 ) */
69
+ /* ( m1 m2 m3 ) */
70
+ double wigner3j (int tj1, int tj2, int tj3, int tm1, int tm2, int tm3);
71
+ float wigner3j_f(int tj1, int tj2, int tj3, int tm1, int tm2, int tm3);
72
+ long double wigner3j_l(int tj1, int tj2, int tj3, int tm1, int tm2, int tm3);
73
+
74
+ /* Wigner 6j: { j1 j2 j3 } */
75
+ /* { j4 j5 j6 } */
76
+ double wigner6j (int tj1, int tj2, int tj3, int tj4, int tj5, int tj6);
77
+ float wigner6j_f(int tj1, int tj2, int tj3, int tj4, int tj5, int tj6);
78
+ long double wigner6j_l(int tj1, int tj2, int tj3, int tj4, int tj5, int tj6);
79
+
80
+ /* Wigner 9j (row-major order) */
81
+ double wigner9j (int tj11, int tj12, int tj13,
82
+ int tj21, int tj22, int tj23,
83
+ int tj31, int tj32, int tj33);
84
+
85
+ /* Clebsch-Gordan: <j1 m1; j2 m2 | J M> */
86
+ double clebsch_gordan (int tj1, int tm1, int tj2, int tm2, int tJ, int tM);
87
+
88
+ /* Racah W-coefficient: W(j1 j2 J j3; j12 j23) */
89
+ double racah_w (int tj1, int tj2, int tJ, int tj3, int tj12, int tj23);
90
+
91
+ /* Fano X-coefficient: X(j1 j2 j12; j3 j4 j34; j13 j24 J)
92
+ * = sqrt[(2j12+1)(2j34+1)(2j13+1)(2j24+1)] * {9j} */
93
+ double fano_x (int tj1, int tj2, int tj12,
94
+ int tj3, int tj4, int tj34,
95
+ int tj13, int tj24, int tJ);
96
+
97
+ /* Gaunt coefficient: integral Y_{l1}^{m1} Y_{l2}^{m2} Y_{l3}^{m3} dΩ */
98
+ double gaunt (int tl1, int tm1, int tl2, int tm2, int tl3, int tm3);
99
+
100
+ /* Real-spherical-harmonic Gaunt coefficient (Wikipedia/Condon-Shortley) */
101
+ double gaunt_real(int tl1, int tm1, int tl2, int tm2, int tl3, int tm3);
102
+ ```
103
+
104
+ Each function is available in three precisions: `double` (no suffix), `float`
105
+ (`_f`), and `long double` (`_l`). When the library is built with
106
+ `-DBUILD_QUADMATH=ON`, an additional `_q` variant returning `__float128`
107
+ is exposed in `wignernj_quadmath.h` (see below). Functions return 0 for
108
+ symbols that vanish by selection rules; selection-rule violations are not
109
+ errors.
110
+
111
+ Linking: `pkg-config --libs libwignernj` or `-lwignernj -lm`.
112
+
113
+ ## libquadmath API
114
+
115
+ Build with `-DBUILD_QUADMATH=ON` (requires a compiler with `__float128`
116
+ support: GCC, Clang, or Intel ICC/ICX on Linux/macOS; not Apple Clang or
117
+ MSVC). Include `wignernj_quadmath.h` in addition to `wignernj.h`. Each public
118
+ symbol gains a `_q` variant returning `__float128` (IEEE 754 binary128,
119
+ 113-bit mantissa):
120
+
121
+ ```c
122
+ #include "wignernj.h"
123
+ #include "wignernj_quadmath.h"
124
+
125
+ __float128 v = wigner6j_q(4, 4, 4, 4, 4, 4);
126
+ ```
127
+
128
+ The Fortran module also exposes the corresponding `wigner3j_q`,
129
+ `wigner6j_q`, ..., `gaunt_real_q` `bind(c)` interfaces and the real-valued
130
+ convenience wrappers `w3jq`, `w6jq`, `w9jq`, `wcgq`, `wracahwq`, `wgauntq`,
131
+ `wgaunt_realq` that take `real(real128)` arguments.
132
+
133
+ ## MPFR API
134
+
135
+ Build with `-DBUILD_MPFR=ON` (requires libmpfr). Include `wignernj_mpfr.h` in
136
+ addition to `wignernj.h`. Set the output precision on `rop` via `mpfr_init2`
137
+ before calling; the rounding mode is the last argument and may be any of the
138
+ standard MPFR modes (`MPFR_RNDN`, `MPFR_RNDZ`, `MPFR_RNDD`, `MPFR_RNDU`,
139
+ `MPFR_RNDA`).
140
+
141
+ ```c
142
+ #include "wignernj.h"
143
+ #include "wignernj_mpfr.h"
144
+
145
+ mpfr_t v;
146
+ mpfr_init2(v, 256); /* 256-bit precision */
147
+
148
+ wigner3j_mpfr(v, 4, 4, 0, 2, -2, 0, MPFR_RNDN);
149
+ wigner6j_mpfr(v, 2, 2, 0, 2, 2, 0, MPFR_RNDN);
150
+ wigner9j_mpfr(v, 2, 2, 2, 2, 2, 2, 2, 2, 2, MPFR_RNDN);
151
+ clebsch_gordan_mpfr(v, 2, 2, 2, -2, 4, 0, MPFR_RNDN);
152
+ racah_w_mpfr(v, 2, 2, 4, 2, 4, 4, MPFR_RNDN);
153
+ gaunt_mpfr(v, 4, 2, 4, -2, 4, 0, MPFR_RNDN);
154
+ gaunt_real_mpfr(v, 4, 2, 4, -2, 0, 0, MPFR_RNDN);
155
+
156
+ mpfr_clear(v);
157
+ ```
158
+
159
+ Link with `-lwignernj -lmpfr -lm`.
160
+
161
+ ## C++ API
162
+
163
+ The header-only wrapper `wignernj.hpp` provides a template interface that accepts
164
+ either `2*j` integers or real-valued doubles (half-integers). The wrapper has
165
+ no separate translation unit, but every function forwards to a C symbol in
166
+ `libwignernj`, so you still need to link the C library (`-lwignernj -lm`):
167
+
168
+ ```cpp
169
+ #include "wignernj.hpp"
170
+
171
+ // Integer 2*j form
172
+ double v = wignernj::symbol3j<double>(2, 2, 0, 0, 0, 0);
173
+ float f = wignernj::symbol6j<float> (2, 2, 2, 2, 2, 2);
174
+
175
+ // Real-valued form (throws std::invalid_argument if not a half-integer)
176
+ double v = wignernj::symbol3j(1.0, 1.0, 0.0, 0.0, 0.0, 0.0);
177
+ double c = wignernj::cg(0.5, 0.5, 0.5, -0.5, 1.0, 0.0);
178
+
179
+ // Available functions: symbol3j, symbol6j, symbol9j, cg, racahw, gaunt, gauntreal
180
+ ```
181
+
182
+ Link with `-lwignernj -lm` (and `-lmpfr` if `BUILD_MPFR=ON`).
183
+
184
+ ## Python API
185
+
186
+ ```sh
187
+ pip install wignernj # from PyPI (pre-built wheels for
188
+ # Linux x86_64/aarch64, macOS arm64,
189
+ # and Windows x86_64; sdist fallback
190
+ # for any other target)
191
+ pip install -e . # or build from a checkout
192
+ ```
193
+
194
+ ```python
195
+ import wignernj
196
+
197
+ wignernj.wigner3j(1, 1, 0, 0, 0, 0) # integer 2*j form
198
+ wignernj.wigner3j(0.5, 0.5, 1, 0.5, -0.5, 0) # real half-integer form
199
+ wignernj.wigner6j(1, 1, 2, 1, 1, 2)
200
+ wignernj.wigner9j(1, 1, 2, 1, 1, 2, 2, 2, 4)
201
+ wignernj.clebsch_gordan(1, 1, 1, -1, 2, 0)
202
+ wignernj.racah_w(1, 1, 2, 1, 2, 2)
203
+ wignernj.fano_x(1, 1, 2, 1, 1, 2, 2, 2, 4)
204
+ wignernj.gaunt(2, 1, 2, -1, 2, 0, precision='longdouble')
205
+ wignernj.gaunt_real(2, 1, 2, -1, 0, 0)
206
+ ```
207
+
208
+ The optional `precision=` keyword selects `'float'`, `'double'` (default), or
209
+ `'longdouble'`. Arguments may be integers, floats, or `fractions.Fraction`.
210
+
211
+ ## Fortran API
212
+
213
+ The `wignernj` module provides real-valued wrappers `w3j`, `w6j`, `w9j`, `wcg`,
214
+ `wracahw`, `wfanox`, `wgaunt`, and `wgaunt_real` that accept double-precision
215
+ real arguments:
216
+
217
+ ```fortran
218
+ use wignernj
219
+ real(8) :: v
220
+ v = w3j(1.0d0, 1.0d0, 0.0d0, 0.0d0, 0.0d0, 0.0d0)
221
+ v = w6j(1.0d0, 1.0d0, 2.0d0, 1.0d0, 1.0d0, 2.0d0)
222
+ v = wcg(0.5d0, 0.5d0, 0.5d0, -0.5d0, 1.0d0, 0.0d0)
223
+ v = wfanox(1.0d0, 1.0d0, 2.0d0, 1.0d0, 1.0d0, 2.0d0, 2.0d0, 2.0d0, 4.0d0)
224
+ v = wgaunt(2.0d0, 1.0d0, 2.0d0, -1.0d0, 2.0d0, 0.0d0)
225
+ v = wgaunt_real(2.0d0, 1.0d0, 2.0d0, -1.0d0, 0.0d0, 0.0d0)
226
+ ```
227
+
228
+ Raw `bind(c)` interfaces using `2*j` integers are also available for all
229
+ three precisions (the `long double` variants require gfortran or Cray
230
+ Fortran), plus the `_q` (`real(real128)`) variants when the library is
231
+ built with `BUILD_QUADMATH=ON`.
232
+
233
+ Link with `-lwignernj_f03 -lwignernj -lm`.
234
+
235
+ ## Limits
236
+
237
+ The prime list and its inverse-lookup index are hard-coded into the compiled
238
+ library (≈49 kB of read-only data), which is what lets the library work with
239
+ **no caller-side initialization step**. The default sieve limit was chosen
240
+ on a rule of thumb that ~50 kB is a reasonable upper bound for compile-time
241
+ constant tables; the resulting prime table covers factorials up to 20020!,
242
+ which translates to:
243
+
244
+ - 3j / 6j / CG / Racah W / complex Gaunt / real Gaunt: j1+j2+j3 ≤ 20019 (equal-j: **j ≤ 6673**)
245
+ - 9j / Fano X: equal-j **j ≤ 5004** (k-dependent triangle denominators reach (4j+1)!; Fano X delegates to the 9j pipeline)
246
+
247
+ Exceeding these limits prints a diagnostic to stderr and aborts. The ceiling is
248
+ **not architectural**: it is set by the size of the compile-time prime table
249
+ (`PRIME_SIEVE_LIMIT` in the auto-generated `src/prime_table_macros.h`) and can
250
+ be raised by regenerating the table with `tools/gen_prime_table.py` and
251
+ rebuilding, at the cost of a proportionally larger compiled-in table.
252
+ `MAX_FACTORIAL_ARG` in the same header is derived from the sieve limit, so
253
+ contributors do not need to keep the two values in sync by hand.
254
+
255
+ The 9j is also O(j⁴) in computation time; evaluations with j > a few hundred
256
+ can be slow. See [docs/reference.md](docs/reference.md#limitations) for details.
257
+
258
+ ## Documentation
259
+
260
+ Full API reference with mathematical definitions, selection rules, and
261
+ per-language examples: [docs/reference.md](docs/reference.md).
262
+
263
+ ## Repository layout
264
+
265
+ - `include/`, `src/` — C99 core library and language wrappers
266
+ - `tests/` — C/C++/Fortran unit tests, OOM-injection harness, symmetry
267
+ oracles; Python tests under `tests/python/`
268
+ - `tests/gen_refs.py` — regenerates the sympy-based reference tables
269
+ consumed by `tests/test_3j.c`, `test_6j.c`, etc.
270
+ - `tests/cmake_downstream/` — minimal out-of-tree project demonstrating
271
+ consumption via `find_package(wignernj REQUIRED COMPONENTS Fortran)`
272
+ - `benchmarks/` — library-versioning microbenches and profile drivers
273
+ (`bench_term_cache.c`, `bench_sweep.c`, `bench_mul.c`, `bench_div128.c`,
274
+ `profile_3j_4000.c`, …) used to validate libwignernj changes against
275
+ prior versions; see `benchmarks/README.md`. Comparative benchmarks
276
+ against external libraries (WIGXJPF, GSL) are published with the
277
+ paper as supplementary material rather than living in this repo.
278
+ - `examples/` — single-file demonstrations of every public symbol in
279
+ C, C++, Fortran, and Python; built and run as ctest tests when
280
+ `BUILD_EXAMPLES=ON` (the default).
281
+ - `tools/` — prime-table and source-list generators run at build time
282
+ - `docs/` — extended reference and the descriptor paper
283
+
284
+ ## License
285
+
286
+ BSD 3-Clause — see [LICENSE](LICENSE).
@@ -0,0 +1,26 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2026 Susi Lehtola
3
+ [build-system]
4
+ requires = ["setuptools>=77", "wheel"]
5
+ build-backend = "setuptools.build_meta"
6
+
7
+ [project]
8
+ name = "wignernj"
9
+ version = "0.4.1"
10
+ description = "Exact Wigner 3j/6j/9j symbols and related coefficients via prime factorization"
11
+ requires-python = ">=3.7"
12
+ license = "BSD-3-Clause"
13
+ license-files = ["LICENSE"]
14
+ classifiers = [
15
+ "Programming Language :: C",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3 :: Only",
18
+ "Topic :: Scientific/Engineering :: Physics",
19
+ ]
20
+
21
+ [project.optional-dependencies]
22
+ test = ["pytest", "sympy"]
23
+ numpy = ["numpy"]
24
+
25
+ [tool.pytest.ini_options]
26
+ testpaths = ["tests/python"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,50 @@
1
+ # SPDX-License-Identifier: BSD-3-Clause
2
+ # Copyright (c) 2026 Susi Lehtola
3
+ import sys
4
+
5
+ from setuptools import setup, Extension
6
+
7
+ sources = [
8
+ "src/python/wignernjmodule.c",
9
+ "src/xalloc.c",
10
+ "src/primes.c",
11
+ "src/bigint.c",
12
+ "src/pfrac.c",
13
+ "src/scratch.c",
14
+ "src/wigner_exact.c",
15
+ "src/wigner3j.c",
16
+ "src/wigner6j.c",
17
+ "src/wigner9j.c",
18
+ "src/clebsch.c",
19
+ "src/racah.c",
20
+ "src/fano_x.c",
21
+ "src/gaunt.c",
22
+ ]
23
+
24
+ # MSVC has no -std=c99 / -O2 spelling and bundles libm into the
25
+ # default C runtime; the GCC/Clang flags would either be passed
26
+ # silently as cl warnings or, in the libm case, produce a hard
27
+ # linker error (LNK1181: cannot open input file 'm.lib').
28
+ if sys.platform == "win32":
29
+ extra_compile_args = ["/O2"]
30
+ libraries = []
31
+ else:
32
+ extra_compile_args = ["-O2", "-std=c99"]
33
+ libraries = ["m"]
34
+
35
+ ext = Extension(
36
+ name="wignernj._wignernj",
37
+ sources=sources,
38
+ include_dirs=["src", "include"],
39
+ extra_compile_args=extra_compile_args,
40
+ libraries=libraries,
41
+ )
42
+
43
+ setup(
44
+ name="wignernj",
45
+ version="0.4.1",
46
+ description="Exact Wigner 3j/6j/9j symbols and related coefficients via prime factorization",
47
+ packages=["wignernj"],
48
+ ext_modules=[ext],
49
+ python_requires=">=3.7",
50
+ )