Пример #1
0
def PrestackLinearModelling(wav,
                            theta,
                            vsvp=0.5,
                            nt0=1,
                            spatdims=None,
                            linearization='akirich',
                            explicit=False,
                            kind='centered'):
    r"""Pre-stack linearized seismic modelling operator.

    Create operator to be applied to elastic property profiles
    for generation of band-limited seismic angle gathers from a
    linearized version of the Zoeppritz equation. The input model must
    be arranged in a vector of size :math:`n_m \times n_{t0} (\times n_x \times n_y)`
    for ``explicit=True`` and :math:`n_{t0} \times n_m  (\times n_x \times n_y)`
    for ``explicit=False``. Similarly the output data is arranged in a
    vector of size :math:`n_{theta} \times n_{t0}  (\times n_x \times n_y)`
    for ``explicit=True`` and :math:`n_{t0} \times n_{theta} (\times n_x \times n_y)`
    for ``explicit=False``.

    Parameters
    ----------
    wav : :obj:`np.ndarray`
        Wavelet in time domain (must had odd number of
        elements and centered to zero). Note that the ``dtype`` of this
        variable will define that of the operator
    theta : :obj:`np.ndarray`
        Incident angles in degrees. Must have same ``dtype`` of ``wav`` (or
        it will be automatically casted to it)
    vsvp : :obj:`float` or :obj:`np.ndarray`
        VS/VP ratio (constant or time/depth variant)
    nt0 : :obj:`int`, optional
        number of samples (if ``vsvp`` is a scalar)
    spatdims : :obj:`int` or :obj:`tuple`, optional
        Number of samples along spatial axis (or axes)
        (``None`` if only one dimension is available)
    linearization : :obj:`str` or :obj:`func`, optional
        choice of linearization, ``akirich``: Aki-Richards, ``fatti``: Fatti,
        ``ps``: PS or any function on the form of
        ``pylops.avo.avo.akirichards``
    explicit : :obj:`bool`, optional
        Create a chained linear operator (``False``, preferred for large data)
        or a ``MatrixMult`` linear operator with dense matrix
        (``True``, preferred for small data)
    kind : :obj:`str`, optional
        Derivative kind (``forward`` or ``centered``).

    Returns
    -------
    Preop : :obj:`LinearOperator`
        pre-stack modelling operator.

    Raises
    ------
    NotImplementedError
        If ``linearization`` is not an implemented linearization
    NotImplementedError
        If ``kind`` is not ``forward`` nor ``centered``

    Notes
    -----
    Pre-stack seismic modelling is the process of constructing seismic
    pre-stack data from three (or two) profiles of elastic parameters in time
    (or depth) domain. This can be easily achieved using the following
    forward model:

    .. math::
        d(t, \theta) = w(t) * \sum_{i=1}^{n_m} G_i(t, \theta) m_i(t)

    where :math:`w(t)` is the time domain seismic wavelet. In compact form:

    .. math::
        \mathbf{d}= \mathbf{G} \mathbf{m}

    On the other hand, pre-stack inversion aims at recovering the different
    profiles of elastic properties from the band-limited seismic
    pre-stack data.

    """
    ncp = get_array_module(wav)

    # check kind is correctly selected
    if kind not in ['forward', 'centered']:
        raise NotImplementedError('%s not an available derivative kind...' %
                                  kind)
    # define dtype to be used
    dtype = theta.dtype  # ensure theta.dtype rules that of operator
    theta = theta.astype(dtype)

    # create vsvp profile
    vsvp = vsvp if isinstance(vsvp, ncp.ndarray) else \
        vsvp * ncp.ones(nt0, dtype=dtype)
    nt0 = len(vsvp)
    ntheta = len(theta)

    # organize dimensions
    if spatdims is None:
        dims = (nt0, ntheta)
        spatdims = None
    elif isinstance(spatdims, int):
        dims = (nt0, ntheta, spatdims)
        spatdims = (spatdims, )
    else:
        dims = (nt0, ntheta) + spatdims

    if explicit:
        # Create AVO operator
        if linearization == 'akirich':
            G = akirichards(theta, vsvp, n=nt0)
        elif linearization == 'fatti':
            G = fatti(theta, vsvp, n=nt0)
        elif linearization == 'ps':
            G = ps(theta, vsvp, n=nt0)
        elif callable(linearization):
            G = linearization(theta, vsvp, n=nt0)
        else:
            logging.error('%s not an available linearization...',
                          linearization)
            raise NotImplementedError('%s not an available linearization...' %
                                      linearization)
        nG = len(G)
        G = [
            ncp.hstack([
                ncp.diag(G_[itheta] * ncp.ones(nt0, dtype=dtype)) for G_ in G
            ]) for itheta in range(ntheta)
        ]
        G = ncp.vstack(G).reshape(ntheta * nt0, nG * nt0)

        # Create derivative operator
        if kind == 'centered':
            D = ncp.diag(0.5 * ncp.ones(nt0 - 1, dtype=dtype), k=1) - \
                ncp.diag(0.5 * ncp.ones(nt0 - 1, dtype=dtype), k=-1)
            D[0] = D[-1] = 0
        else:
            D = ncp.diag(ncp.ones(nt0 - 1, dtype=dtype), k=1) - \
                ncp.diag(ncp.ones(nt0, dtype=dtype), k=0)
            D[-1] = 0
        D = get_block_diag(theta)(*([D] * nG))

        # Create wavelet operator
        C = convmtx(wav, nt0)[:, len(wav) // 2:-len(wav) // 2 + 1]
        C = [C] * ntheta
        C = get_block_diag(theta)(*C)

        # Combine operators
        M = ncp.dot(C, ncp.dot(G, D))
        Preop = MatrixMult(M, dims=spatdims, dtype=dtype)

    else:
        # Create wavelet operator
        Cop = Convolve1D(np.prod(np.array(dims)),
                         h=wav,
                         offset=len(wav) // 2,
                         dir=0,
                         dims=dims,
                         dtype=dtype)

        # create AVO operator
        AVOop = AVOLinearModelling(theta,
                                   vsvp,
                                   spatdims=spatdims,
                                   linearization=linearization,
                                   dtype=dtype)

        # Create derivative operator
        dimsm = list(dims)
        dimsm[1] = AVOop.npars
        Dop = FirstDerivative(np.prod(np.array(dimsm)),
                              dims=dimsm,
                              dir=0,
                              sampling=1.,
                              kind=kind,
                              dtype=dtype)
        Preop = Cop * AVOop * Dop
    return Preop
Пример #2
0
def PrestackInversion(data,
                      theta,
                      wav,
                      m0=None,
                      linearization='akirich',
                      explicit=False,
                      simultaneous=False,
                      epsI=None,
                      epsR=None,
                      dottest=False,
                      returnres=False,
                      epsRL1=None,
                      kind='centered',
                      **kwargs_solver):
    r"""Pre-stack linearized seismic inversion.

    Invert pre-stack seismic operator to retrieve a set of elastic property
    profiles from band-limited seismic pre-stack data (i.e., angle gathers).
    Depending on the choice of input parameters, inversion can be
    trace-by-trace with explicit operator or global with either
    explicit or linear operator.

    Parameters
    ----------
    data : :obj:`np.ndarray`
        Band-limited seismic post-stack data of size
        :math:`[(n_{lins} \times) n_{t0} \times n_{\theta} (\times n_x \times n_y)]`
    theta : :obj:`np.ndarray`
        Incident angles in degrees
    wav : :obj:`np.ndarray`
        Wavelet in time domain (must had odd number of elements
        and centered to zero)
    m0 : :obj:`np.ndarray`, optional
        Background model of size :math:`[n_{t0} \times n_{m}
        (\times n_x \times n_y)]`
    linearization : :obj:`str` or :obj:`list`, optional
        choice of linearization, ``akirich``: Aki-Richards, ``fatti``: Fatti
        ``PS``: PS or a combination of them (required only when ``m0``
        is ``None``).
    explicit : :obj:`bool`, optional
        Create a chained linear operator (``False``, preferred for large data)
        or a ``MatrixMult`` linear operator with dense matrix
        (``True``, preferred for small data)
    simultaneous : :obj:`bool`, optional
        Simultaneously invert entire data (``True``) or invert
        trace-by-trace (``False``) when using ``explicit`` operator
        (note that the entire data is always inverted when working
        with linear operator)
    epsI : :obj:`float` or :obj:`list`, optional
        Damping factor(s) for Tikhonov regularization term. If a list of
        :math:`n_{m}` elements is provided, the regularization term will have
        different strenght for each elastic property
    epsR : :obj:`float`, optional
        Damping factor for additional Laplacian regularization term
    dottest : :obj:`bool`, optional
        Apply dot-test
    returnres : :obj:`bool`, optional
        Return residuals
    epsRL1 : :obj:`float`, optional
        Damping factor for additional blockiness regularization term
    kind : :obj:`str`, optional
        Derivative kind (``forward`` or ``centered``).
    **kwargs_solver
        Arbitrary keyword arguments for :py:func:`scipy.linalg.lstsq`
        solver (if ``explicit=True`` and  ``epsR=None``)
        or :py:func:`scipy.sparse.linalg.lsqr` solver (if ``explicit=False``
        and/or ``epsR`` is not ``None``))

    Returns
    -------
    minv : :obj:`np.ndarray`
        Inverted model of size :math:`[n_{t0} \times n_{m}
        (\times n_x \times n_y)]`
    datar : :obj:`np.ndarray`
        Residual data (i.e., data - background data) of
        size :math:`[n_{t0} \times n_{\theta} (\times n_x \times n_y)]`

    Notes
    -----
    The different choices of cost functions and solvers used in the
    seismic pre-stack inversion module follow the same convention of the
    seismic post-stack inversion module.

    Refer to :py:func:`pylops.avo.poststack.PoststackInversion` for
    more details.
    """
    ncp = get_array_module(data)

    # find out dimensions
    if m0 is None and linearization is None:
        raise NotImplementedError('either m0 or linearization '
                                  'must be provided')
    elif m0 is None:
        if isinstance(linearization, str):
            nm = _linearizations[linearization]
        else:
            nm = _linearizations[linearization[0]]
    else:
        nm = m0.shape[1]

    data_shape = data.shape
    data_ndim = data.ndim
    n_lins = 1
    multi = 0
    if not isinstance(linearization, str):
        n_lins = data_shape[0]
        data_shape = data_shape[1:]
        data_ndim -= 1
        multi = 1

    if data_ndim == 2:
        dims = 1
        nt0, ntheta = data_shape
        nspat = None
        nspatprod = nx = 1
    elif data_ndim == 3:
        dims = 2
        nt0, ntheta, nx = data_shape
        nspat = (nx, )
        nspatprod = nx
    else:
        dims = 3
        nt0, ntheta, nx, ny = data_shape
        nspat = (nx, ny)
        nspatprod = nx * ny
        data = data.reshape(nt0, ntheta, nspatprod)

    # check if background model and data have same shape
    if m0 is not None:
        if nt0 != m0.shape[0] or\
        (dims >= 2 and nx != m0.shape[2]) or\
        (dims == 3 and ny != m0.shape[3]):
            raise ValueError('data and m0 must have same time and space axes')

    # create operator
    if isinstance(linearization, str):
        # single operator
        PPop = PrestackLinearModelling(wav,
                                       theta,
                                       nt0=nt0,
                                       spatdims=nspat,
                                       linearization=linearization,
                                       explicit=explicit,
                                       kind=kind)
    else:
        # multiple operators
        if not isinstance(wav, (list, tuple)):
            wav = [
                wav,
            ] * n_lins
        PPop = [
            PrestackLinearModelling(w,
                                    theta,
                                    nt0=nt0,
                                    spatdims=nspat,
                                    linearization=lin,
                                    explicit=explicit)
            for w, lin in zip(wav, linearization)
        ]
        if explicit:
            PPop = MatrixMult(np.vstack([Op.A for Op in PPop]),
                              dims=nspat,
                              dtype=PPop[0].A.dtype)
        else:
            PPop = VStack(PPop)

    if dottest:
        Dottest(PPop,
                n_lins * nt0 * ntheta * nspatprod,
                nt0 * nm * nspatprod,
                raiseerror=True,
                verb=True,
                backend=get_module_name(ncp))

    # swap axes for explicit operator
    if explicit:
        data = data.swapaxes(0 + multi, 1 + multi)
        if m0 is not None:
            m0 = m0.swapaxes(0, 1)

    # invert model
    if epsR is None:
        # create and remove background data from original data
        datar = data.flatten() if m0 is None else \
            data.flatten() - PPop * m0.flatten()
        # inversion without spatial regularization
        if explicit:
            if epsI is None and not simultaneous:
                # solve unregularized equations indipendently trace-by-trace
                minv = \
                    get_lstsq(data)(PPop.A,
                                    datar.reshape(n_lins*nt0*ntheta, nspatprod).squeeze(),
                                    **kwargs_solver)[0]
            elif epsI is None and simultaneous:
                # solve unregularized equations simultaneously
                if ncp == np:
                    minv = lsqr(PPop, datar, **kwargs_solver)[0]
                else:
                    minv = cgls(PPop,
                                datar,
                                x0=ncp.zeros(int(PPop.shape[1]), PPop.dtype),
                                **kwargs_solver)[0]
            elif epsI is not None:
                # create regularized normal equations
                PP = ncp.dot(PPop.A.T, PPop.A) + \
                     epsI * ncp.eye(nt0*nm, dtype=PPop.A.dtype)
                datarn = np.dot(PPop.A.T,
                                datar.reshape(nt0 * ntheta, nspatprod))
                if not simultaneous:
                    # solve regularized normal eqs. trace-by-trace
                    minv = get_lstsq(data)(PP, datarn, **kwargs_solver)[0]
                else:
                    # solve regularized normal equations simultaneously
                    PPop_reg = MatrixMult(PP, dims=nspatprod)
                    if ncp == np:
                        minv = lsqr(PPop_reg, datarn.ravel(),
                                    **kwargs_solver)[0]
                    else:
                        minv = cgls(PPop_reg,
                                    datarn.ravel(),
                                    x0=ncp.zeros(int(PPop_reg.shape[1]),
                                                 PPop_reg.dtype),
                                    **kwargs_solver)[0]
            #else:
            #    # create regularized normal eqs. and solve them simultaneously
            #    PP = np.dot(PPop.A.T, PPop.A) + epsI * np.eye(nt0*nm)
            #    datarn = PPop.A.T * datar.reshape(nt0*ntheta, nspatprod)
            #    PPop_reg = MatrixMult(PP, dims=ntheta*nspatprod)
            #    minv = lstsq(PPop_reg, datarn.flatten(), **kwargs_solver)[0]
        else:
            # solve unregularized normal equations simultaneously with lop
            if ncp == np:
                minv = lsqr(PPop, datar, **kwargs_solver)[0]
            else:
                minv = cgls(PPop,
                            datar,
                            x0=ncp.zeros(int(PPop.shape[1]), PPop.dtype),
                            **kwargs_solver)[0]
    else:
        # Create Thicknov regularization
        if epsI is not None:
            if isinstance(epsI, (list, tuple)):
                if len(epsI) != nm:
                    raise ValueError('epsI must be a scalar or a list of'
                                     'size nm')
                RegI = Diagonal(np.array(epsI),
                                dims=(nt0, nm, nspatprod),
                                dir=1)
            else:
                RegI = epsI * Identity(nt0 * nm * nspatprod)

        if epsRL1 is None:
            # L2 inversion with spatial regularization
            if dims == 1:
                Regop = SecondDerivative(nt0 * nm,
                                         dtype=PPop.dtype,
                                         dims=(nt0, nm))
            elif dims == 2:
                Regop = Laplacian((nt0, nm, nx), dirs=(0, 2), dtype=PPop.dtype)
            else:
                Regop = Laplacian((nt0, nm, nx, ny),
                                  dirs=(2, 3),
                                  dtype=PPop.dtype)
            if epsI is None:
                Regop = (Regop, )
                epsR = (epsR, )
            else:
                Regop = (Regop, RegI)
                epsR = (epsR, 1)
            minv = \
                RegularizedInversion(PPop, Regop, data.ravel(),
                                     x0=m0.flatten() if m0 is not None
                                     else None, epsRs=epsR,
                                     returninfo=False, **kwargs_solver)
        else:
            # Blockiness-promoting inversion with spatial regularization
            if dims == 1:
                RegL1op = FirstDerivative(nt0 * nm, dtype=PPop.dtype)
                RegL2op = None
            elif dims == 2:
                RegL1op = FirstDerivative(nt0 * nx * nm,
                                          dims=(nt0, nm, nx),
                                          dir=0,
                                          dtype=PPop.dtype)
                RegL2op = SecondDerivative(nt0 * nx * nm,
                                           dims=(nt0, nm, nx),
                                           dir=2,
                                           dtype=PPop.dtype)
            else:
                RegL1op = FirstDerivative(nt0 * nx * ny * nm,
                                          dims=(nt0, nm, nx, ny),
                                          dir=0,
                                          dtype=PPop.dtype)
                RegL2op = Laplacian((nt0, nm, nx, ny),
                                    dirs=(2, 3),
                                    dtype=PPop.dtype)
            if dims == 1:
                if epsI is not None:
                    RegL2op = (RegI, )
                    epsR = (1, )
            else:
                if epsI is None:
                    RegL2op = (RegL2op, )
                    epsR = (epsR, )
                else:
                    RegL2op = (RegL2op, RegI)
                    epsR = (epsR, 1)
            epsRL1 = (epsRL1, )
            if 'mu' in kwargs_solver.keys():
                mu = kwargs_solver['mu']
                kwargs_solver.pop('mu')
            else:
                mu = 1.
            if 'niter_outer' in kwargs_solver.keys():
                niter_outer = kwargs_solver['niter_outer']
                kwargs_solver.pop('niter_outer')
            else:
                niter_outer = 3
            if 'niter_inner' in kwargs_solver.keys():
                niter_inner = kwargs_solver['niter_inner']
                kwargs_solver.pop('niter_inner')
            else:
                niter_inner = 5
            minv = SplitBregman(PPop, (RegL1op, ),
                                data.ravel(),
                                RegsL2=RegL2op,
                                epsRL1s=epsRL1,
                                epsRL2s=epsR,
                                mu=mu,
                                niter_outer=niter_outer,
                                niter_inner=niter_inner,
                                x0=None if m0 is None else m0.flatten(),
                                **kwargs_solver)[0]

    # compute residual
    if returnres:
        if epsR is None:
            datar -= PPop * minv.ravel()
        else:
            datar = data.ravel() - PPop * minv.ravel()

    # re-swap axes for explicit operator
    if explicit:
        if m0 is not None:
            m0 = m0.swapaxes(0, 1)

    # reshape inverted model and residual data
    if dims == 1:
        if explicit:
            minv = minv.reshape(nm, nt0).swapaxes(0, 1)
            if returnres:
                datar = datar.reshape(n_lins, ntheta, nt0).squeeze().swapaxes(
                    0 + multi, 1 + multi)
        else:
            minv = minv.reshape(nt0, nm)
            if returnres:
                datar = datar.reshape(n_lins, nt0, ntheta).squeeze()
    elif dims == 2:
        if explicit:
            minv = minv.reshape(nm, nt0, nx).swapaxes(0, 1)
            if returnres:
                datar = datar.reshape(n_lins, ntheta, nt0,
                                      nx).squeeze().swapaxes(
                                          0 + multi, 1 + multi)
        else:
            minv = minv.reshape(nt0, nm, nx)
            if returnres:
                datar = datar.reshape(n_lins, nt0, ntheta, nx).squeeze()
    else:
        if explicit:
            minv = minv.reshape(nm, nt0, nx, ny).swapaxes(0, 1)
            if returnres:
                datar = datar.reshape(n_lins, ntheta, nt0, nx,
                                      ny).squeeze().swapaxes(
                                          0 + multi, 1 + multi)
        else:
            minv = minv.reshape(nt0, nm, nx, ny)
            if returnres:
                datar = datar.reshape(n_lins, nt0, ntheta, nx, ny).squeeze()

    if m0 is not None and epsR is None:
        minv = minv + m0

    if returnres:
        return minv, datar
    else:
        return minv
Пример #3
0
def PoststackLinearModelling(wav, nt0, ndims=None, explicit=False):
    r"""Post-stack linearized seismic modelling operator.

    Create operator to be applied to acoustic impedance profile
    for generation of band-limited seismic post-stack data.

    Parameters
    ----------
    wav : :obj:`np.ndarray`
        Wavelet in time domain (must had odd number of elements
        and centered to zero)
    nt0 : :obj:`int`
        Number of samples along time axis
    ndims : :obj:`int` or :obj:`tuple`
        Number of samples along horizontal axws
    explicit : :obj:`bool`, optional
        Create a chained linear operator (``False``, preffered for large data)
        or a ``MatrixMult`` linear operator with dense matrix (``True``,
        preffered for small data)

    Returns
    -------
    Postop : :obj:`LinearOperator`
        post-stack modelling operator.

    Notes
    -----
    Post-stack seismic modelling is the process of constructing
    seismic post-stack data from a profile of acoustic impedance in
    time (or depth) domain. This can be easily achieved using the
    following forward model:

    .. math::
        d(t, \theta=0) = w(t) * \frac{dln(AI(t))}{dt}

    where :math:`AI(t)` is the acoustic impedance profile and
    :math:`w(t)` is the time domain seismic wavelet. In compact form:

    .. math::
        \mathbf{d}= \mathbf{W} \mathbf{AI}

    On the other hand, post-stack inversion aims at recovering
    the impedance profile from the band-limited seismic stack data.

    """
    if ndims is None:
        nspat = (1, )
        ndims = (nt0, )
    elif isinstance(ndims, int):
        nspat = (ndims, )
        ndims = (nt0, ndims)
    else:
        nspat = ndims
        ndims = (nt0, ) + ndims
    if explicit:
        # Create derivative operator
        D = np.diag(0.5 * np.ones(nt0 - 1), k=1) - \
            np.diag(0.5 * np.ones(nt0 - 1), -1)
        D[0] = D[-1] = 0

        # Create wavelet operator
        C = convmtx(wav, nt0)[:, len(wav) // 2:-len(wav) // 2 + 1]

        # Combine operators
        M = np.dot(C, D)
        return MatrixMult(M, dims=nspat)
    else:
        # Create wavelet operator
        Cop = Convolve1D(np.prod(np.array(ndims)),
                         h=wav,
                         offset=len(wav) // 2,
                         dir=0,
                         dims=ndims)

        # Create derivative operator
        Dop = FirstDerivative(np.prod(np.array(ndims)),
                              dims=ndims,
                              dir=0,
                              sampling=1.)

        return Cop * Dop
Пример #4
0
def PoststackInversion(data,
                       wav,
                       m0=None,
                       explicit=False,
                       simultaneous=False,
                       epsI=None,
                       epsR=None,
                       dottest=False,
                       epsRL1=None,
                       **kwargs_solver):
    r"""Post-stack linearized seismic inversion.

    Invert post-stack seismic operator to retrieve an elastic parameter of
    choice from band-limited seismic post-stack data.
    Depending on the choice of input parameters, inversion can be
    trace-by-trace with explicit operator or global with either
    explicit or linear operator.

    Parameters
    ----------
    data : :obj:`np.ndarray`
        Band-limited seismic post-stack data of size
        :math:`[n_{t0} (\times n_x \times n_y)]`
    wav : :obj:`np.ndarray`
        Wavelet in time domain (must have odd number of elements
        and centered to zero). If 1d, assume stationary wavelet for the entire
        time axis. If 2d of size :math:`[n_{t0} \times n_h]` use as
        non-stationary wavelet
    m0 : :obj:`np.ndarray`, optional
        Background model of size :math:`[n_{t0} (\times n_x \times n_y)]`
    explicit : :obj:`bool`, optional
        Create a chained linear operator (``False``, preferred for large data)
        or a ``MatrixMult`` linear operator with dense matrix
        (``True``, preferred for small data)
    simultaneous : :obj:`bool`, optional
        Simultaneously invert entire data (``True``) or invert
        trace-by-trace (``False``) when using ``explicit`` operator
        (note that the entire data is always inverted when working
        with linear operator)
    epsI : :obj:`float`, optional
        Damping factor for Tikhonov regularization term
    epsR : :obj:`float`, optional
        Damping factor for additional Laplacian regularization term
    dottest : :obj:`bool`, optional
        Apply dot-test
    epsRL1 : :obj:`float`, optional
        Damping factor for additional blockiness regularization term
    **kwargs_solver
        Arbitrary keyword arguments for :py:func:`scipy.linalg.lstsq`
        solver (if ``explicit=True`` and  ``epsR=None``)
        or :py:func:`scipy.sparse.linalg.lsqr` solver (if ``explicit=False``
        and/or ``epsR`` is not ``None``)

    Returns
    -------
    minv : :obj:`np.ndarray`
        Inverted model of size :math:`[n_{t0} (\times n_x \times n_y)]`
    datar : :obj:`np.ndarray`
        Residual data (i.e., data - background data) of
        size :math:`[n_{t0} (\times n_x \times n_y)]`

    Notes
    -----
    The cost function and solver used in the seismic post-stack inversion
    module depends on the choice of ``explicit``, ``simultaneous``, ``epsI``,
    and ``epsR`` parameters:

    * ``explicit=True``, ``epsI=None`` and ``epsR=None``: the explicit
      solver :py:func:`scipy.linalg.lstsq` is used if ``simultaneous=False``
      (or the iterative solver :py:func:`scipy.sparse.linalg.lsqr` is used
      if ``simultaneous=True``)
    * ``explicit=True`` with ``epsI`` and ``epsR=None``: the regularized
      normal equations :math:`\mathbf{W}^T\mathbf{d} = (\mathbf{W}^T
      \mathbf{W} + \epsilon_I^2 \mathbf{I}) \mathbf{AI}` are instead fed
      into the :py:func:`scipy.linalg.lstsq` solver if ``simultaneous=False``
      (or the iterative solver :py:func:`scipy.sparse.linalg.lsqr`
      if ``simultaneous=True``)
    * ``explicit=False`` and ``epsR=None``: the iterative solver
      :py:func:`scipy.sparse.linalg.lsqr` is used
    * ``explicit=False`` with ``epsR`` and ``epsRL1=None``: the iterative
      solver :py:func:`pylops.optimization.leastsquares.RegularizedInversion`
      is used to solve the spatially regularized problem.
    * ``explicit=False`` with ``epsR`` and ``epsRL1``: the iterative
      solver :py:func:`pylops.optimization.sparsity.SplitBregman`
      is used to solve the blockiness-promoting (in vertical direction)
      and spatially regularized (in additional horizontal directions) problem.

    Note that the convergence of iterative solvers such as
    :py:func:`scipy.sparse.linalg.lsqr` can be very slow for this type of
    operator. It is suggested to take a two steps approach with first a
    trace-by-trace inversion using the explicit operator, followed by a
    regularized global inversion using the outcome of the previous
    inversion as initial guess.
    """
    ncp = get_array_module(wav)

    # check if background model and data have same shape
    if m0 is not None and data.shape != m0.shape:
        raise ValueError('data and m0 must have same shape')

    # find out dimensions
    if data.ndim == 1:
        dims = 1
        nt0 = data.size
        nspat = None
        nspatprod = nx = 1
    elif data.ndim == 2:
        dims = 2
        nt0, nx = data.shape
        nspat = (nx, )
        nspatprod = nx
    else:
        dims = 3
        nt0, nx, ny = data.shape
        nspat = (nx, ny)
        nspatprod = nx * ny
        data = data.reshape(nt0, nspatprod)

    # create operator
    PPop = PoststackLinearModelling(wav,
                                    nt0=nt0,
                                    spatdims=nspat,
                                    explicit=explicit)
    if dottest:
        Dottest(PPop,
                nt0 * nspatprod,
                nt0 * nspatprod,
                raiseerror=True,
                backend=get_module_name(ncp),
                verb=True)

    # create and remove background data from original data
    datar = data.flatten() if m0 is None else \
        data.flatten() - PPop * m0.flatten()
    # invert model
    if epsR is None:
        # inversion without spatial regularization
        if explicit:
            if epsI is None and not simultaneous:
                # solve unregularized equations indipendently trace-by-trace
                minv = \
                get_lstsq(data)(PPop.A, datar.reshape(nt0, nspatprod).squeeze(),
                                **kwargs_solver)[0]
            elif epsI is None and simultaneous:
                # solve unregularized equations simultaneously
                if ncp == np:
                    minv = lsqr(PPop, datar, **kwargs_solver)[0]
                else:
                    minv = \
                        cgls(PPop, datar,
                             x0=ncp.zeros(int(PPop.shape[1]), PPop.dtype),
                             **kwargs_solver)[0]
            elif epsI is not None:
                # create regularized normal equations
                PP = ncp.dot(PPop.A.T, PPop.A) + \
                     epsI * ncp.eye(nt0, dtype=PPop.A.dtype)
                datarn = ncp.dot(PPop.A.T, datar.reshape(nt0, nspatprod))
                if not simultaneous:
                    # solve regularized normal eqs. trace-by-trace
                    minv = get_lstsq(data)(PP, datarn, **kwargs_solver)[0]
                else:
                    # solve regularized normal equations simultaneously
                    PPop_reg = MatrixMult(PP, dims=nspatprod)
                    if ncp == np:
                        minv = lsqr(PPop_reg, datar.ravel(),
                                    **kwargs_solver)[0]
                    else:
                        minv = cgls(PPop_reg,
                                    datar.ravel(),
                                    x0=ncp.zeros(int(PPop_reg.shape[1]),
                                                 PPop_reg.dtype),
                                    **kwargs_solver)[0]
            else:
                # create regularized normal eqs. and solve them simultaneously
                PP = ncp.dot(PPop.A.T, PPop.A) + \
                     epsI * ncp.eye(nt0, dtype=PPop.A.dtype)
                datarn = PPop.A.T * datar.reshape(nt0, nspatprod)
                PPop_reg = MatrixMult(PP, dims=nspatprod)
                minv = \
                    get_lstsq(data)(PPop_reg.A, datarn.flatten(),
                                    **kwargs_solver)[0]
        else:
            # solve unregularized normal equations simultaneously with lop
            if ncp == np:
                minv = lsqr(PPop, datar, **kwargs_solver)[0]
            else:
                minv = \
                    cgls(PPop, datar,
                         x0=ncp.zeros(int(PPop.shape[1]), PPop.dtype),
                         **kwargs_solver)[0]
    else:
        if epsRL1 is None:
            # L2 inversion with spatial regularization
            if dims == 1:
                Regop = SecondDerivative(nt0, dtype=PPop.dtype)
            elif dims == 2:
                Regop = Laplacian((nt0, nx), dtype=PPop.dtype)
            else:
                Regop = Laplacian((nt0, nx, ny), dirs=(1, 2), dtype=PPop.dtype)

            minv = RegularizedInversion(
                PPop, [Regop],
                data.flatten(),
                x0=None if m0 is None else m0.flatten(),
                epsRs=[epsR],
                returninfo=False,
                **kwargs_solver)
        else:
            # Blockiness-promoting inversion with spatial regularization
            if dims == 1:
                RegL1op = FirstDerivative(nt0,
                                          kind='forward',
                                          dtype=PPop.dtype)
                RegL2op = None
            elif dims == 2:
                RegL1op = FirstDerivative(nt0 * nx,
                                          dims=(nt0, nx),
                                          dir=0,
                                          kind='forward',
                                          dtype=PPop.dtype)
                RegL2op = SecondDerivative(nt0 * nx,
                                           dims=(nt0, nx),
                                           dir=1,
                                           dtype=PPop.dtype)
            else:
                RegL1op = FirstDerivative(nt0 * nx * ny,
                                          dims=(nt0, nx, ny),
                                          dir=0,
                                          kind='forward',
                                          dtype=PPop.dtype)
                RegL2op = Laplacian((nt0, nx, ny),
                                    dirs=(1, 2),
                                    dtype=PPop.dtype)

            if 'mu' in kwargs_solver.keys():
                mu = kwargs_solver['mu']
                kwargs_solver.pop('mu')
            else:
                mu = 1.
            if 'niter_outer' in kwargs_solver.keys():
                niter_outer = kwargs_solver['niter_outer']
                kwargs_solver.pop('niter_outer')
            else:
                niter_outer = 3
            if 'niter_inner' in kwargs_solver.keys():
                niter_inner = kwargs_solver['niter_inner']
                kwargs_solver.pop('niter_inner')
            else:
                niter_inner = 5
            if not isinstance(epsRL1, (list, tuple)):
                epsRL1 = list([epsRL1])
            if not isinstance(epsR, (list, tuple)):
                epsR = list([epsR])
            minv = SplitBregman(PPop, [RegL1op],
                                data.ravel(),
                                RegsL2=[RegL2op],
                                epsRL1s=epsRL1,
                                epsRL2s=epsR,
                                mu=mu,
                                niter_outer=niter_outer,
                                niter_inner=niter_inner,
                                x0=None if m0 is None else m0.flatten(),
                                **kwargs_solver)[0]

    # compute residual
    if epsR is None:
        datar -= PPop * minv.ravel()
    else:
        datar = data.ravel() - PPop * minv.ravel()

    # reshape inverted model and residual data
    if dims == 1:
        minv = minv.squeeze()
        datar = datar.squeeze()
    elif dims == 2:
        minv = minv.reshape(nt0, nx)
        datar = datar.reshape(nt0, nx)
    else:
        minv = minv.reshape(nt0, nx, ny)
        datar = datar.reshape(nt0, nx, ny)

    if m0 is not None and epsR is None:
        minv = minv + m0

    return minv, datar
Пример #5
0
def PrestackLinearModelling(wav,
                            theta,
                            vsvp=0.5,
                            nt0=1,
                            spatdims=None,
                            linearization='akirich',
                            explicit=False):
    r"""Pre-stack linearized seismic modelling operator.

    Create operator to be applied to elastic property profiles
    for generation of band-limited seismic angle gathers from a
    linearized version of the Zoeppritz equation.

    Parameters
    ----------
    wav : :obj:`np.ndarray`
        Wavelet in time domain (must had odd number of
        elements and centered to zero)
    theta : :obj:`np.ndarray`
        Incident angles in degrees
    vsvp : :obj:`float` or :obj:`np.ndarray`
        VS/VP ratio (constant or time/depth variant)
    nt0 : :obj:`int`, optional
        number of samples (if ``vsvp`` is a scalar)
    spatdims : :obj:`int` or :obj:`tuple`, optional
        Number of samples along spatial axis (or axes)
        (``None`` if only one dimension is available)
    linearization : :obj:`str`, optional
        choice of linearization, ``akirich``: Aki-Richards, ``fatti``: Fatti
    explicit : :obj:`bool`, optional
        Create a chained linear operator (``False``, preferred for large data)
        or a ``MatrixMult`` linear operator with dense matrix
        (``True``, preferred for small data)

    Returns
    -------
    Preop : :obj:`LinearOperator`
        pre-stack modelling operator.

    Raises
    ------
    NotImplementedError
        If ``linearization`` is not an implemented linearization

    Notes
    -----
    Pre-stack seismic modelling is the process of constructing seismic
    pre-stack data from three (or two) profiles of elastic parameters in time
    (or depth) domain arranged in an input vector :math:`\mathbf{m}` of size
    :math:`nt0 \times N`. This can be easily achieved using the following
    forward model:

    .. math::
        d(t, \theta) = w(t) * \sum_{i=1}^N G_i(t, \theta) m_i(t)

    where :math:`w(t)` is the time domain seismic wavelet. In compact form:

    .. math::
        \mathbf{d}= \mathbf{G} \mathbf{m}

    On the other hand, pre-stack inversion aims at recovering the different
    profiles of elastic properties from the band-limited seismic
    pre-stack data.

    """
    # create vsvp profile
    vsvp = vsvp if isinstance(vsvp, np.ndarray) else vsvp * np.ones(nt0)
    nt0 = len(vsvp)
    ntheta = len(theta)

    # organize dimensions
    if spatdims is None:
        dims = (nt0, ntheta)
        spatdims = None
    elif isinstance(spatdims, int):
        dims = (nt0, ntheta, spatdims)
        spatdims = (spatdims, )
    else:
        dims = (nt0, ntheta) + spatdims

    if explicit:
        # Create derivative operator
        D = np.diag(0.5 * np.ones(nt0 - 1), k=1) - \
            np.diag(0.5 * np.ones(nt0 - 1), -1)
        D[0] = D[-1] = 0
        D = block_diag(*([D] * 3))

        # Create AVO operator
        if linearization == 'akirich':
            G1, G2, G3 = akirichards(theta, vsvp, n=nt0)
        elif linearization == 'fatti':
            G1, G2, G3 = fatti(theta, vsvp, n=nt0)
        else:
            logging.error('%s not an available linearization...',
                          linearization)
            raise NotImplementedError('%s not an available linearization...' %
                                      linearization)

        G = [
            np.hstack((np.diag(G1[itheta] * np.ones(nt0)),
                       np.diag(G2[itheta] * np.ones(nt0)),
                       np.diag(G3[itheta] * np.ones(nt0))))
            for itheta in range(ntheta)
        ]
        G = np.vstack(G).reshape(ntheta * nt0, 3 * nt0)

        # Create wavelet operator
        C = convmtx(wav, nt0)[:, len(wav) // 2:-len(wav) // 2 + 1]
        C = [C] * ntheta
        C = block_diag(*C)

        # Combine operators
        M = np.dot(C, np.dot(G, D))
        return MatrixMult(M, dims=spatdims)

    else:
        # Create wavelet operator
        Cop = Convolve1D(np.prod(np.array(dims)),
                         h=wav,
                         offset=len(wav) // 2,
                         dir=0,
                         dims=dims)

        # create AVO operator
        AVOop = AVOLinearModelling(theta,
                                   vsvp,
                                   spatdims=spatdims,
                                   linearization=linearization)

        # Create derivative operator
        dimsm = list(dims)
        dimsm[1] = AVOop.npars
        Dop = FirstDerivative(np.prod(np.array(dimsm)),
                              dims=dimsm,
                              dir=0,
                              sampling=1.)
        return Cop * AVOop * Dop
Пример #6
0
# only right sided TV smoothing for principle
alpha = np.ones(gt.shape) * 0.4
alpha[:, :gt.shape[1] // 2] = 0
tv_smoothing = PdSmooth(domain_shape=gt.shape,
                        reg_mode='tv',
                        alpha=alpha,
                        tau=0.875)
u0 = tv_smoothing.solve(data=noise_img, maxiter=150, tol=5 * 10**(-4))
draw_images(u0, "splitted_regularization.png")

# Gradient based local regularization - decreasing Gradient influence
Sop = Smoothing2D(nsmooth=[3, 3], dims=gt.shape, dtype='float64')

alpha = np.ones(gt.shape)*0.4 \
        - 2 * np.abs(np.reshape(Sop*FirstDerivative(np.prod(gt.shape), dims=gt.shape, dir=0)*gt.ravel(), gt.shape)) \
        - 2 * np.abs(np.reshape(Sop*FirstDerivative(np.prod(gt.shape), dims=gt.shape, dir=1)*gt.ravel(), gt.shape))
alpha = np.clip(alpha, 0, 1)
alpha = alpha / np.mean(alpha) * 0.6
tv_smoothing = PdSmooth(domain_shape=gt.shape, reg_mode='tv',
                        alpha=alpha)  #, tau=2.3335)
u0 = tv_smoothing.solve(data=noise_img, maxiter=150, tol=5 * 10**(-4))
draw_images(u0, "splitted_regularization_1.png")
draw_images(alpha, "splitted_regularization_1alpha.png")

# only left sided TV smoothing - increasing Gradient influence
Sop = Smoothing2D(nsmooth=[3, 3], dims=gt.shape, dtype='float64')

alpha = np.ones(gt.shape)*0.4 \
        + 2 * np.abs(np.reshape(Sop*FirstDerivative(np.prod(gt.shape), dims=gt.shape, dir=0)*gt.ravel(), gt.shape)) \
        + 2 * np.abs(np.reshape(Sop*FirstDerivative(np.prod(gt.shape), dims=gt.shape, dir=1)*gt.ravel(), gt.shape))
Пример #7
0
def PoststackLinearModelling(wav, nt0, spatdims=None, explicit=False,
                             sparse=False):
    r"""Post-stack linearized seismic modelling operator.

    Create operator to be applied to an acoustic impedance trace (or stack of
    traces) for generation of band-limited seismic post-stack data. The input
    model and data have shape :math:`[n_{t0} (\times n_x \times n_y)]`.

    Parameters
    ----------
    wav : :obj:`np.ndarray`
        Wavelet in time domain (must had odd number of elements
        and centered to zero)
    nt0 : :obj:`int`
        Number of samples along time axis
    spatdims : :obj:`int` or :obj:`tuple`, optional
        Number of samples along spatial axis (or axes)
        (``None`` if only one dimension is available)
    explicit : :obj:`bool`, optional
        Create a chained linear operator (``False``, preferred for large data)
        or a ``MatrixMult`` linear operator with dense matrix (``True``,
        preferred for small data)
    sparse : :obj:`bool`, optional
        Create a sparse matrix (``True``) or dense  (``False``) when
        ``explicit=True``

    Returns
    -------
    Pop : :obj:`LinearOperator`
        post-stack modelling operator.

    Notes
    -----
    Post-stack seismic modelling is the process of constructing
    seismic post-stack data from a profile of acoustic impedance in
    time (or depth) domain. This can be easily achieved using the
    following forward model:

    .. math::
        d(t, \theta=0) = w(t) * \frac{dln(AI(t))}{dt}

    where :math:`AI(t)` is the acoustic impedance profile and
    :math:`w(t)` is the time domain seismic wavelet. In compact form:

    .. math::
        \mathbf{d}= \mathbf{W} \mathbf{D} \mathbf{ai}

    On the other hand, post-stack inversion aims at recovering
    the impedance profile from the band-limited seismic stack data.

    """
    # organize dimensions
    if spatdims is None:
        dims = (nt0, )
        spatdims = None
    elif isinstance(spatdims, int):
        dims = (nt0, spatdims)
        spatdims = (spatdims,)
    else:
        dims = (nt0, ) + spatdims

    if explicit:
        # Create derivative operator
        D = np.diag(0.5 * np.ones(nt0 - 1), k=1) - \
            np.diag(0.5 * np.ones(nt0 - 1), -1)
        D[0] = D[-1] = 0

        # Create wavelet operator
        C = convmtx(wav, nt0)[:, len(wav) // 2:-len(wav) // 2 + 1]

        # Combine operators
        M = np.dot(C, D)
        if sparse:
            M = csc_matrix(M)
        Pop = MatrixMult(M, dims=spatdims)
    else:
        # Create wavelet operator
        Cop = Convolve1D(np.prod(np.array(dims)), h=wav,
                         offset=len(wav)//2, dir=0, dims=dims)

        # Create derivative operator
        Dop = FirstDerivative(np.prod(np.array(dims)), dims=dims,
                              dir=0, sampling=1.)
        Pop = Cop*Dop
    return Pop