Skip to content

Review: last commit#231

Closed
acgetchell wants to merge 1 commit intomainfrom
review-last
Closed

Review: last commit#231
acgetchell wants to merge 1 commit intomainfrom
review-last

Conversation

@acgetchell
Copy link
Owner

CodeRabbit review request

…ure (#228)

- Soften post-condition in normalize_and_promote_positive_orientation:
  add canonicalize_global_orientation_sign before the promote loop to
  prevent oscillation; demote the residual negative-orientation check
  from a hard error to a diagnostic log so near-degenerate but
  structurally valid simplices no longer abort insertion.

- Replace enlarged local repair fallback with global
  repair_delaunay_with_flips_k2_k3 when D<4 per-insertion local repair
  cycles on co-spherical FP configurations. The multi-attempt global
  repair uses robust predicates and alternate queue orders to break
  cycling.

- Gate global repair fallback on a new ConstructionOptions field
  (use_global_repair_fallback, default true) threaded through the
  build chain via DelaunayInsertionState. The periodic builder disables
  it (.without_global_repair_fallback()) so global repair cannot
  disrupt the image-point topology; the existing 24-attempt shuffle
  retry finds a working vertex ordering instead.
@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Walkthrough

This PR introduces a new use_global_repair_fallback configuration option to the Delaunay triangulation construction pipeline. The flag controls whether the algorithm attempts global repair as a fallback when local repair cycles occur, particularly in low-dimensional cases. The flag defaults to true but can be disabled via the new without_global_repair_fallback() builder method. Error handling in orientation normalization is also relaxed to tolerate floating-point noise-induced near-degenerate cases.

Changes

Cohort / File(s) Summary
Core Option Addition
src/core/delaunay_triangulation.rs
Added use_global_repair_fallback: bool field to ConstructionOptions; removed Default derive and implemented custom Default initializing the flag to true; added builder method without_global_repair_fallback() for disabling fallback; threaded the flag through construction and failure-handling paths to conditionally enable global repair fallback on per-insertion repair failures.
Option Propagation
src/core/builder.rs, src/core/operations.rs
Updated Phase 2 periodic expansion in builder to use without_global_repair_fallback() option; added matching use_global_repair_fallback field to DelaunayInsertionState and initialized it to true in the constructor.
Error Handling Relaxation
src/core/triangulation.rs
Reordered global orientation canonicalization before bounded cell promotion in normalize_and_promote_positive_orientation; replaced hard failure on convergence failure with a debug-level diagnostic log, allowing continued execution despite FP-noise-induced near-degeneracy after bounded promotion attempts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

bug, rust, topology, geometry

Poem

🐰 With nose in the air, we've woven a choice,
To fallback or not—the algorithm's own voice!
When local repairs stumble and cycle in place,
We gracefully fall back or hold our own space.
The rabbit hops forward with tolerant grace,
Where floating-point whispers won't trip up the race! ✨

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch review-last

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai bot added bug Something isn't working rust Pull requests that update rust code geometry Geometry-related issues topology labels Mar 4, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/core/triangulation.rs (1)

2307-2311: Add quantitative context to the residual-negative diagnostic.

Please include a count (and ideally a small sample of CellKeys) in this debug log so it is actionable during production triage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/triangulation.rs` around lines 2307 - 2311, In
normalize_and_promote_positive_orientation, update the tracing::debug call to
include quantitative context by computing the number of cells that remain
negative after promotion and a small sample (e.g., up to 5) of their CellKey
values; collect these into a Vec or iterator (e.g., residual_negative_cells and
sampled_keys) and log both the count and the sample (using Debug/Display for
CellKey) in the debug message so the diagnostic reads like "X cells remain
negative; sample CellKeys: [...]". Ensure sampling is cheap (take first N or use
.iter().take(N).cloned()) and handle empty case gracefully.
src/core/delaunay_triangulation.rs (2)

463-468: Add regression coverage for the new opt-out toggle.

The toggle is behavior-critical; a focused test would guard against future wiring regressions.

✅ Suggested test addition
+    #[test]
+    fn test_without_global_repair_fallback_threads_to_insertion_state() {
+        init_tracing();
+        let vertices: Vec<Vertex<f64, (), 3>> = vec![
+            vertex!([0.0, 0.0, 0.0]),
+            vertex!([1.0, 0.0, 0.0]),
+            vertex!([0.0, 1.0, 0.0]),
+            vertex!([0.0, 0.0, 1.0]),
+            vertex!([0.25, 0.25, 0.25]),
+        ];
+
+        let opts = ConstructionOptions::default()
+            .with_retry_policy(RetryPolicy::Disabled)
+            .without_global_repair_fallback();
+
+        let dt: DelaunayTriangulation<FastKernel<f64>, (), (), 3> =
+            DelaunayTriangulation::new_with_options(&vertices, opts).unwrap();
+
+        assert!(!dt.insertion_state.use_global_repair_fallback);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/delaunay_triangulation.rs` around lines 463 - 468, Add a focused
unit test that verifies the new opt-out toggle works by constructing the
triangulation/config using the without_global_repair_fallback(...) builder
method and asserting that the internal flag use_global_repair_fallback is set to
false and that behavior changes accordingly (e.g., run a minimal triangulation
scenario that would normally trigger the D<4 global repair fallback and assert
the fallback is not invoked or that the alternative behavior occurs). Place the
test in the same module (cfg(test)) near other tests in
core/delaunay_triangulation.rs, use the public constructor/build flow that
exposes without_global_repair_fallback, and assert either the internal flag or
an observable behavioral difference to prevent future wiring regressions.

3012-3048: Consider extracting the duplicated D<4 fallback block into one helper.

Both stats and non-stats branches now carry near-identical local-failure/global-fallback handling; a shared helper would reduce drift risk.

♻️ Refactor sketch
+    fn handle_d_lt4_local_repair_failure(
+        &mut self,
+        index: usize,
+        topology: TopologyGuarantee,
+        repair_err: &DelaunayRepairError,
+    ) -> Result<(), DelaunayTriangulationConstructionError>
+    where
+        K::Scalar: ScalarSummable,
+    {
+        if self.insertion_state.use_global_repair_fallback {
+            let (tds, kernel) = (&mut self.tri.tds, &self.tri.kernel);
+            repair_delaunay_with_flips_k2_k3(tds, kernel, None, topology)
+                .map(|_| ())
+                .map_err(|global_err| TriangulationConstructionError::GeometricDegeneracy {
+                    message: format!(
+                        "per-insertion Delaunay repair failed at index {index}: {global_err}"
+                    ),
+                }
+                .into())
+        } else {
+            Err(TriangulationConstructionError::GeometricDegeneracy {
+                message: format!(
+                    "per-insertion Delaunay repair failed at index {index}: {repair_err}"
+                ),
+            }
+            .into())
+        }
+    }
-                                        if D < 4 {
-                                            // duplicated block...
-                                        }
+                                        if D < 4 {
+                                            self.handle_d_lt4_local_repair_failure(
+                                                index,
+                                                topology,
+                                                &repair_err,
+                                            )?;
+                                            continue;
+                                        }

Also applies to: 3201-3237

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/delaunay_triangulation.rs` around lines 3012 - 3048, The duplicated
"D<4 local repair cycling => global repair fallback" block should be extracted
into a single helper to avoid drift between the stats and non-stats branches:
add a private helper (e.g., handle_d_lt_4_fallback or
try_local_then_global_repair) that takes the contextual values (index,
repair_err for logging, topology, and mutable access to self.tri or its parts)
and performs the tracing::debug logging, calls repair_delaunay_with_flips_k2_k3,
logs the global_err on failure, and returns a Result<(), Box<dyn Error>> or a
bool/enum indicating "continue loop" vs "propagate
TriangulationConstructionError::GeometricDegeneracy"; then replace the
duplicated if self.insertion_state.use_global_repair_fallback { ... } blocks
with a single call to that helper and handle its return to either continue the
loop or return the constructed GeometricDegeneracy error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/triangulation.rs`:
- Around line 2306-2313: The post-condition softening in
normalize_and_promote_positive_orientation() is neutralized because callers
(specifically the caller that currently invokes
normalize_and_promote_positive_orientation()? and then
validate_geometric_cell_orientation() ) still enforce strict validation; change
the API so normalize_and_promote_positive_orientation returns an explicit result
flag (e.g., enum or bool like promoted_all: bool or AcceptedResidualNegatives)
instead of only Result<(), Error>, update
normalize_and_promote_positive_orientation (and internal helpers
cells_require_positive_orientation_promotion and
canonicalize_global_orientation_sign if needed) to set that flag when it logs
"accepting coherent orientation", and modify the caller to check the returned
flag and skip or relax validate_geometric_cell_orientation() when residual
negatives were accepted. Ensure the new return type is propagated and handled
where normalize_and_promote_positive_orientation is called.

---

Nitpick comments:
In `@src/core/delaunay_triangulation.rs`:
- Around line 463-468: Add a focused unit test that verifies the new opt-out
toggle works by constructing the triangulation/config using the
without_global_repair_fallback(...) builder method and asserting that the
internal flag use_global_repair_fallback is set to false and that behavior
changes accordingly (e.g., run a minimal triangulation scenario that would
normally trigger the D<4 global repair fallback and assert the fallback is not
invoked or that the alternative behavior occurs). Place the test in the same
module (cfg(test)) near other tests in core/delaunay_triangulation.rs, use the
public constructor/build flow that exposes without_global_repair_fallback, and
assert either the internal flag or an observable behavioral difference to
prevent future wiring regressions.
- Around line 3012-3048: The duplicated "D<4 local repair cycling => global
repair fallback" block should be extracted into a single helper to avoid drift
between the stats and non-stats branches: add a private helper (e.g.,
handle_d_lt_4_fallback or try_local_then_global_repair) that takes the
contextual values (index, repair_err for logging, topology, and mutable access
to self.tri or its parts) and performs the tracing::debug logging, calls
repair_delaunay_with_flips_k2_k3, logs the global_err on failure, and returns a
Result<(), Box<dyn Error>> or a bool/enum indicating "continue loop" vs
"propagate TriangulationConstructionError::GeometricDegeneracy"; then replace
the duplicated if self.insertion_state.use_global_repair_fallback { ... } blocks
with a single call to that helper and handle its return to either continue the
loop or return the constructed GeometricDegeneracy error.

In `@src/core/triangulation.rs`:
- Around line 2307-2311: In normalize_and_promote_positive_orientation, update
the tracing::debug call to include quantitative context by computing the number
of cells that remain negative after promotion and a small sample (e.g., up to 5)
of their CellKey values; collect these into a Vec or iterator (e.g.,
residual_negative_cells and sampled_keys) and log both the count and the sample
(using Debug/Display for CellKey) in the debug message so the diagnostic reads
like "X cells remain negative; sample CellKeys: [...]". Ensure sampling is cheap
(take first N or use .iter().take(N).cloned()) and handle empty case gracefully.

ℹ️ Review info

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 799e4543-859c-4e98-ae1c-f2bcf5d386b0

📥 Commits

Reviewing files that changed from the base of the PR and between c181f28 and 01cc380.

📒 Files selected for processing (4)
  • src/core/builder.rs
  • src/core/delaunay_triangulation.rs
  • src/core/operations.rs
  • src/core/triangulation.rs

Comment on lines 2306 to 2313
if self.cells_require_positive_orientation_promotion()? {
return Err(InsertionError::TopologyValidation(
TdsValidationError::InconsistentDataStructure {
message:
"Failed to converge to positive geometric orientation after bounded promotion passes"
.to_string(),
},
));
tracing::debug!(
"normalize_and_promote_positive_orientation: \
some cells still appear negative after bounded promotion passes \
(likely near-degenerate FP noise); accepting coherent orientation"
);
}
self.canonicalize_global_orientation_sign()?;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Softened post-condition is currently neutralized in the repair path.

This function now accepts residual negatives, but the caller in src/core/delaunay_triangulation.rs still does normalize_and_promote_positive_orientation()? at Line 5106 and then validate_geometric_cell_orientation()? at Line 5130. So near-degenerate cases still abort insertion, only later in the flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/triangulation.rs` around lines 2306 - 2313, The post-condition
softening in normalize_and_promote_positive_orientation() is neutralized because
callers (specifically the caller that currently invokes
normalize_and_promote_positive_orientation()? and then
validate_geometric_cell_orientation() ) still enforce strict validation; change
the API so normalize_and_promote_positive_orientation returns an explicit result
flag (e.g., enum or bool like promoted_all: bool or AcceptedResidualNegatives)
instead of only Result<(), Error>, update
normalize_and_promote_positive_orientation (and internal helpers
cells_require_positive_orientation_promotion and
canonicalize_global_orientation_sign if needed) to set that flag when it logs
"accepting coherent orientation", and modify the caller to check the returned
flag and skip or relax validate_geometric_cell_orientation() when residual
negatives were accepted. Ensure the new return type is propagated and handled
where normalize_and_promote_positive_orientation is called.

@acgetchell acgetchell closed this Mar 4, 2026
@codacy-production
Copy link

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
+0.16% (target: -1.00%) 26.51%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (515dade) 13196 8103 61.40%
Head commit (01cc380) 13268 (+72) 8168 (+65) 61.56% (+0.16%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#231) 83 22 26.51%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

@codecov
Copy link

codecov bot commented Mar 4, 2026

Codecov Report

❌ Patch coverage is 26.50602% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.59%. Comparing base (515dade) to head (01cc380).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/core/delaunay_triangulation.rs 23.68% 58 Missing ⚠️
src/core/triangulation.rs 40.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #231      +/-   ##
==========================================
+ Coverage   61.43%   61.59%   +0.15%     
==========================================
  Files          49       49              
  Lines       13189    13261      +72     
==========================================
+ Hits         8103     8168      +65     
- Misses       5086     5093       +7     
Flag Coverage Δ
unittests 61.59% <26.50%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@acgetchell acgetchell deleted the review-last branch March 4, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working geometry Geometry-related issues rust Pull requests that update rust code topology

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant