Skip to content

Add the OverlayNG noding substrate (phase 1): exact noded arrangements with symbolic nodes#442

Draft
asinghvi17 wants to merge 6 commits into
relatengfrom
overlayng
Draft

Add the OverlayNG noding substrate (phase 1): exact noded arrangements with symbolic nodes#442
asinghvi17 wants to merge 6 commits into
relatengfrom
overlayng

Conversation

@asinghvi17

Copy link
Copy Markdown
Member

Phase 1 of the OverlayNG port: the noding substrate — from two geometries to an exact noded arrangement, plus coordinate emission. Stacked on relateng (#416); merge after it. Design doc: docs/plans/2026-07-16-overlayng-noding-substrate.md (first commit on this branch), which also records the survey + spike evidence base (S1–S4) behind every decision here.

Design in one paragraph

Overlay is computed as an exact arrangement with symbolic nodes, rounded once at emission — the constructive extension of RelateNG's design D2. There is no snapping, no snap-rounding, no precision model, and no retry ladder, permanently: that JTS machinery patches an inexact-noding substrate this engine doesn't have. No constructed coordinate ever enters a decision; Float64 appears only inside certified filters (which escalate to the exact kernel predicates on uncertainty) and at final emission. The pipeline keeps JTS OverlayNG's shape (node → graph → label → build) so later phases stay file-by-file diffable against Java.

What's here

  • src/methods/clipping/overlayng/noding/ (~600 SLOC, all internal, nothing exported):
    • noded_arrangement.jlNodedArrangement{P} / NodedEdge: noded edges are (string, segment, node_lo, node_hi) references; a noded edge carries no geometry, so source metadata cannot desynchronize.
    • collect.jl — candidate enumeration via the existing payload RTree + dual_depth_first_search, classification via rk_classify_intersection; proper crossings become symbolic nodes on both parents, touch/collinear incidences become input-vertex nodes (asserted, not assumed). Accepts optional prebuilt trees (a prepared geometry's edge index is the same structure).
    • node_identity.jl — two-tier node identity: egal NodeKey Dict, then a float-sweep with certified radii proposing candidate coincidences confirmed by rk_nodes_coincide, union-find over confirmed pairs only (zero on all real data measured; exercised by degree-6 constructed cases).
    • split.jl — per-segment ordered/deduped interior node lists → noded edges, zero-length guarded.
    • emit.jl — the only lossy step, memoized: planar crossings via a certified double-double fast path (accepted iff provably correctly rounded, Rational{BigInt} fallback); spherical crossings via a conditioning-gated float direction (exact _sph_crossing_dir fallback); vertex nodes pass through bit-exact.
  • Kernel substrate (in the relateng kernel files, where manifold predicates live):
    • rk_compare_along_segment — new rk_ predicate ordering nodes along a segment: certified float stage, lazy exact fallback (S1 measured the float stage resolving 100% of comparisons on real data, 240–590× vs exact-always in dense regimes).
    • _ring_material_interior_on_left — the material-interior orientation authority (the hole-side flip exists exactly once, next to _ring_interior_on_left), which phase 2's depth_delta derivation consumes.
    • One unified exact-crossing call shape serving ordering keys, coincidence, and emission.

Validation

  • New tests: 7,490 assertions (test/methods/clipping/overlayng/noding.jl) + 422 (test/methods/relateng/kernel_along_segment.jl): the five arrangement invariants on constructed cases (degree-6 coincident node, crossing-on-third-string-vertex, vertex-identical shared borders → zero phantom crossings, a–b–a spikes), float-vs-exact-always ordering cross-check, emission certificate audit (certified result == rational result on every node, >7,000 nodes), and an env-gated NE-110m real-data subset asserting the invariants plus the rounded-arrangement audit (zero rounding-introduced crossings, both manifolds).
  • Existing kernel gates re-run at their exact counts (spherical kernel conformance ~51,943; S2 crossings 4,022).
  • Performance vs the spike budgets: planar emission 0.026 µs/node (budget ≤0.05); NE-110m single-pair arrangements 0.04–0.24 ms (budget ~1 ms); dense-synthetic full build ~30 ms for 63,500 nodes / 125,500 edges.

Not in this PR (phase 2/3, per the design doc)

Half-edge graph, OverlayLabel/labeller, ring builders (phase 2); the ops, symdifference, mixed dimensions, and the public opt-in OverlayNG{M} algorithm with differential validation vs GEOS and s2 (phase 3). Existing clipping behavior is completely untouched.

🤖 Generated with Claude Code

asinghvi17 and others added 6 commits July 16, 2026 14:50
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the `rk_compare_along_segment` kernel predicate (planar and spherical
implementations, certified Float64 filter with a conditioning-inflated error
bound escalating to an exact `Rational{BigInt}` / triple-product fallback) for
OverlayNG node ordering (design §2.5).

Grow the exact-crossing authority in place: give `_exact_crossing_point` a
`NodeKey` call shape so node-ordering keys, coincidence, and emission fallback
share one shape, and route the two internal callers through it (design §2.6).

Add `_ring_material_interior_on_left` beside `_ring_interior_on_left` so the
hole flip for overlay labeling lives in exactly one place (design §2.7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add `src/methods/clipping/overlayng/noding/`, the exact-arrangement noding
substrate (design §2.1–§2.6): `NodedArrangement` and its symbolic `NodeTable`,
the collect / node-identity / split / emit stages, and the certified
double-double planar emission fast path with a rational fallback plus the
gated-float spherical crossing-direction emission. Node identity, ordering, and
coincidence are decided by exact kernel predicates over input vertices and
symbolic `NodeKey`s; Float64 appears only as certified filters and at emission.

Reuses `RelateSegmentString`, the RelateNG edge index, and the exact kernel
unchanged. Everything is internal (no exports). Wired into `GeometryOps.jl`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add `test/methods/clipping/overlayng/noding.jl` covering the design §2.9 gate
list: the `NodedArrangement` invariants on constructed cases (crossing quads,
degree-6 tier-2 merge, crossing-on-a-vertex, collinear shared boundary, a–b–a
spike), the dense-comb ordering cross-check against the exact authority on both
manifolds, the certified emission audit (planar certified == rational for every
node; spherical direction within bound of exact), and the rounded-arrangement +
classification censuses on a small Natural Earth subset. Registered in
`test/runtests.jl`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tier-2 sweep confirms zero coincidences on all real data and on the dense
synthetic (S1), so the O(n) key-rehash compaction was pure overhead there
(~20 ms of a 50 ms synthetic build). Return the merge count from the sweep and
take a fast path that only dedups the interior lists when nothing merged. Also
defer the output-coordinate cache allocation to a single post-split
`_ensure_coord_cache!` instead of growing it per interned node, since it is an
emission concern that noding never touches. Synthetic 250×250 build 50 → 30 ms;
emission unchanged at 0.026 µs/node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`@autodocs Modules = [GeometryOps]` renders unexported docstrings, so an `@ref`
to an internal name without its own rendered docstring (`NodeTable`, `NodedEdge`)
would fail the docs cross-reference check. Reference internal names with plain
backticks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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