Skip to article frontmatterSkip to article content

Learning goals

  • Understand the concept of Tikhonov curve - impact of the trade-off parameter beta
  • Understand the concept of overfitting and underfitting
  • Understand of the concept of depth-of-investigation (DOI)
import numpy as np
from simpeg.electromagnetics import natural_source as nsem
from simpeg import maps
import matplotlib.pyplot as plt
import matplotlib
import matplotlib.gridspec as gridspec
from simpeg.utils import plot_1d_layer_model
from discretize import TensorMesh
from simpeg import (
    maps,
    data,
    data_misfit,
    regularization,
    optimization,
    inverse_problem,
    inversion,
    directives,
    utils,
)
matplotlib.rcParams['font.size'] = 14

Generate synthetic MT data

def run_forward(layer_thicknesses, rho_layers, frequencies, relative_error_rho=0.05, floor_phase=2):
    mesh = TensorMesh([(np.r_[layer_thicknesses, layer_thicknesses[-1]])], "N")

    wire_map = maps.Wires(("sigma", mesh.nC), ("t", mesh.nC - 1))
    sigma_map = maps.ExpMap(nP=mesh.nC) * wire_map.sigma
    layer_map = maps.ExpMap(nP=mesh.nC - 1) * wire_map.t


    sigma_map = maps.ExpMap(nP=len(rho_layers))

    receivers_list = [
        nsem.receivers.PointNaturalSource(component="app_res"),
        nsem.receivers.PointNaturalSource(component="phase"),
    ]

    source_list = []
    for freq in frequencies:
        source_list.append(nsem.sources.Planewave(receivers_list, freq))

    survey = nsem.survey.Survey(source_list)

    simulation = nsem.simulation_1d.Simulation1DRecursive(
        survey=survey, 
        sigmaMap=sigma_map,
        thicknesses=layer_thicknesses,
    )

    true_model = np.r_[np.log(1./rho_layers)]

    dpred = simulation.dpred(true_model)
    rho_app = dpred.reshape((len(frequencies), 2))[:,0]
    phase = dpred.reshape((len(frequencies), 2))[:,1]
    std = np.c_[abs(rho_app)*relative_error_rho, np.ones(len(phase))*floor_phase].flatten()
    noise = np.c_[np.random.randn(rho_app.size)*relative_error_rho*abs(rho_app), np.random.randn(rho_app.size)*floor_phase].flatten()
    dobs = dpred + noise
    return dobs
layer_tops = np.r_[0., -600., -1991., -5786., -9786.][::-1]  # in m
layer_thicknesses = np.diff(layer_tops)
rho_layers = np.r_[250., 25, 100., 10., 25.][::-1]
# frequencies = np.logspace(0, 3, 31)
frequencies = np.logspace(-3, 3, 31)
relative_error_rho = 0.05
floor_phase = 2.
dobs = run_forward(layer_thicknesses, rho_layers, frequencies, relative_error_rho=relative_error_rho, floor_phase=floor_phase)
fig = plt.figure(figsize=(16, 5))
gs = gridspec.GridSpec(1, 5, figure=fig)

ax0 = fig.add_subplot(gs[0, 0])
plot_1d_layer_model(layer_thicknesses[::-1], rho_layers[::-1], ax=ax0, color="k", **{'label':'True'})
ax0.set_xlabel(r"Resistivity [$\Omega$m]")
ax0.set_xlim(1, 1000)
# ax0.set_yscale('log')
ax = fig.add_subplot(gs[0, 2:])
ax.loglog(frequencies, dobs.reshape((len(frequencies), 2))[:,0], color='C0', label=r'$\rho_{a}$')
ax.loglog(frequencies[0], dobs.reshape((len(frequencies), 2))[0,0], color='C1', label=r'$\Phi$')
ax_1 = ax.twinx()
ax_1.plot(frequencies, dobs.reshape((len(frequencies), 2))[:,1], color='C1')
ax.set_xlabel("Frequency (Hz)")    
ax.set_ylim(10, 1000)
ax_1.set_ylim(0, 90)    
ax.grid(True, which='both', alpha=0.5)
ax.set_ylabel(r"Apparent resistivity ($\rho_{a}$) [$\Omega$m]")
ax_1.set_ylabel(r"Phase ($\Phi$) [$\degree$]")
ax.legend(bbox_to_anchor=(-0.1, 1))
ax.set_xlim(frequencies.max(), frequencies.min())
plt.show()
<Figure size 1600x500 with 3 Axes>

Run MT inversion

dz = 10
n_layer = 35
z_factor = 1.18
layer_thicknesses_inv = dz*z_factor**np.arange(n_layer-1)[::-1]
layer_thicknesses_inv.sum() / 1e3
15.386878068914159
def run_smooth_inversion(
    dobs,
    standard_deviation,
    rho_0,
    rho_ref,
    maxIter=10,
    maxIterCG=30,
    maxIterLS=30,
    alpha_s=1e-10,
    alpha_z=1,
    beta0_ratio=1,
    coolingFactor=2,
    coolingRate=1,
    chi_factor=1,
    p_s=0, 
    p_z=0,
    
):
    mesh_inv = TensorMesh([(np.r_[layer_thicknesses_inv, layer_thicknesses_inv[-1]])], "N")
    receivers_list = [
        nsem.receivers.PointNaturalSource(component="app_res"),
        nsem.receivers.PointNaturalSource(component="phase"),
    ]

    source_list = []
    for freq in frequencies:
        source_list.append(nsem.sources.Planewave(receivers_list, freq))

    survey = nsem.survey.Survey(source_list)

    sigma_map = maps.ExpMap(nP=len(layer_thicknesses_inv)+1)
    simulation = nsem.simulation_1d.Simulation1DRecursive(
        survey=survey, 
        sigmaMap=sigma_map,
        thicknesses=layer_thicknesses_inv,
    )
    # Define the data 
    data_object = data.Data(survey, dobs=dobs, standard_deviation=standard_deviation)    
    
    # Initial model
    m0 = np.ones(len(layer_thicknesses_inv)+1) * np.log(1./rho_0)
    
    # Reference model
    mref = np.ones(len(layer_thicknesses_inv)+1) * np.log(1./rho_ref)    
    
    dmis = data_misfit.L2DataMisfit(simulation=simulation, data=data_object)

    # Define the regularization (model objective function)
    reg = regularization.Sparse(
        mesh_inv, alpha_s=alpha_s, alpha_x=alpha_z, 
        reference_model=mref,
        reference_model_in_smooth=False,
        mapping=maps.IdentityMap(mesh=mesh_inv),
        # norms=[p_s, p_z]
    )
    # reg.gradient_type = 'components'
    # Define how the optimization problem is solved. Here we will use an inexact
    # Gauss-Newton approach that employs the conjugate gradient solver.
    opt = optimization.InexactGaussNewton(maxIter=maxIter, maxIterCG=maxIterCG, maxIterLS=maxIterLS)

    # Define the inverse problem
    inv_prob = inverse_problem.BaseInvProblem(dmis, reg, opt)

    #######################################################################
    # Define Inversion Directives
    # ---------------------------
    #
    # Here we define any directives that are carried out during the inversion. This
    # includes the cooling schedule for the trade-off parameter (beta), stopping
    # criteria for the inversion and saving inversion results at each iteration.
    #

    # Defining a starting value for the trade-off parameter (beta) between the data
    # misfit and the regularization.
    starting_beta = directives.BetaEstimate_ByEig(beta0_ratio=beta0_ratio)

    # Set the rate of reduction in trade-off parameter (beta) each time the
    # the inverse problem is solved. And set the number of Gauss-Newton iterations
    # for each trade-off paramter value.
    beta_schedule = directives.BetaSchedule(coolingFactor=coolingFactor, coolingRate=coolingRate)
    save_dictionary = directives.SaveOutputDictEveryIteration()
    save_dictionary.outDict = {}
    # Setting a stopping criteria for the inversion.
    target_misfit = directives.TargetMisfit(chifact=chi_factor)
    precond = directives.UpdatePreconditioner()

    # The directives are defined as a list.
    directives_list = [
        precond,
        starting_beta,
        beta_schedule,
        target_misfit,
        save_dictionary
    ]

    #####################################################################
    # Running the Inversion
    # ---------------------
    #
    # To define the inversion object, we need to define the inversion problem and
    # the set of directives. We can then run the inversion.
    #

    # Here we combine the inverse problem and the set of directives
    inv = inversion.BaseInversion(inv_prob, directives_list)

    # Run the inversion
    recovered_model = inv.run(m0)    
    
    return recovered_model, save_dictionary.outDict
rho_app = dobs.reshape((len(frequencies), 2))[:,0]
phase = dobs.reshape((len(frequencies), 2))[:,1]
standard_deviation = np.c_[abs(rho_app)*relative_error_rho, np.ones(len(phase))*floor_phase].flatten()

rho_0 = 20
rho_ref = 100.

output_dict ={}
recovered_model, output_dict = run_smooth_inversion(
    dobs,
    standard_deviation,
    rho_0,
    rho_ref,
    maxIter=30,
    maxIterCG=30,
    maxIterLS=50, 
    alpha_s=1e-5,
    alpha_z=1,
    beta0_ratio=1e2,
    coolingFactor=2,
    coolingRate=1,
    chi_factor=1e-2
)

Running inversion with SimPEG v0.22.2

                        simpeg.InvProblem is setting bfgsH0 to the inverse of the eval2Deriv.
                        ***Done using same Solver, and solver_opts as the Simulation1DRecursive problem***
                        
model has any nan: 0
============================ Inexact Gauss Newton ============================
  #     beta     phi_d     phi_m       f      |proj(x-g)-x|  LS    Comment   
-----------------------------------------------------------------------------
x0 has any nan: 0
   0  1.29e+05  5.72e+03  3.99e-01  5.70e+04    1.85e+04      0              
   1  6.43e+04  2.89e+04  8.01e-03  2.94e+04    3.05e+04      0              
   2  3.21e+04  6.98e+03  7.73e-02  9.47e+03    3.51e+03      0              
   3  1.61e+04  1.92e+03  1.38e-01  4.13e+03    7.28e+02      0   Skip BFGS  
   4  8.04e+03  1.20e+03  1.68e-01  2.54e+03    6.06e+02      0   Skip BFGS  
   5  4.02e+03  6.05e+02  2.14e-01  1.47e+03    3.31e+02      0              
   6  2.01e+03  2.69e+02  2.59e-01  7.89e+02    1.64e+02      0              
   7  1.00e+03  1.38e+02  2.85e-01  4.25e+02    4.53e+01      0              
   8  5.02e+02  1.01e+02  2.97e-01  2.50e+02    1.06e+02      0              
   9  2.51e+02  9.09e+01  2.92e-01  1.64e+02    1.37e+02      1              
  10  1.26e+02  8.90e+01  2.90e-01  1.25e+02    2.30e+02      0   Skip BFGS  
  11  6.28e+01  7.10e+01  2.83e-01  8.88e+01    3.21e+01      0   Skip BFGS  
  12  3.14e+01  6.35e+01  3.51e-01  7.46e+01    1.13e+02      0   Skip BFGS  
  13  1.57e+01  5.85e+01  3.55e-01  6.40e+01    1.61e+01      0              
  14  7.85e+00  5.78e+01  3.60e-01  6.07e+01    3.85e+01      1   Skip BFGS  
  15  3.92e+00  5.73e+01  3.55e-01  5.87e+01    3.57e+00      0              
  16  1.96e+00  5.72e+01  3.64e-01  5.80e+01    3.92e+01      2   Skip BFGS  
  17  9.81e-01  5.67e+01  3.58e-01  5.70e+01    2.98e+00      0              
  18  4.91e-01  5.65e+01  3.70e-01  5.67e+01    1.47e+01      4   Skip BFGS  
  19  2.45e-01  5.64e+01  3.96e-01  5.65e+01    2.91e+01      3              
  20  1.23e-01  5.61e+01  3.99e-01  5.61e+01    2.32e+00      0              
  21  6.13e-02  5.59e+01  4.15e-01  5.60e+01    8.18e+00      6   Skip BFGS  
  22  3.07e-02  5.59e+01  4.46e-01  5.59e+01    1.30e+01      6              
  23  1.53e-02  5.58e+01  5.08e-01  5.58e+01    1.69e+01      5   Skip BFGS  
  24  7.66e-03  5.58e+01  6.54e-01  5.58e+01    2.09e+01      5   Skip BFGS  
  25  3.83e-03  5.56e+01  8.52e-01  5.56e+01    2.33e+00      0   Skip BFGS  
  26  1.92e-03  5.56e+01  8.99e-01  5.56e+01    1.06e+01      7   Skip BFGS  
  27  9.58e-04  5.56e+01  9.29e-01  5.56e+01    1.35e+01      7   Skip BFGS  
  28  4.79e-04  5.55e+01  1.24e+00  5.55e+01    1.48e+01      6   Skip BFGS  
  29  2.40e-04  5.55e+01  1.27e+00  5.55e+01    1.68e+01      6   Skip BFGS  
  30  1.20e-04  5.55e+01  3.64e+00  5.55e+01    1.81e+01      4   Skip BFGS  
------------------------- STOP! -------------------------
1 : |fc-fOld| = 5.7294e-03 <= tolF*(1+|f0|) = 5.7003e+03
0 : |xc-x_last| = 6.5772e+00 <= tolX*(1+|x0|) = 1.8723e+00
0 : |proj(x-g)-x|    = 1.8125e+01 <= tolG          = 1.0000e-01
0 : |proj(x-g)-x|    = 1.8125e+01 <= 1e3*eps       = 1.0000e-02
1 : maxIter   =      30    <= iter          =     30
------------------------- DONE! -------------------------
target_misfit = dobs.size
iterations = list(output_dict.keys())
n_iteration = len(iterations)
phi_ds = np.zeros(n_iteration)
phi_ms = np.zeros(n_iteration)
betas = np.zeros(n_iteration)
for ii, iteration in enumerate(iterations):
    phi_ds[ii] = output_dict[iteration]['phi_d']
    phi_ms[ii] = output_dict[iteration]['phi_m']
    betas[ii] = output_dict[iteration]['beta']
matplotlib.rcParams['font.size'] = 14
def tikhonov_curve(iteration, scale='log'):
    fig, ax = plt.subplots(1,1, figsize=(5,5))
    ax.plot(phi_ms, phi_ds)
    ax.plot(phi_ms[iteration-1], phi_ds[iteration-1], 'ro')
    ax.set_xlabel(r"$\phi_m$")
    ax.set_ylabel(r"$\phi_d$")
    if scale == 'log':
        ax.set_xscale('log')
        ax.set_yscale('log')
    xlim = ax.get_xlim()
    ax.plot(xlim, np.ones(2) * target_misfit, '--')
    ax.set_title("Iteration={:d}, Beta = {:.1e}".format(iteration, betas[iteration-1]))
    ax.set_xlim(xlim)
    plt.show()
from ipywidgets import interact, widgets
Q_iter = interact(
    tikhonov_curve, 
    iteration=widgets.IntSlider(min=1, max=int(n_iteration), value=n_iteration),
    scale=widgets.RadioButtons(options=['linear', 'log'])
)
Loading...
iteration = Q_iter.widget.kwargs['iteration']
dpred = output_dict[iteration]['dpred']
m = output_dict[iteration]['m']
fig = plt.figure(figsize=(16, 5))
gs = gridspec.GridSpec(1, 5, figure=fig)

ax0 = fig.add_subplot(gs[0, 0])
plot_1d_layer_model(layer_thicknesses[::-1], rho_layers[::-1], ax=ax0, color="k", **{'label':r'$m_{True}$'})
plot_1d_layer_model(
    layer_thicknesses_inv[::-1],
    (1./(np.exp(m)))[::-1],
    ax=ax0,
    color="k",**{'label':r'$m_{Inv}$', 'linestyle':'--'}
)
ax0.legend()
ax0.set_xlabel(r"Resistivity [$\Omega$m]")
ax0.set_xlim(1, 1e4)
ax0.set_title('Model from iteration ' + str(iteration), fontsize=14)

ax = fig.add_subplot(gs[0, 2:])
ax.loglog(1./frequencies, dobs.reshape((len(frequencies), 2))[:,0], 'x', color='C0', label=r'$d_{obs}$ ($\rho_{a}$)')
ax.loglog(1./frequencies, dpred.reshape((len(frequencies), 2))[:,0], color='C0', label=r'$d_{pred}$ ($\rho_{a}$)')

ax_1 = ax.twinx()
ax_1.plot(1./frequencies, dobs.reshape((len(frequencies), 2))[:,1], 'x', color='C1', label=r'$d_{obs}$ ($\Phi$)')
ax_1.plot(1./frequencies, dpred.reshape((len(frequencies), 2))[:,1], color='C1', label=r'$d_{pred}$ ($\Phi$)')
ax.set_xlabel("Period [s]")
ax.grid(True, which='both', alpha=0.5)
ax.set_ylabel(r"Apparent resistivity [$\Omega$m]")
ax_1.set_ylabel(r"Phase [$\degree$]")
# ax.legend(bbox_to_anchor=(1.1,1))
ax.legend(loc=2)
ax_1.legend(loc=1)
ax.set_ylim(1, 10000)
ax_1.set_ylim(0, 90)    
ax0.set_xlim(1, 10000)
plt.show()
<Figure size 1600x500 with 3 Axes>

Estimate depth-of-investigation

DOI index=m1m2mref1mref2\text{DOI index} = \frac{m_1-m_2}{m_{ref 1}-m_{ref 2}}
use_doi_index = False
if use_doi_index:
    rho_ref_1 = 50
    m1, _ = run_smooth_inversion(
        dobs,
        standard_deviation,
        rho_0,
        rho_ref_1,
        maxIter=10,
        maxIterCG=30,
        alpha_s=0.1,
        alpha_z=1,
        beta0_ratio=1e1,
        coolingFactor=2,
        coolingRate=1,
        chi_factor=1
    )

    rho_ref_2 = 100
    m2, _ = run_smooth_inversion(
        dobs,
        standard_deviation,
        rho_0,
        rho_ref_2,
        maxIter=10,
        maxIterCG=30,
        alpha_s=0.1,
        alpha_z=1,
        beta0_ratio=1e1,
        coolingFactor=2,
        coolingRate=1,
        chi_factor=1
    )
    
    def calculate_doi_index(m1, m2, mref1, mref2):
        doi_index = abs((m1-m2) / (mref1-mref2))
        doi_index /= doi_index.max()
        return doi_index

    mref1 = np.exp(1./rho_ref_1)
    mref2 = np.exp(1./rho_ref_2)
    doi_index = calculate_doi_index(m1, m2, mref1, mref2)

    fig, ax = plt.subplots(1,1, figsize=(3, 5))
    plot_1d_layer_model(
        layer_thicknesses_inv[::-1],
        (1./(np.exp(m)))[::-1],
        ax=ax,
        color="k",**{'label':'Pred', 'linestyle':'-'}
    )
    plot_1d_layer_model(
        layer_thicknesses_inv[::-1],
        (1./(np.exp(m1)))[::-1],
        ax=ax,
        color="C1",**{'label':'Pred', 'linestyle':'--'}
    )

    plot_1d_layer_model(
        layer_thicknesses_inv[::-1],
        (1./(np.exp(m2)))[::-1],
        ax=ax,
        color="C2",**{'label':'Pred', 'linestyle':'--'}
    )
    ax.set_xlim(1, 10000)
    ax_1 = ax.twiny()

    plot_1d_layer_model(
        layer_thicknesses_inv[::-1],
        doi_index[::-1],
        ax=ax_1,
        scale='linear',**{'label':'Pred', 'linestyle':'--'}
    )    
    ax.set_xlabel(r"Resistivity ($\Omega$m)")
    ax_1.set_xlabel("DOI index")