Skip to content

Nine grid systems from the DGG literature, with an audit - #25

Open
asinghvi17 wants to merge 17 commits into
mainfrom
codex/implement-literature-dggs
Open

Nine grid systems from the DGG literature, with an audit#25
asinghvi17 wants to merge 17 commits into
mainfrom
codex/implement-literature-dggs

Conversation

@asinghvi17

Copy link
Copy Markdown
Member

Adds nine grid systems from the DGG literature and brings them up to the
package's contracts: ISEA3H, ISEA4H, ISEA4T, rHEALPix, AusPIX, IVEA4R,
IVEA9R, RTEA4R, RTEA9R
. The registry goes from six systems to fifteen.

Each family is pinned against sealed black-box oracle vectors — DGGRID 9.0b for
the ISEA families, DGGAL for the rhombic ones — committed under test/oracles/
rather than regenerated, so the vectors are the contract and not a run of
whatever happened to be installed.

What is in here

The systems, with centre/boundary/area/neighbour implementations, the
conformance harness passing on all nine, and member_neighbors swept over
every registered system (it had been untested on nine of the fifteen).

A performance audit (docs/research/literature-grid-performance.md): a
method, a fifteen-system table, six fixed defects, one documented dead end, and
the residues that remain. The six fixes were repeated work rather than
architecture — a 58-iteration bisection in the rhombic inverse projection, a
node extent sampling twice the perimeter it needed, ring sorts recomputing keys
per comparison, rHEALPix re-deriving seam topology on every crossing, ISEA4T
rebuilding boundaries per vertex, and the ISEA3H/4H inverse re-walking every
prefix it extended.

A corrected cap_inflation for ISEA3H/4H, 4.5 and 3.5 down to 2.3 and 2.0.
The old values came from a triangle inequality that assumes every level's
overhang points the same way; the central-place digit directions rotate and
mostly cancel. The true planar suprema are exactly 2 at aperture 3 (alternating
digits, even-depth ancestors only) and sqrt(3) at aperture 4, derived by a
support-function DP and cross-checked by brute force, with the sphere adding
about 1%. The old 4.5 put a level-0 extent at 168 degrees — past a hemisphere,
which is the only reason these two systems used to opt out of the harness's
convex-extent check. They no longer do.

What the merge with main cost

Main's subset-halo work and this branch were written against different
registries. Three things broke where they met, all detailed in 18961c2:

  • A silent correctness bug. The subset prune descends a coarse node only
    when a one-ring neighbour touches the subset, which is sound when descendants
    stay inside their ancestor's footprint. Central-place refinement does not:
    ISEA3H had 20 escaping neighbours at target level 2, 180 at 4, 1620 at 6, and
    at level 9 the walk returned 596 of 649 halo cells. New coarse_probe_rings
    trait, 2 on ISEA3H and 1 everywhere else — ISEA4H does not need it, having
    zero escapes at one ring through target level 7.
  • A degenerate test cell. ISEA3H/4H seal their polar roots as single-child
    chains, so the cross-system laws — which hang subtrees off root 1 — were
    reading the pentagon's five-cell ring at every depth.
  • AusPIX is a registered AuthalicSystem, so main's re-wrap sweeps tried to
    double-wrap it.

Known gaps, deliberately not closed here

None of the nine has a specialized halo engine, so their walk is the generic
one, which costs what the brute-force reference scan costs: 4.46s against 4.46s
at depth 7 on rHEALPix, where S2 does 0.006s against its own 0.72s. Worst on
IVEA9R/RTEA9R, which also lack descendant_range — one subset walk asks 535,818
questions where rHEALPix asks 9,612 at the same aperture and subset size. Those
counts are pinned in subtree_halos.jl, so closing the gap fails the pin.

Two open decisions are in the audit's final section: the rhombic boundary
density (128 points per cell where every other congruent system draws 32) and
Morton reindexing for IVEA4R/RTEA4R, which would unlock descendant_range and
the engines above but changes canonical cell identity.

Testing

Full suite green locally: 1,230,578 passing, 0 failing, 60 broken (the
pre-existing count), 24 minutes.

Worth noting the suite runtime, since it is a merge artifact rather than
something either side had alone: the cross-system halo file went to 222 minutes
when it first met the fifteen-system registry, because its depth ladders are
written as level numbers and level 7 is a 16k-cell subtree at aperture 4 but a
4.8M-cell one at aperture 9. The ladders are now capped by subtree size at the
largest one those laws already walked, and the eager-engine negative control is
restricted to three systems spanning the engine families rather than sweeping
all fifteen to re-prove a property of the wrapper. That file now runs in 17
minutes; every system main already swept keeps its exact depths.

CI has not run on any of this — the workflow is pull_request-only and this is
the first PR. Julia 1.11 is untested locally.

Note that main has advanced 18 commits past the merge point (9ae1f4e);
none of them touch the halo API or these systems.

🤖 Generated with Claude Code

asinghvi17 and others added 12 commits August 15, 2026 00:18
# Conflicts:
#	README.md
#	docs/src/all_dggs.md
`MOC_SWEEP` and `CONGRUENT` still listed the six systems the file was
written against, so `member_neighbors` — a registry-wide law — was
untested on all nine systems this branch adds. Extend both to all
fifteen entries, and add the coverage guard the subset sweep above
already carries so the list cannot silently fall behind again.

`CONGRUENT` becomes a predicate rather than a type tuple: an
`AuthalicSystem` wrap keeps the wrapped system's refinement, so AusPIX
has to answer the geometric law as rHEALPix does. Levels are chosen per
system so the coverage is genuinely mixed-level on every aperture —
ISEA3H needs 10, ISEA4H and the aperture-4 rhombics 8, rHEALPix and the
aperture-9 rhombics 6.

All nine pass, including the geometric boundary-sharing law.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six defects, none of them an algorithm that had to be slow. Measured at
the level nearest 200 000 cells, every result stored so no call is
eliminated as dead.

  * The IVEA/RTEA inverse projection bisected 58 times and called
    `slerp` inside the loop, recomputing arc BC and its sine on every
    one. The map is smooth, strictly increasing, and [0,1] brackets the
    root, so Illinois keeps the bracket and converges superlinearly.
    `cell_centroid` 2.9µs -> 547ns, and the area residual at the
    level-16 apex probe is 1.0e-25 against the old 1.6e-18.

  * IVEA/RTEA `node_extent` sampled eight segments per chart edge. It is
    a cap, so wider is conservative: four widens it 6.7% against halving
    the projections at every node of every descent. 97µs -> 9.6µs.

  * Four systems sorted rings with `sort!(...; by = ...centroid...)`,
    which runs `by` per comparison — about 25 inverse projections to
    order nine cells. Keys once, then `sortperm`.

  * rHEALPix re-derived seam topology from the projection on every
    crossing, four forward and two inverse projections per cut, nine
    cuts for a vertex star. The topology depends on the two polar-square
    placements alone, so it is a 384-entry table built at load and
    checked against the derivation for 16 placements at three longitude
    origins. `edge_neighbors` 146ns, allocation-free.

  * ISEA4T's vertex star filtered candidates with a FLATTENED generator,
    so each candidate's densified 24-point boundary was rebuilt once per
    vertex of the subject's ring. Vertex incidence is about the three
    corners. `neighbors` V 785µs -> 33.3µs, k=2 10.3ms -> 382µs.

  * ISEA3H/4H `_locate_in_root` rebuilt each candidate's whole
    development-plane offset from digit one, with an allocation each
    time. The offset is a prefix sum. Carry it, tabulate the directions
    and radial scales, hoist the query angle. `cellat` 150µs -> 33.5µs,
    `neighbors` 832µs -> 205µs.

The obvious seventh fix is deliberately absent. Densifying the IVEA/RTEA
boundary to a fixed chart STEP rather than a fixed segment count is 372µs
-> 2.2µs, and it opens a lens between a parent's polyline and its
children's that a mixed-level MultiOrderCoverage reads as a sliver — five
sampled interior points of California in no cell at all. Exact nesting
needs `nseg(l) == s * nseg(l+1)` to the bottom of the tree, which is 2^25
segments at a root. The count stays level-independent; the reasoning is
in the comment so the next reader does not rediscover it the same way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`docs/research/literature-grid-performance.md` joins the four research
docs already tracked past `.gitignore`'s `docs/*`: the fifteen-system
table, the six fixes with mechanism and before/after, the reverted
seventh with why it cannot be had, and what is left slow with the reason
it is architecture rather than oversight.

Two claims in README were left behind by this branch and are corrected
rather than restated. `subtree_border` is not an O(rim) automaton on
every system but A5 — it is one on IGEO7, H3, HEALPix, S2 and ISEA4R,
and the nine systems added here all take the fallback. A5 is not the
only system without `has_sorted_subtrees` — the four IVEA/RTEA rhombics
are row-major within a root rather than curve-ordered, so `level_ranges`
throws there too and `member_neighbors` falls back to a per-call member
dictionary. Morton order would give them contiguous ranges, which makes
it a change they have not made rather than a property they lack.

The `systems()` docstring gains the reason IVEA/RTEA `cell_boundary`
draws 128 points per cell at every level: the count is level-independent
on purpose, and the mixed-level tiling law is why.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rewrite the node_extent paragraph in the performance audit: the central-place
refinement does force the covering cap past the cell, but only to a measured
~2.0 (ISEA3H) / ~1.68 (ISEA4H); the declared 4.5/3.5 come from a magnitude-sum
limit that ignores the rotation of the digit directions, and the hemisphere-
crossing extents (ISEA3H levels 0-1, ISEA4H level 0) that forced
require_convex_extents=false are the constant's doing, not the refinement's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`cap_inflation` was 4.5 and 3.5. The covering factor of a descendant `n` levels
down is `sqrt(3)*|sum_j q^j u_j| + q^n`, and those came from bounding `|sum|` by
`sum |.|` — a triangle inequality that assumes every level's step points the
same way — plus margin.

Aperture 4's digit directions do not depend on depth or on the preceding digit,
so a single repeated direction really is its worst case; but that is
`sqrt(3) = 1.7321`, and `1 + sqrt(3)` had added a whole ancestor circumradius
for a descendant that shrinks to nothing as the subtree deepens.

Aperture 3's directions rotate — odd depths use a fixed gauge, even depths are
gauged by the preceding digit — so consecutive steps largely cancel. Taking the
support function of the reachable displacement set (a DP over
`(depth, previous digit)`, swept over direction with a `sec(pi/M)` enclosure and
a geometric tail bound) gives a supremum of exactly 2, approached by the
alternating sequence `1,2,1,2,...` and only from an even-depth ancestor;
odd-depth ancestors reach only `sqrt(3)`. Brute force over every admissible
sequence to depth 13 gives 1.99942 with 0.0023 of tail left, and the
displacement model is checked digit-by-digit against `_dev_step` itself.

The sphere adds ~1%, not the 59% the chart's worst-case anisotropy (1.5864)
implies: an ancestor and its subtree share one local Jacobian, so the distortion
nearly cancels in a ratio rather than multiplying it. Exhaustively over every
ancestor at levels 0:4 with seven-level subtrees the sphere-side worst is 2.0211
for ISEA3H — on the odd ancestor levels, as predicted — and 1.721 for ISEA4H
over levels 0:3.

So: 2.3 and 2.0, carrying 14% and 16%. The widest extent goes from 168 and 131
degrees to 86 and 75, every cap is convex, and both systems now take the
conformance harness's convex-extent assertions instead of opting out — which was
never a property of the refinement, only of the constant.

A test reproduces the measurement in both directions: it fails if the factor is
lowered under the geometry, and if the geometry moves out from under the factor.
Nothing reproduced either number before, which is how 4.5 went unchallenged.

Full suite: 1 205 622 passing, 0 failing, 60 broken — up 160 passes and down the
two `require_convex_extents` skips.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Main's subset-halo work and this branch's nine literature systems were written
against different registries: main sweeps six systems, this branch registers
fifteen. The textual conflicts were the easy half — nine files, every hunk
main-tightened-prose against branch-added-prose, resolved by adopting main's
register and re-expressing the measured substance in it. The rest is what the
two halves cost each other.

`coarse_probe_rings`, a new system trait. Main's subset prune descends a
coarse node only when a one-ring neighbour touches the subset, which is sound
exactly when a node's descendants stay inside its own footprint. Central-place
refinement does not honour that, and ISEA3H breaks it: counting the neighbours
whose ancestor is neither the cell's own nor in its ancestor's one-ring gives
20 escapes at target level 2, 180 at 4, 1620 at 6, and zero at two rings
throughout. The consequence was silent — at level 9 the walk returned 596 of
the 649 halo cells. The escapes appear only from ODD coarse levels, which is
the parity the alternating-digit maximiser predicts, the even-depth ancestors
being the ones driven to the full covering factor 2 rather than to sqrt(3).

ISEA4H does not need it and does not get it: zero escapes at one ring at every
coarse level through target 7, its overhang being sqrt(3), short of the
one-ring reach. Widening it would have restored exactly the pruning slack this
branch removed from `cap_inflation`.

Two rings costs 4.85x the queries — 43603 against 8987 on a level-9 subset —
which main's "the walk inspects only a fraction of members" bound cannot
absorb. The one-ring walk met that bound because it was wrong. The asymptotic
law, that query growth follows halo size, still holds for every system; the
absolute fraction is now pinned per probe width, so a third ring still fails.

`law_root`, replacing `cellindex(levelgrid(sys, 0), 1)` in the cross-system
laws. ISEA3H and ISEA4H seal their two polar roots as single-child chains, so
root 1 has a one-cell subtree at every depth and every growth law read the
pentagon's five-cell one-ring at every level. It resolves to root 1 on the
other thirteen systems, so the constants pinned against them are undisturbed —
their subset question totals come back byte-identical.

AusPIX is a registered `AuthalicSystem`, so main's re-wrap sweeps now skip the
systems that are already wrapped rather than throwing on them.

The suite went to 230 minutes on the merge, 222 of them in the halo file and
170 in a single @test_broken testset. The cause is not the tests: the four
aperture-9 systems have no specialized halo engine, so their walk costs what
the full-grid reference scan costs — 4.46s each at depth 7 on rHEALPix against
0.006s on S2 — and level 7 is a 16k-cell subtree at aperture 4 but a 4.8M-cell
one at aperture 9. The depth ladders are now capped by subtree size at the
largest one these laws already walked, and stretched at the coarse end where
aperture 3 could not reach an aperture-4 growth ratio. Every system main swept
keeps its exact depths.

That missing engine is recorded rather than hidden: the subset question totals
now pin all fifteen systems, and IVEA9R and RTEA9R sit at 535818 where
rHEALPix, at the same aperture and subset size, asks 9612. They are the systems
with neither a specialized halo engine nor sorted subtrees, so the walk cannot
prune by descendant range at all. Closing that gap will fail these pins, which
is the intent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The eager-engine testset is the negative control for the three allocation
laws above it: wrap the real engine in one that materializes, and check the
laws refuse it. Without it they could be passing because nothing measurable is
allocated either way, which is the failure mode a positive law cannot see.

It does not need the whole registry to say that. Eagerness is a property of
the wrapper, not of the system underneath — an eager engine over IVEA4R fails
for the same reason it fails over S2 — so the only axis worth spanning is the
engine being wrapped. Three systems cover it: a square specialization, a hex
one, and a system on the generic walk.

Sweeping all fifteen re-proved that at depth 7 on 4.8M-cell aperture-9
subtrees. It was 170 of the 230 minutes this suite took when the file first met
the literature registry, and 24 of the 41 that survived sizing the depth
ladders. The file now runs in 16m39s and the suite in 25m32s, with the 48
dropped assertions all being the twelfth through fifteenth re-proof.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The oracle sets are 73k of the 80k lines this branch adds, which buries the
2.5k lines of implementation under them in any review of the whole branch.
They are not large where it costs: coordinate text compresses 7.3x, so the
13 MB tree is about 1.75 MB of a 28 MB pack, and a Float64 binary form would
save only a fifth of that — mantissa bits do not compress — while making the
contract unreadable. So this marks them rather than reformatting them.

`linguist-generated` and deliberately not `-diff`. Collapsing them in the PR
view is the goal; suppressing `git diff` is not. Regenerating a family with
`scripts/oracles/` and reading exactly which cells moved is a normal thing to
want, and `-diff` would answer "binary files differ".

The eol pin covers the other half: these are parsed as coordinate text and read
as fixed-width fields in places, so a CRLF checkout would shift every digit
position. `git add --renormalize .` is a no-op today, so this pins the current
state rather than changing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eighty-three commits, carrying a store-IO layer, the neighborhood forms, a
`GlobalRegridding` workspace package, and `CopernicusDEMSystem`. Every textual
conflict was additive on both sides — main adding CopernicusDEM in the same
places this branch adds the literature families — so all four kept both sides.

What did not survive verbatim is main's arithmetic about itself. Three
statements were true when written and are not true on the merged tree, so
taking main's side unchanged would have committed a falsehood:

  `interface/system.jl` had tightened "none of the shipped here does" to "none
  of the SEVEN shipped"; the registry is fifteen. The number-free form is back.

  `DiscreteGlobalGrids.jl` called CopernicusDEM "the seventh system, included
  but not registered". It is no longer the seventh, and the ordinal bought
  nothing the sentence did not already say.

  The README claimed "All seven return `HierarchicalLevelGrid`" and that A5 is
  "the one system without `has_sorted_subtrees`". The branch's counterparts are
  the post-merge truths — fifteen entries, and A5 plus the four IVEA/RTEA
  systems.

One of main's counts was already stale on main: the README says six tutorials
under `docs/src/tutorials/` and there are seven, `store_io.jl` having arrived
without the count following it. Dropped rather than corrected, since it is the
third count in this file to go stale in two merges.

Main's new `neighborhood.jl` sweep failed on nine systems, which is its own
coverage guard working: the list was written for main's seven. The nine are
added with levels chosen per aperture rather than shared, because `covlvl` is a
level number and level 10 is not the same work at aperture 3 as at aperture 9.
Calibrated against main's own rows — 779-1035 coverage cells over 48-80
windows — the nine land at 213-873 over 19-49, and cost 0.7-2.0s each against
main's 0.6-4.7s.

Suite: 1,255,104 passing, 0 failing, 63 broken, 28m31s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
asinghvi17 and others added 4 commits August 20, 2026 17:11
The literature systems follow main's spellings — `maxlevel`,
`maxneighbors`, and `border`/`interior`/`halo` over a subtree region — and
`coarse_probe_rings`, which this branch adds, joins `cap_inflation` in the
`public` tier rather than the export list.

Congruence is what two of the cross-system laws actually branch on, and it is
now wider than main's `isquadface`: ISEA4T's four triangles, rHEALPix's nine
quads and the IVEA/RTEA rhombi all tile their parent. `iscongruent` states
that in `test/helpers.jl`, beside the trait it generalises, and the sweep
tables go back to main's shape. `sweepcovers` grows an `except` for the two
systems that reject the budget mode outright.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwnXToXzkTdstjqfp8ovhH
`_multi_order_query` moved to `Engine` with the rest of the query planner, so
the two central-place hexagon systems were calling a name that no longer
existed and every coverage query on them threw. The fast conservative path
reaches all nine literature systems once it resolves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwnXToXzkTdstjqfp8ovhH
… break

Four selectors read true on main's six systems and false on the reality the
nine literature ones create:

  * `coarse_probe_rings` was reachable from `Fallbacks` but not from `Engine`,
    where the subset halo walk now lives, so ISEA3H's two-ring probe threw.
  * The coarse-containment law computed `k` and then walked one ring anyway;
    it walks `k` again, which is what ISEA3H's escape counts pin.
  * `sortedsubtrees` stood for "ships a specialized halo engine" only while A5
    was the sole exception. `hashalowalk` asks the question directly, and
    selects the same five systems `runtests.jl` names as automata.
  * The eager negative control needs a ladder it can watch grow. The shallow
    one is A5's cost dodge, and A5 is not one of the three controls.

`mapneighbors.jl` takes `neighborhood.jl`'s literature rows — it is the same
sweep, split. And the rotational-adjacency law is asked only of a shape with
rows enough to answer it: the every-fifth-cell subset leaves ISEA4H two such
rows and the aperture-9 rhombi none. A new testset makes each system witness
the law over its shapes together, so the skip cannot hide a `sort!`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwnXToXzkTdstjqfp8ovhH
…f work

`the subset walk's cost follows the halo, not the subset` materializes the
whole descendant range through `holed_subtree`, and its second rung was a bare
level 9. That is a 262k-cell subtree at aperture 4 and a 387-MILLION-cell one
at aperture 9, so registering rHEALPix and AusPIX took the file's peak
resident size from 0.88 GB to 15.9 GB — past what a CI runner has, which is
why the 1.12 ubuntu job was killed rather than failed.

`law_depths` is the cap the rest of the file already uses. Every system main
swept keeps its exact ladder — the aperture-4 three stay at `(4, 9)` — and the
two aperture-9 newcomers come back to `(4, 6)`, a 531k-cell subtree. Measured
at 2 threads: 24620 pass, peak 0.88 GB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwnXToXzkTdstjqfp8ovhH
@asinghvi17

Copy link
Copy Markdown
Member Author

Merged main (1e41af2) in — a merge commit, four commits on top. CI is green on all six checks.

Conflicts (11). src/DiscreteGlobalGrids.jl, src/interface/system.jl, src/fallbacks/fallbacks.jl, README.md and seven cross-system suites. The literature systems take main's spellings (maxlevel, maxneighbors, border/interior/halo over a subtree region, Engine._multi_order_query), and coarse_probe_rings — which this branch adds — joins cap_inflation in the public tier rather than the export list. The ≈ size (km) column main added to the registry table is filled in for all nine, measured rather than estimated.

Four selectors read true on main's six systems and false on the fifteen. Each was found by a failing assertion, not by inspection:

  • Congruence is wider than isquadface: ISEA4T's four triangles, rHEALPix's nine quads and the IVEA/RTEA rhombi all tile their parent. iscongruent states that in test/helpers.jl; the sweep tables go back to main's shape.
  • sortedsubtrees stood for "ships a specialized halo engine" only while A5 was the sole exception. hashalowalk asks directly, and selects the same five systems crosssystem/runtests.jl names as automata.
  • An AuthalicSystem forwards border_engine/halo_engine, so asking the wrapper called every wrap an automaton. Both places unwrap with basesystem first — AusPIX is a registered wrap.
  • The coarse-containment law computed k = coarse_probe_rings(sys) and then walked one ring anyway; it walks k again, which is what ISEA3H's escape counts pin.

Why 1.12-ubuntu was red. the subset walk's cost follows the halo, not the subset materializes a whole descendant range and its second rung was a bare level 9 — a 262k-cell subtree at aperture 4, a 387-million-cell one at aperture 9. Registering rHEALPix and AusPIX took the suite's peak RSS to 18.7 GB, past what a runner has, so the job was killed rather than failed (signal 15, no test output). Capped with the law_depths the rest of the file already uses: every system main swept keeps its exact ladder, the two aperture-9 newcomers come back to (4, 6). Peak RSS 4.1 GB, same counts.

Suite. 1256419 pass / 0 fail / 63 broken, 39 min at -t 2 (loadavg ~50, 64-core box). The conformance package and the generic-fallbacks job both pass.

Fast-to-regrid check. Every one of the nine reaches the specialized wholeblock(::Conservative, ...) at conservative.jl:349 — not the generic COO fallback — with a concrete cell memo and a correct split_weight. Eager conservative regrid, 20×20 raster over a 1° tile, -t 8, loadavg 50:

system level dst cells wall regrid(ones)
ISEA3H 14 1247 0.78 s [1.0, 1.0]
ISEA4H 11 1092 0.80 s [1.0, 1.0]
ISEA4T 10 598 0.72 s [1.0, 1.0]
rHEALPix 7 780 0.75 s [1.0, 1.0]
AusPIX 7 754 0.82 s [1.0, 1.0]
IVEA4R 11 1137 1.55 s [1.0, 1.0]
IVEA9R 7 1270 1.35 s [1.0, 1.0]
RTEA4R 11 1139 1.67 s [1.0, 1.0]
RTEA9R 7 1285 1.30 s [1.0, 1.0]

No system needed a new hook. The one thing that was actually broken was ISEA3H/ISEA4H calling Fallbacks._multi_order_query, which moved to Engine — every coverage query on them threw, which is what DGGSpace needs to build a partial destination.

The IVEA/RTEA four are ~2× the rest at equal cell counts, and that is the declared has_sorted_subtrees = false, not a missing hook: their whole-space tree is the selection cursor rather than CapCachedTree, exactly as A5's is. The README already records that their row-major order is the change they have not made rather than a property they lack.

Not merging — over to you.

…he cone

`_hex_boundary` asked `dev_angle_deg` whether a corner had run past the
five-face cut. That helper exists for the decoder, where an angle above 345
degrees is the floating-point shadow of angle 0 and must not be read as a
continuation; it folds such an angle to a small negative. A hexagon corner,
though, genuinely reaches into `(345, 360)` — the six corners of a cell whose
centre sits on the cut straddle it — and the guard exempted exactly those from
the wedge test, so they stayed on the wrong side.

One corner per cell was wrong, on the five southern roots, at every level from
2 down: 5 cells at ISEA3H level 2 and 75 at ISEA4H level 4, up to 0.216 degrees
off the sealed DGGRID rings. The paired-edge construction below it then drew
the same physical edge twice, once per incident cell, so 20 ISEA3H and 60
ISEA4H neighbour pairs at level 2 shared one sampled point or none instead of
all nine. The polygons did not tile, and the DGGS-as-source direction of a
conservative regrid lost 0.7% (ISEA3H) and 1.1% (ISEA4H) of a field of ones.

The raw modular angle is what `_dev_fold` and `_hex_neighbors1` already use,
and the fold it enables is the cone's own gluing, so a corner on the cut ray
maps to the same sphere point either way. With it every committed boundary
vector matches to 6.2e-7 degrees through ISEA3H level 5 and ISEA4H level 4,
every level-2 neighbour pair shares its nine samples, and both families
conserve as a regrid source to 1.3e-13 — the other thirteen systems' figure.

The sealed `*-boundaries.geojson` vectors were committed but never read; they
are now the corner oracle, alongside a shared-edge pin that catches a fold
error below the acquired oracle depth. The `@test_broken` arms that pinned the
old behaviour in `regridding_conservation.jl` become ordinary assertions; the
DGGS-as-destination direction stays broken for these two, as it is for every
non-convex ring, on the upstream Sutherland-Hodgman limitation the file's
header describes.

Also: `cellposition` on ISEA4T and ISEA4H answered for levels outside
`levels(sys)` — 1 and a `BoundsError` respectively, where every other system
answers `nothing`; compact `show` methods for the four systems that carry
constructor parameters, so `systems()` prints as its docstring says; and
`member_neighbors`'s congruence lists, which still named only the six systems
that existed before this branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwnXToXzkTdstjqfp8ovhH
@asinghvi17

Copy link
Copy Markdown
Member Author

Codex CLI review, and the disposition of it

codex exec --dangerously-bypass-approvals-and-sandbox -m gpt-5.6-sol -c model_reasoning_effort=high on a fresh checkout of bd9eaeb with the environment instantiated, asked to exercise the nine systems as a user rather than read the diff: coverings over seam/antimeridian/polar tiles, boundaries and centroids and areas, neighbour and subtree walks, parent/child consistency, an eager conservative regrid per system, and the arithmetic checked independently against the literature. Five findings.


1. [major] ISEA3H/4H boundary corners disagree with the sealed DGGRID rings, and paired edges crack. — REAL. Fixed in b5650e9.

Reproduced and root-caused. _hex_boundary asked dev_angle_deg whether a corner had run past the five-face development cut. That helper belongs to the decoder, where a planar angle above 345° is the floating-point shadow of angle 0 and must not be read as a continuation; it folds such an angle to a small negative. A hexagon corner genuinely reaches into (345°, 360°) — the corners of a cell whose centre sits on the cut straddle it — so the guard exempted exactly the corners that needed folding.

One corner per cell, on the five southern roots, at every level from 2 down:

                    before          after
ISEA3H L2   5 cells, 0.080°   0 cells, 5.3e-7°
ISEA3H L5  15 cells, 0.063°   0 cells, 6.2e-7°
ISEA4H L1   5 cells, 0.216°   0 cells, 4.7e-7°
ISEA4H L4  75 cells, 0.165°   0 cells, 6.2e-7°

Codex's downstream observation is right too: the paired-edge construction then drew the same physical edge twice, once per incident cell. Level-2 shared-sample histogram, over every neighbour pair:

before   ISEA3H  1 => 20,  9 => 520      after   ISEA3H  9 => 540
         ISEA4H  0 => 20,  1 => 40,              ISEA4H  9 => 960
                 9 => 900

The polygons therefore did not tile, and that is what the "finite approximation" story in regridding_conservation.jl was really pinning: ISEA3H/4H lost 0.70% and 1.08% of a field of ones as a regrid source, the one direction every other curvilinear system is exact in. With the fold fixed they conserve to 1.3e-13, the same figure as the other thirteen.

The fix is the raw modular angle, which _dev_fold and _hex_neighbors1 already use. A corner exactly on the cut ray maps to the same sphere point under either representative, so the fold is the cone's own gluing and not a heuristic.

The *-boundaries.geojson vectors were committed but never read — that is why this shipped. They are now the corner oracle at every acquired level, alongside a shared-edge pin at level 2 that catches a fold error below the oracle depth. The @test_broken arms that pinned the old behaviour become ordinary assertions.

2. [major] None of the nine preserves constants as the regrid destination. — SPLIT: the source half was #1 and is fixed; the destination half is not this branch's.

Codex diagnosed the cause correctly and then declined to draw the conclusion. The destination direction fails for non-convex rings, which is every curvilinear system in the package, six of which predate this branch:

convex at demo level:  IGeo7 ✓  H3 ✓  S2 ✓
                       HEALPix ✗  A5 ✗  ISEA4R ✗   <- already on main
                       all nine literature systems ✗

ConservativeRegridding always passes the destination as the Sutherland-Hodgman clip window, and that operator's own docstring requires a convex clip. The header of regridding_conservation.jl carries the upstream file and line numbers and the measured post-fix residuals. Nine more @test_broken arms is what adding nine curvilinear systems costs; the fix is upstream in GeometryOps and turns all of them into Unexpectedly Pass at once.

3. [minor] Hierarchy primitives accept malformed or out-of-range ids. — MOSTLY REBUTTED; the real part fixed.

The out-of-range parent/children/descendant_range behaviour is the package's existing convention, not something these nine introduced. Same one-past-end probe, on main's systems:

ISEA4R  parent => LevelIndex(0, 10)   children => 4 ids   descendant_range => 641:644
S2      parent => LevelIndex(0, 6)    children => 4 ids   descendant_range => 385:388
HEALPix parent => LevelIndex(0, 12)   children => 4 ids   descendant_range => 769:772

children's contract throws only at maxlevel; the grid is the layer that bounds-checks, and cellposition(grid, ...) answers nothing for all of these. rHEALPix in fact checks more than the incumbents.

The level-bound half is real and is fixed: cellposition(sys, c) for a level outside levels(sys) returned 1 on ISEA4T and threw a BoundsError on ISEA4H, where every other system returns nothing.

4. [minor] member_neighbors's congruence lists were not updated. — REAL. Fixed.

src/engine/stencil.jl still named only HEALPix/S2/ISEA4R as congruent and IGeo7/H3/A5 as not, while systems() had been updated. Both lists now match the trait, and the has_sorted_subtrees note names the IVEA/RTEA rhombi beside A5.

5. [nit] Constructor display contradicts the systems() example. — REAL. Fixed.

The four systems carrying constructor parameters printed their internals. They now print compactly, as H3System/A5System/CopernicusDEMSystem already do, and the docstring says plainly that AusPIXSystem() is an AuthalicSystem wrap and prints as one.


Negative results worth recording. Codex found no seam, antimeridian or polar covering misses on any of the nine; exact poles longitude-independent; cell counts matching every aperture formula and areas summing to ; coarse_probe_rings correct in both directions (20 ISEA3H escapes at one ring, zero at two; zero for ISEA4H at one); hashalowalk and iscongruent agreeing with behaviour; valid-cell parent/child inverses, subtree ancestry and brute-force halo comparisons all passing. Independently, the cap_inflation covering law holds with margin at the new values — worst descendant-boundary distance is 0.72 of the level-0 ISEA3H cap radius over 57,600 sampled points to depth 4, and 0.80 for ISEA4H.

Suite. Julia 1.12, -t 8, loadavg 41–45 on a 64-core box: 1,272,071 pass / 55 broken / 0 fail, 39m03s. Branch baseline was 1,256,419 / 63 / 0 — the eight fewer broken are the ISEA3H/4H arms that now assert, and the extra passes are the corner-oracle and shared-edge tests.

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