In [1]:
from __future__ import print_function, division, absolute_import
import numpy as np
import random
import quaternion
import numbapro as nb
from numbapro import *
import spherical_functions as sp
ru = lambda : random.uniform(-1,1)
In [2]:
j1,j2,j3,m1,m2,m3 = 1, 0, 1, 0, 0, (-0 - 0)
sp.Wigner3j(j1,j2,j3,m1,m2,m3), sp.Wigner3j(j1,(j2+j3-m1)/2,(j2+j3+m1)/2,j3-j2,(j2-j3-m1)/2+m1+m2,(j2-j3+m1)/2-m1-m2)
Out[2]:
In [3]:
j1,(j2+j3-m1)/2,(j2+j3+m1)/2,j3-j2,(j2-j3-m1)/2+m1+m2,(j2-j3+m1)/2-m1-m2
Out[3]:
In [5]:
j2,j3,m1,(j2-j3-m1)/2
Out[5]:
In [2]:
%%timeit
for j1 in range(8):
for j2 in range(8):
for j3 in range(8):
for m1 in range(-j1,j1+1):
for m2 in range(-j2,j2+1):
sp.wigner3j(j1,j2,j3,m1,m2,-m1-m2)
In [3]:
i=0
for j1 in range(8):
for j2 in range(8):
for j3 in range(8):
for m1 in range(-j1,j1+1):
for m2 in range(-j2,j2+1):
i+=1
i
Out[3]:
In [4]:
22.5e-3/_
Out[4]:
In [2]:
q = np.quaternion(ru(), ru(), ru(), ru())
q = q/q.abs()
q
Out[2]:
In [9]:
# Using slow wrapper
ells = range(16+1)
evals = np.empty_like(ells, dtype=int)
nanoseconds = np.empty_like(ells, dtype=float)
for i,ell_max in enumerate(ells):
indices = np.array([[ell, mp, m] for ell in range(ell_max+1) for mp in range(-ell,ell+1) for m in range(-ell,ell+1)], dtype=int)
elements = np.empty((indices.shape[0],), dtype=complex)
result = %timeit -o sp.WignerD(q, indices)
evals[i] = len(indices)
nanoseconds[i] = 1e9*result.best/len(indices)
print("With ell_max={0}, and {1} evaluations, each D component averages {2:.0f} ns".format(ell_max,evals[i],nanoseconds[i]))
sys.stdout.flush()
In [8]:
# Directly using jitted inner function, also creating array
ells = range(16+1)
evals = np.empty_like(ells, dtype=int)
nanoseconds = np.empty_like(ells, dtype=float)
for i,ell_max in enumerate(ells):
indices = np.array([[ell, mp, m] for ell in range(ell_max+1) for mp in range(-ell,ell+1) for m in range(-ell,ell+1)], dtype=int)
result = %timeit -o sp._WignerD(q.a, q.b, indices, np.empty((indices.shape[0],), dtype=complex))
evals[i] = len(indices)
nanoseconds[i] = 1e9*result.best/len(indices)
print("With ell_max={0}, and {1} evaluations, each D component averages {2:.0f} ns".format(ell_max,evals[i],nanoseconds[i]))
sys.stdout.flush()
In [3]:
# After re-configuring the calculation
ells = range(16+1)
evals = np.empty_like(ells, dtype=int)
nanoseconds = np.empty_like(ells, dtype=float)
for i,ell_max in enumerate(ells):
indices = np.array([[ell, mp, m] for ell in range(ell_max+1) for mp in range(-ell,ell+1) for m in range(-ell,ell+1)], dtype=int)
elements = np.empty((indices.shape[0],), dtype=complex)
result = %timeit -o sp._WignerD(q.a, q.b, indices, elements)
evals[i] = len(indices)
nanoseconds[i] = 1e9*result.best/len(indices)
print("With ell_max={0}, and {1} evaluations, each D component averages {2:.0f} ns".format(ell_max,evals[i],nanoseconds[i]))
sys.stdout.flush()
In [3]:
# Directly using jitted inner function
ells = range(16+1)
evals = np.empty_like(ells, dtype=int)
nanoseconds = np.empty_like(ells, dtype=float)
for i,ell_max in enumerate(ells):
indices = np.array([[ell, mp, m] for ell in range(ell_max+1) for mp in range(-ell,ell+1) for m in range(-ell,ell+1)], dtype=int)
elements = np.empty((indices.shape[0],), dtype=complex)
result = %timeit -o sp._WignerD(q.a, q.b, indices, elements)
evals[i] = len(indices)
nanoseconds[i] = 1e9*result.best/len(indices)
print("With ell_max={0}, and {1} evaluations, each D component averages {2:.0f} ns".format(ell_max,evals[i],nanoseconds[i]))
sys.stdout.flush()
In [20]:
# Before wrapping key math parts in jit
ells = range(1,16)
evals = np.empty_like(ells, dtype=int)
microseconds = np.empty_like(ells, dtype=float)
for i,ell_max in enumerate(ells):
indices = np.array([[ell, mp, m] for ell in range(ell_max+1) for mp in range(-ell,ell+1) for m in range(-ell,ell+1)])
result = %timeit -o sp.wignerD(q.a, q.b, indices)
evals[i] = len(indices)
microseconds[i] = 1e6*result.best/len(indices)
print("With ell_max={0}, and {1} evaluations, each D component averages {2:.3} µs".format(ell_max,evals[i],microseconds[i]))
sys.stdout.flush()
In [22]:
plot(evals, microseconds)
Out[22]:
In [77]:
@njit #('void(complex128, complex128, int[:,:], complex128[:])')
def _wignerD(Ra, Rb, indices, elements):
"""Main work function for computing Wigner D matrix elements
This is the core function that does all the work in the
computation, but it is strict about its input, and does not check
them for validity.
Input arguments
===============
_wignerD(Ra, Rb, indices, elements)
* Ra, Rb are the complex components of the rotor
* indices is an array of integers [ell,mp,m]
* elements is an array of complex with length equal to the first
dimension of indices
The `elements` variable is needed because numba cannot create
arrays at the moment, but this is modified in place and returned.
"""
N = indices.shape[0]
# These constants are the recurring quantities in the computation
# of the matrix elements, so we calculate them here just once
absRa = abs(Ra)
absRb = abs(Rb)
absRRatioSquared = (absRb*absRb/(absRa*absRa) if absRa>epsilon else 0.0)
absRa_exp = (int(np.log(absRa)/_log2) if absRa>epsilon else min_exp)
absRb_exp = (int(np.log(absRb)/_log2) if absRb>epsilon else min_exp)
for i in range(N):
ell = indices[i,0]
mp = indices[i,1]
m = indices[i,2]
if(abs(mp)>ell or abs(m)>ell):
elements[i] = 0.0+0.0j
elif(absRa<=epsilon or 2*absRa_exp*(mp-m)<min_exp+mant_dig):
if mp!=m:
elements[i] = 0.0j
else:
if (ell+mp)%2==0:
elements[i] = Rb**(2*m)
else:
elements[i] = -Rb**(2*m)
elif(absRb<=epsilon or 2*absRb_exp*(mp-m)<min_exp+mant_dig):
if mp!=m:
elements[i] = 0.0j
else:
elements[i] = Ra**(2*m)
else:
rhoMin = max(0,mp-m)
rhoMax = min(ell+mp,ell-m)
if(absRa < 1.e-3):
# In this branch, we deal with NANs in certain cases when
# absRa is small by separating it out from the main sum,
Prefactor = coeff(ell, mp, m) * Ra**(m+mp) * Rb**(m-mp)
absRaSquared = absRa*absRa
absRbSquared = absRb*absRb
Sum = 0.0
for rho in range(rhoMax, rhoMin-1, -1):
aTerm = absRaSquared**(ell-m-rho);
if(aTerm != aTerm or aTerm<1.e-100):
Sum *= absRbSquared
else:
if rho%2==0:
Sum = ( binomial_coefficient(ell+mp,rho) * binomial_coefficient(ell-mp, ell-rho-m) * aTerm
+ Sum * absRbSquared )
else:
Sum = ( -binomial_coefficient(ell+mp,rho) * binomial_coefficient(ell-mp, ell-rho-m) * aTerm
+ Sum * absRbSquared )
elements[i] = Prefactor * Sum * absRbSquared**rhoMin
else:
Prefactor = coeff(ell, mp, m) * absRa**(2*ell-2*m) * Ra**(m+mp) * Rb**(m-mp)
Sum = 0.0
for rho in range(rhoMax, rhoMin-1, -1):
if rho%2==0:
Sum = ( binomial_coefficient(ell+mp,rho) * binomial_coefficient(ell-mp, ell-rho-m)
+ Sum * absRRatioSquared )
else:
Sum = ( -binomial_coefficient(ell+mp,rho) * binomial_coefficient(ell-mp, ell-rho-m)
+ Sum * absRRatioSquared )
elements[i] = Prefactor * Sum * absRRatioSquared**rhoMin
# return elements
In [78]:
ell_max=8
indices = np.array([[ell, mp, m] for ell in range(ell_max+1) for mp in range(-ell,ell+1) for m in range(-ell,ell+1)], dtype=int)
elements = np.empty((indices.shape[0],), dtype=complex)
elements_orig = np.copy(elements)
_wignerD(q.a, q.b, indices, elements)
#print(elements)
print(np.allclose(elements, elements_orig))
In [54]:
indices
Out[54]:
In [55]:
ell_max
Out[55]:
In [43]:
elements, indices.shape[0]
Out[43]:
In [46]:
elements.shape
Out[46]:
In [ ]:
In [12]:
_wignerD.inspect_types()
In [ ]: