def test_FFT2D_random_real(par): shape = par["shape"] dtype, decimal = par["dtype_precision"] ifftshift_before = par["ifftshift_before"] engine = par["engine"] 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] # However, dimensions cannot be repeated axes = _choose_random_axes(x.ndim, n_choices=2) FFTop = FFT2D( dims=x.shape, dirs=axes, ifftshift_before=ifftshift_before, real=True, dtype=dtype, engine=engine, ) 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_FFT2D(par): """Dot-test and inversion for FFT2D operator for 2d signal """ decimal = 4 if np.real(np.ones(1, par['dtype'])).dtype == np.float32 else 8 dt, dx = 0.005, 5 t = np.arange(par['nt']) * dt f0 = 10 nfft1 = par['nt'] if par['nfft'] is None else par['nfft'] nfft2 = par['nx'] if par['nfft'] is None else par['nfft'] d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(par['nx']) + 1) d = d.astype(par['dtype']) FFTop = FFT2D(dims=(par['nt'], par['nx']), nffts=(nfft1, nfft2), sampling=(dt, dx)) assert dottest(FFTop, nfft1*nfft2, par['nt']*par['nx'], complexflag=2, 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=100, 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=decimal) assert_array_almost_equal(d, dinv, decimal=decimal)
def test_FFT2D_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] # However, dimensions cannot be repeated axes = _choose_random_axes(x.ndim, n_choices=2) FFTop = FFT2D( dims=x.shape, dirs=axes, ifftshift_before=ifftshift_before, fftshift_after=fftshift_after, dtype=dtype, engine=engine, ) # Compute FFT of x independently x_ishift = x.copy() for axis, ishift in zip(axes, ifftshift_before): if ishift: x_ishift = np.fft.ifftshift(x_ishift, axes=axis) y_true = np.fft.fft2(x_ishift, axes=axes, norm="ortho") for axis, fshift in zip(axes, fftshift_after): if fshift: y_true = np.fft.fftshift(y_true, axes=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_FFT2D_small_complex(par): dtype, decimal = par["dtype_precision"] norm = par["norm"] x = np.array([ [1, 2 - 1j, -1j, -1 + 2j], [2 - 1j, -1j, -1 - 2j, 1], [-1j, -1 - 2j, 1, 2 - 1j], [-1 - 2j, 1, 2 - 1j, -1j], ]) FFTop = FFT2D( dims=x.shape, dirs=(0, 1), norm=norm, dtype=dtype, ) # Compute FFT of x independently y_true = np.array( [ [8 - 12j, -4, -4j, 4], [4j, 4 - 8j, -4j, 4], [4j, -4, 4j, 4], [4j, -4, -4j, 4 + 16j], ], dtype=FFTop.cdtype, ) # Backward if norm == "ortho": y_true /= 4 elif norm == "1/n": y_true /= 16 # Compute FFT with FFTop and compare with y_true y = FFTop * x.ravel() y = y.reshape(FFTop.dims_fft) assert_array_almost_equal(y, y_true, decimal=decimal) assert dottest(FFTop, *FFTop.shape, complexflag=3, tol=10**(-decimal)) x_inv = FFTop / y.ravel() x_inv = x_inv.reshape(x.shape) assert_array_almost_equal(x_inv, x, decimal=decimal)
def test_FFT2D(par): """Dot-test and inversion for FFT2D operator for 2d signal """ dt, dx = 0.005, 5 t = np.arange(par['nt']) * dt f0 = 10 nfft1, nfft2 = par['nfft'], par['nfft'] // 2 d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(par['nx']) + 1) FFTop = FFT2D(dims=(par['nt'], par['nx']), nffts=(nfft1, nfft2), sampling=(dt, dx)) assert dottest(FFTop, nfft1 * nfft2, 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=100, 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 _UpDownDecomposition2D_analytical(nt, nr, dt, dr, rho, vel, nffts=(None, None), critical=100., ntaper=10, dtype='complex128'): """Analytical up-down decomposition Parameters ---------- nt : :obj:`int` Number of samples along the time axis nr : :obj:`int` Number of samples along the receiver axis dt : :obj:`float` Sampling along the time axis dr : :obj:`float` Sampling along the receiver array rho : :obj:`float` Density along the receiver array (must be constant) vel : :obj:`float` Velocity along the receiver array (must be constant) nffts : :obj:`tuple`, optional Number of samples along the wavenumber and frequency axes critical : :obj:`float`, optional Percentage of angles to retain in obliquity factor. For example, if ``critical=100`` only angles below the critical angle :math`\frac{f(k_x)}{vel}` will be retained ntaper : :obj:`float`, optional Number of samples of taper applied to obliquity factor around critical angle dtype : :obj:`str`, optional Type of elements in input array. Returns ------- FFTop : :obj:`pylops.LinearOperator` FFT operator OBL : :obj:`np.ndarray` Filtered obliquity factor """ # obliquity factor nffts = (int(nffts[0]) if nffts[0] is not None else nr, int(nffts[1]) if nffts[1] is not None else nt) # create obliquity operator FFTop = FFT2D(dims=[nr, nt], nffts=nffts, sampling=[dr, dt], dtype=dtype) [Kx, F] = np.meshgrid(FFTop.f1, FFTop.f2, indexing='ij') k = F / vel Kz = np.sqrt((k**2 - Kx**2).astype(np.complex)) Kz[np.isnan(Kz)] = 0 OBL = rho * (np.abs(F) / Kz) OBL[Kz == 0] = 0 # cut off and taper OBL = _filter_obliquity(OBL, F, Kx, vel, critical, ntaper) return FFTop, OBL
def UpDownComposition2D(nt, nr, dt, dr, rho, vel, nffts=(None, None), critical=100., ntaper=10, scaling=1., dtype='complex128'): r"""2D Up-down wavefield composition. Apply multi-component seismic wavefield composition from its up- and down-going constituents. This input model required by the operator should be created by flattening the concatenated separated wavefields of size :math:`\lbrack n_r \times n_t \rbrack` along the spatial axis. Similarly, the data is also a concatenation of flattened pressure and vertical particle velocity wavefields. Parameters ---------- nt : :obj:`int` Number of samples along the time axis nr : :obj:`int` Number of samples along the receiver axis dt : :obj:`float` Sampling along the time axis dr : :obj:`float` Sampling along the receiver array rho : :obj:`float` Density along the receiver array (must be constant) vel : :obj:`float` Velocity along the receiver array (must be constant) nffts : :obj:`tuple`, optional Number of samples along the wavenumber and frequency axes critical : :obj:`float`, optional Percentage of angles to retain in obliquity factor. For example, if ``critical=100`` only angles below the critical angle :math`\frac{f(k_x)}{vel}` will be retained ntaper : :obj:`float`, optional Number of samples of taper applied to obliquity factor around critical angle scaling : :obj:`float`, optional Scaling to apply to the operator (see Notes for more details) dtype : :obj:`str`, optional Type of elements in input array. Returns ------- UDop : :obj:`pylops.LinearOperator` Up-down wavefield composition operator See Also -------- WavefieldDecomposition: Wavefield decomposition Notes ----- Multi-component seismic data (:math:`p(x, t)` and :math:`v_z(x, t)`) can be synthesized in the frequency-wavenumber domain as the superposition of the up- and downgoing constituents of the pressure wavefield (:math:`p^-(x, t)` and :math:`p^+(x, t)`) as follows [1]_: .. math:: \begin{bmatrix} \mathbf{p}(k_x, \omega) \\ \mathbf{v_z}(k_x, \omega) \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ \frac{k_z}{\omega \rho} & - \frac{k_z}{\omega \rho} \\ \end{bmatrix} \begin{bmatrix} \mathbf{p^+}(k_x, \omega) \\ \mathbf{p^-}(k_x, \omega) \end{bmatrix} which we can write in a compact matrix-vector notation as: .. math:: \begin{bmatrix} \mathbf{p} \\ s*\mathbf{v_z} \end{bmatrix} = \begin{bmatrix} \mathbf{F} & 0 \\ 0 & s*\mathbf{F} \end{bmatrix} \mathbf{W} \begin{bmatrix} \mathbf{F}^H & 0 \\ 0 & \mathbf{F}^H \end{bmatrix} \mathbf{p^{\pm}} where :math:`\mathbf{F}` is the 2-dimensional FFT (:class:`pylops.signalprocessing.FFT2`), :math:`\mathbf{W}` is a weighting matrix implemented via :class:`pylops.basicprocessing.Diagonal`, and :math:`s` is a scaling factor that is applied to both the particle velocity data and to the operator has shown above. Such a scaling is required to balance out the different dynamic range of pressure and particle velocity when solving the wavefield separation problem as an inverse problem. As the operator is effectively obtained by chaining basic PyLops operators the adjoint is automatically implemented for this operator. .. [1] Wapenaar, K. "Reciprocity properties of one-way propagators", Geophysics, vol. 63, pp. 1795-1798. 1998. """ nffts = (int(nffts[0]) if nffts[0] is not None else nr, int(nffts[1]) if nffts[1] is not None else nt) # create obliquity operator FFTop = FFT2D(dims=[nr, nt], nffts=nffts, sampling=[dr, dt], dtype=dtype) [Kx, F] = np.meshgrid(FFTop.f1, FFTop.f2, indexing='ij') k = F / vel Kz = np.sqrt((k**2 - Kx**2).astype(np.complex)) Kz[np.isnan(Kz)] = 0 OBL = Kz / (rho * np.abs(F)) OBL[F == 0] = 0 # cut off and taper OBL = _filter_obliquity(OBL, F, Kx, vel, critical, ntaper) OBLop = Diagonal(OBL.ravel(), dtype=dtype) # create up-down modelling operator UDop = (BlockDiag([FFTop.H, scaling*FFTop.H]) * \ Block([[Identity(nffts[0]*nffts[1], dtype=dtype), Identity(nffts[0]*nffts[1], dtype=dtype)], [OBLop, -OBLop]]) * \ BlockDiag([FFTop, FFTop])) return UDop
def SeismicInterpolation(data, nrec, iava, iava1=None, kind='fk', nffts=None, sampling=None, spataxis=None, spat1axis=None, taxis=None, paxis=None, p1axis=None, centeredh=True, nwins=None, nwin=None, nover=None, design=False, engine='numba', dottest=False, **kwargs_solver): r"""Seismic interpolation (or regularization). Interpolate seismic data from irregular to regular spatial grid. Depending on the size of the input ``data``, interpolation is either 2- or 3-dimensional. In case of 3-dimensional interpolation, data can be irregularly sampled in either one or both spatial directions. Parameters ---------- data : :obj:`np.ndarray` Irregularly sampled seismic data of size :math:`[n_{r_y} (\times n_{r_x} \times n_t)]` nrec : :obj:`int` or :obj:`tuple` Number of elements in the regularly sampled (reconstructed) spatial array, :math:`n_{R_y}` for 2-dimensional data and :math:`(n_{R_y}, n_{R_x})` for 3-dimensional data iava : :obj:`list` or :obj:`numpy.ndarray` Integer (or floating) indices of locations of available samples in first dimension of regularly sampled spatial grid of interpolated signal. The :class:`pylops.basicoperators.Restriction` operator is used in case of integer indices, while the :class:`pylops.signalprocessing.Iterp` operator is used in case of floating indices. iava1 : :obj:`list` or :obj:`numpy.ndarray`, optional Integer (or floating) indices of locations of available samples in second dimension of regularly sampled spatial grid of interpolated signal. Can be used only in case of 3-dimensional data. kind : :obj:`str`, optional Type of inversion: ``fk`` (default), ``spatial``, ``radon-linear``, ``chirpradon-linear``, ``radon-parabolic`` or , ``radon-hyperbolic`` and ``sliding`` nffts : :obj:`int` or :obj:`tuple`, optional nffts : :obj:`tuple`, optional Number of samples in Fourier Transform for each direction. Required if ``kind='fk'`` sampling : :obj:`tuple`, optional Sampling steps ``dy`` (, ``dx``) and ``dt``. Required if ``kind='fk'`` or ``kind='radon-linear'`` spataxis : :obj:`np.ndarray`, optional First spatial axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'``, can also be provided instead of ``sampling`` for ``kind='fk'`` spat1axis : :obj:`np.ndarray`, optional Second spatial axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'``, can also be provided instead of ``sampling`` for ``kind='fk'`` taxis : :obj:`np.ndarray`, optional Time axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'``, can also be provided instead of ``sampling`` for ``kind='fk'`` paxis : :obj:`np.ndarray`, optional First Radon axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'`` and ``kind='sliding'`` p1axis : :obj:`np.ndarray`, optional Second Radon axis. Required for ``kind='radon-linear'``, ``kind='chirpradon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'`` and ``kind='sliding'`` centeredh : :obj:`bool`, optional Assume centered spatial axis (``True``) or not (``False``). Required for ``kind='radon-linear'``, ``kind='radon-parabolic'`` and ``kind='radon-hyperbolic'`` nwins : :obj:`int` or :obj:`tuple`, optional Number of windows. Required for ``kind='sliding'`` nwin : :obj:`int` or :obj:`tuple`, optional Number of samples of window. Required for ``kind='sliding'`` nover : :obj:`int` or :obj:`tuple`, optional Number of samples of overlapping part of window. Required for ``kind='sliding'`` design : :obj:`bool`, optional Print number of sliding window (``True``) or not (``False``) when using ``kind='sliding'`` engine : :obj:`str`, optional Engine used for Radon computations (``numpy/numba`` for ``Radon2D`` and ``Radon3D`` or ``numpy/fftw`` for ``ChirpRadon2D`` and ``ChirpRadon3D`` or ) dottest : :obj:`bool`, optional Apply dot-test **kwargs_solver Arbitrary keyword arguments for :py:func:`pylops.optimization.leastsquares.RegularizedInversion` solver if ``kind='spatial'`` or :py:func:`pylops.optimization.sparsity.FISTA` solver otherwise Returns ------- recdata : :obj:`np.ndarray` Reconstructed data of size :math:`[n_{R_y} (\times n_{R_x} \times n_t)]` recprec : :obj:`np.ndarray` Reconstructed data in the sparse or preconditioned domain in case of ``kind='fk'``, ``kind='radon-linear'``, ``kind='radon-parabolic'``, ``kind='radon-hyperbolic'`` and ``kind='sliding'`` cost : :obj:`np.ndarray` Cost function norm Raises ------ KeyError If ``kind`` is neither ``spatial``, ``fl``, ``radon-linear``, ``radon-parabolic``, ``radon-hyperbolic`` nor ``sliding`` Notes ----- The problem of seismic data interpolation (or regularization) can be formally written as .. math:: \mathbf{y} = \mathbf{R} \mathbf{x} where a restriction or interpolation operator is applied along the spatial direction(s). Here :math:`\mathbf{y} = [\mathbf{y}_{R1}^T, \mathbf{y}_{R2}^T,..., \mathbf{y}_{RN^T}]^T` where each vector :math:`\mathbf{y}_{Ri}` contains all time samples recorded in the seismic data at the specific receiver :math:`R_i`. Similarly, :math:`\mathbf{x} = [\mathbf{x}_{r1}^T, \mathbf{x}_{r2}^T,..., \mathbf{x}_{rM}^T]`, contains all traces at the regularly and finely sampled receiver locations :math:`r_i`. Several alternative approaches can be taken to solve such a problem. They mostly differ in the choice of the regularization (or preconditining) used to mitigate the ill-posedness of the problem: * ``spatial``: least-squares inversion in the original time-space domain with an additional spatial smoothing regularization term, corresponding to the cost function :math:`J = ||\mathbf{y} - \mathbf{R} \mathbf{x}||_2 + \epsilon_\nabla \nabla ||\mathbf{x}||_2` where :math:`\nabla` is a second order space derivative implemented via :class:`pylops.basicoperators.SecondDerivative` in 2-dimensional case and :class:`pylops.basicoperators.Laplacian` in 3-dimensional case * ``fk``: L1 inversion in frequency-wavenumber preconditioned domain corresponding to the cost function :math:`J = ||\mathbf{y} - \mathbf{R} \mathbf{F} \mathbf{x}||_2` where :math:`\mathbf{F}` is frequency-wavenumber transform implemented via :class:`pylops.signalprocessing.FFT2D` in 2-dimensional case and :class:`pylops.signalprocessing.FFTND` in 3-dimensional case * ``radon-linear``: L1 inversion in linear Radon preconditioned domain using the same cost function as ``fk`` but with :math:`\mathbf{F}` being a Radon transform implemented via :class:`pylops.signalprocessing.Radon2D` in 2-dimensional case and :class:`pylops.signalprocessing.Radon3D` in 3-dimensional case * ``radon-parabolic``: L1 inversion in parabolic Radon preconditioned domain * ``radon-hyperbolic``: L1 inversion in hyperbolic Radon preconditioned domain * ``sliding``: L1 inversion in sliding-linear Radon preconditioned domain using the same cost function as ``fk`` but with :math:`\mathbf{F}` being a sliding Radon transform implemented via :class:`pylops.signalprocessing.Sliding2D` in 2-dimensional case and :class:`pylops.signalprocessing.Sliding3D` in 3-dimensional case """ ncp = get_array_module(data) dtype = data.dtype ndims = data.ndim if ndims == 1 or ndims > 3: raise ValueError('data must have 2 or 3 dimensions') if ndims == 2: dimsd = data.shape dims = (nrec, dimsd[1]) else: dimsd = data.shape dims = (nrec[0], nrec[1], dimsd[2]) # sampling if taxis is not None: dt = taxis[1] - taxis[0] if spataxis is not None: dspat = np.abs(spataxis[1] - spataxis[0]) if spat1axis is not None: dspat1 = np.abs(spat1axis[1] - spat1axis[0]) # create restriction/interpolation operator if iava.dtype == float: Rop = Interp(np.prod(dims), iava, dims=dims, dir=0, kind='linear', dtype=dtype) if ndims == 3 and iava1 is not None: dims1 = (len(iava), nrec[1], dimsd[2]) Rop1 = Interp(np.prod(dims1), iava1, dims=dims1, dir=1, kind='linear', dtype=dtype) Rop = Rop1 * Rop else: Rop = Restriction(np.prod(dims), iava, dims=dims, dir=0, dtype=dtype) if ndims == 3 and iava1 is not None: dims1 = (len(iava), nrec[1], dimsd[2]) Rop1 = Restriction(np.prod(dims1), iava1, dims=dims1, dir=1, dtype=dtype) Rop = Rop1 * Rop # create other operators for inversion if kind == 'spatial': prec = False dotcflag = 0 if ndims == 3 and iava1 is not None: Regop = Laplacian(dims=dims, dirs=(0, 1), dtype=dtype) else: Regop = SecondDerivative(np.prod(dims), dims=(dims), dir=0, dtype=dtype) SIop = Rop elif kind == 'fk': prec = True dimsp = nffts dotcflag = 1 if ndims == 3: if sampling is None: if spataxis is None or spat1axis is None or taxis is None: raise ValueError('Provide either sampling or spataxis, ' 'spat1axis and taxis for kind=%s' % kind) else: sampling = (np.abs(spataxis[1] - spataxis[1]), np.abs(spat1axis[1] - spat1axis[1]), np.abs(taxis[1] - taxis[1])) Pop = FFTND(dims=dims, nffts=nffts, sampling=sampling) Pop = Pop.H else: if sampling is None: if spataxis is None or taxis is None: raise ValueError('Provide either sampling or spataxis, ' 'and taxis for kind=%s' % kind) else: sampling = (np.abs(spataxis[1] - spataxis[1]), np.abs(taxis[1] - taxis[1])) Pop = FFT2D(dims=dims, nffts=nffts, sampling=sampling) Pop = Pop.H SIop = Rop * Pop elif 'chirpradon' in kind: prec = True dotcflag = 0 if ndims == 3: Pop = ChirpRadon3D( taxis, spataxis, spat1axis, (np.max(paxis) * dspat / dt, np.max(p1axis) * dspat1 / dt)).H dimsp = (spataxis.size, spat1axis.size, taxis.size) else: Pop = ChirpRadon2D(taxis, spataxis, np.max(paxis) * dspat / dt).H dimsp = (spataxis.size, taxis.size) SIop = Rop * Pop elif 'radon' in kind: prec = True dotcflag = 0 kindradon = kind.split('-')[-1] if ndims == 3: Pop = Radon3D(taxis, spataxis, spat1axis, paxis, p1axis, centeredh=centeredh, kind=kindradon, engine=engine) dimsp = (paxis.size, p1axis.size, taxis.size) else: Pop = Radon2D(taxis, spataxis, paxis, centeredh=centeredh, kind=kindradon, engine=engine) dimsp = (paxis.size, taxis.size) SIop = Rop * Pop elif kind == 'sliding': prec = True dotcflag = 0 if ndims == 3: nspat, nspat1 = spataxis.size, spat1axis.size spataxis_local = np.linspace(-dspat * nwin[0] // 2, dspat * nwin[0] // 2, nwin[0]) spat1axis_local = np.linspace(-dspat1 * nwin[1] // 2, dspat1 * nwin[1] // 2, nwin[1]) dimsslid = (nspat, nspat1, taxis.size) if ncp == np: npaxis, np1axis = paxis.size, p1axis.size Op = Radon3D(taxis, spataxis_local, spat1axis_local, paxis, p1axis, centeredh=True, kind='linear', engine=engine) else: npaxis, np1axis = nwin[0], nwin[1] Op = ChirpRadon3D(taxis, spataxis_local, spat1axis_local, (np.max(paxis) * dspat / dt, np.max(p1axis) * dspat1 / dt)).H dimsp = (nwins[0] * npaxis, nwins[1] * np1axis, dimsslid[2]) Pop = Sliding3D(Op, dimsp, dimsslid, nwin, nover, (npaxis, np1axis), tapertype='cosine') # to be able to reshape correctly the preconditioned model dimsp = (nwins[0], nwins[1], npaxis, np1axis, dimsslid[2]) else: nspat = spataxis.size spataxis_local = np.linspace(-dspat * nwin // 2, dspat * nwin // 2, nwin) dimsslid = (nspat, taxis.size) if ncp == np: npaxis = paxis.size Op = Radon2D(taxis, spataxis_local, paxis, centeredh=True, kind='linear', engine=engine) else: npaxis = nwin Op = ChirpRadon2D(taxis, spataxis_local, np.max(paxis) * dspat / dt).H dimsp = (nwins * npaxis, dimsslid[1]) Pop = Sliding2D(Op, dimsp, dimsslid, nwin, nover, tapertype='cosine', design=design) SIop = Rop * Pop else: raise KeyError('kind must be spatial, fk, radon-linear, ' 'radon-parabolic, radon-hyperbolic or sliding') # dot-test if dottest: Dottest(SIop, np.prod(dimsd), np.prod(dimsp) if prec else np.prod(dims), complexflag=dotcflag, raiseerror=True, verb=True) # inversion if kind == 'spatial': recdata = \ RegularizedInversion(SIop, [Regop], data.flatten(), **kwargs_solver) if isinstance(recdata, tuple): recdata = recdata[0] recdata = recdata.reshape(dims) recprec = None cost = None else: recprec = FISTA(SIop, data.flatten(), **kwargs_solver) if len(recprec) == 3: cost = recprec[2] else: cost = None recprec = recprec[0] recdata = np.real(Pop * recprec) recprec = recprec.reshape(dimsp) recdata = recdata.reshape(dims) return recdata, recprec, cost
def test_FFT2D(par): """Dot-test and inversion for FFT2D operator for 2d signal""" decimal = 3 if np.real(np.ones(1, par["dtype"])).dtype == np.float32 else 8 dt, dx = 0.005, 5 t = np.arange(par["nt"]) * dt f0 = 10 nfft1 = par["nt"] if par["nfft"] is None else par["nfft"] nfft2 = par["nx"] if par["nfft"] is None else par["nfft"] d = np.outer(np.sin(2 * np.pi * f0 * t), np.arange(par["nx"]) + 1) d = d.astype(par["dtype"]) # first fft on dir 1 FFTop = FFT2D( dims=(par["nt"], par["nx"]), nffts=(nfft1, nfft2), sampling=(dt, dx), real=par["real"], dirs=(0, 1), ) if par["real"]: assert dottest( FFTop, nfft1 * (nfft2 // 2 + 1), par["nt"] * par["nx"], complexflag=2, tol=10**(-decimal), ) else: assert dottest( FFTop, nfft1 * nfft2, par["nt"] * par["nx"], complexflag=2, tol=10**(-decimal), ) assert dottest( FFTop, nfft1 * nfft2, par["nt"] * par["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=100, show=0)[0] dadj = np.real(dadj).reshape(par["nt"], par["nx"]) dinv = np.real(dinv).reshape(par["nt"], par["nx"]) # check all signal if nt>nfft and only up to nfft if nfft<nt imax1 = par["nt"] if nfft1 is None else min([par["nt"], nfft1]) imax2 = par["nx"] if nfft2 is None else min([par["nx"], nfft2]) assert_array_almost_equal(d[:imax1, :imax2], dadj[:imax1, :imax2], decimal=decimal) assert_array_almost_equal(d[:imax1, :imax2], dinv[:imax1, :imax2], decimal=decimal) # first fft on dir 0 FFTop = FFT2D( dims=(par["nt"], par["nx"]), nffts=(nfft2, nfft1), sampling=(dx, dt), real=par["real"], dirs=(1, 0), ) if par["real"]: assert dottest( FFTop, nfft2 * (nfft1 // 2 + 1), par["nt"] * par["nx"], complexflag=2, tol=10**(-decimal), ) else: assert dottest( FFTop, nfft1 * nfft2, par["nt"] * par["nx"], complexflag=2, tol=10**(-decimal), ) assert dottest( FFTop, nfft1 * nfft2, par["nt"] * par["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=100, show=0)[0] dadj = np.real(dadj).reshape(par["nt"], par["nx"]) dinv = np.real(dinv).reshape(par["nt"], par["nx"]) # check all signal if nt>nfft and only up to nfft if nfft<nt assert_array_almost_equal(d[:imax1, :imax2], dadj[:imax1, :imax2], decimal=decimal) assert_array_almost_equal(d[:imax1, :imax2], dinv[:imax1, :imax2], decimal=decimal)
def _obliquity2D(nt, nr, dt, dr, rho, vel, nffts, critical=100., ntaper=10, composition=True, dtype='complex128'): """2D Obliquity operator and FFT operator Parameters ---------- nt : :obj:`int` Number of samples along the time axis nr : :obj:`int` Number of samples along the receiver axis dt : :obj:`float` Sampling along the time axis dr : :obj:`float` Sampling along the receiver array rho : :obj:`float` Density along the receiver array (must be constant) vel : :obj:`float` Velocity along the receiver array (must be constant) nffts : :obj:`tuple`, optional Number of samples along the wavenumber and frequency axes critical : :obj:`float`, optional Percentage of angles to retain in obliquity factor. For example, if ``critical=100`` only angles below the critical angle :math:`|k_x| < \frac{f(k_x)}{vel}` will be retained ntaper : :obj:`float`, optional Number of samples of taper applied to obliquity factor around critical angle composition : :obj:`bool`, optional Create obliquity factor for composition (``True``) or decomposition (``False``) dtype : :obj:`str`, optional Type of elements in input array. Returns ------- FFTop : :obj:`pylops.LinearOperator` FFT operator OBL : :obj:`np.ndarray` Filtered obliquity factor """ # create Fourier operator FFTop = FFT2D(dims=[nr, nt], nffts=nffts, sampling=[dr, dt], dtype=dtype) # create obliquity operator [Kx, F] = np.meshgrid(FFTop.f1, FFTop.f2, indexing='ij') k = F / vel Kz = np.sqrt((k**2 - Kx**2).astype(dtype)) Kz[np.isnan(Kz)] = 0 if composition: OBL = Kz / (rho * np.abs(F)) OBL[F == 0] = 0 else: OBL = rho * (np.abs(F) / Kz) OBL[Kz == 0] = 0 # cut off and taper OBL = _filter_obliquity(OBL, F, Kx, vel, critical, ntaper) OBLop = Diagonal(OBL.ravel(), dtype=dtype) return FFTop, OBLop
t, t2, x, y = makeaxis(PAR) wav = ricker(t[:41], f0=PAR['f0'])[0] # 2d data t0_plus = np.array([0.2, 0.5, 0.7]) t0_minus = t0_plus + 0.04 vrms = np.array([1400., 1500., 2000.]) amp = np.array([1., -0.6, 0.5]) vel_sep = 1000.0 # velocity at separation level rho_sep = 1000.0 # density at separation level _, p2d_minus = hyperbolic2d(x, t, t0_minus, vrms, amp, wav) _, p2d_plus = hyperbolic2d(x, t, t0_plus, vrms, amp, wav) FFTop = FFT2D(dims=[PAR['nx'], PAR['nt']], nffts=[nfft, nfft], sampling=[PAR['dx'], PAR['dt']]) [Kx, F] = np.meshgrid(FFTop.f1, FFTop.f2, indexing='ij') k = F / vel_sep Kz = np.sqrt((k**2 - Kx**2).astype(np.complex)) Kz[np.isnan(Kz)] = 0 OBL = rho_sep * (np.abs(F) / Kz) OBL[Kz == 0] = 0 mask = np.abs(Kx) < critical * np.abs(F) / vel_sep OBL *= mask OBL = filtfilt(np.ones(ntaper) / float(ntaper), 1, OBL, axis=0) OBL = filtfilt(np.ones(ntaper) / float(ntaper), 1, OBL, axis=1) UPop = \