Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ jobs:
run: |
set -euo pipefail

results_file="benches/compare_results.txt"
results_file="benches/main_vs_release_compare_results.txt"

# Successful compare step => no regressions beyond configured threshold.
if [ "${{ steps.compare_regression.outcome }}" = "success" ]; then
Expand Down Expand Up @@ -258,7 +258,7 @@ jobs:
with:
name: performance-regression-results-${{ github.run_number }}
path: |
benches/compare_results.txt
benches/main_vs_release_compare_results.txt
baseline-artifact/baseline_results.txt
if-no-files-found: warn
retention-days: 30
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
/cobertura.xml
/logs
/benches/results
/benches/compare_results.txt
/benches/main_vs_release_compare_results.txt
/benches/worktree_vs_*_compare_results.txt
/node_modules/
/.package-lock.json
/package-lock.json
Expand Down
21 changes: 15 additions & 6 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@ keywords:
- "simplicial-complex"
- "triangulation"
abstract: >-
A Rust library for d-dimensional Delaunay triangulations and convex hulls
inspired by CGAL. Features exact arithmetic predicates with Simulation of
Simplicity (SoS) for deterministic degeneracy resolution, a 4-level
validation hierarchy (element, structural, manifold, Delaunay), bistellar
flip editing and repair, and serialization support. Supports 2D through 5D
with safe Rust (no unsafe code).
Rust crate providing D-dimensional Delaunay triangulations and convex hulls
(2D through 5D explicitly tested) constructed with a PL-manifold (default) or
pseudomanifold guarantee on finite point sets with Euclidean and toroidal
global topologies. Uses exact predicates and Simulation of Simplicity for
robustness and degeneracy handling, and Hilbert curves for deterministic
insertion ordering and efficient spatial indexing. Provides an explicit
4-level validation hierarchy on individual elements, triangulation data
structure validity, manifold topology, and Delaunay property adherence.
Allows for the complete set of Pachner moves up to D=5 using bistellar flips,
vertex insertion and deletion, and the conversion of non-Delaunay
triangulations into Delaunay triangulations via bounded flip/rebuilds.
Auxiliary data may be stored directly in vertices and simplices with external
secondary maps provided for vertex- and simplex-keyed algorithm use, and the
entire data structure is serializable/deserializable. Written in safe Rust
with no unsafe code.
license: BSD-3-Clause
32 changes: 24 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,22 @@
[![Audit dependencies](https://github.com/acgetchell/delaunay/actions/workflows/audit.yml/badge.svg)](https://github.com/acgetchell/delaunay/actions/workflows/audit.yml)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/3cad94f994f5434d877ae77f0daee692)](https://app.codacy.com/gh/acgetchell/delaunay/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)

A reusable, tested, and benchmarked [Rust] software artifact for
dimension-generic Delaunay triangulations and convex hulls in finite Euclidean
and periodic point sets, with exact predicates, Simulation-of-Simplicity
degeneracy handling, [PL-manifold]-aware local moves, and exposed bistellar
flips ([Pachner moves]) inspired by [CGAL].
[Rust] crate providing D-dimensional [Delaunay triangulations] and
[convex hulls][Convex hulls] (2D through 5D explicitly tested) constructed with
a [PL-manifold] (default) or [pseudomanifold][Pseudomanifold] guarantee on
finite point sets with Euclidean and toroidal global topologies. Uses
[exact predicates] and [Simulation of Simplicity] for robustness and degeneracy
handling, and [Hilbert curve]s for deterministic insertion ordering and
efficient spatial indexing. Provides an explicit
[4-level validation hierarchy][Validation Guide] on individual elements,
triangulation data structure validity, manifold topology, and Delaunay property
adherence. Allows for the complete set of [Pachner moves] up to D=5 using
bistellar flips, vertex insertion and deletion, and the conversion of
non-Delaunay triangulations into Delaunay triangulations via bounded
flip/rebuilds. Auxiliary data may be stored directly in vertices and simplices
with external [secondary maps][Secondary maps] provided for vertex- and
simplex-keyed algorithm use, and the entire data structure is
serializable/deserializable. Written in safe Rust with no unsafe code.

## 📐 Introduction

Expand Down Expand Up @@ -91,9 +102,10 @@ complete technical background.
## ✨ Features

- [x] Copyable data types associated with vertices and cells (integers,
floats, chars, custom enums)
- [x] d-dimensional [Delaunay triangulations]
- [x] d-dimensional [Convex hulls]
floats, chars, custom enums), plus `CellSecondaryMap` and
`VertexSecondaryMap` aliases for caller-owned key-indexed algorithm state
- [x] D-dimensional [Delaunay triangulations]
- [x] D-dimensional [Convex hulls]
- [x] Bistellar flip / [Pachner moves] Edit API up to 5D: k-flips for
k = 1, 2, 3 plus inverse moves
- [x] [Delaunay repair] using bistellar flips for k=2/k=3 with inverse
Expand Down Expand Up @@ -522,6 +534,10 @@ Portions of this library were developed with the assistance of these AI tools:
[Voronoi diagrams]: https://en.wikipedia.org/wiki/Voronoi_diagram
[Convex hulls]: https://en.wikipedia.org/wiki/Convex_hull
[Hilbert curve]: https://en.wikipedia.org/wiki/Hilbert_curve
[exact predicates]: docs/numerical_robustness_guide.md
[Simulation of Simplicity]: docs/numerical_robustness_guide.md#simulation-of-simplicity-sos
[Secondary maps]: docs/workflows.md#builder-api-auxiliary-vertex-and-cell-data
[Validation Guide]: docs/validation.md
[ChatGPT]: https://openai.com/chatgpt
[Claude]: https://www.anthropic.com/claude
[CodeRabbit]: https://coderabbit.ai/
Expand Down
8 changes: 4 additions & 4 deletions benches/PERFORMANCE_RESULTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,11 @@ insphere query performance independently from full triangulation workflows.

## Implementation Notes

### Performance Advantages of `insphere_lifted`
### Dimension-Dependent InSphere Predicate Performance

1. More efficient matrix formulation using relative coordinates
2. Avoids redundant circumcenter calculations
3. Optimized determinant computation
The tables above are the source of truth for predicate timing. `insphere_lifted`
shows advantages in lower dimensions such as 2D/3D, while `insphere_distance`
often wins in 4D/5D; boundary cases may favor `insphere` because of early exits.

## Benchmark Structure

Expand Down
27 changes: 24 additions & 3 deletions benches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ predicates fast across 2D-5D.
| Use Case | Command |
|----------|---------|
| Final local correctness check | `just ci` |
| Quick local large-scale wall-clock guard | `just perf-large-scale-smoke` |
| Fast local PR performance guard with temporary same-machine baseline | `just perf-no-regressions` |
| Full CI benchmark suite only | `just bench-ci` |
| Persist/update the default local baseline artifact | `just perf-baseline` |
Expand Down Expand Up @@ -52,11 +53,26 @@ The `perf` profile inherits from release and restores ThinLTO with
instead: it catches formatting, lint, test, documentation, example, and
benchmark-harness compile errors rather than publishing benchmark data.

`just perf-no-regressions [threshold]` is the recommended fast performance
check before pushing a PR that touches Rust or benchmark code:
`just perf-large-scale-smoke [max_secs]` is a coarse local wall-clock guard for
the release-mode large-scale harness. It runs the same 2D-5D cases as
`just debug-large-scale-{2,3,4,5}d` with their local defaults, caps each test
runtime at 60 seconds by default, and reports all failing dimensions before
exiting. This is useful while iterating, but it is not a Criterion comparison
and should not be treated as published benchmark data. Run it before pushing
Rust or benchmark changes to catch obvious local performance drift early.

For ordinary Rust or benchmark pushes, use the quick smoke guard with the usual
correctness checks:

```bash
just ci
just perf-large-scale-smoke
```

`just perf-no-regressions [threshold]` is the fuller branch-vs-main comparison
for performance-sensitive changes and PR-ready work:

```bash
just perf-no-regressions
```

Expand Down Expand Up @@ -298,7 +314,12 @@ uv run benchmark-utils compare-tags --old-tag vX.Y.Z --new-tag vA.B.C

Generated summaries should come from fresh perf-profile runs when they are used
as release evidence. For routine PR work, use `just ci` plus
`just perf-no-regressions`.
`just perf-no-regressions`. Release-baseline comparisons write
`benches/main_vs_release_compare_results.txt`; PR/ref comparisons write
`benches/worktree_vs_<ref>_compare_results.txt`. The comparison commands print
a short pass/regression/error status and the report path before exiting. The
local PR/ref guard fails on total matched-time regressions, while individual
regressions and improvements are surfaced in the report.

The generated `Triangulation Data Structure Performance` section is intentionally
first: it is built from the current `target/criterion` construction results,
Expand Down
2 changes: 1 addition & 1 deletion benches/ci_performance_suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ fn interior_facets_4d(dt: &FlipTriangulation4) -> Vec<FacetHandle> {
let mut facets = Vec::new();
for (cell_key, cell) in dt.cells() {
if let Some(neighbors) = cell.neighbors() {
for (facet_index, neighbor) in neighbors.iter().enumerate() {
for (facet_index, neighbor) in neighbors.enumerate() {
if neighbor.is_some() {
let Ok(facet_index) = u8::try_from(facet_index) else {
continue;
Expand Down
2 changes: 1 addition & 1 deletion docs/code_organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ just test-allocation # Run allocation profiling tests
just lint # All linting (code + docs + config)
just lint-code # Code linting (Rust, Python, Shell)
just lint-docs # Documentation linting (Markdown, Spelling)
just lint-config # Configuration validation (JSON, TOML, Actions)
just lint-config # Configuration checks (JSON, TOML, YAML/CFF, Actions)
just clippy # Run Clippy with strict settings
just doc-check # Validate documentation builds
just python-check # Python format/lint/typecheck checks
Expand Down
72 changes: 62 additions & 10 deletions docs/dev/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ Agents must run appropriate checks after modifying code.
- [Benchmark Profiles](#benchmark-profiles)
- [Examples](#examples)
- [Spell Checking](#spell-checking)
- [TOML Formatting](#toml-formatting)
- [TOML Checks](#toml-checks)
- [Shell Script Validation](#shell-script-validation)
- [JSON Validation](#json-validation)
- [CITATION.cff Validation](#citationcff-validation)
- [GitHub Actions Validation](#github-actions-validation)
- [Recommended Command Matrix](#recommended-command-matrix)
- [CI Expectations](#ci-expectations)
Expand Down Expand Up @@ -187,6 +188,13 @@ compile, lint, test, documentation, example, or benchmark-harness build errors.
Use `just bench-smoke` only for quick harness validation with minimal samples;
do not treat smoke output as performance data.

Use `just perf-large-scale-smoke [max_secs]` for a coarse local wall-clock guard
over the release-mode large-scale debug harness. It runs the same 2D-5D defaults
as `just debug-large-scale-{2,3,4,5}d`, caps each test runtime at 60 seconds by
default, and reports all failing dimensions before exiting. It does not compare
against a baseline and should not be treated as benchmark data. Run it before
pushing Rust or benchmark changes to catch obvious local performance drift early.

Use `just bench-perf-summary` from the release PR branch after version and
documentation updates. It runs fresh perf-profile summary benchmarks, records
the current Criterion construction metadata and generated simplex counts, and
Expand All @@ -196,22 +204,51 @@ Before pushing Rust or benchmark changes, run:

```bash
just ci
just perf-large-scale-smoke
```

For performance-sensitive changes and PR-ready work, also run:

```bash
just perf-no-regressions
```

`just perf-no-regressions` is the fast local PR guard. It runs
`just perf-no-regressions` is the fuller local PR guard. It runs
`ci_performance_suite` with the shared dev-mode Criterion arguments against a
temporary same-machine baseline generated from the current GitHub `main` ref.
The temporary baseline checkout and artifact directory are removed after the
comparison.
same-machine baseline generated from the current GitHub `main` ref. The guard
reuses a local cache under `baseline-artifacts/perf-no-regressions/` keyed by
the resolved `origin/main` commit and local Rust compiler version, and refreshes
that baseline when `main` or the compiler changes, or when the cached artifact
does not match the benchmark contract. The current worktree benchmark still runs
fresh each time so repeated comparisons can catch local performance drift.
The comparison report is written to
`benches/worktree_vs_main_compare_results.txt` by default so it is visibly a
branch/PR-vs-main check. The local guard exits nonzero only when benchmark
execution fails or total matched benchmark mean time regresses beyond the
threshold; individual benchmark regressions are warnings in the report. The
report also lists total, geomean, median, top regressions, and top improvements,
and the command prints a short terminal status with the report path.
`just clean` removes Criterion data under `target/`, but it does not remove this
local baseline cache.

```bash
just perf-no-regressions
```

`just perf-baseline` is optional and intentionally persistent: use it only when
you want to create or refresh `baseline-artifact/baseline_results.txt` for later
manual comparisons.
manual comparisons. `just perf-compare <file>` uses that release-baseline
workflow and writes `benches/main_vs_release_compare_results.txt` by default.
It follows the same terminal-status convention, but remains stricter: individual
benchmark regressions still make release-style comparisons fail.

For lower-level workflows, `uv run benchmark-utils ensure-ref-baseline --ref
<ref> --dev` prints the cached/generated same-machine baseline path for a branch
or version tag, and `uv run benchmark-utils fetch-baseline --ref <ref>` downloads
the GitHub Actions artifact instead. Use the generated local baseline for
same-machine regression checks; use the downloaded artifact when you explicitly
want CI-runner parity. `uv run benchmark-utils compare-ref --ref <ref>` writes
`benches/worktree_vs_<ref>_compare_results.txt` unless `--output` is supplied.

To generate a scratch baseline without replacing the default artifact, write it
somewhere else and compare directly:
Expand Down Expand Up @@ -271,13 +308,15 @@ under:

---

## TOML Formatting
## TOML Checks

TOML files should be validated and formatted using Taplo.
TOML files should parse cleanly, pass Taplo linting, and match Taplo
formatting.

Commands:

```bash
just toml-check
just toml-lint
just toml-fmt-check
just toml-fmt
Expand All @@ -302,10 +341,23 @@ Run via CI or `just` commands.

JSON files should be validated after edits.

Example:
Run:

```bash
just json-check
```

---

## CITATION.cff Validation

Citation metadata should pass both YAML style linting and CFF schema
validation.

Run:

```bash
jq empty file.json
just citation-check
```

---
Expand Down
33 changes: 25 additions & 8 deletions docs/dev/tooling-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ Both repositories now share the same core Rust and Python support-tooling loop:
- `.markdownlint.json`, `.yamllint`, and `typos.toml` define documentation and
configuration checks. Delaunay still uses Node-backed Markdownlint and
Prettier-for-YAML today; a Rust-native `dprint`/`pretty_yaml` and Markdown
linter migration remains a future tooling change.
linter migration remains a future tooling change. `CITATION.cff` is
YAML-style-linted with `.yamllint` and schema-validated through an
exact-version `uvx --from cffconvert==2.0.0` invocation, keeping
`cffconvert`'s old `jsonschema` constraint isolated from Semgrep's newer
dependency requirements.
- `justfile` is the local entry point for formatting, linting, tests,
coverage, Semgrep, changelog, setup commands, and supported Cargo feature
surface checks.
Expand Down Expand Up @@ -73,13 +77,26 @@ Some causal-triangulations tooling remains project-specific and was not ported:
- CDT's `performance-analysis` script; Delaunay keeps its benchmark helpers and
generated performance-summary workflow.
- Delaunay has a project-specific `just perf-no-regressions` recipe that runs
the calibrated 2D-5D `ci_performance_suite` canaries against the current
dev-mode baseline artifact. This stays local to Delaunay because the
benchmark contract, fixture sizes, and regression threshold are tied to this
library's triangulation performance expectations. `just perf-baseline [ref]`
generates that baseline on the developer's machine from a temporary checkout
of the requested GitHub ref so the comparison uses the same local hardware as
the current branch while GitHub Actions still publishes shared CI artifacts.
the calibrated 2D-5D `ci_performance_suite` canaries against a cached
same-machine dev-mode baseline for the current GitHub `main` commit. This
stays local to Delaunay because the benchmark contract, fixture sizes, and
regression threshold are tied to this library's triangulation performance
expectations. The cache lives under
`baseline-artifacts/perf-no-regressions/`, is keyed by the resolved
`origin/main` commit and local Rust compiler version, and is refreshed when
either key changes or the artifact no longer matches the benchmark contract.
The cache/validation logic lives in `benchmark-utils ensure-ref-baseline` and
`benchmark-utils compare-ref` so workflows can reuse the same behavior without
depending on justfile shell internals.
`compare-ref` writes branch/PR-vs-ref reports with explicit names such as
`benches/worktree_vs_main_compare_results.txt`, while release-baseline
comparisons use `benches/main_vs_release_compare_results.txt`. Ref
comparisons fail on total matched-time regressions and report individual
regressions/improvements as context; release-baseline comparisons remain
strict for individual regressions.
`just perf-baseline [ref]` remains the manual persistent-baseline workflow for
a requested GitHub ref, so ad-hoc comparisons still use the developer's local
hardware while GitHub Actions publishes shared CI artifacts.
- Delaunay keeps a single `profiling_suite` benchmark target for manual
large-scale and flamegraph work. The previous standalone large-scale target
was folded into that harness so `.github/workflows/profiling-benchmarks.yml`
Expand Down
17 changes: 17 additions & 0 deletions docs/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,23 @@ assert_eq!(dt.tds().cell(cell_key).unwrap().data(), Some(&42));
`set_vertex_data` and `set_cell_data` are safe O(1) operations — they modify only the
user-data field and do not invalidate geometry, topology, or Delaunay invariants.

For algorithm-local state keyed by existing vertices or cells, prefer the
caller-owned secondary-map aliases instead of mutating stored user data:

```rust
use delaunay::prelude::collections::{CellSecondaryMap, VertexSecondaryMap};

let mut visited_cells: CellSecondaryMap<bool> = CellSecondaryMap::new();
let mut vertex_order: VertexSecondaryMap<usize> = VertexSecondaryMap::new();

for (cell_key, _) in dt.cells() {
visited_cells.insert(cell_key, false);
}
for (order, (vertex_key, _)) in dt.vertices().enumerate() {
vertex_order.insert(vertex_key, order);
}
```

## Builder API: insertion statistics

If you need observability (or you want to handle skipped vertices explicitly), use
Expand Down
2 changes: 1 addition & 1 deletion examples/delaunayize_repair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ fn flip_then_repair_2d() -> Result<(), DelaunayizeRepairExampleError> {
let mut facets: Vec<_> = Vec::new();
for (ck, cell) in dt.cells() {
if let Some(neighbors) = cell.neighbors() {
for (i, n) in neighbors.iter().enumerate() {
for (i, n) in neighbors.enumerate() {
if let (Some(_), Ok(idx)) = (n, u8::try_from(i)) {
facets.push(FacetHandle::new(ck, idx));
}
Expand Down
Loading
Loading