Skip to article frontmatterSkip to article content
from simpeg import (
    maps, utils, data, optimization, maps, regularization, 
    inverse_problem, directives, inversion, data_misfit
)
import discretize
from discretize.utils import mkvc, refine_tree_xyz
from simpeg.electromagnetics import natural_source as nsem
import numpy as np
from scipy.spatial import cKDTree
import matplotlib.pyplot as plt
from pymatsolver import Pardiso as Solver
from matplotlib.colors import LogNorm
from ipywidgets import interact, widgets
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)
frequencies = np.array([0.1, 2.])
station_spacing = 8000
factor_spacing = 4
rx_x, rx_y = np.meshgrid(np.arange(0, 50000, station_spacing), np.arange(0, 50000, station_spacing))
rx_loc = np.hstack((mkvc(rx_x, 2), mkvc(rx_y, 2), np.zeros((np.prod(rx_x.shape), 1))))
print(rx_loc.shape)
fig, ax = plt.subplots(1,1, figsize=(5,5))
ax.plot(rx_loc[:, 0], rx_loc[:, 1], '.')
ax.set_aspect(1)
(49, 3)
<Figure size 500x500 with 1 Axes>
import discretize.utils as dis_utils
from discretize import TreeMesh
from geoana.em.fdem import skin_depth

def get_octree_mesh(
    rx_loc, frequencies, sigma_background, station_spacing,
    factor_x_pad=2,
    factor_y_pad=2,
    factor_z_pad_down=2,
    factor_z_core=1,
    factor_z_pad_up=1,
    ):
    f_min =  frequencies.min()
    f_max =  frequencies.max()
    lx_pad = skin_depth(f_min, sigma_background) * factor_x_pad
    ly_pad = skin_depth(f_min, sigma_background) * factor_y_pad
    lz_pad_down = skin_depth(f_min, sigma_background) * factor_z_pad_down
    lz_core = skin_depth(f_min, sigma_background) * factor_z_core
    lz_pad_up = skin_depth(f_min, sigma_background) * factor_z_pad_up

    lx_core = rx_loc[:,0].max() - rx_loc[:,0].min()
    ly_core = rx_loc[:,1].max() - rx_loc[:,1].min()
    lx = lx_pad + lx_core + lx_pad
    ly = ly_pad + ly_core + ly_pad
    lz = lz_pad_down + lz_core + lz_pad_up
    dx = station_spacing / factor_spacing
    dy = station_spacing / factor_spacing
    dz = np.round(skin_depth(f_max, sigma_background) /4, decimals=-1)

    # Compute number of base mesh cells required in x and y
    nbcx = 2 ** int(np.ceil(np.log(lx / dx) / np.log(2.0)))
    nbcy = 2 ** int(np.ceil(np.log(ly / dy) / np.log(2.0)))
    nbcz = 2 ** int(np.ceil(np.log(lz / dz) / np.log(2.0)))
    
    mesh = dis_utils.mesh_builder_xyz(
        rx_loc, 
        [dx, dy, dz],
        padding_distance=[[lx_pad, lx_pad], [ly_pad, ly_pad], [lz_pad_down, lz_pad_up]],
        depth_core=lz_core,
        mesh_type='tree'
    )
    X, Y = np.meshgrid(mesh.nodes_x, mesh.nodes_y)
    topo = np.c_[X.flatten(), Y.flatten(), np.zeros(X.size)]
    mesh = refine_tree_xyz(
        mesh, topo, octree_levels=[0, 0, 4], method="surface", finalize=False
    )

    mesh = refine_tree_xyz(
        mesh, rx_loc, octree_levels=[1, 2, 1], method="radial", finalize=True
    )    
    return mesh
mesh = get_octree_mesh(rx_loc, frequencies, 1e-2, station_spacing)
/var/folders/r9/bx9nxyhn04vb2y8frjczdfw80000gn/T/ipykernel_12869/181399982.py:44: DeprecationWarning: The surface option is deprecated as of `0.9.0` please update your code to use the `TreeMesh.refine_surface` functionality. It will be removed in a future version of discretize.
  mesh = refine_tree_xyz(
/var/folders/r9/bx9nxyhn04vb2y8frjczdfw80000gn/T/ipykernel_12869/181399982.py:48: DeprecationWarning: The radial option is deprecated as of `0.9.0` please update your code to use the `TreeMesh.refine_points` functionality. It will be removed in a future version of discretize.
  mesh = refine_tree_xyz(
sigma_background = np.ones(mesh.nC) * 1e-8
ind_active = mesh.cell_centers[:,2] < 0.
sigma_background[ind_active] = 1e-2
fig, ax = plt.subplots(1,1, figsize=(5, 5))
mesh.plot_slice(
    sigma_background, grid=True, normal='X', ax=ax,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-4, vmax=10)},
    grid_opts={"lw":0.1, "color":'k'}, 
    range_x=(rx_loc[:,0].min()-10000, rx_loc[:,0].max()+10000), 
    range_y=(-50000, 50000*0.1)
)
ax.plot(rx_loc[:,0], rx_loc[:,2], 'ro')
ax.set_aspect(1)
<Figure size 500x500 with 1 Axes>
## create target blocks
ind_block_1 = utils.model_builder.get_indices_block([8000, 44000, -5000], [44000, 36000, -10000], mesh.gridCC)[0].tolist()
ind_block_2 = utils.model_builder.get_indices_block([36000, 44000, -5000], [44000, 8000, -10000], mesh.gridCC)[0].tolist()
# print(block_1)
sigma = sigma_background.copy()
sigma[ind_block_1] = 1.
sigma[ind_block_2] = 1.
fig, ax = plt.subplots(1,1, figsize=(5, 5))
mesh.plot_slice(
    sigma, grid=True, normal='X', ax=ax,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-4, vmax=10)},
    grid_opts={"lw":0.1, "color":'k'}, 
    range_x=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    range_y=(-50000, 50000*0.1)
)
ax.plot(rx_loc[:,0], rx_loc[:,2], 'ro')
ax.set_aspect(1)
<Figure size 500x500 with 1 Axes>
fig, ax = plt.subplots(1,1, figsize=(5, 5))
mesh.plot_slice(
    sigma, grid=True, normal='Z', ax=ax,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-3, vmax=1)},
    grid_opts={"lw":0.1, "color":'k'}, 
    range_x=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    range_y=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    slice_loc=-7000.
)
ax.plot(rx_loc[:,0], rx_loc[:,1], 'ro')
ax.set_aspect(1)
<Figure size 500x500 with 1 Axes>
# Generate a Survey
rx_list = []
rx_orientations_impedance = ['xy', 'yx', 'xx', 'yy']
for rx_orientation in rx_orientations_impedance:    
    rx_list.append(     
        nsem.receivers.PointNaturalSource(
            rx_loc, orientation=rx_orientation, component="real"
        )
    )
    rx_list.append(
        nsem.receivers.PointNaturalSource(
            rx_loc, orientation=rx_orientation, component="imag"
        )
    )
rx_orientations_tipper = ['zx', 'zy']
for rx_orientation in rx_orientations_tipper:    
    rx_list.append(     
        nsem.receivers.Point3DTipper(
            rx_loc, orientation=rx_orientation, component="real"
        )
    )
    rx_list.append(
        nsem.receivers.Point3DTipper(
            rx_loc, orientation=rx_orientation, component="imag"
        )
    )
# Source list
src_list = [nsem.sources.PlanewaveXYPrimary(rx_list, frequency=f) for f in frequencies]

# split by chunks of sources

mt_sims = []
mt_mappings = []
surveys = []
for i in range(len(src_list)):
    # Set the mapping
    active_map = maps.InjectActiveCells(
        mesh=mesh, indActive=ind_active, valInactive=np.log(1e-8)
    )
    mapping = maps.ExpMap(mesh) * active_map    
    survey_chunk = nsem.Survey(src_list[i])
    mt_sims.append(
        nsem.simulation.Simulation3DPrimarySecondary(    
            mesh,
            survey=survey_chunk,
            sigmaMap=maps.IdentityMap(),
            sigmaPrimary=sigma_background,
            solver=Solver
        )
    )
    surveys.append(survey_chunk)
    mt_mappings.append(mapping)
    print (f"{i}, {frequencies[i]:.1f} Hz")
0, 0.1 Hz
1, 2.0 Hz
rx_orientations = rx_orientations_impedance + rx_orientations_tipper
from simpeg.meta import MultiprocessingMetaSimulation
parallel_sim = MultiprocessingMetaSimulation(mt_sims, mt_mappings, n_processes=2)
/Users/Mike/miniconda3/envs/em/lib/python3.12/site-packages/simpeg/meta/multiprocessing.py:241: UserWarning: The MetaSimulation class is a work in progress and might change in the future
  super().__init__(simulations, mappings)
/Users/Mike/miniconda3/envs/em/lib/python3.12/site-packages/simpeg/meta/multiprocessing.py:260: UserWarning: The MetaSimulation class is a work in progress and might change in the future
  sim_chunk = MetaSimulation(
len(frequencies)
2
m_true = np.log(sigma[ind_active])
dpred = parallel_sim.dpred(m_true)
# plt.plot(dpred, '.')
# plt.plot(dpred_serial, 'x')
# # True model 
# m_true = np.log(sigma[ind_active])

# # Setup the problem object
# simulation = nsem.simulation.Simulation3DPrimarySecondary(    
#     mesh,
#     survey=survey,
#     sigmaMap=mapping,
#     sigmaPrimary=sigma_background,
#     # solver=Solver

# )
# dpred = simulation.dpred(m_true)
relative_error = 0.05
n_rx = rx_loc.shape[0]
n_freq = len(frequencies)
n_component = 2
n_orientation = len(rx_orientations)

DOBS = dpred.reshape((n_freq, n_orientation, n_component, n_rx))
DCLEAN = DOBS.copy()
FLOOR = np.zeros_like(DOBS)
FLOOR[:,2,0,:] = np.percentile(abs(DOBS[:,2,0,:].flatten()), 90) * 0.1
FLOOR[:,2,1,:] = np.percentile(abs(DOBS[:,2,1,:].flatten()), 90) * 0.1
FLOOR[:,3,0,:] = np.percentile(abs(DOBS[:,3,0,:].flatten()), 90) * 0.1
FLOOR[:,3,1,:] = np.percentile(abs(DOBS[:,3,1,:].flatten()), 90) * 0.1
FLOOR[:,4,0,:] = np.percentile(abs(DOBS[:,4,0,:].flatten()), 90) * 0.1
FLOOR[:,4,1,:] = np.percentile(abs(DOBS[:,4,1,:].flatten()), 90) * 0.1
FLOOR[:,5,0,:] = np.percentile(abs(DOBS[:,5,0,:].flatten()), 90) * 0.1
FLOOR[:,5,1,:] = np.percentile(abs(DOBS[:,5,1,:].flatten()), 90) * 0.1
STD = abs(DOBS) * relative_error + FLOOR
# STD = (FLOOR)
standard_deviation = STD.flatten()
dobs = DOBS.flatten() 
dobs += abs(dobs) * relative_error * np.random.randn(dobs.size)
DOBS = dobs.reshape((n_freq, n_orientation, n_component, n_rx))
# + standard_deviation * np.random.randn(standard_deviation.size)
components = ['Real', 'Imag']
def foo_data(i_freq, i_orientation, i_component):
    fig, ax = plt.subplots(1,1, figsize=(5, 5))
    vmin = np.min([DOBS[i_freq, i_orientation, i_component,:].min(), DCLEAN[i_freq, i_orientation, i_component,:].min()])
    vmax = np.max([DOBS[i_freq, i_orientation, i_component,:].max(), DCLEAN[i_freq, i_orientation, i_component,:].max()])
    out1 = utils.plot2Ddata(rx_loc, DCLEAN[i_freq, i_orientation, i_component,:], clim=(vmin, vmax), ax=ax, contourOpts={'cmap':'turbo'}, ncontour=20)
    ax.plot(rx_loc[:,0], rx_loc[:,1], 'kx')
    if i_orientation<4:
        transfer_type = "Z"
    else:
        transfer_type = "T"
    ax.set_title("Frequency={:.1e}, {:s}{:s}-{:s}".format(frequencies[i_freq], transfer_type, rx_orientations[i_orientation], components[i_component]))
interact(
    foo_data,
    i_freq=widgets.IntSlider(min=0, max=n_freq-1),
    i_orientation=widgets.IntSlider(min=0, max=n_orientation-1), 
    i_component=widgets.IntSlider(min=0, max=n_component-1), 
)
Loading...
# Assign uncertainties
# make data object
data_object = data.Data(parallel_sim.survey, dobs=dobs, standard_deviation=standard_deviation)
m_0 = np.log(sigma_background[ind_active])
dpred0 = parallel_sim.dpred(m_0)
# data_object.standard_deviation = stdnew.flatten()  # sim.survey.std
plt.semilogy(abs(data_object.dobs), '.r')
plt.semilogy(abs(dpred0), '.b')
plt.semilogy((data_object.standard_deviation), 'k.', ms=1)

plt.show()
<Figure size 640x480 with 1 Axes>
1./(mesh.h[0]**2)
array([2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07, 2.5e-07])
# Optimization
opt = optimization.ProjectedGNCG(maxIter=10, maxIterCG=20, upper=np.inf, lower=-np.inf)
opt.remember('xc')
# Data misfit
dmis = data_misfit.L2DataMisfit(data=data_object, simulation=parallel_sim)
# Regularization
dz = mesh.h[2].min()
dx = mesh.h[0].min()
regmap = maps.IdentityMap(nP=int(ind_active.sum()))
reg = regularization.Sparse(mesh, active_cells=ind_active, mapping=regmap)
reg.alpha_s = 1e-10
reg.alpha_x = dz/dx
reg.alpha_y = dz/dx
reg.alpha_z = 1.

# Inverse problem
inv_prob = inverse_problem.BaseInvProblem(dmis, reg, opt)
# Beta schedule
beta = directives.BetaSchedule(coolingRate=1, coolingFactor=2)
# Initial estimate of beta
beta_est = directives.BetaEstimate_ByEig(beta0_ratio=1e0)
# Target misfit stop
target = directives.TargetMisfit()
# Create an inversion object
save_dictionary = directives.SaveOutputDictEveryIteration()
directive_list = [beta, beta_est, target, save_dictionary]
inv = inversion.BaseInversion(inv_prob, directiveList=directive_list)

# Set an initial or starting model
m_0 = np.log(sigma_background[ind_active])

import time
start = time.time()
# Run the inversion
mInv = inv.run(m_0)
print('Inversion took {0} seconds'.format(time.time() - start))

Running inversion with SimPEG v0.22.2
simpeg.InvProblem will set Regularization.reference_model to m0.
simpeg.InvProblem will set Regularization.reference_model to m0.
simpeg.InvProblem will set Regularization.reference_model to m0.
simpeg.InvProblem will set Regularization.reference_model to m0.

                    simpeg.InvProblem is setting bfgsH0 to the inverse of the eval2Deriv.
                    ***Done using the default solver Pardiso and no solver_opts.***
                    
model has any nan: 0
=============================== Projected GNCG ===============================
  #     beta     phi_d     phi_m       f      |proj(x-g)-x|  LS    Comment   
-----------------------------------------------------------------------------
x0 has any nan: 0
   0  3.35e-02  3.77e+04  0.00e+00  3.77e+04    4.39e+03      0              
   1  1.67e-02  1.45e+04  1.07e+05  1.63e+04    1.08e+03      0              
   2  8.37e-03  1.16e+04  2.12e+05  1.34e+04    6.37e+02      0   Skip BFGS  
   3  4.19e-03  9.04e+03  4.17e+05  1.08e+04    4.02e+02      0   Skip BFGS  
   4  2.09e-03  6.55e+03  7.96e+05  8.22e+03    3.89e+02      0   Skip BFGS  
   5  1.05e-03  4.82e+03  1.29e+06  6.17e+03    7.06e+02      0   Skip BFGS  
   6  5.23e-04  4.24e+03  1.49e+06  5.02e+03    9.38e+02      0              
   7  2.62e-04  2.59e+03  2.69e+06  3.29e+03    7.26e+02      0              
   8  1.31e-04  2.26e+03  2.97e+06  2.64e+03    5.72e+02      0              
   9  6.54e-05  1.88e+03  3.75e+06  2.13e+03    6.81e+02      0              
  10  3.27e-05  1.63e+03  4.35e+06  1.77e+03    8.33e+02      0              
------------------------- STOP! -------------------------
1 : |fc-fOld| = 3.5343e+02 <= tolF*(1+|f0|) = 3.7733e+03
1 : |xc-x_last| = 4.4775e+00 <= tolX*(1+|x0|) = 3.6838e+01
0 : |proj(x-g)-x|    = 8.3300e+02 <= tolG          = 1.0000e-01
0 : |proj(x-g)-x|    = 8.3300e+02 <= 1e3*eps       = 1.0000e-02
1 : maxIter   =      10    <= iter          =     10
------------------------- DONE! -------------------------
Inversion took 464.4634048938751 seconds
target.target
1176.0
# data_object.standard_deviation = stdnew.flatten()  # sim.survey.std
plt.semilogy(abs(data_object.dobs), '.r')
plt.semilogy(abs(inv_prob.dpred), 'k.', ms=1)
plt.semilogy(abs(data_object.standard_deviation), 'b.', ms=1)
plt.show()
<Figure size 640x480 with 1 Axes>
iteration = len(save_dictionary.outDict)
m = save_dictionary.outDict[iteration]['m']
sigma_est = mapping*m
pred = save_dictionary.outDict[iteration]['dpred']
DPRED = pred.reshape((n_freq, n_orientation, n_component, n_rx))
DOBS = dpred.reshape((n_freq, n_orientation, n_component, n_rx))
MISFIT = (DPRED-DOBS)/STD
fig, axs = plt.subplots(1,2, figsize=(10, 5))
ax1, ax2 = axs
z_loc = -7000.
y_loc = 10000.
mesh.plot_slice(
    sigma, grid=False, normal='Z', ax=ax1,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-3, vmax=1)},
    range_x=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    range_y=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    slice_loc=z_loc
)
mesh.plot_slice(
    sigma_est, grid=False, normal='Z', ax=ax2,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-3, vmax=1)},
    range_x=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    range_y=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    slice_loc=z_loc
)
ax2.set_yticklabels([])
for ax in axs:
    ax.plot(rx_loc[:,0], rx_loc[:,1], 'kx')
    ax.set_aspect(1)
    ax.set_xlabel("Easting (m)")
    ax.set_ylabel("Northing (m)")
<Figure size 1000x500 with 2 Axes>
fig, axs = plt.subplots(1,2, figsize=(10, 5))
ax1, ax2 = axs
lx_core = rx_loc[:,0].max() - rx_loc[:,0].min()
mesh.plot_slice(
    sigma, grid=False, normal='Y', ax=ax1,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-3, vmax=1)},
    range_x=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    range_y=(-lx_core, lx_core*0.1),
    slice_loc=y_loc
)
mesh.plot_slice(
    sigma_est, grid=False, normal='Y', ax=ax2,
    pcolor_opts={"cmap":"turbo", "norm":LogNorm(vmin=1e-3, vmax=1)},
    range_x=(rx_loc[:,0].min()-5000, rx_loc[:,0].max()+5000), 
    range_y=(-lx_core, lx_core*0.1),
    slice_loc=y_loc
)
ax2.set_yticklabels([])
for ax in axs:
    ax.plot(rx_loc[:,0], rx_loc[:,2], 'kx')
    ax.set_aspect(1)
    ax.set_xlabel("Easting (m)")
    ax.set_ylabel("Elevation (m)")
<Figure size 1000x500 with 2 Axes>
from ipywidgets import interact, widgets
def foo_misfit(i_freq, i_orientation, i_component):
    fig, axs = plt.subplots(1,3, figsize=(15, 5))
    ax1, ax2, ax3 = axs

    vmin = np.min([DOBS[i_freq, i_orientation, i_component,:].min(), DPRED[i_freq, i_orientation, i_component,:].min()])
    vmax = np.max([DOBS[i_freq, i_orientation, i_component,:].max(), DPRED[i_freq, i_orientation, i_component,:].max()])

    out1 = utils.plot2Ddata(rx_loc, DOBS[i_freq, i_orientation, i_component,:], clim=(vmin, vmax), ax=ax1, contourOpts={'cmap':'turbo'}, ncontour=20)
    out2 = utils.plot2Ddata(rx_loc, DPRED[i_freq, i_orientation, i_component,:], clim=(vmin, vmax), ax=ax2, contourOpts={'cmap':'turbo'}, ncontour=20)
    out3 = utils.plot2Ddata(rx_loc, MISFIT[i_freq, i_orientation, i_component,:], clim=(-5, 5), ax=ax3, contourOpts={'cmap':'turbo'}, ncontour=20)

    ax.plot(rx_loc[:,0], rx_loc[:,1], 'kx')
interact(
    foo_misfit,
    i_freq=widgets.IntSlider(min=0, max=n_freq-1),
    i_orientation=widgets.IntSlider(min=0, max=n_orientation-1), 
    i_component=widgets.IntSlider(min=0, max=n_component-1), 
)
Loading...
# models = {}
# models["estimated_sigma"] = sigma_est
# models['true_sigma'] = sigma
# mesh.write_vtk("mt_synthetic",models=models)