Note
Go to the end to download the full example code.
4. Adjoint-state vs. FD gradient#
The gradient of the misfit function, as implemented in emg3d, uses the
adjoint-state method following [PlMu08] (see
emg3d.simulations.Simulation.gradient
). The method has the advantage
that it is very fast. However, it can be tricky to implement and it is always
good to verify the implementation against another method.
We compare in this example the adjoint-state gradient to a simple forward finite-difference gradient. (See 04. Gradient of the misfit function for more details regarding the adjoint-state gradient.)
import emg3d
import itertools
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.colors import LogNorm, SymLogNorm
1. Create a survey and a simple model#
Create a simple block model and a survey for the comparison.
Survey#
The survey consists of one source, one receiver, both electric, x-directed point-dipoles, where the receiver is on the seafloor and the source 50 m above. Offset is 3.2 km, and acquisition frequency is 1 Hz.
survey = emg3d.surveys.Survey(
name='Gradient Test-Survey',
sources=emg3d.TxElectricDipole((-1600, 0, -1950, 0, 0)),
receivers=emg3d.RxElectricPoint((1600, 0, -2000, 0, 0)),
frequencies=1.0,
noise_floor=1e-15,
relative_error=0.05,
)
Model#
As emg3d internally computes with conductivities we use conductivities to compare the gradient in its purest implementation. Note that if we define our model in terms of resistivities or \(\log_{\{e;10\}}(\{\sigma;\rho\})\), the gradient would look differently.
# Create a simple block model.
hx = np.array([1000, 1500, 1000, 1500, 1000])
hy = np.array([1000, 1500, 1000, 1500, 1000])
hz = np.array([1800., 200., 200., 200., 300., 300., 2000., 500.])
model_grid = emg3d.TensorMesh(
[hx, hy, hz], origin=np.array([-3000, -3000, -5000]))
# Initiate model with conductivities of 1 S/m.
model = emg3d.Model(
model_grid, np.ones(model_grid.n_cells), mapping='Conductivity')
model.property_x[:, :, -1] = 1e-8 # Add air layer.
model.property_x[:, :, -2] = 3.33 # Add seawater layer.
model_bg = model.copy() # Make a copy for the background model.
# Add three blocks.
model.property_x[1, 1:3, 1:3] = 0.02
model.property_x[3, 2:4, 2:4] = 0.01
model.property_x[2, 1:4, 4] = 0.005
QC#
model_grid.plot_3d_slicer(model.property_x.ravel('F'), zslice=-2900,
pcolor_opts={'norm': LogNorm(vmin=0.002, vmax=3.5)})
plt.suptitle('Conductivity (S/m)')
axs = plt.gcf().get_children()
rec_coords = survey.receiver_coordinates()
src_coords = survey.source_coordinates()
axs[1].plot(rec_coords[0], rec_coords[1], 'bv')
axs[2].plot(rec_coords[0], rec_coords[2], 'bv')
axs[3].plot(rec_coords[2], rec_coords[1], 'bv')
axs[1].plot(src_coords[0], src_coords[1], 'r*')
axs[2].plot(src_coords[0], src_coords[2], 'r*')
axs[3].plot(src_coords[2], src_coords[1], 'r*')
Generate synthetic data#
# Gridding options.
gridding_opts = {
'frequency': survey.frequencies['f-1'],
'properties': [3.33, 1, 1, 3.33],
'center': (0, 0, -2000),
'min_width_limits': 100,
'domain': ([-2000, 2000], [-2000, 2000], [-3200, -2000]),
'mapping': model.map,
'center_on_edge': True,
}
data_grid = emg3d.construct_mesh(**gridding_opts)
# Define a simulation for the data.
simulation_data = emg3d.simulations.Simulation(
name='Data for Gradient Test',
survey=survey,
model=model.interpolate_to_grid(data_grid),
gridding='same', # Same grid as for input model.
max_workers=4,
)
# Simulate the data and store them as observed.
simulation_data.compute(observed=True)
# Let's print the survey to check that the observed data are now set.
survey
Compute efields 0/1 [00:00]
Compute efields ██████████ 1/1 [00:04]
Compute efields ██████████ 1/1 [00:04]
Adjoint-state gradient#
For the actual comparison we use a very coarse mesh. The reason is that we have to compute an extra forward model for each cell for the forward finite-difference approximation, so we try to keep that number low (even though we only do a cross-section, not the entire cube).
Our computational grid has only 16,384 cells, which should be fast enough to compute the FD gradient of a cross-section in a few minutes. A cross-section along the survey-line has \(32 \times 11 = 352\) cells, so we need to compute an extra 352 forward models. (There are 16 cells in z, but only 11 below the seafloor.)
# Computational grid (min_width 200 instead of 100).
comp_grid_opts = {**gridding_opts, 'min_width_limits': 200}
comp_grid = emg3d.construct_mesh(**comp_grid_opts)
# Interpolate the background model onto the computational grid.
comp_model = model_bg.interpolate_to_grid(comp_grid)
# AS gradient simulation.
simulation_as = emg3d.simulations.Simulation(
name='AS Gradient Test',
survey=survey,
model=comp_model,
gridding='same', # Same grid as for input model.
max_workers=4, # For parallel workers, adjust if you have more.
receiver_interpolation='linear', # For proper adjoint-state gradient
)
simulation_as
# Get the misfit and the gradient of the misfit.
data_misfit = simulation_as.misfit
as_grad = simulation_as.gradient
# Set water and air gradient to NaN for the plots.
as_grad[:, :, comp_grid.cell_centers_z > -2000] = np.nan
Compute efields 0/1 [00:00]
Compute efields ██████████ 1/1 [00:00]
Compute efields ██████████ 1/1 [00:00]
Back-propagate 0/1 [00:00]
Back-propagate ██████████ 1/1 [00:00]
Back-propagate ██████████ 1/1 [00:00]
Finite-Difference gradient#
To test if the adjoint-state gradient indeed returns the gradient we can compare it to a one-sided finite-difference approximated gradient as given by
Define a fct to compute FD gradient for one cell#
# Define epsilon (some small conductivity value, S/m).
epsilon = 0.0001
# Define the cross-section.
iy = comp_grid.shape_cells[1]//2
def comp_fd_grad(ixiz):
"""Compute forward-FD gradient for one cell."""
# Copy the computational model.
fd_model = comp_model.copy()
# Add conductivity-epsilon to this (ix, iy, iz) cell.
fd_model.property_x[ixiz[0], iy, ixiz[1]] += epsilon
# Create a new simulation with this model
simulation_fd = emg3d.simulations.Simulation(
name='FD Gradient Test',
survey=survey, model=fd_model, gridding='same',
max_workers=1, solver_opts={'verb': 1},
receiver_interpolation='linear', # For proper adjoint-state gradient
)
# Switch-of progress bar in this case
simulation_fd._tqdm_opts['disable'] = True
# Get misfit
fd_data_misfit = simulation_fd.misfit
# Return gradient
return float((fd_data_misfit - data_misfit)/epsilon)
Loop over all required cells#
# Initiate FD gradient.
fd_grad = np.zeros_like(as_grad)
# Get all ix-iz combinations (without air/water).
ixiz = list(itertools.product(
range(comp_grid.shape_cells[0]),
range(len(comp_grid.cell_centers_z[comp_grid.cell_centers_z < -2000])))
)
# Wrap it asynchronously
out = emg3d._multiprocessing.process_map(
comp_fd_grad,
ixiz,
max_workers=4, # Adjust max worker here!
)
# Collect result
for i, (ix, iz) in enumerate(ixiz):
fd_grad[ix, iy, iz] = out[i]
0%| | 0/352 [00:00<?, ?it/s]
0%| | 1/352 [00:00<02:50, 2.05it/s]
1%|▏ | 5/352 [00:00<00:59, 5.87it/s]
3%|▎ | 9/352 [00:01<00:49, 6.95it/s]
4%|▎ | 13/352 [00:01<00:46, 7.36it/s]
5%|▍ | 17/352 [00:02<00:43, 7.64it/s]
6%|▌ | 21/352 [00:02<00:41, 7.91it/s]
7%|▋ | 25/352 [00:03<00:40, 8.05it/s]
8%|▊ | 29/352 [00:03<00:40, 8.05it/s]
9%|▉ | 33/352 [00:04<00:39, 8.10it/s]
10%|▉ | 35/352 [00:04<00:34, 9.07it/s]
11%|█ | 37/352 [00:04<00:40, 7.79it/s]
11%|█ | 39/352 [00:04<00:35, 8.77it/s]
12%|█▏ | 41/352 [00:05<00:41, 7.55it/s]
12%|█▏ | 43/352 [00:05<00:36, 8.51it/s]
13%|█▎ | 45/352 [00:05<00:42, 7.15it/s]
14%|█▎ | 48/352 [00:06<00:31, 9.74it/s]
14%|█▍ | 50/352 [00:06<00:37, 8.03it/s]
15%|█▍ | 52/352 [00:06<00:32, 9.26it/s]
15%|█▌ | 54/352 [00:06<00:38, 7.74it/s]
16%|█▌ | 56/352 [00:07<00:32, 9.03it/s]
16%|█▋ | 58/352 [00:07<00:38, 7.58it/s]
17%|█▋ | 60/352 [00:07<00:32, 8.93it/s]
18%|█▊ | 62/352 [00:07<00:38, 7.47it/s]
18%|█▊ | 63/352 [00:08<00:38, 7.45it/s]
18%|█▊ | 65/352 [00:08<00:40, 7.00it/s]
19%|█▉ | 67/352 [00:08<00:35, 8.01it/s]
20%|█▉ | 69/352 [00:08<00:39, 7.23it/s]
20%|██ | 71/352 [00:09<00:36, 7.66it/s]
21%|██ | 73/352 [00:09<00:37, 7.44it/s]
21%|██▏ | 75/352 [00:09<00:33, 8.38it/s]
22%|██▏ | 77/352 [00:09<00:38, 7.05it/s]
22%|██▏ | 79/352 [00:10<00:32, 8.49it/s]
23%|██▎ | 81/352 [00:10<00:37, 7.26it/s]
24%|██▎ | 83/352 [00:10<00:31, 8.57it/s]
24%|██▍ | 85/352 [00:10<00:36, 7.34it/s]
25%|██▍ | 87/352 [00:11<00:30, 8.71it/s]
25%|██▌ | 89/352 [00:11<00:36, 7.26it/s]
26%|██▌ | 91/352 [00:11<00:30, 8.62it/s]
26%|██▋ | 93/352 [00:11<00:36, 7.12it/s]
27%|██▋ | 95/352 [00:12<00:29, 8.61it/s]
28%|██▊ | 97/352 [00:12<00:36, 7.01it/s]
28%|██▊ | 100/352 [00:12<00:31, 7.99it/s]
29%|██▊ | 101/352 [00:13<00:35, 7.15it/s]
30%|██▉ | 104/352 [00:13<00:30, 8.15it/s]
30%|██▉ | 105/352 [00:13<00:35, 6.88it/s]
30%|███ | 106/352 [00:13<00:34, 7.23it/s]
31%|███ | 108/352 [00:13<00:28, 8.57it/s]
31%|███ | 109/352 [00:14<00:34, 6.96it/s]
31%|███▏ | 110/352 [00:14<00:33, 7.18it/s]
32%|███▏ | 112/352 [00:14<00:29, 8.22it/s]
32%|███▏ | 113/352 [00:14<00:33, 7.11it/s]
32%|███▏ | 114/352 [00:14<00:33, 7.09it/s]
33%|███▎ | 116/352 [00:14<00:28, 8.38it/s]
33%|███▎ | 117/352 [00:15<00:33, 7.04it/s]
34%|███▎ | 118/352 [00:15<00:31, 7.51it/s]
34%|███▍ | 120/352 [00:15<00:28, 8.22it/s]
34%|███▍ | 121/352 [00:15<00:34, 6.69it/s]
35%|███▍ | 123/352 [00:15<00:27, 8.33it/s]
35%|███▌ | 124/352 [00:15<00:26, 8.47it/s]
36%|███▌ | 125/352 [00:16<00:35, 6.48it/s]
36%|███▌ | 127/352 [00:16<00:28, 7.87it/s]
37%|███▋ | 129/352 [00:16<00:31, 7.02it/s]
37%|███▋ | 130/352 [00:16<00:30, 7.30it/s]
37%|███▋ | 131/352 [00:17<00:32, 6.81it/s]
38%|███▊ | 133/352 [00:17<00:28, 7.58it/s]
38%|███▊ | 134/352 [00:17<00:29, 7.51it/s]
38%|███▊ | 135/352 [00:17<00:29, 7.37it/s]
39%|███▉ | 137/352 [00:17<00:27, 7.86it/s]
39%|███▉ | 138/352 [00:17<00:28, 7.56it/s]
39%|███▉ | 139/352 [00:18<00:28, 7.40it/s]
40%|████ | 141/352 [00:18<00:27, 7.58it/s]
40%|████ | 142/352 [00:18<00:26, 7.82it/s]
41%|████ | 143/352 [00:18<00:28, 7.24it/s]
41%|████ | 145/352 [00:18<00:28, 7.33it/s]
42%|████▏ | 147/352 [00:19<00:25, 7.93it/s]
42%|████▏ | 149/352 [00:19<00:27, 7.26it/s]
43%|████▎ | 151/352 [00:19<00:27, 7.43it/s]
43%|████▎ | 153/352 [00:19<00:27, 7.12it/s]
44%|████▍ | 155/352 [00:20<00:24, 7.97it/s]
45%|████▍ | 157/352 [00:20<00:27, 7.08it/s]
45%|████▌ | 159/352 [00:20<00:25, 7.69it/s]
46%|████▌ | 161/352 [00:21<00:26, 7.29it/s]
46%|████▋ | 163/352 [00:21<00:23, 7.94it/s]
47%|████▋ | 165/352 [00:21<00:25, 7.48it/s]
47%|████▋ | 166/352 [00:21<00:24, 7.69it/s]
47%|████▋ | 167/352 [00:21<00:24, 7.52it/s]
48%|████▊ | 169/352 [00:22<00:24, 7.34it/s]
48%|████▊ | 170/352 [00:22<00:24, 7.56it/s]
49%|████▊ | 171/352 [00:22<00:23, 7.79it/s]
49%|████▉ | 173/352 [00:22<00:24, 7.40it/s]
49%|████▉ | 174/352 [00:22<00:23, 7.72it/s]
50%|████▉ | 175/352 [00:22<00:22, 7.85it/s]
50%|█████ | 177/352 [00:23<00:23, 7.51it/s]
51%|█████ | 178/352 [00:23<00:22, 7.81it/s]
51%|█████ | 179/352 [00:23<00:22, 7.74it/s]
51%|█████▏ | 181/352 [00:23<00:22, 7.46it/s]
52%|█████▏ | 183/352 [00:23<00:22, 7.56it/s]
53%|█████▎ | 185/352 [00:24<00:22, 7.28it/s]
53%|█████▎ | 187/352 [00:24<00:20, 7.91it/s]
54%|█████▎ | 189/352 [00:24<00:22, 7.28it/s]
54%|█████▍ | 191/352 [00:24<00:20, 7.97it/s]
55%|█████▍ | 193/352 [00:25<00:21, 7.33it/s]
55%|█████▌ | 195/352 [00:25<00:20, 7.84it/s]
56%|█████▌ | 197/352 [00:25<00:20, 7.42it/s]
57%|█████▋ | 199/352 [00:25<00:19, 7.69it/s]
57%|█████▋ | 201/352 [00:26<00:20, 7.50it/s]
58%|█████▊ | 203/352 [00:26<00:19, 7.63it/s]
58%|█████▊ | 205/352 [00:26<00:19, 7.48it/s]
59%|█████▊ | 206/352 [00:26<00:19, 7.68it/s]
59%|█████▉ | 207/352 [00:27<00:20, 6.93it/s]
59%|█████▉ | 209/352 [00:27<00:19, 7.27it/s]
60%|█████▉ | 211/352 [00:27<00:18, 7.55it/s]
61%|██████ | 213/352 [00:27<00:19, 7.24it/s]
61%|██████ | 215/352 [00:28<00:18, 7.28it/s]
62%|██████▏ | 217/352 [00:28<00:17, 7.54it/s]
62%|██████▏ | 219/352 [00:28<00:18, 7.24it/s]
63%|██████▎ | 221/352 [00:28<00:16, 7.80it/s]
63%|██████▎ | 222/352 [00:29<00:16, 7.81it/s]
63%|██████▎ | 223/352 [00:29<00:17, 7.19it/s]
64%|██████▍ | 225/352 [00:29<00:17, 7.41it/s]
64%|██████▍ | 226/352 [00:29<00:16, 7.82it/s]
64%|██████▍ | 227/352 [00:29<00:18, 6.80it/s]
65%|██████▌ | 229/352 [00:30<00:15, 7.73it/s]
65%|██████▌ | 230/352 [00:30<00:16, 7.20it/s]
66%|██████▌ | 231/352 [00:30<00:17, 6.98it/s]
66%|██████▌ | 232/352 [00:30<00:16, 7.44it/s]
66%|██████▌ | 233/352 [00:30<00:15, 7.68it/s]
66%|██████▋ | 234/352 [00:30<00:16, 7.31it/s]
67%|██████▋ | 235/352 [00:30<00:16, 7.00it/s]
67%|██████▋ | 236/352 [00:31<00:15, 7.35it/s]
68%|██████▊ | 238/352 [00:31<00:16, 6.74it/s]
68%|██████▊ | 240/352 [00:31<00:14, 7.69it/s]
69%|██████▉ | 242/352 [00:31<00:16, 6.83it/s]
69%|██████▉ | 244/352 [00:32<00:13, 7.78it/s]
70%|██████▉ | 246/352 [00:32<00:14, 7.08it/s]
70%|███████ | 248/352 [00:32<00:13, 7.71it/s]
71%|███████ | 250/352 [00:32<00:14, 7.28it/s]
72%|███████▏ | 252/352 [00:33<00:13, 7.36it/s]
72%|███████▏ | 254/352 [00:33<00:13, 7.48it/s]
72%|███████▏ | 255/352 [00:33<00:12, 7.71it/s]
73%|███████▎ | 256/352 [00:33<00:13, 6.99it/s]
73%|███████▎ | 258/352 [00:33<00:12, 7.41it/s]
74%|███████▎ | 259/352 [00:34<00:13, 6.66it/s]
74%|███████▍ | 261/352 [00:34<00:12, 7.46it/s]
75%|███████▍ | 263/352 [00:34<00:12, 6.90it/s]
75%|███████▌ | 265/352 [00:34<00:11, 7.37it/s]
76%|███████▌ | 266/352 [00:35<00:11, 7.76it/s]
76%|███████▌ | 267/352 [00:35<00:12, 6.72it/s]
76%|███████▋ | 269/352 [00:35<00:11, 7.52it/s]
77%|███████▋ | 271/352 [00:35<00:11, 7.13it/s]
77%|███████▋ | 272/352 [00:36<00:11, 6.69it/s]
78%|███████▊ | 274/352 [00:36<00:10, 7.77it/s]
78%|███████▊ | 275/352 [00:36<00:10, 7.49it/s]
78%|███████▊ | 276/352 [00:36<00:11, 6.70it/s]
79%|███████▊ | 277/352 [00:36<00:11, 6.50it/s]
79%|███████▉ | 279/352 [00:36<00:08, 8.13it/s]
80%|███████▉ | 280/352 [00:37<00:10, 6.98it/s]
80%|███████▉ | 281/352 [00:37<00:12, 5.56it/s]
81%|████████ | 284/352 [00:37<00:09, 7.50it/s]
81%|████████ | 285/352 [00:37<00:09, 7.23it/s]
81%|████████▏ | 286/352 [00:37<00:08, 7.52it/s]
82%|████████▏ | 288/352 [00:38<00:08, 7.46it/s]
82%|████████▏ | 289/352 [00:38<00:08, 7.39it/s]
82%|████████▏ | 290/352 [00:38<00:08, 7.71it/s]
83%|████████▎ | 292/352 [00:38<00:07, 7.55it/s]
83%|████████▎ | 293/352 [00:38<00:08, 7.37it/s]
84%|████████▎ | 294/352 [00:38<00:07, 7.69it/s]
84%|████████▍ | 296/352 [00:39<00:07, 7.53it/s]
84%|████████▍ | 297/352 [00:39<00:08, 6.67it/s]
85%|████████▍ | 299/352 [00:39<00:06, 8.38it/s]
85%|████████▌ | 300/352 [00:39<00:06, 7.68it/s]
86%|████████▌ | 301/352 [00:39<00:07, 6.69it/s]
86%|████████▌ | 303/352 [00:40<00:06, 7.85it/s]
86%|████████▋ | 304/352 [00:40<00:06, 7.42it/s]
87%|████████▋ | 305/352 [00:40<00:07, 6.11it/s]
87%|████████▋ | 307/352 [00:40<00:05, 7.96it/s]
88%|████████▊ | 308/352 [00:40<00:06, 6.73it/s]
88%|████████▊ | 309/352 [00:41<00:06, 6.53it/s]
88%|████████▊ | 311/352 [00:41<00:05, 8.15it/s]
89%|████████▊ | 312/352 [00:41<00:06, 6.43it/s]
89%|████████▉ | 313/352 [00:41<00:05, 6.82it/s]
89%|████████▉ | 315/352 [00:41<00:04, 8.13it/s]
90%|████████▉ | 316/352 [00:42<00:05, 6.43it/s]
90%|█████████ | 318/352 [00:42<00:04, 8.44it/s]
91%|█████████ | 319/352 [00:42<00:04, 7.49it/s]
91%|█████████ | 320/352 [00:42<00:04, 6.50it/s]
91%|█████████ | 321/352 [00:42<00:04, 7.00it/s]
92%|█████████▏| 323/352 [00:42<00:03, 8.05it/s]
92%|█████████▏| 324/352 [00:43<00:04, 6.86it/s]
92%|█████████▏| 325/352 [00:43<00:03, 7.14it/s]
93%|█████████▎| 327/352 [00:43<00:03, 7.92it/s]
93%|█████████▎| 328/352 [00:43<00:03, 6.68it/s]
94%|█████████▍| 330/352 [00:43<00:02, 8.24it/s]
94%|█████████▍| 331/352 [00:44<00:02, 7.56it/s]
94%|█████████▍| 332/352 [00:44<00:03, 5.76it/s]
95%|█████████▌| 335/352 [00:44<00:02, 8.03it/s]
95%|█████████▌| 336/352 [00:44<00:02, 6.12it/s]
96%|█████████▋| 339/352 [00:45<00:01, 8.28it/s]
97%|█████████▋| 340/352 [00:45<00:01, 6.30it/s]
97%|█████████▋| 343/352 [00:45<00:01, 8.43it/s]
98%|█████████▊| 344/352 [00:45<00:01, 6.47it/s]
99%|█████████▊| 347/352 [00:46<00:00, 8.51it/s]
99%|█████████▉| 348/352 [00:46<00:00, 6.38it/s]
100%|█████████▉| 351/352 [00:46<00:00, 8.29it/s]
100%|██████████| 352/352 [00:47<00:00, 6.79it/s]
100%|██████████| 352/352 [00:47<00:00, 7.48it/s]
Compare the two gradients#
# Compute NRMSD between AS and FD (%).
nrmsd = 200*abs(as_grad-fd_grad)/(abs(as_grad)+abs(fd_grad))
nrmsd[fd_grad == 0] = np.nan
# Compute sign.
tiny = np.finfo(float).tiny # Avoid division by zero.
diff_sign = np.sign(as_grad/np.where(abs(fd_grad) < tiny, tiny, fd_grad))
def plot_diff(ax, diff):
"""Helper routine to show cells of big NRMSD or different sign."""
for ix in range(comp_grid.h[0].size):
for iz in range(comp_grid.h[2].size):
if diff_sign[ix, iy, iz] < 0:
ax.add_patch(
Rectangle(
(comp_grid.nodes_x[ix], comp_grid.nodes_z[iz]),
comp_grid.h[0][ix], comp_grid.h[2][iz], fill=False,
color='k', lw=1))
if nrmsd[ix, iy, iz] >= diff:
ax.add_patch(
Rectangle(
(comp_grid.nodes_x[ix], comp_grid.nodes_z[iz]),
comp_grid.h[0][ix], comp_grid.h[2][iz], fill=False,
color='m', linestyle='--', lw=0.5))
def set_axis(axs, i):
"""Helper routine to adjust subplots."""
# Show source and receiver.
axs[i].plot(rec_coords[0], rec_coords[2], 'bv')
axs[i].plot(src_coords[0], src_coords[2], 'r*')
# x-label.
axs[i].set_xlabel('Easting')
# y-label depending on column.
if i == 0:
axs[i].set_ylabel('Depth')
else:
axs[i].set_ylabel('')
axs[i].axes.yaxis.set_ticklabels([])
# Set limits.
axs[i].set_xlim(-3000, 3000)
axs[i].set_ylim(-4000, -1900)
# Plotting options.
vmin, vmax = 1e-2, 1e1
pcolor_opts = {'cmap': 'RdBu_r',
'norm': SymLogNorm(linthresh=vmin, base=10,
vmin=-vmax, vmax=vmax)}
fig, axs = plt.subplots(
figsize=(9, 6), nrows=1, ncols=2, constrained_layout=True)
# Adjoint-State Gradient
f0 = comp_grid.plot_slice(as_grad, normal='Y', ind=iy, ax=axs[0],
pcolor_opts=pcolor_opts)
axs[0].set_title("Adjoint-State Gradient")
set_axis(axs, 0)
plot_diff(axs[0], 1)
# Finite-Difference Gradient
f1 = comp_grid.plot_slice(fd_grad, normal='Y', ind=iy, ax=axs[1],
pcolor_opts=pcolor_opts)
axs[1].set_title("Finite-Difference Gradient")
set_axis(axs, 1)
plot_diff(axs[1], 1)
fig.colorbar(f0[0], ax=axs, orientation='horizontal', fraction=0.05)
Visually the two gradients are almost identical. This is amazing, given that the adjoint-state gradient requires one (1) extra forward computation for the entire cube, whereas the finite-difference gradient requires one extra forward computation for each cell, for this cross-section 352 (!).
There are differences, and they are highlighted:
Cells surrounded by a dashed, magenta line: NRMSD is bigger than 1 %.
Cells surrounded by a black line: The two gradients have different signs.
These differences only happen where the amplitude of the gradient is very small, and it therefore blows the NRMSD numerically up (inherit problem of the relative error when the signal approaches zero).
Total running time of the script: (0 minutes 53.776 seconds)
Estimated memory usage: 10 MB