Skip to content

Commit 2be8e50

Browse files
Change UX->Ux interpolators prefix for unstructured grid interpolators
Note also that changes in test assertions were needed for unstructured grid pset.execute tests that use spherical mesh types. This arises due to the fact that we now alter the particle locations by removing the component of the spherical coordinate transformation that is not in the element plane; this changes the resulting barycentric coordinates and thus interpolated values for particle advection.
1 parent 9a3841f commit 2be8e50

7 files changed

Lines changed: 73 additions & 53 deletions

File tree

docs/user_guide/examples/tutorial_interpolation.ipynb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@
258258
"source": [
259259
"## Interpolation on unstructured grids\n",
260260
"Parcels v4 supports the use of general circulation model output that is defined on unstructured grids. We include basic interpolators to help you get started, including\n",
261-
"- `UXPiecewiseConstantFace` - this interpolator implements piecewise constant interpolation and is appropriate for data that is registered to the face centers of the unstructured grid\n",
262-
"- `UXPiecewiseLinearNode` - this interpolator implements barycentric interpolation and is appropriate for data that is registered to the corner vertices of the unstructured grid faces\n",
261+
"- `UxPiecewiseConstantFace` - this interpolator implements piecewise constant interpolation and is appropriate for data that is registered to the face centers of the unstructured grid\n",
262+
"- `UxPiecewiseLinearNode` - this interpolator implements barycentric interpolation and is appropriate for data that is registered to the corner vertices of the unstructured grid faces\n",
263263
"\n",
264264
"To get started, we use a very simple generated `UxArray.UxDataset` that is included with Parcels."
265265
]
@@ -280,7 +280,7 @@
280280
"cell_type": "markdown",
281281
"metadata": {},
282282
"source": [
283-
"Next, we create the `Field` and `Fieldset` objects that will be used later in advancing particles. When creating a `Field` or `VectorField` object for unstructured grid data, we attach a `parcels.UxGrid` object and attach an `interp_method` to each object. For data that is defined on face centers, we use the `UXPiecewiseConstantFace` interpolator and for data that is defined on the face vertices, we use the `UXPiecewiseLinearNode` interpolator. In this example, we will look specifically at interpolating a tracer field that is defined by the same underlying analytical function, but is defined on both faces and vertices as separate fields."
283+
"Next, we create the `Field` and `Fieldset` objects that will be used later in advancing particles. When creating a `Field` or `VectorField` object for unstructured grid data, we attach a `parcels.UxGrid` object and attach an `interp_method` to each object. For data that is defined on face centers, we use the `UxPiecewiseConstantFace` interpolator and for data that is defined on the face vertices, we use the `UxPiecewiseLinearNode` interpolator. In this example, we will look specifically at interpolating a tracer field that is defined by the same underlying analytical function, but is defined on both faces and vertices as separate fields."
284284
]
285285
},
286286
{
@@ -299,13 +299,13 @@
299299
" \"T_node\",\n",
300300
" ds[\"T_node\"],\n",
301301
" grid,\n",
302-
" interp_method=parcels.interpolators.UXPiecewiseLinearNode,\n",
302+
" interp_method=parcels.interpolators.UxPiecewiseLinearNode,\n",
303303
")\n",
304304
"Tface = parcels.Field(\n",
305305
" \"T_face\",\n",
306306
" ds[\"T_face\"],\n",
307307
" grid,\n",
308-
" interp_method=parcels.interpolators.UXPiecewiseConstantFace,\n",
308+
" interp_method=parcels.interpolators.UxPiecewiseConstantFace,\n",
309309
")\n",
310310
"fieldset = parcels.FieldSet([Tnode, Tface])"
311311
]
@@ -340,7 +340,7 @@
340340
"cell_type": "markdown",
341341
"metadata": {},
342342
"source": [
343-
"Now, we can create a `ParticleSet` for each interpolation experiment. In one of the `ParticleSet` objects we use the `SampleTracer_Node` kernel to showcase interpolating with the `UXPiecewiseLinearNode` interpolator for node-registered data. In the other, we use the `SampleTracer_Face` to showcase interpolating with the `UxPiecewiseConstantFace` interpolator for face-registered data."
343+
"Now, we can create a `ParticleSet` for each interpolation experiment. In one of the `ParticleSet` objects we use the `SampleTracer_Node` kernel to showcase interpolating with the `UxPiecewiseLinearNode` interpolator for node-registered data. In the other, we use the `SampleTracer_Face` to showcase interpolating with the `UxPiecewiseConstantFace` interpolator for face-registered data."
344344
]
345345
},
346346
{

docs/user_guide/examples_v3/tutorial_stommel_uxarray.ipynb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
"\n",
113113
"In Parcels, grid searching is conducted with respect to the faces. In other words, when a grid index `ei` is provided to an interpolation method, this refers the face index `fi` at vertical layer `zi` (when unraveled). Within the interpolation method, the `field.grid.uxgrid.face_node_connectivity` attribute can be used to obtain the node indices that surround the face. Using these connectivity tables is necessary for properly indexing node registered data.\n",
114114
"\n",
115-
"For the example Stommel gyre dataset in this tutorial, the `u` and `v` velocity components are face registered (similar to FESOM). Parcels includes a nearest neighbor interpolator for face registered unstructured grid data through `Parcels.application_kernels.interpolation.UXPiecewiseConstantFace`. Below, we create the `Field`s `U` and `V` and associate them with the `UxGrid` we created previously and this interpolation method."
115+
"For the example Stommel gyre dataset in this tutorial, the `u` and `v` velocity components are face registered (similar to FESOM). Parcels includes a nearest neighbor interpolator for face registered unstructured grid data through `Parcels.application_kernels.interpolation.UxPiecewiseConstantFace`. Below, we create the `Field`s `U` and `V` and associate them with the `UxGrid` we created previously and this interpolation method."
116116
]
117117
},
118118
{
@@ -121,26 +121,26 @@
121121
"metadata": {},
122122
"outputs": [],
123123
"source": [
124-
"from parcels.application_kernels.interpolation import UXPiecewiseConstantFace\n",
124+
"from parcels.application_kernels.interpolation import UxPiecewiseConstantFace\n",
125125
"from parcels.field import Field\n",
126126
"\n",
127127
"U = Field(\n",
128128
" name=\"U\",\n",
129129
" data=ds.U,\n",
130130
" grid=grid,\n",
131-
" interp_method=UXPiecewiseConstantFace,\n",
131+
" interp_method=UxPiecewiseConstantFace,\n",
132132
")\n",
133133
"V = Field(\n",
134134
" name=\"V\",\n",
135135
" data=ds.V,\n",
136136
" grid=grid,\n",
137-
" interp_method=UXPiecewiseConstantFace,\n",
137+
" interp_method=UxPiecewiseConstantFace,\n",
138138
")\n",
139139
"P = Field(\n",
140140
" name=\"P\",\n",
141141
" data=ds.p,\n",
142142
" grid=grid,\n",
143-
" interp_method=UXPiecewiseConstantFace,\n",
143+
" interp_method=UxPiecewiseConstantFace,\n",
144144
")"
145145
]
146146
},

src/parcels/_core/fieldset.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from parcels._core.xgrid import _DEFAULT_XGCM_KWARGS, XGrid
1919
from parcels._logger import logger
2020
from parcels._typing import Mesh
21-
from parcels.interpolators import UXPiecewiseConstantFace, UXPiecewiseLinearNode, XConstantField, XLinear
21+
from parcels.interpolators import UxPiecewiseConstantFace, UxPiecewiseLinearNode, XConstantField, XLinear
2222

2323
if TYPE_CHECKING:
2424
from parcels._core.basegrid import BaseGrid
@@ -459,9 +459,9 @@ def _select_uxinterpolator(da: ux.UxDataArray):
459459
"""Selects the appropriate uxarray interpolator for a given uxarray UxDataArray"""
460460
supported_uxinterp_mapping = {
461461
# (nz1,n_face): face-center laterally, layer centers vertically — piecewise constant
462-
"nz1,n_face": UXPiecewiseConstantFace,
462+
"nz1,n_face": UxPiecewiseConstantFace,
463463
# (nz,n_node): node/corner laterally, layer interfaces vertically — barycentric lateral & linear vertical
464-
"nz,n_node": UXPiecewiseLinearNode,
464+
"nz,n_node": UxPiecewiseLinearNode,
465465
}
466466
# Extract only spatial dimensions, neglecting time
467467
da_spatial_dims = tuple(d for d in da.dims if d not in ("time",))

src/parcels/interpolators.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
__all__ = [
1919
"CGrid_Tracer",
2020
"CGrid_Velocity",
21-
"UXPiecewiseConstantFace",
22-
"UXPiecewiseLinearNode",
21+
"UxPiecewiseConstantFace",
22+
"UxPiecewiseLinearNode",
2323
"XConstantField",
2424
"XFreeslip",
2525
"XLinear",
@@ -641,7 +641,7 @@ def XLinearInvdistLandTracer(
641641
return values.compute() if is_dask_collection(values) else values
642642

643643

644-
def UXPiecewiseConstantFace(
644+
def UxPiecewiseConstantFace(
645645
particle_positions: dict[str, float | np.ndarray],
646646
grid_positions: dict[_UXGRID_AXES, dict[str, int | float | np.ndarray]],
647647
field: Field,
@@ -656,7 +656,7 @@ def UXPiecewiseConstantFace(
656656
]
657657

658658

659-
def UXPiecewiseLinearNode(
659+
def UxPiecewiseLinearNode(
660660
particle_positions: dict[str, float | np.ndarray],
661661
grid_positions: dict[_UXGRID_AXES, dict[str, int | float | np.ndarray]],
662662
field: Field,

tests/test_field.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from parcels._datasets.structured.generic import T as T_structured
1010
from parcels._datasets.structured.generic import datasets as datasets_structured
1111
from parcels._datasets.unstructured.generic import datasets as datasets_unstructured
12-
from parcels.interpolators import UXPiecewiseConstantFace, UXPiecewiseLinearNode, XLinear
12+
from parcels.interpolators import UxPiecewiseConstantFace, UxPiecewiseLinearNode, XLinear
1313

1414

1515
def test_field_init_param_types():
@@ -197,7 +197,7 @@ def test_field_unstructured_z_linear():
197197

198198
grid = UxGrid(ds.uxgrid, z=ds.coords["nz"], mesh="flat")
199199
# Note that the vertical coordinate is required to be the position of the layer interfaces ("nz"), not the mid-layers ("nz1")
200-
P = Field(name="p", data=ds.p, grid=grid, interp_method=UXPiecewiseConstantFace)
200+
P = Field(name="p", data=ds.p, grid=grid, interp_method=UxPiecewiseConstantFace)
201201

202202
# Test above first cell center - for piecewise constant, should return the depth of the first cell center
203203
assert np.isclose(
@@ -215,7 +215,7 @@ def test_field_unstructured_z_linear():
215215
944.44445801,
216216
)
217217

218-
W = Field(name="W", data=ds.W, grid=grid, interp_method=UXPiecewiseLinearNode)
218+
W = Field(name="W", data=ds.W, grid=grid, interp_method=UxPiecewiseLinearNode)
219219
assert np.isclose(
220220
W.eval(time=ds.time[0].values, z=[10.0], y=[30.0], x=[30.0], applyConversion=False),
221221
10.0,
@@ -235,7 +235,7 @@ def test_field_constant_in_time():
235235
ds = datasets_unstructured["stommel_gyre_delaunay"]
236236
grid = UxGrid(ds.uxgrid, z=ds.coords["nz"], mesh="flat")
237237
# Note that the vertical coordinate is required to be the position of the layer interfaces ("nz"), not the mid-layers ("nz1")
238-
P = Field(name="p", data=ds.p, grid=grid, interp_method=UXPiecewiseConstantFace)
238+
P = Field(name="p", data=ds.p, grid=grid, interp_method=UxPiecewiseConstantFace)
239239

240240
# Assert that the field can be evaluated at any time, and returns the same value
241241
time = np.datetime64("2000-01-01T00:00:00")

tests/test_particleset_execute.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from parcels._datasets.structured.generated import simple_UV_dataset
2323
from parcels._datasets.structured.generic import datasets as datasets_structured
2424
from parcels._datasets.unstructured.generic import datasets as datasets_unstructured
25-
from parcels.interpolators import UXPiecewiseConstantFace, UXPiecewiseLinearNode, XLinear
25+
from parcels.interpolators import UxPiecewiseConstantFace, UxPiecewiseLinearNode, XLinear
2626
from parcels.kernels import AdvectionEE, AdvectionRK2, AdvectionRK4, AdvectionRK4_3D, AdvectionRK45
2727
from tests.common_kernels import DoNothing
2828

@@ -502,19 +502,19 @@ def test_uxstommelgyre_pset_execute():
502502
name="U",
503503
data=ds.U,
504504
grid=grid,
505-
interp_method=UXPiecewiseConstantFace,
505+
interp_method=UxPiecewiseConstantFace,
506506
)
507507
V = Field(
508508
name="V",
509509
data=ds.V,
510510
grid=grid,
511-
interp_method=UXPiecewiseConstantFace,
511+
interp_method=UxPiecewiseConstantFace,
512512
)
513513
P = Field(
514514
name="P",
515515
data=ds.p,
516516
grid=grid,
517-
interp_method=UXPiecewiseConstantFace,
517+
interp_method=UxPiecewiseConstantFace,
518518
)
519519
UV = VectorField(name="UV", U=U, V=V)
520520
fieldset = FieldSet([UV, UV.U, UV.V, P])
@@ -531,8 +531,8 @@ def test_uxstommelgyre_pset_execute():
531531
runtime=np.timedelta64(10, "m"),
532532
dt=np.timedelta64(60, "s"),
533533
)
534-
np.testing.assert_allclose(pset[0].lon, 30.001259, atol=1e-3)
535-
np.testing.assert_allclose(pset[0].lat, 4.9962664, atol=1e-3)
534+
np.testing.assert_allclose(pset[0].lon, 29.997387, atol=1e-3)
535+
np.testing.assert_allclose(pset[0].lat, 4.998546, atol=1e-3)
536536

537537

538538
def test_uxstommelgyre_multiparticle_pset_execute():
@@ -542,25 +542,25 @@ def test_uxstommelgyre_multiparticle_pset_execute():
542542
name="U",
543543
data=ds.U,
544544
grid=grid,
545-
interp_method=UXPiecewiseConstantFace,
545+
interp_method=UxPiecewiseConstantFace,
546546
)
547547
V = Field(
548548
name="V",
549549
data=ds.V,
550550
grid=grid,
551-
interp_method=UXPiecewiseConstantFace,
551+
interp_method=UxPiecewiseConstantFace,
552552
)
553553
W = Field(
554554
name="W",
555555
data=ds.W,
556556
grid=grid,
557-
interp_method=UXPiecewiseLinearNode,
557+
interp_method=UxPiecewiseLinearNode,
558558
)
559559
P = Field(
560560
name="P",
561561
data=ds.p,
562562
grid=grid,
563-
interp_method=UXPiecewiseConstantFace,
563+
interp_method=UxPiecewiseConstantFace,
564564
)
565565
UVW = VectorField(name="UVW", U=U, V=V, W=W)
566566
fieldset = FieldSet([UVW, UVW.U, UVW.V, UVW.W, P])
@@ -587,19 +587,19 @@ def test_uxstommelgyre_pset_execute_output():
587587
name="U",
588588
data=ds.U,
589589
grid=grid,
590-
interp_method=UXPiecewiseConstantFace,
590+
interp_method=UxPiecewiseConstantFace,
591591
)
592592
V = Field(
593593
name="V",
594594
data=ds.V,
595595
grid=grid,
596-
interp_method=UXPiecewiseConstantFace,
596+
interp_method=UxPiecewiseConstantFace,
597597
)
598598
P = Field(
599599
name="P",
600600
data=ds.p,
601601
grid=grid,
602-
interp_method=UXPiecewiseConstantFace,
602+
interp_method=UxPiecewiseConstantFace,
603603
)
604604
UV = VectorField(name="UV", U=U, V=V)
605605
fieldset = FieldSet([UV, UV.U, UV.V, P])

tests/test_uxarray_fieldset.py

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
)
1414
from parcels._datasets.unstructured.generic import datasets as datasets_unstructured
1515
from parcels.interpolators import (
16-
UXPiecewiseConstantFace,
17-
UXPiecewiseLinearNode,
16+
UxPiecewiseConstantFace,
17+
UxPiecewiseLinearNode,
1818
)
1919

2020

@@ -39,13 +39,13 @@ def uv_fesom_channel(ds_fesom_channel) -> VectorField:
3939
name="U",
4040
data=ds_fesom_channel.U,
4141
grid=UxGrid(ds_fesom_channel.uxgrid, z=ds_fesom_channel.coords["nz"], mesh="flat"),
42-
interp_method=UXPiecewiseConstantFace,
42+
interp_method=UxPiecewiseConstantFace,
4343
),
4444
V=Field(
4545
name="V",
4646
data=ds_fesom_channel.V,
4747
grid=UxGrid(ds_fesom_channel.uxgrid, z=ds_fesom_channel.coords["nz"], mesh="flat"),
48-
interp_method=UXPiecewiseConstantFace,
48+
interp_method=UxPiecewiseConstantFace,
4949
),
5050
)
5151
return UV
@@ -59,19 +59,19 @@ def uvw_fesom_channel(ds_fesom_channel) -> VectorField:
5959
name="U",
6060
data=ds_fesom_channel.U,
6161
grid=UxGrid(ds_fesom_channel.uxgrid, z=ds_fesom_channel.coords["nz"], mesh="flat"),
62-
interp_method=UXPiecewiseConstantFace,
62+
interp_method=UxPiecewiseConstantFace,
6363
),
6464
V=Field(
6565
name="V",
6666
data=ds_fesom_channel.V,
6767
grid=UxGrid(ds_fesom_channel.uxgrid, z=ds_fesom_channel.coords["nz"], mesh="flat"),
68-
interp_method=UXPiecewiseConstantFace,
68+
interp_method=UxPiecewiseConstantFace,
6969
),
7070
W=Field(
7171
name="W",
7272
data=ds_fesom_channel.W,
7373
grid=UxGrid(ds_fesom_channel.uxgrid, z=ds_fesom_channel.coords["nz"], mesh="flat"),
74-
interp_method=UXPiecewiseLinearNode,
74+
interp_method=UxPiecewiseLinearNode,
7575
),
7676
)
7777
return UVW
@@ -101,8 +101,8 @@ def test_set_interp_methods(ds_fesom_channel, uv_fesom_channel):
101101
assert (fieldset.V.data == ds_fesom_channel.V).all()
102102

103103
# Set the interpolation method for each field
104-
fieldset.U.interp_method = UXPiecewiseConstantFace
105-
fieldset.V.interp_method = UXPiecewiseConstantFace
104+
fieldset.U.interp_method = UxPiecewiseConstantFace
105+
fieldset.V.interp_method = UxPiecewiseConstantFace
106106

107107

108108
def test_fesom2_square_delaunay_uniform_z_coordinate_eval():
@@ -115,17 +115,37 @@ def test_fesom2_square_delaunay_uniform_z_coordinate_eval():
115115
grid = UxGrid(ds.uxgrid, z=ds.coords["nz"], mesh="flat")
116116
UVW = VectorField(
117117
name="UVW",
118-
U=Field(name="U", data=ds.U, grid=grid, interp_method=UXPiecewiseConstantFace),
119-
V=Field(name="V", data=ds.V, grid=grid, interp_method=UXPiecewiseConstantFace),
120-
W=Field(name="W", data=ds.W, grid=grid, interp_method=UXPiecewiseLinearNode),
118+
U=Field(name="U", data=ds.U, grid=grid, interp_method=UxPiecewiseConstantFace),
119+
V=Field(name="V", data=ds.V, grid=grid, interp_method=UxPiecewiseConstantFace),
120+
W=Field(name="W", data=ds.W, grid=grid, interp_method=UxPiecewiseLinearNode),
121121
)
122-
P = Field(name="p", data=ds.p, grid=grid, interp_method=UXPiecewiseLinearNode)
122+
P = Field(name="p", data=ds.p, grid=grid, interp_method=UxPiecewiseLinearNode)
123123
fieldset = FieldSet([UVW, P, UVW.U, UVW.V, UVW.W])
124124

125-
assert fieldset.U.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False) == 1.0
126-
assert fieldset.V.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False) == 1.0
127-
assert fieldset.W.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False) == 0.0
128-
assert fieldset.p.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False) == 1.0
125+
assert np.isclose(
126+
fieldset.U.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
127+
1.0,
128+
rtol=1e-3,
129+
atol=1e-6,
130+
)
131+
assert np.isclose(
132+
fieldset.V.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
133+
1.0,
134+
rtol=1e-3,
135+
atol=1e-6,
136+
)
137+
assert np.isclose(
138+
fieldset.W.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
139+
0.0,
140+
rtol=1e-3,
141+
atol=1e-6,
142+
)
143+
assert np.isclose(
144+
fieldset.p.eval(time=ds.time[0].values, z=[1.0], y=[30.0], x=[30.0], applyConversion=False),
145+
1.0,
146+
rtol=1e-3,
147+
atol=1e-6,
148+
)
129149

130150

131151
def test_fesom2_square_delaunay_antimeridian_eval():
@@ -139,7 +159,7 @@ def test_fesom2_square_delaunay_antimeridian_eval():
139159
name="p",
140160
data=ds.p,
141161
grid=UxGrid(ds.uxgrid, z=ds.coords["nz"], mesh="spherical"),
142-
interp_method=UXPiecewiseLinearNode,
162+
interp_method=UxPiecewiseLinearNode,
143163
)
144164
fieldset = FieldSet([P])
145165

0 commit comments

Comments
 (0)