#![cfg_attr(any(doc, doctest), doc = include_str!("../README.md"))]
#![expect(clippy::multiple_crate_versions)]
#![forbid(unsafe_code)]
pub mod core {
pub mod algorithms {
pub mod flips;
pub mod incremental_insertion;
pub mod locate;
pub(crate) mod pl_manifold_repair;
}
pub mod adjacency;
pub mod boundary;
pub mod builder;
pub mod cell;
pub mod collections {
mod aliases;
mod buffers;
mod helpers;
mod key_maps;
mod secondary_maps;
mod triangulation_maps;
pub(crate) mod spatial_hash_grid;
pub(crate) use aliases::StorageMap;
pub use aliases::{
Entry, FacetIndex, FastBuildHasher, FastHashMap, FastHashSet, FastHasher,
MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer, Uuid,
};
pub use buffers::*;
pub use helpers::*;
pub use key_maps::*;
pub use secondary_maps::*;
pub use triangulation_maps::*;
}
pub mod delaunay_triangulation;
pub mod edge;
pub mod facet;
pub mod operations;
pub mod triangulation;
pub mod triangulation_data_structure;
pub mod util {
pub(crate) mod canonical_points;
pub mod deduplication;
pub mod delaunay_validation;
pub mod facet_keys;
pub mod facet_utils;
pub mod hashing;
pub mod hilbert;
pub mod jaccard;
pub mod measurement;
pub mod uuid;
pub use deduplication::*;
pub use delaunay_validation::*;
pub use facet_keys::*;
pub use facet_utils::*;
pub use hashing::*;
pub use hilbert::*;
pub use jaccard::*;
pub use measurement::*;
pub use uuid::*;
}
pub mod vertex;
pub mod traits {
pub mod boundary_analysis;
pub mod data_type;
pub mod facet_cache;
pub use boundary_analysis::*;
pub use data_type::*;
pub use facet_cache::*;
}
pub use adjacency::*;
pub use builder::DelaunayTriangulationBuilder;
pub use cell::*;
pub use delaunay_triangulation::*;
pub use edge::*;
pub use facet::*;
pub use traits::*;
pub use triangulation_data_structure::*;
pub use util::*;
pub use vertex::*;
}
pub mod geometry {
pub mod algorithms {
pub mod convex_hull;
pub use convex_hull::*;
}
#[macro_use]
pub mod matrix;
pub mod kernel;
pub mod point;
pub mod predicates;
pub mod quality;
pub mod robust_predicates;
pub mod sos;
pub mod util {
use crate::geometry::matrix::{MatrixError, StackMatrixDispatchError};
use crate::geometry::traits::coordinate::CoordinateConversionError;
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum ValueConversionError {
#[error("Cannot convert {value} from {from_type} to {to_type}: {details}")]
ConversionFailed {
value: String,
from_type: &'static str,
to_type: &'static str,
details: String,
},
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum RandomPointGenerationError {
#[error("Invalid coordinate range: minimum {min} must be less than maximum {max}")]
InvalidRange {
min: String,
max: String,
},
#[error("Failed to generate random value in range [{min}, {max}]: {details}")]
RandomGenerationFailed {
min: String,
max: String,
details: String,
},
#[error("Invalid number of points: {n_points} (must be non-negative)")]
InvalidPointCount {
n_points: isize,
},
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum CircumcenterError {
#[error("Empty point set")]
EmptyPointSet,
#[error(
"Points do not form a valid simplex: expected {expected} points for dimension {dimension}, got {actual}"
)]
InvalidSimplex {
actual: usize,
expected: usize,
dimension: usize,
},
#[error("Matrix inversion failed: {details}")]
MatrixInversionFailed {
details: String,
},
#[error("Matrix error: {0}")]
MatrixError(#[from] MatrixError),
#[error("Array conversion failed: {details}")]
ArrayConversionFailed {
details: String,
},
#[error("Coordinate conversion error: {0}")]
CoordinateConversion(#[from] CoordinateConversionError),
#[error("Value conversion error: {0}")]
ValueConversion(#[from] ValueConversionError),
}
impl From<StackMatrixDispatchError> for CircumcenterError {
fn from(source: StackMatrixDispatchError) -> Self {
match source {
StackMatrixDispatchError::UnsupportedDim { k, max } => {
Self::MatrixInversionFailed {
details: format!("unsupported stack matrix size: {k} (max {max})"),
}
}
StackMatrixDispatchError::La(source) => Self::MatrixInversionFailed {
details: format!("la-stack error: {source}"),
},
}
}
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum SurfaceMeasureError {
#[error("Failed to retrieve facet vertices: {0}")]
FacetError(#[from] crate::core::facet::FacetError),
#[error("Geometry computation failed: {0}")]
GeometryError(#[from] CircumcenterError),
}
pub mod circumsphere;
pub mod conversions;
pub mod measures;
pub mod norms;
pub mod point_generation;
pub mod triangulation_generation;
pub use circumsphere::*;
pub use conversions::*;
pub use measures::*;
pub use norms::*;
pub use point_generation::*;
pub use triangulation_generation::*;
}
pub mod traits {
pub mod coordinate;
pub use coordinate::*;
}
pub use algorithms::*;
pub use matrix::*;
pub use point::*;
pub use predicates::*;
pub use quality::*;
pub use traits::*;
pub use util::*;
}
pub mod triangulation {
pub mod delaunayize;
pub mod flips;
pub use crate::core::delaunay_triangulation::DelaunayTriangulation;
pub use crate::core::triangulation::Triangulation;
}
pub mod topology {
pub mod traits {
pub(crate) mod global_topology_model;
pub mod topological_space;
pub use topological_space::*;
}
pub mod characteristics {
pub mod euler;
pub mod validation;
pub use euler::*;
pub use validation::*;
}
pub mod manifold;
pub mod spaces {
pub mod euclidean;
pub mod spherical;
pub mod toroidal;
pub use euclidean::EuclideanSpace;
pub use spherical::SphericalSpace;
pub use toroidal::ToroidalSpace;
}
pub use crate::core::triangulation::TopologyGuarantee;
pub use characteristics::*;
pub use manifold::{
ManifoldError, validate_closed_boundary, validate_facet_degree, validate_ridge_links,
validate_vertex_links,
};
pub use traits::*;
}
pub mod prelude {
pub use crate::core::{
adjacency::*,
cell::*,
delaunay_triangulation::*,
edge::*,
facet::*,
traits::{boundary_analysis::*, data_type::*},
triangulation::*,
triangulation_data_structure::*,
vertex::*,
};
pub use crate::core::util::{
deduplication::*, delaunay_validation::*, facet_keys::*, facet_utils::*, hashing::*,
hilbert::*, jaccard::*, measurement::*, uuid::*,
};
pub use crate::core::algorithms::locate::{
LocateError, LocateFallback, LocateFallbackReason, LocateResult, LocateStats, locate,
locate_with_stats,
};
pub use crate::core::algorithms::incremental_insertion::InsertionError;
pub use crate::core::operations::{InsertionOutcome, InsertionStatistics, SuspicionFlags};
pub use crate::core::algorithms::flips::{
DelaunayRepairDiagnostics, DelaunayRepairError, DelaunayRepairStats, RepairQueueOrder,
};
pub use crate::core::collections::{
CellNeighborsMap, CellSecondaryMap, FacetToCellsMap, FastHashMap, FastHashSet, SmallBuffer,
VertexSecondaryMap, VertexToCellsMap, fast_hash_map_with_capacity,
fast_hash_set_with_capacity,
};
pub use crate::geometry::{
algorithms::*, kernel::*, matrix::*, point::*, predicates::*, quality::*,
robust_predicates::*, traits::coordinate::*, util::*,
};
pub mod triangulation {
pub use crate::core::builder::DelaunayTriangulationBuilder;
pub use crate::core::{
adjacency::*,
cell::*,
delaunay_triangulation::*,
edge::*,
facet::*,
traits::{boundary_analysis::*, data_type::*},
triangulation::*,
triangulation_data_structure::*,
vertex::*,
};
pub use crate::core::algorithms::incremental_insertion::InsertionError;
pub use crate::core::operations::{InsertionOutcome, InsertionStatistics, SuspicionFlags};
pub use crate::core::algorithms::flips::{
DelaunayRepairDiagnostics, DelaunayRepairError, DelaunayRepairStats, RepairQueueOrder,
};
pub use crate::topology::traits::{GlobalTopology, TopologyKind, ToroidalConstructionMode};
pub mod flips {
pub use crate::core::delaunay_triangulation::DelaunayTriangulation;
pub use crate::core::triangulation::{TopologyGuarantee, Triangulation};
pub use crate::triangulation::flips::*;
pub use crate::vertex;
}
pub mod delaunayize {
pub use crate::core::delaunay_triangulation::DelaunayTriangulation;
pub use crate::triangulation::delaunayize::*;
pub use crate::vertex;
}
pub use crate::vertex;
}
pub mod collections {
pub use crate::core::collections::{
CellNeighborsMap, CellSecondaryMap, FacetToCellsMap, FastHashMap, FastHashSet,
SmallBuffer, VertexSecondaryMap, VertexToCellsMap, fast_hash_map_with_capacity,
fast_hash_set_with_capacity,
};
}
pub mod geometry {
pub use crate::geometry::{
algorithms::*, kernel::*, matrix::*, point::*, predicates::*, quality::*,
robust_predicates::*, traits::coordinate::*, util::*,
};
}
pub mod algorithms {
pub use crate::core::algorithms::locate::{
LocateError, LocateFallback, LocateFallbackReason, LocateResult, LocateStats, locate,
locate_with_stats,
};
}
pub mod query {
pub use crate::core::adjacency::{AdjacencyIndex, AdjacencyIndexBuildError};
pub use crate::core::delaunay_triangulation::DelaunayTriangulation;
pub use crate::core::edge::EdgeKey;
pub use crate::core::triangulation::Triangulation;
pub use crate::core::triangulation_data_structure::{CellKey, VertexKey};
pub use crate::core::facet::FacetView;
pub use crate::core::traits::boundary_analysis::BoundaryAnalysis;
pub use crate::core::traits::data_type::DataType;
pub use crate::core::{Cell, Vertex};
pub use crate::geometry::Point;
pub use crate::geometry::kernel::{
AdaptiveKernel, ExactPredicates, FastKernel, Kernel, RobustKernel,
};
pub use crate::geometry::traits::coordinate::Coordinate;
pub use crate::geometry::{insphere, insphere_distance, insphere_lifted};
pub use crate::geometry::algorithms::convex_hull::ConvexHull;
pub use crate::geometry::util::{
generate_random_points_seeded, generate_random_triangulation,
};
pub use crate::core::util::measure_with_result;
pub use crate::vertex;
}
pub mod topology {
pub mod validation {
pub use crate::topology::TopologyGuarantee;
pub use crate::topology::characteristics::*;
pub use crate::topology::manifold::{
ManifoldError, validate_closed_boundary, validate_facet_degree,
validate_ridge_links, validate_vertex_links,
};
pub use crate::topology::traits::*;
}
}
pub use crate::vertex;
}
#[must_use]
pub const fn is_normal<T: Sized + Send + Sync + Unpin>() -> bool {
true
}
#[cfg(test)]
mod tests {
use crate::{
core::{
adjacency::AdjacencyIndex, cell::Cell, delaunay_triangulation::DelaunayTriangulation,
edge::EdgeKey, triangulation::Triangulation, triangulation_data_structure::Tds,
vertex::Vertex,
},
geometry::{Point, algorithms::convex_hull::ConvexHull, kernel::FastKernel},
is_normal,
};
#[test]
fn normal_types() {
assert!(is_normal::<Point<f64, 3>>());
assert!(is_normal::<Point<f32, 3>>());
assert!(is_normal::<Vertex<f64, (), 3>>());
assert!(is_normal::<Cell<f64, (), (), 4>>());
assert!(is_normal::<Tds<f64, (), (), 4>>());
assert!(is_normal::<Triangulation<FastKernel<f64>, (), (), 3>>());
assert!(is_normal::<DelaunayTriangulation<FastKernel<f64>, (), (), 3>>());
assert!(is_normal::<ConvexHull<FastKernel<f64>, (), (), 3>>());
assert!(is_normal::<EdgeKey>());
assert!(is_normal::<AdjacencyIndex>());
}
#[test]
fn test_prelude_collections_exports() {
use crate::prelude::*;
let mut map: FastHashMap<u64, usize> = FastHashMap::default();
map.insert(123, 456);
assert_eq!(map.get(&123), Some(&456));
let mut set: FastHashSet<u64> = FastHashSet::default();
set.insert(789);
assert!(set.contains(&789));
let mut buffer: SmallBuffer<i32, 8> = SmallBuffer::new();
buffer.push(42);
assert_eq!(buffer.len(), 1);
let map_with_cap = fast_hash_map_with_capacity::<u64, usize>(100);
assert!(map_with_cap.capacity() >= 100);
let set_with_cap = fast_hash_set_with_capacity::<u64>(50);
assert!(set_with_cap.capacity() >= 50);
let _facet_map: FacetToCellsMap = FacetToCellsMap::default();
let _neighbors: CellNeighborsMap = CellNeighborsMap::default();
let _vertex_cells: VertexToCellsMap = VertexToCellsMap::default();
}
#[test]
fn test_prelude_quality_exports() {
use crate::prelude::*;
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let (cell_key, _) = dt.cells().next().unwrap();
let ratio = radius_ratio(dt.as_triangulation(), cell_key).unwrap();
assert!(ratio > 0.0);
let norm_vol = normalized_volume(dt.as_triangulation(), cell_key).unwrap();
assert!(norm_vol > 0.0);
}
#[test]
fn test_prelude_kernel_exports() {
use crate::prelude::*;
let fast_kernel = FastKernel::<f64>::new();
let robust_kernel = RobustKernel::<f64>::new();
let triangle = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let fast_orientation = fast_kernel.orientation(&triangle).unwrap();
assert_ne!(fast_orientation, 0, "Triangle should be non-degenerate");
let robust_orientation = robust_kernel.orientation(&triangle).unwrap();
assert_eq!(
fast_orientation, robust_orientation,
"Both kernels should agree"
);
let collinear = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([2.0, 0.0]),
];
assert_eq!(
fast_kernel.orientation(&collinear).unwrap(),
0,
"Collinear points should have zero orientation"
);
let inside_point = Point::new([0.25, 0.25]);
let result = fast_kernel.in_sphere(&triangle, &inside_point).unwrap();
assert_eq!(result, 1, "Point should be inside circumcircle");
let outside_point = Point::new([2.0, 2.0]);
let result = fast_kernel.in_sphere(&triangle, &outside_point).unwrap();
assert_eq!(result, -1, "Point should be outside circumcircle");
}
#[test]
fn test_prelude_core_types() {
use crate::prelude::*;
let p1 = Point::new([0.0, 0.0, 0.0]);
let p2 = Point::new([1.0, 0.0, 0.0]);
assert_ne!(p1, p2);
let v1: Vertex<f64, (), 3> = vertex!([0.0, 0.0, 0.0]);
let v2: Vertex<f64, (), 3> = vertex!([1.0, 0.0, 0.0]);
assert_ne!(v1.point(), v2.point());
let vertices = 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]),
];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
assert_eq!(dt.number_of_vertices(), 4);
assert_eq!(dt.number_of_cells(), 1);
let tri = dt.as_triangulation();
assert_eq!(tri.number_of_vertices(), 4);
let tds = &tri.tds;
assert_eq!(tds.number_of_cells(), 1);
for (cell_key, _cell) in tri.cells() {
assert!(tds.get_cell(cell_key).is_some());
}
}
#[test]
fn test_prelude_point_location() {
use crate::prelude::*;
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let kernel = FastKernel::<f64>::new();
let query_point = Point::new([0.3, 0.3]);
let result = locate(dt.tds(), &kernel, &query_point, None);
assert!(result.is_ok());
match result.unwrap() {
LocateResult::InsideCell(_)
| LocateResult::OnFacet { .. }
| LocateResult::OnEdge { .. }
| LocateResult::OnVertex(_) => { }
LocateResult::Outside => panic!("Point should be inside triangulation"),
}
let outside_point = Point::new([10.0, 10.0]);
let result = locate(dt.tds(), &kernel, &outside_point, None);
assert!(result.is_ok());
}
#[test]
fn test_prelude_geometry_types() {
use crate::prelude::*;
let p = Point::new([1.0_f64, 2.0_f64, 3.0_f64]);
assert!((p.coords()[0] - 1.0_f64).abs() < f64::EPSILON);
assert!((p.coords()[1] - 2.0_f64).abs() < f64::EPSILON);
assert!((p.coords()[2] - 3.0_f64).abs() < f64::EPSILON);
let triangle = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let orientation = simplex_orientation(&triangle).unwrap();
assert_ne!(orientation, Orientation::DEGENERATE);
let test_point = Point::new([0.25, 0.25]);
let result = insphere(&triangle, test_point).unwrap();
assert_eq!(result, InSphere::INSIDE);
}
#[test]
fn test_prelude_convex_hull() {
use crate::prelude::*;
let vertices = 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]),
];
let dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let hull = ConvexHull::from_triangulation(dt.as_triangulation()).unwrap();
assert_eq!(hull.number_of_facets(), 4);
let outside_point = Point::new([2.0, 2.0, 2.0]);
let is_outside = hull
.is_point_outside(&outside_point, dt.as_triangulation())
.unwrap();
assert!(is_outside);
let inside_point = Point::new([0.25, 0.25, 0.25]);
let is_outside = hull
.is_point_outside(&inside_point, dt.as_triangulation())
.unwrap();
assert!(!is_outside);
}
#[cfg(feature = "count-allocations")]
#[test]
fn test_basic_allocation_counting() {
use allocation_counter::measure;
let result = measure(|| {
let x = 1 + 1;
assert_eq!(x, 2);
});
assert_eq!(
result.count_total, 0,
"Expected zero total allocations for trivial operation, found: {}",
result.count_total
);
assert_eq!(
result.bytes_total, 0,
"Expected zero total bytes allocated for trivial operation, found: {}",
result.bytes_total
);
assert_eq!(
result.count_current, 0,
"Expected zero current allocations after trivial operation, found: {}",
result.count_current
);
assert_eq!(
result.bytes_current, 0,
"Expected zero current bytes allocated after trivial operation, found: {}",
result.bytes_current
);
}
#[cfg(feature = "count-allocations")]
#[test]
fn test_allocation_counting_with_allocating_operation() {
use allocation_counter::measure;
let result = measure(|| {
let _vec: Vec<i32> = vec![1, 2, 3, 4, 5];
});
assert!(
result.count_total > 0,
"Expected some allocations for Vec creation, found: {}",
result.count_total
);
assert!(
result.bytes_total > 0,
"Expected some bytes allocated for Vec creation, found: {}",
result.bytes_total
);
assert_eq!(
result.count_current, 0,
"Expected zero current allocations after Vec drop, found: {}",
result.count_current
);
assert_eq!(
result.bytes_current, 0,
"Expected zero current bytes after Vec drop, found: {}",
result.bytes_current
);
assert!(
result.count_max >= result.count_total,
"Max count should be >= total count"
);
assert!(
result.bytes_max >= result.bytes_total,
"Max bytes should be >= total bytes"
);
}
}