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
def PrestackWaveletModelling(m, theta, nwav, wavc=None, vsvp=0.5, linearization='akirich'): r"""Pre-stack linearized seismic modelling operator for wavelet. Create operator to be applied to a wavelet for generation of band-limited seismic angle gathers using a linearized version of the Zoeppritz equation. Parameters ---------- m : :obj:`np.ndarray` elastic parameter profles of size :math:`[n_{t0} \times N]` where :math:`N=3/2`. Note that the ``dtype`` of this variable will define that of the operator theta : :obj:`int` Incident angles in degrees. Must have same ``dtype`` of ``m`` (or it will be automatically casted to it) nwav : :obj:`np.ndarray` Number of samples of wavelet to be applied/estimated wavc : :obj:`int`, optional Index of the center of the wavelet vsvp : :obj:`np.ndarray` or :obj:`float`, optional VS/VP ratio linearization : :obj:`str`, optional choice of linearization, ``akirich``: Aki-Richards, ``fatti``: Fatti, ``ps``: PS, or any function on the form of ``pylops.avo.avo.akirichards`` Returns ------- Mconv : :obj:`LinearOperator` pre-stack modelling operator for wavelet estimation. Raises ------ NotImplementedError If ``linearization`` is not an implemented linearization Notes ----- Pre-stack seismic modelling for wavelet estimate is the process of constructing seismic reflectivities using 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`: .. math:: d(t, \theta) = \sum_{i=1}^N G_i(t, \theta) m_i(t) * w(t) where :math:`w(t)` is the time domain seismic wavelet. In compact form: .. math:: \mathbf{d}= \mathbf{G} \mathbf{w} On the other hand, pre-stack wavelet estimation aims at recovering the wavelet given knowledge of the band-limited seismic pre-stack data and the elastic parameter profiles. """ ncp = get_array_module(theta) # define dtype to be used dtype = m.dtype # ensure m.dtype rules that of operator theta = theta.astype(dtype) # Create vsvp profile vsvp = vsvp if isinstance(vsvp, ncp.ndarray) else \ vsvp * ncp.ones(m.shape[0], dtype=dtype) wavc = nwav // 2 if wavc is None else wavc nt0 = len(vsvp) ntheta = len(theta) # 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 D = ncp.diag(0.5 * np.ones(nt0 - 1, dtype=dtype), k=1) - \ ncp.diag(0.5 * np.ones(nt0 - 1, dtype=dtype), k=-1) D[0] = D[-1] = 0 D = get_block_diag(theta)(*([D] * nG)) # Create infinite-reflectivity data M = ncp.dot(G, ncp.dot(D, m.T.flatten())).reshape(ntheta, nt0) Mconv = VStack([ MatrixMult(convmtx(M[itheta], nwav)[wavc:-nwav + wavc + 1], dtype=dtype) for itheta in range(ntheta) ]) return Mconv