-
Notifications
You must be signed in to change notification settings - Fork 168
Description
Terminology1:
- Parcels Grid: The logical grid representation (via XGrid or UxGrid) used by Parcels to determine particle position. In structured grids, this corresponds to the grid of F-points (see Structured grids barycentric coordinates #2037). Time is not part of the grid abstraction.
- Axis (Axes): The spatial dimensions of the Parcels Grid. For structured grids: X, Y, Z; for unstructured: Z and FACE. These relate closely to the underlying data layout (e.g., in the structured case, only a small correction about left/right grid indexing is required to get to the underlying data representation). Indexing a particle involves locating it along each axis.
- Index (Indices): The cell position of a particle along a given axis. Combined indices define the full grid cell a particle occupies.
- Barycentric Coordinates: The coordinates defining a particle’s position within a grid cell. In structured grids, this is a single coordinate per axis (e.g., xsi); in unstructured, this can be a mix (e.g., a single coordinate for zeta, and n-coordinates for an n-gon that the particle resides in - 3 for triangular cells).
- Encoded Indices (ei): A compact, single-integer encoding of a particle’s full grid indices.
Locating a particle’s cell and barycentric coordinates within a Parcels Grid is essential for interpolation. This functionality should reside in the Grid class, exposing a clear API that the Field class can use for interpolation—ensuring separation of concerns, so Field need not manage grid geometry.
In the structured case, Parcels must support grids with varying axis dimensionality:
- 3D:
- Options: 2D lat + 2D lon + 1D depth (curvilinear), or 1D lat + 1D lon + 1D depth (rectilinear)
- Use case: 3D U/V fields
- 2D:
- Options: 2D lat + 2D lon, or 1D lat/lon + 1D depth
- Use case: Wave flumes, windage
- 1D: Single 1D axis (lat/lon/depth)
- 0D: Constant value everywhere (constant field)
Data may exist on any subset of the grid axes (e.g., U may be on X, Y, Z; depth-averaged U on X, Y). During interpolation, the particle’s full position is used, but only relevant axis indices (e.g., xi, yi) are needed to access the data.
Proposed API
>>> x, y, z = ..., ..., ... # float float float
>>> grid = ....search(z, y, x)
>>> grid.search(z, y, x)
# returns:
# {
# axis: (index, bcoords), ...
# }
# if grid is 3D structured
{"X": (xi, xsi), "Y": (yi, eta), "Z": (zi, zeta)}
# if grid is 2D structured (no depth)
{"X": (xi, xsi), "Y": (yi, eta)}
# if grid is 1D structured (depth)
{"Z": (zi, zeta)}
# if grid is unstructured
{"Z": (zi, zeta), "FACE": (fi, bcoords)}
# e.g., {"Z": (2, 0.5), "FACE": (200, np.array(0.2, 0.3, 0.5))}ravel_index(axis_indices)
>>> axis_indices = {"X": xi, "Y": yi, "Z": zi} # 3d structured grid
>>> axis_indices = {"X": xi, "Y": yi} # 2d structured grid
>>> axis_indices = {"Z": zi} # 1d structured grid (depth)
>>> axis_indices = {"Z": zi, "FACE": fi} # unstructured grid
>>> grid.ravel_index(axis_indices)
# returns eiOld API for reference
Old approach
# XGrid class
def unravel_index(self, ei):
"""
Converts a single encoded index back into a Z, Y, and X indices.
Parameters
----------
ei : int
Encoded index to be unraveled.
Returns
-------
zi : int
Vertical index.
yi : int
Y index.
xi : int
X index.
"""
def ravel_index(self, zi, yi, xi):
... # inverse of unravel_index
# UxGrid class
def unravel_index(self, ei):
"""
Converts a single encoded index back into a vertical index and face index.
Parameters
----------
ei : int
Encoded index to be unraveled.
Returns
-------
zi : int
Vertical index.
fi : int
Face index.
"""
def ravel_index(self, zi, fi):
... # inverse of unravel_index
# applies to both XGrid and UxGrid
def search(self, z: float, y: float, x: float, ei=None, search2D: bool = False):
"""
Perform a spatial (and optionally vertical) search to locate the grid element
that contains a given point (x, y, z).
This method delegates to grid-type-specific logic (e.g., structured or unstructured)
to determine the appropriate indices and interpolation coordinates for evaluating a field.
Parameters
----------
z : float
Vertical coordinate of the query point. If `search2D=True`, this may be ignored.
y : float
Latitude or vertical index, depending on grid type and projection.
x : float
Longitude or horizontal index, depending on grid type and projection.
ei : int, optional
A previously computed encoded index (e.g., raveled face or cell index). If provided,
the search will first attempt to validate and reuse it before falling back to
a global or local search strategy.
search2D : bool, default=False
If True, perform only a 2D search (x, y), ignoring the vertical component z.
Returns
-------
bcoords : np.ndarray or tuple
Interpolation weights or barycentric coordinates within the containing cell/face.
The interpretation of `bcoords` depends on the grid type.
ei : int
Encoded index of the identified grid cell or face. This value can be cached for
future lookups to accelerate repeated searches.
Raises
------
FieldOutOfBoundError
Raised when the queried point lies outside the bounds of the grid.
NotImplementedError
Raised if the search method is not implemented for the current grid type.
"""Benefits of this approach
- Clarifies the return values of .search() and .ravel_index() based on the Grid’s Axes.
- For 2D grids, only relevant axes are returned - e.g., no need to return zi, zeta = (0, 0). This simplifies interpolator logic and removes the need for grid dimensionality checks in the interpolator.
- Removes the need to coerce arrays to 4D, superseding Coerce StructuredGrid field data to 4D array of TZYX #2047.
- Unifies the Grid API, stabilizing BaseGrid behavior across subclasses (XGrid, UxGrid) and enabling future extensions.
- Clarifies the link between Axes and barycentric coordinates.
- Provides full particle position on the grid via the new API (including the zeta value).
- Improves maintainability, as all grid logic stays within the Grid class.
Additional considerations
- Caching ei may be unnecessary for 0D or 1D grids, where lookups are already fast.
- Unclear role of search2D parameter. Is this legacy or is it still needed?
- Sigma layer support: Future users must provide F-points for depth (i.e., a 3D depth array based on X and Y via bathymetry). This would require users to broadcast the sigma ratio with their bathymetry before passing to Parcels (which I think is a good direction for us to go in when we get around to it).
- Particles still live in z,y,x space (just this might not be relevant when indexing on some grids). For surface advection we can have z=0
Keen to hear your thoughts (on concept, implementation or terminology) here.
Footnotes
-
Once finalised, I think we would benefit from documenting this terminology (internally and in documentation for those interested in writing interpolators). ↩
Metadata
Metadata
Assignees
Labels
Type
Projects
Status