def WavefieldDecomposition(p, vz, nt, nr, dt, dr, rho, vel, nffts=(None, None), critical=100., ntaper=10, scaling=1., kind='inverse', restriction=None, sptransf=None, solver=lsqr, dottest=False, dtype='complex128', **kwargs_solver): r"""Up-down wavefield decomposition. Apply seismic wavefield decomposition from its multi-component (pressure and vertical particle velocity) data. Parameters ---------- p : :obj:`np.ndarray` Pressure data of of size :math:`\lbrack n_r \times n_t \rbrack` (or :math:`\lbrack n_{r,sub} \times n_t \rbrack` in case a ``restriction`` operator is provided, and :math:`n_{r,sub}` must agree with the size of the output of this an operator) vz : :obj:`np.ndarray` Vertical particle velocity data of size :math:`\lbrack n_r \times n_t \rbrack` (or :math:`\lbrack n_{r,sub} \times n_t \rbrack`) nt : :obj:`int` Number of samples along the time axis nr : :obj:`np.ndarray` Number of samples along the receiver axis of the separated pressure consituents dt : :obj:`float` Sampling along the time axis dr : :obj:`float` Sampling along the receiver array of the separated pressure consituents 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 kind : :obj:`str`, optional Type of separation: ``inverse`` (default) or ``analytical`` scaling : :obj:`float`, optional Scaling to apply to the particle velocity data at the restriction : :obj:`pylops.LinearOperator`, optional Restriction operator sptransf : :obj:`pylops.LinearOperator`, optional Sparsifying operator solver : :obj:`float`, optional Function handle of solver to be used if ``kind='inverse'`` dottest : :obj:`bool`, optional Apply dot-test dtype : :obj:`str`, optional Type of elements in input array. **kwargs_solver Arbitrary keyword arguments for chosen ``solver`` Returns ------- pup : :obj:`np.ndarray` Up-going wavefield pdown : :obj:`np.ndarray` Down-going wavefield Raises ------ KeyError If ``kind`` is neither ``analytical`` nor ``inverse`` Notes ----- Up- and down-going components of seismic data (:math:`p^-(x, t)` and :math:`p^+(x, t)`) can be estimated from multi-component data (:math:`p(x, t)` and :math:`v_z(x, t)`) by computing the following expression [1]_: .. math:: \begin{bmatrix} \mathbf{p^+}(k_x, \omega) \\ \mathbf{p^-}(k_x, \omega) \end{bmatrix} = \frac{1}{2} \begin{bmatrix} 1 & \frac{\omega \rho}{k_z} \\ 1 & - \frac{\omega \rho}{k_z} \\ \end{bmatrix} \begin{bmatrix} \mathbf{p}(k_x, \omega) \\ \mathbf{v_z}(k_x, \omega) \end{bmatrix} if ``kind='analytical'`` or alternatively by solving the equation in :func:`ptcpy.waveeqprocessing.UpDownComposition2D` as an inverse problem, if ``kind='inverse'``. The latter approach has several advantages as data regularization can be included as part of the separation process allowing the input data to be aliased. This is obtained by solving the following problem: .. math:: \begin{bmatrix} \mathbf{p} \\ s*\mathbf{v_z} \end{bmatrix} = \begin{bmatrix} \mathbf{R}\mathbf{F} & 0 \\ 0 & s*\mathbf{R}\mathbf{F} \end{bmatrix} \mathbf{W} \begin{bmatrix} \mathbf{F}^H \mathbf{S} & 0 \\ 0 & \mathbf{F}^H \mathbf{S} \end{bmatrix} \mathbf{p^{\pm}} where :math:`\mathbf{R}` is a :class:`ptcpy.basicoperators.Restriction` operator and :math:`\mathbf{S}` is sparsyfing transform operator (e.g., :class:`ptcpy.signalprocessing.Radon2D`). .. [1] Wapenaar, K. "Reciprocity properties of one-way propagators", Geophysics, vol. 63, pp. 1795-1798. 1998. """ ndims = p.ndim if ndims == 2: decomposition = _UpDownDecomposition2D_analytical composition = UpDownComposition2D if kind == 'analytical': FFTop, OBL = \ decomposition(nt, nr, dt, dr, rho, vel, nffts=nffts, critical=critical, ntaper=ntaper, dtype=dtype) VZ = FFTop * vz.ravel() VZ = VZ.reshape(nffts) # scaled Vz VZ_obl = OBL * VZ vz_obl = FFTop.H * VZ_obl.ravel() vz_obl = np.real(vz_obl.reshape(nr, nt)) # separation pup = (p - vz_obl) / 2 pdown = (p + vz_obl) / 2 elif kind == 'inverse': d = np.concatenate((p.ravel(), scaling * vz.ravel())) UDop = \ composition(nt, nr, dt, dr, rho, vel, nffts=nffts, critical=critical, ntaper=ntaper, scaling=scaling, dtype=dtype) if restriction is not None: UDop = restriction * UDop if sptransf is not None: UDop = UDop * BlockDiag([sptransf, sptransf]) UDop.dtype = np.real(np.ones(1, UDop.dtype)).dtype if dottest: Dottest(UDop, UDop.shape[0], UDop.shape[1], complexflag=2, verb=True) # separation by inversion dud = solver(UDop, d.ravel(), **kwargs_solver)[0] if sptransf is None: dud = np.real(dud) else: dud = BlockDiag([sptransf, sptransf]) * np.real(dud) dud = dud.reshape(2 * nr, nt) pdown, pup = dud[:nr], dud[nr:] else: raise KeyError('kind must be analytical or inverse') return pup, pdown
def WavefieldDecomposition(p, vz, nt, nr, dt, dr, rho, vel, nffts=(None, None, None), critical=100.0, ntaper=10, scaling=1.0, kind="inverse", restriction=None, sptransf=None, solver=lsqr, dottest=False, dtype="complex128", **kwargs_solver): r"""Up-down wavefield decomposition. Apply seismic wavefield decomposition from multi-component (pressure and vertical particle velocity) data. This process is also generally referred to as data-based deghosting. Parameters ---------- p : :obj:`np.ndarray` Pressure data of size :math:`\lbrack n_{r_x} \,(\times n_{r_y}) \times n_t \rbrack` (or :math:`\lbrack n_{r_{x,\text{sub}}} \,(\times n_{r_{y,\text{sub}}}) \times n_t \rbrack` in case a ``restriction`` operator is provided. Note that :math:`n_{r_{x,\text{sub}}}` (and :math:`n_{r_{y,\text{sub}}}`) must agree with the size of the output of this operator.) vz : :obj:`np.ndarray` Vertical particle velocity data of same size as pressure data nt : :obj:`int` Number of samples along the time axis nr : :obj:`int` or :obj:`tuple` Number of samples along the receiver axis (or axes) dt : :obj:`float` Sampling along the time axis dr : :obj:`float` or :obj:`tuple` Sampling along the receiver array (or axes) rho : :obj:`float` Density :math:`\rho` along the receiver array (must be constant) vel : :obj:`float` Velocity :math:`c` 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)}{c}` will be retained ntaper : :obj:`float`, optional Number of samples of taper applied to obliquity factor around critical angle kind : :obj:`str`, optional Type of separation: ``inverse`` (default) or ``analytical`` scaling : :obj:`float`, optional Scaling to apply to the operator (see Notes of :func:`pylops.waveeqprocessing.wavedecomposition.UpDownComposition2D` for more details) restriction : :obj:`pylops.LinearOperator`, optional Restriction operator sptransf : :obj:`pylops.LinearOperator`, optional Sparsifying operator solver : :obj:`float`, optional Function handle of solver to be used if ``kind='inverse'`` dottest : :obj:`bool`, optional Apply dot-test dtype : :obj:`str`, optional Type of elements in input array. **kwargs_solver Arbitrary keyword arguments for chosen ``solver`` Returns ------- pup : :obj:`np.ndarray` Up-going wavefield pdown : :obj:`np.ndarray` Down-going wavefield Raises ------ KeyError If ``kind`` is neither ``analytical`` nor ``inverse`` Notes ----- Up- and down-going components of seismic data :math:`p^-(x, t)` and :math:`p^+(x, t)` can be estimated from multi-component data :math:`p(x, t)` and :math:`v_z(x, t)` by computing the following expression [1]_: .. math:: \begin{bmatrix} \hat{p}^+ \\ \hat{p}^- \end{bmatrix}(k_x, \omega) = \frac{1}{2} \begin{bmatrix} 1 & \frac{\omega \rho}{k_z} \\ 1 & - \frac{\omega \rho}{k_z} \\ \end{bmatrix} \begin{bmatrix} \hat{p} \\ \hat{v}_z \end{bmatrix}(k_x, \omega) if ``kind='analytical'`` or alternatively by solving the equation in :func:`ptcpy.waveeqprocessing.UpDownComposition2D` as an inverse problem, if ``kind='inverse'``. The latter approach has several advantages as data regularization can be included as part of the separation process allowing the input data to be aliased. This is obtained by solving the following problem: .. math:: \begin{bmatrix} \mathbf{p} \\ s\mathbf{v_z} \end{bmatrix} = \begin{bmatrix} \mathbf{R}\mathbf{F} & 0 \\ 0 & s\mathbf{R}\mathbf{F} \end{bmatrix} \mathbf{W} \begin{bmatrix} \mathbf{F}^H \mathbf{S} & 0 \\ 0 & \mathbf{F}^H \mathbf{S} \end{bmatrix} \mathbf{p^{\pm}} where :math:`\mathbf{R}` is a :class:`ptcpy.basicoperators.Restriction` operator and :math:`\mathbf{S}` is sparsyfing transform operator (e.g., :class:`ptcpy.signalprocessing.Radon2D`). .. [1] Wapenaar, K. "Reciprocity properties of one-way propagators", Geophysics, vol. 63, pp. 1795-1798. 1998. """ ncp = get_array_module(p) backend = get_module_name(ncp) ndims = p.ndim if ndims == 2: dims = (nr, nt) dims2 = (2 * nr, nt) nr2 = nr decomposition = _obliquity2D composition = UpDownComposition2D else: dims = (nr[0], nr[1], nt) dims2 = (2 * nr[0], nr[1], nt) nr2 = nr[0] decomposition = _obliquity3D composition = UpDownComposition3D if kind == "analytical": FFTop, OBLop = decomposition( nt, nr, dt, dr, rho, vel, nffts=nffts, critical=critical, ntaper=ntaper, composition=False, backend=backend, dtype=dtype, ) VZ = FFTop * vz.ravel() # scaled Vz VZ_obl = OBLop * VZ vz_obl = FFTop.H * VZ_obl vz_obl = ncp.real(vz_obl.reshape(dims)) # separation pup = (p - vz_obl) / 2 pdown = (p + vz_obl) / 2 elif kind == "inverse": d = ncp.concatenate((p.ravel(), scaling * vz.ravel())) UDop = composition( nt, nr, dt, dr, rho, vel, nffts=nffts, critical=critical, ntaper=ntaper, scaling=scaling, backend=backend, dtype=dtype, ) if restriction is not None: UDop = restriction * UDop if sptransf is not None: UDop = UDop * BlockDiag([sptransf, sptransf]) UDop.dtype = ncp.real(ncp.ones(1, UDop.dtype)).dtype if dottest: Dottest( UDop, UDop.shape[0], UDop.shape[1], complexflag=2, backend=backend, verb=True, ) # separation by inversion dud = solver(UDop, d.ravel(), **kwargs_solver)[0] if sptransf is None: dud = ncp.real(dud) else: dud = BlockDiag([sptransf, sptransf]) * ncp.real(dud) dud = dud.reshape(dims2) pdown, pup = dud[:nr2], dud[nr2:] else: raise KeyError("kind must be analytical or inverse") return pup, pdown