Interpolation with grid refinement¶
The previous notebook introduced how to use yt_xarray’s embedded transformation framework. When embedding a non-cartesian dataset within a cartesian grid, however, it is common to end up with null regions of the cartesian dataset where the cartesian bounding box exceeds the underlying bounds of the non-cartesian dataset. To help avoid over-sampling regions with no underlying data, you can utilize several grid-refinement options.
To illustrate the issue let’s first create a global dataset of the upper mantle to a depth of 1000 km and initialize our transformation object:
[1]:
from yt_xarray.utilities._utilities import (
construct_minimal_ds,
)
from yt_xarray.transformations import build_interpolated_cartesian_ds, GeocentricCartesian
import yt
dim_names = ("longitude", "latitude", "depth")
ds = construct_minimal_ds(
min_x=0,
max_x=360,
x_stretched=False,
x_name=dim_names[0],
n_x=50,
min_y=-90.,
max_y=90,
y_stretched=False,
y_name=dim_names[1],
n_y=60,
z_stretched=False,
z_name=dim_names[2],
n_z=80,
min_z=0.0,
max_z=1000.0,
)
fields = list(ds.data_vars)
gc = GeocentricCartesian(radial_type='depth', r_o=6371., use_neg_lons=True)
In order to resolve the spherical shell in cartesian coordinates on a uniform grid, a fairly high resolution would be needed, resulting in many cells that do not contain data:
[2]:
ds_yt = build_interpolated_cartesian_ds(
ds,
gc,
fields=('test_field',),
grid_resolution = (128,128,128),
)
slc = yt.SlicePlot(ds_yt, 'y', ('test_field'), window_size=(3,3))
slc.set_log('test_field', False)
slc.annotate_cell_edges(alpha=0.2)
slc.annotate_grids(edgecolors=(1.,0,0,0))
slc.show()
yt : [INFO ] 2024-05-20 14:13:59,570 Parameters: current_time = 0.0
yt : [INFO ] 2024-05-20 14:13:59,570 Parameters: domain_dimensions = [128 128 128]
yt : [INFO ] 2024-05-20 14:13:59,571 Parameters: domain_left_edge = [-6371. -6371. -6371.]
yt : [INFO ] 2024-05-20 14:13:59,571 Parameters: domain_right_edge = [6371. 6371. 6371.]
yt : [INFO ] 2024-05-20 14:13:59,572 Parameters: cosmological_simulation = 0
yt : [INFO ] 2024-05-20 14:14:00,192 xlim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:14:00,193 ylim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:14:00,194 xlim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:14:00,195 ylim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:14:00,202 Making a fixed resolution buffer of (('stream', 'test_field')) 800 by 800
To avoid the over-sampling, build_interpolated_cartesian_ds accepts a number of keyword arguments that will subdivide the domain into a number of grid patches. To turn this feature on, provide refine_grid=True. When refining, the grid_resolution argument is the resolution of the coarsest grid and at present, only a single level of refinement is added. That single level, however, can controlled by the arguments refine_min_grid_size and refine_by:
refine_bycontrols the refinement factor and is the number of elements contained within a single coarse grid element.refine_min_grid_sizeis the minimum number of coarse elements required to subdivide a grid. For example, ifrefine_min_grid_size=4, continuous sections of the coarse grid are only subdivided if they contain more than this number of coarse cells.
[4]:
ds_yt = build_interpolated_cartesian_ds(
ds,
gc,
fields=('test_field',),
grid_resolution = (16,16,16),
refine_grid=True,
refine_by=8,
refine_min_grid_size=4,
)
slc = yt.SlicePlot(ds_yt, 'y', ('test_field'), window_size=(3,3))
slc.set_log('test_field', False)
slc.annotate_cell_edges(alpha=0.2)
slc.annotate_grids(edgecolors=(1.,0,0,0))
slc.show()
yt_xarray : [INFO ] 2024-05-20 14:25:59,483: Creating image mask for grid decomposition.
yt_xarray : [INFO ] 2024-05-20 14:25:59,696: Decomposing image mask and building yt dataset.
yt_xarray : [INFO ] 2024-05-20 14:25:59,715: Decomposed into 200 grids after 201 iterations.
yt : [INFO ] 2024-05-20 14:25:59,762 Parameters: current_time = 0.0
yt : [INFO ] 2024-05-20 14:25:59,763 Parameters: domain_dimensions = [16 16 16]
yt : [INFO ] 2024-05-20 14:25:59,764 Parameters: domain_left_edge = [-6371. -6371. -6371.]
yt : [INFO ] 2024-05-20 14:25:59,764 Parameters: domain_right_edge = [6371. 6371. 6371.]
yt : [INFO ] 2024-05-20 14:25:59,765 Parameters: cosmological_simulation = 0
yt : [INFO ] 2024-05-20 14:25:59,948 xlim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:25:59,949 ylim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:25:59,951 xlim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:25:59,951 ylim = -6371.000000 6371.000000
yt : [INFO ] 2024-05-20 14:25:59,953 Making a fixed resolution buffer of (('stream', 'test_field')) 800 by 800
The default aglorithm proceeds via bi-section: the coarse grid is iteratively divded in half (in each dimension), discarding empty grids along the way. Additionally, one can use the decomposition method of Berger and Rigoutsos 1991 (https://doi.org/10.1109/21.120081) by specifying refinement_method='signature_filter'.
Now let’s return to the real world example from the previous notebook: the regional upper mantle tomography model from Schmandt and Humphreys (2010, doi:10.1016/j.epsl.2010.06.047, accessible at doi:10.17611/DP/9991760).
Without grid refinement:
[10]:
import yt_xarray
ds = yt_xarray.open_dataset("IRIS/wUS-SH-2010_percent.nc")
grid_resolution = (128, 128, 128)
gc = GeocentricCartesian(radial_type='depth', r_o=6371., use_neg_lons=True)
ds_yt = build_interpolated_cartesian_ds(ds, gc,
fields='dvs',
length_unit='km',
grid_resolution=grid_resolution)
slc = yt.SlicePlot(ds_yt, 'x', ('stream', 'dvs'), window_size=(4,4))
slc.set_log(("stream", "dvs"), False)
slc.set_cmap(("stream", "dvs"), "magma_r")
slc.set_zlim(("stream", "dvs"), -6, 6)
slc.annotate_cell_edges(alpha=0.2)
slc.annotate_grids(edgecolors=(1.,0,0,0))
slc.show()
yt : [INFO ] 2024-05-20 14:37:27,242 Parameters: current_time = 0.0
yt : [INFO ] 2024-05-20 14:37:27,243 Parameters: domain_dimensions = [128 128 128]
yt : [INFO ] 2024-05-20 14:37:27,243 Parameters: domain_left_edge = [-3271. -5573. 2533.]
yt : [INFO ] 2024-05-20 14:37:27,244 Parameters: domain_right_edge = [ -334. -2832. 4870.]
yt : [INFO ] 2024-05-20 14:37:27,245 Parameters: cosmological_simulation = 0
yt : [INFO ] 2024-05-20 14:37:27,660 xlim = -5573.000000 -2832.000000
yt : [INFO ] 2024-05-20 14:37:27,660 ylim = 2533.000000 4870.000000
yt : [INFO ] 2024-05-20 14:37:27,662 xlim = -5573.000000 -2832.000000
yt : [INFO ] 2024-05-20 14:37:27,663 ylim = 2533.000000 4870.000000
yt : [INFO ] 2024-05-20 14:37:27,664 Making a fixed resolution buffer of (('stream', 'dvs')) 800 by 800
while with grid refinement, we can avoid oversampling those empty regions:
[14]:
import xarray as xr
import yt_xarray
import yt
from yt_xarray.transformations import GeocentricCartesian, build_interpolated_cartesian_ds
ds = yt_xarray.open_dataset("IRIS/wUS-SH-2010_percent.nc")
grid_resolution = (16, 16, 16)
gc = GeocentricCartesian(radial_type='depth', r_o=6371., use_neg_lons=True)
ds_yt = build_interpolated_cartesian_ds(
ds,
gc,
fields = 'dvs' ,
grid_resolution = grid_resolution,
refine_grid=True,
refine_max_iters=2000,
refine_min_grid_size=4,
refine_by=8,
interp_method='interpolate',
)
slc = yt.SlicePlot(ds_yt, 'x', ('stream', 'dvs'), window_size=(4,4))
slc.set_log(("stream", "dvs"), False)
slc.set_cmap(("stream", "dvs"), "magma_r")
slc.set_zlim(("stream", "dvs"), -8, 0)
slc.annotate_cell_edges(color=(1,0,0), alpha=0.3)
slc.annotate_grids(edgecolors=(1,0,1,1))
slc.show()
yt_xarray : [INFO ] 2024-05-20 14:38:17,801: Creating image mask for grid decomposition.
yt_xarray : [INFO ] 2024-05-20 14:38:18,012: Decomposing image mask and building yt dataset.
yt_xarray : [INFO ] 2024-05-20 14:38:18,031: Decomposed into 210 grids after 258 iterations.
yt : [INFO ] 2024-05-20 14:38:18,072 Parameters: current_time = 0.0
yt : [INFO ] 2024-05-20 14:38:18,073 Parameters: domain_dimensions = [16 16 16]
yt : [INFO ] 2024-05-20 14:38:18,073 Parameters: domain_left_edge = [-3271. -5573. 2533.]
yt : [INFO ] 2024-05-20 14:38:18,074 Parameters: domain_right_edge = [ -334. -2832. 4870.]
yt : [INFO ] 2024-05-20 14:38:18,074 Parameters: cosmological_simulation = 0
yt : [INFO ] 2024-05-20 14:38:18,367 xlim = -5573.000000 -2832.000000
yt : [INFO ] 2024-05-20 14:38:18,367 ylim = 2533.000000 4870.000000
yt : [INFO ] 2024-05-20 14:38:18,369 xlim = -5573.000000 -2832.000000
yt : [INFO ] 2024-05-20 14:38:18,369 ylim = 2533.000000 4870.000000
yt : [INFO ] 2024-05-20 14:38:18,371 Making a fixed resolution buffer of (('stream', 'dvs')) 800 by 800
as before, we’ll add a new derived field for volume rendering that picks up just the slow anomalies and fills null values as 0.0:
[16]:
import numpy as np
fill_val_dict = {'val': 0.0}
def _slow_vels(field, data):
# return negative velocities only, 0 all other elements
dvs = data['dvs'].copy()
dvs[np.isnan(dvs)] = fill_val_dict['val']
dvs[dvs>0] = fill_val_dict['val']
return np.abs(dvs)
ds_yt.add_field(
name=("stream", "slow_dvs"),
function=_slow_vels,
sampling_type="local",
)
slc = yt.SlicePlot(ds_yt, 'x', ('stream', 'slow_dvs'), window_size=(4,4))
slc.set_log(("stream", "slow_dvs"), False)
slc.set_cmap(("stream", "slow_dvs"), "magma_r")
slc.annotate_cell_edges(color=(1,0,0), alpha=0.3)
slc.annotate_grids(edgecolors=(1,0,0,1))
slc.show()
yt : [WARNING ] 2024-05-20 14:39:45,173 Field ('stream', 'slow_dvs') already exists. To override use `force_override=True`.
yt : [INFO ] 2024-05-20 14:39:45,399 xlim = -5573.000000 -2832.000000
yt : [INFO ] 2024-05-20 14:39:45,400 ylim = 2533.000000 4870.000000
yt : [INFO ] 2024-05-20 14:39:45,402 xlim = -5573.000000 -2832.000000
yt : [INFO ] 2024-05-20 14:39:45,402 ylim = 2533.000000 4870.000000
yt : [INFO ] 2024-05-20 14:39:45,403 Making a fixed resolution buffer of (('stream', 'slow_dvs')) 800 by 800
and volume rendering again:
[17]:
reg = ds_yt.region( ds_yt.domain_center, ds_yt.domain_left_edge, ds_yt.domain_right_edge)
reg
sc = yt.create_scene(reg, field=('stream', 'slow_dvs'))
cam = sc.add_camera(ds_yt)
source = sc[0]
# Set the bounds of the transfer function
source.tfh.set_bounds((0.1, 8))
# set that the transfer function should be evaluated in log space
source.tfh.set_log(True)
cam.zoom(2)
cam.yaw(100*np.pi/180)
cam.roll(220*np.pi/180)
cam.rotate(30*np.pi/180)
sc.show(sigma_clip=5.)
yt : [INFO ] 2024-05-20 14:39:51,064 Rendering scene (Can take a while).
yt : [INFO ] 2024-05-20 14:39:51,065 Creating volume
yt : [INFO ] 2024-05-20 14:39:52,766 Creating transfer function
[ ]:
[ ]: