Repository navigation
Conversation
When constructing fields from SGRID conventions in StructuredModelData, default tracer fields defined at cell centers (face dimensions) on staggered C-grids to CGrid_Tracer rather than hardcoding all scalar fields to XLinear.
|
Thanks for this PR< but you have not filled out the AI statement in our PR template. As per our AI-usage policy, that is very important for us |
Thanks, I have updated the PR description with the AI disclosure. |
|
Updated with root-cause fix and synced with latest main:
|
There was a problem hiding this comment.
Hello, thanks for this PR! See comments.
Broadly I think some of the testing introduced in this PR is out of scope since it focuses more on the correctness of the CGrid_Tracer interpolator rather than the interpolator selection.
It also broke CI but that seemed to be resolved as I finalized this review.
| model.grid.sgrid_metadata, | ||
| model.vector_field_components, | ||
| ) | ||
| if interp_cls is not None: |
There was a problem hiding this comment.
_select_xinterpolator never returns None
There was a problem hiding this comment.
Fixed, directly instantiated without the redundant None check.
| return XLinear | ||
|
|
||
| # Velocity components (U, V, W or components of vector fields) are not tracers | ||
| velocity_names = {"U", "V", "W"} |
There was a problem hiding this comment.
I don't think this is needed. CGrid horizontal velocities will be caught in the second check above since they are not on the face, and the vertical velocity gets caught below.
There was a problem hiding this comment.
Agreed, removed the velocity names check.
| @pytest.mark.parametrize( | ||
| "dataset_name", | ||
| [ | ||
| "ds_2d_left", |
There was a problem hiding this comment.
These datasets all store their velocities/tracers on CGrids, so they all check the exact same logic, only one should be used.
There was a problem hiding this comment.
Updated to test only \ds_2d_left.
| assert isinstance(fieldset.UV.interp_method, CGrid_Velocity) | ||
|
|
||
|
|
||
| def test_cgrid_tracer_piecewise_constant_evaluation(): |
There was a problem hiding this comment.
I think this is out of scope for this PR.
| ) | ||
|
|
||
| # Unstaggered grid metadata (face == node) -> XLinear | ||
| from parcels._sgrid.core import FaceNodePadding, Padding, SGrid2DMetadata |
There was a problem hiding this comment.
This import should be moved to the top of the file.
| assert _select_xinterpolator(da_unstaggered, unstaggered_metadata) is XLinear | ||
|
|
||
|
|
||
| def test_cgrid_tracer_particle_sampling(): |
| over the grid cell using CGrid_Tracer. Non-tracer fields, velocity components, | ||
| node-located variables, or fields on unstaggered grids default to XLinear. | ||
| """ | ||
| fnp_x, fnp_y = sgrid_metadata.face_dimensions |
There was a problem hiding this comment.
I am confused on what this is checking for, my understanding is that if a single dimension is assigned to both the faces and nodes it would break assertions during Fieldset creation, so I believe this is unneeded.
|
Thanks for the review and guidance @wyatt-fluidnumerics! I have addressed all the feedback in commit
|
wyatt-fluidnumerics
left a comment
There was a problem hiding this comment.
Looks good! Just one question regarding testing. I'll approve once its resolved.
| ) | ||
|
|
||
|
|
||
| def test_agrid_all_fields_default_to_xlinear(): |
There was a problem hiding this comment.
Is this not already tested by the assert above at line 782?
There was a problem hiding this comment.
Good catch, you're right. I've removed est_agrid_all_fields_default_to_xlinear as est_select_xinterpolator_edge_cases already verifies that A-grid scalar fields evaluate to XLinear.
| Following Delandmeter and Van Sebille (2019), tracer fields on an Arakawa C-grid | ||
| are located at cell centers (face dimensions) and should be interpolated constant | ||
| over the grid cell using CGrid_Tracer. Non-tracer fields, node-located variables, | ||
| or fields on unstaggered/A-grids default to XLinear. | ||
| """ | ||
| # If the model grid is an A-grid (velocity components defined on identical dimensions), | ||
| # all fields are collocated; default to XLinear. | ||
| if dataset is not None and vector_fields: | ||
| for comps in vector_fields.values(): | ||
| if len(comps) >= 2 and comps[0] in dataset and comps[1] in dataset: | ||
| if _is_agrid(dataset, u=comps[0], v=comps[1]): | ||
| return XLinear | ||
|
|
||
| # Tracers on a C-grid must be defined at the face (cell center) dimensions | ||
| fnp_x, fnp_y = sgrid_metadata.face_dimensions | ||
| face_dims = {fnp_x.face, fnp_y.face} | ||
| if not face_dims.issubset(da.dims): | ||
| return XLinear | ||
|
|
||
| # If vertical dimensions are present and staggered, interface-defined variables | ||
| # (e.g. vertical velocity W defined on node) are not cell-centered tracers | ||
| if sgrid_metadata.vertical_dimensions: | ||
| fnp_z = sgrid_metadata.vertical_dimensions[0] | ||
| if fnp_z.face != fnp_z.node and fnp_z.node in da.dims: | ||
| return XLinear | ||
|
|
||
| return CGrid_Tracer |
There was a problem hiding this comment.
Can this not be made much simpler by only catching the case where we expect a CGrid_Tracer (a non-vector-field that resides on a c-grid) and in all other cases return XLinear? This seems overly-complicated? Or am I missing something?
There was a problem hiding this comment.
Done! Simplified _select_xinterpolator to positively check for the C-grid tracer condition (cell-centered on a C-grid) and default to XLinear in all other cases, removing the extra dataset/vector_fields inspection from the function.
|
Thanks @erikvansebille and @wyatt-fluidnumerics for the feedback! I've simplified the interpolator selection logic and addressed the redundant test coverage. Pushed the follow-up changes in 155d6b2 and updated the PR description. Appreciate the guidance! |
| fnp_x, fnp_y = sgrid_metadata.face_dimensions | ||
| is_face_centered = {fnp_x.face, fnp_y.face}.issubset(da.dims) | ||
| is_vertical_interface = False | ||
| if sgrid_metadata.vertical_dimensions: | ||
| fnp_z = sgrid_metadata.vertical_dimensions[0] | ||
| is_vertical_interface = fnp_z.node in da.dims and fnp_z.face != fnp_z.node |
There was a problem hiding this comment.
Thanks for simplifying, @v3ravani - but I still think this logic is too complex. Why the is_vertical_interface check? What does that do? Which cases does it remove? Can you perhaps link to some sketches or background material that show how this check exactly works?
There was a problem hiding this comment.
The is_vertical_interface check was originally intended for 3D Lorenz-staggered C-grids (e.g., Fig. 1 in Delandmeter & Van Sebille 2019, https://doi.org/10.5194/gmd-12-3571-2019), where vertical velocity w and vertical mixing reside at cell interfaces in the vertical while lying at cell centers laterally (XC, YC). It guarded against such interface variables defaulting to CGrid_Tracer.
However, you are completely right that this is unnecessary complexity here: velocities are handled via VectorField (CGrid_Velocity), and SGRID metadata specifies 2D lateral topology where {face_x, face_y} cleanly identifies cell-centered tracers.
I have removed the vertical interface logic entirely in commit 7d893552. _select_xinterpolator now simply checks {fnp_x.face, fnp_y.face}.issubset(da.dims) on C-grids and defaults all other fields to XLinear.
| skip_field_data_validation=skip_field_data_validation, | ||
| ) | ||
| model._fields = model.construct_fields() | ||
| is_cgrid = any(isinstance(f.interp_method, CGrid_Velocity) for f in model._fields if isinstance(f, VectorField)) |
There was a problem hiding this comment.
I now see that this logic wouldn't work for C-grid FieldSets that have no VectorFields at all.
Can we use the _is_agrid or the new _is_cgrid function from #3059 here - instead of making yet another logic?
There was a problem hiding this comment.
Updated! We now adopt the canonical _is_cgrid logic from PR #3059 and expose StructuredModelData.is_cgrid. When vector fields are defined, it delegates to _is_cgrid(ds, u, v). If no vector fields are specified (e.g. vector_fields={}), it inspects the dataset for default velocity components or SGRID edge-staggered variables to distinguish C-grids from A-grids and B-grids.
Added regression tests verifying that cell-centred tracers on C-grid FieldSets without vector fields default to CGrid_Tracer, while A-grid and B-grid fields default to XLinear.
|
Updated C-grid detection logic in commit
|
Description
Fixes #3049
Problem
In
StructuredModelData.from_sgrid_conventions, all scalar fields defaulted toXLinear, even for tracer fields located at cell centers on staggered C-grids. Following Delandmeter and Van Sebille (2019), C-grid tracers should default toCGrid_Tracerto be interpolated constant over the grid cell.Solution
_select_xinterpolatorinsrc/parcels/_core/model.pyto classify scalar fields on SGRID grids. When the model is a staggered C-grid, fields defined at cell centers (lateral face dimensions) default toCGrid_Tracer; all other fields default toXLinear._is_cgrid(aligning with PR Rename _is_agrid to _is_cgrid #3059) to identify C-grid topology using vector field components, default velocity discovery, or SGRID edge dimensions. This reliably identifies C-grids even when a FieldSet has no VectorFields, while ensuring A-grids and B-grids default toXLinear.XLinear.peninsula_dataset, aligned streamfunctionPdimensions to["YG", "XG"]to match its underlying node coordinateslat(YG) andlon(XG) (consistent withstommel_gyre_dataset).Testing
tests/test_fieldset.py:test_cgrid_tracer_default_interpolator: verifies default interpolators on C-grids.test_select_xinterpolator_edge_cases: verifies dimensional edge cases across 2D/3D tracers, nodes, edges, and A-grid configurations.test_cgrid_tracer_without_vector_fields: verifies that cell-centred tracers on C-grids default toCGrid_Tracereven when no VectorFields are present.test_agrid_and_bgrid_without_vector_fields: verifies that A-grid and B-grid fields default toXLinearwithout VectorFields.tests/test_fieldset.py,tests/test_advection.py,tests/test_interpolation.py,tests/test_field.py,tests/test_xgrid.py).pixi run lintandpixi run typing.Checklist
mainfor normal development,v3-supportfor v3 support)AI Disclosure
_select_xinterpolator, align dataset coordinates, and author regression tests. The code was developed at my direction, and I have read, tested, and validated all changes locally.