Skip to content

Make isInCircleRobust robust - #1094

Open
tinko92 wants to merge 1 commit into
locationtech:masterfrom
tinko92:enhancement-robust-isincirclerobust
Open

Make isInCircleRobust robust#1094
tinko92 wants to merge 1 commit into
locationtech:masterfrom
tinko92:enhancement-robust-isincirclerobust

Conversation

@tinko92

@tinko92 tinko92 commented Oct 29, 2024

Copy link
Copy Markdown

This commit directly ports Shewchuk's public domain robust incircle predicate ( https://www.cs.cmu.edu/afs/cs/project/quake/public/code/predicates.c ) to Java as closely as possible and uses it for isInCircleRobust, thereby addressing #310 and (imho more thoroughly) achieve the motivation of #311 . One test is changed, assuming Shewchuk's robust predicates are the ground truth. Many of the existing tests produce calls with sufficiently pathological points to run into the lines past stage B (I'd say DD-arithmetic is effectively ~stage B/stage C).

Allocating so many arrays (past stage C) may look slow but the idea is that this almost never happens (in Shewchuk's original paper this happened 4 times out of ~7.2M calls in a degenerate dataset representing a tilted grid). Everything that is sampled from a continuous distribution with support covering some positive area should never go past the first error bound check, which is the fast double implementation. Almost everything coming from a distribution on a grid or circle or sth. like that should be caught in stage B or C.

I understand that this is borderline unreviewable. The implementation is intentionally as close as possible to Shewchuk's implementation and the idea is to enable review and potential future debugging by direct comparison to the source. The greatest deviation comes from the problem of Java having no macros and no references to primitives, so the respective functions return arrays instead. All of the ported methods from Shewchuk are private implementation details. Some of the arrays in stage D are initialized with values that are never used because the system will complain about potentially uninitialized use, e.g. axtbc, even though, this could never happen.

…ust, change circle test case, assuming Shewchuk's predicate is always correct.
@grootstebozewolf

Copy link
Copy Markdown
Contributor

Thanks for this — differential check from the NetTopologySuite.Proofs side (mesh / Delaunay lane).

We treated this PR and #1212 as one product lane (same isInCircleRobust surface). Numbers below are for this PR’s Stage A shape (|det| >= errbound, then adaptive); we did not re-run the full adaptive Java path or the Maven suite, and we do not claim bit-identity of incircleadapt with the oracle.

Method

  • Oracle / FFI: extracted b64_inCircle via in-process nts_rocq_in_circle (and bit-parity vs oracle_bin INCIRCLE_SIGN). Shewchuk sign: POS ⇔ CCW triangle and P strictly inside.
  • Stage A mirror of this PR: permanent-scaled iccerrboundA = (10 + 96·ε)·ε, ε = ulp(0.5); CERTAIN iff |det| >= errbound (non-strict, as in the PR).

Write-up + table: jts-1094-incircle-lane (oracle/jts1094_incircle_vectors.txt, 27 vectors).

Results

Check Result
Certified pin failures (EXPECTED set) 0
Stage A CERTAIN vs FFI sign conflicts 0
FFI ≡ oracle_bin bit-parity 0 failures
Stage A UNCERTAIN (correct declines) 4

Decisive CERTAIN matches: knife-edge ±, flip witness A=(0,0),B=(2,0),C=(1,1),P=(1,-½) → POS (value 1.5), outside-disk → NEG, scaled flip → POS, densified testCircle sample quads → NEG.

Vertex pins (permanent = 0, det = 0): Stage A is CERTAIN ZERO under this PR’s >= bound (0 >= 0) and returns not-inside without adaptive. Same boolean as escalating; #1212’s strict > marks those UNCERTAIN instead.

Load-bearing uncertain case (adaptive path must run)

JTS#1171 / GEOS 955 subset (also used on the #1212 table):

A = (18.68285714285716, 100.105)
B = (13.41, 104.82100000000001)
C = (13.41, 107.179)
P = (18.682857142857145, 111.89500000000001)
  • Stage A double: det classifies as ZERO, filter UNCERTAIN
  • Oracle / FFI b64_inCircle: POS (strictly inside)

A bare det > 0 without the uncertain→adaptive branch would return false here. The escalation structure is doing real work.

Notes (not merge blockers for this design)

  1. Bound test is non-strict (>=); on |det| == errbound with nonzero det that would return without adaptive (rare; not hit on this table except the zero/permanent=0 vertex case above).
  2. Two_Product uses Math.fma — worth CI coverage on platforms where FMA is emulated (Shewchuk C uses splitter-based product).
  3. This PR does not change Vertex.isCCW; mesh legality is in-circle and orientation (Improve triangulation robustness #1212 pairs the filter with Orientation.index).
  4. Near-cocircular DTs are non-unique — golden testCircle MULTILINESTRING retarget is fine as a regression lock, but edge-wise empty-circle checks are a stronger legality oracle.

Bottom line

Stage A algebra is consistent with an independent extracted in-circle kernel on this table (no CERTAIN wrong signs; zeros and the 955 subset correctly force escalation). Happy to re-run if you add more pathological quads.

(Affiliation: NetTopologySuite.Proofs — formal mesh/predicate companion, not a JTS committer review.)

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.

2 participants