Skip to content

perf(core): localize remove_vertex repair#401

Merged
acgetchell merged 5 commits into
mainfrom
perf/remove-vertex-local-postconditions
May 19, 2026
Merged

perf(core): localize remove_vertex repair#401
acgetchell merged 5 commits into
mainfrom
perf/remove-vertex-local-postconditions

Conversation

@acgetchell
Copy link
Copy Markdown
Owner

  • Avoid global simplex scans and incident-simplex rebuilds on successful vertex removal by using the affected vertex star and scoped repair.
  • Promote and validate orientation over the touched removal scope, with the full global orientation path retained as a fallback.
  • Add a Criterion remove_vertex benchmark covering successful removals and invalid-remnant rollback across 2D through 5D.

- Avoid global simplex scans and incident-simplex rebuilds on successful
  vertex removal by using the affected vertex star and scoped repair.
- Promote and validate orientation over the touched removal scope, with the
  full global orientation path retained as a fallback.
- Add a Criterion remove_vertex benchmark covering successful removals and
  invalid-remnant rollback across 2D through 5D.
@acgetchell acgetchell self-assigned this May 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: a9658180-3c1f-4ffa-93f8-e75413ad7bed

📥 Commits

Reviewing files that changed from the base of the PR and between 53a73a6 and 50ed347.

📒 Files selected for processing (2)
  • src/core/insertion.rs
  • src/core/repair.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/insertion.rs
  • src/core/repair.rs

Walkthrough

Refactors vertex removal into a transactional fan-retriangulation flow with bounded orientation normalization and incidence-pointer repair; adds insertion repair-budgeting; introduces a Criterion benchmark for remove_vertex (2D–5D) and updates Cargo/docs, CI/nextest installation, justfile recipes, and example-run script.

Changes

Vertex Removal Refactoring and Benchmarking

Layer / File(s) Summary
Refactored remove_vertex orchestration
src/core/repair.rs
Main entry now gathers simplices via find_simplices_containing_vertex, computes boundary facets and affected vertices, selects a fan apex, snapshots for rollback, delegates fan-retriangulation, canonicalizes orientations, wires cavity neighbors, removes old simplices, and runs localized validation and incidence repair.
Insertion repair budgeting
src/core/insertion.rs
Adds per-insertion max_repair_simplices_removed derived from initial conflict/hull sets and passes remaining budget into repair_local_facet_issues_with_frontier each iteration.
Vertex removal support helpers and validation
src/core/repair.rs
Adds repair_vertex_removal_facet_issues, normalize_vertex_removal_orientation (bounded), repair_affected_vertex_incidence_from_scope, and validation scaffolding: affected_vertices_for_vertex_removal, live_simplices_from, vertex_removal_validation_scope, push_live_simplex_once, and postcondition validators; updates tests.
Benchmark implementation: fixtures, runners, and wiring
benches/remove_vertex.rs
Criterion benchmark with deterministic fixture generation (interior, near-boundary, cospherical, near-degenerate, large-coordinate), fixture search for removable vertices (success), minimal-simplex rollback fixtures (failure), per-dimension runners (2D–5D), throughput as vertices+simplices, and criterion_group!/criterion_main! registration.
Benchmark configuration and documentation
Cargo.toml, benches/README.md
Adds [[bench]] target remove_vertex (harness = false) to Cargo.toml and documents the benchmark in benches/README.md with usage cargo bench --profile perf --bench remove_vertex -- --noplot.
Tooling, CI and script updates
.github/workflows/*, justfile, docs/dev/tooling-alignment.md, scripts/run_all_examples.sh
Pins and installs/verifies cargo-nextest in CI/Codecov, adds _ensure-nextest and converts many justfile recipes to use cargo nextest run, documents local cargo-nextest install, and prebuilds examples then runs binaries in the example-run script.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RemoveVertex as remove_vertex
  participant Finder as find_simplices_containing_vertex
  participant FanHelper as remove_vertex_with_fan_retriangulation
  participant Cavity as fan_fill_cavity / fan_boundary_facets_excluding_apex
  participant Validator as validate_vertex_removal_postconditions
  Client->>RemoveVertex: request remove(vertex_key)
  RemoveVertex->>Finder: find incident simplices
  RemoveVertex->>RemoveVertex: compute boundary & affected_vertices
  RemoveVertex->>FanHelper: snapshot, choose apex, delegate retriangulation
  FanHelper->>Cavity: fan_fill_cavity (exclude apex)
  FanHelper->>FanHelper: canonicalize orientations
  FanHelper->>FanHelper: wire cavity neighbors, remove old simplices
  FanHelper->>Validator: run localized normalization & postcondition checks
  FanHelper->>Client: return Ok or Err (with rollback)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I nibble code and seed each run,
Fans fill cavities till the work is done,
Orientation loops stay bounded and neat,
Benches hop through 2D to 5D beat,
A happy rabbit cheers the merge — light on its feet!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'perf(core): localize remove_vertex repair' directly and clearly describes the main change: optimization of the remove_vertex repair process through localization.
Description check ✅ Passed The description is relevant to the changeset, detailing the three main objectives: avoiding global scans through scoped repair, promoting orientation validation over the removal scope, and adding benchmarks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 100.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/remove-vertex-local-postconditions

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

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 19, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 53 complexity

Metric Results
Complexity 53

View in Codacy

🟢 Coverage 90.61% diff coverage · +0.03% coverage variation

Metric Results
Coverage variation +0.03% coverage variation (-1.00%)
Diff coverage 90.61% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (dadb724) 61899 55992 90.46%
Head commit (50ed347) 62304 (+405) 56379 (+387) 90.49% (+0.03%)

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 (#401) 479 434 90.61%

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%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation enhancement New feature or request rust Pull requests that update rust code geometry Geometry-related issues topology labels May 19, 2026
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (2)
benches/remove_vertex.rs (1)

59-175: ⚖️ Poor tradeoff

Consider expanding adversarial geometric coverage in success fixtures.

The success benchmark fixtures currently use well-conditioned interior points placed within a canonical simplex. The rollback case covers a degenerate scenario (minimal simplex), but the success path would benefit from additional adversarial geometric inputs such as near-boundary points, cospherical sets, and large coordinates to stress-test removal performance under challenging conditions.

As per coding guidelines: "Include adversarial inputs (near-boundary points, cospherical sets, degenerate simplices, large coordinates) alongside well-conditioned inputs in both tests and benchmarks in Rust"

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benches/remove_vertex.rs` around lines 59 - 175, The success fixtures only
generate well-conditioned interior points inside a canonical simplex
(generate_vertices, normalized_positive_direction, simplex_points), so expand
adversarial geometric coverage by adding alternative point generators
(near-boundary, cospherical, near-degenerate/simplex, large-coordinate) and
trying them when building the source in build_success_source; implement small
helper generators (e.g., generate_near_boundary_points,
generate_cospherical_points, generate_large_coordinate_points,
generate_near_degenerate_simplex) that accept the same seed and count and return
Point<f64, D> arrays, then update generate_vertices to select between the
original interior generator and these adversarial generators based on the seed
or an attempt index and include their points into vertices before calling
DelaunayTriangulation::new; ensure successful_removal_vertex is still used to
validate and that seed search attempts exercise all generator types so
benchmarks include near-boundary, cospherical, degenerate, and large-coordinate
cases.
src/core/repair.rs (1)

456-473: 💤 Low value

Consider naming the iteration bound constant.

The hardcoded 3 in the orientation normalization loop lacks documentation. Per coding guidelines, repair paths should use explicit budgets. A named constant would clarify the convergence rationale.

💡 Suggested improvement
+/// Maximum local orientation-normalization attempts before falling back to global promotion.
+const MAX_LOCAL_ORIENTATION_ATTEMPTS: usize = 3;
+
 fn normalize_vertex_removal_orientation(
     &mut self,
     validation_scope: &SimplexKeyBuffer,
 ) -> Result<(), InvariantError> {
-    for _ in 0..3 {
+    for _ in 0..MAX_LOCAL_ORIENTATION_ATTEMPTS {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/repair.rs` around lines 456 - 473, Replace the magic number 3 in the
retry loop with a named constant to document the orientation-normalization
budget: define a constant (e.g., ORIENTATION_NORMALIZATION_ATTEMPTS or
ORIENTATION_NORMALIZATION_BUDGET) and use it in the for loop that calls
canonicalize_positive_orientation_for_simplices,
tds.normalize_coherent_orientation, and
validate_geometric_simplex_orientation_for_simplices; keep the existing behavior
but centralize the value so future readers and maintainers understand the
convergence attempt limit.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@benches/remove_vertex.rs`:
- Around line 59-175: The success fixtures only generate well-conditioned
interior points inside a canonical simplex (generate_vertices,
normalized_positive_direction, simplex_points), so expand adversarial geometric
coverage by adding alternative point generators (near-boundary, cospherical,
near-degenerate/simplex, large-coordinate) and trying them when building the
source in build_success_source; implement small helper generators (e.g.,
generate_near_boundary_points, generate_cospherical_points,
generate_large_coordinate_points, generate_near_degenerate_simplex) that accept
the same seed and count and return Point<f64, D> arrays, then update
generate_vertices to select between the original interior generator and these
adversarial generators based on the seed or an attempt index and include their
points into vertices before calling DelaunayTriangulation::new; ensure
successful_removal_vertex is still used to validate and that seed search
attempts exercise all generator types so benchmarks include near-boundary,
cospherical, degenerate, and large-coordinate cases.

In `@src/core/repair.rs`:
- Around line 456-473: Replace the magic number 3 in the retry loop with a named
constant to document the orientation-normalization budget: define a constant
(e.g., ORIENTATION_NORMALIZATION_ATTEMPTS or ORIENTATION_NORMALIZATION_BUDGET)
and use it in the for loop that calls
canonicalize_positive_orientation_for_simplices,
tds.normalize_coherent_orientation, and
validate_geometric_simplex_orientation_for_simplices; keep the existing behavior
but centralize the value so future readers and maintainers understand the
convergence attempt limit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 6630e8ef-9d3f-4fdd-882f-a49c6722045d

📥 Commits

Reviewing files that changed from the base of the PR and between dadb724 and 655096c.

📒 Files selected for processing (4)
  • Cargo.toml
  • benches/README.md
  • benches/remove_vertex.rs
  • src/core/repair.rs

@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

❌ Patch coverage is 90.60543% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.48%. Comparing base (dadb724) to head (50ed347).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/core/repair.rs 92.50% 35 Missing ⚠️
src/core/insertion.rs 16.66% 10 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #401      +/-   ##
==========================================
+ Coverage   90.44%   90.48%   +0.03%     
==========================================
  Files          71       71              
  Lines       61695    62100     +405     
==========================================
+ Hits        55802    56189     +387     
- Misses       5893     5911      +18     
Flag Coverage Δ
unittests 90.48% <90.60%> (+0.03%) ⬆️

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.

- Add adversarial remove_vertex benchmark fixtures for near-boundary,
  cospherical, near-degenerate, and large-coordinate point sets.
- Include the selected fixture kind in Criterion case names so benchmark
  output shows which geometry each run exercises.
- Name the vertex-removal orientation normalization budget.
Add focused regression coverage for vertex-removal helper behavior:
- affected-vertex collection and missing-simplex error preservation
- local validation-scope deduplication and live-simplex filtering
- incident-simplex repair, isolated-vertex reporting, and postcondition failure
- fan-boundary filtering for facets that already contain the apex
Copy link
Copy Markdown
Contributor

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/repair.rs`:
- Around line 415-447: repair_vertex_removal_facet_issues currently calls
repair_local_facet_issues_with_frontier without a budget; add a
max_simplices_removed parameter to repair_vertex_removal_facet_issues and thread
it into repair_local_facet_issues_with_frontier (and any intermediate helpers)
so the local-facet repair honors the same explicit budget as the public repair
entry (e.g., repair_facet_oversharing). Change
repair_local_facet_issues_with_frontier to accept the limit and return a typed
non-convergence error when the limit is exceeded, map that error into the
existing Result<..., InvariantError> path (same pattern used by the public entry
point), only apply removed counts when within the budget, and ensure callers
handle and propagate the typed failure instead of allowing unbounded removals.
- Around line 441-447: The current branch calls repair_neighbor_pointers(&mut
self.tds) after a successful removal which triggers a full-TDS scan; instead,
reuse the scoped neighbor-repair helper used elsewhere (the logic in
repair_neighbors_after_local_simplex_removal) and invoke it with the
post_repair_frontier so neighbor fixes remain local and use the existing
local-repair/fallback flow. Replace the repair_neighbor_pointers(&mut self.tds)
call in the removed > 0 path with the scoped helper invocation that accepts
self.tds (or a mutable reference) and post_repair_frontier, preserving error
mapping via insertion_error_to_invariant_error as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3ef66234-834f-409d-b8a9-ae393dc971fc

📥 Commits

Reviewing files that changed from the base of the PR and between 3ca92fb and 02dad75.

📒 Files selected for processing (1)
  • src/core/repair.rs

Comment thread src/core/repair.rs Outdated
Comment thread src/core/repair.rs Outdated
- Preserve local repair removal budgets before mutating the TDS.
- Reuse scoped neighbor repair after vertex-removal facet cleanup.
- Install and verify cargo-nextest for faster CI test recipes.
- Build examples once before running their compiled binaries.
Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/insertion.rs (1)

1695-1729: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Budget the interior repair loop from new_simplices, not the removed conflict set.

This loop only detects and removes simplices from new_simplices, but the cap is based on conflict_simplices.len(). In higher dimensions the cavity boundary can produce more new simplices than conflicted ones, so a valid local repair can now fail early with MaxSimplicesRemovedExceeded even though it never leaves the inserted star.

♻️ Suggested fix
-        let max_repair_simplices_removed = conflict_simplices.len();
+        let max_repair_simplices_removed = new_simplices.len();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/insertion.rs` around lines 1695 - 1729, The repair loop budgets
removals using max_repair_simplices_removed = conflict_simplices.len(), but the
loop only scans and removes from new_simplices, so in high-dimensions the number
of new_simplices can exceed the conflict set and trigger
MaxSimplicesRemovedExceeded prematurely; change the budget to derive from
new_simplices.len() (or the remaining size of new_simplices) instead of
conflict_simplices.len() so repair_budget_remaining is calculated against the
pool actually being modified (update the binding that sets
max_repair_simplices_removed and the subsequent use in repair_budget_remaining
passed to repair_local_facet_issues_with_frontier, referencing new_simplices,
total_removed, and repair_local_facet_issues_with_frontier).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/repair.rs`:
- Around line 416-460: The current repair_vertex_removal_facet_issues does
one-pass repair; change it to an iterative, budgeted loop that repeatedly calls
detect_local_facet_issues() and then
repair_local_facet_issues_with_frontier(&issues,
max_simplices_removed_remaining) until either detect_local_facet_issues()
returns None (no issues), validate_facet_sharing() (or equivalent check)
succeeds, or the max_simplices_removed budget is exhausted. In each iteration
update *simplices_removed, decrement the remaining budget, extend
post_repair_frontier with repair_outcome.frontier_simplices, and call
repair_neighbors_after_local_simplex_removal(new_simplices,
post_repair_frontier) when removed>0; if the budget is exhausted return a typed
non-convergence InvariantError (similar to insertion path error handling)
instead of silently returning Ok.

---

Outside diff comments:
In `@src/core/insertion.rs`:
- Around line 1695-1729: The repair loop budgets removals using
max_repair_simplices_removed = conflict_simplices.len(), but the loop only scans
and removes from new_simplices, so in high-dimensions the number of
new_simplices can exceed the conflict set and trigger
MaxSimplicesRemovedExceeded prematurely; change the budget to derive from
new_simplices.len() (or the remaining size of new_simplices) instead of
conflict_simplices.len() so repair_budget_remaining is calculated against the
pool actually being modified (update the binding that sets
max_repair_simplices_removed and the subsequent use in repair_budget_remaining
passed to repair_local_facet_issues_with_frontier, referencing new_simplices,
total_removed, and repair_local_facet_issues_with_frontier).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3b52fbe5-ab56-4bf3-8da4-d339d2e635e2

📥 Commits

Reviewing files that changed from the base of the PR and between 02dad75 and 53a73a6.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • .github/workflows/codecov.yml
  • docs/dev/tooling-alignment.md
  • justfile
  • scripts/run_all_examples.sh
  • src/core/insertion.rs
  • src/core/repair.rs
✅ Files skipped from review due to trivial changes (1)
  • docs/dev/tooling-alignment.md

Comment thread src/core/repair.rs
@acgetchell acgetchell enabled auto-merge (squash) May 19, 2026 23:00
- Iterate vertex-removal facet repair until facet sharing is valid or the
  explicit simplex-removal budget is exhausted.
- Preserve typed invariant failures when local repair cannot converge.
- Size insertion repair budgets from the new simplex pool being repaired.
@acgetchell acgetchell merged commit 382f9e1 into main May 19, 2026
22 checks passed
@acgetchell acgetchell deleted the perf/remove-vertex-local-postconditions branch May 19, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request 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