def test_FFT_random_real(par): shape = par["shape"] dtype, decimal = par["dtype_precision"] ifftshift_before = par["ifftshift_before"] x = np.random.randn(*shape).astype(dtype) # Select an axis to apply FFT on. It can be any integer # in [0,..., ndim-1] but also in [-ndim, ..., -1] axis = _choose_random_axes(x.ndim, n_choices=1)[0] FFTop = FFT( dims=x.shape, dir=axis, ifftshift_before=ifftshift_before, real=True, dtype=dtype, ) x = x.ravel() y = FFTop * x # Ensure inverse and adjoint recover x xadj = FFTop.H * y # adjoint is same as inverse for fft xinv = lsqr(FFTop, y, damp=0, iter_lim=10, show=0)[0] assert_array_almost_equal(x, xadj, decimal=decimal) assert_array_almost_equal(x, xinv, decimal=decimal) # Dot tests nr, nc = FFTop.shape assert dottest(FFTop, nr, nc, complexflag=0, tol=10**(-decimal)) assert dottest(FFTop, nr, nc, complexflag=2, tol=10**(-decimal))
def test_FFT_1dsignal(par): """Dot-test and inversion for FFT operator for 1d signal """ dt = 0.005 t = np.arange(par['nt']) * dt f0 = 10 x = np.sin(2 * np.pi * f0 * t) nfft = par['nt'] if isinstance(par['nfft'], bool) else par['nfft'] FFTop = FFT(dims=[par['nt']], nfft=nfft, sampling=dt, real=par['real'], engine=par['engine']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, par['nt'], par['nt'], complexflag=0) else: assert dottest(FFTop, nfft, par['nt'], complexflag=2) assert dottest(FFTop, nfft, par['nt'], complexflag=3) y = FFTop * x xadj = FFTop.H * y # adjoint is same as inverse for fft xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0] assert_array_almost_equal(x, xadj, decimal=8) assert_array_almost_equal(x, xinv, decimal=8)
def test_FFT_1dsignal(par): """Dot-test and inversion for FFT operator for 1d signal """ decimal = 4 if np.real(np.ones(1, par['dtype'])).dtype == np.float32 else 8 dt = 0.005 t = np.arange(par['nt']) * dt f0 = 10 x = np.sin(2 * np.pi * f0 * t) x = x.astype(par['dtype']) nfft = par['nt'] if par['nfft'] is None else par['nfft'] FFTop = FFT(dims=[par['nt']], nfft=nfft, sampling=dt, real=par['real'], engine=par['engine'], dtype=par['dtype']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, par['nt'], par['nt'], complexflag=0, tol=10**(-decimal), verb=True) else: assert dottest(FFTop, nfft, par['nt'], complexflag=2, tol=10**(-decimal), verb=True) assert dottest(FFTop, nfft, par['nt'], complexflag=3, tol=10**(-decimal), verb=True) y = FFTop * x xadj = FFTop.H * y # adjoint is same as inverse for fft xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0] assert_array_almost_equal(x, xadj, decimal=decimal) assert_array_almost_equal(x, xinv, decimal=decimal)
def test_unknown_engine(par): """Check error is raised if unknown engine is passed """ with pytest.raises(NotImplementedError): _ = FFT(dims=[par['nt']], nfft=par['nfft'], sampling=0.005, real=par['real'], engine='foo')
def test_unknown_engine(par): """Check error is raised if unknown engine is passed""" with pytest.raises(NotImplementedError): _ = FFT( dims=[par["nt"]], nfft=par["nfft"], sampling=0.005, real=par["real"], engine="foo", )
def test_FFT_2dsignal(par): """Dot-test and inversion for fft operator for 2d signal (fft on single dimension) """ dt = 0.005 nt, nx = par['nt'], par['nx'] t = np.arange(nt) * dt f0 = 10 d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1) # 1st dimension FFTop = FFT(dims=(nt, nx), dir=0, nfft=par['nfft'], sampling=dt) assert dottest(FFTop, par['nfft'] * par['nx'], par['nt'] * par['nx'], complexflag=2) D = FFTop * d.flatten() dadj = FFTop.H * D # adjoint is same as inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(par['nt'], par['nx'])) dinv = np.real(dinv.reshape(par['nt'], par['nx'])) assert_array_almost_equal(d, dadj, decimal=8) assert_array_almost_equal(d, dinv, decimal=8) # 2nd dimension FFTop = FFT(dims=(nt, nx), dir=1, nfft=par['nfft'], sampling=dt) assert dottest(FFTop, par['nt'] * par['nfft'], par['nt'] * par['nx'], complexflag=2) D = FFTop * d.flatten() dadj = FFTop.H * D # adjoint is inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(par['nt'], par['nx'])) dinv = np.real(dinv.reshape(par['nt'], par['nx'])) assert_array_almost_equal(d, dadj, decimal=8) assert_array_almost_equal(d, dinv, decimal=8)
def test_FFT_random_complex(par): shape = par["shape"] dtype, decimal = par["dtype_precision"] ifftshift_before = par["ifftshift_before"] fftshift_after = par["fftshift_after"] engine = par["engine"] x = np.random.randn(*shape).astype(dtype) if np.issubdtype(dtype, np.complexfloating): x += 1j * np.random.randn(*shape).astype(dtype) # Select an axis to apply FFT on. It can be any integer # in [0,..., ndim-1] but also in [-ndim, ..., -1] axis = _choose_random_axes(x.ndim, n_choices=1)[0] FFTop = FFT( dims=x.shape, dir=axis, ifftshift_before=ifftshift_before, fftshift_after=fftshift_after, dtype=dtype, engine=engine, ) # Compute FFT of x independently y_true = np.fft.fft(x, axis=axis, norm="ortho") if fftshift_after: y_true = np.fft.fftshift(y_true, axes=axis) if ifftshift_before: y_true = np.swapaxes(y_true, axis, -1) x0 = -np.ceil(x.shape[axis] / 2) phase_correction = np.exp(2 * np.pi * 1j * FFTop.f * x0) y_true *= phase_correction y_true = np.swapaxes(y_true, -1, axis) y_true = y_true.ravel() # Compute FFT with FFTop and compare with y_true x = x.ravel() y = FFTop * x assert_array_almost_equal(y, y_true, decimal=decimal) # Ensure inverse and adjoint recover x xadj = FFTop.H * y # adjoint is same as inverse for fft xinv = lsqr(FFTop, y, damp=0, iter_lim=10, show=0)[0] assert_array_almost_equal(x, xadj, decimal=decimal) assert_array_almost_equal(x, xinv, decimal=decimal) # Dot tests nr, nc = FFTop.shape assert dottest(FFTop, nr, nc, complexflag=0, tol=10**(-decimal)) assert dottest(FFTop, nr, nc, complexflag=2, tol=10**(-decimal)) if np.issubdtype(dtype, np.complexfloating): assert dottest(FFTop, nr, nc, complexflag=1, tol=10**(-decimal)) assert dottest(FFTop, nr, nc, complexflag=3, tol=10**(-decimal))
def test_FFT_1dsignal(par): """Dot-test and inversion for FFT operator for 1d signal """ dt = 0.005 t = np.arange(par['nt']) * dt f0 = 10 x = np.sin(2 * np.pi * f0 * t) FFTop = FFT(dims=[par['nt']], nfft=par['nfft'], sampling=dt) assert dottest(FFTop, par['nfft'], par['nt'], complexflag=2) y = FFTop * x xadj = FFTop.H * y # adjoint is same as inverse for fft xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0] assert_array_almost_equal(x, xadj, decimal=8) assert_array_almost_equal(x, xinv, decimal=8)
def test_FFT_small_real(par): dtype, decimal = par["dtype_precision"] norm = par["norm"] ifftshift_before = par["ifftshift_before"] engine = par["engine"] x = np.array([1, 0, -1, 1], dtype=dtype) FFTop = FFT( dims=x.shape, dir=0, norm=norm, real=True, ifftshift_before=ifftshift_before, dtype=dtype, engine=engine, ) y = FFTop * x.ravel() if norm == "ortho": y_true = np.array([0.5, 1 + 0.5j, -0.5], dtype=FFTop.cdtype) elif norm == "none": y_true = np.array([1, 2 + 1j, -1], dtype=FFTop.cdtype) elif norm == "1/n": y_true = np.array([0.25, 0.5 + 0.25j, -0.25], dtype=FFTop.cdtype) y_true[1:-1] *= np.sqrt(2) # Zero and Nyquist if ifftshift_before: # `ifftshift_before`` is useful when the time-axis is centered around zero as # it ensures the time axis to starts at zero: # [-2, -1, 0, 1] ---ifftshift--> [0, 1, -2, -1] # This does not alter the amplitude of the FFT, but does alter the phase. To # match the results without ifftshift, we need to add a phase shift opposite to # the one introduced by FFT as given below. See "An FFT Primer for physicists", # by Thomas Kaiser. # https://www.iap.uni-jena.de/iapmedia/de/Lecture/Computational+Photonics/CoPho19_supp_FFT_primer.pdf x0 = -np.ceil(len(x) / 2) y_true *= np.exp(2 * np.pi * 1j * FFTop.f * x0) assert_array_almost_equal(y, y_true, decimal=decimal) assert dottest(FFTop, len(y), len(x), complexflag=0, tol=10**(-decimal)) assert dottest(FFTop, len(y), len(x), complexflag=2, tol=10**(-decimal)) x_inv = FFTop / y x_inv = x_inv.reshape(x.shape) assert_array_almost_equal(x_inv, x, decimal=decimal)
def test_FFT_1dsignal(par): """Dot-test and comparison with pylops FFT operator for 1d signal """ dt, f0 = 0.005, 10 t = np.arange(par['nt']) * dt x = da.from_array(np.sin(2 * np.pi * f0 * t)) nfft = par['nt'] if par['nfft'] is None else par['nfft'] dFFTop = dFFT(dims=[par['nt']], nfft=nfft, sampling=dt, real=par['real'], chunks=(par['nt'], nfft), dtype=par['dtype']) FFTop = FFT(dims=[par['nt']], nfft=nfft, sampling=dt, real=par['real'], dtype=par['dtype']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: dFFTop = dFFTop.H * dFFTop FFTop = FFTop.H * FFTop assert dottest(dFFTop, par['nt'], par['nt'], chunks=(par['nt'], nfft), complexflag=0) else: assert dottest(dFFTop, nfft, par['nt'], chunks=(par['nt'], nfft), complexflag=2) assert dottest(dFFTop, nfft, par['nt'], chunks=(par['nt'], nfft), complexflag=3) dy = dFFTop * x y = FFTop * x.compute() assert_array_almost_equal(dy, y, decimal=5)
def test_FFT_small_complex(par): dtype, decimal = par["dtype_precision"] norm = par["norm"] ifftshift_before = par["ifftshift_before"] fftshift_after = par["fftshift_after"] x = np.array([1, 2 - 1j, -1j, -1 + 2j], dtype=dtype) FFTop = FFT( dims=x.shape, dir=0, norm=norm, ifftshift_before=ifftshift_before, fftshift_after=fftshift_after, dtype=dtype, ) # Compute FFT of x independently if norm == "ortho": y_true = np.array([1, -1 - 1j, -1j, 2 + 2j], dtype=FFTop.cdtype) elif norm == "none": y_true = np.array([2, -2 - 2j, -2j, 4 + 4j], dtype=FFTop.cdtype) elif norm == "1/n": y_true = np.array([0.5, -0.5 - 0.5j, -0.5j, 1 + 1j], dtype=FFTop.cdtype) if fftshift_after: y_true = np.fft.fftshift(y_true) if ifftshift_before: x0 = -np.ceil(x.shape[0] / 2) y_true *= np.exp(2 * np.pi * 1j * FFTop.f * x0) # Compute FFT with FFTop and compare with y_true y = FFTop * x.ravel() assert_array_almost_equal(y, y_true, decimal=decimal) assert dottest(FFTop, *FFTop.shape, complexflag=3, tol=10**(-decimal)) x_inv = FFTop / y x_inv = x_inv.reshape(x.shape) assert_array_almost_equal(x_inv, x, decimal=decimal)
def test_FFT_3dsignal(par): """Dot-test and inversion for fft operator for 3d signal (fft on single dimension) """ dt = 0.005 nt, nx, ny = par['nt'], par['nx'], par['ny'] t = np.arange(nt) * dt f0 = 10 d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1) d = np.tile(d[:, :, np.newaxis], [1, 1, ny]) # 1st dimension nfft = par['nt'] if isinstance(par['nfft'], bool) else par['nfft'] FFTop = FFT(dims=(nt, nx, ny), dir=0, nfft=nfft, sampling=dt, real=par['real'], engine=par['engine']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, nt * nx * ny, nt * nx * ny, complexflag=0) else: assert dottest(FFTop, nfft * nx * ny, nt * nx * ny, complexflag=2) assert dottest(FFTop, nfft * nx * ny, nt * nx * ny, complexflag=3) D = FFTop * d.flatten() dadj = FFTop.H * D # adjoint is same as inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx, ny)) dinv = np.real(dinv.reshape(nt, nx, ny)) assert_array_almost_equal(d, dadj, decimal=8) assert_array_almost_equal(d, dinv, decimal=8) # 2nd dimension if isinstance(par['nfft'], bool): nfft = par['nx'] FFTop = FFT(dims=(nt, nx, ny), dir=1, nfft=nfft, sampling=dt, real=par['real'], engine=par['engine']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, nt * nx * ny, nt * nx * ny, complexflag=0) else: assert dottest(FFTop, nt * nfft * ny, nt * nx * ny, complexflag=2) assert dottest(FFTop, nt * nfft * ny, nt * nx * ny, complexflag=3) D = FFTop * d.flatten() dadj = FFTop.H * D # adjoint is inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx, ny)) dinv = np.real(dinv.reshape(nt, nx, ny)) assert_array_almost_equal(d, dadj, decimal=8) assert_array_almost_equal(d, dinv, decimal=8) # 3rd dimension if isinstance(par['nfft'], bool): nfft = par['ny'] FFTop = FFT(dims=(nt, nx, ny), dir=2, nfft=nfft, sampling=dt, real=par['real'], engine=par['engine']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, nt * nx * ny, nt * nx * ny, complexflag=0) else: assert dottest(FFTop, nt * nx * nfft, nt * nx * ny, complexflag=2) assert dottest(FFTop, nt * nx * nfft, nt * nx * ny, complexflag=3) D = FFTop * d.flatten() dadj = FFTop.H * D # adjoint is inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx, ny)) dinv = np.real(dinv.reshape(nt, nx, ny)) assert_array_almost_equal(d, dadj, decimal=8) assert_array_almost_equal(d, dinv, decimal=8)
def test_FFT_2dsignal(par): """Dot-test and inversion for fft operator for 2d signal (fft on single dimension) """ decimal = 4 if np.real(np.ones(1, par['dtype'])).dtype == np.float32 else 8 dt = 0.005 nt, nx = par['nt'], par['nx'] t = np.arange(nt) * dt f0 = 10 d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1) d = d.astype(par['dtype']) # 1st dimension nfft = par['nt'] if par['nfft'] is None else par['nfft'] FFTop = FFT(dims=(nt, nx), dir=0, nfft=nfft, sampling=dt, real=par['real'], engine=par['engine'], dtype=par['dtype']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, nt * nx, nt * nx, complexflag=0, tol=10**(-decimal)) else: assert dottest(FFTop, nfft * nx, nt * nx, complexflag=2, tol=10**(-decimal)) assert dottest(FFTop, nfft * nx, nt * nx, complexflag=3, tol=10**(-decimal)) D = FFTop * d.flatten() dadj = FFTop.H*D # adjoint is same as inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx)) dinv = np.real(dinv.reshape(nt, nx)) assert_array_almost_equal(d, dadj, decimal=decimal) assert_array_almost_equal(d, dinv, decimal=decimal) # 2nd dimension nfft = par['nx'] if par['nfft'] is None else par['nfft'] FFTop = FFT(dims=(nt, nx), dir=1, nfft=nfft, sampling=dt, real=par['real'], engine=par['engine'], dtype=par['dtype']) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: FFTop = FFTop.H * FFTop assert dottest(FFTop, nt * nx, nt * nx, complexflag=0, tol=10**(-decimal)) else: assert dottest(FFTop, nt * nfft, nt * nx, complexflag=2, tol=10**(-decimal)) assert dottest(FFTop, nt * nfft, nt * nx, complexflag=3, tol=10**(-decimal)) D = FFTop * d.flatten() dadj = FFTop.H * D # adjoint is inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx)) dinv = np.real(dinv.reshape(nt, nx)) assert_array_almost_equal(d, dadj, decimal=decimal) assert_array_almost_equal(d, dinv, decimal=decimal)
def test_FFT_2dsignal(par): """Dot-test and inversion for fft operator for 2d signal (fft on single dimension) """ decimal = 3 if np.real(np.ones(1, par["dtype"])).dtype == np.float32 else 8 dt = 0.005 nt, nx = par["nt"], par["nx"] t = np.arange(nt) * dt f0 = 10 d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1) d = d.astype(par["dtype"]) # 1st dimension nfft = par["nt"] if par["nfft"] is None else par["nfft"] FFTop = FFT( dims=(nt, nx), dir=0, nfft=nfft, sampling=dt, real=par["real"], engine=par["engine"], dtype=par["dtype"], ) if par["real"]: assert dottest(FFTop, (nfft // 2 + 1) * nx, nt * nx, complexflag=2, tol=10**(-decimal)) else: assert dottest(FFTop, nfft * nx, nt * nx, complexflag=2, tol=10**(-decimal)) assert dottest(FFTop, nfft * nx, nt * nx, complexflag=3, tol=10**(-decimal)) D = FFTop * d.ravel() dadj = FFTop.H * D # adjoint is same as inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx)) dinv = np.real(dinv.reshape(nt, nx)) # check all signal if nt>nfft and only up to nfft if nfft<nt imax = par["nt"] if par["nfft"] is None else min([par["nt"], par["nfft"]]) assert_array_almost_equal(d[:imax], dadj[:imax], decimal=decimal) assert_array_almost_equal(d[:imax], dinv[:imax], decimal=decimal) if not par["real"]: FFTop_fftshift = FFT( dims=(nt, nx), dir=0, nfft=nfft, sampling=dt, real=par["real"], fftshift_after=True, engine=par["engine"], dtype=par["dtype"], ) assert_array_almost_equal(FFTop_fftshift.f, np.fft.fftshift(FFTop.f)) D_fftshift = FFTop_fftshift * d.flatten() D2 = np.fft.fftshift(D.reshape(nfft, nx), axes=0).flatten() assert_array_almost_equal(D_fftshift, D2) dadj = FFTop_fftshift.H * D_fftshift # adjoint is same as inverse for fft dinv = lsqr(FFTop_fftshift, D_fftshift, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx)) dinv = np.real(dinv.reshape(nt, nx)) assert_array_almost_equal(d[:imax], dadj[:imax], decimal=decimal) assert_array_almost_equal(d[:imax], dinv[:imax], decimal=decimal) # 2nd dimension nfft = par["nx"] if par["nfft"] is None else par["nfft"] FFTop = FFT( dims=(nt, nx), dir=1, nfft=nfft, sampling=dt, real=par["real"], engine=par["engine"], dtype=par["dtype"], ) if par["real"]: assert dottest(FFTop, nt * (nfft // 2 + 1), nt * nx, complexflag=2, tol=10**(-decimal)) else: assert dottest(FFTop, nt * nfft, nt * nx, complexflag=2, tol=10**(-decimal)) assert dottest(FFTop, nt * nfft, nt * nx, complexflag=3, tol=10**(-decimal)) D = FFTop * d.ravel() dadj = FFTop.H * D # adjoint is inverse for fft dinv = lsqr(FFTop, D, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx)) dinv = np.real(dinv.reshape(nt, nx)) # check all signal if nx>nfft and only up to nfft if nfft<nx imax = par["nx"] if par["nfft"] is None else min([par["nx"], par["nfft"]]) assert_array_almost_equal(d[:, :imax], dadj[:, :imax], decimal=decimal) assert_array_almost_equal(d[:, :imax], dinv[:, :imax], decimal=decimal) if not par["real"]: FFTop_fftshift = FFT( dims=(nt, nx), dir=1, nfft=nfft, sampling=dt, real=par["real"], fftshift_after=True, engine=par["engine"], dtype=par["dtype"], ) assert_array_almost_equal(FFTop_fftshift.f, np.fft.fftshift(FFTop.f)) D_fftshift = FFTop_fftshift * d.flatten() D2 = np.fft.fftshift(D.reshape(nt, nfft), axes=1).flatten() assert_array_almost_equal(D_fftshift, D2) dadj = FFTop_fftshift.H * D_fftshift # adjoint is same as inverse for fft dinv = lsqr(FFTop_fftshift, D_fftshift, damp=1e-10, iter_lim=10, show=0)[0] dadj = np.real(dadj.reshape(nt, nx)) dinv = np.real(dinv.reshape(nt, nx)) assert_array_almost_equal(d[:, :imax], dadj[:, :imax], decimal=decimal) assert_array_almost_equal(d[:, :imax], dinv[:, :imax], decimal=decimal)
def test_FFT_1dsignal(par): """Dot-test and inversion for FFT operator for 1d signal""" decimal = 3 if np.real(np.ones(1, par["dtype"])).dtype == np.float32 else 8 dt = 0.005 t = np.arange(par["nt"]) * dt f0 = 10 x = np.sin(2 * np.pi * f0 * t) x = x.astype(par["dtype"]) nfft = par["nt"] if par["nfft"] is None else par["nfft"] FFTop = FFT( dims=[par["nt"]], nfft=nfft, sampling=dt, real=par["real"], engine=par["engine"], dtype=par["dtype"], ) if par["real"]: assert dottest(FFTop, nfft // 2 + 1, par["nt"], complexflag=2, tol=10**(-decimal)) else: assert dottest(FFTop, nfft, par["nt"], complexflag=2, tol=10**(-decimal)) assert dottest(FFTop, nfft, par["nt"], complexflag=3, tol=10**(-decimal)) y = FFTop * x xadj = FFTop.H * y # adjoint is same as inverse for fft xinv = lsqr(FFTop, y, damp=1e-10, iter_lim=10, show=0)[0] # check all signal if nt>nfft and only up to nfft if nfft<nt imax = par["nt"] if par["nfft"] is None else min([par["nt"], par["nfft"]]) assert_array_almost_equal(x[:imax], xadj[:imax], decimal=decimal) assert_array_almost_equal(x[:imax], xinv[:imax], decimal=decimal) if not par["real"]: FFTop_fftshift = FFT( dims=[par["nt"]], nfft=nfft, sampling=dt, real=par["real"], ifftshift_before=par["ifftshift_before"], fftshift_after=True, engine=par["engine"], dtype=par["dtype"], ) assert_array_almost_equal(FFTop_fftshift.f, np.fft.fftshift(FFTop.f)) y_fftshift = FFTop_fftshift * x assert_array_almost_equal(y_fftshift, np.fft.fftshift(y)) xadj = FFTop_fftshift.H * y_fftshift # adjoint is same as inverse for fft xinv = lsqr(FFTop_fftshift, y_fftshift, damp=1e-10, iter_lim=10, show=0)[0] assert_array_almost_equal(x[:imax], xadj[:imax], decimal=decimal) assert_array_almost_equal(x[:imax], xinv[:imax], decimal=decimal)
def MDC(G, nt, nv, dt=1., dr=1., twosided=True, fast=None, dtype=None, fftengine='numpy', transpose=True, saveGt=True, conj=False): r"""Multi-dimensional convolution. Apply multi-dimensional convolution between two datasets. If ``transpose=True``, model and data should be provided after flattening 2- or 3-dimensional arrays of size :math:`[n_r (\times n_{vs}) \times n_t]` and :math:`[n_s (\times n_{vs}) \times n_t]` (or :math:`2*n_t-1` for ``twosided=True``), respectively. If ``transpose=False``, model and data should be provided after flattening 2- or 3-dimensional arrays of size :math:`[n_t \times n_r (\times n_{vs})]` and :math:`[n_t \times n_s (\times n_{vs})]` (or :math:`2*n_t-1` for ``twosided=True``), respectively. .. warning:: A new implementation of MDC is provided in v1.5.0. This currently affects only the inner working of the operator and end-users can use the operator in the same way as they used to do with the previous one. Nevertheless, it is now reccomended to use the operator with ``transpose=False``, as this behaviour will become default in version v2.0.0 and the behaviour with ``transpose=True`` will be deprecated. Parameters ---------- G : :obj:`numpy.ndarray` Multi-dimensional convolution kernel in frequency domain of size :math:`[\times n_s \times n_r \times n_{fmax}]` if ``transpose=True`` or size :math:`[n_{fmax} \times n_s \times n_r]` if ``transpose=False`` nt : :obj:`int` Number of samples along time axis nv : :obj:`int` Number of samples along virtual source axis dt : :obj:`float`, optional Sampling of time integration axis dr : :obj:`float`, optional Sampling of receiver integration axis twosided : :obj:`bool`, optional MDC operator has both negative and positive time (``True``) or only positive (``False``) fast : :obj:`bool`, optional *Deprecated*, will be removed in v2.0.0 dtype : :obj:`str`, optional *Deprecated*, will be removed in v2.0.0 fftengine : :obj:`str`, optional Engine used for fft computation (``numpy`` or ``fftw``) transpose : :obj:`bool`, optional Transpose ``G`` and inputs such that time/frequency is placed in first dimension. This allows back-compatibility with v1.4.0 and older but will be removed in v2.0.0 where time/frequency axis will be required to be in first dimension for efficiency reasons. saveGt : :obj:`bool`, optional Save ``G`` and ``G^H`` to speed up the computation of adjoint of :class:`pylops.signalprocessing.Fredholm1` (``True``) or create ``G^H`` on-the-fly (``False``) Note that ``saveGt=True`` will be faster but double the amount of required memory conj : :obj:`str`, optional Perform Fredholm integral computation with complex conjugate of ``G`` See Also -------- MDD : Multi-dimensional deconvolution Notes ----- The so-called multi-dimensional convolution (MDC) is a chained operator [1]_. It is composed of a forward Fourier transform, a multi-dimensional integration, and an inverse Fourier transform: .. math:: y(f, s, v) = \mathscr{F}^{-1} \Big( \int_S G(f, s, r) \mathscr{F}(x(f, r, v)) dr \Big) This operation can be discretized and performed by means of a linear operator .. math:: \mathbf{D}= \mathbf{F}^H \mathbf{G} \mathbf{F} where :math:`\mathbf{F}` is the Fourier transform applied along the time axis and :math:`\mathbf{G}` is the multi-dimensional convolution kernel. .. [1] Wapenaar, K., van der Neut, J., Ruigrok, E., Draganov, D., Hunziker, J., Slob, E., Thorbecke, J., and Snieder, R., "Seismic interferometry by crosscorrelation and by multi-dimensional deconvolution: a systematic comparison", Geophysical Journal International, vol. 185, pp. 1335-1364. 2011. """ warnings.warn( 'A new implementation of MDC is provided in v1.5.0. This ' 'currently affects only the inner working of the operator, ' 'end-users can continue using the operator in the same way. ' 'Nevertheless, it is now recommended to start using the ' 'operator with transpose=True, as this behaviour will ' 'become default in version v2.0.0 and the behaviour with ' 'transpose=False will be deprecated.', FutureWarning) if twosided and nt % 2 == 0: raise ValueError('nt must be odd number') # transpose G if transpose: G = np.transpose(G, axes=(2, 0, 1)) # create Fredholm operator dtype = G[0, 0, 0].dtype fdtype = (G[0, 0, 0] + 1j * G[0, 0, 0]).dtype Frop = Fredholm1(dr * dt * np.sqrt(nt) * G, nv, saveGt=saveGt, usematmul=False, dtype=fdtype) if conj: Frop = Frop.conj() # create FFT operators nfmax, ns, nr = G.shape # ensure that nfmax is not bigger than allowed nfft = int(np.ceil((nt + 1) / 2)) if nfmax > nfft: nfmax = nfft logging.warning('nfmax set equal to ceil[(nt+1)/2=%d]' % nfmax) Fop = FFT(dims=(nt, nr, nv), dir=0, real=True, fftshift=twosided, engine=fftengine, dtype=fdtype) F1op = FFT(dims=(nt, ns, nv), dir=0, real=True, fftshift=False, engine=fftengine, dtype=fdtype) # create Identity operator to extract only relevant frequencies Iop = Identity(N=nfmax * nr * nv, M=nfft * nr * nv, inplace=True, dtype=dtype) I1op = Identity(N=nfmax * ns * nv, M=nfft * ns * nv, inplace=True, dtype=dtype) F1opH = F1op.H I1opH = I1op.H # create transpose operator if transpose: dims = [nr, nt] if nv == 1 else [nr, nv, nt] axes = (1, 0) if nv == 1 else (2, 0, 1) Top = Transpose(dims, axes, dtype=dtype) dims = [nt, ns] if nv == 1 else [nt, ns, nv] axes = (1, 0) if nv == 1 else (1, 2, 0) TopH = Transpose(dims, axes, dtype=dtype) # create MDC operator MDCop = F1opH * I1opH * Frop * Iop * Fop if transpose: MDCop = TopH * MDCop * Top return MDCop
def PhaseShift(vel, dz, nt, freq, kx, ky=None, dtype="float64"): r"""Phase shift operator Apply positive (forward) phase shift with constant velocity in forward mode, and negative (backward) phase shift with constant velocity in adjoint mode. Input model and data should be 2- or 3-dimensional arrays in time-space domain of size :math:`[n_t \times n_x \;(\times n_y)]`. Parameters ---------- vel : :obj:`float`, optional Constant propagation velocity dz : :obj:`float`, optional Depth step nt : :obj:`int`, optional Number of time samples of model and data freq : :obj:`numpy.ndarray` Positive frequency axis kx : :obj:`int`, optional Horizontal wavenumber axis (centered around 0) of size :math:`[n_x \times 1]`. ky : :obj:`int`, optional Second horizontal wavenumber axis for 3d phase shift (centered around 0) of size :math:`[n_y \times 1]`. dtype : :obj:`str`, optional Type of elements in input array Returns ------- Pop : :obj:`pylops.LinearOperator` Phase shift operator Notes ----- The phase shift operator implements a one-way wave equation forward propagation in frequency-wavenumber domain by applying the following transformation to the input model: .. math:: d(f, k_x, k_y) = m(f, k_x, k_y) e^{-j \Delta z \sqrt{\omega^2/v^2 - k_x^2 - k_y^2}} where :math:`v` is the constant propagation velocity and :math:`\Delta z` is the propagation depth. In adjoint mode, the data is propagated backward using the following transformation: .. math:: m(f, k_x, k_y) = d(f, k_x, k_y) e^{j \Delta z \sqrt{\omega^2/v^2 - k_x^2 - k_y^2}} Effectively, the input model and data are assumed to be in time-space domain and forward Fourier transform is applied to both dimensions, leading to the following operator: .. math:: \mathbf{d} = \mathbf{F}^H_t \mathbf{F}^H_x \mathbf{P} \mathbf{F}_x \mathbf{F}_t \mathbf{m} where :math:`\mathbf{P}` perfoms the phase-shift as discussed above. """ dtypefft = (np.ones(1, dtype=dtype) + 1j * np.ones(1, dtype=dtype)).dtype if ky is None: dims = (nt, kx.size) dimsfft = (freq.size, kx.size) else: dims = (nt, kx.size, ky.size) dimsfft = (freq.size, kx.size, ky.size) Fop = FFT(dims, dir=0, nfft=nt, real=True, dtype=dtype) Kxop = FFT( dimsfft, dir=1, nfft=kx.size, real=False, fftshift_after=True, dtype=dtypefft ) if ky is not None: Kyop = FFT( dimsfft, dir=2, nfft=ky.size, real=False, fftshift_after=True, dtype=dtypefft, ) Pop = _PhaseShift(vel, dz, freq, kx, ky, dtypefft) if ky is None: Pop = Fop.H * Kxop * Pop * Kxop.H * Fop else: Pop = Fop.H * Kxop * Kyop * Pop * Kyop.H * Kxop.H * Fop # Recasting of type is required to avoid FFT operators to cast to complex. # We know this is correct because forward and inverse FFTs are applied at # the beginning and end of this combined operator Pop.dtype = dtype return LinearOperator(Pop)
def test_FFT_2dsignal(par): """Dot-test and comparison with pylops FFT operator for 2d signal (fft on single dimension) """ dt, f0 = 0.005, 10 nt, nx = par['nt'], par['nx'] t = np.arange(nt) * dt d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(nx) + 1) d = da.from_array(d) # 1st dimension nfft = par['nt'] if par['nfft'] is None else par['nfft'] dFFTop = dFFT(dims=(nt, nx), dir=0, nfft=nfft, sampling=dt, real=par['real'], chunks=((nt, nx), (nfft, nx))) FFTop = FFT(dims=(nt, nx), dir=0, nfft=nfft, sampling=dt) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: dFFTop = dFFTop.H * dFFTop FFTop = FFTop.H * FFTop assert dottest(dFFTop, nt * nx, nt * nx, chunks=(nt * nx, nt * nx), complexflag=0) else: assert dottest(dFFTop, nfft * nx, nt * nx, chunks=(nt * nx, nfft * nx), complexflag=2) assert dottest(dFFTop, nfft * nx, nt * nx, chunks=(nt * nx, nfft * nx), complexflag=3) dy = dFFTop * d.ravel() y = FFTop * d.ravel().compute() assert_array_almost_equal(dy, y, decimal=5) # 2nd dimension nfft = par['nx'] if par['nfft'] is None else par['nfft'] dFFTop = dFFT(dims=(nt, nx), dir=1, nfft=nfft, sampling=dt, real=par['real'], chunks=((nt, nx), (nt, nfft))) FFTop = FFT(dims=(nt, nx), dir=1, nfft=nfft, sampling=dt) # FFT with real=True cannot pass dot-test neither be inverted correctly, # see FFT documentation for a detailed explanation. We thus test FFT.H*FFT if par['real']: dFFTop = dFFTop.H * dFFTop FFTop = FFTop.H * FFTop assert dottest(dFFTop, nt * nx, nt * nx, chunks=(nt * nx, nt * nx), complexflag=0) else: assert dottest(dFFTop, nt * nfft, nt * nx, chunks=(nt * nx, nt * nfft), complexflag=2) assert dottest(dFFTop, nt * nfft, nt * nx, chunks=(nt * nx, nt * nfft), complexflag=3) dy = dFFTop * d.ravel() y = FFTop * d.ravel().compute() assert_array_almost_equal(dy, y, decimal=5)