Skip to content

Plan: moczarr — sparse-DGGS xarray reader for morton-hive stores #1

Description

@espg

🤖 from Claude

Plan: moczarr — sparse-DGGS xarray reader for morton-hive stores

The O3 standalone extension from englacial/zagg#198 (resolution comment), and the Phase-2 gate deliverable for englacial/zagg#251. moczarr reads morton-hive stores (the convention frozen on espg/mortie#62, written by zagg) into lazy xarray Datasets, with optional xdggs integration via its public grid registry.

What it is

Three layers (from the O3 resolution):

  1. Core reader (no xdggs dependency): moczarr.open_hive(store_root, aoi=..., window=...) → lazy xr.Dataset. Flow per sparse_coverage.md §5: manifest GET → root coverage.moc ∩ AOI → arithmetic hive paths → stamped-leaf opens (tier-0 box / bitmap-sidecar AOI reject) → concat cells (+ time across windowed leaves). LIST-walk as fallback/discovery only.
    Acceptance (the zagg#251 Phase-2 criterion): as ergonomic as xr.open_zarr() for the debug/interop case.
  2. xdggs layer (moczarr[xdggs] extra): MortonInfo(DGGSInfo) + MortonIndex(DGGSIndex) registered via register_dggs("morton"), backed by mortie (mort2geo, geo2mort, mort2polygon, clip2order/generate_morton_children for zoom_to). Plus a MOC-backed lazy index with a from_moc() constructor — bootstrapped from coverage.moc without reading cell arrays (the HealpixMocIndex analog). This layer is the future upstream offering (Upstream (xdggs): support MortonIndexDtype as a native DGGS coordinate englacial/zagg#72, pitched at Design/Datamodel Decision: Very large datasets xarray-contrib/xdggs#143).
  3. Cross-resolution join (ours permanently): the truncation join (fine_id.startswith(coarse_id); §5 rules) across two stores at different orders — upstream xdggs forbids mixed-grid alignment by design, so this never migrates.

Non-goals: no write path (zagg owns it); no pyramid sweep (zagg §7); no FITS/IVOA MOC interop (per mortie#50).

Dependencies

Core: mortie>=0.9.0 (kernels: moc_and, hive_path, decimal↔word, clip2order, children/parents), zarr>=3.1.5, xarray, numpy>=2.0, obstore, numcodecs (zstd sidecar decode). Extras: xdggs (heavy — lonboard/matplotlib/etc. are its hard deps, which is exactly why it's an extra), test, docs group. Python >=3.12, matching zagg (Q5 below).

Phases

  • Phase 0 — scaffold + CI harness. (done — PR Scaffold + convention core (phases 0-1 of issue #1) #2) src/moczarr/ layout, hatchling + hatch-vcs (tag-driven version, pip-installable from day one), MIT. CI ported from zagg's template: test.yml (uv, 3.12/3.13 matrix, pytest + coverage), lint.yml (ruff PR-review bot), docs.yml (mkdocs → GitHub Pages), .pre-commit-config.yaml (ruff + mypy + codespell), README.
  • Phase 1 — convention core (pure functions, no network). (done — PR Scaffold + convention core (phases 0-1 of issue #1) #2) Read-side implementation of morton-hive/1+/2 and morton-moc/1: decimal-id helpers (order/base/rank), hive path arithmetic + node invariant, windowed leaf-name grammar (first-_ split), manifest parse/validate, coverage envelopes (ranges | bitmap | full), zstd bitmap decode, ranges↔words. Golden vectors ported from zagg's tests so the two implementations can never drift silently.
  • Phase 2 — store layer. (done — PR Scaffold + convention core (phases 0-1 of issue #1) #2) obstore-backed root/leaf access (s3 + local file), manifest/root-MOC/commit-stamp readers with zagg's postures (debris = absent; corrupt sidecar raises; unusable root MOC degrades to walk, never wrong answers — D9), delimiter-LIST discovery walk with the digit-children termination rule.
  • Phase 3 — the opener. (done — PR open_hive() + zagg-written SERC fixture (phase 3 of issue #1) #3; cross-window time concat deferred, see checklist below) open_hive() assembling the lazy Dataset: shard set from root MOC ∩ AOI (walk fallback), box/bitmap leaf rejects, per-leaf xr.open_zarr, concat along the cells dim, window selection + time concat for /2 stores, convention attrs preserved on the result. Integration-tested against a zagg-written fixture store committed to the tree.
  • Phase 4 — xdggs extra. (done — PR xdggs integration: grid_name morton (phase 4 of issue #1) #6, incl. cell_boundaries backend= argument) MortonInfo/MortonIndex + register_dggs("morton"); open_hive(..., decode=True); accessor round-trip tests (sel_latlon, cell_centers, cell_boundaries, zoom_to).
  • Phase 5 — MOC-backed lazy index. from_moc() from the store/leaf coverage tiers; lazy coordinate fabrication; sel/isel without materializing the domain.
  • Phase 6 — cross-resolution join. Truncation-join API across two open stores (exact xarray surface is zagg#198's O4 — design question to settle on this issue before the phase starts).
  • Phase 7 — docs + binder notebook + publish workflow. Runnable-on-binder example against a public store or in-tree fixture (zagg §4 docs convention); publish.yml mirroring zagg's tag-driven TestPyPI→PyPI flow (tags are espg's to push).

Phases 0–3 are the critical path to the zagg#251 Phase-2 gate; 4–6 can interleave after 3.

Added scope (post-plan, from ratified decisions — state as of 2026-07-17)

Design questions for espg

  1. Code-sharing direction with zagg. moczarr re-implements the read-side convention from the spec (goldens ported, so drift fails tests). Longer term: should zagg's consumption layer (zagg/coverage.py, the read half of zagg/hive.py) eventually delegate to moczarr as a dependency, or do both sides keep independent implementations pinned by shared golden vectors? My lean: independent now, zagg→moczarr dep after moczarr stabilizes (deletes ~300 lines from zagg).
  2. Store backend. obstore for parity with zagg (and it's already proven on the write side) vs fsspec for broader ecosystem reach. My lean: obstore core; fsspec never.
  3. Naming. open_hive() vs plain moczarr.open(); and the accessor name for layer-3 ops (.moc?). My lean: open_hive (says what it reads), accessor decision deferred to Phase 6.
  4. Fixture store. Phase 3 needs a small zagg-written hive store committed in-tree (also serves the binder docs). Generate from the NEON SERC config? Needs to be small (<~5 MB).
  5. Python floor. >=3.12 (zagg parity, and mortie targets it) vs >=3.11 (xdggs parity). My lean: 3.12.

I'll proceed on the leans above unless redirected; each phase lands as a draft-PR commit chain per the zagg workflow conventions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions