-
Notifications
You must be signed in to change notification settings - Fork 168
Add FieldSet.from_sgrid_conventions()
#2432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e5f9989
5963375
2190842
1053d52
1616536
514da6b
5d7c8fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| import xgcm | ||
|
|
||
| from parcels._core.field import Field, VectorField | ||
| from parcels._core.utils import sgrid | ||
| from parcels._core.utils.string import _assert_str_and_python_varname | ||
| from parcels._core.utils.time import get_datetime_type_calendar | ||
| from parcels._core.utils.time import is_compatible as datetime_is_compatible | ||
|
|
@@ -295,6 +296,92 @@ def from_fesom2(ds: ux.UxDataset): | |
|
|
||
| return FieldSet(list(fields.values())) | ||
|
|
||
| def from_sgrid_conventions( | ||
| ds: xr.Dataset, mesh: Mesh | ||
| ): # TODO: Update mesh to be discovered from the dataset metadata | ||
| """Create a FieldSet from a dataset using SGRID convention metadata. | ||
|
|
||
| This is the primary ingestion method in Parcels for structured grid datasets. | ||
|
|
||
| Assumes that U, V, (and optionally W) variables are named 'U', 'V', and 'W' in the dataset. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| ds : xarray.Dataset | ||
| xarray.Dataset with SGRID convention metadata. | ||
| mesh : str | ||
| String indicating the type of mesh coordinates and units used during | ||
| velocity interpolation. Options are "spherical" or "flat". | ||
|
|
||
| Returns | ||
| ------- | ||
| FieldSet | ||
| FieldSet object containing the fields from the dataset that can be used for a Parcels simulation. | ||
|
|
||
| Notes | ||
| ----- | ||
| This method uses the SGRID convention metadata to parse the grid structure | ||
| and create appropriate Fields for a Parcels simulation. The dataset should | ||
| contain a variable with 'cf_role' attribute set to 'grid_topology'. | ||
|
|
||
| See https://sgrid.github.io/ for more information on the SGRID conventions. | ||
| """ | ||
| ds = ds.copy() | ||
|
|
||
| # Ensure time dimension has axis attribute if present | ||
| if "time" in ds.dims and "time" in ds.coords: | ||
| if "axis" not in ds["time"].attrs: | ||
| logger.debug( | ||
| "Dataset contains 'time' dimension but no 'axis' attribute. Setting 'axis' attribute to 'T'." | ||
| ) | ||
| ds["time"].attrs["axis"] = "T" | ||
|
|
||
| # Find time dimension based on axis attribute and rename to `time` | ||
| if (time_dims := ds.cf.axes.get("T")) is not None: | ||
| if len(time_dims) > 1: | ||
| raise ValueError("Multiple time coordinates found in dataset. This is not supported by Parcels.") | ||
| (time_dim,) = time_dims | ||
| if time_dim != "time": | ||
| logger.debug(f"Renaming time axis coordinate from {time_dim} to 'time'.") | ||
| ds = ds.rename({time_dim: "time"}) | ||
|
|
||
| # Parse SGRID metadata and get xgcm kwargs | ||
| _, xgcm_kwargs = sgrid.parse_sgrid(ds) | ||
|
|
||
| # Add time axis to xgcm_kwargs if present | ||
| if "time" in ds.dims: | ||
| if "T" not in xgcm_kwargs["coords"]: | ||
| xgcm_kwargs["coords"]["T"] = {"center": "time"} | ||
|
|
||
| # Create xgcm Grid object | ||
| xgcm_grid = xgcm.Grid(ds, autoparse_metadata=False, **xgcm_kwargs, **_DEFAULT_XGCM_KWARGS) | ||
|
|
||
| # Wrap in XGrid | ||
| grid = XGrid(xgcm_grid, mesh=mesh) | ||
|
|
||
| # Create fields from data variables, skipping grid metadata variables | ||
| # Skip variables that are SGRID metadata (have cf_role='grid_topology') | ||
| skip_vars = set() | ||
| for var in ds.data_vars: | ||
| if ds[var].attrs.get("cf_role") == "grid_topology": | ||
| skip_vars.add(var) | ||
|
|
||
| fields = {} | ||
| if "U" in ds.data_vars and "V" in ds.data_vars: | ||
| fields["U"] = Field("U", ds["U"], grid, XLinear) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But this interpolator may not be the default for nemo; so how would from_nemo overwrite the the interpolator here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be the default no? Since XLinear would be aware of the grid positioning? Otherwise we would need to expose this as a param to the function
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Discussed in person. Outcome: I think we can find where the variables are on the grid via the sgrid conventions, and then use that to choose the interpolator. We'll merge for now, I can rework from_copernicusmarine to use this new code path, and we can explore in future how we can specify this in SGRID and adapt our interpolators |
||
| fields["V"] = Field("V", ds["V"], grid, XLinear) | ||
|
|
||
| if "W" in ds.data_vars: | ||
| fields["W"] = Field("W", ds["W"], grid, XLinear) | ||
| fields["UVW"] = VectorField("UVW", fields["U"], fields["V"], fields["W"]) | ||
| else: | ||
| fields["UV"] = VectorField("UV", fields["U"], fields["V"]) | ||
|
|
||
| for varname in set(ds.data_vars) - set(fields.keys()) - skip_vars: | ||
| fields[varname] = Field(varname, ds[varname], grid, XLinear) | ||
|
|
||
| return FieldSet(list(fields.values())) | ||
|
|
||
|
|
||
| class CalendarError(Exception): # TODO: Move to a parcels errors module | ||
| """Exception raised when the calendar of a field is not compatible with the rest of the Fields. The user should ensure that they only add fields to a FieldSet that have compatible CFtime calendars.""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it necessary to make a copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a shallow copy -
from_fesom2andfrom_copernicusmarinedo them both to avoid changes to the dataset propogating to the original dataset