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
40 changes: 16 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,31 +121,26 @@ prelude map and namespace policy, see the

```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError,
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex,
};
use delaunay::prelude::geometry::CoordinateConversionError;

#[derive(Debug, thiserror::Error)]
enum ExampleError {
#[error(transparent)]
Construction(#[from] DelaunayTriangulationConstructionError),
#[error(transparent)]
Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError),
Coordinate(#[from] CoordinateConversionError),
}

fn main() -> Result<(), ExampleError> {
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0])
?,
delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0])
?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0])
?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0])
?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0])
?,
delaunay::prelude::Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2])
?,
Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([1.0, 0.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 1.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 1.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 0.0, 1.0])?,
Vertex::<(), _>::try_new([0.2, 0.2, 0.2, 0.2])?,
];

let dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?;
Expand All @@ -168,31 +163,28 @@ For coordinate wrapping on a toroidal domain, use

```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyKind,
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyKind, Vertex,
ToroidalDomainError,
};
use delaunay::prelude::geometry::CoordinateConversionError;

#[derive(Debug, thiserror::Error)]
enum ExampleError {
#[error(transparent)]
Construction(#[from] DelaunayTriangulationConstructionError),
#[error(transparent)]
Coordinate(#[from] delaunay::prelude::geometry::CoordinateConversionError),
Coordinate(#[from] CoordinateConversionError),
#[error(transparent)]
Topology(#[from] ToroidalDomainError),
}

fn main() -> Result<(), ExampleError> {
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.2])
?,
delaunay::prelude::Vertex::<(), _>::try_new([0.8, 0.3])
?,
delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.7])
?,
Vertex::<(), _>::try_new([0.1, 0.2])?,
Vertex::<(), _>::try_new([0.8, 0.3])?,
Vertex::<(), _>::try_new([0.5, 0.7])?,
// Wraps to [0.2, 0.4].
delaunay::prelude::Vertex::<(), _>::try_new([1.2, 0.4])
?,
Vertex::<(), _>::try_new([1.2, 0.4])?,
];

let dt = DelaunayTriangulationBuilder::new(&vertices)
Expand Down
8 changes: 4 additions & 4 deletions docs/ORIENTATION_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ drive repair, but replacement-simplex orientation itself uses `robust_orientatio

`src/delaunay/builder.rs` normalizes explicit and periodic construction:

- `from_vertices_and_simplices(...)` accepts user-provided simplex orderings, assembles
the TDS, calls `normalize_and_promote_positive_orientation()`, validates TDS
structure/topology, rejects geometrically degenerate simplices, and then enforces
the Delaunay property.
- `try_from_vertices_and_simplices(...)` validates user-provided simplex specs
before storage, assembles the TDS, calls `normalize_and_promote_positive_orientation()`,
validates TDS structure/topology, rejects geometrically degenerate simplices,
and then enforces the Delaunay property.
- `.try_toroidal([..])` builds an image-point triangulation and then runs
orientation normalization, lifted geometric orientation validation, final
Levels 1-3 topology validation, and final Level 4 Delaunay validation before
Expand Down
70 changes: 43 additions & 27 deletions docs/api_design.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ For most use cases, the builder with default options is sufficient:

```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError,
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex,
};
use delaunay::prelude::geometry::CoordinateConversionError;
use delaunay::prelude::insertion::InsertionError;
use delaunay::prelude::tds::InvariantError;

Expand All @@ -79,20 +80,22 @@ enum ExampleError {
Insertion(#[from] InsertionError),
#[error(transparent)]
Topology(#[from] InvariantError),
#[error(transparent)]
Coordinate(#[from] CoordinateConversionError),
}

fn main() -> Result<(), ExampleError> {
// Simple construction from vertices (Euclidean space, default options)
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
];
let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?;

// Incremental insertion (maintains Delaunay property)
let new_vertex = delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?;
let new_vertex = Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?;
dt.insert(new_vertex)?;

// Vertex removal (topology-preserving, with automatic repair when enabled)
Expand All @@ -111,7 +114,9 @@ use `DelaunayTriangulationBuilder`:
```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, TopologyGuarantee,
Vertex,
};
use delaunay::prelude::geometry::CoordinateConversionError;
use delaunay::prelude::insertion::InsertionError;
use delaunay::prelude::validation::ValidationPolicy;

Expand All @@ -121,14 +126,16 @@ enum ExampleError {
Construction(#[from] DelaunayTriangulationConstructionError),
#[error(transparent)]
Insertion(#[from] InsertionError),
#[error(transparent)]
Coordinate(#[from] CoordinateConversionError),
}

fn main() -> Result<(), ExampleError> {
// Canonicalized toroidal triangulation in 2D
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.1, 0.1])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.9, 0.9])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5])?,
Vertex::<(), _>::try_new([0.1, 0.1])?,
Vertex::<(), _>::try_new([0.9, 0.9])?,
Vertex::<(), _>::try_new([0.5, 0.5])?,
];

let mut dt = DelaunayTriangulationBuilder::new(&vertices)
Expand All @@ -140,7 +147,7 @@ fn main() -> Result<(), ExampleError> {
dt.set_validation_policy(ValidationPolicy::Always);

// Works like any other DelaunayTriangulation
dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.75])?)?;
dt.insert(Vertex::<(), _>::try_new([0.25, 0.75])?)?;
Ok(())
}
```
Expand Down Expand Up @@ -189,33 +196,36 @@ The Edit API is exposed through the `BistellarFlips` trait in `prelude::flips`:

```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError,
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex,
};
use delaunay::prelude::flips::*;
use delaunay::prelude::geometry::CoordinateConversionError;

#[derive(Debug, thiserror::Error)]
enum ExampleError {
#[error(transparent)]
Construction(#[from] DelaunayTriangulationConstructionError),
#[error(transparent)]
Flip(#[from] FlipError),
#[error(transparent)]
Coordinate(#[from] CoordinateConversionError),
}

fn main() -> Result<(), ExampleError> {
// Start with a valid triangulation
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
];
let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?;

// k=1 move: Insert a vertex into a simplex (splits simplex into D+1 simplices)
let Some((simplex_key, _)) = dt.simplices().next() else {
return Ok(());
};
let info = dt.flip_k1_insert(simplex_key, delaunay::prelude::Vertex::<(), _>::try_new([0.25, 0.25, 0.25])?)?;
let info = dt.flip_k1_insert(simplex_key, Vertex::<(), _>::try_new([0.25, 0.25, 0.25])?)?;

// k=1 inverse: Remove a vertex (collapses its star)
let vertex_key = info.inserted_face_vertices[0];
Expand Down Expand Up @@ -312,9 +322,10 @@ You can mix both APIs in the same workflow:

```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError,
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex,
};
use delaunay::prelude::flips::*;
use delaunay::prelude::geometry::CoordinateConversionError;
use delaunay::prelude::insertion::InsertionError;

#[derive(Debug, thiserror::Error)]
Expand All @@ -325,20 +336,22 @@ enum ExampleError {
Insertion(#[from] InsertionError),
#[error(transparent)]
Flip(#[from] FlipError),
#[error(transparent)]
Coordinate(#[from] CoordinateConversionError),
}

fn main() -> Result<(), ExampleError> {
// 1. Build initial triangulation (Builder API)
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
];
let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?;

// 2. Add vertices using Builder API (maintains Delaunay)
dt.insert(delaunay::prelude::Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?)?;
dt.insert(Vertex::<(), _>::try_new([0.5, 0.5, 0.5])?)?;

// 3. Make custom topology edits (Edit API)
let facet = /* ... */;
Expand Down Expand Up @@ -422,26 +435,29 @@ common "repair topology then restore Delaunay" workflow:

```rust
use delaunay::prelude::construction::{
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError,
DelaunayTriangulationBuilder, DelaunayTriangulationConstructionError, Vertex,
};
use delaunay::prelude::delaunayize::{
DelaunayizeConfig, DelaunayizeError, delaunayize_by_flips,
};
use delaunay::prelude::geometry::CoordinateConversionError;

#[derive(Debug, thiserror::Error)]
enum ExampleError {
#[error(transparent)]
Construction(#[from] DelaunayTriangulationConstructionError),
#[error(transparent)]
Delaunayize(#[from] DelaunayizeError),
#[error(transparent)]
Coordinate(#[from] CoordinateConversionError),
}

fn main() -> Result<(), ExampleError> {
let vertices = vec![
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
delaunay::prelude::Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([1.0, 0.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 1.0, 0.0])?,
Vertex::<(), _>::try_new([0.0, 0.0, 1.0])?,
];
let mut dt = DelaunayTriangulationBuilder::new(&vertices).build::<()>()?;

Expand Down
57 changes: 44 additions & 13 deletions docs/dev/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,19 @@ types and where already-validated values are merely assembled.

Use fallible names for raw or invariant-bearing input:

- `try_new*`, `try_from_*`, `parse`, `FromStr`, or `TryFrom` parse caller
input and reject invalid values before storage.
- `try_new*` is the default smart-constructor family for raw values becoming a
proof-bearing domain type.
- `try_from_*`, `TryFrom`, and clearly named `parse` methods are appropriate
when the source shape matters, especially conversions from another
representation, deserialized snapshot data, or textual/raw DTO input. Prefer
these names over owned `from_str` constructors so fallibility remains visible
in the repository's constructor taxonomy.
- `try_<variant>` is appropriate for fallible enum variant constructors, such as
`DedupPolicy::try_epsilon`, when the variant name is the clearest API.
- `try_<builder_option>` is appropriate for fallible builder setters, such as
`DelaunayTriangulationBuilder::try_toroidal`, when the builder remains an
intermediate state and final construction still happens at `build`.
- All of these names parse caller input and reject invalid values before storage.
- Raw numeric coordinates, slotmap keys, facet indexes, dimensions, UUIDs,
explicit connectivity, deserialized snapshots, and topology data are
invariant-bearing input unless a narrower validated type already carries the
Expand Down Expand Up @@ -292,7 +303,9 @@ being parsed:
- Empty containers and empty triangulations may use `empty`, `new_empty`, or
`with_empty_*` because no user geometry or topology is accepted.
- Builder creation may use `Builder::new` when validation is explicitly deferred
to `build`; builder setters remain infallible and return `Self`.
to `build`; fallible builder setters must use descriptive `try_*` names, while
infallible builder setters keep `with_*` or domain-specific names and return
`Self`.
- Configuration and statistics types may derive or implement `Default` when the
default value is valid and documented as a policy choice or accumulator state.
- `from_*` is acceptable for passive report/view extraction or infallible
Expand All @@ -306,34 +319,52 @@ Current migration targets for API-normalization work:
`DelaunayTriangulation::try_with_*` methods are the fallible custom-kernel
constructors. Infallible empty constructors remain `empty` and
`with_empty_*` because they accept no user geometry or topology.
- `DelaunayTriangulationBuilder::from_vertices_and_simplices*` stores explicit
connectivity for later validation in `build`. If this API is renamed, keep the
validation boundary at `build` or introduce a fallible `try_from_*` path that
proves connectivity before storage.
- `DelaunayTriangulationBuilder::try_from_vertices_and_simplices*` validates
explicit simplex specs before storing them in a private proof-bearing wrapper.
Full TDS/topology/Delaunay validation still happens at `build`, where the
assembled triangulation exists.
- `ConvexHull::try_from_triangulation` is the fallible hull-snapshot
constructor. Reserve `from_*` for infallible conversions from proof-bearing
input or passive view/report extraction.
- Broad public `from_*` helpers should be reviewed case by case. Keep them when
they consume proof-bearing inputs and cannot fail; rename to `try_from_*` when
they parse raw invalidable state.

Semgrep guardrails for constructor names should stay narrow and repo-specific:
protect established public parse boundaries such as `DelaunayTriangulation` and
`ConvexHull`, while preserving intentional exceptions such as empty
constructors, builder `new`, and infallible construction from proof-bearing
values.
Semgrep guardrails for constructor names should stay narrow and repo-specific.
They enforce that fallible constructor definitions do not use misleading `new`
or `from_*` names, and they protect established public parse boundaries such as
`DelaunayTriangulation` and `ConvexHull`. Do not make the rules require every
fallible boundary to be named `try_new*`; descriptive `try_*` names are allowed
for builder setters and enum variant constructors when they better describe the
operation.
Do not add `from_unchecked_*` constructors; use an explicit candidate type for
temporarily assembled state, then consume validation proof before converting to
the final domain type. Other infallible `from_*` names remain acceptable only
for total conversions, passive report/view extraction, or proof-bearing input.

---

## Panic Policy

Panics should be avoided in library code.

User-facing Rust surfaces must also avoid panic-based examples. Do not use
unwrap or expect calls in committed examples, benchmarks, Markdown Rust blocks,
or doctests. These artifacts are copied by users and should model typed error
propagation with `?`, local `thiserror` enums, or crate error types. Reserve
unwrap and expect calls for unit tests and test-only fixtures, where a panic
clearly reports a broken test assumption.

Acceptable panic situations:

- internal invariants violated
- unreachable logic errors
- debugging assertions

Do not use `debug_assert!`, `debug_assert_eq!`, or `debug_assert_ne!` in
production source. Debug-only assertions disappear in release builds, so they
cannot protect library invariants or serve as parse-don't-validate boundaries.
Encode the invariant in a type, return a typed error, or cover the assumption
with tests instead.

Prefer returning:

Expand Down
Loading
Loading