def test_WignerDindex(ell_max_slow): k = 0 for ell_max in range(ell_max_slow + 1): r = sf.WignerDrange(0, ell_max) for ell in range(ell_max + 1): for mp in range(-ell, ell + 1): for m in range(-ell, ell + 1): i = sf.WignerDindex(ell, mp, m) assert np.array_equal(r[i], [ell, mp, m]), ((ell, mp, m), i, ell_max, r) k += 1 for ell_min in range(ell_max + 1): r = sf.WignerDrange(ell_min, ell_max) for ell in range(ell_min, ell_max + 1): for mp in range(-ell, ell + 1): for m in range(-ell, ell + 1): i = sf.WignerDindex(ell, mp, m, ell_min) assert np.array_equal(r[i], [ell, mp, m]), ((ell, mp, m), i, ell_min, ell_max, r) k += 1 for mp_max in range(ell_max + 1): r = sf.WignerDrange(ell_min, mp_max, ell_max) for ell in range(ell_min, ell_max + 1): for mp in range(-min(ell, mp_max), min(ell, mp_max) + 1): for m in range(-ell, ell + 1): i = sf.WignerDindex(ell, mp, m, ell_min, mp_max) assert np.array_equal( r[i], [ell, mp, m]), ((ell, mp, m), i, ell_min, mp_max, ell_max, r) k += 1
def test_WignerDrange(ell_max_slow): def r(ell_min, mp_max, ell_max): return [(ℓ, mp, m) for ℓ in range(ell_min, ell_max + 1) for mp in range(-min(ℓ, mp_max), min(ℓ, mp_max) + 1) for m in range(-ℓ, ℓ + 1)] for ell_max in range(ell_max_slow // 2 + 1): for ell_min in range(ell_max + 1): a = sf.WignerDrange(ell_min, ell_max) # Implicitly, mp_max=ell_max b = r(ell_min, ell_max, ell_max) assert np.array_equal(a, b), ((ell_min, mp_max, ell_max), a, b) for mp_max in range(ell_max + 1): a = sf.WignerDrange(ell_min, mp_max, ell_max) b = r(ell_min, mp_max, ell_max) assert np.array_equal(a, b), ((ell_min, mp_max, ell_max), a, b)
def test_Wigner_D_underflow(Rs, ell_max, eps): # NOTE: This is a delicate test, which depends on the result underflowing exactly when expected. # In particular, it should underflow to 0.0 when |mp+m|>32, but should never underflow to 0.0 # when |mp+m|<32. So it's not the end of the world if this test fails, but it does mean that # the underflow properties have changed, so it might be worth a look. epsilon = 1.e-10 ϵ = 5 * ell_max * eps D = np.zeros(sf.WignerDsize(0, ell_max), dtype=complex) wigner = sf.Wigner(ell_max) ell_mp_m = sf.WignerDrange(0, ell_max) # Test |Ra|=1e-10 R = quaternionic.array(epsilon, 1, 0, 0).normalized wigner.D(R, out=D) # print(R.to_euler_angles.tolist()) # print(D.tolist()) non_underflowing_indices = np.abs(ell_mp_m[:, 1] + ell_mp_m[:, 2]) < 32 assert np.all(D[non_underflowing_indices] != 0j) underflowing_indices = np.abs(ell_mp_m[:, 1] + ell_mp_m[:, 2]) > 32 assert np.all(D[underflowing_indices] == 0j) # Test |Rb|=1e-10 R = quaternionic.array(1, epsilon, 0, 0).normalized wigner.D(R, out=D) non_underflowing_indices = np.abs(ell_mp_m[:, 1] - ell_mp_m[:, 2]) < 32 assert np.all(D[non_underflowing_indices] != 0j) underflowing_indices = np.abs(ell_mp_m[:, 1] - ell_mp_m[:, 2]) > 32 assert np.all(D[underflowing_indices] == 0j)
def test_WignerDsize_mpmax_ellmin(ell_max): # k = 0 for ell_max in range(ell_max + 1): for ell_min in range(ell_max + 1): for mp_max in range(ell_max + 1): a = sf.WignerDsize(ell_min, mp_max, ell_max) b = len(sf.WignerDrange(ell_min, mp_max, ell_max)) assert a == b, ((ell_min, mp_max, ell_max), a, b) #, k)
def test_Wigner_D_symmetries(Rs, ell_max, eps): # We have two obvious symmetries to test. First, # # D_{mp,m}(R) = (-1)^{mp+m} \bar{D}_{-mp,-m}(R) # # Second, since D is a unitary matrix, its conjugate transpose is its # inverse; because of the representation property, D(R) should equal the # matrix inverse of D(R⁻¹). Thus, # # D_{mp,m}(R) = \bar{D}_{m,mp}(\bar{R}) ϵ = 5 * ell_max * eps D1 = np.zeros(sf.WignerDsize(0, ell_max), dtype=complex) D2 = np.zeros(sf.WignerDsize(0, ell_max), dtype=complex) wigner = sf.Wigner(ell_max) ell_mp_m = sf.WignerDrange(0, ell_max) flipped_indices = np.array( [sf.WignerDindex(ell, -mp, -m) for ell, mp, m in ell_mp_m]) swapped_indices = np.array( [sf.WignerDindex(ell, m, mp) for ell, mp, m in ell_mp_m]) signs = (-1)**np.abs(np.sum(ell_mp_m[:, 1:], axis=1)) for R in Rs: wigner.D(R, out=D1) wigner.D(R.inverse, out=D2) # D_{mp,m}(R) = (-1)^{mp+m} \bar{D}_{-mp,-m}(R) a = D1 b = signs * D1[flipped_indices].conjugate() assert np.allclose(a, b, rtol=ϵ, atol=ϵ) # D_{mp,m}(R) = \bar{D}_{m,mp}(\bar{R}) b = D2[swapped_indices].conjugate() assert np.allclose(a, b, rtol=ϵ, atol=ϵ) D1 = wigner.D(Rs) D2 = wigner.D(Rs.inverse) # D_{mp,m}(R) = (-1)^{mp+m} \bar{D}_{-mp,-m}(R) a = D1 b = signs * D1[..., flipped_indices].conjugate() assert np.allclose(a, b, rtol=ϵ, atol=ϵ) # D_{mp,m}(R) = \bar{D}_{m,mp}(\bar{R}) b = D2[..., swapped_indices].conjugate() assert np.allclose(a, b, rtol=ϵ, atol=ϵ)
def test_Wigner_D_vs_Wikipedia(special_angles, ell_max_slow, eps): ell_max = ell_max_slow ϵ = 5 * ell_max**6 * eps D = np.zeros(sf.WignerDsize(0, ell_max), dtype=complex) wigner = sf.Wigner(ell_max) ell_mp_m = sf.WignerDrange(0, ell_max) print("") for alpha in special_angles: print("\talpha={0}".format(alpha)) # Need to show some progress for CI for beta in special_angles[len(special_angles) // 2:]: # Skip beta < 0 print("\t\tbeta={0}".format(beta)) for gamma in special_angles: a = np.conjugate( np.array([ Wigner_D_Wikipedia(alpha, beta, gamma, ell, mp, m) for ell, mp, m in ell_mp_m ])) b = wigner.D(quaternionic.array.from_euler_angles( alpha, beta, gamma), out=D) assert np.allclose(a, b, rtol=ϵ, atol=ϵ)