Post Stack Inversion - 3D#

This tutorial demonstrates the implementation of a distributed 3D Post-stack inversion. It consists of a first part showing how to model a 3D synthetic post-stack seismic data from a 3D model of the subsurface acoustic impedance in a distributed manner, following by a second part when inversion is carried out.

This tutorial builds on the pylops.avo.poststack.PoststackLinearModelling operator to model 1d post-stack seismic traces 1d profiles of the subsurface acoustic impedence by means of the following equation

\[d(t) = \frac{1}{2} w(t) * \frac{\mathrm{d}\ln \text{AI}(t)}{\mathrm{d}t}\]

where \(\text{AI}(t)\) is the acoustic impedance profile and \(w(t)\) is the time domain seismic wavelet. Being this inherently a 1d operator, we can easily set up a problem where one of the dimensions (here the y-dimension) is distributed across ranks and each of them is in charge of performing modelling for a subvolume of the entire domain. Using a compact matrix-vector notation, the entire problem can be written as

\[\begin{split}\begin{bmatrix} \mathbf{d}_{1} \\ \mathbf{d}_{2} \\ \vdots \\ \mathbf{d}_{N} \end{bmatrix} = \begin{bmatrix} \mathbf{G}_1 & \mathbf{0} & \ldots & \mathbf{0} \\ \mathbf{0} & \mathbf{G}_2 & \ldots & \mathbf{0} \\ \vdots & \vdots & \ddots & \vdots \\ \mathbf{0} & \mathbf{0} & \ldots & \mathbf{G}_N \end{bmatrix} \begin{bmatrix} \mathbf{ai}_{1} \\ \mathbf{ai}_{2} \\ \vdots \\ \mathbf{ai}_{N} \end{bmatrix}\end{split}\]

where \(\mathbf{G}_i\) is a post-stack modelling operator, \(\mathbf{d}_i\) is the data, and \(\mathbf{ai}_i\) is the input model for the i-th portion of the model.

This problem can be easily set up using the pylops_mpi.basicoperators.MPIBlockDiag operator.

import numpy as np
from scipy.signal import filtfilt
from matplotlib import pyplot as plt
from mpi4py import MPI

from pylops.utils.wavelets import ricker
from pylops.basicoperators import Transpose
from pylops.avo.poststack import PoststackLinearModelling

import pylops_mpi

plt.close("all")
rank = MPI.COMM_WORLD.Get_rank()
size = MPI.COMM_WORLD.Get_size()

Let’s start by defining all the parameters required by the pylops.avo.poststack.PoststackLinearModelling operator.

# Model
model = np.load("../testdata/avo/poststack_model.npz")
x, z, m = model['x'][::3], model['z'], np.log(model['model'])[:, ::3]

# Making m a 3D model
ny_i = 20  # size of model in y direction for rank i
y = np.arange(ny_i)
m3d_i = np.tile(m[:, :, np.newaxis], (1, 1, ny_i)).transpose((2, 1, 0))
ny_i, nx, nz = m3d_i.shape

# Size of y at all ranks
ny = MPI.COMM_WORLD.allreduce(ny_i)

# Smooth model
nsmoothy, nsmoothx, nsmoothz = 5, 30, 20
mback3d_i = filtfilt(np.ones(nsmoothy) / float(nsmoothy), 1, m3d_i, axis=0)
mback3d_i = filtfilt(np.ones(nsmoothx) / float(nsmoothx), 1, mback3d_i, axis=1)
mback3d_i = filtfilt(np.ones(nsmoothz) / float(nsmoothz), 1, mback3d_i, axis=2)

# Wavelet
dt = 0.004
t0 = np.arange(nz) * dt
ntwav = 41
wav = ricker(t0[:ntwav // 2 + 1], 15)[0]

# Collecting all the m3d and mback3d at all ranks
m3d = np.concatenate(MPI.COMM_WORLD.allgather(m3d_i))
mback3d = np.concatenate(MPI.COMM_WORLD.allgather(mback3d_i))

We now create the linear operator version of pylops.avo.poststack.PoststackLinearModelling at each rank to model a subset of the data along the y-axis. Such operators are passed to the pylops_mpi.basicoperators.MPIBlockDiag operator, which is then used to perform the different forward operations of each individual operator at different ranks to compute the overall data. Note that to simplify the handling of the model and data, we split and distribute the first axis, and use pylops.Transpose to rearrange the model and data in the form required by the pylops.avo.poststack.PoststackLinearModelling operator.

# Create flattened model data
m3d_dist = pylops_mpi.DistributedArray(global_shape=ny * nx * nz)
m3d_dist[:] = m3d_i.flatten()

# Create flattened smooth model data
mback3d_dist = pylops_mpi.DistributedArray(global_shape=ny * nx * nz)
mback3d_dist[:] = mback3d_i.flatten()

# LinearOperator PostStackLinearModelling
PPop = PoststackLinearModelling(wav, nt0=nz, spatdims=(ny_i, nx))
Top = Transpose((ny_i, nx, nz), (2, 0, 1))
BDiag = pylops_mpi.basicoperators.MPIBlockDiag(ops=[Top.H @ PPop @ Top, ])

# Data
d_dist = BDiag @ m3d_dist
d_local = d_dist.local_array.reshape((ny_i, nx, nz))
d = d_dist.asarray().reshape((ny, nx, nz))
d_0_dist = BDiag @ mback3d_dist
d_0 = d_dist.asarray().reshape((ny, nx, nz))

We perform 2 different kinds of inversions:

  • Inversion calculated iteratively using the pylops_mpi.optimization.cls_basic.CGLS solver.

  • Inversion with spatial regularization using normal equations along all three dimensions (x, y and z). This requires extending the operator and data in the following manner:

\[\begin{split}\mathbf{N} = \begin{bmatrix} \mathbf{G}_1^H & \mathbf{0} & \ldots & \mathbf{0} \\ \mathbf{0} & \mathbf{G}_2^H & \ldots & \mathbf{0} \\ \vdots & \vdots & \ddots & \vdots \\ \mathbf{0} & \mathbf{0} & \ldots & \mathbf{G}_N^H \end{bmatrix} \begin{bmatrix} \mathbf{G}_1 & \mathbf{0} & \ldots & \mathbf{0} \\ \mathbf{0} & \mathbf{G}_2 & \ldots & \mathbf{0} \\ \vdots & \vdots & \ddots & \vdots \\ \mathbf{0} & \mathbf{0} & \ldots & \mathbf{G}_N \end{bmatrix} + \epsilon \mathbf{L}^H \mathbf{L} \\\end{split}\]
\[\begin{split}\begin{bmatrix} \mathbf{d}_{1}^{Norm} \\ \mathbf{d}_{2}^{Norm} \\ \vdots \\ \mathbf{d}_{N}^{Norm} \end{bmatrix} = \begin{bmatrix} \mathbf{G}_1^H & \mathbf{0} & \ldots & \mathbf{0} \\ \mathbf{0} & \mathbf{G}_2^H & \ldots & \mathbf{0} \\ \vdots & \vdots & \ddots & \vdots \\ \mathbf{0} & \mathbf{0} & \ldots & \mathbf{G}_N^H \end{bmatrix} \begin{bmatrix} \mathbf{d}_{1} \\ \mathbf{d}_{2} \\ \vdots \\ \mathbf{d}_{N} \end{bmatrix}\end{split}\]

where \(\mathbf{L}\) is the pylops_mpi.basicoperators.MPILaplacian operator which is used to apply second derivative along all three axes, \(\mathbf{N}\) is an operator computing the normal equations, and \(\mathbf{d}^{Norm}\) is the data of the normal equation operator used for inversion.

# Inversion using CGLS solver
minv3d_iter_dist = pylops_mpi.optimization.basic.cgls(BDiag, d_dist, x0=mback3d_dist, niter=100, show=True)[0]
minv3d_iter = minv3d_iter_dist.asarray().reshape((ny, nx, nz))
CGLS
-----------------------------------------------------------------
The Operator Op has 2937000 rows and 2937000 cols
damp = 0.000000e+00     tol = 1.000000e-04      niter = 100
-----------------------------------------------------------------

    Itn          x[0]              r1norm         r2norm
     1        6.2034e-01         7.5690e+01     7.5690e+01
     2        6.2500e-01         4.5718e+01     4.5718e+01
     3        6.2669e-01         3.3126e+01     3.3126e+01
     4        6.2452e-01         2.6621e+01     2.6621e+01
     5        6.2091e-01         2.2097e+01     2.2097e+01
     6        6.1845e-01         1.8653e+01     1.8653e+01
     7        6.1636e-01         1.5872e+01     1.5872e+01
     8        6.1486e-01         1.3797e+01     1.3797e+01
     9        6.1562e-01         1.2071e+01     1.2071e+01
    10        6.1730e-01         1.0660e+01     1.0660e+01
    11        6.1619e-01         9.5376e+00     9.5376e+00
    21        6.1733e-01         4.7497e+00     4.7497e+00
    31        6.1264e-01         2.9346e+00     2.9346e+00
    41        6.1379e-01         2.0154e+00     2.0154e+00
    51        6.1548e-01         1.5044e+00     1.5044e+00
    61        6.1695e-01         1.1912e+00     1.1912e+00
    71        6.1561e-01         1.0001e+00     1.0001e+00
    81        6.1437e-01         8.6449e-01     8.6449e-01
    91        6.1327e-01         7.2838e-01     7.2838e-01
    92        6.1343e-01         7.1870e-01     7.1870e-01
    93        6.1361e-01         7.1127e-01     7.1127e-01
    94        6.1389e-01         7.0269e-01     7.0269e-01
    95        6.1422e-01         6.9241e-01     6.9241e-01
    96        6.1453e-01         6.8123e-01     6.8123e-01
    97        6.1485e-01         6.7010e-01     6.7010e-01
    98        6.1520e-01         6.5942e-01     6.5942e-01
    99        6.1544e-01         6.5026e-01     6.5026e-01
   100        6.1561e-01         6.4212e-01     6.4212e-01

Iterations = 100        Total time (s) = 30.02
-----------------------------------------------------------------
# Regularized inversion with normal equations
epsR = 1e2
LapOp = pylops_mpi.MPILaplacian(dims=(ny, nx, nz), axes=(0, 1, 2), weights=(1, 1, 1),
                                sampling=(1, 1, 1), dtype=BDiag.dtype)
NormEqOp = BDiag.H @ BDiag + epsR * LapOp.H @ LapOp
dnorm_dist = BDiag.H @ d_dist
minv3d_ne_dist = pylops_mpi.optimization.basic.cg(NormEqOp, dnorm_dist, x0=mback3d_dist, niter=100, show=True)[0]
minv3d_ne = minv3d_ne_dist.asarray().reshape((ny, nx, nz))
CG
-------------------------------------------------------
The Operator Op has 2937000 rows and 2937000 cols
tol = 1.000000e-04      niter = 100
-------------------------------------------------------

    Itn           x[0]              r2norm
     1         6.1587e-01         1.6825e+03
     2         6.1667e-01         1.6533e+03
     3         6.1695e-01         1.4528e+03
     4         6.1725e-01         1.6359e+03
     5         6.1746e-01         1.3555e+03
     6         6.1767e-01         1.2633e+03
     7         6.1787e-01         1.3221e+03
     8         6.1811e-01         1.2846e+03
     9         6.1831e-01         1.1952e+03
    10         6.1851e-01         1.1626e+03
    11         6.1869e-01         1.1780e+03
    21         6.2050e-01         7.4711e+02
    31         6.2265e-01         6.6863e+02
    41         6.2516e-01         5.1781e+02
    51         6.2692e-01         4.3140e+02
    61         6.2847e-01         3.7187e+02
    71         6.2977e-01         2.9441e+02
    81         6.3072e-01         2.6242e+02
    91         6.3077e-01         2.3101e+02
    92         6.3075e-01         2.3022e+02
    93         6.3072e-01         2.2334e+02
    94         6.3068e-01         2.2033e+02
    95         6.3063e-01         2.1755e+02
    96         6.3056e-01         2.1555e+02
    97         6.3049e-01         2.1291e+02
    98         6.3041e-01         2.0469e+02
    99         6.3033e-01         1.9903e+02
   100         6.3026e-01         1.9777e+02

Iterations = 100        Total time (s) = 44.46
-------------------------------------------------------
# Regularized inversion with regularized equations
StackOp = pylops_mpi.MPIStackedVStack([BDiag, np.sqrt(epsR) * LapOp])
d0_dist = pylops_mpi.DistributedArray(global_shape=ny * nx * nz)
d0_dist[:] = 0.
dstack_dist = pylops_mpi.StackedDistributedArray([d_dist, d0_dist])

dnorm_dist = BDiag.H @ d_dist
minv3d_reg_dist = pylops_mpi.optimization.basic.cgls(StackOp, dstack_dist, x0=mback3d_dist, niter=100, show=False)[0]
minv3d_reg = minv3d_reg_dist.asarray().reshape((ny, nx, nz))

Finally, we display the modeling and inversion results

if rank == 0:
    # Check the distributed implementation gives the same result
    # as the one running only on rank0
    PPop0 = PoststackLinearModelling(wav, nt0=nz, spatdims=(ny, nx))
    d0 = (PPop0 @ m3d.transpose(2, 0, 1)).transpose(1, 2, 0)
    d0_0 = (PPop0 @ m3d.transpose(2, 0, 1)).transpose(1, 2, 0)

    # Check the two distributed implementations give the same modelling results
    print('Distr == Local', np.allclose(d, d0))
    print('Smooth Distr == Local', np.allclose(d_0, d0_0))

    # Visualize
    fig, axs = plt.subplots(nrows=6, ncols=3, figsize=(9, 14), constrained_layout=True)
    axs[0][0].imshow(m3d[5, :, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[0][0].set_title("Model x-z")
    axs[0][0].axis("tight")
    axs[0][1].imshow(m3d[:, 200, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[0][1].set_title("Model y-z")
    axs[0][1].axis("tight")
    axs[0][2].imshow(m3d[:, :, 220].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[0][2].set_title("Model y-z")
    axs[0][2].axis("tight")

    axs[1][0].imshow(mback3d[5, :, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[1][0].set_title("Smooth Model x-z")
    axs[1][0].axis("tight")
    axs[1][1].imshow(mback3d[:, 200, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[1][1].set_title("Smooth Model y-z")
    axs[1][1].axis("tight")
    axs[1][2].imshow(mback3d[:, :, 220].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[1][2].set_title("Smooth Model y-z")
    axs[1][2].axis("tight")

    axs[2][0].imshow(d[5, :, :].T, cmap="gray", vmin=-1, vmax=1)
    axs[2][0].set_title("Data x-z")
    axs[2][0].axis("tight")
    axs[2][1].imshow(d[:, 200, :].T, cmap='gray', vmin=-1, vmax=1)
    axs[2][1].set_title('Data y-z')
    axs[2][1].axis('tight')
    axs[2][2].imshow(d[:, :, 220].T, cmap='gray', vmin=-1, vmax=1)
    axs[2][2].set_title('Data x-y')
    axs[2][2].axis('tight')

    axs[3][0].imshow(minv3d_iter[5, :, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[3][0].set_title("Inverted Model iter x-z")
    axs[3][0].axis("tight")
    axs[3][1].imshow(minv3d_iter[:, 200, :].T, cmap='gist_rainbow', vmin=m.min(), vmax=m.max())
    axs[3][1].set_title('Inverted Model iter y-z')
    axs[3][1].axis('tight')
    axs[3][2].imshow(minv3d_iter[:, :, 220].T, cmap='gist_rainbow', vmin=m.min(), vmax=m.max())
    axs[3][2].set_title('Inverted Model iter x-y')
    axs[3][2].axis('tight')

    axs[4][0].imshow(minv3d_ne[5, :, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[4][0].set_title("Normal Equations Inverted Model iter x-z")
    axs[4][0].axis("tight")
    axs[4][1].imshow(minv3d_ne[:, 200, :].T, cmap='gist_rainbow', vmin=m.min(), vmax=m.max())
    axs[4][1].set_title('Normal Equations Inverted Model iter y-z')
    axs[4][1].axis('tight')
    axs[4][2].imshow(minv3d_ne[:, :, 220].T, cmap='gist_rainbow', vmin=m.min(), vmax=m.max())
    axs[4][2].set_title('Normal Equations Inverted Model iter x-y')
    axs[4][2].axis('tight')

    axs[5][0].imshow(minv3d_reg[5, :, :].T, cmap="gist_rainbow", vmin=m.min(), vmax=m.max())
    axs[5][0].set_title("Regularized Inverted Model iter x-z")
    axs[5][0].axis("tight")
    axs[5][1].imshow(minv3d_reg[:, 200, :].T, cmap='gist_rainbow', vmin=m.min(), vmax=m.max())
    axs[5][1].set_title('Regularized Inverted Model iter y-z')
    axs[5][1].axis('tight')
    axs[5][2].imshow(minv3d_reg[:, :, 220].T, cmap='gist_rainbow', vmin=m.min(), vmax=m.max())
    axs[5][2].set_title('Regularized Inverted Model iter x-y')
    axs[5][2].axis('tight')
Model x-z, Model y-z, Model y-z, Smooth Model x-z, Smooth Model y-z, Smooth Model y-z, Data x-z, Data y-z, Data x-y, Inverted Model iter x-z, Inverted Model iter y-z, Inverted Model iter x-y, Normal Equations Inverted Model iter x-z, Normal Equations Inverted Model iter y-z, Normal Equations Inverted Model iter x-y, Regularized Inverted Model iter x-z, Regularized Inverted Model iter y-z, Regularized Inverted Model iter x-y
Distr == Local True
Smooth Distr == Local True

Total running time of the script: (2 minutes 18.545 seconds)

Gallery generated by Sphinx-Gallery