Skip to content

Address the review of the example notebooks (into #26) - #34

Merged
hdrake merged 4 commits into
examples-06-tripolar-foldfrom
examples-review-fixes
Jul 17, 2026
Merged

hdrake merged 4 commits into
examples-06-tripolar-foldfrom
examples-review-fixes

Conversation

@hdrake

@hdrake hdrake commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Targets examples-06-tripolar-fold (the branch behind #26), so the diff is only the review fixes on top of the five examples.

Acts on an independent review of the examples as of #26, against the stated goal of demonstrating xgcm's functionality for common ocean-modelling workflows. All five notebooks were re-executed end-to-end against the xgcm#711 build — 0 cell errors. The 1.1 GB ECCOv4r4 download was pulled and 01 genuinely re-run.

The one correctness bug

02_mitgcm was shipping wrong physics. The boundary→padding migration in 20bb1a1 turned the deliberate non-periodic Y from 534d73a ("make Y non-periodic (extend)") into padding={'Y': 'periodic'}. The domain is global in longitude but spans only 78°S–78°N, so periodic Y wraps the Antarctic edge (YG=−80) onto the Arctic row (YC=+78).

Measured impact — narrower than it looks, which is why it survived review:

rows differing (periodic vs extend) [39] of 40 — the northernmost only
row 39 max|div|, periodic (wrong) 2.553e-06
row 39 max|div|, extend (correct) 3.289e-06
typical interior max|div| 6.233e-06

Only the north edge is padded by diff/interp on a left-edge Y axis, so exactly one row moves, by the same order as the interior. psi is unaffected — it passes an explicit padding='fill' to cumsum.

Plotting into modules

Per the brief: modules for plotting routines and nothing else. Every xgcm call stays inline in the notebook where a reader can see it.

  • llc_plots.py — LLCMapper out of 01 (56 lines of pyresample/cartopy against ~4 lines of xgcm, reused 6×). Drops a dead else branch that built a stray unused axes; documents the antimeridian split and the radius_of_influence/dx constants.
  • tripolar_plots.py — the ~250-line matplotlib layer out of 05. The notebook now computes every halo with xgcm up front; the module only draws.

A cheap first plot everywhere

  • 02 — ds.THETA[0, 0].plot() before any xgcm; the grid is a plain lon/lat rectangle.
  • 03 — plots in nominal index coordinates first, then explains why they mislead. The top row's nominal y reads as a single 89.95, while the true 2-D lat along that row runs 64.11°–89.94°: it is the fold seam, flattened into a straight line. That motivates the 2-D lon/lat map that follows.
  • 05 — a raw index-space overview before any fold machinery.
  • 01 — already had one (per-tile in index space); reframed as the honest layout view rather than a failed map.

Clarity

  • 02: prose claimed xgcm found "five axes … 1RHO"; it reports four (X/Y/Z/T) and the dataset has no 1RHO dim. Rewritten, along with the pre-rename "periodic" framing. Four empty trailing cells removed.
  • 01: the 13-tile face_connections dict now explains how to read a (tile, axis, reverse) triple and links to the Face Connections Spec.
  • 05: dropped three dead padding= kwargs — see below.
  • 05: explained autoparse_metadata=False; framed the xgcm.padding.pad use explicitly as an under-the-hood aside.
  • 04: dropped the cell printing a dev version hash (0.9.1.dev59+g...) into the rendered docs.
  • environment.yml: advertised the pre-rename boundary={"Y": {"fold": ...}}.
  • Readme.md: documents the module convention and the cheap-plot-first guidance.

On 05's padding= kwargs — a correction worth recording

An initial review claimed 05's interp/diff never cross the fold, because padding="extend"/"fill" at the call site would override the grid's fold via defaults | user_kwargs (grid.py:311). That is false, and so was a follow-up claim that the kwarg is silently ignored on a fold axis.

What is actually true, per padding.py:53–55 and test_fold_south_edge_respects_per_call_padding:

  • north edge → always folds (topology; mirrors how _pad_face_connections dispatches on grid state and likewise cannot be overridden);
  • south edge → honours the per-call padding.

The kwargs in 05 are no-ops for a mundane reason: these operators pad only the north edge (padding_width=(0, 1)), so the south mode is never consulted. Verified behaviour-preserving before removal — fold vs extend gives max|diff| = 0 on real Oceananigans data for both interp and diff. 05's thesis stands and its outputs are unchanged. The kwargs go because they misled a careful reader into a false bug report.

Not done here (deliberately)

  • Grid ufuncs still have zero example coverage — no notebook calls apply_as_grid_ufunc/as_grid_ufunc, despite two dedicated tutorial pages. Also uncovered: average, interp_like, get_metric/set_metrics, interp_2d_vector, Grid.min/max. Worth a follow-up.
  • Nav reordering (suggested 02 → 04 → 03 → 01 → 05, renumbering files to match) — touches mkdocs.yml in the main repo, so it belongs with #711.
  • Coverage is concentrated: 04 is the only notebook using metrics/derivative/integrate/cumint/transform.

Follow-up for xgcm#711

Passing padding= on a fold axis quietly does nothing whenever only the north edge is padded — the common case. It misled two consecutive readers. xgcm should warn in that situation; it would have caught these dead kwargs immediately.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C2MVD2DtZLieu1zXgEoXbv

…lotting, add cheap first plots

Acts on an independent review of the five examples as of #26. Every notebook
changed here was re-executed end-to-end against the xgcm#711 (north-fold) build,
0 cell errors.

Correctness
- 02_mitgcm: the boundary->padding migration mistranslated a deliberate
  non-periodic Y into `padding={'Y': 'periodic'}`, wrapping the Antarctic edge
  (YG=-80) onto the Arctic row (YC=+78). The domain is global in longitude but
  spans only 78S-78N, so Y must be 'extend'. Measured: exactly one row (39, the
  northernmost) changes, max|div| 2.55e-6 -> 3.29e-6 against an interior scale of
  6.23e-6 -- the same order as the interior, which is why it survived review.
  psi is unaffected (it passes an explicit padding='fill' to cumsum).

Plotting moved into modules (plotting only -- every xgcm call stays inline)
- llc_plots.py: LLCMapper out of 01 (56 lines of pyresample/cartopy vs ~4 lines
  of xgcm). Drops a dead `else` branch that built a stray unused axes, and
  documents the antimeridian split and the resampling constants.
- tripolar_plots.py: the ~250-line matplotlib layer out of 05. The notebook now
  computes every halo with xgcm up front and the module only draws.

A cheap first plot in each notebook
- 02: `ds.THETA[0, 0].plot()` before any xgcm -- the grid is a plain lon/lat
  rectangle, so no projection is needed.
- 03: plots in nominal index coordinates first, then explains why they mislead.
  The top row's nominal y reads as a single 89.95, while the true 2-D lat along
  that row runs 64.11-89.94 -- it is the fold seam, flattened into a straight
  line. Motivates the 2-D lon/lat map that follows.
- 05: a raw index-space overview of each model before any fold machinery.
- 01 already had one (per-tile in index space); reframed so it reads as the
  honest layout view rather than a failed map.

Clarity
- 02: prose said xgcm found "five axes ... 1RHO"; it reports four (X/Y/Z/T) and
  the dataset has no 1RHO dim. Rewritten, along with the pre-rename "periodic"
  framing. Four empty trailing cells removed.
- 01: the 13-tile face_connections dict now says how to read a
  (tile, axis, reverse) triple and links to the Face Connections Spec.
- 05: dropped three dead `padding=` kwargs. On a fold axis the north edge always
  folds (topology) and only the south edge honours a per-call padding; these
  operators pad only the north edge, so the kwargs were exact no-ops. Verified
  behaviour-preserving: fold vs extend gives max|diff| = 0 on real data. They
  misled a reviewer into a false bug report, which is reason enough to go.
- 05: explained autoparse_metadata=False, and framed the xgcm.padding.pad use
  explicitly as an under-the-hood aside.
- 04: dropped the cell printing a dev version hash into the rendered docs.

environment.yml advertised the pre-rename `boundary={"Y": {"fold": ...}}`.
Readme documents the module convention and the cheap-plot-first guidance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2MVD2DtZLieu1zXgEoXbv
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

Renumbering (simplest -> most complex)
- 02_mitgcm        -> 01_mitgcm          simplest grid, plain .plot()
- 04_nemo_idealized -> 02_nemo_idealized  closed basin; adds metrics/derivative/
                                          integrate/cumint/transform
- 03_MOM6          -> 03_MOM6            first real curvilinear grid (unchanged)
- 01_eccov4        -> 04_eccov4          hardest topology (13-tile LLC), 1.1 GB
- 05_tripolar_fold -> 05_tripolar_fold   most specialized (unchanged)

Filenames now match the nav rather than the order the examples happened to be
written in. Cross-refs in Readme.md and llc_plots.py updated; the mkdocs nav and
the docs/ links live in the xgcm repo and land with #711.

05 restructured to start where 03 leaves off
03 computes GFDL-CM4's relative vorticity and then admits it "just looks away
from the Arctic seam". 05 now opens by finishing that sentence: same dataset,
same grid, same diff, same Robinson/RdBu_r figure -- the only change is
padding={"Y": {"fold": "corner"}} instead of {"Y": "extend"}. Load, grid,
compute, plot; the machinery comes after.

Vorticity rather than surface speed, because speed cannot show this: MOM6 puts vo
on the north face, so interpolating it to centres pads the SOUTH edge and the
fold never enters -- fold vs extend is bit-identical (max|diff| = 0). diff(uo,'Y')
goes centre -> north face and does pad north, so vorticity changes at the seam
(max|diff| = 9.3e-02, and 95th-pct seam |zeta| roughly doubles, 2.0e-2 -> 4.0e-2).
It is also the diagnostic 03 explicitly punts on.

The opening figure is 03's map redrawn, and deliberately shows no visible change:
the correction is one row of 1080, a hairline near the pole an order of magnitude
below the coastal vorticity that sets the colour scale. That is what motivates
the index-space zoom-ins that follow.

Then: why "corner" is not a magic word (the four pivots, linked to the topology
docs), the same fold on three models with two pivots, and only then the
under-the-hood pad() asides and the seam figures.

03_MOM6: dropped the per-call padding="fill" from the vorticity, which overrode
the grid's periodic X and put a spurious seam at the dateline. X now wraps and Y
uses the grid's 'extend' -- so 05 recomputes exactly 03's field and the two agree.
Prose reframed to hand off to 05.

All notebooks re-executed against the xgcm#711 build, 0 cell errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2MVD2DtZLieu1zXgEoXbv
hdrake and others added 2 commits July 15, 2026 14:23
The prose added in this PR was overwritten and dramatic. Rewritten across all five
notebooks, the two plotting modules and the Readme: fewer rhetorical set-pieces
("this notebook stops looking away", "that is the honest situation", "the mirror
symmetry is the tell", "that 'extend' is a lie"), less bolding, fewer em-dashes,
and plain declarative sentences instead.

05 specifically
- Opens with what a tripolar grid is ("Many ocean models, like MOM6, NEMO and
  Oceananigans, have tripolar grids: ...") rather than a build-up to a reveal.
- Refers to "the MOM6 example earlier in these docs", not "03_MOM6" -- readers see
  page titles in the rendered docs, not notebook filenames. Same for the summary
  and the module docstrings.
- The "picking up where 03_MOM6 left off" section is cut to the two things a
  reader needs: same velocities and grid as before, and why each component is
  passed as a {"X": vo} dict.

Also drops an unverified claim from the pivot table: the "center"/"t" row said
"some MOM5 configurations", which is not something the topology docs state and I
have not confirmed. The table now lists only what each pivot means, and the prose
names the two pivots this notebook actually exercises ("corner" for MOM6/NEMO,
"u" for Oceananigans).

Prose-only, except the pivot table and two figure titles. 05 re-executed (its
suptitle lives in the module); all five notebooks show 0 cell errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2MVD2DtZLieu1zXgEoXbv
The text said "the red line marks the top edge of the array" but there was no red
line. index_space_overview drew ax.axhline(ny - 0.5), and imshow's default extent
makes the y range exactly [-0.5, ny - 0.5], so the line landed on the top of the
axes and was hidden under the frame.

The folded edge is the top of the array, so there is nothing to draw a line at:
mark the frame instead. The top spine is now red and 3pt, which is visible in all
three panels. Text and suptitle updated to match ("the red edge along the top of
each panel").

05 re-executed; 0 cell errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C2MVD2DtZLieu1zXgEoXbv
@hdrake
hdrake merged commit a327003 into examples-06-tripolar-fold Jul 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant