Characterize orientation-predicate robustness over R² (tests for #1106)#2
Merged
grootstebozewolf merged 5 commits intoJun 1, 2026
Merged
Conversation
Adds a bespoke Java reference runner (RocqRefRunner) and JUnit suite that exercise the orientation predicate (Orientation.index / CGAlgorithmsDD) against an exact reference, addressing the point-line orientation robustness requirement tracked in locationtech#1106. R^2 coverage (the property the issue actually asks for): compares Orientation.index against an exact BigDecimal determinant sign over arbitrary double coordinates -- uniform random (moderate and large magnitude), adversarial near-collinear (ULP-level perpendicular offsets), and the double-precision-hard cases from OrientationIndexFailureTest. JTS's double-double predicate agrees with the exact reference across the full ~1.6M-case sample. This is strong empirical evidence, not a proof: DD is ~106-bit, not adaptive-exact, so soundness over all of R^2 is not claimed. Bounded-integer floor: exhaustive (|c| <= 4) plus large randomized, near-collinear and domain-boundary samples over integer coords |c| <= 2^25, where the determinant is exact in 64-bit arithmetic. A loader consumes externally exported reference vectors (e.g. from a Rocq development) and cross-checks every supplied sign against the in-code reference. https://claude.ai/code/session_019x4KgiMf2qvbYV7c81D6Z3 Co-authored-by: Claude <noreply@anthropic.com>
Adds DDCounterexampleHunter, which searches for inputs where an orientation predicate disagrees with the exact BigDecimal reference, and a regression test (OrientationDDRobustnessTest) that runs it. The hunter evaluates three predicates against the same exact oracle using adversarial generators (near-collinear across magnitudes up to ~2^52, minimal-determinant product collisions, uniform random): strategy / magnitude DD RobustDeterminant NonRobustCGAlgorithms near-collinear 1e7 0 36014 107214 near-collinear 1e12 0 35261 107522 near-collinear 4.5e15 0 34398 106623 product-collision 0 6295 395614 The current DD predicate (Orientation.index) yields zero counterexamples across millions of adversarial cases, while the naive and legacy predicates it replaced fail tens to hundreds of thousands of times. The test asserts both that the search is effective (legacy predicates do fail) and that DD does not. This is strong empirical evidence, not a proof: DD is ~106-bit, not adaptive-exact, but for double inputs the smallest non-zero orientation determinant is ~2^-104 relative to the product scale, giving DD about two bits of margin. https://claude.ai/code/session_019x4KgiMf2qvbYV7c81D6Z3 Co-authored-by: Claude <noreply@anthropic.com>
The previous commit overstated the result. Its searches were bounded to
|coord| <= ~1e15, and concluded DD orientation had no discoverable
counterexamples. That conclusion was wrong: it implicitly assumed the DD
products neither overflow nor underflow.
Searching the full IEEE-754 double range finds counterexamples immediately:
(0,0),(1e200,1e200),(2e200,2.0000001e200) -> DD=0, exact=+1
Mechanism and sharp thresholds:
- Overflow: for |coord| >= 2^512 (~1.34e154) a DD product exceeds
Double.MAX_VALUE -> Infinity, the determinant is Infinity-Infinity=NaN,
and orientationIndex returns 0 (collinear) for non-collinear points.
- Underflow: for |coord| <~ 2^-512 the products flush to zero and the
determinant is reported as 0.
DD is sound only within roughly [2^-511, 2^511]; the ~2-bit margin argument
holds only inside that band.
Adds DDCounterexampleHunter.extremeMagnitude generator, KNOWN_COUNTEREXAMPLES,
and OVERFLOW/UNDERFLOW magnitude constants. OrientationDDRobustnessTest now
characterizes the limitation (testDDFailsOnOverflow, testDDFailsOnUnderflow,
testKnownCounterexamples) and scopes the soundness assertions to the safe band.
https://claude.ai/code/session_019x4KgiMf2qvbYV7c81D6Z3
Co-authored-by: Claude <noreply@anthropic.com>
…efect
Property-tests the Shewchuk expansion primitives in ShewchuksDeterminant
(test-source) against an exact BigDecimal reference, mirroring refSignExact:
- Two_Sum / Fast_Two_Sum / Two_Product are exact (head+tail == a+b / a*b);
- ShewchuksDeterminant.orientationIndex is exact end-to-end within the safe
magnitude band (transitively exercising the expansion stack);
- Two_Product overflows for products beyond Double.MAX_VALUE (range limit;
adaptive precision does not grant overflow immunity).
Documents a real defect found while probing fast_expansion_sum_zeroelim: lines
713/717 use post-increment (e[eindex++]) where Shewchuk's reference uses
pre-increment (e[++eindex]), so it re-reads the first component instead of
advancing -- double-counting the first and dropping the largest for inputs of
length >= 2 (e.g. [1,16,256,4096]+[2,32,512,8192] -> 822, should be 13107).
This is test-source code (production orientation uses CGAlgorithmsDD), and the
defect is masked in orientationIndex by the Stage-A float filter, so no sign
errors occur over 2M near-collinear cases. Not auto-fixed: a correct Java port
must also guard the one-past-end read Shewchuk's C relies on.
https://claude.ai/code/session_019x4KgiMf2qvbYV7c81D6Z3
Co-authored-by: Claude <noreply@anthropic.com>
Adds a brief prose note in DDCounterexampleHunter: the self-contained BigDecimal reference (RocqRefRunner.refSignExact) remains the sole ground truth for the tests, and its verdicts -- including the overflow/underflow counterexamples -- were independently spot-checked against an external GMP-backed exact-integer orientation oracle and agreed in all cases. The oracle is corroboration only, not a test dependency; no binary or harness is vendored. https://claude.ai/code/session_019x4KgiMf2qvbYV7c81D6Z3 Co-authored-by: Claude <noreply@anthropic.com>
github-actions Bot
pushed a commit
that referenced
this pull request
Jun 2, 2026
§7 risk #2 in issue locationtech#1195 says today's CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true, against OGC SFA intent. Root cause: LineString overrides Geometry.isEquivalentClass with a lenient `other instanceof LineString` check (intentional, supports LinearRing ↔ LineString equivalence); CircularString and CompoundCurve extend LineString and inherit the lenient check. Branching options in SPEC_R_EQ.md: A — override `isEquivalentClass` on each curve-side LineString subclass to be strict. Asymmetric: cs.eE(ls) returns false, ls.eE(cs) stays true (LineString's lenient check is preserved). One method per curve type, no jts-core change. B — tighten LineString.isEquivalentClass to strict everywhere. Symmetric, but breaks LinearRing ↔ LineString equivalence which is widely relied on. Much bigger change. C — add a parallel `equalsExactCurveAware` method. Surface-area cost, doesn't fix the bug for existing callers. Lean: Option A. Implemented in this commit: - CircularString.isEquivalentClass overridden to `other instanceof CircularString` - CompoundCurve.isEquivalentClass overridden to `other instanceof CompoundCurve` Asymmetry contract is part of the spike; documented in SPEC_R_EQ.md "asymmetry trap" and exercised by test_R_EQ_asymmetryIsDocumentedAndAccepted in EqualsExactSemanticSpec. Measurement on this branch: BEFORE override (probe): cs.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(cs) true -- WRONG (R-EQ target) cs.equalsExact(cc) true -- WRONG (R-EQ target) ls.equalsExact(cs) true -- intentional (asymmetry) cs.equalsExact(cs) true -- correct poly.equalsExact(ls) false -- already correct AFTER override (probe): cs.equalsExact(ls) false -- FIXED cc.equalsExact(ls) false -- FIXED cc.equalsExact(cs) false -- FIXED cs.equalsExact(cc) false -- FIXED ls.equalsExact(cs) true -- unchanged (asymmetry) cs.equalsExact(cs) true -- unchanged poly.equalsExact(ls) false -- unchanged Spec class: 3 reds flipped green, 3 sanity tests stayed green => 6/6 Default curved-module suite: 55 / 55 still green (zero regressions) Full reactor with checkstyle: BUILD SUCCESS Conclusion: Option A is a clean, two-method change that fixes the §7 violation on the side where the type information lives, with no regressions in jts-core or jts-curved. The asymmetric dual is a release-note item, not a correctness issue. Smallest concrete next step (SPEC_R_EQ.md): maintainer A/B/C ack, then this branch becomes the implementation PR (cherry-pick the two overrides, add release-note bullet, add Javadoc on each curve type calling out the asymmetric contract). Files: - modules/curved/SPEC_R_EQ.md - modules/curved/src/main/java/.../CircularString.java - modules/curved/src/main/java/.../CompoundCurve.java - modules/curved/src/test/java/.../EqualsExactSemanticSpec.java - modules/curved/src/test/java/.../EqualsExactCurrentBehaviourProbe.java - modules/curved/pom.xml (Surefire exclude per epic §11) Assisted-by: Claude (Opus-4.7) Signed-off-by: Jeroen Bloemscheer <jeroen@jeroentechsolutions.uk>
github-actions Bot
pushed a commit
that referenced
this pull request
Jun 14, 2026
§7 risk #2 in issue locationtech#1195 says today's CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true, against OGC SFA intent. Root cause: LineString overrides Geometry.isEquivalentClass with a lenient `other instanceof LineString` check (intentional, supports LinearRing ↔ LineString equivalence); CircularString and CompoundCurve extend LineString and inherit the lenient check. Branching options in SPEC_R_EQ.md: A — override `isEquivalentClass` on each curve-side LineString subclass to be strict. Asymmetric: cs.eE(ls) returns false, ls.eE(cs) stays true (LineString's lenient check is preserved). One method per curve type, no jts-core change. B — tighten LineString.isEquivalentClass to strict everywhere. Symmetric, but breaks LinearRing ↔ LineString equivalence which is widely relied on. Much bigger change. C — add a parallel `equalsExactCurveAware` method. Surface-area cost, doesn't fix the bug for existing callers. Lean: Option A. Implemented in this commit: - CircularString.isEquivalentClass overridden to `other instanceof CircularString` - CompoundCurve.isEquivalentClass overridden to `other instanceof CompoundCurve` Asymmetry contract is part of the spike; documented in SPEC_R_EQ.md "asymmetry trap" and exercised by test_R_EQ_asymmetryIsDocumentedAndAccepted in EqualsExactSemanticSpec. Measurement on this branch: BEFORE override (probe): cs.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(cs) true -- WRONG (R-EQ target) cs.equalsExact(cc) true -- WRONG (R-EQ target) ls.equalsExact(cs) true -- intentional (asymmetry) cs.equalsExact(cs) true -- correct poly.equalsExact(ls) false -- already correct AFTER override (probe): cs.equalsExact(ls) false -- FIXED cc.equalsExact(ls) false -- FIXED cc.equalsExact(cs) false -- FIXED cs.equalsExact(cc) false -- FIXED ls.equalsExact(cs) true -- unchanged (asymmetry) cs.equalsExact(cs) true -- unchanged poly.equalsExact(ls) false -- unchanged Spec class: 3 reds flipped green, 3 sanity tests stayed green => 6/6 Default curved-module suite: 55 / 55 still green (zero regressions) Full reactor with checkstyle: BUILD SUCCESS Conclusion: Option A is a clean, two-method change that fixes the §7 violation on the side where the type information lives, with no regressions in jts-core or jts-curved. The asymmetric dual is a release-note item, not a correctness issue. Smallest concrete next step (SPEC_R_EQ.md): maintainer A/B/C ack, then this branch becomes the implementation PR (cherry-pick the two overrides, add release-note bullet, add Javadoc on each curve type calling out the asymmetric contract). Files: - modules/curved/SPEC_R_EQ.md - modules/curved/src/main/java/.../CircularString.java - modules/curved/src/main/java/.../CompoundCurve.java - modules/curved/src/test/java/.../EqualsExactSemanticSpec.java - modules/curved/src/test/java/.../EqualsExactCurrentBehaviourProbe.java - modules/curved/pom.xml (Surefire exclude per epic §11) Assisted-by: Claude (Opus-4.7) Signed-off-by: Jeroen Bloemscheer <jeroen@jeroentechsolutions.uk>
github-actions Bot
pushed a commit
that referenced
this pull request
Jun 18, 2026
§7 risk #2 in issue locationtech#1195 says today's CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true, against OGC SFA intent. Root cause: LineString overrides Geometry.isEquivalentClass with a lenient `other instanceof LineString` check (intentional, supports LinearRing ↔ LineString equivalence); CircularString and CompoundCurve extend LineString and inherit the lenient check. Branching options in SPEC_R_EQ.md: A — override `isEquivalentClass` on each curve-side LineString subclass to be strict. Asymmetric: cs.eE(ls) returns false, ls.eE(cs) stays true (LineString's lenient check is preserved). One method per curve type, no jts-core change. B — tighten LineString.isEquivalentClass to strict everywhere. Symmetric, but breaks LinearRing ↔ LineString equivalence which is widely relied on. Much bigger change. C — add a parallel `equalsExactCurveAware` method. Surface-area cost, doesn't fix the bug for existing callers. Lean: Option A. Implemented in this commit: - CircularString.isEquivalentClass overridden to `other instanceof CircularString` - CompoundCurve.isEquivalentClass overridden to `other instanceof CompoundCurve` Asymmetry contract is part of the spike; documented in SPEC_R_EQ.md "asymmetry trap" and exercised by test_R_EQ_asymmetryIsDocumentedAndAccepted in EqualsExactSemanticSpec. Measurement on this branch: BEFORE override (probe): cs.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(cs) true -- WRONG (R-EQ target) cs.equalsExact(cc) true -- WRONG (R-EQ target) ls.equalsExact(cs) true -- intentional (asymmetry) cs.equalsExact(cs) true -- correct poly.equalsExact(ls) false -- already correct AFTER override (probe): cs.equalsExact(ls) false -- FIXED cc.equalsExact(ls) false -- FIXED cc.equalsExact(cs) false -- FIXED cs.equalsExact(cc) false -- FIXED ls.equalsExact(cs) true -- unchanged (asymmetry) cs.equalsExact(cs) true -- unchanged poly.equalsExact(ls) false -- unchanged Spec class: 3 reds flipped green, 3 sanity tests stayed green => 6/6 Default curved-module suite: 55 / 55 still green (zero regressions) Full reactor with checkstyle: BUILD SUCCESS Conclusion: Option A is a clean, two-method change that fixes the §7 violation on the side where the type information lives, with no regressions in jts-core or jts-curved. The asymmetric dual is a release-note item, not a correctness issue. Smallest concrete next step (SPEC_R_EQ.md): maintainer A/B/C ack, then this branch becomes the implementation PR (cherry-pick the two overrides, add release-note bullet, add Javadoc on each curve type calling out the asymmetric contract). Files: - modules/curved/SPEC_R_EQ.md - modules/curved/src/main/java/.../CircularString.java - modules/curved/src/main/java/.../CompoundCurve.java - modules/curved/src/test/java/.../EqualsExactSemanticSpec.java - modules/curved/src/test/java/.../EqualsExactCurrentBehaviourProbe.java - modules/curved/pom.xml (Surefire exclude per epic §11) Assisted-by: Claude (Opus-4.7) Signed-off-by: Jeroen Bloemscheer <jeroen@jeroentechsolutions.uk>
github-actions Bot
pushed a commit
that referenced
this pull request
Jun 20, 2026
§7 risk #2 in issue locationtech#1195 says today's CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true, against OGC SFA intent. Root cause: LineString overrides Geometry.isEquivalentClass with a lenient `other instanceof LineString` check (intentional, supports LinearRing ↔ LineString equivalence); CircularString and CompoundCurve extend LineString and inherit the lenient check. Branching options in SPEC_R_EQ.md: A — override `isEquivalentClass` on each curve-side LineString subclass to be strict. Asymmetric: cs.eE(ls) returns false, ls.eE(cs) stays true (LineString's lenient check is preserved). One method per curve type, no jts-core change. B — tighten LineString.isEquivalentClass to strict everywhere. Symmetric, but breaks LinearRing ↔ LineString equivalence which is widely relied on. Much bigger change. C — add a parallel `equalsExactCurveAware` method. Surface-area cost, doesn't fix the bug for existing callers. Lean: Option A. Implemented in this commit: - CircularString.isEquivalentClass overridden to `other instanceof CircularString` - CompoundCurve.isEquivalentClass overridden to `other instanceof CompoundCurve` Asymmetry contract is part of the spike; documented in SPEC_R_EQ.md "asymmetry trap" and exercised by test_R_EQ_asymmetryIsDocumentedAndAccepted in EqualsExactSemanticSpec. Measurement on this branch: BEFORE override (probe): cs.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(cs) true -- WRONG (R-EQ target) cs.equalsExact(cc) true -- WRONG (R-EQ target) ls.equalsExact(cs) true -- intentional (asymmetry) cs.equalsExact(cs) true -- correct poly.equalsExact(ls) false -- already correct AFTER override (probe): cs.equalsExact(ls) false -- FIXED cc.equalsExact(ls) false -- FIXED cc.equalsExact(cs) false -- FIXED cs.equalsExact(cc) false -- FIXED ls.equalsExact(cs) true -- unchanged (asymmetry) cs.equalsExact(cs) true -- unchanged poly.equalsExact(ls) false -- unchanged Spec class: 3 reds flipped green, 3 sanity tests stayed green => 6/6 Default curved-module suite: 55 / 55 still green (zero regressions) Full reactor with checkstyle: BUILD SUCCESS Conclusion: Option A is a clean, two-method change that fixes the §7 violation on the side where the type information lives, with no regressions in jts-core or jts-curved. The asymmetric dual is a release-note item, not a correctness issue. Smallest concrete next step (SPEC_R_EQ.md): maintainer A/B/C ack, then this branch becomes the implementation PR (cherry-pick the two overrides, add release-note bullet, add Javadoc on each curve type calling out the asymmetric contract). Files: - modules/curved/SPEC_R_EQ.md - modules/curved/src/main/java/.../CircularString.java - modules/curved/src/main/java/.../CompoundCurve.java - modules/curved/src/test/java/.../EqualsExactSemanticSpec.java - modules/curved/src/test/java/.../EqualsExactCurrentBehaviourProbe.java - modules/curved/pom.xml (Surefire exclude per epic §11) Assisted-by: Claude (Opus-4.7) Signed-off-by: Jeroen Bloemscheer <jeroen@jeroentechsolutions.uk>
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 1, 2026
§7 risk #2 in issue locationtech#1195 says today's CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true, against OGC SFA intent. Root cause: LineString overrides Geometry.isEquivalentClass with a lenient `other instanceof LineString` check (intentional, supports LinearRing ↔ LineString equivalence); CircularString and CompoundCurve extend LineString and inherit the lenient check. Branching options in SPEC_R_EQ.md: A — override `isEquivalentClass` on each curve-side LineString subclass to be strict. Asymmetric: cs.eE(ls) returns false, ls.eE(cs) stays true (LineString's lenient check is preserved). One method per curve type, no jts-core change. B — tighten LineString.isEquivalentClass to strict everywhere. Symmetric, but breaks LinearRing ↔ LineString equivalence which is widely relied on. Much bigger change. C — add a parallel `equalsExactCurveAware` method. Surface-area cost, doesn't fix the bug for existing callers. Lean: Option A. Implemented in this commit: - CircularString.isEquivalentClass overridden to `other instanceof CircularString` - CompoundCurve.isEquivalentClass overridden to `other instanceof CompoundCurve` Asymmetry contract is part of the spike; documented in SPEC_R_EQ.md "asymmetry trap" and exercised by test_R_EQ_asymmetryIsDocumentedAndAccepted in EqualsExactSemanticSpec. Measurement on this branch: BEFORE override (probe): cs.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(cs) true -- WRONG (R-EQ target) cs.equalsExact(cc) true -- WRONG (R-EQ target) ls.equalsExact(cs) true -- intentional (asymmetry) cs.equalsExact(cs) true -- correct poly.equalsExact(ls) false -- already correct AFTER override (probe): cs.equalsExact(ls) false -- FIXED cc.equalsExact(ls) false -- FIXED cc.equalsExact(cs) false -- FIXED cs.equalsExact(cc) false -- FIXED ls.equalsExact(cs) true -- unchanged (asymmetry) cs.equalsExact(cs) true -- unchanged poly.equalsExact(ls) false -- unchanged Spec class: 3 reds flipped green, 3 sanity tests stayed green => 6/6 Default curved-module suite: 55 / 55 still green (zero regressions) Full reactor with checkstyle: BUILD SUCCESS Conclusion: Option A is a clean, two-method change that fixes the §7 violation on the side where the type information lives, with no regressions in jts-core or jts-curved. The asymmetric dual is a release-note item, not a correctness issue. Smallest concrete next step (SPEC_R_EQ.md): maintainer A/B/C ack, then this branch becomes the implementation PR (cherry-pick the two overrides, add release-note bullet, add Javadoc on each curve type calling out the asymmetric contract). Files: - modules/curved/SPEC_R_EQ.md - modules/curved/src/main/java/.../CircularString.java - modules/curved/src/main/java/.../CompoundCurve.java - modules/curved/src/test/java/.../EqualsExactSemanticSpec.java - modules/curved/src/test/java/.../EqualsExactCurrentBehaviourProbe.java - modules/curved/pom.xml (Surefire exclude per epic §11) Assisted-by: Claude (Opus-4.7) Signed-off-by: Jeroen Bloemscheer <jeroen@jeroentechsolutions.uk>
github-actions Bot
pushed a commit
that referenced
this pull request
Jul 2, 2026
§7 risk #2 in issue locationtech#1195 says today's CIRCULARSTRING(p0,p1,p2).equalsExact(LINESTRING(p0,p1,p2)) returns true, against OGC SFA intent. Root cause: LineString overrides Geometry.isEquivalentClass with a lenient `other instanceof LineString` check (intentional, supports LinearRing ↔ LineString equivalence); CircularString and CompoundCurve extend LineString and inherit the lenient check. Branching options in SPEC_R_EQ.md: A — override `isEquivalentClass` on each curve-side LineString subclass to be strict. Asymmetric: cs.eE(ls) returns false, ls.eE(cs) stays true (LineString's lenient check is preserved). One method per curve type, no jts-core change. B — tighten LineString.isEquivalentClass to strict everywhere. Symmetric, but breaks LinearRing ↔ LineString equivalence which is widely relied on. Much bigger change. C — add a parallel `equalsExactCurveAware` method. Surface-area cost, doesn't fix the bug for existing callers. Lean: Option A. Implemented in this commit: - CircularString.isEquivalentClass overridden to `other instanceof CircularString` - CompoundCurve.isEquivalentClass overridden to `other instanceof CompoundCurve` Asymmetry contract is part of the spike; documented in SPEC_R_EQ.md "asymmetry trap" and exercised by test_R_EQ_asymmetryIsDocumentedAndAccepted in EqualsExactSemanticSpec. Measurement on this branch: BEFORE override (probe): cs.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(ls) true -- WRONG (R-EQ target) cc.equalsExact(cs) true -- WRONG (R-EQ target) cs.equalsExact(cc) true -- WRONG (R-EQ target) ls.equalsExact(cs) true -- intentional (asymmetry) cs.equalsExact(cs) true -- correct poly.equalsExact(ls) false -- already correct AFTER override (probe): cs.equalsExact(ls) false -- FIXED cc.equalsExact(ls) false -- FIXED cc.equalsExact(cs) false -- FIXED cs.equalsExact(cc) false -- FIXED ls.equalsExact(cs) true -- unchanged (asymmetry) cs.equalsExact(cs) true -- unchanged poly.equalsExact(ls) false -- unchanged Spec class: 3 reds flipped green, 3 sanity tests stayed green => 6/6 Default curved-module suite: 55 / 55 still green (zero regressions) Full reactor with checkstyle: BUILD SUCCESS Conclusion: Option A is a clean, two-method change that fixes the §7 violation on the side where the type information lives, with no regressions in jts-core or jts-curved. The asymmetric dual is a release-note item, not a correctness issue. Smallest concrete next step (SPEC_R_EQ.md): maintainer A/B/C ack, then this branch becomes the implementation PR (cherry-pick the two overrides, add release-note bullet, add Javadoc on each curve type calling out the asymmetric contract). Files: - modules/curved/SPEC_R_EQ.md - modules/curved/src/main/java/.../CircularString.java - modules/curved/src/main/java/.../CompoundCurve.java - modules/curved/src/test/java/.../EqualsExactSemanticSpec.java - modules/curved/src/test/java/.../EqualsExactCurrentBehaviourProbe.java - modules/curved/pom.xml (Surefire exclude per epic §11) Assisted-by: Claude (Opus-4.7) Signed-off-by: Jeroen Bloemscheer <jeroen@jeroentechsolutions.uk>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a test-only harness that characterizes the robustness of the orientation
predicate (
Orientation.index→CGAlgorithmsDD) over arbitrarydoublecoordinates, which is the property the point-line orientation robustness issues
ask for. The headline tool is a counterexample hunter that compares a
predicate against a self-contained exact reference (
BigDecimal, exact fordyadic doubles) and searches adversarial inputs for sign disagreements.
Key findings (all backed by passing tests):
Orientation.indexagrees with the exact sign onevery one of millions of adversarial cases — uniform-random across
magnitudes, near-collinear at ULP-level perpendicular offsets, and the
double-precision-hard cases from
OrientationIndexFailureTest— forcoordinates roughly in
[2^-511, 2^511].counterexamples for the predicates DD replaced (
RobustDeterminant,NonRobustCGAlgorithms), while DD yields zero.for
|coord| ≥ 2^512the products overflow (Inf − Inf = NaN) and for|coord| ≲ 2^-512they underflow, soorientationIndexreturns0(collinear) for points that are not collinear. e.g.
(0,0),(1e200,1e200),(2e200,2.0000001e200)is CCW but DD reports collinear.A robust predicate would scale coordinates to avoid this.
Shewchuk expansion primitives,
ShewchuksDeterminant.fast_expansion_sum_zeroelimwas found to double-count the first component and drop the largest for inputs
of length ≥ 2 (post-increment
e[eindex++]where Shewchuk's reference usespre-increment
e[++eindex]): e.g.[1,16,256,4096]+[2,32,512,8192] → 822(should be
13107). This is masked inorientationIndexby the Stage-A floatfilter (verified exact over 2M near-collinear cases) and lives in test
sources only; it is documented in code, not auto-fixed.
Relevant issues
Orientation.indexproblem with collinearityContribution type / module
jts-core(test sources only — no production code changed)What's included (all under
modules/core/src/test/.../algorithm/)RocqRefRunner+RocqRefRunnerTest— exact orientation reference andsoundness tests: exhaustive over integer coords
|c| ≤ 4, plus largerandomized / near-collinear / boundary samples over
|c| ≤ 2^25(exact in64-bit), and the R² exact-
BigDecimalreference; an optional loader forexternally-exported reference vectors.
DDCounterexampleHunter+OrientationDDRobustnessTest— the hunter,adversarial generators, and the overflow/underflow characterization tests.
ShewchukExpansionExactnessTest— exactness ofTwo_Sum/Fast_Two_Sum/Two_Product, end-to-end exactness ofShewchuksDeterminant.orientationIndex,and the documented
fast_expansion_sum_zeroelimdefect.All new files carry the EPL/EDL license header and are JUnit tests.
Notes
master, not upstream; commits are not ECAsigned-off.
the overflow/underflow counterexamples) were independently spot-checked
against an external GMP-backed exact-integer orientation oracle and agreed in
all cases; that oracle is corroboration only and is not vendored or required.
Generated by Claude Code