def test_fourier_trafo_create_temp(): shape = 10 space_discr = odl.uniform_discr(0, 1, shape, dtype="complex64") ft = FourierTransform(space_discr) ft.create_temporaries() assert ft._tmp_r is not None assert ft._tmp_f is not None ift = ft.inverse assert ift._tmp_r is not None assert ift._tmp_f is not None ft.clear_temporaries() assert ft._tmp_r is None assert ft._tmp_f is None
def test_fourier_trafo_call(impl, floating_dtype): # Test if all variants can be called without error # Not supported, skip if floating_dtype == np.dtype("float16") and impl == "pyfftw": return shape = 10 halfcomplex, _ = _params_from_dtype(floating_dtype) space_discr = odl.uniform_discr(0, 1, shape, dtype=floating_dtype) ft = FourierTransform(space_discr, impl=impl, halfcomplex=halfcomplex) ift = ft.inverse one = space_discr.one() assert np.allclose(ift(ft(one)), one) # With temporaries ft.create_temporaries() ift = ft.inverse # shares temporaries one = space_discr.one() assert np.allclose(ift(ft(one)), one)
def test_fourier_trafo_scaling(): # Test if the FT scales correctly # Characteristic function of [0, 1], its Fourier transform is # given by exp(-1j * y / 2) * sinc(y/2) def char_interval(x): return (x >= 0) & (x <= 1) def char_interval_ft(x): return np.exp(-1j * x / 2) * sinc(x / 2) / np.sqrt(2 * np.pi) fspace = odl.FunctionSpace(odl.IntervalProd(-2, 2), field=odl.ComplexNumbers()) discr = odl.uniform_discr_fromspace(fspace, 40, impl='numpy') dft = FourierTransform(discr) for factor in (2, 1j, -2.5j, 1 - 4j): func_true_ft = factor * dft.range.element(char_interval_ft) func_dft = dft(factor * fspace.element(char_interval)) assert (func_dft - func_true_ft).norm() < 1e-6
def test_fourier_trafo_hat_1d(): # Hat function as used in linear interpolation. It is not so # well discretized by nearest neighbor interpolation, so a larger # error is to be expected. def hat_func(x): out = np.where(x < 0, 1 + x, 1 - x) out[x < -1] = 0 out[x > 1] = 0 return out def hat_func_ft(x): return sinc(x / 2)**2 / np.sqrt(2 * np.pi) # Using a single-precision implementation, should be as good # With linear interpolation in the discretization, should be better? discr = odl.uniform_discr(-2, 2, 101, impl='numpy', dtype='float32') dft = FourierTransform(discr) func_true_ft = dft.range.element(hat_func_ft) func_dft = dft(hat_func) assert (func_dft - func_true_ft).norm() < 0.001
def test_fourier_trafo_create_temp(): shape = 10 space_discr = odl.uniform_discr(0, 1, shape, dtype='complex64') ft = FourierTransform(space_discr) ft.create_temporaries() assert ft._tmp_r is not None assert ft._tmp_f is not None ift = ft.inverse assert ift._tmp_r is not None assert ift._tmp_f is not None ft.clear_temporaries() assert ft._tmp_r is None assert ft._tmp_f is None
def test_dft_with_known_pairs_2d(): # Frequency-shifted product of characteristic functions def fshift_char_rect(x): # Characteristic function of the cuboid # [-1, 1] x [1, 2] return (x[0] >= -1) & (x[0] <= 1) & (x[1] >= 1) & (x[1] <= 2) def fshift_char_rect_ft(x): # FT is a product of shifted and frequency-shifted sinc functions # 1st comp.: 2 * sinc(y) # 2nd comp.: exp(-1j * y * 3/2) * sinc(y/2) # Overall factor: (2 * pi)^(-1) return (2 * sinc(x[0]) * np.exp(-1j * x[1] * 3 / 2) * sinc(x[1] / 2) / (2 * np.pi)) discr = odl.uniform_discr([-2] * 2, [2] * 2, (100, 400), impl='numpy', dtype='complex64') dft = FourierTransform(discr) func_true_ft = dft.range.element(fshift_char_rect_ft) func_dft = dft(fshift_char_rect) assert (func_dft - func_true_ft).norm() < 0.001
def test_fourier_trafo_inverse(impl, sign): # Test if the inverse really is the inverse def char_interval(x): return (x >= 0) & (x <= 1) # Complex-to-complex discr = odl.uniform_discr(-2, 2, 40, impl="numpy", dtype="complex64") discr_char = discr.element(char_interval) ft = FourierTransform(discr, sign=sign, impl=impl) assert all_almost_equal(ft.inverse(ft(char_interval)), discr_char) assert all_almost_equal(ft.adjoint(ft(char_interval)), discr_char) # Half-complex discr = odl.uniform_discr(-2, 2, 40, impl="numpy", dtype="float32") ft = FourierTransform(discr, impl=impl, halfcomplex=True) assert all_almost_equal(ft.inverse(ft(char_interval)), discr_char) def char_rect(x): return (x[0] >= 0) & (x[0] <= 1) & (x[1] >= 0) & (x[1] <= 1) # 2D with axes, C2C discr = odl.uniform_discr([-2, -2], [2, 2], (20, 10), impl="numpy", dtype="complex64") discr_rect = discr.element(char_rect) for axes in [(0,), 1]: ft = FourierTransform(discr, sign=sign, impl=impl, axes=axes) assert all_almost_equal(ft.inverse(ft(char_rect)), discr_rect) assert all_almost_equal(ft.adjoint(ft(char_rect)), discr_rect) # 2D with axes, halfcomplex discr = odl.uniform_discr([-2, -2], [2, 2], (20, 10), impl="numpy", dtype="float32") discr_rect = discr.element(char_rect) for halfcomplex in [False, True]: if halfcomplex and sign == "+": continue # cannot mix halfcomplex with sign for axes in [(0,), (1,)]: ft = FourierTransform(discr, sign=sign, impl=impl, axes=axes, halfcomplex=halfcomplex) assert all_almost_equal(ft.inverse(ft(char_rect)), discr_rect) assert all_almost_equal(ft.adjoint(ft(char_rect)), discr_rect)
def test_fourier_trafo_init_plan(impl, floating_dtype): # Not supported, skip if floating_dtype == np.dtype("float16") and impl == "pyfftw": return shape = 10 halfcomplex, _ = _params_from_dtype(floating_dtype) space_discr = odl.uniform_discr(0, 1, shape, dtype=floating_dtype) ft = FourierTransform(space_discr, impl=impl, halfcomplex=halfcomplex) if impl != "pyfftw": with pytest.raises(ValueError): ft.init_fftw_plan() with pytest.raises(ValueError): ft.clear_fftw_plan() else: ft.init_fftw_plan() # Make sure plan can be used ft._fftw_plan(ft.domain.element().asarray(), ft.range.element().asarray()) ft.clear_fftw_plan() assert ft._fftw_plan is None # With temporaries ft.create_temporaries(r=True, f=False) if impl != "pyfftw": with pytest.raises(ValueError): ft.init_fftw_plan() with pytest.raises(ValueError): ft.clear_fftw_plan() else: ft.init_fftw_plan() # Make sure plan can be used ft._fftw_plan(ft.domain.element().asarray(), ft.range.element().asarray()) ft.clear_fftw_plan() assert ft._fftw_plan is None ft.create_temporaries(r=False, f=True) if impl != "pyfftw": with pytest.raises(ValueError): ft.init_fftw_plan() with pytest.raises(ValueError): ft.clear_fftw_plan() else: ft.init_fftw_plan() # Make sure plan can be used ft._fftw_plan(ft.domain.element().asarray(), ft.range.element().asarray()) ft.clear_fftw_plan() assert ft._fftw_plan is None
def test_fourier_trafo_completely(): # Complete explicit test of all FT components on two small examples # Discretization with 4 points discr = odl.uniform_discr(-2, 2, 4, dtype='complex') # Interval boundaries -2, -1, 0, 1, 2 assert np.allclose(discr.partition.cell_boundary_vecs[0], [-2, -1, 0, 1, 2]) # Grid points -1.5, -0.5, 0.5, 1.5 assert np.allclose(discr.grid.coord_vectors[0], [-1.5, -0.5, 0.5, 1.5]) # First test function, symmetric. Can be represented exactly in the # discretization. def f(x): return (x >= -1) & (x <= 1) def fhat(x): return np.sqrt(2 / np.pi) * sinc(x) # Discretize f, check values f_discr = discr.element(f) assert np.allclose(f_discr, [0, 1, 1, 0]) # "s" = shifted, "n" = not shifted # Reciprocal grids recip_s = reciprocal_grid(discr.grid, shift=True) recip_n = reciprocal_grid(discr.grid, shift=False) assert np.allclose(recip_s.coord_vectors[0], np.linspace(-np.pi, np.pi / 2, 4)) assert np.allclose(recip_n.coord_vectors[0], np.linspace(-3 * np.pi / 4, 3 * np.pi / 4, 4)) # Range range_part_s = odl.uniform_partition_fromgrid(recip_s) range_s = odl.uniform_discr_frompartition(range_part_s, dtype='complex') range_part_n = odl.uniform_partition_fromgrid(recip_n) range_n = odl.uniform_discr_frompartition(range_part_n, dtype='complex') # Pre-processing preproc_s = [1, -1, 1, -1] preproc_n = [np.exp(1j * 3 / 4 * np.pi * k) for k in range(4)] fpre_s = dft_preprocess_data(f_discr, shift=True) fpre_n = dft_preprocess_data(f_discr, shift=False) assert np.allclose(fpre_s, f_discr * discr.element(preproc_s)) assert np.allclose(fpre_n, f_discr * discr.element(preproc_n)) # FFT step, replicating the _call_numpy method fft_s = np.fft.fftn(fpre_s, s=discr.shape, axes=[0]) fft_n = np.fft.fftn(fpre_n, s=discr.shape, axes=[0]) assert np.allclose(fft_s, [0, -1 + 1j, 2, -1 - 1j]) assert np.allclose(fft_n, [ np.exp(1j * np.pi * (3 - 2 * k) / 4) + np.exp(1j * np.pi * (3 - 2 * k) / 2) for k in range(4) ]) # Interpolation kernel FT interp_s = np.sinc(np.linspace(-1 / 2, 1 / 4, 4)) / np.sqrt(2 * np.pi) interp_n = np.sinc(np.linspace(-3 / 8, 3 / 8, 4)) / np.sqrt(2 * np.pi) assert np.allclose( interp_s, _interp_kernel_ft(np.linspace(-1 / 2, 1 / 4, 4), interp='nearest')) assert np.allclose( interp_n, _interp_kernel_ft(np.linspace(-3 / 8, 3 / 8, 4), interp='nearest')) # Post-processing postproc_s = np.exp(1j * np.pi * np.linspace(-3 / 2, 3 / 4, 4)) postproc_n = np.exp(1j * np.pi * np.linspace(-9 / 8, 9 / 8, 4)) fpost_s = dft_postprocess_data(range_s.element(fft_s), real_grid=discr.grid, recip_grid=recip_s, shift=[True], axes=(0, ), interp='nearest') fpost_n = dft_postprocess_data(range_n.element(fft_n), real_grid=discr.grid, recip_grid=recip_n, shift=[False], axes=(0, ), interp='nearest') assert np.allclose(fpost_s, fft_s * postproc_s * interp_s) assert np.allclose(fpost_n, fft_n * postproc_n * interp_n) # Comparing to the known result sqrt(2/pi) * sinc(x) assert np.allclose(fpost_s, fhat(recip_s.coord_vectors[0])) assert np.allclose(fpost_n, fhat(recip_n.coord_vectors[0])) # Doing the exact same with direct application of the FT operator ft_op_s = FourierTransform(discr, shift=True) ft_op_n = FourierTransform(discr, shift=False) assert ft_op_s.range.grid == recip_s assert ft_op_n.range.grid == recip_n ft_f_s = ft_op_s(f) ft_f_n = ft_op_n(f) assert np.allclose(ft_f_s, fhat(recip_s.coord_vectors[0])) assert np.allclose(ft_f_n, fhat(recip_n.coord_vectors[0])) # Second test function, asymmetric. Can also be represented exactly in the # discretization. def f(x): return (x >= 0) & (x <= 1) def fhat(x): return np.exp(-1j * x / 2) * sinc(x / 2) / np.sqrt(2 * np.pi) # Discretize f, check values f_discr = discr.element(f) assert np.allclose(f_discr, [0, 0, 1, 0]) # Pre-processing fpre_s = dft_preprocess_data(f_discr, shift=True) fpre_n = dft_preprocess_data(f_discr, shift=False) assert np.allclose(fpre_s, [0, 0, 1, 0]) assert np.allclose(fpre_n, [0, 0, -1j, 0]) # FFT step fft_s = np.fft.fftn(fpre_s, s=discr.shape, axes=[0]) fft_n = np.fft.fftn(fpre_n, s=discr.shape, axes=[0]) assert np.allclose(fft_s, [1, -1, 1, -1]) assert np.allclose(fft_n, [-1j, 1j, -1j, 1j]) fpost_s = dft_postprocess_data(range_s.element(fft_s), real_grid=discr.grid, recip_grid=recip_s, shift=[True], axes=(0, ), interp='nearest') fpost_n = dft_postprocess_data(range_n.element(fft_n), real_grid=discr.grid, recip_grid=recip_n, shift=[False], axes=(0, ), interp='nearest') assert np.allclose(fpost_s, fft_s * postproc_s * interp_s) assert np.allclose(fpost_n, fft_n * postproc_n * interp_n) # Comparing to the known result exp(-1j*x/2) * sinc(x/2) / sqrt(2*pi) assert np.allclose(fpost_s, fhat(recip_s.coord_vectors[0])) assert np.allclose(fpost_n, fhat(recip_n.coord_vectors[0])) # Doing the exact same with direct application of the FT operator ft_f_s = ft_op_s(f) ft_f_n = ft_op_n(f) assert np.allclose(ft_f_s, fhat(recip_s.coord_vectors[0])) assert np.allclose(ft_f_n, fhat(recip_n.coord_vectors[0]))
def test_fourier_trafo_inverse(impl, sign): # Test if the inverse really is the inverse def char_interval(x): return (x >= 0) & (x <= 1) # Complex-to-complex discr = odl.uniform_discr(-2, 2, 40, impl='numpy', dtype='complex64') discr_char = discr.element(char_interval) ft = FourierTransform(discr, sign=sign, impl=impl) assert all_almost_equal(ft.inverse(ft(char_interval)), discr_char) assert all_almost_equal(ft.adjoint(ft(char_interval)), discr_char) # Half-complex discr = odl.uniform_discr(-2, 2, 40, impl='numpy', dtype='float32') ft = FourierTransform(discr, impl=impl, halfcomplex=True) assert all_almost_equal(ft.inverse(ft(char_interval)), discr_char) def char_rect(x): return (x[0] >= 0) & (x[0] <= 1) & (x[1] >= 0) & (x[1] <= 1) # 2D with axes, C2C discr = odl.uniform_discr([-2, -2], [2, 2], (20, 10), impl='numpy', dtype='complex64') discr_rect = discr.element(char_rect) for axes in [(0, ), 1]: ft = FourierTransform(discr, sign=sign, impl=impl, axes=axes) assert all_almost_equal(ft.inverse(ft(char_rect)), discr_rect) assert all_almost_equal(ft.adjoint(ft(char_rect)), discr_rect) # 2D with axes, halfcomplex discr = odl.uniform_discr([-2, -2], [2, 2], (20, 10), impl='numpy', dtype='float32') discr_rect = discr.element(char_rect) for halfcomplex in [False, True]: if halfcomplex and sign == '+': continue # cannot mix halfcomplex with sign for axes in [(0, ), (1, )]: ft = FourierTransform(discr, sign=sign, impl=impl, axes=axes, halfcomplex=halfcomplex) assert all_almost_equal(ft.inverse(ft(char_rect)), discr_rect) assert all_almost_equal(ft.adjoint(ft(char_rect)), discr_rect)
def test_fourier_trafo_init_plan(impl, floating_dtype): # Not supported, skip if floating_dtype == np.dtype('float16') and impl == 'pyfftw': return shape = 10 halfcomplex, _ = _params_from_dtype(floating_dtype) space_discr = odl.uniform_discr(0, 1, shape, dtype=floating_dtype) ft = FourierTransform(space_discr, impl=impl, halfcomplex=halfcomplex) if impl != 'pyfftw': with pytest.raises(ValueError): ft.init_fftw_plan() with pytest.raises(ValueError): ft.clear_fftw_plan() else: ft.init_fftw_plan() # Make sure plan can be used ft._fftw_plan(ft.domain.element().asarray(), ft.range.element().asarray()) ft.clear_fftw_plan() assert ft._fftw_plan is None # With temporaries ft.create_temporaries(r=True, f=False) if impl != 'pyfftw': with pytest.raises(ValueError): ft.init_fftw_plan() with pytest.raises(ValueError): ft.clear_fftw_plan() else: ft.init_fftw_plan() # Make sure plan can be used ft._fftw_plan(ft.domain.element().asarray(), ft.range.element().asarray()) ft.clear_fftw_plan() assert ft._fftw_plan is None ft.create_temporaries(r=False, f=True) if impl != 'pyfftw': with pytest.raises(ValueError): ft.init_fftw_plan() with pytest.raises(ValueError): ft.clear_fftw_plan() else: ft.init_fftw_plan() # Make sure plan can be used ft._fftw_plan(ft.domain.element().asarray(), ft.range.element().asarray()) ft.clear_fftw_plan() assert ft._fftw_plan is None