Skip to content

check_cell_boundaries_interval: polar-cap dual cells on unstructured grids #1293

@JanStreffing

Description

@JanStreffing

Follow-up to #1292 (vectorized / wrap-aware check_cell_boundaries_interval). Looking for upstream guidance on how strictly to implement §7.1 Rec 1/2 ("points should lie within, or on the boundary, of the cells") for polar-cap dual cells on unstructured grids.

Symptom

On an FESOM2 CORE2 mesh (126 858 nodes, lat_bnds(nod2, 18), lon_bnds(nod2, 18)), after the wrap-aware fix in #1292 there is still one residual flag:

§7.1 Cell Boundaries
* 1 point(s) specified by the coordinate variable 'lat' lie outside the bounding box of the associated boundary variable 'lat_bnds'

The offending node (#41056) is the closest mesh node to the North Pole: lat = 89.958°, lon = -24.1°. Its 12 unique explicit lat_bnds top out at 89.934° — i.e. 0.024° below the node. Its 12 unique lon_bnds span the entire globe (-146.6° → +177.8°): the dual cell is a polar cap.

On the sphere this cell geometrically contains the node (the pole itself belongs to the cap, and the node sits just below the pole). In flat (lat, lon) bounding-box space the node looks "above" the cell because the pole itself is not an explicit vertex of the dual cell — which is common to every unstructured mesh I know of (ICON, MPAS, FESOM, …), since the dual-cell vertices are the circumcenters of the incident triangles, all of which are south of the pole-nearest node.

Question

What semantics should check_cell_boundaries_interval implement for this case?

Option A — accept as known limitation, flag as-is (status quo after #1292)

Pros: strict CF reading, bounding box is a transparent test.
Cons: every well-formed unstructured mesh from every model will carry one spurious §7.1 flag for each pole-nearest node. Reviewers see noise, not signal.

Option B — polar-cap heuristic

Detect polar-cap cells via lon_span > 300° AND |center_lat| > 80°, extend the lat bounding box to ±90° for those cells before the comparison. Requires the lat check to also see the sibling lon_bnds.

Pros: clears the one-node-per-mesh noise; narrow enough that a mid-lat cell with lon_bnds spanning nearly the whole globe (implausible under wrap-aware unwrap) wouldn't satisfy both conditions.
Cons: thresholds are empirical. A pole-adjacent cell in a poorly-made structured grid with genuinely broken bounds could escape detection (bug: if someone writes lat=89.9° but cell bounds legitimately end at 89.0°, that's a real 0.9° error we'd miss if someone also contrived a polar lon span — unlikely combo).

Option C — switch the whole test to point-in-spherical-polygon

A Haversine / winding-number test handles pole, antimeridian and arbitrary polygons uniformly with no heuristics. ~5 extra lines with numpy, still O(N · V). Benchmark: ~1 s on 126 858 cells vs ~0.1 s for the current bounding box.

Pros: academically correct; no thresholds.
Cons: more code, slightly slower, and CF §7.1 doesn't explicitly prescribe this — the recommendation text just says "within or on the boundary", which leaves the implementation open.

Option D — demote the polar-cap flag to a warning

Keep the bounding-box test but down-grade from MEDIUM to LOW (or add a separate informational result) when (|center| > ε_pole) AND (lon_span > θ_wrap). Preserves visibility without blocking.

Ask

Which of A / B / C / D (or something else) do the maintainers prefer? I'm happy to implement the agreed direction as a follow-up PR, with tests covering:

  • pole-nearest node of a real unstructured mesh
  • structured grid with genuine lat-bounds error at lat > 89° (must still be flagged under B/D)
  • south-pole mirror
  • lon_bnds broken or missing (safe fallback to the plain bounding box)

Reference data (FESOM2 sample, world-readable on DKRZ Levante): /work/ab0246/a270092/share/cc-plugin-wcrp-issue-33/sample_unstructured.nc — same file used in #1292 benchmarks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions