def test_SWSH_conjugation(special_angles, ell_max): # {s}Y{l,m}.conjugate() = (-1.)**(s+m) {-s}Y{l,-m} indices1 = sf.LM_range(0, ell_max) indices2 = np.array([[ell, -m] for ell, m in indices1]) neg1_to_m = np.array([(-1.)**m for ell, m in indices1]) for iota in special_angles: for phi in special_angles: R = quaternion.from_spherical_coords(iota, phi) for s in range(1-ell_max, ell_max): assert np.allclose(sf.SWSH(R, s, indices1), (-1.)**s * neg1_to_m * np.conjugate(sf.SWSH(R, -s, indices2)), atol=1e-15, rtol=1e-15)
def test_SWSH_signatures(Rs): """There are two ways to call the SWSH function: with an array of Rs, or with an array of (ell,m) values. This test ensures that the results are the same in both cases.""" s_max = 5 ss = np.arange(-s_max, s_max + 1) ell_max = 5 ell_ms = sf.LM_range(0, ell_max) SWSHs1 = np.empty((Rs.size, ss.size, ell_ms.size // 2), dtype=np.complex) SWSHs2 = np.empty_like(SWSHs1) for i_s, s in enumerate(ss): for i_ellm, (ell, m) in enumerate(ell_ms): SWSHs1[:, i_s, i_ellm] = sf.SWSH(Rs, s, [ell, m]) for i_s, s in enumerate(ss): for i_R, R in enumerate(Rs): SWSHs2[i_R, i_s, :] = sf.SWSH(R, s, ell_ms) assert np.array_equal(SWSHs1, SWSHs2)
def test_vector_as_ell_1_modes(special_angles): indices = np.array([[1, -1], [1, 0], [1, 1]]) def nhat(theta, phi): return np.array([ math.sin(theta) * math.cos(phi), math.sin(theta) * math.sin(phi), math.cos(theta) ]) np.random.seed(123) for rep in range(1000): vector = np.random.uniform(-1, 1, size=(3, )) vec_ell_m = sf.vector_as_ell_1_modes(vector) assert np.allclose(vector, sf.vector_from_ell_1_modes(vec_ell_m), atol=1.0e-16, rtol=1.0e-15) for theta in special_angles: for phi in special_angles: dot1 = np.dot(vector, nhat(theta, phi)) dot2 = np.dot( vec_ell_m, sf.SWSH(quaternion.from_spherical_coords(theta, phi), 0, indices)).real assert abs(dot1 - dot2) < 1e-15
def test_SWSH_spin_behavior(Rs, special_angles, ell_max): # We expect that the SWSHs behave according to # sYlm( R * exp(gamma*z/2) ) = sYlm(R) * exp(-1j*s*gamma) # See http://moble.github.io/spherical_functions/SWSHs.html#fn:2 # for a more detailed explanation # print("") for i, R in enumerate(Rs): # print("\t{0} of {1}: R = {2}".format(i, len(Rs), R)) for gamma in special_angles: for ell in range(ell_max + 1): for s in range(-ell, ell + 1): LM = np.array([[ell, m] for m in range(-ell, ell + 1)]) Rgamma = R * np.quaternion(math.cos(gamma / 2.), 0, 0, math.sin(gamma / 2.)) sYlm1 = sf.SWSH(Rgamma, s, LM) sYlm2 = sf.SWSH(R, s, LM) * cmath.exp(-1j * s * gamma) # print(R, gamma, ell, s, np.max(np.abs(sYlm1-sYlm2))) assert np.allclose(sYlm1, sYlm2, atol=ell ** 6 * precision_SWSH, rtol=ell ** 6 * precision_SWSH)
def test_SWSH_values(special_angles, ell_max): for iota in special_angles: for phi in special_angles: for ell in range(ell_max + 1): for s in range(-ell, ell + 1): for m in range(-ell, ell + 1): R = quaternion.from_euler_angles(phi, iota, 0) assert abs(sf.SWSH(R, s, np.array([[ell, m]])) - slow_sYlm(s, ell, m, iota, phi)) < ell_max ** 6 * precision_SWSH
def test_SWSH_WignerD_expression(special_angles, ell_max): for iota in special_angles: for phi in special_angles: for ell in range(ell_max + 1): for s in range(-ell, ell + 1): R = quaternion.from_euler_angles(phi, iota, 0) LM = np.array([[ell, m] for m in range(-ell, ell + 1)]) Y = sf.SWSH(R, s, LM) LMS = np.array([[ell, m, -s] for m in range(-ell, ell + 1)]) D = (-1.) ** (s) * math.sqrt((2 * ell + 1) / (4 * np.pi)) * sf.Wigner_D_element(R.a, R.b, LMS) assert np.allclose(Y, D, atol=ell ** 6 * precision_SWSH, rtol=ell ** 6 * precision_SWSH)
def test_SWSH_grid(special_angles, ell_max): LM = sf.LM_range(0, ell_max) # Test flat array arrangement R_grid = np.array([quaternion.from_euler_angles(alpha, beta, gamma).normalized() for alpha in special_angles for beta in special_angles for gamma in special_angles]) for s in range(-ell_max + 1, ell_max): values_explicit = np.array([sf.SWSH(R, s, LM) for R in R_grid]) values_grid = sf.SWSH_grid(R_grid, s, ell_max) assert np.array_equal(values_explicit, values_grid) # Test nested array arrangement R_grid = np.array([[[quaternion.from_euler_angles(alpha, beta, gamma) for alpha in special_angles] for beta in special_angles] for gamma in special_angles]) for s in range(-ell_max + 1, ell_max): values_explicit = np.array([[[sf.SWSH(R, s, LM) for R in R1] for R1 in R2] for R2 in R_grid]) values_grid = sf.SWSH_grid(R_grid, s, ell_max) assert np.array_equal(values_explicit, values_grid)
def swsh(s, modes, theta, phi, psi=0): """ Return a value of a spin-weighted spherical harmonic of spin-weight s. If passed a list of several modes, then a numpy array is returned with SWSH values of each mode for the given point. For one mode: swsh(s,[(l,m)],theta,phi,psi=0) For several modes: swsh(s,[(l1,m1),(l2,m2),(l3,m3),...],theta,phi,psi=0) """ import spherical_functions as sf import quaternion as qt return sf.SWSH(qt.from_spherical_coords(theta, phi), s, modes) * np.exp( 1j * s * psi )
def test_constant_as_ell_0_mode(special_angles): indices = np.array([[0, 0]]) np.random.seed(123) for imaginary_part in [0.0, 1.0j]: # Test both real and imaginary constants for rep in range(1000): constant = np.random.uniform( -1, 1) + imaginary_part * np.random.uniform(-1, 1) const_ell_m = sf.constant_as_ell_0_mode(constant) assert abs(constant - sf.constant_from_ell_0_mode(const_ell_m)) < 1e-15 for theta in special_angles: for phi in special_angles: dot = np.dot( const_ell_m, sf.SWSH(quaternion.from_spherical_coords(theta, phi), 0, indices)) assert abs(constant - dot) < 1e-15