def test_trivial_multiplication(multiplication_function): """Test 1*g and g*1 We test trivial multiplication by 1. The modes of the `1` function are just sqrt(4*pi) for the (ell,m)=(0,0) mode, and 0 for everything else. Even though this can be described by just the first element, we test this for each ellmax_f up to 8 just to make sure the multiplication function works as expected. This is multiplied by a function with random values input for its modes. """ np.random.seed(1234) atol = 2e-15 rtol = 2e-15 ellmax_g = 8 for ellmax_f in range(1, ellmax_g): f = np.zeros(sf.LM_total_size(0, ellmax_f), dtype=np.complex) f[0] = np.sqrt(4*np.pi) ellmin_f = 0 s_f = 0 ellmin_g = 0 for s_g in range(1-ellmax_g, ellmax_g): i_max = sf.LM_index(ellmax_g, ellmax_g, ellmin_g) g = np.random.rand(sf.LM_total_size(0, ellmax_g)) + 1j*np.random.rand(sf.LM_total_size(0, ellmax_g)) g[:sf.LM_total_size(0, abs(s_g)-1)+1] = 0.0 fg, ellmin_fg, ellmax_fg, s_fg = multiplication_function(f, ellmin_f, ellmax_f, s_f, g, ellmin_g, ellmax_g, s_g) assert s_fg == s_f + s_g assert ellmin_fg == 0 assert ellmax_fg == ellmax_f + ellmax_g assert np.allclose(fg[:i_max+1], g[:i_max+1], atol=atol, rtol=rtol) gf, ellmin_gf, ellmax_gf, s_gf = multiplication_function(g, ellmin_g, ellmax_g, s_g, f, ellmin_f, ellmax_f, s_f) assert s_gf == s_g + s_f assert ellmin_gf == 0 assert ellmax_gf == ellmax_g + ellmax_f assert np.allclose(gf[:i_max+1], g[:i_max+1], atol=atol, rtol=rtol)
def to_modes(self, ell_max=None, ell_min=None): """Transform to modes of a spin-weighted spherical harmonic expansion Parameters ---------- self : WaveformGrid object This is the object to be transformed to SWSH modes ell_max : int, optional The largest ell value to include in the output data. Default value is deduced from n_theta and n_phi. ell_min : int, optional The smallest ell value to include in the output data. Default value is abs(spin_weight). """ s = SpinWeights[self.dataType] if ell_max is None: ell_max = int((max(self.n_theta, self.n_phi) - 1) // 2) if ell_min is None: ell_min = abs(s) if not isinstance(ell_max, numbers.Integral) or ell_max < 0: raise ValueError(f"Input `ell_max` should be a nonnegative integer; got `{ell_max}`.") if not isinstance(ell_min, numbers.Integral) or ell_min < 0 or ell_min > ell_max: raise ValueError(f"Input `ell_min` should be an integer between 0 and {ell_max}; got `{ell_min}`.") final_dim = int(np.prod(self.data.shape[2:])) old_data = self.data.reshape((self.n_times, self.n_theta, self.n_phi, final_dim)) new_data = np.empty((self.n_times, sf.LM_total_size(ell_min, ell_max), final_dim), dtype=complex) # Note that spinsfast returns all modes, including ell<abs(s). So we just chop those off for i_time in range(self.n_times): for i_final in range(final_dim): new_data[i_time, :, i_final] = spinsfast.map2salm(old_data[i_time, :, :, i_final], s, ell_max)[ sf.LM_index(ell_min, -ell_min, 0) : ] new_data = new_data.reshape((self.n_times, sf.LM_total_size(ell_min, ell_max)) + self.data.shape[2:]) # old_data = self.data.reshape((self.n_times, self.n_theta, self.n_phi)+self.data.shape[2:]) # new_data = np.empty((self.n_times, sf.LM_total_size(ell_min, ell_max))+self.data.shape[2:], dtype=complex) # # Note that spinsfast returns all modes, including ell<abs(s). So we just chop those off # for i_time in range(self.n_times): # new_data[i_time, :] = spinsfast.map2salm(old_data[i_time, :, :], s, ell_max)\ # [sf.LM_index(ell_min, -ell_min, 0):] m = WaveformModes( t=self.t, data=new_data, history=self.history, ell_min=ell_min, ell_max=ell_max, frameType=self.frameType, dataType=self.dataType, r_is_scaled_out=self.r_is_scaled_out, m_is_scaled_out=self.m_is_scaled_out, constructor_statement=f"{self}.to_modes({ell_max})", ) return m
def kerr_schild(mass, spin, ell_max=8): psi2 = np.zeros(sf.LM_total_size(0, ell_max), dtype=complex) psi1 = np.zeros(sf.LM_total_size(0, ell_max), dtype=complex) psi0 = np.zeros(sf.LM_total_size(0, ell_max), dtype=complex) # In the Moreschi-Boyle convention psi2[0] = -sf.constant_as_ell_0_mode(mass) psi1[2] = -np.sqrt(2) * (3j * spin / 2) * (np.sqrt((8 / 3) * np.pi)) psi0[6] = 2 * (3 * spin ** 2 / mass / 2) * (np.sqrt((32 / 15) * np.pi)) return psi2, psi1, psi0
def test_eth_derivation(eth, spin_weight_of_eth): """Ensure that the various `eth` operators are derivations -- i.e., they obey the Leibniz product law Given two spin-weighted functions `f` and `g`, we need to test that eth(f * g) = eth(f) * g + f * eth(g) This test generates a set of random modes with equal power for `f` and `g` (though more realistic functions can be expected to have exponentially decaying mode amplitudes). Because of the large power in high-ell modes, we need to double the number of modes in the representation of their product, which is why we use n_theta = n_phi = 4 * ell_max + 1 These `f` and `g` functions must be transformed to the physical-space representation, multiplied there, the product transformed back to spectral space, the eth operator evaluated, and then transformed back again to physical space for comparison. We test both the Newman-Penrose and Geroch-Held-Penrose versions of eth, as well as their conjugates. """ import spinsfast ell_max = 16 n_modes = sf.LM_total_size(0, ell_max) n_theta = n_phi = 4 * ell_max + 1 for s1 in range(-2, 2 + 1): for s2 in range(-s1, s1 + 1): np.random.seed(1234) ell_min1 = abs(s1) ell_min2 = abs(s2) f = np.random.rand(n_modes) + 1j * np.random.rand(n_modes) f[:sf.LM_total_size(0, ell_min1 - 1)] = 0j f_j_k = spinsfast.salm2map(f, s1, ell_max, n_theta, n_phi) g = np.random.rand(n_modes) + 1j * np.random.rand(n_modes) g[:sf.LM_total_size(0, ell_min2 - 1)] = 0j g_j_k = spinsfast.salm2map(g, s2, ell_max, n_theta, n_phi) fg_j_k = f_j_k * g_j_k fg = spinsfast.map2salm(fg_j_k, s1 + s2, 2 * ell_max) ethf = eth(f, s1, ell_min=0) ethg = eth(g, s2, ell_min=0) ethfg = eth(fg, s1 + s2, ell_min=0) ethf_j_k = spinsfast.salm2map(ethf, s1 + spin_weight_of_eth, ell_max, n_theta, n_phi) ethg_j_k = spinsfast.salm2map(ethg, s2 + spin_weight_of_eth, ell_max, n_theta, n_phi) ethfg_j_k = spinsfast.salm2map(ethfg, s1 + s2 + spin_weight_of_eth, 2 * ell_max, n_theta, n_phi) assert np.allclose(ethfg_j_k, ethf_j_k * g_j_k + f_j_k * ethg_j_k, rtol=1e-10, atol=1e-10)
def test_modes_squared_angular_momenta(): tolerance = 1e-13 np.random.seed(1234) L2 = sf.Modes.Lsquared Lz = sf.Modes.Lz Lp = sf.Modes.Lplus Lm = sf.Modes.Lminus R2 = sf.Modes.Rsquared Rz = sf.Modes.Rz Rp = sf.Modes.Rplus Rm = sf.Modes.Rminus for s in range(-2, 2 + 1): ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) # Test L^2 = 0.5(L+L- + L-L+) + LzLz m1 = L2(m) m2 = 0.5 * (Lp(Lm(m)) + Lm(Lp(m))) + Lz(Lz(m)) assert np.allclose(m1, m2, rtol=tolerance, atol=tolerance) # Test R^2 = 0.5(R+R- + R-R+) + RzRz m1 = R2(m) m2 = 0.5 * (Rp(Rm(m)) + Rm(Rp(m))) + Rz(Rz(m)) assert np.allclose(m1, m2, rtol=tolerance, atol=tolerance) # Test L^2 = R^2 m1 = L2(m) m2 = R2(m) assert np.allclose(m1, m2, rtol=tolerance, atol=tolerance)
def test_modes_conjugate(): tolerance = 1e-15 np.random.seed(1234) for inplace in [False, True]: for s in range(-2, 2 + 1): ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) g = m.grid() s = m.s ell_min = m.ell_min ell_max = m.ell_max shape = m.shape mbar = m.conjugate(inplace) gbar = mbar.grid() assert s == -mbar.s assert ell_min == mbar.ell_min assert ell_max == mbar.ell_max assert shape == mbar.shape assert np.allclose(g, np.conjugate(gbar), rtol=tolerance, atol=tolerance)
def single_mode_proportional_to_time(**kwargs): """Return WaveformModes object a single nonzero mode, proportional to time The waveform output by this function will have just one nonzero mode. The behavior of that mode will be particularly simple; it will just be proportional to time. Parameters ---------- s : int, optional Spin weight of the waveform field. Default is -2. ell, m : int, optional The (ell, m) values of the nonzero mode in the returned waveform. Default value is (abs(s), -abs(s)). ell_min, ell_max : int, optional Smallest and largest ell values present in the output. Default values are abs(s) and 8. data_type : int, optional Default value is whichever psi_n corresponds to the input spin. It is important to choose these, rather than `h` or `sigma` for the analytical solution to translations, which doesn't account for the direct contribution of supertranslations (as opposed to the indirect contribution, which involves moving points around). t_0, t_1 : float, optional Beginning and end of time. Default values are -20. and 20. dt : float, optional Time step. Default value is 0.1. beta : complex, optional Constant of proportionality such that nonzero mode is beta*t. Default is 1. """ 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 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 read_from_h5(file_name, **kwargs): """Read data from an H5 file in LVC format""" import re import h5py from scipy.interpolate import InterpolatedUnivariateSpline as Spline phase_re = re.compile('phase_l(?P<ell>.*)_m(?P<m>.*)') amp_re = re.compile('amp_l(?P<ell>.*)_m(?P<m>.*)') with h5py.File(file_name) as f: t = f['NRtimes'][:] ell_m = np.array([[int(match['ell']), int(match['m'])] for key in f for match in [phase_re.match(key)] if match]) ell_min = np.min(ell_m[:, 0]) ell_max = np.max(ell_m[:, 0]) data = np.empty((t.size, sf.LM_total_size(ell_min, ell_max)), dtype=complex) for ell in range(ell_min, ell_max+1): for m in range(-ell, ell+1): amp = Spline(f['amp_l{0}_m{1}/X'.format(ell, m)][:], f['amp_l{0}_m{1}/Y'.format(ell, m)][:], k=int(f['amp_l{0}_m{1}/deg'.format(ell, m)][()]))(t) phase = Spline(f['phase_l{0}_m{1}/X'.format(ell, m)][:], f['phase_l{0}_m{1}/Y'.format(ell, m)][:], k=int(f['phase_l{0}_m{1}/deg'.format(ell, m)][()]))(t) data[:, sf.LM_index(ell, m, ell_min)] = amp * np.exp(1j * phase) if 'auxiliary-info' in f and 'history.txt' in f['auxiliary-info']: history = ("### " + f['auxiliary-info/history.txt'][()].decode().replace('\n', '\n### ')).split('\n') else: history = [""] constructor_statement = "scri.LVC.read_from_h5('{0}')".format(file_name) w = WaveformModes(t=t, data=data, ell_min=ell_min, ell_max=ell_max, frameType=Inertial, dataType=h, history=history, constructor_statement=constructor_statement, r_is_scaled_out=True, m_is_scaled_out=True) return w
def single_mode_constant_rotation(**kwargs): """Return WaveformModes object a single nonzero mode, with phase proportional to time The waveform output by this function will have just one nonzero mode. The behavior of that mode will be fairly simple; it will be given by exp(i*omega*t). Note that omega can be complex, which gives damping. Parameters ---------- s : int, optional Spin weight of the waveform field. Default is -2. ell, m : int, optional The (ell, m) values of the nonzero mode in the returned waveform. Default value is (abs(s), -abs(s)). ell_min, ell_max : int, optional Smallest and largest ell values present in the output. Default values are abs(s) and 8. data_type : int, optional Default value is whichever psi_n corresponds to the input spin. It is important to choose these, rather than `h` or `sigma` for the analytical solution to translations, which doesn't account for the direct contribution of supertranslations (as opposed to the indirect contribution, which involves moving points around). t_0, t_1 : float, optional Beginning and end of time. Default values are -20. and 20. dt : float, optional Time step. Default value is 0.1. omega : complex, optional Constant of proportionality such that nonzero mode is exp(i*omega*t). Note that this can be complex, which implies damping. Default is 0.5. """ 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. / 10.) t = np.arange(t_0, t_1 + dt, dt) n_times = t.size omega = complex(kwargs.pop('omega', 0.5)) data = np.zeros((n_times, sf.LM_total_size(ell_min, ell_max)), dtype=complex) data[:, sf.LM_index(ell, m, ell_min)] = np.exp(1j * omega * t) if kwargs: import pprint warnings.warn("\nUnused kwargs passed to this function:\n{0}".format( 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 construct_and_validate(modifier, validator, ell_max=8): time = np.linspace(-100, 100, num=2001) sigma, sigmadot, sigmaddot, psi2, psi1, psi0 = np.zeros( (6, sf.LM_total_size(0, ell_max)), dtype=complex) modifier(sigma, sigmadot, sigmaddot, psi2, psi1, psi0) abd = ABD.from_initial_values(time, ell_max, sigma, sigmadot, sigmaddot, psi2, psi1, psi0) validator(abd) return True
def test_supertranslation_inverses(): w1 = samples.random_waveform_proportional_to_time(rotating=False) ell_max = 4 for ellpp, mpp in sf.LM_range(0, ell_max): supertranslation = np.zeros((sf.LM_total_size(0, ell_max), ), dtype=complex) if mpp == 0: supertranslation[sf.LM_index(ellpp, mpp, 0)] = 1.0 elif mpp < 0: supertranslation[sf.LM_index(ellpp, mpp, 0)] = 1.0 supertranslation[sf.LM_index(ellpp, -mpp, 0)] = (-1.0)**mpp elif mpp > 0: supertranslation[sf.LM_index(ellpp, mpp, 0)] = 1.0j supertranslation[sf.LM_index(ellpp, -mpp, 0)] = (-1.0)**mpp * -1.0j max_displacement = abs( spinsfast.salm2map(supertranslation, 0, ell_max, 4 * ell_max + 1, 4 * ell_max + 1)).max() w2 = copy.deepcopy(w1) w2 = w2.transform(supertranslation=supertranslation) w2 = w2.transform(supertranslation=-supertranslation) i1A = np.argmin(abs(w1.t - (w1.t[0] + 3 * max_displacement))) i1B = np.argmin(abs(w1.t - (w1.t[-1] - 3 * max_displacement))) i2A = np.argmin(abs(w2.t - w1.t[i1A])) i2B = np.argmin(abs(w2.t - w1.t[i1B])) try: assert np.allclose(w1.t[i1A:i1B + 1], w2.t[i2A:i2B + 1], rtol=0.0, atol=1e-15), ( w1.t[i1A], w2.t[i2A], w1.t[i1B], w2.t[i2B], w1.t[i1A:i1B + 1].shape, w2.t[i2A:i2B + 1].shape, ) except ValueError: print("Indices:\n\t", i1A, i1B, i2A, i2B) print("Times:\n\t", w1.t[i1A], w1.t[i1B], w2.t[i2A], w2.t[i2B]) raise data1 = w1.data[i1A:i1B + 1] data2 = w2.data[i2A:i2B + 1] try: assert np.allclose(data1, data2, rtol=5e-10, atol=5e-14), [ abs(data1 - data2).max(), data1.ravel()[np.argmax(abs(data1 - data2))], data2.ravel()[np.argmax(abs(data1 - data2))], np.unravel_index(np.argmax(abs(data1 - data2)), data1.shape), ] # list(sf.LM_range(0, ell_max)[np.unravel_index(np.argmax(abs(data1-data2)), # data1.shape)[1]])]) except: print("Indices:\n\t", i1A, i1B, i2A, i2B) print("Times:\n\t", w1.t[i1A], w1.t[i1B], w2.t[i2A], w2.t[i2B]) raise
def test_modes_ufuncs(): for s1 in range(-2, 2 + 1): ell_min1 = abs(s1) ell_max1 = 8 a1 = np.random.rand(11, sf.LM_total_size(ell_min1, ell_max1) * 2).view(complex) m1 = sf.Modes(a1, spin_weight=s1, ell_min=ell_min1, ell_max=ell_max1) positivem1 = +m1 assert np.array_equal(m1.view(np.ndarray), positivem1.view(np.ndarray)) negativem1 = -m1 assert np.array_equal(-(m1.view(np.ndarray)), negativem1.view(np.ndarray))
def test_rotations_of_each_mode_individually(Rs): ell_min = 0 ell_max = 8 # sf.ell_max is just too much; this test is too slow, and ell=8 should be fine R_basis = Rs Ds = np.empty((len(Rs), sf.LMpM_total_size(ell_min, ell_max)), dtype=complex) for i, R in enumerate(Rs): Ds[i, :] = sf.Wigner_D_matrices(R, ell_min, ell_max) for ell in range(ell_max + 1): first_zeros = np.zeros((len(Rs), sf.LM_total_size(ell_min, ell - 1)), dtype=complex) later_zeros = np.zeros((len(Rs), sf.LM_total_size(ell + 1, ell_max)), dtype=complex) for Mp in range(-ell, ell): W_in = delta_waveform(ell, Mp, begin=-10.0, end=100.0, n_times=len(Rs), ell_min=ell_min, ell_max=ell_max) # Now, the modes are f^{\ell,m[} = \delta^{\ell,mp}_{L,Mp} assert W_in.ensure_validity(alter=False) W_out = scri.WaveformModes(W_in) W_out.rotate_decomposition_basis(Rs) assert W_out.ensure_validity(alter=False) assert np.array_equal(W_out.t, W_in.t) assert np.max(np.abs(W_out.frame - R_basis)) == 0.0 i_D0 = sf.LMpM_index(ell, Mp, -ell, ell_min) assert np.array_equal( W_out.data[:, :sf.LM_total_size(ell_min, ell - 1)], first_zeros) if ell < ell_max: assert np.array_equal( W_out.data[:, sf.LM_total_size(ell_min, ell - 1):-sf. LM_total_size(ell + 1, ell_max)], Ds[:, i_D0:i_D0 + (2 * ell + 1)], ) assert np.array_equal( W_out.data[:, -sf.LM_total_size(ell + 1, ell_max):], later_zeros) else: assert np.array_equal( W_out.data[:, sf.LM_total_size(ell_min, ell - 1):], Ds[:, i_D0:i_D0 + (2 * ell + 1)]) assert W_out.ell_min == W_in.ell_min assert W_out.ell_max == W_in.ell_max assert np.array_equal(W_out.LM, W_in.LM) for h_in, h_out in zip(W_in.history, W_out.history[:-1]): assert h_in == h_out.replace( type(W_out).__name__ + str(W_out.num), type(W_in).__name__ + str(W_in.num)) assert W_out.frameType == W_in.frameType assert W_out.dataType == W_in.dataType assert W_out.r_is_scaled_out == W_in.r_is_scaled_out assert W_out.m_is_scaled_out == W_in.m_is_scaled_out assert W_out.num != W_in.num
def test_modes_norm(): tolerance = 1e-15 np.random.seed(1234) for s in range(-2, 2 + 1): ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) mmbar = m.multiply(m.conjugate()) norm = np.sqrt(2 * math.sqrt(np.pi) * mmbar[..., 0].view(np.ndarray).real) assert np.allclose(norm, m.norm(), rtol=tolerance, atol=tolerance)
def read_from_h5(file_name, **kwargs): """Read data from an H5 file in LVC format""" import re import h5py from scipy.interpolate import InterpolatedUnivariateSpline as Spline phase_re = re.compile("phase_l(?P<ell>.*)_m(?P<m>.*)") amp_re = re.compile("amp_l(?P<ell>.*)_m(?P<m>.*)") with h5py.File(file_name, "r") as f: t = f["NRtimes"][:] ell_m = np.array([[int(match["ell"]), int(match["m"])] for key in f for match in [phase_re.match(key)] if match]) ell_min = np.min(ell_m[:, 0]) ell_max = np.max(ell_m[:, 0]) data = np.empty((t.size, sf.LM_total_size(ell_min, ell_max)), dtype=complex) for ell in range(ell_min, ell_max + 1): for m in range(-ell, ell + 1): amp = Spline(f[f"amp_l{ell}_m{m}/X"][:], f[f"amp_l{ell}_m{m}/Y"][:], k=int(f[f"amp_l{ell}_m{m}/deg"][()]))(t) phase = Spline(f[f"phase_l{ell}_m{m}/X"][:], f[f"phase_l{ell}_m{m}/Y"][:], k=int(f[f"phase_l{ell}_m{m}/deg"][()]))(t) data[:, sf.LM_index(ell, m, ell_min)] = amp * np.exp(1j * phase) if "auxiliary-info" in f and "history.txt" in f["auxiliary-info"]: history = ("### " + f["auxiliary-info/history.txt"][()].decode().replace( "\n", "\n### ")).split("\n") else: history = [""] constructor_statement = f"scri.LVC.read_from_h5('{file_name}')" w = WaveformModes( t=t, data=data, ell_min=ell_min, ell_max=ell_max, frameType=Inertial, dataType=h, history=history, constructor_statement=constructor_statement, r_is_scaled_out=True, m_is_scaled_out=True, ) return w
def test_modes_copying_and_pickling(copier): for s in range(-2, 2 + 1): ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) c = copier(m) assert m is not c assert np.array_equal(c, m) assert isinstance(c, type(m)) assert c.s == m.s assert c.ell_min == m.ell_min assert c.ell_max == m.ell_max
def check_modes(modes, nonzero_ℓm): import numpy as np import spherical_functions as sf non_zero_indices = np.array([modes.index(ℓ, m) for ℓ, m in nonzero_ℓm], dtype=int) zero_indices = np.array(list( set(range(sf.LM_total_size(0, modes.ell_max))) - set(non_zero_indices)), dtype=int) assert not np.any( modes[..., zero_indices]), f"nonzero values among indices {zero_indices}" for non_zero_index in non_zero_indices: assert np.any(modes[ ..., non_zero_index]), f"no nonzero values at index {non_zero_index}"
def test_modes_grid(): for s in range(-2, 2 + 1): ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) n = 2 * ell_max + 1 for n_theta, n_phi in [[None, None], [n, None], [None, n], [n, n], [n + 1, n], [n, n + 1], [n + 1, n + 1]]: g = m.grid(n_theta=n_theta, n_phi=n_phi) assert g.dtype == np.complex assert g.shape[:-2] == a.shape[:-1] if n_theta is None: n_theta = n if n_phi is None: n_phi = n assert g.shape[-2:] == (n_theta, n_phi)
def test_hyper_translation(): """Compare code-transformed waveform to analytically transformed waveform""" print("") ell_max = 4 for s in range(-2, 2+1): for ell in range(abs(s), ell_max+1): for m in range(-ell, ell+1): print("\tWorking on spin s =", s, ", ell =", ell, ", m =", m) for ellpp, mpp in sf.LM_range(2, ell_max): supertranslation = np.zeros((sf.LM_total_size(0, ell_max),), dtype=complex) if mpp == 0: supertranslation[sf.LM_index(ellpp, mpp, 0)] = 1.0 elif mpp < 0: supertranslation[sf.LM_index(ellpp, mpp, 0)] = 1.0 supertranslation[sf.LM_index(ellpp, -mpp, 0)] = (-1.0)**mpp elif mpp > 0: supertranslation[sf.LM_index(ellpp, mpp, 0)] = 1.0j supertranslation[sf.LM_index(ellpp, -mpp, 0)] = (-1.0)**mpp * -1.0j max_displacement = abs(spinsfast.salm2map(supertranslation, 0, ell_max, 4*ell_max+1, 4*ell_max+1)).max() w_m1 = (samples.single_mode_proportional_to_time(s=s, ell=ell, m=m) .transform(supertranslation=supertranslation)) w_m2 = samples.single_mode_proportional_to_time_supertranslated(s=s, ell=ell, m=m, supertranslation=supertranslation) i1A = np.argmin(abs(w_m1.t-(w_m1.t[0]+2*max_displacement))) i1B = np.argmin(abs(w_m1.t-(w_m1.t[-1]-2*max_displacement))) i2A = np.argmin(abs(w_m2.t-w_m1.t[i1A])) i2B = np.argmin(abs(w_m2.t-w_m1.t[i1B])) assert np.allclose(w_m1.t[i1A:i1B+1], w_m2.t[i2A:i2B+1], rtol=0.0, atol=1e-16), \ (w_m1.t[i1A], w_m2.t[i2A], w_m1.t[i1B], w_m2.t[i2B], w_m1.t[i1A:i1B+1].shape, w_m2.t[i2A:i2B+1].shape) data1 = w_m1.data[i1A:i1B+1] data2 = w_m2.data[i2A:i2B+1] assert np.allclose(data1, data2, rtol=0.0, atol=5e-14), \ ([s, ell, m], supertranslation, [abs(data1-data2).max(), data1.ravel()[np.argmax(abs(data1-data2))], data2.ravel()[np.argmax(abs(data1-data2))]], [np.unravel_index(np.argmax(abs(data1-data2)), data1.shape)[0], list(sf.LM_range(abs(s), ell_max)[np.unravel_index(np.argmax(abs(data1-data2)), data1.shape)[1]])])
def test_modes_real(): tolerance = 1e-14 np.random.seed(1234) for inplace in [False, True]: s = 0 ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) # Test success with spin_weight==0 m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) g = m.grid() s = m.s ell_min = m.ell_min ell_max = m.ell_max shape = m.shape mreal = m._real_func(inplace) greal = mreal.grid() assert s == mreal.s assert ell_min == mreal.ell_min assert ell_max == mreal.ell_max assert shape == mreal.shape assert np.allclose(greal, np.real(greal) + 0.0j, rtol=tolerance, atol=tolerance) assert np.allclose(np.real(g), np.real(greal), rtol=tolerance, atol=tolerance) assert np.allclose(np.zeros_like(g, dtype=float), np.imag(greal), rtol=tolerance, atol=tolerance) # Test failure with s!=0 for s in [-3, -2, -1, 1, 2, 3]: m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) with pytest.raises(ValueError): mreal = m._real_func(inplace)
def test_modes_imag(): tolerance = 1e-14 np.random.seed(1234) for inplace in [False, True]: s = 0 ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) # Test success with spin_weight==0 m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) g = m.grid() s = m.s ell_min = m.ell_min ell_max = m.ell_max shape = m.shape mimag = m._imag_func(inplace) gimag = mimag.grid() assert s == mimag.s assert ell_min == mimag.ell_min assert ell_max == mimag.ell_max assert shape == mimag.shape assert np.allclose(gimag, np.real(gimag), rtol=tolerance, atol=tolerance) # gimag is purely real assert np.allclose(np.array(np.imag(g.ndarray), dtype=complex), gimag.ndarray, rtol=tolerance, atol=tolerance) # imag(g) == gimag assert np.allclose(np.imag(gimag.ndarray), np.zeros_like(g.ndarray, dtype=float), rtol=tolerance, atol=tolerance) # imag(gimag) == 0 # Test failure with s!=0 for s in [-3, -2, -1, 1, 2, 3]: m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) with pytest.raises(ValueError): mimag = m._imag_func(inplace)
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 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, )
mass_ratio = 1.0 / mass_ratio s = -2 ell_min = abs(s) data_type = scri.h nu = mass_ratio / (1 + mass_ratio)**2 t = np.arange(t_0, t_1 + 0.99 * dt, dt) t_merger = t_1 - 100.0 i_merger = np.argmin(abs(t - t_merger)) if i_merger < 20: raise ValueError( f"Insufficient space between initial time (t={t_merger}) and merger (t={t_0})." ) n_times = t.size data = np.zeros((n_times, sf.LM_total_size(ell_min, ell_max)), dtype=complex) # Get a rough approximation to the phasing through merger tau = nu * (t_merger - t) / 5 with warnings.catch_warnings( ): # phi and omega will have NaNs after t_merger for now warnings.simplefilter("ignore") phi = -4 * tau**(5 / 8) omega = (nu / 2) * tau**(-3 / 8) # Now, transition omega smoothly up to a constant value of 0.25 omega_transition_width = 5.0 i1 = np.argmin(np.abs(omega[~np.isnan(omega)] - 0.25)) i0 = np.argmin(np.abs(t - (t[i1] - omega_transition_width))) transition = transition_function(t, t[i0], t[i1])
def test_modes_multiplication(): tolerance = 1e-13 np.random.seed(1234) # Test without truncation for i_mul, mul in enumerate([ np.multiply, lambda a, b: a.multiply(b), lambda a, b: a.multiply(b, truncator=max) ]): for s1 in range(-2, 2 + 1): ell_min1 = abs(s1) ell_max1 = 8 a1 = np.random.rand(3, 7, sf.LM_total_size(ell_min1, ell_max1) * 2).view(complex) m1 = sf.Modes(a1, spin_weight=s1, ell_min=ell_min1, ell_max=ell_max1) # Check scalar multiplications s = np.random.rand() m1s = mul(m1, s) assert m1.s == s1 assert m1s.ell_max == m1.ell_max g1s = m1s.grid() n_theta, n_phi = g1s.shape[-2:] g1 = m1.grid(n_theta, n_phi) assert np.allclose(g1 * s, g1s, rtol=tolerance, atol=tolerance) if mul is np.multiply: sm1 = mul(s, m1) assert sm1.s == s1 assert sm1.ell_max == m1.ell_max sg1 = sm1.grid() n_theta, n_phi = sg1.shape[-2:] g1 = m1.grid(n_theta, n_phi) assert np.allclose(s * g1, sg1, rtol=tolerance, atol=tolerance) # Check scalar-array multiplications s = np.random.rand(3, 7) m1s = mul(m1, s) assert m1.s == s1 assert m1s.ell_max == m1.ell_max g1s = m1s.grid() n_theta, n_phi = g1s.shape[-2:] g1 = m1.grid(n_theta, n_phi) assert np.allclose(g1 * s, g1s, rtol=tolerance, atol=tolerance) if mul is np.multiply: sm1 = mul(s, m1) assert sm1.s == s1 assert sm1.ell_max == m1.ell_max sg1 = sm1.grid() n_theta, n_phi = sg1.shape[-2:] g1 = m1.grid(n_theta, n_phi) assert np.allclose(s * g1, sg1, rtol=tolerance, atol=tolerance) # Check spin-weighted multiplications for s2 in range(-s1, s1 + 1): ell_min2 = ell_min1 + 1 ell_max2 = ell_max1 - 1 a2 = np.random.rand(3, 7, sf.LM_total_size(ell_min2, ell_max2) * 2).view(complex) m2 = sf.Modes(a2, spin_weight=s2, ell_min=ell_min2, ell_max=ell_max2) m1m2 = mul(m1, m2) assert m1m2.s == s1 + s2 if i_mul == 2: assert m1m2.ell_max == max(m1.ell_max, m2.ell_max) else: assert m1m2.ell_max == m1.ell_max + m2.ell_max g12 = m1m2.grid() n_theta, n_phi = g12.shape[-2:] g1 = m1.grid(n_theta, n_phi) g2 = m2.grid(n_theta, n_phi) assert np.allclose(g1 * g2, g12, rtol=tolerance, atol=tolerance)
def test_LM_total_size(ell_max): for l_min in range(ell_max + 1): for l_max in range(l_min, ell_max + 1): assert sf.LM_index(l_max + 1, -(l_max + 1), l_min) == sf.LM_total_size(l_min, l_max)
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_modes_derivatives_on_grids(): # Test various SWSH-derivative expressions on grids tolerance = 2e-14 np.random.seed(1234) for s in range(-2, 2 + 1): ell_min = 0 ell_max = abs(s) + 5 zeros = lambda: np.zeros(sf.LM_total_size(ell_min, ell_max), dtype=complex) for ell in range(abs(s), ell_max + 1): for m in range(-ell, ell + 1): sYlm = sf.Modes(zeros(), spin_weight=s, ell_min=ell_min, ell_max=ell_max) sYlm[sYlm.index(ell, m)] = 1.0 g_sYlm = sYlm.grid() n_theta, n_phi = g_sYlm.shape[-2:] # Test Lsquared {s}Y{l,m} = l * (l+1) * {s}Y{l,m} L2_sYlm = sYlm.Lsquared() g_L2_sYlm = L2_sYlm.grid(n_theta, n_phi) factor = ell * (ell + 1) assert np.allclose(g_L2_sYlm, factor * g_sYlm, rtol=tolerance, atol=tolerance) # Test Lz {s}Y{l,m} = m * {s}Y{l,m} Lz_sYlm = sYlm.Lz() g_Lz_sYlm = Lz_sYlm.grid(n_theta, n_phi) factor = m assert np.allclose(g_Lz_sYlm, factor * g_sYlm, rtol=tolerance, atol=tolerance) # Test Lplus {s}Y{l,m} = sqrt((l-m)*(l+m+1)) {s}Y{l,m+1} invalid = abs(m + 1) > ell sYlmp1 = sf.Modes(zeros(), spin_weight=s, ell_min=ell_min, ell_max=ell_max) if invalid: with pytest.raises(ValueError): sYlmp1.index(ell, m + 1) else: sYlmp1[sYlmp1.index(ell, m + 1)] = 1.0 g_sYlmp1 = sYlmp1.grid(n_theta, n_phi) Lp_sYlm = sYlm.Lplus() g_Lp_sYlm = Lp_sYlm.grid(n_theta, n_phi) factor = 0.0 if invalid else math.sqrt( (ell - m) * (ell + m + 1)) assert np.allclose(g_Lp_sYlm, factor * g_sYlmp1, rtol=tolerance, atol=tolerance) # Test Lminus {s}Y{l,m} = sqrt((l+m)*(l-m+1)) * {s}Y{l,m-1} invalid = abs(m - 1) > ell sYlmm1 = sf.Modes(zeros(), spin_weight=s, ell_min=ell_min, ell_max=ell_max) if invalid: with pytest.raises(ValueError): sYlmm1.index(ell, m - 1) else: sYlmm1[sYlmm1.index(ell, m - 1)] = 1.0 g_sYlmm1 = sYlmm1.grid(n_theta, n_phi) Lm_sYlm = sYlm.Lminus() g_Lm_sYlm = Lm_sYlm.grid(n_theta, n_phi) factor = 0.0 if invalid else math.sqrt( (ell + m) * (ell - m + 1)) assert np.allclose(g_Lm_sYlm, factor * g_sYlmm1, rtol=tolerance, atol=tolerance) # Test Rsquared {s}Y{l,m} = l * (l+1) * {s}Y{l,m} R2_sYlm = sYlm.Rsquared() g_R2_sYlm = R2_sYlm.grid(n_theta, n_phi) factor = ell * (ell + 1) assert np.allclose(g_R2_sYlm, factor * g_sYlm, rtol=tolerance, atol=tolerance) # Test Rz {s}Y{l,m} = -s * {s}Y{l,m} Rz_sYlm = sYlm.Rz() g_Rz_sYlm = Rz_sYlm.grid(n_theta, n_phi) factor = -s assert np.allclose(g_Rz_sYlm, factor * g_sYlm, rtol=tolerance, atol=tolerance) # Test Rplus {s}Y{l,m} = sqrt((l+s)(l-s+1)) {s-1}Y{l,m} invalid = abs(s - 1) > ell sm1Ylm = sf.Modes(zeros(), spin_weight=s - 1, ell_min=ell_min, ell_max=ell_max) if invalid: with pytest.raises(ValueError): sm1Ylm.index(ell, m) else: sm1Ylm[sm1Ylm.index(ell, m)] = 1.0 g_sm1Ylm = sm1Ylm.grid(n_theta, n_phi) Rp_sYlm = sYlm.Rplus() g_Rp_sYlm = Rp_sYlm.grid(n_theta, n_phi) factor = 0.0 if invalid else math.sqrt( (ell + s) * (ell - s + 1)) assert np.allclose(g_Rp_sYlm, factor * g_sm1Ylm, rtol=tolerance, atol=tolerance) # Test Rminus {s}Y{l,m} = sqrt((l-s)(l+s+1)) {s+1}Y{l,m} invalid = abs(s + 1) > ell sp1Ylm = sf.Modes(zeros(), spin_weight=s + 1, ell_min=ell_min, ell_max=ell_max) if invalid: with pytest.raises(ValueError): sp1Ylm.index(ell, m) else: sp1Ylm[sp1Ylm.index(ell, m)] = 1.0 Rm_sYlm = sYlm.Rminus() g_sp1Ylm = sp1Ylm.grid(n_theta, n_phi) g_Rm_sYlm = Rm_sYlm.grid(n_theta, n_phi) factor = 0.0 if invalid else math.sqrt( (ell - s) * (ell + s + 1)) assert np.allclose(g_Rm_sYlm, factor * g_sp1Ylm, rtol=tolerance, atol=tolerance) # Test eth {s}Y{l,m} = sqrt((l-s)(l+s+1)) {s+1}Y{l,m} invalid = abs(s + 1) > ell sp1Ylm = sf.Modes(zeros(), spin_weight=s + 1, ell_min=ell_min, ell_max=ell_max) if invalid: with pytest.raises(ValueError): sp1Ylm.index(ell, m) else: sp1Ylm[sp1Ylm.index(ell, m)] = 1.0 eth_sYlm = sYlm.eth g_sp1Ylm = sp1Ylm.grid(n_theta, n_phi) g_eth_sYlm = eth_sYlm.grid(n_theta, n_phi) factor = 0.0 if invalid else math.sqrt( (ell - s) * (ell + s + 1)) assert np.allclose(g_eth_sYlm, factor * g_sp1Ylm, rtol=tolerance, atol=tolerance) # Test ethbar {s}Y{l,m} = -sqrt((l+s)(l-s+1)) {s-1}Y{l,m} invalid = abs(s - 1) > ell sm1Ylm = sf.Modes(zeros(), spin_weight=s - 1, ell_min=ell_min, ell_max=ell_max) if invalid: with pytest.raises(ValueError): sm1Ylm.index(ell, m) else: sm1Ylm[sm1Ylm.index(ell, m)] = 1.0 g_sm1Ylm = sm1Ylm.grid(n_theta, n_phi) ethbar_sYlm = sYlm.ethbar g_ethbar_sYlm = ethbar_sYlm.grid(n_theta, n_phi) factor = 0.0 if invalid else -math.sqrt( (ell + s) * (ell - s + 1)) assert np.allclose(g_ethbar_sYlm, factor * g_sm1Ylm, rtol=tolerance, atol=tolerance) # Test ethbar eth sYlm = -(l-s)(l+s+1) sYlm ethbar_eth_sYlm = sYlm.eth.ethbar g_ethbar_eth_sYlm = ethbar_eth_sYlm.grid(n_theta, n_phi) factor = 0.0 if (abs(s + 1) > ell or abs(s) > ell) else -(ell - s) * (ell + s + 1) assert np.allclose(g_ethbar_eth_sYlm, factor * g_sYlm, rtol=tolerance, atol=tolerance)
def test_first_nontrivial_multiplication(multiplication_function): """Test f*g where f is an ell=1, s=0 function We can write out the expression for f*g in the case where f is a pure ell=1 function, so we can check the multiplication function against that formula. As in `test_trivial_multiplication`, we test with multiple values of ellmax_f, and construct `g` from random values. """ 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 np.random.seed(1234) atol = 2e-15 rtol = 2e-15 ellmax_g = 8 print() for ellmax_f in range(1, ellmax_g + 1): # print(ellmax_f) f = np.zeros(sf.LM_total_size(0, ellmax_f), dtype=np.complex) f[1:4] = np.random.rand(3) + 1j * np.random.rand(3) ellmin_f = 0 s_f = 0 ellmin_g = 0 for s_g in range(1 - ellmax_g, ellmax_g): # print('\t', s_g) i_max = sf.LM_index(ellmax_g, ellmax_g, ellmin_g) g = np.random.rand(sf.LM_total_size( 0, ellmax_g)) + 1j * np.random.rand(sf.LM_total_size(0, ellmax_g)) g[:sf.LM_total_size(0, abs(s_g) - 1) + 1] = 0.0 fg, ellmin_fg, ellmax_fg, s_fg = multiplication_function( f, ellmin_f, ellmax_f, s_f, g, ellmin_g, ellmax_g, s_g) fg2, ellmin_fg2, ellmax_fg2, s_fg2 = specialized_multiplication( f, ellmin_f, ellmax_f, s_f, g, ellmin_g, ellmax_g, s_g) assert s_fg == s_f + s_g assert ellmin_fg == 0 assert ellmax_fg == ellmax_f + ellmax_g import pprint assert np.allclose( fg[:i_max + 1], fg2[:i_max + 1], atol=atol, rtol=rtol), pprint.pformat(list(fg)) + '\n\n' + pprint.pformat( list(fg2)) gf, ellmin_gf, ellmax_gf, s_gf = multiplication_function( g, ellmin_g, ellmax_g, s_g, f, ellmin_f, ellmax_f, s_f) assert s_gf == s_g + s_f assert ellmin_gf == 0 assert ellmax_gf == ellmax_g + ellmax_f assert np.allclose(gf[:i_max + 1], fg2[:i_max + 1], atol=atol, rtol=rtol)
def test_modes_derivative_commutators(): tolerance = 1e-13 np.random.seed(1234) # Note that post-fix operators are in the opposite order compared # to prefixed commutators, so we pull the post-fix operators out # as functions to make things look right. np.random.seed(1234) L2 = sf.Modes.Lsquared Lz = sf.Modes.Lz Lp = sf.Modes.Lplus Lm = sf.Modes.Lminus R2 = sf.Modes.Rsquared Rz = sf.Modes.Rz Rp = sf.Modes.Rplus Rm = sf.Modes.Rminus eth = lambda modes: modes.eth ethbar = lambda modes: modes.ethbar for s in range(-2, 2 + 1): ell_min = abs(s) ell_max = 8 a = np.random.rand(3, 7, sf.LM_total_size(ell_min, ell_max) * 2).view(complex) m = sf.Modes(a, spin_weight=s, ell_min=ell_min, ell_max=ell_max) # Test [Ri, Lj] = 0 for R in [Rz, Rp, Rm]: for L in [Lz, Lp, Lm]: assert np.max(np.abs(L(R(m)) - R(L(m)))) < tolerance # Test [L2, Lj] = 0 for L in [Lz, Lp, Lm]: assert np.max(np.abs(L2(L(m)) - L(L2(m)))) < 5 * tolerance # Test [R2, Rj] = 0 for R in [Rz, Rp, Rm]: assert np.max(np.abs(R2(R(m)) - R(R2(m)))) < 5 * tolerance # Test [Lz, Lp] = Lp assert np.allclose(Lz(Lp(m)) - Lp(Lz(m)), Lp(m), rtol=tolerance, atol=tolerance) # Test [Lz, Lm] = -Lm assert np.allclose(Lz(Lm(m)) - Lm(Lz(m)), -Lm(m), rtol=tolerance, atol=tolerance) # Test [Lp, Lm] = 2Lz assert np.allclose(Lp(Lm(m)) - Lm(Lp(m)), 2 * Lz(m), rtol=tolerance, atol=tolerance) # Test [Rz, Rp] = Rp assert np.allclose(Rz(Rp(m)) - Rp(Rz(m)), Rp(m), rtol=tolerance, atol=tolerance) # Test [Rz, Rm] = -Rm assert np.allclose(Rz(Rm(m)) - Rm(Rz(m)), -Rm(m), rtol=tolerance, atol=tolerance) # Test [Rp, Rm] = 2Rz assert np.allclose(Rp(Rm(m)) - Rm(Rp(m)), 2 * Rz(m), rtol=tolerance, atol=tolerance) # Test [ethbar, eth] = 2s assert np.allclose(ethbar(eth(m)) - eth(ethbar(m)), 2 * m.s * m, rtol=tolerance, atol=tolerance)