add wigner9j symbols. update README, .gitignore Manifest

This commit is contained in:
Thomas (Tom) C. Gorordo 2024-08-15 07:18:17 -07:00
parent 473e618cf0
commit 152d52595e
Signed by: tgorordo
GPG key ID: 0CBED22BB0D94490
6 changed files with 206 additions and 12 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
Manifest.toml
*.jl.cov *.jl.cov
*.jl.*.cov *.jl.*.cov
*.jl.mem *.jl.mem

View file

@ -1,25 +1,26 @@
name = "WignerSymbols" name = "WignerSymbols"
uuid = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b" uuid = "9f57e263-0b3d-5e2e-b1be-24f2bb48858b"
authors = ["Jutho Haegeman"] authors = ["Jutho Haegeman"]
version = "2.0.0" version = "2.1.0"
[deps] [deps]
RationalRoots = "308eb6b3-cc68-5ff3-9e97-c3c4da4fa681" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721" HalfIntegers = "f0d1745a-41c9-11e9-1dd9-e5d34d218721"
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" LRUCache = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637"
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
RationalRoots = "308eb6b3-cc68-5ff3-9e97-c3c4da4fa681"
[compat] [compat]
RationalRoots = "0.1 - 0.9"
HalfIntegers = "1" HalfIntegers = "1"
Primes = "0.4 - 0.9"
LRUCache = "1.3" LRUCache = "1.3"
Primes = "0.4 - 0.9"
RationalRoots = "0.1 - 0.9"
julia = "1.5" julia = "1.5"
[extras] [extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[targets] [targets]
test = ["Test", "LinearAlgebra", "Random"] test = ["Test", "LinearAlgebra", "Random"]

View file

@ -52,6 +52,7 @@ While the following function signatures are probably self-explanatory, you can q
for them in the Julia REPL to get further details. for them in the Julia REPL to get further details.
* `wigner3j(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, m₁, m₂, m₃ = -m₂-m₁) -> ::T` * `wigner3j(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, m₁, m₂, m₃ = -m₂-m₁) -> ::T`
* `wigner6j(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, j₄, j₅, j₆) -> ::T` * `wigner6j(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, j₄, j₅, j₆) -> ::T`
* `wigner9j(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉) -> ::T`
* `clebschgordan(T::Type{<:Real} = RationalRoot{BigInt}, j₁, m₁, j₂, m₂, j₃, m₃ = m₁+m₂) -> ::T` * `clebschgordan(T::Type{<:Real} = RationalRoot{BigInt}, j₁, m₁, j₂, m₂, j₃, m₃ = m₁+m₂) -> ::T`
* `racahV(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, m₁, m₂, m₃ = -m₁-m₂) -> ::T` * `racahV(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, m₁, m₂, m₃ = -m₁-m₂) -> ::T`
* `racahW(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, J, j₃, J₁₂, J₂₃) -> ::T` * `racahW(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, J, j₃, J₁₂, J₂₃) -> ::T`
@ -89,9 +90,8 @@ Also uses ideas from
[2] [J. Rasch and A. C. H. Yu, SIAM Journal on Scientific Compututing 25 (2003), 14161428](https://doi.org/10.1137/S1064827503422932) [2] [J. Rasch and A. C. H. Yu, SIAM Journal on Scientific Compututing 25 (2003), 14161428](https://doi.org/10.1137/S1064827503422932)
for caching the computed 3j and 6j symbols. for caching the computed 3j and 6j symbols. Wigner 9-j symbols implemented based on
## Todo [3] [L. Wei, New formula for 9-j symbols and their direct calculation, Computers in Physics, 12 (1998), 632634.](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.481.5946&rep=rep1&type=pdf)
* Wigner 9-j symbols, as explained in [1] and based on
[3] [L. Wei, New formula for 9-j symbols and their direct calculation, Computers in Physics, 12 (1998), 632634.](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.481.5946&rep=rep1&type=pdf) with binomials additionally optimized using the methods described in [1].

View file

@ -1,11 +1,12 @@
module WignerSymbols module WignerSymbols
export δ, Δ, clebschgordan, wigner3j, wigner6j, racahV, racahW, HalfInteger export δ, Δ, clebschgordan, wigner3j, wigner6j, wigner9j, racahV, racahW, HalfInteger
using HalfIntegers using HalfIntegers
using RationalRoots using RationalRoots
using LRUCache using LRUCache
const RRBig = RationalRoot{BigInt} const RRBig = RationalRoot{BigInt}
import RationalRoots: _convert import RationalRoots: _convert
using Combinatorics: permutations
include("growinglist.jl") include("growinglist.jl")
include("bigint.jl") # additional GMP BigInt functionality not wrapped in Base.GMP.MPZ include("bigint.jl") # additional GMP BigInt functionality not wrapped in Base.GMP.MPZ
@ -14,9 +15,11 @@ convert(BigInt, primefactorial(401)) # trigger compilation and generate some fix
const Key3j = Tuple{UInt,UInt,UInt,Int,Int} const Key3j = Tuple{UInt,UInt,UInt,Int,Int}
const Key6j = NTuple{6,UInt} const Key6j = NTuple{6,UInt}
const Key9j = NTuple{9,HalfInteger}
const Wigner3j = LRU{Key3j,Tuple{Rational{BigInt},Rational{BigInt}}}(; maxsize = 10^6) const Wigner3j = LRU{Key3j,Tuple{Rational{BigInt},Rational{BigInt}}}(; maxsize = 10^6)
const Wigner6j = LRU{Key6j,Tuple{Rational{BigInt},Rational{BigInt}}}(; maxsize = 10^6) const Wigner6j = LRU{Key6j,Tuple{Rational{BigInt},Rational{BigInt}}}(; maxsize = 10^6)
const Wigner9j = LRU{Key9j,Tuple{Rational{BigInt},Rational{BigInt}}}(; maxsize = 10^6)
function set_buffer3j_size!(; maxsize) function set_buffer3j_size!(; maxsize)
resize!(Wigner3j; maxsize = maxsize) resize!(Wigner3j; maxsize = maxsize)
@ -24,6 +27,9 @@ end
function set_buffer6j_size!(; maxsize) function set_buffer6j_size!(; maxsize)
resize!(Wigner6j; maxsize = maxsize) resize!(Wigner6j; maxsize = maxsize)
end end
function set_buffer9j_size!(; maxsize)
resize!(Wigner9j; maxsize = maxsize)
end
# check integerness and correctness of (j,m) angular momentum # check integerness and correctness of (j,m) angular momentum
ϵ(j, m) = (abs(m) <= j && ishalfinteger(j) && isinteger(j-m) && isinteger(j+m)) ϵ(j, m) = (abs(m) <= j && ishalfinteger(j) && isinteger(j-m) && isinteger(j+m))
@ -242,6 +248,74 @@ function racahW(T::Type{<:Real}, j₁, j₂, J, j₃, J₁₂, J₂₃)
end end
end end
"""
wigner9j(T::Type{<:Real} = RationalRoot{BigInt}, j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉) -> ::T
Compute the value of the Wigner-9j symbol
j₁ j₂ j₃
j₄ j₅ j₆
j₇ j₈ j₉
as a type `T` real number. By default `T = RationalRoot{BigInt}`.
Returns `zero(T)` if any of the triangle conditions TODO
are not satisfied, but throws a `DomainError` if
the `jᵢ`s are not integer or halfinteger.
"""
wigner9j(j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉) = wigner9j(RRBig, j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)
function wigner9j(T::Type{<:Real}, j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)
# check validity of `jᵢ`s
for jᵢ in (j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)
(ishalfinteger(jᵢ) && jᵢ >= zero(jᵢ)) || throw(DomainError("invalid jᵢ", jᵢ))
end
return _wigner9j(T, HalfInteger.((j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉))...)
end
const _perms9j = [(i, j) for i in permutations([1, 2, 3]),
j in permutations([1, 2, 3])]
function _wigner9j(T::Type{<:Real}, j₁::HalfInteger, j₂::HalfInteger, j₃::HalfInteger,
j₄::HalfInteger, j₅::HalfInteger, j₆::HalfInteger,
j₇::HalfInteger, j₈::HalfInteger, j₉::HalfInteger)
# check triangle conditions
if !(δ(j₁, j₂, j₃) && δ(j₄, j₅, j₆) && δ(j₇, j₈, j₉) && δ(j₁, j₄, j₇) && δ(j₂, j₅, j₈) && δ(j₃, j₆, j₉))
return zero(T)
end
# dictionary lookup, check all 72 permutations
k = [j₁ j₂ j₃; j₄ j₅ j₆; j₇ j₈ j₉]
for p in _perms9j
kk = tuple(reshape(k[p...], 9))
kkT = tuple(reshape(transpose(k[p...]), 9))
if haskey(Wigner9j, kk)
r, s = Wigner9j[kk]
return _convert(T, s) * convert(T, signedroot(r))
elseif haskey(Wigner9j, kkT)
r, s = Wigner9j[kkT]
return _convert(T, s) * convert(T, signedroot(r))
end
end
# order irrelevant: product remains the same
n₁, d₁ = Δ²(j₁, j₂, j₃)
n₂, d₂ = Δ²(j₄, j₅, j₆)
n₃, d₃ = Δ²(j₇, j₈, j₉)
n₄, d₄ = Δ²(j₁, j₄, j₇)
n₅, d₅ = Δ²(j₂, j₅, j₈)
n₆, d₆ = Δ²(j₃, j₆, j₉)
snum, rnum = splitsquare(n₁ * n₂ * n₃ * n₄ * n₅ * n₆)
sden, rden = splitsquare(d₁ * d₂ * d₃ * d₄ * d₅ * d₆)
snum, sden = divgcd!(snum, sden)
rnum, rden = divgcd!(rnum, rden)
s = Base.unsafe_rational(convert(BigInt, snum), convert(BigInt, sden))
r = Base.unsafe_rational(convert(BigInt, rnum), convert(BigInt, rden))
s *= compute9jseries(j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)
Wigner9j[(j₁, j₂, j₃, j₄, j₅, j₆, j₇, j₈, j₉)] = (r, s)
return _convert(T, s) * convert(T, signedroot(r))
end
# COMPUTATIONAL ROUTINES # COMPUTATIONAL ROUTINES
#------------------------ #------------------------
# squared triangle coefficient # squared triangle coefficient
@ -312,7 +386,6 @@ function compute3jseries(β₁, β₂, β₃, α₁, α₂)
end end
den = commondenominator!(nums, dens) den = commondenominator!(nums, dens)
totalnum = sumlist!(nums) totalnum = sumlist!(nums)
totalden = convert(BigInt, den)
for n = 1:length(den.powers) for n = 1:length(den.powers)
p = bigprime(n) p = bigprime(n)
while den.powers[n] > 0 while den.powers[n] > 0
@ -365,13 +438,83 @@ function compute6jseries(β₁, β₂, β₃, α₁, α₂, α₃, α₄)
return Base.unsafe_rational(totalnum, totalden) return Base.unsafe_rational(totalnum, totalden)
end end
# compute the sum appearing in the 9j symbol
function compute9jseries(a, b, c, d, e, f, g, h, j)
I1 = max(abs(h - d), abs(b - f), abs(a - j))
I2 = min(h + d, b + f, a + j)
krange = I1:I2
sum(krange) do k
p = iseven(2k) ? big(2k + 1) : -big(2k + 1)
b₁ = let (m₁, m₂, m₃, m₄, m₅, m₆) = (a, b, c, f, j, k)
α₁ = convert(BigInt, m₁ + m₅ - m₆)
α₂ = convert(BigInt, m₁ - m₅ + m₆)
α₃ = convert(BigInt, -m₁ + m₅ + m₆)
β₁ = convert(BigInt, m₁ + m₅ + m₆)
β₂ = convert(BigInt, m₂ + m₄ + m₆)
β₃ = convert(BigInt, m₃ + m₄ + m₅)
β₀ = convert(BigInt, m₁ + m₂ + m₃)
wei9jbracket(α₁, α₂, α₃, β₁, β₂, β₃, β₀)
end
b₂ = let (m₁, m₂, m₃, m₄, m₅, m₆) = (f, d, e, h, b, k)
α₁ = convert(BigInt, m₁ + m₅ - m₆)
α₂ = convert(BigInt, m₁ - m₅ + m₆)
α₃ = convert(BigInt, -m₁ + m₅ + m₆)
β₁ = convert(BigInt, m₁ + m₅ + m₆)
β₂ = convert(BigInt, m₂ + m₄ + m₆)
β₃ = convert(BigInt, m₃ + m₄ + m₅)
β₀ = convert(BigInt, m₁ + m₂ + m₃)
wei9jbracket(α₁, α₂, α₃, β₁, β₂, β₃, β₀)
end
b₃ = let (m₁, m₂, m₃, m₄, m₅, m₆) = (h, j, g, a, d, k)
α₁ = convert(BigInt, m₁ + m₅ - m₆)
α₂ = convert(BigInt, m₁ - m₅ + m₆)
α₃ = convert(BigInt, -m₁ + m₅ + m₆)
β₁ = convert(BigInt, m₁ + m₅ + m₆)
β₂ = convert(BigInt, m₂ + m₄ + m₆)
β₃ = convert(BigInt, m₃ + m₄ + m₅)
β₀ = convert(BigInt, m₁ + m₂ + m₃)
wei9jbracket(α₁, α₂, α₃, β₁, β₂, β₃, β₀)
end
p * b₁ * b₂ * b₃
end
end
export wei9jbracket
# Wei square bracket terms appearing the 9j series
function wei9jbracket(α₁::BigInt, α₂::BigInt, α₃::BigInt, β₁::BigInt, β₂::BigInt, β₃::BigInt, β₀::BigInt)
p = max(β₁, β₂, β₃, β₀)
q = min(α₁ + β₂, α₂ + β₃, α₃ + β₀)
lrange = p:q
T = PrimeFactorization{eltype(eltype(factorialtable))}
terms = Vector{T}(undef, length(lrange))
for (j, l) in enumerate(lrange)
b₁ = iseven(l) ? copy(primebinomial(big(l + 1), l - β₁)) : neg!(copy(primebinomial(big(l + 1), l - β₁)))
b₂ = primebinomial(α₁, l - β₂)
b₃ = primebinomial(α₂, l - β₃)
b₄ = primebinomial(α₃, l - β₀)
terms[j] = mul!(mul!(mul!(b₁, b₂), b₃), b₄)
end
total = sumlist!(terms)
return total
end
function _precompile_() function _precompile_()
@assert precompile(wigner3j, (Type{Float64}, Int, Int, Int, Int, Int, Int)) @assert precompile(wigner3j, (Type{Float64}, Int, Int, Int, Int, Int, Int))
@assert precompile(wigner6j, (Type{Float64}, Int, Int, Int, Int, Int, Int)) @assert precompile(wigner6j, (Type{Float64}, Int, Int, Int, Int, Int, Int))
@assert precompile(wigner9j, (Type{Float64}, Int, Int, Int, Int, Int, Int, Int, Int, Int))
@assert precompile(wigner3j, (Type{BigFloat}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int})) @assert precompile(wigner3j, (Type{BigFloat}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}))
@assert precompile(wigner6j, (Type{BigFloat}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int})) @assert precompile(wigner6j, (Type{BigFloat}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}))
@assert precompile(wigner9j, (Type{BigFloat}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}, Rational{Int}))
@assert precompile(wigner3j, (Type{RRBig}, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt)) @assert precompile(wigner3j, (Type{RRBig}, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt))
@assert precompile(wigner6j, (Type{RRBig}, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt)) @assert precompile(wigner6j, (Type{RRBig}, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt))
@assert precompile(wigner9j, (Type{RRBig}, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt, HalfInt))
end end
_precompile_() _precompile_()

View file

@ -384,3 +384,32 @@ function sumlist!(list::Vector{<:PrimeFactorization}, ind = 1:length(list))
end end
return MPZ.mul!(s, _convert!(i, g)) return MPZ.mul!(s, _convert!(i, g))
end end
#=
# A cached binomial implementation.
bcache = LRU{Tuple{BigInt, BigInt}, PrimeFactorization}(; maxsize=10^6)
function primebinomial(n::BigInt, k::BigInt)
T = PrimeFactorization{eltype(eltype(factorialtable))}
if k == 0
return one(T)
end # guard
if haskey(bcache, (n, k))
return bcache[(n, k)]
else
den = primefactor(k)
num = mul!(copy(primefactor(n + 1 - k)), primebinomial(n, k - 1))
res = divexact!(num, den)
bcache[(n, k)] = res
return res
end
end
=#
function primebinomial(n::BigInt, k::BigInt)
num = copy(primefactorial(n))
den = copy(primefactorial(k))
den = mul!(den, primefactorial(n - k))
res = divexact!(num, den)
return res
end

View file

@ -207,3 +207,23 @@ end
end end
end end
end end
@threads for i = 1:N
@testset "wigner9j: relation to sum over 6j products, thread $i" begin
for k = 1:10_000
@testset let (j1, j2, j3, j4, j5, j6, j7, j8, j9) = rand(smalljlist, 9)
@test wigner9j(j1, j2, j3,
j4, j5, j6,
j7, j8, j9) sum(largejlist) do x # lazy choice for range of this sum, but good enough
(iseven(2x) ? (2x + 1) : -(2x + 1)) *
wigner6j(j1, j4, j7,
j8, j9, x ) *
wigner6j(j2, j5, j8,
j4, x , j6) *
wigner6j(j3, j6, j9,
x , j1, j2)
end
end
end
end
end