Estimating a regional field#
Here we will present the 4 methods we provide for estimating the regional component of gravity.
Import packages#
[1]:
from __future__ import annotations
%load_ext autoreload
%autoreload 2
import pandas as pd
import verde as vd
import xarray as xr
from polartoolkit import maps
from polartoolkit import utils as polar_utils
from invert4geom import plotting, regional, synthetic, utils
Create observed gravity data#
Create 2 synthetic surfaces, an upper one with shorter wavelength topographic features, and a lower once with longer wavelength features. The upper surface will produce the gravity component of interest, the residual, and the lower surface will produce the gravity component we aim to estimate and remove, the regional. We will create prism layers for both of these layers, forward model them onto a set of gravity observation points. The combined forward gravity effect of these layes, with some added noise, will represent the observed gravity.
[2]:
# set grid parameters
spacing = 500
region = [0, 40000, 0, 30000]
# create upper synthetic topography data
upper_topography = synthetic.synthetic_topography_simple(
spacing,
region,
)
# create lower synthetic topography data
lower_topography = synthetic.synthetic_topography_regional(
spacing,
region,
scale=3,
yoffset=-1000,
)
[3]:
# create constraint points by sampling upper surface at random locations
num_constraints = 30
coords = vd.scatter_points(region=region, size=num_constraints, random_state=7)
constraint_points = pd.DataFrame(data={"easting": coords[0], "northing": coords[1]})
# sample true topography at these points
constraint_points = utils.sample_grids(
constraint_points, upper_topography, "upward", coord_names=("easting", "northing")
)
Prism layers#
[14]:
# the density contrast is between sediment (~1800 kg/m3) and air (~1 kg/m3)
density_contrast = 1800
# prisms are created between the mean topography value and the height of the topography
zref = upper_topography.values.mean()
# prisms above zref have positive density contrast and prisms below zref have negative
# density contrast
density = xr.where(upper_topography >= zref, density_contrast, -density_contrast)
# create layer of prisms
upper_prisms = utils.grids_to_prisms(
upper_topography,
zref,
density=density,
)
# the density contrast is between mantle (~3300 kg/m3) and rock (~2670 kg/m3)
density_contrast = 3300 - 2670
# to add an offset, prisms are created between -2km the height of the topography
zref = -2e3 # lower_topography.values.mean()
# prisms above zref have positive density contrast and prisms below zref have negative
# density contrast
density = xr.where(lower_topography >= zref, density_contrast, -density_contrast)
# create layer of prisms
lower_prisms = utils.grids_to_prisms(
lower_topography,
zref,
density=density,
)
plotting.show_prism_layers(
[upper_prisms, lower_prisms],
color_by="thickness",
log_scale=False,
zscale=20,
backend="static",
)
Forward gravity of prism layers#
[15]:
# make pandas dataframe of locations to calculate gravity
# this represents the station locations of a gravity survey
# create lists of coordinates
coords = vd.grid_coordinates(
region=region,
spacing=spacing,
pixel_register=False,
extra_coords=1000, # survey elevation
)
# grid the coordinates
observations = vd.make_xarray_grid(
(coords[0], coords[1]),
data=coords[2],
data_names="upward",
dims=("northing", "easting"),
).upward
grav_df = vd.grid_to_table(observations)
# forward gravity of upper surface prisms
grav_df["upper_surface_grav"] = upper_prisms.prism_layer.gravity(
coordinates=(
grav_df.easting,
grav_df.northing,
grav_df.upward,
),
field="g_z",
progressbar=True,
)
# forward gravity of lower surface prisms
grav_df["lower_surface_grav"] = lower_prisms.prism_layer.gravity(
coordinates=(
grav_df.easting,
grav_df.northing,
grav_df.upward,
),
field="g_z",
progressbar=True,
)
grav_df["grav"] = grav_df.upper_surface_grav + grav_df.lower_surface_grav
grav_df
[15]:
northing | easting | upward | upper_surface_grav | lower_surface_grav | grav | |
---|---|---|---|---|---|---|
0 | 0.0 | 0.0 | 1000.0 | 4.702821 | 6.858162 | 11.560984 |
1 | 0.0 | 500.0 | 1000.0 | 5.761512 | 7.729221 | 13.490732 |
2 | 0.0 | 1000.0 | 1000.0 | 5.940439 | 8.493371 | 14.433810 |
3 | 0.0 | 1500.0 | 1000.0 | 5.886537 | 9.126971 | 15.013508 |
4 | 0.0 | 2000.0 | 1000.0 | 5.748903 | 9.635873 | 15.384776 |
... | ... | ... | ... | ... | ... | ... |
4936 | 30000.0 | 38000.0 | 1000.0 | 1.911976 | 12.415943 | 14.327919 |
4937 | 30000.0 | 38500.0 | 1000.0 | 1.900899 | 11.744157 | 13.645055 |
4938 | 30000.0 | 39000.0 | 1000.0 | 1.866645 | 10.909692 | 12.776336 |
4939 | 30000.0 | 39500.0 | 1000.0 | 1.760582 | 9.901674 | 11.662256 |
4940 | 30000.0 | 40000.0 | 1000.0 | 1.391119 | 8.749302 | 10.140421 |
4941 rows à 6 columns
[16]:
# contaminate gravity with 0.5 mGal of random noise
grav_df["observed_grav"], stddev = synthetic.contaminate(
grav_df.grav,
stddev=0.5,
percent=False,
seed=0,
)
# grid the contaminated gravity
grav_grid = grav_df.set_index(["northing", "easting"]).to_xarray()
grav_grid.observed_grav.plot()
grav_df
[16]:
northing | easting | upward | upper_surface_grav | lower_surface_grav | grav | observed_grav | |
---|---|---|---|---|---|---|---|
0 | 0.0 | 0.0 | 1000.0 | 4.702821 | 6.858162 | 11.560984 | 11.626785 |
1 | 0.0 | 500.0 | 1000.0 | 5.761512 | 7.729221 | 13.490732 | 13.427617 |
2 | 0.0 | 1000.0 | 1000.0 | 5.940439 | 8.493371 | 14.433810 | 14.756958 |
3 | 0.0 | 1500.0 | 1000.0 | 5.886537 | 9.126971 | 15.013508 | 15.068894 |
4 | 0.0 | 2000.0 | 1000.0 | 5.748903 | 9.635873 | 15.384776 | 15.119878 |
... | ... | ... | ... | ... | ... | ... | ... |
4936 | 30000.0 | 38000.0 | 1000.0 | 1.911976 | 12.415943 | 14.327919 | 15.208171 |
4937 | 30000.0 | 38500.0 | 1000.0 | 1.900899 | 11.744157 | 13.645055 | 13.156471 |
4938 | 30000.0 | 39000.0 | 1000.0 | 1.866645 | 10.909692 | 12.776336 | 13.497419 |
4939 | 30000.0 | 39500.0 | 1000.0 | 1.760582 | 9.901674 | 11.662256 | 10.902134 |
4940 | 30000.0 | 40000.0 | 1000.0 | 1.391119 | 8.749302 | 10.140421 | 9.877312 |
4941 rows à 7 columns
Regional estimation methods#
DC-Shift#
[24]:
# estimate regional with a 30km low pass filter
grav_df = regional.regional_dc_shift(
grav_grid=grav_grid.observed_grav,
grav_df=grav_df,
constraint_points=constraint_points,
regional_col_name="dc_shift_reg",
)
# grid the results
grav_grid = grav_df.set_index(["northing", "easting"]).to_xarray()
# compare with true regional
_ = polar_utils.grd_compare(
grav_grid.lower_surface_grav,
grav_grid.dc_shift_reg,
plot_type="xarray",
robust=True,
plot=True,
grid1_name="True regional field",
grid2_name="Regional field from method: DC-Shift",
hist=True,
inset=False,
verbose="q",
title="difference",
grounding_line=False,
points=constraint_points.rename(columns={"easting": "x", "northing": "y"}),
)
Filter#
[18]:
# estimate regional with a 30km low pass filter
grav_df = regional.regional_filter(
filter_width=30e3,
grav_grid=grav_grid.observed_grav,
grav_df=grav_df,
regional_col_name="filter_reg",
)
# grid the results
grav_grid = grav_df.set_index(["northing", "easting"]).to_xarray()
# compare with true regional
_ = polar_utils.grd_compare(
grav_grid.lower_surface_grav,
grav_grid.filter_reg,
plot_type="xarray",
robust=True,
plot=True,
grid1_name="True regional field",
grid2_name="Regional field from method: Filter",
hist=True,
inset=False,
verbose="q",
title="difference",
grounding_line=False,
)
Trend#
[19]:
# estimate regional with fitting a 4th order trend
grav_df = regional.regional_trend(
trend=4,
grav_grid=grav_grid.observed_grav,
grav_df=grav_df,
fill_method="verde",
regional_col_name="trend_reg",
)
# grid the results
grav_grid = grav_df.set_index(["northing", "easting"]).to_xarray()
# compare with true regional
_ = polar_utils.grd_compare(
grav_grid.lower_surface_grav,
grav_grid.trend_reg,
plot_type="xarray",
robust=True,
plot=True,
grid1_name="True regional field",
grid2_name="Regional field from method: Trend",
hist=True,
inset=False,
verbose="q",
title="difference",
grounding_line=False,
)
Equivalent Sources#
[20]:
# estimate regional with fitting deep equivalent sources
grav_df = regional.regional_eq_sources(
source_depth=-100e3,
grav_df=grav_df,
input_grav_name="observed_grav",
eq_damping=None,
block_size=2e3,
regional_col_name="eq_sources_reg",
)
# grid the results
grav_grid = grav_df.set_index(["northing", "easting"]).to_xarray()
# compare with true regional
_ = polar_utils.grd_compare(
grav_grid.lower_surface_grav,
grav_grid.eq_sources_reg,
plot_type="xarray",
robust=True,
plot=True,
grid1_name="True regional field",
grid2_name="Regional field from method: Equivalent sources",
hist=True,
inset=False,
verbose="q",
title="difference",
grounding_line=False,
)
Constraint point minimization#
[21]:
# estimate regional with the constraints method
grav_df = regional.regional_constraints(
constraint_points=constraint_points,
grav_grid=grav_grid.observed_grav,
grav_df=grav_df,
region=region,
spacing=spacing,
regional_col_name="constraints_reg",
grid_method="pygmt",
tension_factor=1,
)
# grid the results
grav_grid = grav_df.set_index(["northing", "easting"]).to_xarray()
# compare with true regional
_ = polar_utils.grd_compare(
grav_grid.lower_surface_grav,
grav_grid.constraints_reg,
plot_type="xarray",
robust=True,
plot=True,
grid1_name="True regional field",
grid2_name="Regional field from method: Constraints",
hist=True,
inset=False,
verbose="q",
title="difference",
grounding_line=False,
points=constraint_points.rename(columns={"easting": "x", "northing": "y"}),
)
[23]:
names = ["dc_shift_reg", "filter_reg", "trend_reg", "eq_sources_reg", "constraints_reg"]
fig = maps.subplots(
[grav_grid[n] for n in names],
region,
margins="2c",
fig_width=20,
# grd2cpt=True,
fig_title="Regional components",
subplot_titles=names,
cbar_units=["m"] * len(names),
autolabel=True,
)
fig.show()
makecpt [ERROR]: Option T: min >= max
ERROR:root:Module 'makecpt' failed with status code 72:
makecpt [ERROR]: Option T: min >= max
Traceback (most recent call last):
File "/home/mdtanker/miniforge3/envs/invert4geom/lib/python3.12/site-packages/polartoolkit/maps.py", line 432, in plot_grd
pygmt.makecpt(
File "/home/mdtanker/miniforge3/envs/invert4geom/lib/python3.12/site-packages/pygmt/helpers/decorators.py", line 603, in new_module
return module_func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mdtanker/miniforge3/envs/invert4geom/lib/python3.12/site-packages/pygmt/helpers/decorators.py", line 776, in new_module
return module_func(*bound.args, **bound.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/mdtanker/miniforge3/envs/invert4geom/lib/python3.12/site-packages/pygmt/src/makecpt.py", line 165, in makecpt
lib.call_module(module="makecpt", args=arg_str)
File "/home/mdtanker/miniforge3/envs/invert4geom/lib/python3.12/site-packages/pygmt/clib/session.py", line 624, in call_module
raise GMTCLibError(
pygmt.exceptions.GMTCLibError: Module 'makecpt' failed with status code 72:
makecpt [ERROR]: Option T: min >= max
WARNING:root:grid region can't be extracted.
The above plot and code show four techniques to estimate the regional field from gravity data. Each of these techniques requires the user input to pick the value of a certain parameter, each of which has a large effect on the outcome. Below these hyperparameters
for each technique are explained:
Filter technique: the width of the gaussian filter
Trend technique: the degree-order of the trend fit to the data
Equivalent Sources technique: the source depth and optionally a damping parameter
Constraint Point Minimization technique: the tension factor if using minimum curvature gridding, or the damping value if using bi-harmonic spline gridding
Above we just picked these hyperparameter values arbitrarily. Below we show a more informed technique for choosing each of these hyperparameter values.
[1]:
# STILL TO COME
[ ]: