Skip to content

Add the tripolar bipolar-north-fold example (05_tripolar_fold) - #26

Merged
hdrake merged 13 commits into
xgcm:masterfrom
hdrake:examples-06-tripolar-fold
Jul 21, 2026
Merged

hdrake merged 13 commits into
xgcm:masterfrom
hdrake:examples-06-tripolar-fold

Conversation

@hdrake

@hdrake hdrake commented Jun 27, 2026 •

Copy link
Copy Markdown
Collaborator

Ready to merge. xgcm#711 (the north-fold feature) has merged into xgcm master (cbb6082), so this is no longer gated on a draft. Rebased onto current master (the modernized 01–04 examples), so the diff is the new fold example plus re-executed outputs.

Adds 05_tripolar_fold.ipynb, which demonstrates that xgcm's diff/interp work correctly across the bipolar north fold of a tripolar grid, on three real models.

Changes

  • New 05_tripolar_fold.ipynb — a self-contained, hand-editable notebook (like the other examples). Data:
    • MOM6 (GFDL-CM4) and NEMO (IPSL-CM6A-LR) — surface velocities read anonymously from the CMIP6 Zarr store on Google Cloud (zarr+gcsfs).
    • Oceananigans (ClimaOcean 1°) — a realistic surface snapshot downloaded from Zenodo (10.5281/zenodo.20950679) via pooch (md5-pinned). The URL and md5 are constants in the notebook.
  • environment.yml — adds pooch and pins xgcm>=0.10.1 (the release that carries the north-fold feature); until v0.10.1 is published to conda-forge the file notes installing xgcm from source.
  • Re-executed 01–04 against the same north-fold build so all committed outputs are consistent.

The full fixture-regeneration bundle (ClimaOcean sim script, netCDF converter, pinned Julia Manifest.toml, README) lives in the Zenodo deposit.

Verification

All five notebooks re-executed end-to-end against the north-fold build, 0 cell errors:

  • 01_eccov4 · 02_mitgcm (7 figs) · 03_MOM6 (2 figs) · 04_nemo_idealized (9 figs) · 05_tripolar_fold (8 code cells, 4 figs).

Dependencies / merge order

  • xgcm#711 is merged. The north-fold feature and its xgcm-side docs (grid_topology.md north-fold section, mkdocs.yml nav, changelog) are in xgcm master. This PR is ready to merge alongside it.
  • Not yet in a tagged release. The latest xgcm release is v0.10.0; the feature ships in the next release (v0.10.1). Until v0.10.1 is on conda-forge, environment.yml's pin is satisfied by installing xgcm from source, as noted in the file.
  • Submodule bump to follow. After this PR merges, xgcm's docs xgcm-examples submodule needs a bump to this commit to pick up 05 (the previous bump, xgcm#763, covered 01–04).

🤖 Generated with Claude Code


Polish passes (post-review)

An independent review pass applied behavior-preserving cleanups to 05_tripolar_fold.ipynb (operator outputs unchanged except the noted fix):

  • removed the unused speed_corner helper and the never-taken fold=False branches/parameter;
  • restored zonal periodicity in the divergence demo (∂u/∂x no longer overrides the periodic-X boundary with fill);
  • dropped the inert other_component= from the fold interp/diff calls (the 180° fold sign-flips the lone dict-wrapped component and ignores a partner);
  • re-executed end-to-end (0 cell errors, 4 figures).

A follow-up doc pass fixed rendering/cross-references (markdown/comment/docstring only — no outputs re-run):

  • rewrote the vorticity and divergence equations in \frac{}{} form on single lines so mkdocs-jupyter's markdown parser no longer splits the inline $…$ math (a wrapped continuation line starting with + was being parsed as a bullet list) or drops the \nabla\! backslash;
  • linked the previous MOM6 example from the intro and vorticity sections; removed the notebook filename from a docstring/comment; made the "single keyword" note explicit; and pointed the summary at the MOM6 Analysis Cookbook and COSIMA cosima-recipes (matching 03_MOM6) instead of a dead self-link;
  • dropped the stale "day-365 v2 bump" comment on the Zenodo constants.

@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

Adds 05_tripolar_fold.ipynb, demonstrating that xgcm's diff/interp work
across the bipolar north fold of a tripolar grid on three real models
(MOM6 GFDL-CM4, NEMO IPSL-CM6A-LR via the CMIP6 GCS zarr store, and an
Oceananigans/ClimaOcean snapshot fetched from Zenodo via pooch).

- environment.yml: add pooch and note the fold example needs the
  north-fold feature (install xgcm from the tripolar-north-fold branch)
  until it is released.
- Re-execute 01-04 against the same xgcm#711 build so all committed
  notebook outputs are consistent (0 cell errors across all five).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
@hdrake
hdrake force-pushed the examples-06-tripolar-fold branch from c1c4858 to d57c790 Compare June 30, 2026 14:16
@hdrake hdrake changed the title Add the tripolar bipolar-north-fold example (06_tripolar_fold) Add the tripolar bipolar-north-fold example (05_tripolar_fold) Jun 30, 2026
Independent review pass — behavior-preserving cleanups (verified the
operator outputs are unchanged except the noted X-wrap fix):

- Remove the unused `speed_corner` helper and the never-taken `fold=False`
  branches / `fold` parameter from `speed_centre`/`divergence` (the notebook
  only ever calls them with the fold active).
- `divergence`: drop `boundary="fill"` on the periodic-X `du/dx` diff so the
  zonal seam wraps instead of being filled (it was only invisible because the
  plotted window avoids the x=0 wrap).
- Drop the inert `other_component=` from the fold interp/diff calls (the 180°
  fold sign-flips the lone dict-wrapped component and ignores a partner) and
  tidy the dependency note (deps are already in environment.yml).

Re-executed end-to-end: 0 cell errors, 4 figures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
hdrake added a commit to hdrake/xgcm that referenced this pull request Jun 30, 2026
Independent review pass on the fold boundary:

- padding.py: validate the position strings in an explicit {axis: position}
  fold pivot. Previously a typo (e.g. "centre") was silently coerced to an
  edge pivot, giving a wrong halo with no error; it now raises with the
  allowed positions. Adds a test.
- grid_topology.md: clarify that a vector is folded by passing it as a
  single-key dict (that is what flags the sign flip); the 180° fold needs no
  partner, so `other_component` is optional on the fold path.
- Bump the xgcm-examples submodule to the reviewed example notebook
  (xgcm/xgcm-examples#26): drops dead helper code, restores X-periodicity in
  the divergence demo, re-executed clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
…branch

- 02_mitgcm: the MITgcm domain is bounded in Y, not zonally periodic in the
  meridional direction. Build the grid X-periodic / Y-extend
  (`Grid(ds, periodic=['X'], boundary={'Y': 'extend'})`) instead of
  periodic in both, so diff/interp at the northern and southern Y edges
  extend the edge value rather than wrapping across the domain.
- Re-executed 02-05 against the merged tripolar-north-fold xgcm build
  (the live working tree), refreshing outputs. 0 cell errors across all five.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
hdrake added a commit to hdrake/xgcm that referenced this pull request Jun 30, 2026
…e-exec)

Points at xgcm/xgcm-examples#26 = 534d73a: 02_mitgcm now uses an X-periodic /
Y-extend grid (the meridional direction is bounded, not periodic), and all
example notebooks were re-executed against this branch's xgcm build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
The `diff`/`interp` code spans in the H1 made mkdocs-jupyter mis-render the
page title in the docs nav sidebar. Drop the ": do `diff` and `interp` work
across the seam?" subtitle, leaving a plain "The bipolar north fold (on
tripolar grids)". Markdown-only; cell outputs unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
hdrake added a commit to hdrake/xgcm that referenced this pull request Jun 30, 2026
Points at xgcm/xgcm-examples#26 = e2858a5: drops the backtick'd subtitle from
the notebook's H1 so the docs nav sidebar renders the page title correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKgY8TmjWfwdr8xLTkCME4
hdrake and others added 2 commits July 13, 2026 09:44
…aster

- Rename `boundary`->`padding` (Grid, interp, diff) and
  `boundary_width`->`padding_width` (pad) to match xgcm's v1.0 API.
- Re-execute 05 against the tripolar-north-fold branch (xgcm/xgcm#711) and
  commit the regenerated figure outputs.
- Reset notebooks 01–04 to upstream master so this branch carries only the
  new tripolar example on top of the current examples.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lotting, add cheap first plots

Acts on an independent review of the five examples as of xgcm#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
hdrake and others added 4 commits July 15, 2026 14:03
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
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
Address the review of the example notebooks (into xgcm#26)
hdrake added a commit to hdrake/xgcm that referenced this pull request Jul 17, 2026
…ebooks)

Advance the docs/xgcm-examples pointer from xgcm-examples master
(24aa947) to the head of xgcm/xgcm-examples#26 (20bb1a1), which renames
and improves the example notebooks (adds 05_tripolar_fold, renumbers
01-04, and updates to the xgcm v1.0 padding API).

This matches the pointer already recorded on the base branch
(tripolar-north-fold), so PR #1's remaining diff is just the nav reorder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hdrake and others added 2 commits July 17, 2026 22:12
…ebooks

master advanced with the v0.10.0 modernization (xgcm#33) under the old notebook
numbering (01_eccov4, 02_mitgcm, 04_nemo_idealized), which conflicts with this
branch's renumber-to-reading-order + xgcm v1.0 API rewrite.

Resolve every notebook conflict in favor of this branch: the renumbered
notebooks (01_mitgcm, 02_nemo_idealized, 03_MOM6, 04_eccov4, 05_tripolar_fold)
already carry the modernization and go further to the v1.0 padding API, so they
supersede master's versions. 02_mitgcm.ipynb stays deleted (renamed to
01_mitgcm). environment.yml auto-merged (master's xgcm>=0.10.0 pin + this
branch's tripolar-fold install note).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docs render notebook cells through mkdocs-jupyter's mistune parser
(MathJax on $...$), not arithmatex. The divergence equation broke because
its inline $...$ spanned a source line break whose continuation started
with "+ ", which mistune parsed as a bullet-list item, splitting the math
across </p> and <li>; \nabla\!\cdot also lost its backslash to a literal
"!". Rewrite both the vorticity and divergence equations in the repo's
\frac{}{} convention, each on one line, no leading "+", no \!.

Also:
- drop the stale "day-365 v2 bump" comment on the Zenodo constants;
- link the previous MOM6 example from the intro and vorticity sections;
- remove the notebook name from the mom6_vorticity docstring/comment;
- make the "single keyword" note explicit (extend -> {"fold": "corner"});
- point the summary at the MOM6 Analysis Cookbook and COSIMA cosima-recipes
  (matching 03_MOM6) instead of the dead "more MOM6 recipes" self-link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hdrake added a commit to xgcm/xgcm that referenced this pull request Jul 21, 2026
* Add bipolar/tripolar north-fold boundary condition (closes #194)

Tripolar ocean grids (MOM6/OM4, NEMO/ORCA, MOM5, Oceananigans) have a
bipolar Arctic seam where the northern edge of the logical grid folds onto
itself: the seam (zonal) axis is mirrored about the pole and vector
components reverse sign. xgcm previously ignored this, so a Y diff/interp/
derivative across the top row used an ordinary boundary and was wrong.

Model this the way all three reference codes do -- as a north-edge boundary
condition on the single logical tile (FMS FOLD_NORTH_EDGE, NEMO lbcnfd,
Oceananigans Zipper) -- rather than as a face connection. It is requested as
a per-axis boundary value on the fold axis:

    Grid(ds, boundary={"X": "periodic", "Y": {"fold": "corner"}})

The value names the pivot type (center/T, corner/F, U, V, or an explicit
{axis: position} pair) -- the center-vs-edge sublattice the fold's fixed line
sits on. The seam axis is inferred (the periodic partner); the north edge
folds, the south uses a normal mode (default fill). Per field, the seam
reflection offset and the redundant-row skip are derived from the pivot pair
and the field's own stagger, reproducing Oceananigans' four per-location
kernels:

    seam roll = (pivot_seam=="center") + (field_seam=="edge")
    skip      = 1 if field_fold_kind == pivot_fold else 0

Implemented in the padding layer (_pad_fold + _fold_north_halo, dispatched
from pad()); validated at construction in Grid._validate_folds. Pure indexed
gather + concat, so it stays lazy and works with multi-chunk dask. No
face_connections changes.

Verified against Oceananigans' UPivot kernels and MOM6's vmo = -vmo[::-1]
seam transport. New tests in xgcm/test/test_fold.py cover per-position halos
for multiple pivots, an independent geometric check that center and edge
fields mirror about the same pole, vector sign, left/right pivot
equivalence, multi-row halos, simultaneous seam padding, end-to-end diff
across the seam, and dask parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: add tripolar fold example to nav; bump xgcm-examples

Adds the 06_tripolar_fold notebook (relative vorticity across the fold for
MOM6, NEMO eORCA1, and Oceananigans, on a polar projection) to the docs nav
and bumps the xgcm-examples submodule (also rewrites 03_MOM6's fold section
to the new boundary API).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: seamless tripolar-fold maps + cross-fold transect; bump xgcm-examples

Bump docs/xgcm-examples to the re-plotted 06_tripolar_fold (seamless polar-cap
maps with the fold seam drawn, per-panel difference, and a cross-fold transect
showing fold-continuation vs naive flatline). Restrict mkdocs-jupyter to
*.ipynb so the example builder scripts under scripts/ aren't rendered as pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: add bipolar north-fold section + schematics; clarify terminology

Add a "The Bipolar North Fold (Tripolar Grids)" section to grid_topology.md
with two publication-quality schematics -- the four pivot conventions, and how
the northern halo is filled by mirroring about the pole (with a sign flip for
vector components) -- generated by scripts/make_fold_schematics.py. The worked
code blocks print the mirror and the sign flip on index-encoded data.

Clarify throughout (here, padding.py, test_fold.py) that the global grids are
tripolar -- the South Pole plus two poles displaced over Arctic land -- while
the fold itself is bipolar: its seam is the line joining the two northern poles.

Bump the xgcm-examples submodule pointer (xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* docs: add changelog entry for the bipolar north-fold boundary

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — seam-space tripolar-fold example

Re-do 06_tripolar_fold in logical grid-index space (seam strip, vector
sign-flip strip, cross-seam transect, Rossby seam-row difference) instead of
polar-stereographic maps, so the fold's action at the seam is transparent and
free of projection distortion / land grey-line / noise ambiguity
(xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — crisp per-cell seam-space figures

The 06_tripolar_fold heatmaps now use nearest-neighbour shading and zoom to a
window of open-water columns, so individual grid cells are resolvable instead of
looking upscaled/blurry (xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — shared seam window + NEMO gap explanation

All three 06_tripolar_fold seam figures now share one open-water window per
model (directly comparable), and a note explains the two-row gap in NEMO's speed
strip as a masked top-v-row dataset quirk spread by centre interpolation, not a
fold error (xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — reconstruct NEMO's masked seam v-row

The 06_tripolar_fold example now rebuilds the redundant (masked) top
meridional-velocity row that CMIP6 NEMO/MOM6 ship on the fold seam, so
centre-interpolated diagnostics are gap-free and continuous across the seam
(xgcm/xgcm-examples#24). The fold method is unchanged; this completes the input.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — fix Rossby seam figure layout

Redraw the Rossby ζ/f seam figure as naive / fold / difference (matching the
speed strip) instead of a difference-only panel that looked empty/broken
(xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — uniform seam-figure formatting + grey land

The Rossby figure now straddles the seam with the same axis limits and solid
seam line as the speed/v strips, and masked land renders grey (distinct from the
diverging colormap) across all three figures (xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples — fix vorticity sign across the fold

ζ/f is a pseudoscalar (flips sign across the bipolar fold), so its halo is the
sign-reversed mirror of the interior; the Rossby figure previously padded it as
a plain scalar and showed a spurious sign jump at the seam (xgcm/xgcm-examples#24).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add known-answer interp/diff-across-the-seam test; bump xgcm-examples

Add test_interp_diff_across_seam_known_answer to test_fold.py: it builds the
north-fold halo by hand in pure numpy (mirror in x, skip the redundant seam row,
sign-flip only for vectors) and checks that grid.interp and grid.diff across the
seam match that reference exactly, for both a scalar and a vector. This verifies
the operators (not just pad) are correct across the fold with an exact,
non-statistical check.

Bump the docs/xgcm-examples submodule: the example notebook now demonstrates
diff-across-the-fold with a horizontal-divergence strip (a true scalar, so no
ad-hoc sign handling) instead of the Rossby/ζ figure, and adds u-velocity panels
to the component strip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: broaden the seam known-answer test; bump xgcm-examples

Extend test_interp_diff_across_seam_known_answer to span the distinct fold
branches through the operators, not just the degenerate one: add a seam=edge
field (corner pivot, q-position) whose mirror is a non-trivial roll rather than a
plain reverse, and a skip=0 case (U pivot). Previously only corner+v (seam=center,
reverse, skip 1) was covered, which could not catch a regression in the seam
offset/roll branch.

Bump docs/xgcm-examples: correct the divergence-strip prose (the difference panel
is nonzero across the halo, not only on the seam row).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fold: guard unsupported combos and let per-call boundary override the south edge

Address review nits on the north-fold boundary:

- Reject `fold` + `face_connections` at construction with a clear
  NotImplementedError, instead of a cryptic `TypeError: unhashable type: 'dict'`
  raised later inside `_pad_basic`.
- Raise a clear NotImplementedError for an `inner` seam position under a
  center-type pivot (center/V) instead of an opaque IndexError: reflecting the
  inner sublattice about a cell-center pole has no mirror partner.
- The south edge of a fold axis now honours a per-call `boundary` override when
  one is given (north always folds; only a plain string is a real override,
  distinguished from the default fold-spec dict), matching xgcm's usual
  per-call-overrides-default semantics.

Add tests for all three, and update the docs (whats-new, grid_topology) — also
correcting a stale reference to the removed Rossby-number diagnostic (now the
horizontal-divergence diagnostic).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* scripts: silence ruff T201 for the schematic generator's status prints

`make_fold_schematics.py` prints "wrote <file>" after saving each figure
(fold_pivots.png / fold_halo.png) — intentional CLI feedback in a dev script.
Add `# noqa: T201` to the two print calls so the repo-wide T201 ban (which
targets stray prints in library code) passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix silent north-halo under-fill in _fold_north_halo

A north-fold halo can only mirror as many rows as there are interior
rows below the fold (grid length minus any redundant pole row). When a
wider halo was requested, the row slice silently clamped and returned a
too-short array with no error. Raise a clear ValueError naming the axis,
the requested width, and the max available instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Infer fold seam only from explicitly-periodic axes

The north-fold seam axis was inferred as "the unique other axis whose
boundary is periodic". Because unspecified axes default to periodic, a
3D grid (X periodic, Y fold, Z left unspecified) could not be built: the
defaulted-periodic vertical made the seam ambiguous, and a partial dict
boundary even KeyError'd before validation.

Capture the set of axes the user *explicitly* marked periodic (via
boundary={ax: "periodic"} or an explicit periodic list/dict; the legacy
periodic=True default is not treated as explicit) and restrict seam
candidates to that set. Also make a dict `boundary` tolerate omitted
axes (they take the periodic/fill default) instead of raising KeyError,
so the vertical can simply be left unspecified.

Also document why a 180-degree north fold needs no `other_component`
(both vector components sign-flip but do not swap axes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Make north-fold pivot schematic subtitles model-agnostic

The per-panel subtitles in the 4-pivot north-fold schematic attributed
specific pivot conventions to specific ocean models (e.g. MOM5 -> T-point;
NEMO/ORCA and Oceananigans pinned to single panels). These attributions
were misleading: several of these models support more than one pivot
convention, so tying each to a single panel overstates the relationship.

Replace the model-name subtitles with general, geometric descriptions of
where the pole sits on the staggered C-grid sublattice (tracer T point,
cell corner F point, zonal U face, meridional V face) and regenerate
docs/images/fold_pivots.png. The geometry and the halo figure are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Make north-fold pivot table model-agnostic in grid_topology.md

Match the doc prose to the now-model-agnostic schematic: the pivot
conventions table previously bound specific models to specific pivots
(MOM5 -> T, MOM6/OM4 + NEMO/ORCA -> F, Oceananigans -> U), which
overstates the relationship since several of these models support more
than one convention. Replace the "Used by" column with a "Pole sits on"
column describing the C-grid sublattice point (tracer T point, cell
corner F point, zonal-velocity U face, meridional-velocity V face),
mirroring the schematic subtitles.

The general tripolar-grid model list elsewhere in the section is left
intact: it lists these models as examples of tripolar grids in general,
making no pivot-specific claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Revert "Potential fix for pull request finding"

This reverts commit 42962e6.

* Address Copilot findings correctly (replace broken autofix)

The Copilot autofix (reverted in the previous commit) tried to make the
fold-halo iteration deterministic but deleted the `fold_axes` definition
while still referencing it, leaving `for fax in sorted(fold_axes)` with
`fold_axes` undefined (NameError at pad time). Re-apply the three
findings properly:

1. padding.py: keep the `fold_axes` set definition and iterate it in
   sorted order, so multi-fold-axis concatenation is deterministic.

2. grid_topology.md / make_fold_schematics.py: the redundant (skipped)
   top row is per-field, not a property of the pivot alone. A field's
   top row lies on the fold line exactly when the field's own Y
   sublattice (center/edge) matches the pivot's Y role -- so Y-centered
   pivots (center/u) also skip a row for their center-positioned fields,
   not only the Y-edge pivots (corner/v). Reword the prose and the
   schematic captions to state the field-relative rule, and regenerate
   fold_pivots.png.

All 28 tests in test_fold.py pass.

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

* Apply review feedback to the north-fold feature

Independent review pass on the fold boundary:

- padding.py: validate the position strings in an explicit {axis: position}
  fold pivot. Previously a typo (e.g. "centre") was silently coerced to an
  edge pivot, giving a wrong halo with no error; it now raises with the
  allowed positions. Adds a test.
- grid_topology.md: clarify that a vector is folded by passing it as a
  single-key dict (that is what flags the sign flip); the 180° fold needs no
  partner, so `other_component` is optional on the fold path.
- Bump the xgcm-examples submodule to the reviewed example notebook
  (xgcm/xgcm-examples#26): drops dead helper code, restores X-periodicity in
  the divergence demo, re-executed clean.

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

* docs: bump xgcm-examples submodule (02_mitgcm Y-extend + live-build re-exec)

Points at xgcm/xgcm-examples#26 = 534d73a: 02_mitgcm now uses an X-periodic /
Y-extend grid (the meridional direction is bounded, not periodic), and all
example notebooks were re-executed against this branch's xgcm build.

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

* docs: bump xgcm-examples submodule (simplify 05_tripolar_fold title)

Points at xgcm/xgcm-examples#26 = e2858a5: drops the backtick'd subtitle from
the notebook's H1 so the docs nav sidebar renders the page title correctly.

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

* docs: drop duplicated v0.9.0 changelog entries from the unreleased section

A prior merge of master's #732 (which split the stale "v0.9.0 (unreleased)"
changelog into a released v0.9.0 + a fresh empty v0.10.0) left the SGRID
autoparse (#559) and multi-dim transform (#642) entries duplicated: once
correctly under the released ## v0.9.0 (2025/08/20) heading, and again under
## v0.10.0 (unreleased) -> New Features. The stray copies made them show up
in this PR's diff. Remove them from the unreleased section, leaving only the
bipolar north-fold (#711) entry there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fold: document Mapping boundary + fail loudly on multiple fold axes

Address review feedback on #711:
- Axis.__init__ docstring now documents the fold-spec dict boundary
  option, matching the widened `Union[str, Mapping]` type hint.
- _pad_fold raises NotImplementedError for more than one fold axis
  instead of defensively sorting the fold-axis set; a tripolar grid
  has a single north fold and multiple folds are unsupported.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: add idealized tripolar-grid globe figure to grid topology

Add a "hero" visualization to the Bipolar North Fold section of
docs/grid_topology.md: a hand-shaded orthographic globe showing the
bipolar Arctic cap with its two displaced northern poles and the
bipolar seam, joining an ordinary spherical lat-lon grid at the join
latitude. The cap is a faithful port of the Murray (1996) / MOM6
bipolar projection, so every cap grid line meets the regular grid
continuously at the join (verified to ~1e-14 deg).

New generator scripts/make_tripolar_grid.py (pure numpy + matplotlib,
mirroring scripts/make_fold_schematics.py) writes
docs/images/tripolar_grid.png.

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

* docs+code: flag the north fold as experimental

Mark the tripolar north-fold boundary as experimental so users know its
API and numerical behavior may still change:

- grid.py: emit a UserWarning at Grid construction when a fold boundary is
  declared, with an inline NOTE explaining why.
- padding.py: EXPERIMENTAL banner atop the fold kernel/comment block.
- grid_topology.md: an "Experimental" warning admonition under the north-fold
  section header.
- whats-new.md: label the changelog entry experimental and note the warning.

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

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* docs: bump xgcm-examples submodule to add the tripolar north-fold example

Point docs/xgcm-examples at examples-06-tripolar-fold (xgcm/xgcm-examples
@20bb1a1), which adds 05_tripolar_fold.ipynb — the worked tripolar example
that the docs nav and grid_topology.md already reference. The previous pin
lacked that notebook, so `mkdocs build --strict` aborted on the dangling nav
entry and cross-reference. The notebook is executed against this branch's
`padding`/`padding_width` API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: reorder the examples nav to reading order and follow the renumbering

The examples nav went MITgcm, ECCOv4, MOM6, NEMO -- which was neither the
filename order nor a difficulty order. xgcm-examples renumbers the notebooks to
match how they are meant to be read, and this follows:

  01_mitgcm          simplest grid, plain .plot(), diff/interp/cumsum
  02_nemo_idealized  closed basin; adds metrics/derivative/integrate/cumint/transform
  03_MOM6            first real curvilinear (tripolar) grid; sets up the fold
  04_eccov4          hardest topology (13-tile LLC face_connections), 1.1 GB
  05_tripolar_fold   most specialized; picks up 03's Arctic seam

Two changes of substance over the old order: NEMO moves up from last (it is the
natural second step, and burying it meant metrics/transform appeared only at the
very end) and ECCOv4 moves down from second (13-tile face_connections is the
hardest concept in the suite and it carries the largest download).

Also updates the two links the renumbering would have broken:
- docs/grid_topology.md -> xgcm-examples/04_eccov4.ipynb
- docs/grid_metrics.ipynb -> .../xgcm-examples/01_mitgcm/ (two anchors)

Needs the xgcm-examples submodule bumped to the renumbered commit; the notebooks
are renamed there, so this nav is only correct alongside that bump.

🤖 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

* docs: bump xgcm-examples submodule to PR #26 head (renumbered notebooks)

Advance the docs/xgcm-examples pointer from xgcm-examples master
(24aa947) to the head of xgcm/xgcm-examples#26 (20bb1a1), which renames
and improves the example notebooks (adds 05_tripolar_fold, renumbers
01-04, and updates to the xgcm v1.0 padding API).

This matches the pointer already recorded on the base branch
(tripolar-north-fold), so PR #1's remaining diff is just the nav reorder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: bump xgcm-examples submodule to a327003 (renamed notebooks)

The nav in mkdocs.yml references the renumbered example notebooks
(01_mitgcm, 02_nemo_idealized, 04_eccov4), but the submodule pointer
was still at 20bb1a1, whose notebooks use the old numbering
(01_eccov4, 02_mitgcm, 04_nemo_idealized). Under `mkdocs build --strict`
this mismatch produced 4 warnings and aborted the Read the Docs build.

Advance the pointer to a327003 (head of xgcm-examples#26), which merges
the review fixes from #34 and renames the notebooks to match the nav.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: add docstrings to north-fold test functions

Document each test and helper in test_fold.py so the fold cases are easier
to review and maintain, without changing any behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: cut v0.10.1 release and open v1.0.0 unreleased section

Move the experimental north-fold entry into a released v0.10.1 (2026/07/20)
section and reset v1.0.0 as the next unreleased target. Minor wording tweak
in grid_topology.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Henri Drake <hfdrake@Henris-MacBook-Pro-2.local>

@hdrake hdrake left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already reviewed via xgcm/xgcm@2565dbc

@hdrake
hdrake marked this pull request as ready for review July 21, 2026 02:39
@hdrake
hdrake merged commit 2adce43 into xgcm:master Jul 21, 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