Repository navigation
Vertical Coordinate Interpolators - #3037
wyatt-fluidnumerics wants to merge 18 commits into
Conversation
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
…s-code/Parcels into vertical-coordinate-interpolators
for more information, see https://pre-commit.ci
erikvansebille
left a comment
There was a problem hiding this comment.
Looks good! Some comments below
| for field in self.fieldset.fields.values(): | ||
| has_time_varying_z = (isinstance(field.grid, UxGrid) and field.grid.z.ndim == 3) or ( | ||
| isinstance(field.grid, XGrid) and "Z" in field.grid.axes and field.grid._ds["depth"].ndim == 4 | ||
| isinstance(field.grid, XGrid) and field.grid.depth.ndim == 4 |
There was a problem hiding this comment.
Why was this change necessary? Why did the old code not work? Or was it simply superfluous because a 4-D grid always has a Z dimension?
| warnings.warn( | ||
| "Time-varying (3D) z coordinates are experimental and may cause significant memory overhead that " | ||
| f"leads to OOM errors. This z coordinate has sizes {dict(z.sizes)} ({z.nbytes / 1e9:.3g} GB). " | ||
| f"Assumptions: z has dims {z.dims}, i.e. it is defined at the layer interfaces ('zf') on the mesh " | ||
| "nodes or faces, and is strictly increasing along 'zf'; each particle's z column is interpolated with " | ||
| "the grid's `z_interp_method` (by default barycentrically from its face's nodes for node z, or " | ||
| "constant over its face for face z), and linearly in time between z snapshots.", | ||
| FieldSetWarning, | ||
| stacklevel=5, |
There was a problem hiding this comment.
But I thought this warning may not be necessary anymore because the 4D depth fields can use the ChunkCachedArray or WindowedArray backend?
| f"A 3D z must have dims ('time', 'zf', 'n_node') or ('time', 'zf', 'n_face'), got {z.dims}" | ||
| ) | ||
|
|
||
| if z.dims[-1] == "n_node": |
There was a problem hiding this comment.
Do we also need to check that the dimension order of z is such that the last is n_node or n_face? Or is that done somewhere else?
| isinstance(value, XVerticalCoordinateConstant) | ||
| and get_dim_position(self.sgrid_metadata, depth_dims[-1]) != "face" | ||
| ): | ||
| raise ValueError(f"XVerticalCoordinateConstant needs depth on the cell centres, got dims {depth_dims}") |
There was a problem hiding this comment.
Can we be more explicit here what the cell centres are called in this dataset? Would we know that?
|
|
||
| if "Z" in self.axes and ds.depth.ndim == 4: | ||
| zi, zeta = self._search_time_varying_depth(z, yi, xi, ti, tau) | ||
| zi, zeta = self._search_time_varying_depth(z, yi, eta, xi, xsi, ti, tau) |
There was a problem hiding this comment.
I only realise this now, but would it not make more sense to use the order ti, tau, yi, eta, xi, xsi for the position here - in order to harmonise dimensionality throughout the codebase?
There was a problem hiding this comment.
I can't comment on individual lines, so below some comments on the notebook
- rename the title to use lowercase `time varying'?
- use the hide-input tag for the dataset creation cell, so that users don't get overwhelmed by it? It's not that relevant for most users how the dataset is created?
- Type-o in coordiante
- Is it expected that the particles don't move horizontally on the right panel of the last animation?
| self._ds = model_data | ||
| self.mesh = mesh | ||
| self._spatialhash = None | ||
| self._z_interp_method = None |
There was a problem hiding this comment.
The _interp_method is set on the Field. Why is the _z_interp_method then set on the grid? Can different Fields on the same grid not have different interpolation methods?
| self._ds = model_data | ||
| self.mesh = mesh | ||
| self._spatialhash = None | ||
| self._z_interp_method = None |
There was a problem hiding this comment.
Should we now rename interp_method to h_interp_method or something similar to make explicit that these are horizontal interpolation methods? Or should/could that be done in another pR?
| def __repr__(self): | ||
| return f"{self.__class__.__name__}(...)" |
There was a problem hiding this comment.
Should we move this interpolators.__repr__ to the repr_utils.py file, so they are all in one place
| upper_half = bcoord >= 0.5 | ||
| lower_face = np.where(upper_half, face_index, face_index - 1) | ||
| centre_bcoord = np.where(upper_half, bcoord - 0.5, bcoord + 0.5) | ||
| below_first_centre = lower_face < 0 | ||
| return np.where(below_first_centre, 0, lower_face), np.where(below_first_centre, 0.0, centre_bcoord) |
There was a problem hiding this comment.
Should we use the offsets here? Because some models are indexed by the upper-right corner and others by the lower-left corner. Or is that not relevant here?
Description
This PR addresses the need for more complex interpolation methods for time varying z dimensions, while maintaining flexibility for users and alignment with the current interpolator API.
The key change is the introduction of a
VerticalCoordinateInterpolatorabstract class that sits at the same level as the Vector and Scalar interpolator classes. At its core, theVerticalCoordinateInterpolatorclass enables any existing or user madeScalarInterpolatorto be used to interpolate the vertical coordinate. This is done through its base method_columns_from_scalar, which takes in a scalar interpolator and uses it to run a horizontal interpolation on every vertical interface at each particle's horizontal position. These interfaces are then used to reconstruct the vertical columns at each particle position, which can be used for the vertical grid search.For time varying z coordinate datasets, a vertical coordinate interpolator is set during Fieldset creation, and can be overwritten by the user.
This PR also adjusts all UxInterpolators to perform linear in time interpolation for data that falls between two model snapshots. This is required to avoid large temporal jump discontinuities in time varying vertical grids.
Checklist
mainfor normal development,v3-supportfor v3 support)AI Disclosure