def _matvec(self, x): if type(self.diag) != type(x): self.diag = to_cupy_conditional(x, self.diag) if not self.reshape: y = self.diag * x.ravel() else: x = x.reshape(self.dims) y = self.diag * x return y.ravel()
def _rmatvec(self, x): # correct type of h if different from x and choose methods accordingly if type(self.h) != type(x): self.h = to_cupy_conditional(x, self.h) self.convolve = get_convolve(self.h) self.correlate = get_correlate(self.h) x = np.reshape(x, self.dims) y = self.correlate(x, self.h, mode='same', method=self.method) y = y.ravel() return y
def _rmatvec(self, x): if type(self.hstar) != type(x): self.hstar = to_cupy_conditional(x, self.hstar) self.convfunc, self.method = \ _choose_convfunc(self.hstar, self.method, self.dimsorig) if not self.reshape: y = self.convfunc(x.squeeze(), self.hstar, mode='same', method=self.method) else: x = np.reshape(x, self.dims) y = self.convfunc(x, self.hstar, mode='same') y = y.ravel() return y
def _rmatvec(self, x): if type(self.diag) != type(x): self.diag = to_cupy_conditional(x, self.diag) if self.complex: diagadj = self.diag.conj() else: diagadj = self.diag if not self.reshape: y = diagadj * x.ravel() else: x = x.reshape(self.dims) y = diagadj * x return y.ravel()
def _matvec(self, x): if type(self.h) != type(x): self.h = to_cupy_conditional(x, self.h) self.convfunc, self.method = _choose_convfunc( self.h, self.method, self.dimsorig) if not self.reshape: y = self.convfunc(x.squeeze(), self.h, mode="same", method=self.method) else: x = np.reshape(x, self.dims) y = self.convfunc(x, self.h, mode="same") y = y.ravel() return y
def _rmatvec(self, x): ncp = get_array_module(x) if not self.inplace: x = x.copy() if not self.reshape: y = ncp.zeros(self.dims, dtype=self.dtype) y[self.iava] = x else: x = ncp.reshape(x, self.dimsd) if ncp == np: y = ncp.zeros(self.dims, dtype=self.dtype) ncp.put_along_axis(y, ncp.reshape(self.iava, self.iavareshape), x, axis=self.dir) else: if not hasattr(self, 'iavamask'): self.iava = to_cupy_conditional(x, self.iava) self.iavamask = _compute_iavamask(self.dims, self.dir, self.iava, ncp) y = ncp.zeros(int(self.M), dtype=self.dtype) y[self.iavamask] = x.ravel() y = y.ravel() return y
def _rmatvec(self, x): if not isinstance(self.gazx, type(x)): self.gazx = to_cupy_conditional(x, self.gazx) y = x.reshape(self.dims) * np.conj(self.gazx) return y.ravel()
def MDD( G, d, dt=0.004, dr=1.0, nfmax=None, wav=None, twosided=True, causality_precond=False, adjoint=False, psf=False, dtype="float64", dottest=False, saveGt=True, add_negative=True, smooth_precond=0, fftengine="numpy", **kwargs_solver ): r"""Multi-dimensional deconvolution. Solve multi-dimensional deconvolution problem using :py:func:`scipy.sparse.linalg.lsqr` iterative solver. Parameters ---------- G : :obj:`numpy.ndarray` Multi-dimensional convolution kernel in time domain of size :math:`[n_s \times n_r \times n_t]` for ``twosided=False`` or ``twosided=True`` and ``add_negative=True`` (with only positive times) or size :math:`[n_s \times n_r \times 2n_t-1]` for ``twosided=True`` and ``add_negative=False`` (with both positive and negative times) d : :obj:`numpy.ndarray` Data in time domain :math:`[n_s \,(\times n_{vs}) \times n_t]` if ``twosided=False`` or ``twosided=True`` and ``add_negative=True`` (with only positive times) or size :math:`[n_s \,(\times n_{vs}) \times 2n_t-1]` if ``twosided=True`` dt : :obj:`float`, optional Sampling of time integration axis dr : :obj:`float`, optional Sampling of receiver integration axis nfmax : :obj:`int`, optional Index of max frequency to include in deconvolution process wav : :obj:`numpy.ndarray`, optional Wavelet to convolve to the inverted model and psf (must be centered around its index in the middle of the array). If ``None``, the outputs of the inversion are returned directly. twosided : :obj:`bool`, optional MDC operator and data both negative and positive time (``True``) or only positive (``False``) add_negative : :obj:`bool`, optional Add negative side to MDC operator and data (``True``) or not (``False``)- operator and data are already provided with both positive and negative sides. To be used only with ``twosided=True``. causality_precond : :obj:`bool`, optional Apply causality mask (``True``) or not (``False``) smooth_precond : :obj:`int`, optional Lenght of smoothing to apply to causality preconditioner adjoint : :obj:`bool`, optional Compute and return adjoint(s) psf : :obj:`bool`, optional Compute and return Point Spread Function (PSF) and its inverse dtype : :obj:`bool`, optional Type of elements in input array. dottest : :obj:`bool`, optional Apply dot-test saveGt : :obj:`bool`, optional Save ``G`` and ``G.H`` to speed up the computation of adjoint of :class:`pylops.signalprocessing.Fredholm1` (``True``) or create ``G.H`` on-the-fly (``False``) Note that ``saveGt=True`` will be faster but double the amount of required memory fftengine : :obj:`str`, optional Engine used for fft computation (``numpy``, ``scipy`` or ``fftw``) **kwargs_solver Arbitrary keyword arguments for chosen solver (:py:func:`scipy.sparse.linalg.cg` and :py:func:`pylops.optimization.solver.cg` are used as default for numpy and cupy `data`, respectively) Returns ------- minv : :obj:`numpy.ndarray` Inverted model of size :math:`[n_r \,(\times n_{vs}) \times n_t]` for ``twosided=False`` or :math:`[n_r \,(\times n_vs) \times 2n_t-1]` for ``twosided=True`` madj : :obj:`numpy.ndarray` Adjoint model of size :math:`[n_r \,(\times n_{vs}) \times n_t]` for ``twosided=False`` or :math:`[n_r \,(\times n_r) \times 2n_t-1]` for ``twosided=True`` psfinv : :obj:`numpy.ndarray` Inverted psf of size :math:`[n_r \times n_r \times n_t]` for ``twosided=False`` or :math:`[n_r \times n_r \times 2n_t-1]` for ``twosided=True`` psfadj : :obj:`numpy.ndarray` Adjoint psf of size :math:`[n_r \times n_r \times n_t]` for ``twosided=False`` or :math:`[n_r \times n_r \times 2n_t-1]` for ``twosided=True`` See Also -------- MDC : Multi-dimensional convolution Notes ----- Multi-dimensional deconvolution (MDD) is a mathematical ill-solved problem, well-known in the image processing and geophysical community [1]_. MDD aims at removing the effects of a Multi-dimensional Convolution (MDC) kernel or the so-called blurring operator or point-spread function (PSF) from a given data. It can be written as .. math:: \mathbf{d}= \mathbf{D} \mathbf{m} or, equivalently, by means of its normal equation .. math:: \mathbf{m}= (\mathbf{D}^H\mathbf{D})^{-1} \mathbf{D}^H\mathbf{d} where :math:`\mathbf{D}^H\mathbf{D}` is the PSF. .. [1] Wapenaar, K., van der Neut, J., Ruigrok, E., Draganov, D., Hunziker, J., Slob, E., Thorbecke, J., and Snieder, R., "Seismic interferometry by crosscorrelation and by multi-dimensional deconvolution: a systematic comparison", Geophyscial Journal International, vol. 185, pp. 1335-1364. 2011. """ ncp = get_array_module(d) ns, nr, nt = G.shape if len(d.shape) == 2: nv = 1 else: nv = d.shape[1] if twosided: if add_negative: nt2 = 2 * nt - 1 else: nt2 = nt nt = (nt2 + 1) // 2 nfmax_allowed = int(np.ceil((nt2 + 1) / 2)) else: nt2 = nt nfmax_allowed = nt # Fix nfmax to be at maximum equal to half of the size of fft samples if nfmax is None or nfmax > nfmax_allowed: nfmax = nfmax_allowed logging.warning("nfmax set equal to ceil[(nt+1)/2=%d]" % nfmax) # Add negative part to data and model if twosided and add_negative: G = np.concatenate((ncp.zeros((ns, nr, nt - 1)), G), axis=-1) d = np.concatenate((np.squeeze(np.zeros((ns, nv, nt - 1))), d), axis=-1) # Bring kernel to frequency domain Gfft = np.fft.rfft(G, nt2, axis=-1) Gfft = Gfft[..., :nfmax] # Bring frequency/time to first dimension Gfft = np.moveaxis(Gfft, -1, 0) d = np.moveaxis(d, -1, 0) if psf: G = np.moveaxis(G, -1, 0) # Define MDC linear operator MDCop = MDC( Gfft, nt2, nv=nv, dt=dt, dr=dr, twosided=twosided, transpose=False, saveGt=saveGt, fftengine=fftengine, ) if psf: PSFop = MDC( Gfft, nt2, nv=nr, dt=dt, dr=dr, twosided=twosided, transpose=False, saveGt=saveGt, fftengine=fftengine, ) if dottest: Dottest( MDCop, nt2 * ns * nv, nt2 * nr * nv, verb=True, backend=get_module_name(ncp) ) if psf: Dottest( PSFop, nt2 * ns * nr, nt2 * nr * nr, verb=True, backend=get_module_name(ncp), ) # Adjoint if adjoint: madj = MDCop.H * d.ravel() madj = np.squeeze(madj.reshape(nt2, nr, nv)) madj = np.moveaxis(madj, 0, -1) if psf: psfadj = PSFop.H * G.ravel() psfadj = np.squeeze(psfadj.reshape(nt2, nr, nr)) psfadj = np.moveaxis(psfadj, 0, -1) # Inverse if twosided and causality_precond: P = np.ones((nt2, nr, nv)) P[: nt - 1] = 0 if smooth_precond > 0: P = filtfilt(np.ones(smooth_precond) / smooth_precond, 1, P, axis=0) P = to_cupy_conditional(d, P) Pop = Diagonal(P) minv = PreconditionedInversion( MDCop, Pop, d.ravel(), returninfo=False, **kwargs_solver ) else: if ncp == np and "callback" not in kwargs_solver: minv = lsqr(MDCop, d.ravel(), **kwargs_solver)[0] else: minv = cgls( MDCop, d.ravel(), ncp.zeros(int(MDCop.shape[1]), dtype=MDCop.dtype), **kwargs_solver )[0] minv = np.squeeze(minv.reshape(nt2, nr, nv)) minv = np.moveaxis(minv, 0, -1) if wav is not None: wav1 = wav.copy() for _ in range(minv.ndim - 1): wav1 = wav1[ncp.newaxis] minv = get_fftconvolve(d)(minv, wav1, mode="same") if psf: if ncp == np: psfinv = lsqr(PSFop, G.ravel(), **kwargs_solver)[0] else: psfinv = cgls( PSFop, G.ravel(), ncp.zeros(int(PSFop.shape[1]), dtype=PSFop.dtype), **kwargs_solver )[0] psfinv = np.squeeze(psfinv.reshape(nt2, nr, nr)) psfinv = np.moveaxis(psfinv, 0, -1) if wav is not None: wav1 = wav.copy() for _ in range(psfinv.ndim - 1): wav1 = wav1[np.newaxis] psfinv = get_fftconvolve(d)(psfinv, wav1, mode="same") if adjoint and psf: return minv, madj, psfinv, psfadj elif adjoint: return minv, madj elif psf: return minv, psfinv else: return minv
def apply_multiplepoints(self, trav, G0=None, nfft=None, rtm=False, greens=False, dottest=False, usematmul=False, **kwargs_solver): r"""Marchenko redatuming for multiple points Solve the Marchenko redatuming inverse problem for multiple points given their direct arrival traveltime curves (``trav``) and waveforms (``G0``). Parameters ---------- trav : :obj:`numpy.ndarray` Traveltime of first arrival from subsurface points to surface receivers of size :math:`[n_r \times n_{vs}]` G0 : :obj:`numpy.ndarray`, optional Direct arrival in time domain of size :math:`[n_r \times n_{vs} \times n_t]` (if None, create arrival using ``trav``) nfft : :obj:`int`, optional Number of samples in fft when creating the analytical direct wave rtm : :obj:`bool`, optional Compute and return rtm redatuming greens : :obj:`bool`, optional Compute and return Green's functions dottest : :obj:`bool`, optional Apply dot-test usematmul : :obj:`bool`, optional Use :func:`numpy.matmul` (``True``) or for-loop with :func:`numpy.dot` (``False``) in :py:class:`pylops.signalprocessing.Fredholm1` operator. Refer to Fredholm1 documentation for details. **kwargs_solver Arbitrary keyword arguments for chosen solver (:py:func:`scipy.sparse.linalg.lsqr` and :py:func:`pylops.optimization.solver.cgls` are used as default for numpy and cupy `data`, respectively) Returns ---------- f1_inv_minus : :obj:`numpy.ndarray` Inverted upgoing focusing function of size :math:`[n_r \times n_{vs} \times n_t]` f1_inv_plus : :obj:`numpy.ndarray` Inverted downgoing focusing functionof size :math:`[n_r \times n_{vs} \times n_t]` p0_minus : :obj:`numpy.ndarray` Single-scattering standard redatuming upgoing Green's function of size :math:`[n_r \times n_{vs} \times n_t]` g_inv_minus : :obj:`numpy.ndarray` Inverted upgoing Green's function of size :math:`[n_r \times n_{vs} \times n_t]` g_inv_plus : :obj:`numpy.ndarray` Inverted downgoing Green's function of size :math:`[n_r \times n_{vs} \times n_t]` """ nvs = trav.shape[1] # Create window trav_off = trav - self.toff trav_off = np.round(trav_off / self.dt).astype(int) w = np.zeros((self.nr, nvs, self.nt), dtype=self.dtype) for ir in range(self.nr): for ivs in range(nvs): w[ir, ivs, :trav_off[ir, ivs]] = 1 w = np.concatenate((np.flip(w, axis=-1), w[:, :, 1:]), axis=-1) if self.nsmooth > 0: smooth = np.ones(self.nsmooth, dtype=self.dtype) / self.nsmooth w = filtfilt(smooth, 1, w) w = to_cupy_conditional(self.Rtwosided_fft, w) # Create operators Rop = MDC( self.Rtwosided_fft, self.nt2, nv=nvs, dt=self.dt, dr=self.dr, twosided=True, conj=False, fftengine=self.fftengine, transpose=False, prescaled=self.prescaled, usematmul=usematmul, dtype=self.dtype, ) R1op = MDC( self.Rtwosided_fft, self.nt2, nv=nvs, dt=self.dt, dr=self.dr, twosided=True, conj=True, fftengine=self.fftengine, transpose=False, prescaled=self.prescaled, usematmul=usematmul, dtype=self.dtype, ) Rollop = Roll( self.ns * nvs * self.nt2, dims=(self.nt2, self.ns, nvs), dir=0, shift=-1, dtype=self.dtype, ) Wop = Diagonal(w.transpose(2, 0, 1).ravel()) Iop = Identity(self.nr * nvs * self.nt2) Mop = Block([[Iop, -1 * Wop * Rop], [-1 * Wop * Rollop * R1op, Iop] ]) * BlockDiag([Wop, Wop]) Gop = Block([[Iop, -1 * Rop], [-1 * Rollop * R1op, Iop]]) if dottest: Dottest( Gop, 2 * self.nr * nvs * self.nt2, 2 * self.nr * nvs * self.nt2, raiseerror=True, verb=True, backend=get_module_name(self.ncp), ) if dottest: Dottest( Mop, 2 * self.ns * nvs * self.nt2, 2 * self.nr * nvs * self.nt2, raiseerror=True, verb=True, backend=get_module_name(self.ncp), ) # Create input focusing function if G0 is None: if self.wav is not None and nfft is not None: G0 = np.zeros((self.nr, nvs, self.nt), dtype=self.dtype) for ivs in range(nvs): G0[:, ivs] = (directwave( self.wav, trav[:, ivs], self.nt, self.dt, nfft=nfft, derivative=True, )).T G0 = to_cupy_conditional(self.Rtwosided_fft, G0) else: logging.error("wav and/or nfft are not provided. " "Provide either G0 or wav and nfft...") raise ValueError("wav and/or nfft are not provided. " "Provide either G0 or wav and nfft...") fd_plus = np.concatenate(( np.flip(G0, axis=-1).transpose(2, 0, 1), self.ncp.zeros((self.nt - 1, self.nr, nvs), dtype=self.dtype), )) # Run standard redatuming as benchmark if rtm: p0_minus = Rop * fd_plus.ravel() p0_minus = p0_minus.reshape(self.nt2, self.ns, nvs).transpose(1, 2, 0) # Create data and inverse focusing functions d = Wop * Rop * fd_plus.ravel() d = np.concatenate(( d.reshape(self.nt2, self.ns, nvs), self.ncp.zeros((self.nt2, self.ns, nvs), dtype=self.dtype), )) # Invert for focusing functions if self.ncp == np: f1_inv = lsqr(Mop, d.ravel(), **kwargs_solver)[0] else: f1_inv = cgls(Mop, d.ravel(), x0=self.ncp.zeros(2 * (2 * self.nt - 1) * self.nr * nvs, dtype=self.dtype), **kwargs_solver)[0] f1_inv = f1_inv.reshape(2 * self.nt2, self.nr, nvs) f1_inv_tot = f1_inv + np.concatenate((self.ncp.zeros( (self.nt2, self.nr, nvs), dtype=self.dtype), fd_plus)) f1_inv_minus = f1_inv_tot[:self.nt2].transpose(1, 2, 0) f1_inv_plus = f1_inv_tot[self.nt2:].transpose(1, 2, 0) if greens: # Create Green's functions g_inv = Gop * f1_inv_tot.ravel() g_inv = g_inv.reshape(2 * self.nt2, self.ns, nvs) g_inv_minus = -g_inv[:self.nt2].transpose(1, 2, 0) g_inv_plus = np.flip(g_inv[self.nt2:], axis=0).transpose(1, 2, 0) if rtm and greens: return f1_inv_minus, f1_inv_plus, p0_minus, g_inv_minus, g_inv_plus elif rtm: return f1_inv_minus, f1_inv_plus, p0_minus elif greens: return f1_inv_minus, f1_inv_plus, g_inv_minus, g_inv_plus else: return f1_inv_minus, f1_inv_plus
def apply_onepoint(self, trav, G0=None, nfft=None, rtm=False, greens=False, dottest=False, fast=None, **kwargs_solver): r"""Marchenko redatuming for one point Solve the Marchenko redatuming inverse problem for a single point given its direct arrival traveltime curve (``trav``) and waveform (``G0``). Parameters ---------- trav : :obj:`numpy.ndarray` Traveltime of first arrival from subsurface point to surface receivers of size :math:`[n_r \times 1]` G0 : :obj:`numpy.ndarray`, optional Direct arrival in time domain of size :math:`[n_r \times n_t]` (if None, create arrival using ``trav``) nfft : :obj:`int`, optional Number of samples in fft when creating the analytical direct wave rtm : :obj:`bool`, optional Compute and return rtm redatuming greens : :obj:`bool`, optional Compute and return Green's functions dottest : :obj:`bool`, optional Apply dot-test fast : :obj:`bool` *Deprecated*, will be removed in v2.0.0 **kwargs_solver Arbitrary keyword arguments for chosen solver (:py:func:`scipy.sparse.linalg.lsqr` and :py:func:`pylops.optimization.solver.cgls` are used as default for numpy and cupy `data`, respectively) Returns ---------- f1_inv_minus : :obj:`numpy.ndarray` Inverted upgoing focusing function of size :math:`[n_r \times n_t]` f1_inv_plus : :obj:`numpy.ndarray` Inverted downgoing focusing function of size :math:`[n_r \times n_t]` p0_minus : :obj:`numpy.ndarray` Single-scattering standard redatuming upgoing Green's function of size :math:`[n_r \times n_t]` g_inv_minus : :obj:`numpy.ndarray` Inverted upgoing Green's function of size :math:`[n_r \times n_t]` g_inv_plus : :obj:`numpy.ndarray` Inverted downgoing Green's function of size :math:`[n_r \times n_t]` """ # Create window trav_off = trav - self.toff trav_off = np.round(trav_off / self.dt).astype(np.int) w = np.zeros((self.nr, self.nt), dtype=self.dtype) for ir in range(self.nr): w[ir, :trav_off[ir]] = 1 w = np.hstack((np.fliplr(w), w[:, 1:])) if self.nsmooth > 0: smooth = np.ones(self.nsmooth, dtype=self.dtype) / self.nsmooth w = filtfilt(smooth, 1, w) w = to_cupy_conditional(self.Rtwosided_fft, w) # Create operators Rop = MDC(self.Rtwosided_fft, self.nt2, nv=1, dt=self.dt, dr=self.dr, twosided=True, conj=False, transpose=False, saveGt=self.saveRt, prescaled=self.prescaled, dtype=self.dtype) R1op = MDC(self.Rtwosided_fft, self.nt2, nv=1, dt=self.dt, dr=self.dr, twosided=True, conj=True, transpose=False, saveGt=self.saveRt, prescaled=self.prescaled, dtype=self.dtype) Rollop = Roll(self.nt2 * self.ns, dims=(self.nt2, self.ns), dir=0, shift=-1, dtype=self.dtype) Wop = Diagonal(w.T.flatten()) Iop = Identity(self.nr * self.nt2) Mop = Block([[Iop, -1 * Wop * Rop], [-1 * Wop * Rollop * R1op, Iop] ]) * BlockDiag([Wop, Wop]) Gop = Block([[Iop, -1 * Rop], [-1 * Rollop * R1op, Iop]]) if dottest: Dottest(Gop, 2 * self.ns * self.nt2, 2 * self.nr * self.nt2, raiseerror=True, verb=True, backend=get_module_name(self.ncp)) if dottest: Dottest(Mop, 2 * self.ns * self.nt2, 2 * self.nr * self.nt2, raiseerror=True, verb=True, backend=get_module_name(self.ncp)) # Create input focusing function if G0 is None: if self.wav is not None and nfft is not None: G0 = (directwave(self.wav, trav, self.nt, self.dt, nfft=nfft, derivative=True)).T G0 = to_cupy_conditional(self.Rtwosided_fft, G0) else: logging.error('wav and/or nfft are not provided. ' 'Provide either G0 or wav and nfft...') raise ValueError('wav and/or nfft are not provided. ' 'Provide either G0 or wav and nfft...') fd_plus = np.concatenate((np.fliplr(G0).T, self.ncp.zeros((self.nt - 1, self.nr), dtype=self.dtype))) # Run standard redatuming as benchmark if rtm: p0_minus = Rop * fd_plus.flatten() p0_minus = p0_minus.reshape(self.nt2, self.ns).T # Create data and inverse focusing functions d = Wop * Rop * fd_plus.flatten() d = np.concatenate((d.reshape(self.nt2, self.ns), self.ncp.zeros((self.nt2, self.ns), self.dtype))) # Invert for focusing functions if self.ncp == np: f1_inv = lsqr(Mop, d.flatten(), **kwargs_solver)[0] else: f1_inv = cgls(Mop, d.flatten(), x0=self.ncp.zeros(2 * (2 * self.nt - 1) * self.nr, dtype=self.dtype), **kwargs_solver)[0] f1_inv = f1_inv.reshape(2 * self.nt2, self.nr) f1_inv_tot = f1_inv + np.concatenate((self.ncp.zeros( (self.nt2, self.nr), dtype=self.dtype), fd_plus)) f1_inv_minus = f1_inv_tot[:self.nt2].T f1_inv_plus = f1_inv_tot[self.nt2:].T if greens: # Create Green's functions g_inv = Gop * f1_inv_tot.flatten() g_inv = g_inv.reshape(2 * self.nt2, self.ns) g_inv_minus, g_inv_plus = -g_inv[:self.nt2].T, \ np.fliplr(g_inv[self.nt2:].T) if rtm and greens: return f1_inv_minus, f1_inv_plus, p0_minus, g_inv_minus, g_inv_plus elif rtm: return f1_inv_minus, f1_inv_plus, p0_minus elif greens: return f1_inv_minus, f1_inv_plus, g_inv_minus, g_inv_plus else: return f1_inv_minus, f1_inv_plus