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()
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()
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()
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()
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()
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()
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()
create a post-processing operator: apply a gain with depth and a Laplacian filter
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()
Least-Squares!¶
Thanks to OccamyPy we can add also an (anisotropic) total variation regularization in
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()
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()
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()
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()