3. Including a starting model#

Here we will expand the previous notebook example by showing how to incorporate a non-flat starting model. A typical scenario for where this is useful is if you have a few point measurements of the elevations of the surface you are aiming to recover. These point measurements, referred to here as constraints, may be boreholes, acoustic basement from seismic surveys, or other types of measurements.

3.1. Import packages#

[1]:
# set EPSG for plotting functions
import os

import polartoolkit as ptk

import invert4geom

os.environ["POLARTOOLKIT_EPSG"] = "3031"
/home/mdtanker/miniforge3/envs/invert4geom/lib/python3.12/site-packages/UQpy/__init__.py:6: UserWarning:

pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.

3.2. Get a synthetic model#

Here we will load synthetic topography data, calculate its forward gravity with added noise, and sample the topography at 10 random points which represent places we have measurements of the topography (from boreholes for example). We will then interpolate these 10 points to make a starting model of topography.

[2]:
# the density contrast is between rock (~2670 kg/m3) and air (~1 kg/m3)
density_contrast = 2670 - 1

(
    true_topography,
    starting_topography,
    constraint_points,
    observed_gravity,
) = invert4geom.load_synthetic_model(
    spacing=1000,
    region=(0, 40000, 0, 30000),
    number_of_constraints=10,
    density_contrast=density_contrast,
    gravity_noise=0.2,
)
../_images/tutorial_03_including_starting_model_4_0.png
../_images/tutorial_03_including_starting_model_4_1.png

3.3. Gravity misfit#

Now we need to calculate the forward gravity of the starting topography. We then can subtract it from our observed gravity to get a starting gravity misfit.

[3]:
data = invert4geom.create_data(observed_gravity)
data
[3]:
<xarray.Dataset> Size: 21kB
Dimensions:          (northing: 31, easting: 41)
Coordinates:
  * northing         (northing) float64 248B 0.0 1e+03 2e+03 ... 2.9e+04 3e+04
  * easting          (easting) float64 328B 0.0 1e+03 2e+03 ... 3.9e+04 4e+04
Data variables:
    upward           (northing, easting) float64 10kB 1e+03 1e+03 ... 1e+03
    gravity_anomaly  (northing, easting) float64 10kB 9.57 10.41 ... 3.12 2.973
Attributes:
    region:        (0.0, 40000.0, 0.0, 30000.0)
    spacing:       1000.0
    buffer_width:  3000.0
    inner_region:  (3000.0, 37000.0, 3000.0, 27000.0)
    dataset_type:  data
    model_type:    prisms
    coord_names:   ('easting', 'northing')
[4]:
model = invert4geom.create_model(
    # set the reference level to the mean of the constraints
    zref=constraint_points.upward.mean(),
    density_contrast=density_contrast,
    topography=starting_topography.to_dataset(name="upward"),
)
model.inv.plot_model(
    color_by="thickness",
    zscale=20,
)
../_images/tutorial_03_including_starting_model_7_0.png
[5]:
model
[5]:
<xarray.Dataset> Size: 92kB
Dimensions:                (northing: 31, easting: 41)
Coordinates:
  * northing               (northing) float64 248B 0.0 1e+03 ... 2.9e+04 3e+04
  * easting                (easting) float64 328B 0.0 1e+03 ... 3.9e+04 4e+04
    top                    (northing, easting) float64 10kB 496.3 ... 496.3
    bottom                 (northing, easting) float64 10kB 418.5 ... 468.7
Data variables:
    density                (northing, easting) int64 10kB -2669 -2669 ... -2669
    thickness              (northing, easting) float64 10kB 77.84 ... 27.62
    starting_topography    (northing, easting) float64 10kB 418.5 ... 468.7
    topography             (northing, easting) float64 10kB 418.5 ... 468.7
    mask                   (northing, easting) float64 10kB 1.0 1.0 ... 1.0 1.0
    upper_confining_layer  (northing, easting) float64 10kB nan nan ... nan nan
    lower_confining_layer  (northing, easting) float64 10kB nan nan ... nan nan
Attributes:
    zref:              496.3097858411461
    density_contrast:  2669
    region:            (0.0, 40000.0, 0.0, 30000.0)
    spacing:           1000.0
    buffer_width:      0
    inner_region:      (0.0, 40000.0, 0.0, 30000.0)
    dataset_type:      model
    model_type:        prisms
    coord_names:       ('easting', 'northing')
[6]:
data.inv.forward_gravity(
    model,
    progressbar=True,
)

# estimate regional with the mean misfit at constraints
data.inv.regional_separation(
    method="constant",
    constant=0,
)

data.inv.plot_anomalies()
data
makecpt [ERROR]: Option T: min >= max
../_images/tutorial_03_including_starting_model_9_2.png
[6]:
<xarray.Dataset> Size: 102kB
Dimensions:                   (northing: 31, easting: 41)
Coordinates:
  * northing                  (northing) float64 248B 0.0 1e+03 ... 3e+04
  * easting                   (easting) float64 328B 0.0 1e+03 ... 3.9e+04 4e+04
Data variables:
    upward                    (northing, easting) float64 10kB 1e+03 ... 1e+03
    gravity_anomaly           (northing, easting) float64 10kB 9.57 ... 2.973
    forward_gravity           (northing, easting) float64 10kB -4.617 ... -1.716
    misfit                    (northing, easting) float64 10kB 14.19 ... 4.689
    reg                       (northing, easting) float64 10kB 0.0 0.0 ... 0.0
    res                       (northing, easting) float64 10kB 14.19 ... 4.689
    starting_forward_gravity  (northing, easting) float64 10kB -4.617 ... -1.716
    starting_misfit           (northing, easting) float64 10kB 14.19 ... 4.689
    starting_reg              (northing, easting) float64 10kB 0.0 0.0 ... 0.0
    starting_res              (northing, easting) float64 10kB 14.19 ... 4.689
Attributes:
    region:        (0.0, 40000.0, 0.0, 30000.0)
    spacing:       1000.0
    buffer_width:  3000.0
    inner_region:  (3000.0, 37000.0, 3000.0, 27000.0)
    dataset_type:  data
    model_type:    prisms
    coord_names:   ('easting', 'northing')

3.4. Perform inversion#

Now that we have a starting model and residual gravity misfit data we can start the inversion.

[7]:
# setup the inversion
inv = invert4geom.Inversion(
    data,
    model,
    solver_damping=0.06,
    # set stopping criteria
    l2_norm_tolerance=0.45,
    delta_l2_norm_tolerance=1.005,
)

# run the inversion
inv.invert(
    plot_convergence=True,
)
../_images/tutorial_03_including_starting_model_11_1.png
[8]:
inv.stats_df
[8]:
iteration rmse l2_norm delta_l2_norm iter_time_sec
0 0.0 6.432235 2.536185 inf NaN
1 1.0 1.673239 1.293537 1.960658 0.675588
2 2.0 0.625742 0.791038 1.635240 0.118010
3 3.0 0.322734 0.568097 1.392435 0.146290
4 4.0 0.208199 0.456288 1.245041 0.156962
5 5.0 0.156382 0.395451 1.153841 0.153559
[9]:
inv.plot_inversion_results(
    iters_to_plot=2,
    constraints_df=constraint_points,
)
../_images/tutorial_03_including_starting_model_13_0.png
../_images/tutorial_03_including_starting_model_13_1.png
../_images/tutorial_03_including_starting_model_13_2.png
[10]:
_ = ptk.grid_compare(
    true_topography,
    inv.model.topography,
    grid1_name="True topography",
    grid2_name="Inverted topography",
    robust=True,
    hist=True,
    inset=False,
    title="difference",
    reverse_cpt=True,
    cmap="rain",
    diff_lims=(-20, 20),
    points=constraint_points,
    points_style="x.3c",
)
../_images/tutorial_03_including_starting_model_14_0.png
[11]:
# sample the inverted topography at the constraint points
constraint_points = invert4geom.sample_grids(
    constraint_points,
    inv.model.topography,
    "inverted_topography",
)

rmse = invert4geom.rmse(
    constraint_points.upward - constraint_points.inverted_topography
)
print(f"RMSE: {rmse:.2f} m")
RMSE: 3.33 m
[12]:
constraint_points
[12]:
easting northing upward starting_topography inverted_topography
0 3052.331575 20376.899884 619.779099 580.163740 622.402758
1 31196.751690 24112.171083 479.563412 481.652035 481.809963
2 17536.369258 11428.233994 465.786893 458.230368 468.817484
3 28938.607113 1978.090407 426.413502 438.512760 430.553204
4 39119.580480 8644.367979 428.773305 425.210470 433.235065
5 21539.834816 27287.805832 546.901160 547.287244 548.818799
6 20044.818546 6401.560607 450.406755 455.069067 452.341247
7 2882.045334 13563.718855 460.979806 499.138482 459.679771
8 10737.559204 27936.180591 614.084318 624.948915 620.202486
9 19995.300033 746.976827 470.409607 452.397262 472.844831

The RMSE between the constraintโ€™s true values and the inverted topography at the constraintโ€™s is not 0. This shows that while the starting model helped the inversion, the actual values of the constraints is not adhered too. The adhere to point measurements notebook will show how to help the model stick to the constraints.