"""
This module provides functions to calculate the Wigner D-function.
:math:`D^{j}_{m_1,m_2}(\\alpha,\\beta,\\gamma)=e^{-im_1\\alpha}d^{j}_{m_1,m_2}(\\beta)e^{-im_2\\gamma}`,
where the expression of the Wigner d-function is
.. math:: d^{j}_{m_1,m_2}(\\beta) = \\sum_{l=0}^{2j} w_{l}^{(j,m_1,m_2)}\\sin^{l}(\\frac{\\beta}{2}) \\cos^{2j-l}(\\frac{\\beta}{2}),
where the weight :math:`w_{l}^{(j,m_1,m_2)}` in each term satisfies
.. math:: w^{(j,m_1,m_2)}_{l} = (-1)^{m_1-m_2+k}\\frac{\\sqrt{(j+m_1)!(j-m_1)!(j+m_2)!(j-m_2)!}}{(j-m_1-k)!(j+m_2-k)!(m_1-m_2+k)!k!}
when :math:`k=\\frac{l+m_2-m_1}{2} \\in [\\max(0,m_2-m_1),\\min(j-m_1,j+m_2)]`, and otherwise
:math:`w^{(j,m_1,m_2)}_{l} = 0`.
"""
import functools
import math
import numpy as np
from .tensorflow_wrapper import tf
def _spin_int(x):
if isinstance(x, int):
return x
return int(x + 0.1)
@functools.lru_cache()
def _tuple_delta_D_trans(j, la, lb, lc):
ln = _spin_int(2 * j + 1)
s = np.zeros(shape=(ln, ln, len(la), len(lb), len(lc)))
for i_a, la_i in enumerate(la):
for i_b, lb_i in enumerate(lb):
for i_c, lc_i in enumerate(lc):
delta = lb_i - lc_i
if abs(delta) <= j:
idx = la_i + j, delta + j, i_a, i_b, i_c
s[tuple(map(_spin_int, idx))] = 1.0
return s
[docs]
def delta_D_trans(j, la, lb, lc):
"""
The decay from particle *a* to *b* and *c* requires :math:`|l_b-l_c|\\leqslant j`
(ja,ja) -> (ja,jb,jc)???
"""
la, lb, lc = map(tuple, (la, lb, lc))
ret = _tuple_delta_D_trans(j, la, lb, lc)
return ret
[docs]
def delta_D_index(j, la, lb, lc):
la, lb, lc = map(tuple, (la, lb, lc))
ret = _tuple_delta_D_index(j, la, lb, lc)
return ret
@functools.lru_cache()
def _tuple_delta_D_index(j, la, lb, lc):
ln = _spin_int(2 * j + 1)
ret = []
max_idx = ln * ln
for i_a, la_i in enumerate(la):
for i_b, lb_i in enumerate(lb):
for i_c, lc_i in enumerate(lc):
delta = lb_i - lc_i
if abs(delta) <= j:
ret.append(_spin_int((la_i + j) * ln + delta + j))
else:
ret.append(max_idx)
return ret
[docs]
def Dfun_delta(d, ja, la, lb, lc=(0,)):
"""
The decay from particle *a* to *b* and *c* requires :math:`|l_b-l_c|\\leqslant j`
:math:`D_{ma,mb-mc} = \\delta[(m1,m2)->(ma, mb,mc))] D_{m1,m2}`
"""
t = delta_D_trans(ja, la, lb, lc)
ln = _spin_int(2 * ja + 1)
t_trans = tf.reshape(t, (ln * ln, len(la) * len(lb) * len(lc)))
t_cast = tf.cast(t_trans, d.dtype)
# print(d[0])
d = tf.reshape(d, (-1, ln * ln))
ret = tf.matmul(d, t_cast)
return tf.reshape(ret, (-1, len(la), len(lb), len(lc)))
[docs]
def Dfun_delta_v2(d, ja, la, lb, lc=(0,)):
"""
The decay from particle *a* to *b* and *c* requires :math:`|l_b-l_c|\\leqslant j`
:math:`D_{ma,mb-mc} = \\delta[(m1,m2)->(ma, mb,mc))] D_{m1,m2}`
"""
idx = delta_D_index(ja, la, lb, lc)
ln = _spin_int(2 * ja + 1)
# print(d[0])
d = tf.reshape(d, (-1, ln * ln))
over_d = tf.pad(d, [[0, 0], [0, 1]], mode="CONSTANT")
# print(d.shape, over_d.shape)
# zeros = tf.zeros((d.shape[0], 1), dtype=d.dtype)
# over_d = tf.concat([d, zeros], axis=-1)
ret = tf.gather(over_d, idx, axis=-1)
return tf.reshape(ret, (-1, len(la), len(lb), len(lc)))
[docs]
@functools.lru_cache()
def small_d_weight(j): # the prefactor in the d-function of β
"""
For a certain j, the weight coefficient with index (:math:`m_1,m_2,l`) is
:math:`w^{(j,m_1,m_2)}_{l} = (-1)^{m_1-m_2+k}\\frac{\\sqrt{(j+m_1)!(j-m_1)!(j+m_2)!(j-m_2)!}}{(j-m_1-k)!(j+m_2-k)!(m_1-m_2+k)!k!}`,
and :math:`l` is an integer ranging from 0 to :math:`2j`.
:param j: Integer :math:`2j` in the formula???
:return: Of the shape (**j** +1, **j** +1, **j** +1). The indices correspond to (:math:`l,m_1,m_2`)
"""
ret = np.zeros(shape=(j + 1, j + 1, j + 1))
def f(x):
return math.factorial(x >> 1)
for m in range(-j, j + 1, 2):
for n in range(-j, j + 1, 2):
for k in range(max(0, n - m), min(j - m, j + n) + 1, 2):
l = (2 * k + (m - n)) // 2
sign = (-1) ** ((k + m - n) // 2)
tmp = sign * math.sqrt(
1.0 * f(j + m) * f(j - m) * f(j + n) * f(j - n)
)
tmp /= f(j - m - k) * f(j + n - k) * f(k + m - n) * f(k)
ret[l][(m + j) // 2][(n + j) // 2] = tmp
return ret
[docs]
def small_d_matrix(theta, j):
"""
The matrix element of :math:`d^{j}(\\theta)` is
:math:`d^{j}_{m_1,m_2}(\\theta) = \\sum_{l=0}^{2j} w_{l}^{(j,m_1,m_2)}\\sin^{l}(\\frac{\\theta}{2}) \\cos^{2j-l}(\\frac{\\theta}{2})`
:param theta: Array :math:`\\theta` in the formula
:param j: Integer :math:`2j` in the formula???
:return: The d-matrices array. Same length as theta
"""
a = tf.reshape(tf.range(0, j + 1, 1), (1, -1))
half_theta = np.array(0.5) * theta
sintheta = tf.reshape(tf.sin(half_theta), (-1, 1))
costheta = tf.reshape(tf.cos(half_theta), (-1, 1))
a = tf.cast(a, dtype=sintheta.dtype)
s = tf.pow(sintheta, a)
c = tf.pow(costheta, j - a)
sc = s * c
w = small_d_weight(j)
w = tf.cast(w, sc.dtype)
w = tf.reshape(w, (j + 1, (j + 1) * (j + 1)))
ret = tf.matmul(sc, w)
# ret = tf.einsum("il,lab->iab", sc, w)
return tf.reshape(ret, (-1, j + 1, j + 1))
[docs]
def exp_i(theta, mi):
"""
:math:`e^{im\\theta}`
:param theta: Array :math:`\\theta` in the formula
:param mi: Integer or half-integer :math:`m` in the formula\\
:return: Array of tf.complex. Same length as **theta**
"""
theta_i = tf.reshape(theta, (-1, 1))
mi = tf.cast(mi, dtype=theta.dtype)
m_theta = mi * theta_i
zeros = tf.zeros_like(m_theta)
if m_theta.dtype in [tf.complex128, tf.complex64]:
im_theta = 1.0j * m_theta
else:
im_theta = tf.complex(zeros, m_theta)
exp_theta = tf.exp(im_theta)
return exp_theta
[docs]
def D_matrix_conj(alpha, beta, gamma, j):
"""
The conjugated D-matrix element with indices (:math:`m_1,m_2`) is
.. math::
D^{j}_{m_1,m_2}(\\alpha, \\beta, \\gamma)^\\star =
e^{i m_1 \\alpha} d^{j}_{m_1,m_2}(\\beta) e^{i m_2 \\gamma}
:param alpha: Array
:param beta: Array
:param gamma: Array
:param j: Integer :math:`2j` in the formula
:return: Array of the conjugated D-matrices. Same shape as **alpha**, **beta**, and **gamma**
"""
m = tf.reshape(np.arange(-j / 2, j / 2 + 1, 1), (1, -1))
d = small_d_matrix(beta, j)
expi_alpha = tf.reshape(exp_i(alpha, m), (-1, j + 1, 1))
expi_gamma = tf.reshape(exp_i(gamma, m), (-1, 1, j + 1))
expi_gamma = tf.cast(expi_gamma, dtype=expi_alpha.dtype)
dc = tf.complex(d, tf.zeros_like(d))
ret = tf.cast(expi_alpha * expi_gamma, dc.dtype) * dc
return ret
[docs]
def get_D_matrix_for_angle(angle, j, cached=True):
"""
Interface to *D_matrix_conj()*
:param angle: Dict of angle data {"alpha","beta","gamma"}
:param j: Integer :math:`2j` in the formula
:param cached: Haven't been used???
:return: Array of the conjugated D-matrices. Same length as the angle data
"""
if angle:
beta = angle["beta"]
else:
beta = np.array([0.0])
alpha = angle.get("alpha", tf.zeros_like(beta))
gamma = angle.get("gamma", tf.zeros_like(beta))
name = "D_matrix_{}".format(j)
if cached:
if name not in angle:
angle[name] = D_matrix_conj(alpha, beta, gamma, j)
return angle[name]
return D_matrix_conj(alpha, beta, gamma, j)
[docs]
def get_D_matrix_lambda(angle, ja, la, lb, lc=None):
"""
Get the D-matrix element
:param angle: Dict of angle data {"alpha","beta","gamma"}
:param ja:
:param la:
:param lb:
:param lc:
:return:
"""
if angle is None:
assert lc is None
ret = np.zeros((1, len(la), len(lb)), dtype=np.complex128)
for idxi, i in enumerate(la):
for idxj, j in enumerate(lb):
if i == j:
ret[0, idxi, idxj] = 1
return ret
d = get_D_matrix_for_angle(angle, _spin_int(2 * ja))
if lc is None:
return tf.reshape(
Dfun_delta_v2(d, ja, la, lb, (0,)), (-1, len(la), len(lb))
)
else:
return Dfun_delta_v2(d, ja, la, lb, lc)