LS-RTM with devito: a use case

Authors
Affiliations

Least-Squares Reverse Time Migration through Devito

@Author: Francesco Picetti - picettifrancesco@gmail.com

To use this notebook, we need devito installed. On your occamypy-ready env, run

pip install --user git+https://github.com/devitocodes/devito.git

import occamypy as o
import born_devito as b

# Plotting
from matplotlib import rcParams
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.pyplot as plt
rcParams.update({
    'image.cmap'     : 'gray',
    'image.aspect'   : 'auto',
    'image.interpolation': None,
    'axes.grid'      : False,
    'figure.figsize' : (10, 6),
    'savefig.dpi'    : 300,
    'axes.labelsize' : 14,
    'axes.titlesize' : 16,
    'font.size'      : 14,
    'legend.fontsize': 14,
    'xtick.labelsize': 14,
    'ytick.labelsize': 14,
    'text.usetex'    : True,
    'font.family'    : 'serif',
    'font.serif'     : 'Latin Modern Roman',
})
WARNING! DATAPATH not found. The folder /tmp will be used to write binary files
/Users/francesco/miniconda3/envs/occd/lib/python3.8/site-packages/dask_jobqueue/core.py:20: FutureWarning: tmpfile is deprecated and will be removed in a future release. Please use dask.utils.tmpfile instead.
  from distributed.utils import tmpfile

Setup

args = dict(
    filter_sigma=(1, 1),
    spacing=(10., 10.),  # meters
    shape=(101, 101),    # samples
    nbl=20,              # samples
    nreceivers=51,
    src_x=[100,500,900], # meters
    src_depth=20.,       # meters
    rec_depth=30.,       # meters
    t0=0.,
    tn=2000.,            # Simulation lasts 2 second (in ms)
    f0=0.010,            # Source peak frequency is 10Hz (in kHz)
    space_order=5,
    kernel="OT2",
    src_type='Ricker',
    workers=3,
    chunks=(1,1,1),
)
def unpad(model, nbl: int = args["nbl"]):
    return model[nbl:-nbl, nbl:-nbl]

create hard model and migration model

model_true, model_smooth, water = b.create_models(args)
model = o.VectorNumpy(model_smooth.vp.data.__array__())
model.ax_info = [
    o.AxInfo(model_smooth.vp.shape[0], model_smooth.origin[0] - model_smooth.nbl * model_smooth.spacing[0],
             model_smooth.spacing[0], "x [m]"),
    o.AxInfo(model_smooth.vp.shape[1], model_smooth.origin[1] - model_smooth.nbl * model_smooth.spacing[1],
             model_smooth.spacing[1], "z [m]")]
model_extent = [0., 1000., 1000., 0.]

Define acquisition geometry

rec = b.build_rec_coordinates(model_true, args)

Case 1: single shot

src = [b.build_src_coordinates(x, args["src_depth"]) for x in [500.]]

create the common shot gather (CSG) data

csg_nonlinear = b.propagate_shots(model=model_true, src_pos=src, rec_pos=rec, param=args)
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(csg_nonlinear.plot(), clim=o.plot.clim(csg_nonlinear[:]),
           extent=[csg_nonlinear.ax_info[1].o, csg_nonlinear.ax_info[1].last, csg_nonlinear.ax_info[0].last, csg_nonlinear.ax_info[0].o])
axs.set_xlabel(csg_nonlinear.ax_info[1].l)
axs.set_ylabel(csg_nonlinear.ax_info[0].l)
fig.suptitle(r"Nonlinear data: $\mathbf{d}$")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

Instantiate the Born operator

B = b.BornSingleSource(velocity=model_smooth, src_pos=src[0], rec_pos=rec, args=args)

Check the wavefield

t = 300
fig, axs = plt.subplots(1, 1, figsize=(12,6), sharey=True)
i1 = axs.imshow(unpad(B.src_wfld.data[t]).T, extent=model_extent)
i2 = axs.imshow(unpad(B.velocity[:]).T, cmap="jet", alpha=0.2, extent=model_extent)
axs.set_xlabel(B.velocity.ax_info[0].l)
axs.set_ylabel(B.velocity.ax_info[1].l)
divider = make_axes_locatable(axs)
c1 = divider.append_axes("right", size="2%", pad=0.05)
c2 = divider.append_axes("right", size="2%", pad=1)
plt.colorbar(i1, cax=c1, label="amplitude")
plt.colorbar(i2, cax=c2, label=r"$v_p$ [m/s]")
fig.suptitle(r"Source Wavefield at $t$=%s [s]" % (B.geometry.t0 + t * B.geometry.dt / 1000))
plt.tight_layout()
plt.show()
<Figure size 864x432 with 3 Axes>

Migration

m = B.T * csg_nonlinear
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(unpad(m).T, clim=o.plot.clim(m[:]), extent=model_extent)
axs.set_xlabel(m.ax_info[0].l)
axs.set_ylabel(m.ax_info[1].l)
fig.suptitle(r"$\mathbf{B}'\mathbf{d}$")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

For the sake of completeness, let's see how the linear data look like

img = o.VectorNumpy(model_true.vp.data.__array__() - model_smooth.vp.data.__array__())
d = B * img
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(d.plot(), clim=o.plot.clim(d[:]),
           extent=[d.ax_info[1].o, d.ax_info[1].last, d.ax_info[0].last, d.ax_info[0].o])
axs.set_xlabel(d.ax_info[1].l)
axs.set_ylabel(d.ax_info[0].l)
fig.suptitle(r"$\mathbf{B}\left(\mathbf{m} - \mathbf{m}_s \right)$")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

Case 2: multiple shots

In this case, we distribute each shot to a worker with Dask.

src = [b.build_src_coordinates(x, args["src_depth"]) for x in args["src_x"]]

instantiate Dask client

client = o.DaskClient(local_params={"processes": True}, n_wrks=args["workers"])
print("%d workers instantiated: %s" % (client.num_workers, client.WorkerIds))
print("If you have bokeh installed, you can monitor Dask at %s" % client.dashboard_link)
3 workers instantiated: ['tcp://127.0.0.1:49464', 'tcp://127.0.0.1:49467', 'tcp://127.0.0.1:49469']
If you have bokeh installed, you can monitor Dask at http://127.0.0.1:8787/status
# define how many shots will be processed by each worker:
chunks = args["chunks"] if args["chunks"] is not None else tuple([1] * len(src))
if len(chunks) != client.num_workers:
    raise ValueError("Provided chunks has to fit with client number of workers")
if sum(chunks) != len(src):
    raise UserWarning("Not all shots will be distributed")
    
print("Number of shots for each worker: ", chunks)
Number of shots for each worker:  (1, 1, 1)

Instantiate the Born operator

B = o.DaskOperator(dask_client=client,
                   chunks=chunks,
                   op_constructor=b.BornSingleSource,
                   op_args=[(v, s, r, a) for v, s, r, a in zip([model_smooth] * len(chunks),
                                                               src,
                                                               [rec] * len(chunks),
                                                               [args] * len(chunks))])

we need also a Spread operator to spread models to workers, and "stack" the images back

B *= o.DaskSpread(dask_client=client, chunks=chunks, domain=model.clone())

create the common shot gather (CSG) data

csg_nonlinear = b.propagate_shots(model=model_true, src_pos=src, rec_pos=rec, param=args)
fig, axs = plt.subplots(1, csg_nonlinear.n, sharey=True)
for shot in range(csg_nonlinear.n):
    _ = csg_nonlinear[shot]
    axs[shot].imshow(_.plot(), clim=o.plot.clim(_.plot()),
                     extent=[_.ax_info[1].o, _.ax_info[1].last, _.ax_info[0].last, _.ax_info[0].o])
    axs[shot].set_xlabel(_.ax_info[1].l)
    if shot == 0:
        axs[shot].set_ylabel(_.ax_info[0].l)
    axs[shot].set_title("$x_s$=%.0f [m]" % (src[shot][0, 0]))
    fig.suptitle(r"Nonlinear CSGs: $\mathbf{d}$")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 3 Axes>

muting the direct arrival can be done by analytically computing the direct arrival to mask the data

_da_mask = o.superVector([b.direct_arrival_mask(g, src_pos=s, rec_pos=rec, vel_sep=1500., offset=0.35)
                          for g, s in zip(csg_nonlinear.vecs, src)])

csg_muted = csg_nonlinear.clone().multiply(_da_mask)
fig, axs = plt.subplots(1, csg_muted.n, sharey=True)
for shot in range(csg_muted.n):
    _ = csg_muted[shot]
    axs[shot].imshow(_.plot(), clim=o.plot.clim(_.plot()),
                     extent=[_.ax_info[1].o, _.ax_info[1].last, _.ax_info[0].last, _.ax_info[0].o])
    axs[shot].set_xlabel(_.ax_info[1].l)
    if shot == 0:
        axs[shot].set_ylabel(_.ax_info[0].l)
    axs[shot].set_title("$x_s$=%.0f [m]" % (src[shot][0, 0]))
    fig.suptitle(r"Muted Nonlinear CSGs: $\mathbf{d} - \mathbf{d}_{DA}$")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 3 Axes>

Finally, distribute the data to Dask

csg_ = o.DaskVector(client, vectors=csg_muted.vecs, chunks=args["chunks"])
imaging_label = r"$\mathbf{B}' \left(\mathbf{d} - \mathbf{d}_{DA}\right)$"
inverse_label = r"$\mathbf{B}^{-1} \left(\mathbf{d} - \mathbf{d}_{DA}\right)$"

Migration

image = B.T * csg_
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(unpad(image).T, clim=o.plot.clim(image[:]), extent=model_extent)
axs.set_xlabel(image.ax_info[0].l)
axs.set_ylabel(image.ax_info[1].l)
fig.suptitle(imaging_label)
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

create a post-processing operator: apply a gain with depth Gz\mathbf{G}_z and a Laplacian filter L\mathbf{L}

G = o.Diagonal(b.depth_compensation_mask(model, 1.5))
L = o.SecondDerivative(model, axis=1, sampling=args["spacing"][1])

gain_label = r"$\mathbf{G}_z$"
lapl_label = r"$\mathbf{L}$"
image_post = G * L * image
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(unpad(image_post).T, clim=o.plot.clim(image_post[:]), extent=model_extent)
axs.set_xlabel(image_post.ax_info[0].l)
axs.set_ylabel(image_post.ax_info[1].l)
fig.suptitle(gain_label+lapl_label+imaging_label)
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

Least-Squares!

Thanks to OccamyPy we can add also an (anisotropic) total variation regularization in 1\ell_1

Under the assumption that our reflectors are almost flat, we define an unbalanced TV, in which the x-derivative is stronger than the z-derivative.

R = o.Vstack(10 * o.FirstDerivative(model.clone(), axis=0, sampling=args["spacing"][0]),
                  o.FirstDerivative(model.clone(), axis=1, sampling=args["spacing"][1]))

Instantiate the L1-regularized problem, known as GeneralizedLasso

problem = o.GeneralizedLasso(model.clone().zero(), csg_, B, reg=R, eps=1e2)
problem.name = "TV-L1 LS-RTM (3 shots)"

Instantiate the Split-Bregman solver

solver = o.SplitBregman(o.BasicStopper(niter=10), niter_inner=3, niter_solver=3,
                        linear_solver='LSQR', breg_weight=1., warm_start=True)
solver.setDefaults(save_obj=True)

Now, solve for real! Don't forget to check the Dask dashboard!

solver.run(problem, verbose=True, inner_verbose=False)
##########################################################################################
            SPLIT-BREGMAN Solver
    Restart folder: /tmp/restart_2022-04-25T19-24-06.830017/
    Inner iterations: 3
    Solver iterations: 3
    Problem: TV-L1 LS-RTM (3 shots)
    L1 Regularizer weight: 1.00e+02
    Bregman update weight: 1.00e+00
    Using warm start option for inner problem
##########################################################################################

iter = 00, obj = 8.43233e+03, df_obj = 8.43e+03, reg_obj = 0.00e+00, rnorm = 1.30e+02
iter = 01, obj = 6.89802e+03, df_obj = 3.30e+03, reg_obj = 3.60e+03, rnorm = 8.12e+01
iter = 02, obj = 7.64437e+03, df_obj = 2.72e+03, reg_obj = 4.93e+03, rnorm = 7.37e+01
iter = 03, obj = 8.08229e+03, df_obj = 2.44e+03, reg_obj = 5.64e+03, rnorm = 6.99e+01
iter = 04, obj = 8.38706e+03, df_obj = 2.28e+03, reg_obj = 6.11e+03, rnorm = 6.75e+01
iter = 05, obj = 8.53805e+03, df_obj = 2.17e+03, reg_obj = 6.37e+03, rnorm = 6.59e+01
iter = 06, obj = 8.63467e+03, df_obj = 2.10e+03, reg_obj = 6.53e+03, rnorm = 6.49e+01
iter = 07, obj = 8.66150e+03, df_obj = 2.06e+03, reg_obj = 6.60e+03, rnorm = 6.41e+01
iter = 08, obj = 8.68659e+03, df_obj = 2.02e+03, reg_obj = 6.66e+03, rnorm = 6.36e+01
iter = 09, obj = 8.66472e+03, df_obj = 2.00e+03, reg_obj = 6.67e+03, rnorm = 6.32e+01
iter = 10, obj = 8.65067e+03, df_obj = 1.98e+03, reg_obj = 6.67e+03, rnorm = 6.30e+01
Terminate: maximum number of iterations reached

##########################################################################################
            SPLIT-BREGMAN Solver end
##########################################################################################

Let's check the objective function terms

fig, axs = plt.subplots(1, 1, sharey=True)
axs.semilogy(solver.obj / solver.obj[0], 'k', lw=2, label='Obj')
axs.semilogy(solver.obj_terms[:, 0] / solver.obj[0], 'b--', label=r"$0.5 \Vert \mathbf{Bm-d} \Vert_2^2$")
axs.semilogy(solver.obj_terms[:, 1] / solver.obj[0], 'r--',
             label=r"$\sum_i\varepsilon_i \Vert \nabla_i\mathbf{m}\Vert_1$")
axs.legend(), axs.grid(True)
axs.set_xlim(0, solver.stopper.niter), axs.set_ylim(1e-2, 1e1)
axs.set_xlabel("main iteration")
plt.suptitle("Split-Bregman objective terms")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

Now check the result

fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(unpad(problem.model).T, clim=o.plot.clim(problem.model[:]), extent=model_extent)
axs.set_xlabel(image.ax_info[0].l)
axs.set_ylabel(image.ax_info[1].l)
fig.suptitle(inverse_label)
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

Finally, apply the post-processing operator

img_inv_post = G * L * problem.model
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(unpad(img_inv_post).T, clim=o.plot.clim(img_inv_post[:]), extent=model_extent)
axs.set_xlabel(img_inv_post.ax_info[0].l)
axs.set_ylabel(img_inv_post.ax_info[1].l)
fig.suptitle(gain_label+lapl_label+inverse_label + " with L1 constraint")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>

Compare sparsity promoting result with simple CG inversion

Let's now compare the result we obtained with Split-Bregman with the simpler CG with the same number of linear inversion iterations (approx. the same time).

problem_cg = o.LeastSquares(model.clone().zero(), csg_, B)
problem_cg.name = "LS-RTM (3 shots)"
cg = o.CG(o.BasicStopper(niter=int(solver.stopper.niter * solver.niter_inner * solver.niter_solver)))
cg.stopper.niter
90
cg.setDefaults(save_obj=True)
cg.run(problem_cg, verbose=True)
##########################################################################################
            CG Solver
    Restart folder: /tmp/restart_2022-04-25T19-25-59.037417/
    Problem: LS-RTM (3 shots)
##########################################################################################

iter = 00, obj = 8.43233e+03, rnorm = 1.30e+02, gnorm = 7.35e+03, feval = 002
iter = 01, obj = 6.07498e+03, rnorm = 1.10e+02, gnorm = 3.53e+03, feval = 004
iter = 02, obj = 5.52355e+03, rnorm = 1.05e+02, gnorm = 3.83e+03, feval = 006
iter = 03, obj = 4.52681e+03, rnorm = 9.52e+01, gnorm = 2.48e+03, feval = 008
iter = 04, obj = 4.04192e+03, rnorm = 8.99e+01, gnorm = 1.98e+03, feval = 010
iter = 05, obj = 3.49174e+03, rnorm = 8.36e+01, gnorm = 1.37e+03, feval = 012
iter = 06, obj = 3.16442e+03, rnorm = 7.96e+01, gnorm = 1.24e+03, feval = 014
iter = 07, obj = 2.90649e+03, rnorm = 7.62e+01, gnorm = 1.24e+03, feval = 016
iter = 08, obj = 2.69179e+03, rnorm = 7.34e+01, gnorm = 1.28e+03, feval = 018
iter = 09, obj = 2.53832e+03, rnorm = 7.13e+01, gnorm = 8.29e+02, feval = 020
iter = 10, obj = 2.48939e+03, rnorm = 7.06e+01, gnorm = 1.35e+03, feval = 022
iter = 11, obj = 2.33930e+03, rnorm = 6.84e+01, gnorm = 6.50e+02, feval = 024
iter = 12, obj = 2.20933e+03, rnorm = 6.65e+01, gnorm = 6.72e+02, feval = 026
iter = 13, obj = 2.16732e+03, rnorm = 6.58e+01, gnorm = 8.72e+02, feval = 028
iter = 14, obj = 2.11167e+03, rnorm = 6.50e+01, gnorm = 5.05e+02, feval = 030
iter = 15, obj = 2.04484e+03, rnorm = 6.40e+01, gnorm = 4.82e+02, feval = 032
iter = 16, obj = 1.98327e+03, rnorm = 6.30e+01, gnorm = 4.97e+02, feval = 034
iter = 17, obj = 1.95725e+03, rnorm = 6.26e+01, gnorm = 7.75e+02, feval = 036
iter = 18, obj = 1.93042e+03, rnorm = 6.21e+01, gnorm = 6.29e+02, feval = 038
iter = 19, obj = 1.90943e+03, rnorm = 6.18e+01, gnorm = 3.92e+02, feval = 040
iter = 20, obj = 1.86890e+03, rnorm = 6.11e+01, gnorm = 3.25e+02, feval = 042
iter = 21, obj = 1.84155e+03, rnorm = 6.07e+01, gnorm = 3.89e+02, feval = 044
iter = 22, obj = 1.82661e+03, rnorm = 6.04e+01, gnorm = 3.48e+02, feval = 046
iter = 23, obj = 1.80332e+03, rnorm = 6.01e+01, gnorm = 3.47e+02, feval = 048
iter = 24, obj = 1.79698e+03, rnorm = 5.99e+01, gnorm = 3.20e+02, feval = 050
iter = 25, obj = 1.77544e+03, rnorm = 5.96e+01, gnorm = 3.10e+02, feval = 052
iter = 26, obj = 1.76496e+03, rnorm = 5.94e+01, gnorm = 4.24e+02, feval = 054
iter = 27, obj = 1.75716e+03, rnorm = 5.93e+01, gnorm = 4.15e+02, feval = 056
iter = 28, obj = 1.74325e+03, rnorm = 5.90e+01, gnorm = 2.80e+02, feval = 058
iter = 29, obj = 1.72783e+03, rnorm = 5.88e+01, gnorm = 3.34e+02, feval = 060
iter = 30, obj = 1.71677e+03, rnorm = 5.86e+01, gnorm = 2.22e+02, feval = 062
iter = 31, obj = 1.69818e+03, rnorm = 5.83e+01, gnorm = 3.26e+02, feval = 064
iter = 32, obj = 1.68983e+03, rnorm = 5.81e+01, gnorm = 2.12e+02, feval = 066
iter = 33, obj = 1.67455e+03, rnorm = 5.79e+01, gnorm = 2.53e+02, feval = 068
iter = 34, obj = 1.66287e+03, rnorm = 5.77e+01, gnorm = 4.48e+02, feval = 070
iter = 35, obj = 1.65693e+03, rnorm = 5.76e+01, gnorm = 2.48e+02, feval = 072
iter = 36, obj = 1.65114e+03, rnorm = 5.75e+01, gnorm = 3.22e+02, feval = 074
iter = 37, obj = 1.63933e+03, rnorm = 5.73e+01, gnorm = 3.21e+02, feval = 076
iter = 38, obj = 1.63330e+03, rnorm = 5.72e+01, gnorm = 2.44e+02, feval = 078
iter = 39, obj = 1.62551e+03, rnorm = 5.70e+01, gnorm = 2.44e+02, feval = 080
iter = 40, obj = 1.61151e+03, rnorm = 5.68e+01, gnorm = 1.97e+02, feval = 082
iter = 41, obj = 1.59848e+03, rnorm = 5.65e+01, gnorm = 1.75e+02, feval = 084
iter = 42, obj = 1.59035e+03, rnorm = 5.64e+01, gnorm = 3.15e+02, feval = 086
iter = 43, obj = 1.58738e+03, rnorm = 5.63e+01, gnorm = 2.28e+02, feval = 088
iter = 44, obj = 1.58097e+03, rnorm = 5.62e+01, gnorm = 2.03e+02, feval = 090
iter = 45, obj = 1.56938e+03, rnorm = 5.60e+01, gnorm = 1.69e+02, feval = 092
iter = 46, obj = 1.56029e+03, rnorm = 5.59e+01, gnorm = 2.92e+02, feval = 094
iter = 47, obj = 1.55301e+03, rnorm = 5.57e+01, gnorm = 1.67e+02, feval = 096
iter = 48, obj = 1.54523e+03, rnorm = 5.56e+01, gnorm = 2.20e+02, feval = 098
iter = 49, obj = 1.53989e+03, rnorm = 5.55e+01, gnorm = 2.33e+02, feval = 100
iter = 50, obj = 1.53652e+03, rnorm = 5.54e+01, gnorm = 2.41e+02, feval = 102
iter = 51, obj = 1.53377e+03, rnorm = 5.54e+01, gnorm = 2.32e+02, feval = 104
iter = 52, obj = 1.52879e+03, rnorm = 5.53e+01, gnorm = 3.06e+02, feval = 106
iter = 53, obj = 1.52105e+03, rnorm = 5.52e+01, gnorm = 1.90e+02, feval = 108
iter = 54, obj = 1.51796e+03, rnorm = 5.51e+01, gnorm = 1.99e+02, feval = 110
iter = 55, obj = 1.50804e+03, rnorm = 5.49e+01, gnorm = 1.50e+02, feval = 112
iter = 56, obj = 1.49982e+03, rnorm = 5.48e+01, gnorm = 2.36e+02, feval = 114
iter = 57, obj = 1.49445e+03, rnorm = 5.47e+01, gnorm = 1.63e+02, feval = 116
iter = 58, obj = 1.48954e+03, rnorm = 5.46e+01, gnorm = 1.88e+02, feval = 118
iter = 59, obj = 1.48486e+03, rnorm = 5.45e+01, gnorm = 2.83e+02, feval = 120
iter = 60, obj = 1.48207e+03, rnorm = 5.44e+01, gnorm = 2.16e+02, feval = 122
iter = 61, obj = 1.47729e+03, rnorm = 5.44e+01, gnorm = 1.67e+02, feval = 124
iter = 62, obj = 1.47252e+03, rnorm = 5.43e+01, gnorm = 1.83e+02, feval = 126
iter = 63, obj = 1.47050e+03, rnorm = 5.42e+01, gnorm = 1.90e+02, feval = 128
iter = 64, obj = 1.46283e+03, rnorm = 5.41e+01, gnorm = 1.24e+02, feval = 130
iter = 65, obj = 1.45556e+03, rnorm = 5.40e+01, gnorm = 1.40e+02, feval = 132
iter = 66, obj = 1.45047e+03, rnorm = 5.39e+01, gnorm = 1.94e+02, feval = 134
iter = 67, obj = 1.44721e+03, rnorm = 5.38e+01, gnorm = 1.75e+02, feval = 136
iter = 68, obj = 1.44298e+03, rnorm = 5.37e+01, gnorm = 1.71e+02, feval = 138
iter = 69, obj = 1.44133e+03, rnorm = 5.37e+01, gnorm = 2.29e+02, feval = 140
iter = 70, obj = 1.43715e+03, rnorm = 5.36e+01, gnorm = 1.97e+02, feval = 142
iter = 71, obj = 1.43254e+03, rnorm = 5.35e+01, gnorm = 1.83e+02, feval = 144
iter = 72, obj = 1.42895e+03, rnorm = 5.35e+01, gnorm = 1.62e+02, feval = 146
iter = 73, obj = 1.42631e+03, rnorm = 5.34e+01, gnorm = 2.19e+02, feval = 148
iter = 74, obj = 1.42249e+03, rnorm = 5.33e+01, gnorm = 1.32e+02, feval = 150
iter = 75, obj = 1.41528e+03, rnorm = 5.32e+01, gnorm = 1.31e+02, feval = 152
iter = 76, obj = 1.41170e+03, rnorm = 5.31e+01, gnorm = 2.10e+02, feval = 154
iter = 77, obj = 1.40706e+03, rnorm = 5.30e+01, gnorm = 2.60e+02, feval = 156
iter = 78, obj = 1.40516e+03, rnorm = 5.30e+01, gnorm = 1.47e+02, feval = 158
iter = 79, obj = 1.39989e+03, rnorm = 5.29e+01, gnorm = 1.62e+02, feval = 160
iter = 80, obj = 1.39577e+03, rnorm = 5.28e+01, gnorm = 1.38e+02, feval = 162
iter = 81, obj = 1.39230e+03, rnorm = 5.28e+01, gnorm = 1.69e+02, feval = 164
iter = 82, obj = 1.38926e+03, rnorm = 5.27e+01, gnorm = 2.11e+02, feval = 166
iter = 83, obj = 1.38516e+03, rnorm = 5.26e+01, gnorm = 2.14e+02, feval = 168
iter = 84, obj = 1.38190e+03, rnorm = 5.26e+01, gnorm = 1.33e+02, feval = 170
iter = 85, obj = 1.37877e+03, rnorm = 5.25e+01, gnorm = 2.29e+02, feval = 172
iter = 86, obj = 1.37572e+03, rnorm = 5.25e+01, gnorm = 2.33e+02, feval = 174
iter = 87, obj = 1.37383e+03, rnorm = 5.24e+01, gnorm = 1.23e+02, feval = 176
iter = 88, obj = 1.36730e+03, rnorm = 5.23e+01, gnorm = 1.37e+02, feval = 178
iter = 89, obj = 1.36365e+03, rnorm = 5.22e+01, gnorm = 1.35e+02, feval = 180
iter = 90, obj = 1.36107e+03, rnorm = 5.22e+01, gnorm = 1.43e+02, feval = 182
Terminate: maximum number of iterations reached

##########################################################################################
            CG Solver end
##########################################################################################

cg_img_inv_post = G * L * problem_cg.model
fig, axs = plt.subplots(1, 1, sharey=True)
axs.imshow(unpad(cg_img_inv_post).T, clim=o.plot.clim(cg_img_inv_post[:]), extent=model_extent)
axs.set_xlabel(cg_img_inv_post.ax_info[0].l)
axs.set_ylabel(cg_img_inv_post.ax_info[1].l)
fig.suptitle(gain_label+lapl_label+inverse_label + " through CG")
plt.tight_layout()
plt.show()
<Figure size 720x432 with 1 Axes>