def test_Convolve1D(par): """Dot-test and inversion for Convolve1D operator """ np.random.seed(10) #1D if par['dir'] == 0: Cop = Convolve1D(par['nx'], h=h1, offset=par['offset'], dtype='float32') assert dottest(Cop, par['nx'], par['nx']) x = np.zeros((par['nx'])) x[par['nx']//2] = 1. xlsqr = lsqr(Cop, Cop * x, damp=1e-20, iter_lim=200, show=0)[0] assert_array_almost_equal(x, xlsqr, decimal=1) # 1D on 2D Cop = Convolve1D(par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['ny'], par['nx']), dir=par['dir'], dtype='float32') assert dottest(Cop, par['ny'] * par['nx'], par['ny'] * par['nx']) x = np.zeros((par['ny'], par['nx'])) x[int(par['ny']/2-3):int(par['ny']/2+3), int(par['nx']/2-3):int(par['nx']/2+3)] = 1. x = x.flatten() xlsqr = lsqr(Cop, Cop * x, damp=1e-20, iter_lim=200, show=0)[0] assert_array_almost_equal(x, xlsqr, decimal=1)
def test_Convolve1D(par): """Dot-test and inversion for Convolve1D operator""" np.random.seed(10) # 1D if par["dir"] == 0: Cop = Convolve1D(par["nx"], h=h1, offset=par["offset"], dtype="float64") assert dottest(Cop, par["nx"], par["nx"]) x = np.zeros((par["nx"])) x[par["nx"] // 2] = 1.0 xlsqr = lsqr(Cop, Cop * x, damp=1e-20, iter_lim=200, show=0)[0] assert_array_almost_equal(x, xlsqr, decimal=1) # 1D on 2D if par["dir"] < 2: Cop = Convolve1D( par["ny"] * par["nx"], h=h1, offset=par["offset"], dims=(par["ny"], par["nx"]), dir=par["dir"], dtype="float64", ) assert dottest(Cop, par["ny"] * par["nx"], par["ny"] * par["nx"]) x = np.zeros((par["ny"], par["nx"])) x[int(par["ny"] / 2 - 3):int(par["ny"] / 2 + 3), int(par["nx"] / 2 - 3):int(par["nx"] / 2 + 3), ] = 1.0 x = x.ravel() xlsqr = lsqr(Cop, Cop * x, damp=1e-20, iter_lim=200, show=0)[0] assert_array_almost_equal(x, xlsqr, decimal=1) # 1D on 3D Cop = Convolve1D( par["nz"] * par["ny"] * par["nx"], h=h1, offset=par["offset"], dims=(par["nz"], par["ny"], par["nx"]), dir=par["dir"], dtype="float64", ) assert dottest(Cop, par["nz"] * par["ny"] * par["nx"], par["nz"] * par["ny"] * par["nx"]) x = np.zeros((par["nz"], par["ny"], par["nx"])) x[int(par["nz"] / 2 - 3):int(par["nz"] / 2 + 3), int(par["ny"] / 2 - 3):int(par["ny"] / 2 + 3), int(par["nx"] / 2 - 3):int(par["nx"] / 2 + 3), ] = 1.0 x = x.ravel() xlsqr = lsqr(Cop, Cop * x, damp=1e-20, iter_lim=200, show=0)[0] assert_array_almost_equal(x, xlsqr, decimal=1)
def Smoothing1D(nsmooth, dims, dir=0, dtype='float32'): r"""1D Smoothing. Apply smoothing to model (and data) along a specific direction of a multi-dimensional array depending on the choice of ``dir``. Parameters ---------- nsmooth : :obj:`int` Lenght of smoothing operator (must be odd) dims : :obj:`tuple` or :obj:`int` Number of samples for each dimension dir : :obj:`int`, optional Direction along which smoothing is applied dtype : :obj:`str`, optional Type of elements in input array. Attributes ---------- shape : :obj:`tuple` Operator shape explicit : :obj:`bool` Operator contains a matrix that can be solved explicitly (``True``) or not (``False``) Notes ----- The Smoothing1D operator is a special type of convolutional operator that convolves the input model (or data) with a constant filter of size :math:`n_{smooth}`: .. math:: \mathbf{f} = [ 1/n_{smooth}, 1/n_{smooth}, ..., 1/n_{smooth} ] When applied to the first direction: .. math:: y[i,j,k] = 1/n_{smooth} \sum_{l=-(n_{smooth}-1)/2}^{(n_{smooth}-1)/2} x[l,j,k] Similarly when applied to the second direction: .. math:: y[i,j,k] = 1/n_{smooth} \sum_{l=-(n_{smooth}-1)/2}^{(n_{smooth}-1)/2} x[i,l,k] and the third direction: .. math:: y[i,j,k] = 1/n_{smooth} \sum_{l=-(n_{smooth}-1)/2}^{(n_{smooth}-1)/2} x[i,j,l] Note that since the filter is symmetrical, the *Smoothing1D* operator is self-adjoint. """ if isinstance(dims, int): dims = (dims, ) nsmooth = nsmooth + 1 if nsmooth % 2 == 0 else nsmooth return Convolve1D(np.prod(np.array(dims)), dims=dims, dir=dir, h=np.ones(nsmooth) / float(nsmooth), offset=(nsmooth - 1) / 2, dtype=dtype)
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 test_Convolve1D(par): """Dot-test and comparison with Pylops for Convolve1D operator """ np.random.seed(10) # 1D if par['dir'] == 0: Cop = Convolve1D(par['nx'], h=h1, offset=par['offset'], dtype='float32') dCop = dConvolve1D(par['nx'], h=h1, offset=par['offset'], compute=(True, True), dtype='float32') assert dottest(dCop, par['nx'], par['nx'], chunks=(par['nx']//2 + 1, par['nx']//2 + 1)) x = np.random.normal(0., 1., par['nx']) x = da.from_array(x, chunks=par['nx']//2 + 1) dy = dCop * x y = Cop * x.compute() assert_array_almost_equal(y, dy, decimal=1) # 1D on 2D if par['dir'] < 2: Cop = Convolve1D(par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['ny'], par['nx']), dir=par['dir'], dtype='float32') dCop = dConvolve1D(par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['ny'], par['nx']), dir=par['dir'], compute=(True, True), chunks=((par['ny'] // 2 + 1, par['nx'] // 2 + 1), (par['ny'] // 2 + 1, par['nx'] // 2 + 1)), dtype='float32') assert dottest(dCop, par['ny'] * par['nx'], par['ny'] * par['nx'], chunks=(par['ny'] * par['nx'], par['ny'] * par['nx'])) x = np.random.normal(0., 1., (par['ny'], par['nx'])) x = da.from_array(x, chunks=(par['ny'] // 2 + 1, par['nx'] // 2 + 1)).flatten() dy = dCop * x y = Cop * x.compute() assert_array_almost_equal(y, dy, decimal=1) # 1D on 3D Cop = Convolve1D(par['nz'] * par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['nz'], par['ny'], par['nx']), dir=par['dir'], dtype='float32') dCop = dConvolve1D(par['nz'] * par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['nz'], par['ny'], par['nx']), dir=par['dir'], compute=(True, True), chunks=((par['nz'] // 2 + 1, par['ny'] // 2 + 1, par['nx'] // 2 + 1), (par['nz'] // 2 + 1, par['ny'] // 2 + 1, par['nx'] // 2 + 1)), dtype='float32') assert dottest(dCop, par['nz'] * par['ny'] * par['nx'], par['nz'] * par['ny'] * par['nx'], chunks=(par['nz'] * par['ny'] * par['nx'], par['nz'] * par['ny'] * par['nx'])) x = np.random.normal(0., 1., (par['nz'], par['ny'], par['nx'])) x = da.from_array(x, chunks=(par['nz'] // 2 + 1, par['ny'] // 2 + 1, par['nx'] // 2 + 1)) dy = dCop * x.flatten() y = Cop * x.compute().flatten() assert_array_almost_equal(y, dy, decimal=1)
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 Demigration(z, x, t, srcs, recs, vel, wav, wavcenter, y=None, trav=None, mode='eikonal'): r"""Kirchoff Demigration operator. Traveltime based seismic demigration/migration operator. Parameters ---------- z : :obj:`numpy.ndarray` Depth axis x : :obj:`numpy.ndarray` Spatial axis t : :obj:`numpy.ndarray` Time axis for data srcs : :obj:`numpy.ndarray` Sources in array of size :math:`\lbrack 2/3 \times n_s \rbrack` The first axis should be ordered as (``y``,) ``x``, ``z``. recs : :obj:`numpy.ndarray` Receivers in array of size :math:`\lbrack 2/3 \times n_r \rbrack` The first axis should be ordered as (``y``,) ``x``, ``z``. vel : :obj:`numpy.ndarray` or :obj:`float` Velocity model of size :math:`\lbrack (n_y \times) n_x \times n_z \rbrack` (or constant) wav : :obj:`numpy.ndarray` Wavelet wavcenter : :obj:`int` Index of wavelet center y : :obj:`numpy.ndarray` Additional spatial axis (for 3-dimensional problems) mode : :obj:`str`, optional Computation mode (``analytic``, ``eikonal`` or ``byot``, see Notes for more details) trav : :obj:`numpy.ndarray`, optional Traveltime table of size :math:`\lbrack (n_y*) n_x*n_z \times n_r \rbrack` (to be provided if ``mode='byot'``) Returns ------- demop : :obj:`pylops.LinearOperator` Demigration/Migration operator Raises ------ NotImplementedError If ``mode`` is neither ``analytic``, ``eikonal``, or ``byot`` Notes ----- The demigration operator synthetizes seismic data given from a propagation velocity model :math:`v` and a reflectivity model :math:`m`. In forward mode: .. math:: d(\mathbf{x_r}, \mathbf{x_s}, t) = w(t) * \int_V G(\mathbf{x}, \mathbf{x_s}, t) G(\mathbf{x_r}, \mathbf{x}, t) m(\mathbf{x}) d\mathbf{x} where :math:`m(\mathbf{x})` is the model and it represents the reflectivity at every location in the subsurface, :math:`G(\mathbf{x}, \mathbf{x_s}, t)` and :math:`G(\mathbf{x_r}, \mathbf{x}, t)` are the Green's functions from source-to-subsurface-to-receiver and finally :math:`w(t)` is the wavelet. Depending on the choice of ``mode`` the Green's function will be computed and applied differently: * ``mode=analytic`` or ``mode=eikonal``: traveltime curves between source to receiver pairs are computed for every subsurface point and Green's functions are implemented from traveltime look-up tables, placing the reflectivity values at corresponding source-to-receiver time in the data. * ``byot``: bring your own table. Traveltime table provided directly by user using ``trav`` input parameter. Green's functions are then implemented in the same way as previous options. The adjoint of the demigration operator is a *migration* operator which projects data in the model domain creating an image of the subsurface reflectivity. """ ndim, _, dims, ny, nx, nz, ns, nr, _, _, _, _, _ = \ _identify_geometry(z, x, srcs, recs, y=y) dt = t[1] - t[0] nt = len(t) if mode in ['analytic', 'eikonal', 'byot']: if mode in ['analytic', 'eikonal']: # compute traveltime table trav = _traveltime_table(z, x, srcs, recs, vel, y=y, mode=mode)[0] itrav = (trav / dt).astype('int32') travd = (trav / dt - itrav) if ndim == 2: itrav = itrav.reshape(nx, nz, ns * nr) travd = travd.reshape(nx, nz, ns * nr) dims = tuple(dims) else: itrav = itrav.reshape(ny*nx, nz, ns * nr) travd = travd.reshape(ny*nx, nz, ns * nr) dims = (dims[0]*dims[1], dims[2]) # create operator sop = Spread(dims=dims, dimsd=(ns * nr, nt), table=itrav, dtable=travd, engine='numba') cop = Convolve1D(ns * nr * nt, h=wav, offset=wavcenter, dims=(ns * nr, nt), dir=1) demop = cop * sop else: raise NotImplementedError('method must be analytic or eikonal') return demop
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
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
itrav_tmp = (trav_tmp / dt).astype('int32') travd_tmp = (trav_tmp / dt - itrav_tmp) if ndim == 2: itrav_tmp = itrav_tmp.reshape(nx, nz, ns * nr) travd_tmp = travd_tmp.reshape(nx, nz, ns * nr) dims = tuple(dims) else: itrav_tmp = itrav_tmp.reshape(ny*nx, nz, ns * nr) travd_tmp = travd_tmp.reshape(ny*nx, nz, ns * nr) dims = (dims[0]*dims[1], dims[2]) # create operator sop = Spread(dims=dims, dimsd=(ns * nr, nt), table=itrav_tmp, dtable=travd_tmp, engine='numba') cop = Convolve1D(ns * nr * nt, h=wav, offset=wavcenter, dims=(ns * nr, nt), dir=1) demop = cop * sop #%% madj = lsm.Demop.H * d.ravel() madj = madj.reshape(nx, nz) plt.figure(figsize=(10,5)) im = plt.imshow(madj.T, cmap='gray') plt.colorbar(im) plt.axis('tight') plt.xlabel('x [m]'),plt.ylabel('y [m]') plt.title('madj') # demigration
def test_Convolve1D(par): """Dot-test, comparison with pylops and inversion for Convolve1D operator """ np.random.seed(10) #1D if par['dir'] == 0: gCop = gConvolve1D(par['nx'], h=h1, offset=par['offset'], dtype=torch.float32) assert dottest(gCop, par['nx'], par['nx'], tol=1e-3) x = torch.zeros((par['nx']), dtype=torch.float32) x[par['nx'] // 2] = 1. # comparison with pylops Cop = Convolve1D(par['nx'], h=h1.cpu().numpy(), offset=par['offset'], dtype='float32') assert_array_almost_equal(gCop * x, Cop * x.cpu().numpy(), decimal=3) #assert_array_equal(gCop * x, Cop * x.cpu().numpy()) # inversion if par['offset'] == nfilt[0] // 2: # zero phase xcg = cg(gCop, gCop * x, niter=100)[0] else: # non-zero phase xcg = cg(gCop.H * gCop, gCop.H * (gCop * x), niter=100)[0] assert_array_almost_equal(x, xcg, decimal=1) # 1D on 2D gCop = gConvolve1D(par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['ny'], par['nx']), dir=par['dir'], dtype=torch.float32) assert dottest(gCop, par['ny'] * par['nx'], par['ny'] * par['nx'], tol=1e-3) x = torch.zeros((par['ny'], par['nx']), dtype=torch.float32) x[int(par['ny'] / 2 - 3):int(par['ny'] / 2 + 3), int(par['nx'] / 2 - 3):int(par['nx'] / 2 + 3)] = 1. x = x.flatten() # comparison with pylops Cop = Convolve1D(par['ny'] * par['nx'], h=h1.cpu().numpy(), offset=par['offset'], dims=(par['ny'], par['nx']), dir=par['dir'], dtype='float32') assert_array_almost_equal(gCop * x, Cop * x.cpu().numpy(), decimal=3) # assert_array_equal(gCop * x, Cop * x.cpu().numpy()) # inversion if par['offset'] == nfilt[0] // 2: # zero phase xcg = cg(gCop, gCop * x, niter=100)[0] else: # non-zero phase xcg = cg(gCop.H * gCop, gCop.H * (gCop * x), niter=100)[0] assert_array_almost_equal(x, xcg, decimal=1) # 1D on 3D gCop = gConvolve1D(par['nz'] * par['ny'] * par['nx'], h=h1, offset=par['offset'], dims=(par['nz'], par['ny'], par['nx']), dir=par['dir'], dtype=torch.float32) assert dottest(gCop, par['nz'] * par['ny'] * par['nx'], par['nz'] * par['ny'] * par['nx'], tol=1e-3) x = torch.zeros((par['nz'], par['ny'], par['nx']), dtype=torch.float32) x[int(par['nz'] / 2 - 3):int(par['nz'] / 2 + 3), int(par['ny'] / 2 - 3):int(par['ny'] / 2 + 3), int(par['nx'] / 2 - 3):int(par['nx'] / 2 + 3)] = 1. x = x.flatten() # comparison with pylops Cop = Convolve1D(par['nz'] * par['ny'] * par['nx'], h=h1.cpu().numpy(), offset=par['offset'], dims=(par['nz'], par['ny'], par['nx']), dir=par['dir'], dtype='float32') assert_array_almost_equal(gCop * x, Cop * x.cpu().numpy(), decimal=3) # inversion if par['offset'] == nfilt[0] // 2: # zero phase xcg = cg(gCop, gCop * x, niter=100)[0] else: # non-zero phase xcg = cg(gCop.H * gCop, gCop.H * (gCop * x), niter=100)[0] assert_array_almost_equal(x, xcg, decimal=1)