Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Qualitative Analysis of MT Transfer Functions

The MT transfer functions contain a wealth of information and can be visualized in various ways to get a qualitative analysis of subsurface resistivity structure.

A common method is to plot the phase tensors at different periods along with induction vectors in map view.

This example will go through the SAGE data.

Import modules

Here we are importing the data and the class to contain the data MTData.

# data class
from mtpy import MTCollection 

Create MTData object

Here we are creating the MTData object that will hold all the MT transfer functions from the example survey.

with MTCollection() as mc:
    mc.open_collection(r"c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\sage-geophysics.github.io\notebooks\mt\sage_2026.h5")
    md = mc.to_mt_data()
    md.utm_epsg = 32613
26:06:28T08:43:30 | INFO | line:1035 |mth5.mth5 | close_mth5 | Flushing and closing c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\sage-geophysics.github.io\notebooks\mt\sage_2026.h5

Phase Tensor Maps

The phase tensor is a comprehensive way of plotting information contained within the MT transfer function Caldwell et al., (2004). Again the phase tensor is estimated as the ratio of the imaginary and the real parts of the impedance tensor. The benefits of estimating the phase tensor is to remove the influence of distortion, like static shift. Graphically, the phase tensor can be represented as an ellipse where the long axis aligns with the preferred direction of current flow. Therefore, when the ellipses are circular electrical current does not have a preferred direction and the subsurface is 1D. When the ellipse elongates in a particular direction then the subsurface resistivity structure is at least 2D. The coloring represents the vertical rate of change of resistivity as a function of period, or whether the subsurface is becoming more resistive (blue) or conductive (red). Note that the change is relative and not representivite of the actual value. Therefore, the subsurface could change from 1 Ωm\Omega \cdot m to 2 Ωm\Omega \cdot m and the relative change is to be more resistive and the ellipse will become more blue. Plotting the phase tensor in map view per period provides a qualitative representation of subsurface resistivity structure.

It is important to remember that the depth of penetration is a function of period and volumetric apparent resistivity. So in areas that are more conductive the depth of penetration is shallower and vise-versa. This means that not all stations are representative of the same depths at the same periods.

Plotting the induction vectors provides information about structural boundaries and areas of low resistivity. The black arrows represent the real part of the vertical magnetic transfer function and by convention point towards a good conductor. The cyan arrow represents the imaginary part of the vertical magnetic transfer function and represent dimensionality of the subsurface. In a simple subsurface they should point in the opposite direction as the real arrows, when they rotate off of anti-parallel the resistivity of the subsurface becomes more complicated. When induction vectors are small this could suggest the station is over a strong conductor.

The example below is from transfer functions estimated over a conductive block in a layered half-space, notice the pattern of the phase tensor ellipse shape and induction vector orientation. The ellipeses have a major and minor axis represented by the wedges within the ellipse. The major axis generally aligns with direction of maximum current flow which results in a coloring of the wedge as more conductive. In the example below the major axis is a warm color that aligns in the direction of the conductive block.

The skew values are small in this example, but do have a directionality to them. The skew values represent a quantitative way to estimate 3D effects, basically the skew is measuring the angle to which a linearly oriented flow of current is deflected by a conductive body out of the plane. Notice that at the corners of the conductive body are where the highest value of skew occur, which is about 45 degrees from the axis of the conductive body. This means that currents flowing at an angle to a face of the conductive block will be skewed towards the block. The coloring indicates in which direction electrical currents are skewed with blue being a negative deflection (westward in a geographic coordinate system) and reds being deflected in the positive direction (eastward in a geographic coordinate system). The skew angle is important for identifying 3D bodies and to support which dimension data are modeled. If the skew angle is small (less than 3 Caldwell et al., (2004)) then 2D modeling may be appropriate if the stations are properly placed. But if the absolute value of the skew angle is larger than 3 degrees, then 3D modeling is the appropriate dimensional space to represent the data.

In this example, only the real induction arrows are plotted for clarity. Notice that they point towards the conductive block, but when the stations are above the center of the block the arrows are small because the maximum current flow is directly below. If this were a sedimentary basin within a resistive basement one would observe large induction vectors in the basement rock pointing at the basin.

ptm = md.plot_phase_tensor_map(
    backend="matplotlib",
    ellipse_size=0.008,
    arrow_size=.01,
    arrow_head_width=0.0015,
    arrow_head_length=0.0015,
    arrow_lw= 0.00025,
    plot_tipper="yr", # to plot imaginary arrows use 'yri' or 'yi'
    x_pad=0.01,
    y_pad=0.01,
    plot_period=3,
    phase_limits=(0, 90),
    skew_limits=(-6, 6),
    skew_step=2
)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 1
----> 1 ptm = md.plot_phase_tensor_map(
      2     backend="matplotlib",
      3     ellipse_size=0.008,
      4     arrow_size=.01,

File ~\OneDrive - DOI\Documents\GitHub\mtpy-v2\mtpy\core\mt_data.py:4279, in MTData.plot_phase_tensor_map(self, backend, **kwargs)
   4275     raise ValueError(
   4276         f"Unknown backend {backend!r}. Choose 'bokeh' or 'matplotlib'."
   4277     )
   4278 cls = PlotPhaseTensorMapsBokeh if backend == "bokeh" else PlotPhaseTensorMaps
-> 4279 return cls(mt_data=self, **kwargs)

File ~\OneDrive - DOI\Documents\GitHub\mtpy-v2\mtpy\imaging\plot_phase_tensor_maps.py:111, in PlotPhaseTensorMaps.__init__(self, mt_data, **kwargs)
    109 # --> plot if desired ------------------------
    110 if self.show_plot:
--> 111     self.plot()

File ~\OneDrive - DOI\Documents\GitHub\mtpy-v2\mtpy\imaging\plot_phase_tensor_maps.py:903, in PlotPhaseTensorMaps.plot(self, fig, save_path, show, raster_file, raster_kwargs)
    901     self._add_colorbar_ellipse()
    902 elif self.pt_type == "wedges" and self.plot_pt:
--> 903     self._add_colorbar_wedges()
    904 self._add_tipper_legend()

File ~\OneDrive - DOI\Documents\GitHub\mtpy-v2\mtpy\imaging\plot_phase_tensor_maps.py:707, in PlotPhaseTensorMaps._add_colorbar_wedges(self)
    705 dict_key = f"{key}_cmap"
    706 cmap = getattr(self, dict_key)
--> 707 cmap_input = mtcolors.cm.get_cmap(cmap)
    708 if "seg" in cmap and "ellipse" in key:
    709     norms = colors.BoundaryNorm(self.ellipse_cmap_bounds, cmap_input.N)

AttributeError: module 'matplotlib.cm' has no attribute 'get_cmap'
<Figure size 960x720 with 3 Axes>

Apparent Resistivity and Phase Maps

Apparent resistivity and phase maps provide a nice qualitative look at subsurface resistivity structures and can highlight questionable stations prior to modeling. We are still plotting results from the conductive block. Here we are plotting the xyxy and yxyx components of the impedance tensor as well as the determinant, which is an average of all components. Note the shape of the conductive anomaly in each component.

The xyxy component (electric north and magnetic east) images a conductive block that is stretched east-west, whereas the yxyx component (electric east and magnetic north) images the conductive block stretched north-south, and the determinant as the original symmetrical shape. The elongation is due to charge build up on the edge perpendicular to current flow which reduces the amount of current going through the body making it appear the body is elongated orthogonal to the direction of current flow. This simple example demonstrates the need to understand the physics controlling the electromagnetic waves in the subsurface. Note that modeling will take into account the physics to provide a physically based resistivity model, but nontheless understanding subsurface charges and electrical currents is beneficial to qualitatively interpret your data.

arp = md.plot_resistivity_phase_maps(
    backend="matplotlib",
    plot_period=0.1,
    marker_size=1.2,
    plot_xx=False,
    plot_xy=True,
    plot_yx=True,
    plot_yy=False,
    plot_det=True,
    cmap_limits={
        "res_xx": (-1, 2),
        "res_xy": (1, 2.5),
        "res_yx": (1, 2.5),
        "res_yy": (-1, 2),
        "res_det": (1, 2.5),
        "phase_xx": (-180, 180),
        "phase_xy": (0, 90),
        "phase_yx": (0, 90),
        "phase_yy": (-180, 180),
        "phase_det": (15, 60),
    },
    subplot_wspace=.3,
    subplot_hspace=.1,
    fig_size=(18, 8),

)
<Figure size 2700x1200 with 12 Axes>

Phase Tensor Pseudosection

Here we will plot the phase tensors as a function of period along an east-west profile.

The phase tensor pseudosection provides information as a function of period, a proxy for depth. Each ellipse and induction vector is plotted in map view where up is North and the right is East. The ellipse elongate east-west near the edge of the conductor but inside stay circular. Similarly, the induction vectors point towards the conductor.

end_point_01 = {"latitude": 35.95, "longitude": -106.2}
end_point_02 = {"latitude": 35.95, "longitude": -106.7}

# the syntax is (lon1, lat1, lon2, lat2, distance from profile in meters)
profile_line = md.get_profile(end_point_01["longitude"], end_point_01["latitude"], end_point_02["longitude"], end_point_02["latitude"], 1000)
pts = profile_line.plot_phase_tensor_pseudosection(
    backend="matplotlib",
    y_stretch=2000,
    x_stretch=1,
    plot_tipper="yri",
    ellipse_size=600,
    arrow_size=1500,
    arrow_head_length=100,
    arrow_head_width=100,
)
<Figure size 960x720 with 2 Axes>

Apparent Resistivity and Phase Pseudosection

Here we will plot the profile as a pseudosection as a function of period similar to the phase tensors. As with the map view the conductive body appears to be elongated perpendicular to current flow, and the yxyx mode appears to be more conductive. Note that in the apparent resistivity the body seems to be imaged all the way down to the longest periods. This is caused by the volumetric effect of the apparent resistivity.

aps = profile_line.plot_resistivity_phase_pseudosections(
    backend="matplotlib",
    y_stretch=2500, plot_det=True, fig_size=(16, 10)
)
<Figure size 2400x1500 with 12 Axes>

Geoelectric Strike

An important parameter to estimate is the geoelectric strike direction, which aligns with the preferred direction of current flow. The geoelectric strike direction can change with period and position. Often in the near surface geoelectric strike mirrors geologic structures, but in the mid- and lower-crust strike directions are effected by more subtle physical properties such as rheologic structure and fabric. Understanding geoelectric strike can help determine the dimensional space in which to model your data.

Here we are estimating geoelectrical strike from invariants of the impedance tensor Weaver et al. (2000), azimuth of the phase tensor Caldwell et al. (2004), and the induction vectors. Note that the induction vectors are often orthogonal to the geoelectric strike as they point towards strong conductors, but not always the case as seen below.

Also note, estimation of geoelectrical strike has a 90 degree ambiguity. That is if you try to minimize impedance tensor by tracing around 360 degrees you will get 4 peaks instead of the normal 2. This can be observed in the plots below. One way to try to rectify which angle is the strike direction is compare impedance strike with induction vector strike. At periods longer than 1 second there is a strike in the east-west and the north-south in the impedance, but in the induction vectors the strike is mainly north-south, suggesting the dominate strike direction is east-west. Now this example is bias because of the station coverage, perhaps a better example would be to look at the profile strike angles.

plot_strike = md.plot_strike(plot_type=1, backend="matplotlib",)
26:06:28T08:46:16 | INFO | line:769 |mtpy.imaging.plot_strike | _plot_per_period | Note: North is assumed to be 0 and the strike angle is measuredclockwise positive.
<Figure size 960x720 with 27 Axes>

Profile Geoelectrical Strike

A more demonstrative example of geoelectrical strike changing with period is along the profile. In this case the induction vectors and the geoelectric strike are in the same direction at longer periods, but at shorter periods, where the conductive body is they are orthogonal.

profile_plot_strike = profile_line.plot_strike(plot_type=1, backend="matplotlib",)
26:06:28T08:46:41 | INFO | line:769 |mtpy.imaging.plot_strike | _plot_per_period | Note: North is assumed to be 0 and the strike angle is measuredclockwise positive.
<Figure size 960x720 with 24 Axes>

With all this information just from the transfer functions preliminary interpretations can be made and a determination of the correct dimensional space to model the data can be assumed.

Modeling and Inversion

Modeling the data in the correct dimensional space is of critical importance. These days with compute power and resources 3D is the best option because as we have seen MT data inherently contains directional information about subsurface resistivity structures. 1D and 2D modeling have their place but should be used with caution. The classic example is from Krafla, Iceland where 1D modeling missed the conductive zone of melt hit when drilling into what was thought to be a resistive zone. This was later rectified by 3D modeling Lee et al. (2019). 1D and 2D modeling are much faster, but good things come to those who wait for a 3D inversion to finish.

Here we will demonstrate some simple 1D, 2D, and 3D modeling using Simpeg. Recipes for using simpeg are included in mtpy-v2 except for 3D.

1D Inversion

In this example we will invert in 1D a station that sits directly over the conductive body.

station = md.get_station("SAGE2026/sg2602", as_mt=True)

Set some error floors to the data. Here we are setting the error to be 3% of the impedance tensor elements. Setting an error floor is common in inversion as the data errors maybe too small to fit appropriately. The error floors are what dictates how close the inversion will try to fit the original data. If the data are nice and smooth small error floors may be appropriate, if the data are noisy then some points may have large error to downweight the importance of fitting the bad data point. The point is that when doing an inversion be mindful of the data weighting through the assignment of data errors.

station.compute_model_z_errors(error_value=.03, error_type="percent")

Compute a 1D inversion using simpeg. For more information on parameters associated with the inversion see simpeg documentation.

Here we are computing a smooth 1D inversion of the impedance tensor determinant. The determinant is basically an average of the impedance tensor elements and is a safe mode to invert in 1D.

In the plots below, the first plot is the Tikhonov curve showing the model roughness on the x-axis versus data misfit on the y-axis. In general this will take an L shape, where the elbow is the optimum model that fits the data according to misfit and a relatively smooth model. Here the inversion stops when it hits the target misfit.

The second plot is the 1D resistivity model as a function of depth along with the model fit to the apparent resistivity and phase of the impedance determinant. The model fits the data well and has a similar shape to what would be expected. A resistive near surface, the conductive block, and into a half-space of 10 Ωm\Omega \cdot m.

You can try different norms and other parameters to see how the model changes. Setting the norms to be 0 will produce a blocky model.

inv_1d = station.to_simpeg_1d(
    alpha_s=1e-15,             # mulitplier constant to the smallness term.  The small the more smooth  
    p_z=2,                     # norm in the z-direction 2: L2-norm, 1: L1-norm, 0: blocky model
    p_s=2,                     # for the smallness 2: L2-norm, 1: L1-norm, 0: blocky model
    maxIter=20,                # maximum number of iterations
    z_factor=1.1,              # factor to increase cell thickness with depth  
    n_layers=100,              # number of layers
    **{"y_scale": "linear", "y_limits": (30, 0)} # plotting directives
)
INFO: 
simpeg.InvProblem is setting bfgsH0 to the inverse of the reg.deriv2.
using the default solver Pardiso with the 'is_symmetric=True` option set.

INFO: Directive TargetMisfit: Target data misfit is 128.0

Running inversion with SimPEG v0.25.2
============================ Inexact Gauss Newton ============================
  #     beta     phi_d     phi_m       f      |proj(x-g)-x|  LS    Comment   
-----------------------------------------------------------------------------
   0  9.44e+02  8.51e+03  0.00e+00  8.51e+03                                 
   1  9.44e+02  1.20e+03  3.86e-04  1.20e+03    3.25e+03      0              
   2  4.72e+02  1.95e+02  4.41e-03  1.97e+02    5.79e+02      0   Skip BFGS  
   3  2.36e+02  1.53e+02  1.24e-02  1.55e+02    1.09e+02      0              
   4  1.18e+02  6.91e+01  1.37e-02  7.08e+01    7.92e+01      1              
------------------------- STOP! -------------------------
1 : |fc-fOld| = 8.3229e+01 <= tolF*(1+|f0|) = 8.5107e+02
0 : |xc-x_last| = 7.4611e+00 <= tolX*(1+|x0|) = 4.7281e+00
0 : |proj(x-g)-x|    = 7.9243e+01 <= tolG          = 1.0000e-01
0 : |proj(x-g)-x|    = 7.9243e+01 <= 1e3*eps       = 1.0000e-02
0 : maxIter   =      20    <= iter          =      4
------------------------- DONE! -------------------------
<Figure size 1000x1000 with 1 Axes>
<Figure size 2000x1200 with 3 Axes>
Run a blocky 1D model
inv_1d_block = station.to_simpeg_1d(mode="det", **{"p_s": 0, "p_z": 0, "use_irls": True, "alpha_s": 1e-2})
INFO: 
simpeg.InvProblem is setting bfgsH0 to the inverse of the reg.deriv2.
using the default solver Pardiso with the 'is_symmetric=True` option set.


Running inversion with SimPEG v0.25.2
============================ Inexact Gauss Newton ============================
  #     beta     phi_d     phi_m       f      |proj(x-g)-x|  LS    Comment   
-----------------------------------------------------------------------------
   0  1.94e+00  8.51e+03  0.00e+00  8.51e+03                                 
   1  1.94e+00  1.48e+03  3.06e+02  2.08e+03    4.13e+03      0              
   2  9.72e-01  6.08e+02  6.64e+02  1.25e+03    5.77e+02      0   Skip BFGS  
   3  4.86e-01  3.79e+02  8.51e+02  7.93e+02    2.49e+02      0              
   4  2.43e-01  2.71e+02  1.11e+03  5.41e+02    1.07e+02      0   Skip BFGS  
   5  1.21e-01  1.90e+02  1.55e+03  3.78e+02    6.43e+01      0   Skip BFGS  
   6  6.07e-02  1.24e+02  2.32e+03  2.65e+02    6.60e+01      0              
Reached starting chifact with l2-norm regularization: Start IRLS steps...
irls_threshold 2.8848667698488866
   7  6.07e-02  1.94e+02  2.65e+03  3.55e+02    1.38e+02      0              
   8  4.28e-02  1.57e+02  3.77e+03  3.18e+02    1.34e+02      0              
   9  3.35e-02  1.52e+02  4.03e+03  2.87e+02    7.39e+01      2              
  10  2.67e-02  1.43e+02  4.36e+03  2.60e+02    8.28e+01      2              
  11  2.19e-02  1.38e+02  4.53e+03  2.38e+02    9.36e+01      2              
  12  2.19e-02  1.29e+02  4.38e+03  2.25e+02    1.24e+02      2              
  13  2.19e-02  1.15e+02  3.98e+03  2.02e+02    1.31e+02      1              
  14  3.42e-02  1.25e+02  3.01e+03  2.28e+02    1.77e+02      2              
  15  3.42e-02  1.01e+02  2.16e+03  1.74e+02    2.25e+02      1              
  16  5.60e-02  8.70e+01  1.43e+03  1.67e+02    2.16e+02      1              
  17  9.71e-02  1.14e+02  8.66e+02  1.98e+02    2.35e+02      0   Skip BFGS  
  18  1.52e-01  1.35e+02  6.67e+02  2.36e+02    3.02e+02      0              
  19  1.52e-01  6.06e+01  5.60e+02  1.46e+02    2.74e+02      0              
  20  3.12e-01  6.83e+01  4.24e+02  2.00e+02    8.96e+01      0   Skip BFGS  
  21  6.04e-01  9.21e+01  3.31e+02  2.92e+02    5.32e+01      0              
  22  1.02e+00  1.26e+02  2.60e+02  3.93e+02    6.78e+01      0              
  23  1.02e+00  1.29e+02  2.35e+02  3.69e+02    5.72e+01      0              
  24  1.02e+00  1.23e+02  2.03e+02  3.31e+02    6.05e+01      0              
  25  1.02e+00  1.11e+02  1.76e+02  2.91e+02    5.83e+01      0              
  26  1.62e+00  1.21e+02  1.33e+02  3.36e+02    7.23e+01      0              
  27  1.62e+00  1.20e+02  1.15e+02  3.07e+02    4.03e+01      0              
  28  1.62e+00  1.20e+02  9.48e+01  2.74e+02    5.14e+01      0              
  29  1.62e+00  1.14e+02  8.28e+01  2.48e+02    7.38e+01      0              
  30  2.52e+00  1.20e+02  6.33e+01  2.80e+02    1.66e+02      0              
  31  2.52e+00  1.17e+02  5.52e+01  2.57e+02    9.10e+01      0              
  32  2.52e+00  1.14e+02  4.70e+01  2.33e+02    2.84e+01      0              
  33  3.94e+00  1.18e+02  3.77e+01  2.66e+02    1.05e+02      0              
  34  3.94e+00  1.16e+02  3.29e+01  2.45e+02    5.53e+01      0              
  35  3.94e+00  1.13e+02  2.79e+01  2.24e+02    3.10e+01      0              
  36  6.16e+00  1.18e+02  2.26e+01  2.57e+02    5.93e+01      0              
  37  6.16e+00  1.17e+02  1.99e+01  2.40e+02    1.06e+02      0              
  38  6.16e+00  1.17e+02  1.71e+01  2.22e+02    3.05e+01      0              
  39  6.16e+00  1.16e+02  1.50e+01  2.08e+02    6.70e+01      0              
  40  6.16e+00  1.15e+02  1.34e+01  1.97e+02    2.88e+01      0   Skip BFGS  
------------------------- STOP! -------------------------
1 : |fc-fOld| = 3.1397e+01 <= tolF*(1+|f0|) = 8.5107e+02
1 : |xc-x_last| = 1.6845e-01 <= tolX*(1+|x0|) = 3.3887e+00
0 : |proj(x-g)-x|    = 8.8158e+01 <= tolG          = 1.0000e-01
0 : |proj(x-g)-x|    = 8.8158e+01 <= 1e3*eps       = 1.0000e-02
1 : maxIter   =      40    <= iter          =     40
------------------------- DONE! -------------------------
<Figure size 1000x1000 with 1 Axes>
<Figure size 2000x1200 with 3 Axes>
plot_block = inv_1d_block.plot_response(13)
<Figure size 2000x1200 with 3 Axes>
References
  1. Caldwell, T. G., Bibby, H. M., & Brown, C. (2004). The magnetotelluric phase tensor. Geophysical Journal International, 158(2), 457–469. 10.1111/j.1365-246x.2004.02281.x
  2. Caldwell, T. G., Bibby, H. M., & Brown, C. (2004). The magnetotelluric phase tensor. Geophysical Journal International, 158(2), 457–469. 10.1111/j.1365-246x.2004.02281.x
  3. Weaver, J. T., Agarwal, A. K., & Lilley, F. E. M. (2000). Characterization of the magnetotelluric tensor in terms of its invariants. Geophysical Journal International, 141(2), 321–336. 10.1046/j.1365-246x.2000.00089.x
  4. Lee, B., Unsworth, M., Árnason, K., & Cordell, D. (2019). Imaging the magmatic system beneath the Krafla geothermal field, Iceland: A new 3-D electrical resistivity model from inversion of magnetotelluric data. Geophysical Journal International, 220(1), 541–567. 10.1093/gji/ggz427
  5. Heagy, L. J., Cockett, R., Kang, S., Rosenkjaer, G. K., & Oldenburg, D. W. (2017). A framework for simulation and inversion in electromagnetics. Computers & Geosciences, 107, 1–19. 10.1016/j.cageo.2017.06.018