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 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
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
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
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
# 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))
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