Source code for fracspy.mtinversion.mtwi

__all__ = [
    "MTW",
]

from scipy.sparse.linalg import lsqr
from pylops.optimization.sparsity import *

from fracspy.modelling.mt_kirchhoff import TAKirchhoff, MTSKirchhoff, MTMKirchhoff
from fracspy.mtinversion.greensfunction import *
from fracspy.mtinversion.utils import MT_comp_dict


[docs] class MTW(): r"""Moment-Tensor Waveform modelling and inversion This class acts as an abstract interface for users to perform moment-tensor modelling of waveforms Parameters ---------- x : :obj:`numpy.ndarray` X-axis y : :obj:`numpy.ndarray` Y-axis z : :obj:`numpy.ndarray` Z-axis recs : :obj:`numpy.ndarray` Receiver locations of size :math:`3 \times n_r` vel : :obj:`numpy.ndarray` Velocity model of size :math:`n_x \times n_y \times n_z` src_idx : :obj:`numpy.ndarray` Source location indices (relative to x, y, and z axes) comp_idx : :obj:`int` Index of component at receiver side omega_p : :obj:`float` Peak frequency of the given wave aoi : :obj:`tuple` Area of interest for waveform computation defined as half windows to place either size of the source in center of region (defined by `src_idx`) t : :obj:`numpy.ndarray` Time axis for data wav : :obj:`numpy.ndarray` Wavelet. wavcenter : :obj:`int` Index of wavelet center Ms_scaling : :obj:`float` Scaling to be incorporated in the MTI engine : :obj:`str`, optional Engine used for computations (``numpy`` or ``numba``). multicomp : : obj:`boolean` Whether running for single or multicomponent data cosine_sourceangles : :obj:`numpy.ndarray` Cosine source angles of size :math:`3 \times n_r \times n_x \times n_y \times n_z` dists : :obj:`numpy.ndarray` Distances of size :math:`\times n_r \times n_x \times n_y \times n_z` """ def __init__(self, x, y, z, recs, vel, src_idx, comp_idx, omega_p, aoi, t, wav, wavc, Ms_scaling=1., engine="numpy", multicomp=False, cosine_sourceangles=None, dists=None): self.x, self.y, self.z = x, y, z self.n_xyz = x.size, y.size, z.size self.recs = recs self.nr = recs.shape[1] self.vel = vel if isinstance(vel, np.ndarray): self.mode = "eikonal" else: self.mode = "analytic" self.src_idx = src_idx self.comp_idx = comp_idx self.omega_p = omega_p self.aoi = aoi self.t, self.wav, self.wavc = t, wav, wavc self.nt = t.size self.multicomp = multicomp self.ncomps = 3 if multicomp else 1 self.Ms_scaling = Ms_scaling self.engine = engine self.cosine_sourceangles, self.dists = cosine_sourceangles, dists if cosine_sourceangles is None and dists is None: self.cosine_sourceangles, self.dists = collect_source_angles(self.x, self.y, self.z, recs=self.recs) elif (cosine_sourceangles is None and dists is not None) or (cosine_sourceangles is not None and dists is None): raise NotImplementedError('Must provide both cosine_sourceangles and dists or neither') self.Op, self.n_xyz_aoi = self._create_op() def _create_op(self): # Traveltime tables trav = TAKirchhoff._traveltime_table(self.z, self.x, y=self.y, recs=self.recs, vel=self.vel, mode=self.mode) trav = trav.reshape(self.n_xyz[0], self.n_xyz[1], self.n_xyz[2], self.nr).transpose([3, 0, 1, 2]) # Define area of interest hwin_nx_aoi, hwin_ny_aoi, hwin_nz_aoi = self.aoi xsi, xfi = self.src_idx[0] - hwin_nx_aoi, self.src_idx[0] + hwin_nx_aoi + 1 # start/end index of x-region of interest ysi, yfi = self.src_idx[1] - hwin_ny_aoi, self.src_idx[1] + hwin_ny_aoi + 1 # start/end index of y-region of interest zsi, zfi = self.src_idx[2] - hwin_nz_aoi, self.src_idx[2] + hwin_nz_aoi + 1 # start/end index of z-region of interest nxsi = xfi - xsi nysi = yfi - ysi nzsi = zfi - zsi dimsai = (nxsi, nysi, nzsi) # Extract parameters in the area of interest cosine_sourceangles = self.cosine_sourceangles[:, :, xsi:xfi, ysi:yfi, zsi:zfi] dists = self.dists[:, xsi:xfi, ysi:yfi, zsi:zfi] trav = trav[:, xsi:xfi, ysi:yfi, zsi:zfi] nx_aoi, ny_aoi, nz_aoi = trav.shape[1:] # Green's functions if not self.multicomp: Gz = mt_pwave_greens_comp(n_xyz=[nx_aoi, ny_aoi, nz_aoi], cosine_sourceangles=cosine_sourceangles, dists=dists, vel=self.vel, MT_comp_dict=MT_comp_dict, comp_idx=self.comp_idx, omega_p=self.omega_p, ) Op = MTSKirchhoff( self.x[xsi:xfi], self.y[ysi:yfi], self.z[zsi:zfi], self.recs, self.t, self.wav, self.wavc, trav, Gz, Ms_scaling=self.Ms_scaling, engine=self.engine, checkdottest=False) else: Gx, Gy, Gz = mt_pwave_greens_multicomp(n_xyz=[nx_aoi, ny_aoi, nz_aoi], cosine_sourceangles=cosine_sourceangles, dists=dists, vel=self.vel, MT_comp_dict=MT_comp_dict, omega_p=self.omega_p, ) Op = MTMKirchhoff( self.x[xsi:xfi], self.y[ysi:yfi], self.z[zsi:zfi], self.recs, self.t, self.wav, self.wavc, trav, Gx, Gy, Gz, Ms_scaling=self.Ms_scaling, engine=self.engine, checkdottest=False) return Op, dimsai def model(self, mt): """Modelling """ data = self.Op @ mt.ravel() data = data.reshape(self.ncomps, self.nr, self.nt).squeeze() return data def adjoint(self, data): """Adjoint modelling """ mt = self.Op.H @ data.ravel() mt = mt.reshape([6, self.n_xyz_aoi[0], self.n_xyz_aoi[1], self.n_xyz_aoi[2]]) return mt def lsi(self, data, niter=100, verbose=False): mt = lsqr(self.Op, data.ravel(), iter_lim=niter, atol=0, btol=0, show=verbose)[0] mt = mt.reshape([6, self.n_xyz_aoi[0], self.n_xyz_aoi[1], self.n_xyz_aoi[2]]) return mt def sparselsi(self, data, niter=100, l1eps=1e2, verbose=False): mt = fista( self.Op, data.ravel(), x0=np.zeros([6, self.n_xyz_aoi[0], self.n_xyz_aoi[1], self.n_xyz_aoi[2]]).ravel(), niter=niter, eps=l1eps, show=verbose)[0] mt = mt.reshape([6, self.n_xyz_aoi[0], self.n_xyz_aoi[1], self.n_xyz_aoi[2]]) return mt def invert(self, data, kind='lsi', **kwargs): if kind == 'adjoint': mt = self.adjoint(data, **kwargs) elif kind == 'lsi': mt = self.lsi(data, **kwargs) elif kind == 'sparselsi': mt = self.sparselsi(data, **kwargs) else: raise NotImplementedError('kind must be adjoint, lsi, or sparselsi') return mt