Exemple #1
0
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))
Exemple #2
0
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)
Exemple #3
0
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))
Exemple #4
0
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)
Exemple #5
0
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
Exemple #9
0
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)
Exemple #10
0
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 = \