Skip to content

Commit d918ad9

Browse files
Merge branch 'v4-dev' into adding_circulation_models_datasets
2 parents 52a490d + dc614e9 commit d918ad9

4 files changed

Lines changed: 34 additions & 72 deletions

File tree

parcels/application_kernels/interpolation.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
"""Collection of pre-built interpolation kernels."""
22

3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
37
import numpy as np
48

59
from parcels.field import Field
610

11+
if TYPE_CHECKING:
12+
from parcels.uxgrid import _UXGRID_AXES
13+
714
__all__ = [
815
"UXPiecewiseConstantFace",
916
"UXPiecewiseLinearNode",
@@ -13,8 +20,7 @@
1320
def UXPiecewiseConstantFace(
1421
field: Field,
1522
ti: int,
16-
ei: int,
17-
bcoords: np.ndarray,
23+
position: dict[_UXGRID_AXES, tuple[int, float | np.ndarray]],
1824
tau: np.float32 | np.float64,
1925
t: np.float32 | np.float64,
2026
z: np.float32 | np.float64,
@@ -26,15 +32,13 @@ def UXPiecewiseConstantFace(
2632
This interpolation method is appropriate for fields that are
2733
face registered, such as u,v in FESOM.
2834
"""
29-
zi, fi = field.grid.unravel_index(ei)
30-
return field.data.values[ti, zi, fi]
35+
return field.data.values[ti, position["Z"][0], position["FACE"][0]]
3136

3237

3338
def UXPiecewiseLinearNode(
3439
field: Field,
3540
ti: int,
36-
ei: int,
37-
bcoords: np.ndarray,
41+
position: dict[_UXGRID_AXES, tuple[int, float | np.ndarray]],
3842
tau: np.float32 | np.float64,
3943
t: np.float32 | np.float64,
4044
z: np.float32 | np.float64,
@@ -47,7 +51,8 @@ def UXPiecewiseLinearNode(
4751
velocity W in FESOM2. Effectively, it applies barycentric interpolation in the lateral direction
4852
and piecewise linear interpolation in the vertical direction.
4953
"""
50-
k, fi = field.grid.unravel_index(ei)
54+
k, fi = position["Z"][0], position["FACE"][0]
55+
bcoords = position["FACE"][1]
5156
node_ids = field.grid.uxgrid.face_node_connectivity[fi, :]
5257
# The zi refers to the vertical layer index. The field in this routine are assumed to be defined at the vertical interface levels.
5358
# For interface zi, the interface indices are [zi, zi+1], so we need to use the values at zi and zi+1.

parcels/field.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,7 @@ class Field:
105105
def _interp_template(
106106
self,
107107
ti: int,
108-
ei: int,
109-
bcoords: np.ndarray,
108+
position: dict[str, tuple[int, float | np.ndarray]],
110109
tau: np.float32 | np.float64,
111110
t: np.float32 | np.float64,
112111
z: np.float32 | np.float64,
@@ -316,8 +315,8 @@ def eval(self, time: datetime, z, y, x, particle=None, applyConversion=True):
316315

317316
try:
318317
tau, ti = _search_time_index(self, time)
319-
bcoords, _ei = self.grid.search(z, y, x, ei=_ei)
320-
value = self._interp_method(self, ti, _ei, bcoords, tau, time, z, y, x)
318+
position = self.grid.search(z, y, x, ei=_ei)
319+
value = self._interp_method(self, ti, position, tau, time, z, y, x)
321320

322321
if np.isnan(value):
323322
# Detect Out-of-bounds sampling and raise exception
@@ -445,14 +444,14 @@ def eval(self, time: datetime, z, y, x, particle=None, applyConversion=True):
445444

446445
try:
447446
tau, ti = _search_time_index(self.U, time)
448-
bcoords, _ei = self.grid.search(z, y, x, ei=_ei)
447+
position = self.grid.search(z, y, x, ei=_ei)
449448
if self._vector_interp_method is None:
450-
u = self.U._interp_method(self.U, ti, _ei, bcoords, tau, time, z, y, x)
451-
v = self.V._interp_method(self.V, ti, _ei, bcoords, tau, time, z, y, x)
449+
u = self.U._interp_method(self.U, ti, position, tau, time, z, y, x)
450+
v = self.V._interp_method(self.V, ti, position, tau, time, z, y, x)
452451
if "3D" in self.vector_type:
453-
w = self.W._interp_method(self.W, ti, _ei, bcoords, tau, time, z, y, x)
452+
w = self.W._interp_method(self.W, ti, position, tau, time, z, y, x)
454453
else:
455-
(u, v, w) = self._vector_interp_method(self, ti, _ei, bcoords, time, z, y, x)
454+
(u, v, w) = self._vector_interp_method(self, ti, position, time, z, y, x)
456455

457456
# print(u,v)
458457
if applyConversion:

parcels/uxgrid.py

Lines changed: 12 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
from __future__ import annotations
22

3+
from typing import Literal
4+
35
import numpy as np
46
import uxarray as ux
57
from uxarray.grid.neighbors import _barycentric_coordinates
68

79
from parcels.field import FieldOutOfBoundError # Adjust import as necessary
10+
from parcels.xgrid import _search_1d_array
811

912
from .basegrid import BaseGrid
1013

14+
_UXGRID_AXES = Literal["Z", "FACE"]
15+
1116

1217
class UxGrid(BaseGrid):
1318
"""
@@ -49,9 +54,7 @@ def depth(self):
4954
return np.zeros(1)
5055
return self.z.values
5156

52-
def search(
53-
self, z: float, y: float, x: float, ei: int | None = None, search2D: bool = False
54-
) -> tuple[np.ndarray, int]:
57+
def search(self, z, y, x, ei=None):
5558
tol = 1e-10
5659

5760
def try_face(fid):
@@ -60,21 +63,7 @@ def try_face(fid):
6063
return bcoords, fid
6164
return None, None
6265

63-
def find_vertical_index() -> int:
64-
if search2D:
65-
return 0
66-
else:
67-
nz = self.z.shape[0]
68-
if nz == 1:
69-
return 0
70-
zf = self.z.values
71-
# Return zi such that zf[zi] <= z < zf[zi+1]
72-
zi = np.searchsorted(zf, z, side="right") - 1 # Search assumes that z is positive and increasing with i
73-
if zi < 0 or zi >= nz - 1:
74-
raise FieldOutOfBoundError(z, y, x)
75-
return zi
76-
77-
zi = find_vertical_index() # Find the vertical cell center nearest to z
66+
zi, zeta = _search_1d_array(self.z.values, z)
7867

7968
if ei is not None:
8069
_, fi = self.unravel_index(ei)
@@ -94,7 +83,7 @@ def find_vertical_index() -> int:
9483
if fi == -1:
9584
raise FieldOutOfBoundError(z, y, x)
9685

97-
return bcoords[0], self.ravel_index(zi, fi[0])
86+
return {"Z": (zi, zeta), "FACE": (fi, bcoords[0])}
9887

9988
def _get_barycentric_coordinates(self, y, x, fi):
10089
"""Checks if a point is inside a given face id on a UxGrid."""
@@ -113,40 +102,10 @@ def _get_barycentric_coordinates(self, y, x, fi):
113102
err = abs(np.dot(bcoord, nodes[:, 0]) - coord[0]) + abs(np.dot(bcoord, nodes[:, 1]) - coord[1])
114103
return bcoord, err
115104

116-
def ravel_index(self, zi, fi):
117-
"""
118-
Converts a face index and a vertical index into a single encoded index.
119-
120-
Parameters
121-
----------
122-
zi : int
123-
Vertical index (not used in unstructured grids, but kept for compatibility).
124-
fi : int
125-
Face index.
126-
127-
Returns
128-
-------
129-
int
130-
Encoded index combining the face index and vertical index.
131-
"""
132-
return fi + self.uxgrid.n_face * zi
133-
134-
def unravel_index(self, ei):
135-
"""
136-
Converts a single encoded index back into a vertical index and face index.
105+
def ravel_index(self, axis_indices: dict[_UXGRID_AXES, int]):
106+
return axis_indices["FACE"] + self.uxgrid.n_face * axis_indices["Z"]
137107

138-
Parameters
139-
----------
140-
ei : int
141-
Encoded index to be unraveled.
142-
143-
Returns
144-
-------
145-
zi : int
146-
Vertical index.
147-
fi : int
148-
Face index.
149-
"""
108+
def unravel_index(self, ei) -> dict[_UXGRID_AXES, int]:
150109
zi = ei // self.uxgrid.n_face
151110
fi = ei % self.uxgrid.n_face
152-
return zi, fi
111+
return {"Z": zi, "FACE": fi}

parcels/xgrid.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
_XGCM_AXIS_DIRECTION = Literal["X", "Y", "Z", "T"]
1717
_XGCM_AXIS_POSITION = Literal["center", "left", "right", "inner", "outer"]
18-
_AXIS_DIRECTION = Literal["X", "Y", "Z"]
1918
_XGCM_AXES = Mapping[_XGCM_AXIS_DIRECTION, xgcm.Axis]
2019

2120

@@ -196,13 +195,13 @@ def search(self, z, y, x, ei=None):
196195

197196
raise NotImplementedError("Searching in >2D lon/lat arrays is not implemented yet.")
198197

199-
def ravel_index(self, axis_indices: dict[_AXIS_DIRECTION, int]) -> int:
198+
def ravel_index(self, axis_indices: dict[_XGRID_AXES, int]) -> int:
200199
xi = axis_indices.get("X", 0)
201200
yi = axis_indices.get("Y", 0)
202201
zi = axis_indices.get("Z", 0)
203202
return xi + self.xdim * yi + self.xdim * self.ydim * zi
204203

205-
def unravel_index(self, ei) -> dict[_AXIS_DIRECTION, int]:
204+
def unravel_index(self, ei) -> dict[_XGRID_AXES, int]:
206205
zi = ei // (self.xdim * self.ydim)
207206
ei = ei % (self.xdim * self.ydim)
208207

0 commit comments

Comments
 (0)