def Wigner3jCached(j_1,j_2,j_3,m_1,m_2,m_3): """Wrapper for 3j caching with functools.lru_cache""" return sf.Wigner3j(j_1,j_2,j_3,m_1,m_2,m_3)
def single_mode_proportional_to_time_supertranslated(**kwargs): """Return WaveformModes as in single_mode_proportional_to_time, with analytical supertranslation This function constructs the same basic object as the `single_mode_proportional_to_time`, but then applies an analytical supertranslation. The arguments to this function are the same as to the other, with two additions: Additional parameters --------------------- supertranslation : complex array, optional Spherical-harmonic modes of the supertranslation to apply to the waveform. This is overwritten by `space_translation` if present. Default value is `None`. space_translation : float array of length 3, optional This is just the 3-vector representing the displacement to apply to the waveform. Note that if `supertranslation`, this parameter overwrites it. Default value is [1.0, 0.0, 0.0]. """ s = kwargs.pop("s", -2) ell = kwargs.pop("ell", abs(s)) m = kwargs.pop("m", -ell) ell_min = kwargs.pop("ell_min", abs(s)) ell_max = kwargs.pop("ell_max", 8) data_type = kwargs.pop("data_type", scri.DataType[scri.SpinWeights.index(s)]) t_0 = kwargs.pop("t_0", -20.0) t_1 = kwargs.pop("t_1", 20.0) dt = kwargs.pop("dt", 1.0 / 10.0) t = np.arange(t_0, t_1 + dt, dt) n_times = t.size beta = kwargs.pop("beta", 1.0) data = np.zeros((n_times, sf.LM_total_size(ell_min, ell_max)), dtype=complex) data[:, sf.LM_index(ell, m, ell_min)] = beta * t supertranslation = np.array(kwargs.pop("supertranslation", np.array([], dtype=complex)), dtype=complex) if "space_translation" in kwargs: if supertranslation.size < 4: supertranslation.resize((4, )) supertranslation[1:4] = -sf.vector_as_ell_1_modes( kwargs.pop("space_translation")) supertranslation_ell_max = int(math.sqrt(supertranslation.size) - 1) if supertranslation_ell_max * (supertranslation_ell_max + 2) + 1 != supertranslation.size: raise ValueError( f"Bad number of elements in supertranslation: {supertranslation.size}" ) for i, (ellpp, mpp) in enumerate(sf.LM_range(0, supertranslation_ell_max)): if supertranslation[i] != 0.0: mp = m + mpp for ellp in range(ell_min, min(ell_max, (ell + ellpp)) + 1): if ellp >= abs(mp): addition = (beta * supertranslation[i] * math.sqrt( ((2 * ellpp + 1) * (2 * ell + 1) * (2 * ellp + 1)) / (4 * math.pi)) * sf.Wigner3j(ellpp, ell, ellp, 0, -s, s) * sf.Wigner3j(ellpp, ell, ellp, mpp, m, -mp)) if (s + mp) % 2 == 1: addition *= -1 data[:, sf.LM_index(ellp, mp, ell_min)] += addition if kwargs: import pprint warnings.warn( f"\nUnused kwargs passed to this function:\n{pprint.pformat(kwargs, width=1)}" ) return scri.WaveformModes( t=t, data=data, ell_min=ell_min, ell_max=ell_max, frameType=scri.Inertial, dataType=data_type, r_is_scaled_out=True, m_is_scaled_out=True, )
def specialized_multiplication(f, ellmin_f, ellmax_f, s_f, g, ellmin_g, ellmax_g, s_g): ellmin_fg = 0 ellmax_fg = ellmax_f + ellmax_g s_fg = s_f + s_g fg = np.zeros(sf.LM_total_size(0, ellmax_fg), dtype=np.complex) # for ell in range(ellmin_g, ellmax_g+1): # for m in range(-ell, ell+1): # i_g = sf.LM_index(ell, m, ellmin_g) # for ellprime in [ell-1, ell, ell+1]: # if ellprime < ellmin_g or ellprime > ellmax_g or ellprime < abs(s_g): # continue # for mprime in [m-1, m, m+1]: # if mprime < -ellprime or mprime > ellprime: # continue # i_fg = sf.LM_index(ellprime, mprime, ellmin_fg) # m1 = mprime - m # fg[i_fg] += ( # # (-1)**(s_g+m+1) # # * np.sqrt(3*(2*ell+1)/(4*np.pi)) # # * (-1)**(m1) # # * np.sqrt(2*ellprime+1) # (-1)**(1 + ell + ellprime + s_g + m + m1) # * np.sqrt(3*(2*ell+1)*(2*ellprime+1)/(4*np.pi)) # * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ellprime, m1, m, -mprime) # * f[sf.LM_index(1, m1, 0)] # * g[i_g] # ) #################################################################################### # for ell in range(ellmin_g, ellmax_g+1): # for m in range(-ell, ell+1): # i_g = sf.LM_index(ell, m, ellmin_g) # coefficient = (-1)**(s_g + m) * np.sqrt(3*(2*ell+1)/(4*np.pi)) # for ellprime in [ell-1, ell, ell+1]: # if ellprime < ellmin_g or ellprime > ellmax_g or ellprime < abs(s_g): # continue # if m-1 >= -ellprime and m-1 <= ellprime: # i_fg = sf.LM_index(ellprime, m-1, ellmin_fg) # fg[i_fg] += ( # coefficient # * (-1)**(ell + ellprime) # * np.sqrt((2*ellprime+1)) # * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ellprime, -1, m, -m+1) # * f[sf.LM_index(1, -1, 0)] # * g[i_g] # ) # if m >= -ellprime and m <= ellprime: # i_fg = sf.LM_index(ellprime, m, ellmin_fg) # fg[i_fg] += ( # coefficient # * (-1)**(ell + ellprime) # * -np.sqrt(2*ellprime+1) # * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ellprime, 0, m, -m) # * f[sf.LM_index(1, 0, 0)] # * g[i_g] # ) # if m+1 >= -ellprime and m+1 <= ellprime: # i_fg = sf.LM_index(ellprime, m+1, ellmin_fg) # fg[i_fg] += ( # coefficient # * (-1)**(ell + ellprime) # * np.sqrt(2*ellprime+1) # * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ellprime, 1, m, -m-1) # * f[sf.LM_index(1, 1, 0)] # * g[i_g] # ) #################################################################################### # for ell in range(ellmin_g, ellmax_g+1): # for m in range(-ell, ell+1): # i_g = sf.LM_index(ell, m, ellmin_g) # coefficient = (-1)**(s_g + m + 1) * np.sqrt(3*(2*ell+1)/(4*np.pi)) # if ell-1 >= ellmin_g and ell-1 <= ellmax_g and ell-1 >= abs(s_g): # if m-1 >= -ell+1 and m-1 <= ell-1: # i_fg = sf.LM_index(ell-1, m-1, ellmin_fg) # fg[i_fg] += ( # coefficient # * np.sqrt(2*ell-1) # * sf.Wigner3j(1, ell, ell-1, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell-1, -1, m, -m+1) # * f[sf.LM_index(1, -1, 0)] # * g[i_g] # ) # if m >= -ell+1 and m <= ell-1: # i_fg = sf.LM_index(ell-1, m, ellmin_fg) # fg[i_fg] += ( # -coefficient # * np.sqrt(2*ell-1) # * sf.Wigner3j(1, ell, ell-1, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell-1, 0, m, -m) # * f[sf.LM_index(1, 0, 0)] # * g[i_g] # ) # if m+1 >= -ell+1 and m+1 <= ell-1: # i_fg = sf.LM_index(ell-1, m+1, ellmin_fg) # fg[i_fg] += ( # coefficient # * np.sqrt(2*ell-1) # * sf.Wigner3j(1, ell, ell-1, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell-1, 1, m, -m-1) # * f[sf.LM_index(1, 1, 0)] # * g[i_g] # ) # if ell >= ellmin_g and ell <= ellmax_g and ell >= abs(s_g): # if m-1 >= -ell and m-1 <= ell: # i_fg = sf.LM_index(ell, m-1, ellmin_fg) # fg[i_fg] += ( # -coefficient # * np.sqrt(2*ell+1) # * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell, -1, m, -m+1) # * f[sf.LM_index(1, -1, 0)] # * g[i_g] # ) # if m >= -ell and m <= ell: # i_fg = sf.LM_index(ell, m, ellmin_fg) # fg[i_fg] += ( # coefficient # * np.sqrt(2*ell+1) # * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell, 0, m, -m) # * f[sf.LM_index(1, 0, 0)] # * g[i_g] # ) # if m+1 >= -ell and m+1 <= ell: # i_fg = sf.LM_index(ell, m+1, ellmin_fg) # fg[i_fg] += ( # -coefficient # * np.sqrt(2*ell+1) # * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell, 1, m, -m-1) # * f[sf.LM_index(1, 1, 0)] # * g[i_g] # ) # if ell+1 >= ellmin_g and ell+1 <= ellmax_g and ell+1 >= abs(s_g): # if m-1 >= -ell-1 and m-1 <= ell+1: # i_fg = sf.LM_index(ell+1, m-1, ellmin_fg) # fg[i_fg] += ( # coefficient # * np.sqrt(2*ell+3) # * sf.Wigner3j(1, ell, ell+1, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell+1, -1, m, -m+1) # * f[sf.LM_index(1, -1, 0)] # * g[i_g] # ) # if m >= -ell-1 and m <= ell+1: # i_fg = sf.LM_index(ell+1, m, ellmin_fg) # fg[i_fg] += ( # -coefficient # * np.sqrt(2*ell+3) # * sf.Wigner3j(1, ell, ell+1, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell+1, 0, m, -m) # * f[sf.LM_index(1, 0, 0)] # * g[i_g] # ) # if m+1 >= -ell-1 and m+1 <= ell+1: # i_fg = sf.LM_index(ell+1, m+1, ellmin_fg) # fg[i_fg] += ( # coefficient # * np.sqrt(2*ell+3) # * sf.Wigner3j(1, ell, ell+1, 0, s_g, -s_g) # * sf.Wigner3j(1, ell, ell+1, 1, m, -m-1) # * f[sf.LM_index(1, 1, 0)] # * g[i_g] # ) #################################################################################### for ell in range(ellmin_g, ellmax_g + 1): for m in range(-ell, ell + 1): i_fg = sf.LM_index(ell, m, ellmin_fg) coefficient = (-1)**(s_g + m) * np.sqrt(3 * (2 * ell + 1) / (4 * np.pi)) if ell + 1 >= ellmin_g and ell + 1 <= ellmax_g and ell + 1 >= abs( s_g): if m + 1 >= -ell - 1 and m + 1 <= ell + 1: fg[i_fg] += ( coefficient * np.sqrt(2 * ell + 3) * sf.Wigner3j(1, ell + 1, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell + 1, ell, -1, m + 1, -m) * f[sf.LM_index(1, -1, 0)] * g[sf.LM_index(ell + 1, m + 1, ellmin_g)]) if m >= -ell - 1 and m <= ell + 1: fg[i_fg] += ( coefficient * np.sqrt(2 * ell + 3) * sf.Wigner3j(1, ell + 1, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell + 1, ell, 0, m, -m) * f[sf.LM_index(1, 0, 0)] * g[sf.LM_index(ell + 1, m, ellmin_g)]) if m - 1 >= -ell - 1 and m - 1 <= ell + 1: fg[i_fg] += ( coefficient * np.sqrt(2 * ell + 3) * sf.Wigner3j(1, ell + 1, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell + 1, ell, 1, m - 1, -m) * f[sf.LM_index(1, 1, 0)] * g[sf.LM_index(ell + 1, m - 1, ellmin_g)]) if ell >= ellmin_g and ell <= ellmax_g and ell >= abs(s_g): if m + 1 >= -ell and m + 1 <= ell: fg[i_fg] += (-coefficient * np.sqrt(2 * ell + 1) * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell, ell, -1, m + 1, -m) * f[sf.LM_index(1, -1, 0)] * g[sf.LM_index(ell, m + 1, ellmin_g)]) if m >= -ell and m <= ell: fg[i_fg] += (-coefficient * np.sqrt(2 * ell + 1) * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell, ell, 0, m, -m) * f[sf.LM_index(1, 0, 0)] * g[sf.LM_index(ell, m, ellmin_g)]) if m - 1 >= -ell and m - 1 <= ell: fg[i_fg] += (-coefficient * np.sqrt(2 * ell + 1) * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell, ell, 1, m - 1, -m) * f[sf.LM_index(1, 1, 0)] * g[sf.LM_index(ell, m - 1, ellmin_g)]) if ell - 1 >= ellmin_g and ell - 1 <= ellmax_g and ell - 1 >= abs( s_g): if m + 1 >= -ell + 1 and m + 1 <= ell - 1: fg[i_fg] += ( coefficient * np.sqrt(2 * ell - 1) * sf.Wigner3j(1, ell - 1, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell - 1, ell, -1, m + 1, -m) * f[sf.LM_index(1, -1, 0)] * g[sf.LM_index(ell - 1, m + 1, ellmin_g)]) if m >= -ell + 1 and m <= ell - 1: fg[i_fg] += ( coefficient * np.sqrt(2 * ell - 1) * sf.Wigner3j(1, ell - 1, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell - 1, ell, 0, m, -m) * f[sf.LM_index(1, 0, 0)] * g[sf.LM_index(ell - 1, m, ellmin_g)]) if m - 1 >= -ell + 1 and m - 1 <= ell - 1: fg[i_fg] += ( coefficient * np.sqrt(2 * ell - 1) * sf.Wigner3j(1, ell - 1, ell, 0, s_g, -s_g) * sf.Wigner3j(1, ell - 1, ell, 1, m - 1, -m) * f[sf.LM_index(1, 1, 0)] * g[sf.LM_index(ell - 1, m - 1, ellmin_g)]) return fg, ellmin_fg, ellmax_fg, s_fg
def test_SWSH_multiplication_formula(multiplication_function): """Test formula for multiplication of SWSHs Much of the analysis is based on the formula s1Yl1m1 * s2Yl2m2 = sum([ s3Yl3m3 * (-1)**(l1+l2+l3+s3+m3) * sqrt((2*l1+1)*(2*l2+1)*(2*l3+1)/(4*pi)) * Wigner3j(l1, l2, l3, s1, s2, -s3) * Wigner3j(l1, l2, l3, m1, m2, -m3) for s3 in [s1+s2] for l3 in range(abs(l1-l2), l1+l2+1) for m3 in [m1+m2] ]) This test evaluates each side of this formula, and compares the values at all collocation points on the sphere. This is tested for each possible value of (s1, l1, m1, s2, l2, m2) up to l1=4 and l2=4 [the number of items to test scales very rapidly with ell], and tested again for each (0, 1, m1, s2, l2, m2) up to l2=8. """ atol = 2e-15 rtol = 2e-15 ell_max = 4 for ell1 in range(ell_max + 1): for s1 in range(-ell1, ell1 + 1): for m1 in range(-ell1, ell1 + 1): for ell2 in range(ell_max + 1): for s2 in range(-ell2, ell2 + 1): for m2 in range(-ell2, ell2 + 1): swsh1 = np.zeros(sf.LM_total_size(0, ell_max), dtype=np.complex) swsh1[sf.LM_index(ell1, m1, 0)] = 1.0 swsh2 = np.zeros(sf.LM_total_size(0, ell_max), dtype=np.complex) swsh2[sf.LM_index(ell2, m2, 0)] = 1.0 swsh3, ell_min_3, ell_max_3, s3 = multiplication_function( swsh1, 0, ell_max, s1, swsh2, 0, ell_max, s2) assert s3 == s1 + s2 assert ell_min_3 == 0 assert ell_max_3 == 2 * ell_max n_theta = 2 * ell_max_3 + 1 n_phi = n_theta swsh3_map = spinsfast.salm2map( swsh3, s3, ell_max_3, n_theta, n_phi) swsh4_map = np.zeros_like(swsh3_map) for ell4 in range(abs(ell1 - ell2), ell1 + ell2 + 1): for s4 in [s1 + s2]: for m4 in [m1 + m2]: swsh4_k = np.zeros_like(swsh3) swsh4_k[sf.LM_index(ell4, m4, 0)] = ( (-1) **(ell1 + ell2 + ell4 + s4 + m4) * np.sqrt( (2 * ell1 + 1) * (2 * ell2 + 1) * (2 * ell4 + 1) / (4 * np.pi)) * sf.Wigner3j( ell1, ell2, ell4, s1, s2, -s4) * sf.Wigner3j( ell1, ell2, ell4, m1, m2, -m4)) swsh4_map[:] += spinsfast.salm2map( swsh4_k, s4, ell_max_3, n_theta, n_phi) assert np.allclose(swsh3_map, swsh4_map, atol=atol, rtol=rtol) atol = 8e-15 rtol = 8e-15 ell_max = 8 for ell1 in [1]: for s1 in [0]: for m1 in [-1, 0, 1]: for ell2 in range(ell_max + 1): for s2 in range(-ell2, ell2 + 1): for m2 in range(-ell2, ell2 + 1): swsh1 = np.zeros(sf.LM_total_size(0, ell_max), dtype=np.complex) swsh1[sf.LM_index(ell1, m1, 0)] = 1.0 swsh2 = np.zeros(sf.LM_total_size(0, ell_max), dtype=np.complex) swsh2[sf.LM_index(ell2, m2, 0)] = 1.0 swsh3, ell_min_3, ell_max_3, s3 = multiplication_function( swsh1, 0, ell_max, s1, swsh2, 0, ell_max, s2) assert s3 == s1 + s2 assert ell_min_3 == 0 assert ell_max_3 == 2 * ell_max n_theta = 2 * ell_max_3 + 1 n_phi = n_theta swsh3_map = spinsfast.salm2map( swsh3, s3, ell_max_3, n_theta, n_phi) swsh4_map = np.zeros_like(swsh3_map) for ell4 in [ell2 - 1, ell2, ell2 + 1]: if ell4 < 0: continue swsh4_k = np.zeros_like(swsh3) # swsh4_k[sf.LM_index(ell4, m1+m2, 0)] = ( # (-1)**(1+ell2+ell4+s2+m1+m2) # * np.sqrt(3*(2*ell2+1)*(2*ell4+1)/(4*np.pi)) # * sf.Wigner3j(1, ell2, ell4, 0, s2, -s2) # * sf.Wigner3j(1, ell2, ell4, m1, m2, -m1-m2) # ) # swsh4_map[:] += ( # spinsfast.salm2map(swsh4_k, s2, ell_max_3, n_theta, n_phi) # ) swsh4_k[sf.LM_index(ell4, m1 + m2, 0)] = ( (-1)**(ell2 + ell4 + m1) * np.sqrt( (2 * ell4 + 1)) * sf.Wigner3j(1, ell2, ell4, 0, s2, -s2) * sf.Wigner3j(1, ell2, ell4, m1, m2, -m1 - m2)) swsh4_map[:] += ( (-1)**(s2 + m2 + 1) * np.sqrt(3 * (2 * ell2 + 1) / (4 * np.pi)) * spinsfast.salm2map(swsh4_k, s2, ell_max_3, n_theta, n_phi)) assert np.allclose(swsh3_map, swsh4_map, atol=atol, rtol=rtol), np.max( np.abs(swsh3_map - swsh4_map))
def test_Wigner3j_properties(): assert abs(sf.Wigner3j(2, 6, 4, 0, 0, 0) - 0.1869893980016914) < precision_Wigner3j ## The following test various symmetries and other properties fo ## the Wigner 3-j symbols j_max = 8 for j1 in range(j_max + 1): for j2 in range(j_max + 1): for j3 in range(j_max + 1): # Selection rule if ((j1 + j2 + j3) % 2 != 0): assert abs(sf.Wigner3j(j1, j2, j3, 0, 0, 0)) < precision_Wigner3j for m1 in range(-j1, j1 + 1): for m2 in range(-j2, j2 + 1): # Selection rule if abs(j1 - j2) > j3 or j1 + j2 < j3: assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2)) < precision_Wigner3j # Test even permutations assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - sf.Wigner3j(j2, j3, j1, m2, -m1 - m2, m1)) < precision_Wigner3j assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - sf.Wigner3j(j2, j3, j1, m2, -m1 - m2, m1)) < precision_Wigner3j assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - sf.Wigner3j(j3, j1, j2, -m1 - m2, m1, m2)) < precision_Wigner3j # Test odd permutations assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - (-1.)**(j1 + j2 + j3) * sf.Wigner3j(j2, j1, j3, m2, m1, -m1 - m2) ) < precision_Wigner3j assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - (-1.)**(j1 + j2 + j3) * sf.Wigner3j(j1, j3, j2, m1, -m1 - m2, m2) ) < precision_Wigner3j # Test sign change assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - (-1.)**(j1 + j2 + j3) * sf.Wigner3j(j1, j2, j3, -m1, -m2, m1 + m2) ) < precision_Wigner3j # Regge symmetries (skip for non-integer values) if ((j2 + j3 - m1) % 2 == 0): assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - sf.Wigner3j(j1, (j2 + j3 - m1) // 2, (j2 + j3 + m1) // 2, j3 - j2, (j2 - j3 - m1) // 2 + m1 + m2, (j2 - j3 + m1) // 2 - m1 - m2)) < precision_Wigner3j if ((j2 + j3 - m1) % 2 == 0): assert abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - sf.Wigner3j(j1, (j2 + j3 - m1) // 2, (j2 + j3 + m1) // 2, j3 - j2, (j2 - j3 - m1) // 2 + m1 + m2, (j2 - j3 + m1) // 2 - m1 - m2)) < precision_Wigner3j if ((j2 + j3 + m1) % 2 == 0 and (j1 + j3 + m2) % 2 == 0 and (j1 + j2 - m1 - m2) % 2 == 0): assert (abs( sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) - (-1.)**(j1 + j2 + j3) * sf.Wigner3j( (j2 + j3 + m1) // 2, (j1 + j3 + m2) // 2, (j1 + j2 - m1 - m2) // 2, j1 - (j2 + j3 - m1) // 2, j2 - (j1 + j3 - m2) // 2, j3 - (j1 + j2 + m1 + m2) // 2)) < precision_Wigner3j)