import numpy as np
import torch
from scipy.sparse.linalg import lsqr
import pylops
from pylops.optimization.sparsity import *
from tqdm.auto import tqdm
from torch.optim.lr_scheduler import StepLR
from fracspy.location.utils import get_max_locs
[docs]
def lsi(data, n_xyz, Op, niter=100, nforhc=10, verbose=False):
"""Least-squares imaging for microseismic source location
This routine performs imaging of microseismic data by
least-squares inversion using a Kirchhoff modelling operator.
Parameters
----------
data : :obj:`numpy.ndarray`
Data of shape :math`n_r \times n_t`
n_xyz : :obj:`tuple`
Number of grid points in X-, Y-, and Z-axes for the imaging area
Op : :obj:`pyfrac.modelling.kirchhoff.Kirchhoff`
Kirchhoff operator
niter : :obj:`int`, optional
Number of iterations for inversion
nforhc : :obj:`int`, optional
Number of points for hypocenter
verbose : :obj:`bool`, optional
Verbosity (if ``True``, show iteration progression of the
:func:`scipy.sparse.linalg.lsqr` solver)
Returns
-------
inv : :obj:`numpy.ndarray`
Inverted volume
hc : :obj:`numpy.ndarray`
Estimated hypocentral location
"""
nx, ny, nz = n_xyz
inv = (lsqr(Op, data.ravel(), iter_lim=niter, show=verbose)[0]).reshape(nx, ny, nz)
hc, _ = get_max_locs(inv, n_max=nforhc, rem_edge=False)
return inv, hc
[docs]
def sparselsi(data, n_xyz, Op, niter=100, l1eps=1e2, nforhc=10, verbose=False):
"""Sparsity-promoting imaging for microseismic source location
This routine performs imaging of microseismic data by
sparsity-promoting inversion using a Kirchhoff modelling operator.
Parameters
----------
data : :obj:`numpy.ndarray`
Data of shape :math`n_r \times n_t`
n_xyz : :obj:`tuple`
Number of grid points in X-, Y-, and Z-axes for the imaging area
Op : :obj:`pyfrac.modelling.kirchhoff.Kirchhoff`
Kirchhoff operator
niter : :obj:`int`, optional
Number of iterations for inversion
l1eps : :obj:`float`, optional
Weight of the L1 regularization term
nforhc : :obj:`int`, optional
Number of points for hypocenter
verbose : :obj:`bool`, optional
Verbosity (if ``True``, show iteration progression of the
:func:`scipy.sparse.linalg.lsqr` solver)
Returns
-------
inv : :obj:`numpy.ndarray`
Inverted volume
hc : :obj:`numpy.ndarray`
Estimated hypocentral location
"""
nx, ny, nz = n_xyz
with pylops.disabled_ndarray_multiplication():
inv = fista(Op, data.flatten(), niter=niter, eps=l1eps, show=verbose)[0].reshape(nx, ny, nz)
hc, _ = get_max_locs(inv, n_max=nforhc, rem_edge=False)
return inv, hc
def xcorr_of(x, y):
"""Pearson correlation coefficient
Compute the Pearson correlation coefficient between two datasets
Parameters
----------
x : :obj:`torch.Tensor`
First dataset
y : :obj:`torch.Tensor`
Second dataset
Returns
-------
r2 : :obj:`float`
Pearson correlation coefficient
"""
x = x / torch.linalg.norm(x)
y = y / torch.linalg.norm(y)
r2 = - torch.sum(torch.mul(x, y))
return r2
[docs]
def xcorri(data, n_xyz, Op, niter=100, l1eps=8e-1,
lr=1e-5, nforhc=10):
"""Cross-correlation-based imaging for microseismic source location
This routine performs imaging of microseismic data by inversion using an
objective function based on the Pearson correlation coefficient. This idea
is borrowed from the field of seismic migration, and more specifically from
[1]_, and is intended to create a location algorithm that is less sensitive to
inaccuracies in the knowledge of the source signature.
.. [1] Zhang, Y., and Duan, L., and Xie, Y. "A stable and practical implementation
of least-squares reverse time migration", Geophysics, Vol. 80, 1, pp. 1JF-Z39, 2015.
Parameters
----------
data : :obj:`numpy.ndarray`
Data of shape :math`n_r \times n_t`
n_xyz : :obj:`tuple`
Number of grid points in X-, Y-, and Z-axes for the imaging area
Op : :obj:` pyfrac.modelling.kirchhoff.Kirchhoff`
Kirchhoff operator
niter : :obj:`int`, optional
Number of iterations
l1eps : :obj:`float`, optional
Weight of the L1 regularization term
lr : :obj:`int`, optional
Learning rate used by the optimizer
nforhc : :obj:`int`, optional
Number of points for hypocenter
Returns
-------
mls_torch : :obj:`numpy.ndarray`
Migrated volume
hc : :obj:`numpy.ndarray`
Estimated hypocentral location
dls_torch : :obj:`numpy.ndarray`
Predicted data volume
losshist : :obj:`list`
Loss history
"""
# Initialise with migrated image
migrated = (Op.H @ data).reshape(n_xyz)
dmigrated = Op @ migrated.ravel()
scaling = data.max() / dmigrated.max()
m = torch.from_numpy(migrated.copy().ravel() * scaling)
m.requires_grad = True # make sure we compute the gradient with respect to m
# Initialise torch operator and torch tensors
TOp = pylops.TorchOperator(Op)
dobs = torch.from_numpy(data.copy().ravel())
# Optimization
optimizer = torch.optim.SGD([m], lr=lr)
scheduler = StepLR(optimizer, step_size=20, gamma=0.5)
losshist = np.zeros(niter)
pbar = tqdm(range(niter))
for i in pbar:
optimizer.zero_grad()
d = TOp(m)
# data term
lossd = xcorr_of(d, dobs)
# L1 regularization
reg = 0.
if l1eps > 0:
reg = torch.sum(torch.abs(m))
# total loss
loss = lossd + l1eps * reg
loss.backward()
optimizer.step()
scheduler.step()
losshist[i] = loss.item()
pbar.set_postfix({"Data Loss": lossd.item(), "L1reg Loss": reg.item(), "Total Loss": loss.item()})
dls_torch = d.detach().cpu().numpy().reshape(data.shape)
mls_torch = m.detach().cpu().numpy().reshape(n_xyz)
hc, hcs = get_max_locs(mls_torch, n_max=nforhc, rem_edge=False)
return mls_torch, hc, dls_torch, losshist