Skip to article frontmatterSkip to article content

Modeling

Looking at the MT response is useful for understanding your data and generally what the data represents. However, to get a full understanding of the subsurface we need to convert the measured MT response to an Earth response through forward modeling and inversion. 1D and 2D modeling can be done using Simpeg as presented by Seogi. Here we will look at creating input files for ModEM. We will create a model for just the Yellowstone area with the option of adding topography.

from pathlib import Path
import numpy as np
from mtpy import MTCollection
from mtpy.modeling import StructuredGrid3D

from mtpy.modeling.modem import (
 Covariance, ControlFwd, ControlInv
)

%matplotlib inline

Open MTCollection

In the previous example we created a MTH5 file from existing Yellowstone data. Let’s load in the file and only pick out the area we’d like to invert, by applying a bounding box.

%%time
with MTCollection() as mc:
    mc.open_collection(Path().cwd().parent.parent.joinpath("data", "transfer_functions", "yellowstone_mt_collection.h5"))
    mc.apply_bbox(*[-111.4, -109.85, 44, 45.2])
    mt_data = mc.to_mt_data()
24:10:24T12:37:39 | INFO | line:777 |mth5.mth5 | close_mth5 | Flushing and closing c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\transfer_functions\yellowstone_mt_collection.h5
CPU times: total: 17.5 s
Wall time: 19.5 s

Plot Stations

Make sure we got everything we want.

plot_stations = mt_data.plot_stations(fig_num=1)
24:10:24T12:38:40 | WARNING | line:163 |mtpy.imaging.plot_stations | plot | Could not add base map because HTTPSConnectionPool(host='basemap.nationalmap.gov', port=443): Max retries exceeded with url: /arcgis/rest/services/USGSTopo/MapServer/tile/9/183/97 (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1129)')))
<Figure size 960x720 with 1 Axes>

Check Stations

It looks like there are some stations close together, so we should decide which of them we should use for modeling.

  • YNP44S, YNPXLS
  • YNP33B, WYYS3, YNP34S

Compare YNP44S and YNPXLS

We can plot these together and make an executive decision to keep one over the other. From an eyeball test it appears YNPXLS is smoother in phase. So lets keep YNPXLS and remove YNP44S

plot_compare = mt_data.plot_mt_response(station_key=["YSBB.YNP44S", "YSBB.YNPXLS"], plot_style="compare", fig_num=2)
<Figure size 640x480 with 0 Axes>
<Figure size 960x720 with 4 Axes>

Compare YNP33B, WYYS3, YNP34S

It apears that YNP33B has a large static shift, and the phase is a little more noisy than YNP34. Looks like WYYS3 is similar to YNP34S, so for this excersize we can remove WYYS3 as well because we will only use periods down to 1000 seconds.

compare_plot_02 = mt_data.plot_mt_response(station_key=["YSBB.YNP33B", "YSBB.YNP34S", "Transportable_Array.WYYS3"], plot_style="compare", fig_num=4)
<Figure size 640x480 with 0 Axes>
<Figure size 960x720 with 4 Axes>

Remove stations

Now that we have picked out some stations to remove let’s get rid of them.

mt_data.remove_station("YNP44S", survey_id="YSBB")
mt_data.remove_station("YNP33B", survey_id="YSBB")
mt_data.remove_station("YWYYS3", survey_id="Transportabl_Array")

Replot Stations

plot_stations_02 = mt_data.plot_stations(fig_num=5)
24:10:24T12:43:07 | WARNING | line:163 |mtpy.imaging.plot_stations | plot | Could not add base map because HTTPSConnectionPool(host='basemap.nationalmap.gov', port=443): Max retries exceeded with url: /arcgis/rest/services/USGSTopo/MapServer/tile/9/183/97 (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate in certificate chain (_ssl.c:1129)')))
<Figure size 960x720 with 1 Axes>

Model Periods

Now that we have a decent station coverage we need to choose the periods to invert. For 3D this is commonly decided by the computer architecture because the parallelization is often by period. Here our compute power is limited to your desktop so lets use 10 periods over the period range (0.01, 1000). We will use a spline interpolation for each component and both real and imaginary components.

interp_periods = np.logspace(-2, 3, 10)
interp_mt_data = mt_data.interpolate(interp_periods, inplace=False)

Just check to make sure interpolation is accurate, lets plot some stations together.

mt_01 = mt_data.get_subset(["YSBB.YNP34S"])
mt_02 = interp_mt_data.get_station(station_key="YSBB.YNP34S")
mt_02.survey = "interp"
mt_01.add_station(mt_02)

pmr = mt_01.plot_mt_response(station_key=list(mt_01.keys()), plot_style="compare", fig_num=6)
<Figure size 640x480 with 0 Axes>
<Figure size 960x720 with 4 Axes>

Model Errors

Now that we have a decent station coverage we need to assigne model errors. Typically we use some combination of the diagonal components and set an error floor of between 3 - 5%. Below are the supported error types.

Error TypeDfinition
egberterror_value * sqrt(Zxy * Zyx)
geometric_meanerror_value * sqrt(Zxy * Zyx)
arithmetic_meanerror_value * (Zxy + Zyx) / 2
mean_oderror_value * (Zxy + Zyx) / 2
off_diagonalszxx_err == zxy_err, zyx_err == zyy_err
medianerror_value * median(z)
eigenerror_value * mean(eigen(z))
percenterror_value * z
absoluteerror_value

For this excersize we will use the defaults which are:

  • Impedance
    • error_type = 'geometric_mean'
    • error_value = 0.05
    • floor = True
  • Tipper
    • error_type = 'absolute'
    • error_value = 0.02
    • floor = True
interp_mt_data.compute_model_errors()

Model Locations

ModEM Uses a relative grid for calculating the fields and is agnostic to geospatial postion. Therefore, we need to place our stations on a relative grid where the center point is (0, 0). We can then use these locations to make a model mesh. Somewhat counterintuitvely we need to provide a UTM coordinate system to estimate the easting and northing. We will use WGS84 UTM Zone 12 --> EPSG = 32612

interp_mt_data.model_epsg = 32612
interp_mt_data.compute_relative_locations()
interp_mt_data.station_locations
Loading...

Write Data File

The data is ready to go, lets write it to a file.

interp_mt_data.to_modem(
    Path().cwd().parent.parent.joinpath("data", "modeling", "ynp_z05_t02.dat"),
    topography=False
)
24:10:24T12:50:40 | WARNING | line:553 |mtpy.modeling.modem.data | _check_for_too_small_errors | Found errors with values less than 0.02 in z_xy 1 times. Setting error as z_xy x 0.05.
24:10:24T12:50:40 | WARNING | line:553 |mtpy.modeling.modem.data | _check_for_too_small_errors | Found errors with values less than 0.02 in z_yx 3 times. Setting error as z_yx x 0.05.
24:10:24T12:50:40 | INFO | line:680 |mtpy.modeling.modem.data | write_data_file | Wrote ModEM data file to c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\ynp_z05_t02.dat
ModEM Data Object: Number of impedance stations: 46 Number of tipper stations: 46 Number of phase tensor stations: 46 Number of periods: 10 Period range (s): Min: 0.01 Max: 1000 Rotation angle: 0 Data center: Latitude: 44.5957 deg Northing: 4938112.1287 m Longitude: -110.6258 deg Easting: 529702.9612 m Datum epsg: 4326 UTM epsg: 32612 Elevation: 0.0 m Impedance data: True Tipper data: True Inversion Mode: Full_Impedance, Full_Vertical_Components

Model Grid

ModEM uses the finite difference which computes the fields on the faces of an unstructured rectangular grid.

Horizontal Cells

In the horizontal direction cell sizes are often uniform in width within the area of interest. Commonly you would like at least 1 free cell between stations and usually more to allow the inversion to model smoothly varying resistivity structures. So if your station spaceing is 2 km, your model cell sizes could be between 200 - 1000 meters.

Vertical Cells

In the vertical direction the height should increase with depth to account for MT being a diffusive method. The rate of increase would ideally be a geometric factor of about 1.2, but computationally this can be expensive, therefore it is a good idea to change that parameter to grid that can provide resolution down to a desired depth and be relatively efficient. The first layer should be about 1/5th the skin depth of your shortest period. If you shortest period is 0.01 seconds and an estimate of the subsurface apparent resistivity is 100, then your skin depth is 500 * sqrt(0.01 * 100) = 500 m, and your top layer could be 100 m.

Padding Cells

Because MT is a volumetric measurement and measurement periods are long, the model needs to be sufficiently large enough to minimize edge effects. The extents of the model should be about 2-5 times the maximum skin depth away from the station area. If your longest period is 1000 seconds, the approximate skin depth for an apparent resistivity of 100 would be 500 * sqrt(1000 * 100) ~ 150 km. Therefore your model should extend at leat 150 km from the edge of the station area in all directions. So if you station area is 25 km v 25 km your model extents should be 325 km x 325 km. Now this isn’t always practical and the skin depths are just estimates, so playing around with padding cells can be a useful excersize. These padding cells can be large and you may only need a few.

Making the Mesh

To make the mesh we need the station locations and the center point. We will set some parameters that are coarse so that this can run on a desktop.

model_mesh = StructuredGrid3D(interp_mt_data.station_locations, interp_mt_data.center_point)
model_mesh.station_locations.model_elevation = 0
model_mesh.cell_size_east = 3500
model_mesh.cell_size_north = 3500
model_mesh.z1_layer = 50
model_mesh.z_target_depth = 30000
model_mesh.n_layers = 40
model_mesh.make_mesh()
model_mesh.plot_mesh(fig_num=8, plot_station_id=False)
24:10:24T12:57:22 | WARNING | line:1747 |mtpy.modeling.structured_mesh_3d | _validate_extent | Provided or default ew_ext not sufficient to fit stations + padding, updating extent
24:10:24T12:57:22 | WARNING | line:1753 |mtpy.modeling.structured_mesh_3d | _validate_extent | Provided or default ns_ext not sufficient to fit stations + padding, updating extent
Structured3DMesh Model Object:
--------------------
	Number of stations = 46
	Mesh Parameter: 
		cell_size_east:    3500
		cell_size_north:   3500
		pad_east:          7
		pad_north:         7
		pad_num:           3
		z1_layer:          50
		z_target_depth:    30000
		n_layers:          40
		n_air_layers:      0
		res_initial_value: 100.0
	Dimensions: 
		e-w: 56
		n-s: 53
		z:   41 (without 7 air layers)
	Extensions: 
		e-w:  291500.0 (m)
		n-s:  265000.0 (m)
		0-z:  52380.0 (m)
--------------------
24:10:24T12:57:22 | WARNING | line:49 |mtpy.modeling.plots.plot_mesh | _plot_topography | Cannot find topography information, skipping
24:10:24T12:57:22 | WARNING | line:94 |mtpy.modeling.plots.plot_mesh | _plot_topography_ax2 | Cannot find topography information, skipping
<Figure size 960x720 with 2 Axes>
Plotting PlotMesh

Estimating Starting Half Space

The starting half-space is important because it provides the inversion with a decent starting point. Therefore, in practice it is a good idea to estimate what a good starting resistivity is. Here we will take the average and median of all apparent resistivity values across the survey. From this 100 is a good choice, which is a common starting resistivity.

interp_mt_data.estimate_starting_rho()
<Figure size 640x480 with 1 Axes>
model_mesh.res_initial_value = 100

Write Model File

We have a decent mesh, lets write to a file

model_mesh.to_modem(
    model_fn=Path().cwd().parent.parent.joinpath("data", "modeling", "ynp_sm02.rho"), 
)
24:10:24T12:57:35 | INFO | line:873 |mtpy.modeling.structured_mesh_3d | to_modem | Wrote file to: c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\ynp_sm02.rho

Covariance

ModEM uses a regularization parameter called covariance, which controls how smooth the inversion should be, with higher values being more smooth and lower values being coarser. You can apply the covariance multiple times if you like, so sometimes it is adventageous to use a smaller covariance value but apply it multiple times. The covariance in ModEM sort of describes the length of smoothing, in that it uses the a certain number of cells depending on the value. At the padding cells this can blowup and provide unrealistic values and are usually ignored in plotting. Varying the covariance is an excersize in itself. For this we will apply a common covariance of 0.3 in all directions just once.

covariance = Covariance(grid_dimensions=model_mesh.res_model.shape)
covariance.write_covariance_file(
    Path().cwd().parent.parent.joinpath("data", "modeling", "ynp_covariance.cov")
)
24:10:24T12:57:38 | INFO | line:235 |mtpy.modeling.modem.convariance | write_covariance_file | Wrote covariance file to c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\ynp_covariance.cov

Control Files

How ModEM computes the forward model and how the inversion runs can be controlled through control files. These are often kept to the default values, you may change the starting lambda depending on your modeling method.

control_fwd = ControlFwd()
control_fwd.write_control_file(
    Path().cwd().parent.parent.joinpath("data", "modeling", "control.fwd")
)
Wrote ModEM control file to c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\control.fwd
control_inv = ControlInv()
control_inv.write_control_file(
    Path().cwd().parent.parent.joinpath("data", "modeling", "control.inv")
)
Wrote ModEM control file to c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\control.inv

Adding Topography

For topographically extreme areas it can be useful to include topography into the model. We just need to provide an ascii elevation file formatted in the form ArcGIS. More formats will be supported in the future, but these are relatively common for coarse topography. You can download DEMs from https://www.ncei.noaa.gov/maps/grid-extract/. One is provided for this data set.

Note: If you are including topography in the model, you need to center the stations to the model cell, otherwise weird things can happen.

model_mesh.n_air_layers = 15
model_mesh.add_topography_to_model(
    Path().cwd().parent.parent.joinpath("data", "modeling", "yellowstone.asc"),
    airlayer_type="log_down"
)
model_mesh.plot_mesh(fig_num=10)
model_mesh.to_modem(
    model_fn=Path().cwd().parent.parent.joinpath("data", "modeling", "ynp_sm02_topo.rho"), 
)
<Figure size 960x720 with 3 Axes>
24:10:24T12:58:12 | INFO | line:873 |mtpy.modeling.structured_mesh_3d | to_modem | Wrote file to: c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\ynp_sm02_topo.rho

Write Topography Covariance

Now that topography is added into the model we want to make sure the air cells remain locked.

topo_covariance = Covariance(grid_dimensions=model_mesh.res_model.shape)
topo_covariance.write_covariance_file(
    Path().cwd().parent.parent.joinpath("data", "modeling", "ynp_covariance_topo.cov"),
    res_model=model_mesh.res_model,
)
24:10:24T12:59:25 | INFO | line:235 |mtpy.modeling.modem.convariance | write_covariance_file | Wrote covariance file to c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\ynp_covariance_topo.cov

Centering Station and Projecting on to Topography

We need to now center the stations to their cell and project the station to the topgraphic surface.

interp_mt_data.center_stations(model_mesh)
interp_mt_data.project_stations_on_topography(model_mesh)
interp_mt_data.station_locations
Loading...
interp_mt_data.to_modem(
    Path().cwd().parent.parent.joinpath("data", "modeling", "ynp_z05_t02_topo.dat"),
    topography=True
)
24:10:24T12:59:47 | WARNING | line:553 |mtpy.modeling.modem.data | _check_for_too_small_errors | Found errors with values less than 0.02 in z_xy 1 times. Setting error as z_xy x 0.05.
24:10:24T12:59:47 | WARNING | line:553 |mtpy.modeling.modem.data | _check_for_too_small_errors | Found errors with values less than 0.02 in z_yx 3 times. Setting error as z_yx x 0.05.
24:10:24T12:59:47 | INFO | line:680 |mtpy.modeling.modem.data | write_data_file | Wrote ModEM data file to c:\Users\jpeacock\OneDrive - DOI\Documents\GitHub\iris-mt-course-2022\data\modeling\ynp_z05_t02_topo.dat
ModEM Data Object: Number of impedance stations: 46 Number of tipper stations: 46 Number of phase tensor stations: 46 Number of periods: 10 Period range (s): Min: 0.01 Max: 1000 Rotation angle: 0 Data center: Latitude: 44.5957 deg Northing: 4938112.1287 m Longitude: -110.6258 deg Easting: 529702.9612 m Datum epsg: 4326 UTM epsg: 32612 Elevation: 0.0 m Impedance data: True Tipper data: True Inversion Mode: Full_Impedance, Full_Vertical_Components