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
61 changes: 60 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Return fallible facet iterators [#458](https://github.com/acgetchell/delaunay/pull/458)
- Hydrate TDS through validated UUID snapshots [#454](https://github.com/acgetchell/delaunay/pull/454) [#460](https://github.com/acgetchell/delaunay/pull/460)
- Normalize fallible constructors [#459](https://github.com/acgetchell/delaunay/pull/459) [#464](https://github.com/acgetchell/delaunay/pull/464)
- Borrow topology views from canonical storage [#472](https://github.com/acgetchell/delaunay/pull/472) [#474](https://github.com/acgetchell/delaunay/pull/474)
- Make topology views and boundaries owner-aware [#476](https://github.com/acgetchell/delaunay/pull/476)
- Split Pachner moves from vertex lifecycle edits [#477](https://github.com/acgetchell/delaunay/pull/477)
- Reject stale adjacency indexes [#451](https://github.com/acgetchell/delaunay/pull/451) [#463](https://github.com/acgetchell/delaunay/pull/463)
- Update tooling to Rust 1.96.0 [#430](https://github.com/acgetchell/delaunay/pull/430) [#431](https://github.com/acgetchell/delaunay/pull/431)

### Merged Pull Requests

- Split Pachner moves from vertex lifecycle edits [#477](https://github.com/acgetchell/delaunay/pull/477)
- Make topology views and boundaries owner-aware [#476](https://github.com/acgetchell/delaunay/pull/476)
- Borrow topology views from canonical storage [#472](https://github.com/acgetchell/delaunay/pull/472) [#474](https://github.com/acgetchell/delaunay/pull/474)
- Refactor!(tds): canonicalize incidence storage [#473](https://github.com/acgetchell/delaunay/pull/473)
- Streamline iterator helpers for diagnostics work [#471](https://github.com/acgetchell/delaunay/pull/471)
- Add common Delaunay result alias [#470](https://github.com/acgetchell/delaunay/pull/470)
- Add vertex construction macro [#469](https://github.com/acgetchell/delaunay/pull/469)
- Preserve setup failure messages [#468](https://github.com/acgetchell/delaunay/pull/468)
- Refactor/443 329 typed validation errors [#465](https://github.com/acgetchell/delaunay/pull/465)
Expand Down Expand Up @@ -62,14 +71,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Align shared development tooling pins with causal-triangulations and harden
benchmark baseline metadata parsing.

- [**breaking**] Add vertex construction macro [#469](https://github.com/acgetchell/delaunay/pull/469)
[`63228a0`](https://github.com/acgetchell/delaunay/commit/63228a06ffca2ee8d68806823995d92ebfa84525)

- Add `vertex!` as a fallible constructor for coordinate-only and data-bearing vertices.
- Export the macro through the root, construction, and triangulation preludes.
- Migrate public docs, examples, and benchmark setup to prefer `vertex!` for incidental vertex construction.
- Retire the Semgrep rule that banned `vertex!` and document the new Rust style guidance.
- Add common Delaunay result alias [#470](https://github.com/acgetchell/delaunay/pull/470)
[`db46fa2`](https://github.com/acgetchell/delaunay/commit/db46fa2dd1518c1a47defda676453df8829665a6)

- Add DelaunayError and DelaunayResult for common construction, insertion, validation, coordinate conversion, and toroidal-domain setup workflows.
- Re-export the aliases from the crate root and construction preludes for downstream examples and applications.
- Update public docs and examples to use DelaunayResult when workflow-specific errors are not required.

### Changed

Expand Down Expand Up @@ -165,11 +179,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Report generated simplex counts in the large-scale smoke benchmark output.
- Refactor/443 329 typed validation errors [#465](https://github.com/acgetchell/delaunay/pull/465)
[`f6a85e8`](https://github.com/acgetchell/delaunay/commit/f6a85e84d43a81f1d5fdd8ec4c20e65e303dfe40)
- Streamline iterator helpers for diagnostics work [#471](https://github.com/acgetchell/delaunay/pull/471)
[`1985e6f`](https://github.com/acgetchell/delaunay/commit/1985e6ff95d0a452a09034d27d1bc9b21414befa)

- Preserve point-generator preallocation while using fallible iterator control flow.
- Extract Poisson spacing checks and stream geometry test distance calculations.
- Gate benchmark setup tracing behind bench-logging and update diagnostics examples to use DelaunayResult.
- Refactor!(tds): canonicalize incidence storage [#473](https://github.com/acgetchell/delaunay/pull/473)
[`9a973df`](https://github.com/acgetchell/delaunay/commit/9a973dfa598692261d9ea475f179862e3e92de4d)
- [**breaking**] Borrow topology views from canonical storage [#472](https://github.com/acgetchell/delaunay/pull/472)
[#474](https://github.com/acgetchell/delaunay/pull/474) [`3b6d2d4`](https://github.com/acgetchell/delaunay/commit/3b6d2d4a71e0f40a2e8b1be3871696e9b8f13866)

- Return validated borrowed simplex vertex slices instead of owned or optional detached snapshots.
- Split convex hull facet access into detached `facet_handles()` and borrowed `facets(triangulation)` views with freshness checks.
- Make vertex and simplex payload setters checked mutations that report typed stale-key errors.
- Preserve fallback rebuild payload restoration through typed simplex-data restore errors.
- [**breaking**] Make topology views and boundaries owner-aware [#476](https://github.com/acgetchell/delaunay/pull/476)
[`db3fcb3`](https://github.com/acgetchell/delaunay/commit/db3fcb3a899db625f3e969c8d2fa098520826479)

- Replace raw TDS boundary queries with one-sided facet-incidence APIs and keep true boundary classification topology-aware.
- Add borrowed EdgeView, RidgeQuery, RidgeView, and RidgeLinkView surfaces around detached EdgeKey and RidgeCandidate values.
- Interpret boundary facets through GlobalTopology so periodic quotient self-identifications remain closed topology.
- Align Euler validation, focused preludes, docs, examples, benchmarks, and Semgrep rules with the new view/candidate/incidence model.
- [**breaking**] Split Pachner moves from vertex lifecycle edits [#477](https://github.com/acgetchell/delaunay/pull/477)
[`f6efe10`](https://github.com/acgetchell/delaunay/commit/f6efe10c776e08a52efe1d2a4b4e65cf56271cf2)

- Add a unified PachnerMove request/result API with attempt_pachner dispatch and a focused prelude::pachner import surface.
- Move vertex deletion into its own Delaunay module with typed DeleteVertexError and keep insertion/deletion terminology explicit through public docs and
examples.

- Keep primitive bistellar flip APIs available from delaunay::flips while hiding them from focused preludes intended for workflow users.
- Add Pachner stress coverage, delete_vertex benchmarks, and Semgrep rules that require fallible results to be consumed and workflow fixtures to use vertex!.
- Split long agent/development guidance into focused dev and architecture docs, including release citation and performance-tuning checklists.

### Dependencies

- Bump support tooling and smallvec [`1799d3c`](https://github.com/acgetchell/delaunay/commit/1799d3cbd4a5c01a10c490fffe15cc28b1f3784d)

### Documentation

- Refresh README and changelog [`b80d25a`](https://github.com/acgetchell/delaunay/commit/b80d25a40df9a06de8554a9900598bdf2a584dbf)

- Reorganize the README around a concise quickstart, documentation map,
ecosystem notes, benchmarking guidance, and current limitations.

- Preserve the citation abstract and detailed feature checklist while updating
quickstart guidance for the current published crate version.

- Regenerate the active and archived changelog files with current release-note
formatting.

### Fixed

- [**breaking**] Reject stale adjacency indexes [#451](https://github.com/acgetchell/delaunay/pull/451) [#463](https://github.com/acgetchell/delaunay/pull/463)
Expand Down
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ name = "delete_vertex"
path = "benches/delete_vertex.rs"
harness = false

[[bench]]
name = "edge_key_queries"
path = "benches/edge_key_queries.rs"
harness = false

[[bench]]
name = "tds_clone"
path = "benches/tds_clone.rs"
Expand Down
2 changes: 2 additions & 0 deletions benches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ predicates fast across 2D-5D.
| `pl_manifold_repair.rs` | Over-shared facet repair plus orphan cleanup | 3D synthetic repair fixtures | <1 min | PL-manifold repair tuning |
| `profiling_suite.rs` | Large-scale construction, memory, query, validation profiling | 2D/3D 10k, 4D 3k, 5D 1k | ~2-3 hr | Manual/monthly |
| `delete_vertex.rs` | Vertex deletion and rollback cost | 2D-5D fixed cases | ~1-5 min | Vertex deletion |
| `edge_key_queries.rs` | Public `EdgeKey` construction microbenchmarks | 2D-5D fixed live-edge cases | <1 min | Query API tuning |
| `tds_clone.rs` | `Tds::clone()` snapshot cost | Deterministic 2D-5D triangulations | ~1-3 min | Rollback design baselines |
| `topology_guarantee_construction.rs` | Cost of topology guarantee modes | 2D-5D construction cases | ~5-15 min | Manual topology policy work |

Expand Down Expand Up @@ -45,6 +46,7 @@ predicates fast across 2D-5D.
| PL-manifold repair path | `cargo bench --profile perf --features bench --bench pl_manifold_repair -- --noplot` |
| Large-scale scaling suite | `cargo bench --profile perf --bench profiling_suite -- --noplot` |
| Vertex deletion mutation baseline | `cargo bench --profile perf --bench delete_vertex -- --noplot` |
| Edge-key query construction | `cargo bench --profile perf --bench edge_key_queries -- --noplot` |
| One-dimension acceptance/profiling run | `just debug-large-scale-{2,3,4,5}d [n] [repair_every]` |
| Deep profiling | `cargo bench --profile perf --bench profiling_suite --features count-allocations` |

Expand Down
168 changes: 168 additions & 0 deletions benches/edge_key_queries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#![forbid(unsafe_code)]

//! Focused microbenchmarks for public edge-key query construction.
//!
//! Run with:
//!
//! ```bash
//! cargo bench --profile perf --bench edge_key_queries -- --noplot
//! ```

#[path = "common/bench_utils.rs"]
mod bench_utils;

use criterion::{
BenchmarkGroup, BenchmarkId, Criterion, criterion_group, criterion_main, measurement::WallTime,
};
use delaunay::prelude::construction::{DelaunayTriangulation, DelaunayTriangulationBuilder};
use delaunay::prelude::generators::generate_random_points_in_range_seeded;
use delaunay::prelude::geometry::{AdaptiveKernel, CoordinateRange};
use delaunay::prelude::tds::{EdgeKey, VertexKey};
use delaunay::try_vertices_from_points;
use std::{collections::BTreeSet, hint::black_box, time::Duration};

use bench_utils::{OrAbort, OrAbortWithContext, abort_benchmark};

const EDGE_PAIR_COUNT: usize = 128;
const COUNT_2D: usize = 1_000;
const COUNT_3D: usize = 350;
const COUNT_4D: usize = 80;
const COUNT_5D: usize = 35;
const SEED_2D: u64 = 9_321;
const SEED_3D: u64 = 9_322;
const SEED_4D: u64 = 9_323;
const SEED_5D: u64 = 9_324;
const SAMPLE_SIZE: usize = 32;

type BenchTriangulation<const D: usize> = DelaunayTriangulation<AdaptiveKernel<f64>, (), (), D>;

/// Holds a prebuilt triangulation and live edge endpoints selected outside the measured path.
struct EdgeKeyFixture<const D: usize> {
dt: BenchTriangulation<D>,
endpoint_pairs: Vec<(VertexKey, VertexKey)>,
vertex_count: usize,
simplex_count: usize,
}

/// Returns a deterministic coordinate range for benchmark point clouds.
fn benchmark_bounds() -> CoordinateRange<f64> {
CoordinateRange::try_new(-100.0_f64, 100.0).or_abort()
}

/// Builds a deterministic triangulation and live edge endpoint pairs for one dimension.
fn prepare_fixture<const D: usize>(count: usize, seed: u64) -> EdgeKeyFixture<D> {
let points =
generate_random_points_in_range_seeded::<D>(count, benchmark_bounds(), seed).or_abort();
let vertices = try_vertices_from_points(&points).or_abort();
let dt = DelaunayTriangulationBuilder::new(&vertices)
.build::<()>()
.or_abort();
let endpoint_pairs = late_simplex_edge_pairs(&dt);
if endpoint_pairs.len() < EDGE_PAIR_COUNT {
abort_benchmark(format_args!(
"{D}D EdgeKey benchmark found {} unique edge pairs, expected at least {EDGE_PAIR_COUNT}",
endpoint_pairs.len()
));
}
let _first_pair = endpoint_pairs.first().or_abort(format_args!(
"{D}D EdgeKey benchmark should contain at least one endpoint pair"
));

EdgeKeyFixture {
vertex_count: dt.number_of_vertices(),
simplex_count: dt.number_of_simplices(),
dt,
endpoint_pairs,
}
}

/// Selects unique live edges from the end of simplex storage to exercise lookup cost.
fn late_simplex_edge_pairs<const D: usize>(
dt: &BenchTriangulation<D>,
) -> Vec<(VertexKey, VertexKey)> {
let simplex_vertices: Vec<_> = dt
.simplices()
.map(|(_simplex_key, simplex)| simplex.vertices().to_vec())
.collect();
let mut pairs = BTreeSet::new();

for vertices in simplex_vertices.iter().rev() {
for i in 0..vertices.len() {
for j in (i + 1)..vertices.len() {
let first = vertices[i];
let second = vertices[j];
pairs.insert(canonical_pair(first, second));
if pairs.len() >= EDGE_PAIR_COUNT {
return pairs.into_iter().collect();
}
}
}
}

pairs.into_iter().collect()
}

/// Canonicalizes an endpoint pair without constructing the `EdgeKey` being benchmarked.
fn canonical_pair(first: VertexKey, second: VertexKey) -> (VertexKey, VertexKey) {
if first <= second {
(first, second)
} else {
(second, first)
}
}

/// Measures successful public `EdgeKey::try_new` construction for one dimension.
fn bench_edge_key_try_new<const D: usize>(
group: &mut BenchmarkGroup<'_, WallTime>,
fixture: &EdgeKeyFixture<D>,
) {
let tds = fixture.dt.tds();
let endpoint_pairs = &fixture.endpoint_pairs;

group.bench_function(
BenchmarkId::new(
format!("edge_key_try_new_{D}d"),
format!(
"vertices_{}_simplices_{}_edges_{}",
fixture.vertex_count,
fixture.simplex_count,
endpoint_pairs.len()
),
),
|b| {
b.iter(|| {
let mut edge_count = 0_usize;
for &(first, second) in endpoint_pairs {
let edge =
EdgeKey::try_new(tds, black_box(first), black_box(second)).or_abort();
black_box(edge);
edge_count += 1;
}
assert_eq!(edge_count, endpoint_pairs.len());
black_box(edge_count);
});
},
);
}

/// Runs edge-key construction benchmarks across supported practical dimensions.
fn edge_key_queries(c: &mut Criterion) {
let mut group = c.benchmark_group("edge_key_queries");
group.sample_size(SAMPLE_SIZE);
group.warm_up_time(Duration::from_secs(1));
group.measurement_time(Duration::from_secs(2));

let fixture_2d = prepare_fixture::<2>(COUNT_2D, SEED_2D);
let fixture_3d = prepare_fixture::<3>(COUNT_3D, SEED_3D);
let fixture_4d = prepare_fixture::<4>(COUNT_4D, SEED_4D);
let fixture_5d = prepare_fixture::<5>(COUNT_5D, SEED_5D);

bench_edge_key_try_new(&mut group, &fixture_2d);
bench_edge_key_try_new(&mut group, &fixture_3d);
bench_edge_key_try_new(&mut group, &fixture_4d);
bench_edge_key_try_new(&mut group, &fixture_5d);
group.finish();
}

criterion_group!(benches, edge_key_queries);
criterion_main!(benches);
41 changes: 41 additions & 0 deletions docs/api_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,47 @@ Topology APIs use names to make ownership visible:
full-TDS clone rollback with a journaled or localized design remains tracked
by #364.

### Simplex-Local Incidence Query Vocabulary

The public incidence-query surface names topology by simplex dimension, not by
one downstream move type:

| Concept | Simplex dimension | Current public shape |
|---|---:|---|
| Vertex | 0 | `VertexKey`, `adjacent_simplices(vertex)` |
| Edge | 1 | `EdgeKey`, `EdgeView`, `incident_edges(vertex)` |
| Ridge | `D - 2` | `RidgeCandidate<D>`, `RidgeQuery<'tds>`, `RidgeView<'tds>` |
| Facet | `D - 1` | `FacetHandle`, `FacetView<'tds>`, `FacetToSimplicesIndex<'tds, ...>` |
| Cell | `D` | `SimplexKey`, `Simplex<V, D>` |

In 2D, an edge is also a cell facet. The first public edge-to-facet bridge is
therefore 2D-specific:

```rust
dt.try_incident_facets_to_edge_2d(edge)
dt.try_interior_facet_for_edge_2d(edge)
```

`try_incident_facets_to_edge_2d` parses the detached edge key against the
current TDS and returns the current simplex-local facet handles for that edge:
one handle for a boundary edge and two for an interior edge in a valid 2D PL
manifold. `try_interior_facet_for_edge_2d` returns one of those handles only
when the edge has exactly two incident 2D facets, making it suitable for
consumer code that needs a `FacetHandle` for a 2D k=2 local move. On
deliberately invalid low-level topology, non-manifold edge multiplicity is
visible through `try_incident_facets_to_edge_2d`; the narrower
`try_interior_facet_for_edge_2d` still returns `Ok(None)` because the edge is not
a two-sided 2D move support.

These queries are read-only and do not expose a mutable cache. Implementations
may use neighbor walks, maintained TDS incidence, or lifetime-bound derived
indexes internally, but the public contract is stable: detached `*Key` and
`*Handle` inputs are revalidated against the current live owner. Stale keys and
corrupted incidence metadata return typed parse errors rather than being
silently conflated with empty topology. Higher-dimensional incidence should
generalize through simplex-key and ridge/facet/cell vocabulary instead of
treating edge-to-facet as universal.

Runtime generation or identity checks remain appropriate for detached handles,
owned snapshots, serialization boundaries, persistent performance caches, and
tests that intentionally construct inconsistent topology. They should not be
Expand Down
Loading
Loading