Skip to content

Juniata single-catchment sample: self-contained bundle, train/test module, physics notebook - #193

Merged
taddyb merged 9 commits into
DeepGroundwater:masterfrom
taddyb:juniata-sample
Aug 19, 2026
Merged

Juniata single-catchment sample: self-contained bundle, train/test module, physics notebook#193
taddyb merged 9 commits into
DeepGroundwater:masterfrom
taddyb:juniata-sample

Conversation

@taddyb

@taddyb taddyb commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

A self-contained sample under examples/juniata/ for teams learning how DDR works on a single catchment: the Juniata River at Newport, PA (USGS 01567000, 8,657 km², 213 MERIT reaches — the dMC-Juniata heritage basin, in the 5–10k km² band where routing physically acts). No HPC, S3, or external store access required.

Spec: docs/superpowers/specs/2026-08-18-juniata-single-catchment-sample-design.md · Plan: docs/superpowers/plans/2026-08-18-juniata-sample.md

What's included

  • Data bundle (8.9 MB, committed at examples/juniata/data/) — the 213-reach subgraph re-indexed from CONUS-positional space into a compact 0..212 space (tested pure function; topological/lower-triangular ordering preserved): icechunk Q' (dHBV2 UH retrospective) and USGS obs stores both starting 1980-01-01 (required by the readers' positional time origin), attributes NetCDF, binsparse adjacency zarrs, one-row gage CSV. The unmodified library (Merit, StreamflowReader, kan, dmc) runs on it — nothing under src/ddr/ changed.
  • extract_bundle.py — maintainer-run extraction script (regenerates the bundle from the CONUS stores).
  • train_and_test.py — plain-Python, no Hydra: make_config / train / test / summed_qprime_baseline, runnable as a CLI or from the notebook. The q_prime = flow(...) line is the documented seam for the next project: a differentiable runoff model returning the same gradient-capable hourly (T, N) m³/s tensor plugs in there for a full end-to-end gradient chain.
  • juniata_routing.ipynb (+ committed make_notebook.py generator) — six sections: the basin, Muskingum-Cunge physics (coefficients + mass identity, trapezoid geometry, trapezoid-exact celerity β, Cunge X diffusion matching), the lower-triangular network solve, a live backward pass into the KAN, train & evaluate, and the road to end-to-end. Executes clean top-to-bottom on CPU.
  • README — fresh-machine quickstart, bundle schema table, icechunk 2.0.3 pin, expectation management.

Reference result (30 epochs, CPU, test 1995-10-01–2010-09-30)

NSE KGE
DDR routed 0.784 0.877
summed-Q' baseline 0.695 0.820

Routing beats the no-routing baseline by +0.089 NSE on a single gauge with 30 optimizer steps — also a live single-catchment validation of the corrected physics from #192.

Test plan

  • uv run pytest -q — 650 passed, 1 skipped (includes 7 new tests: re-indexing semantics, bundle contract via real readers, 1-epoch train→test→baseline smoke)
  • uv run ruff format . --check and uv run ruff check . clean repo-wide (notebook included)
  • Notebook executed clean via nbclient
  • Bundle stores verified live: 213 reaches / 212 edges, both time axes at 1980-01-01, lower-triangular adjacency

🤖 Generated with Claude Code

taddyb and others added 9 commits August 18, 2026 05:43
…sics notebook

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement reindex_subgraph() to remap CONUS-positional indices into a compact
0..N-1 space, preserving topological ordering for CSR solve. Add synthetic-array
tests validating edge remapping, physical array alignment, and lower-triangular
structure preservation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the Juniata River (01567000) MERIT-Hydro subgraph bundle for
self-contained training and testing without CONUS-scale data stores.
Bundle: 213 reaches, 212 edges, gage_idx 212; icechunk stores start
1980-01-01; total size 8.9 MB. Adds bundle-contract test skeleton and
updates .gitignore to commit the bundle directory while blocking any
future statistics/ subdirectory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… baseline

- examples/juniata/train_and_test.py: make_config / train / test /
  summed_qprime_baseline / CLI; mirrors scripts/train.py + test.py at
  batch_size=1 with no Hydra dependency.  q_prime = flow(...) is the
  documented seam for a future differentiable runoff model.
- tests/examples/test_juniata_bundle.py: appended TestSmoke
  (1-epoch train + 1-year test + baseline, all assertions green);
  fixed GeoDataset.get_dataset_class unbound-method bug in three
  TestBundleContract tests (mirror cfg.geodataset.get_dataset_class).
- 30-epoch CPU run: routed NSE=0.784 KGE=0.877 vs baseline NSE=0.695
  KGE=0.820 (test 1995/10/01–2010/09/30, gauge 01567000).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Guard against NaN observation days poisoning the gradient by masking
with torch.isfinite() before l1_loss, rather than dropping whole gages
as scripts/train.py does — safer for the single-gauge case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- make_notebook.py builder generates juniata_routing.ipynb (6 sections:
  basin structure, Muskingum-Cunge physics, network solve, differentiable
  chain, train/eval, road to end-to-end). Executes clean top-to-bottom via
  nbclient (epochs=1 fast mode). API correction: GeoDataset.get_dataset_class
  called as cfg.geodataset.get_dataset_class(cfg=cfg) (enum instance method);
  sys.path insert ensures examples package importable when CWD=examples/juniata.
- README.md fills both reference numbers from 30-epoch CPU run:
  routed NSE 0.784 KGE 0.877; summed-q' baseline NSE 0.695 KGE 0.820.
- pyproject.toml: extend *.ipynb per-file ruff ignores to include E702,
  D103, B905, I001 (semicolons, missing docstring, bare zip, import sort
  — all standard notebook style used by the brief's cells).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…axis, zarr attrs

- De-semicolonize cell sources in make_notebook.py so ruff format passes on the
  generated notebook; remove E702 from *.ipynb per-file-ignores in pyproject.toml
- Add missing ## 1. The basin and ## 5. Train & evaluate section headings
- Fix obs hydrograph x-axis: use batch_daily_time_range instead of daily_time_range
- Set root attrs {format, shape, geodataset} on juniata_conus_adjacency.zarr so
  Merit all-segments mode can read conus_adjacency.attrs["shape"] without KeyError;
  update extract_bundle.py to write these attrs at extraction time

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same CI-vs-local drift as 3b18cfd: CI resolves ruff 0.16.3, which formats
Python code fences inside markdown; the new spec and plan docs carried
aligned inline comments it rewrites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@taddyb
taddyb merged commit 4620192 into DeepGroundwater:master Aug 19, 2026
4 checks passed
@taddyb
taddyb deleted the juniata-sample branch August 19, 2026 02:25
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