use crate::core::algorithms::flips::DelaunayRepairError;
use crate::core::algorithms::incremental_insertion::{
CavityFillingError, DelaunayRepairFailureContext, HullExtensionReason, InsertionError,
InsertionTopologyValidationContext, NeighborWiringError, SpatialIndexConstructionFailure,
};
use crate::core::algorithms::locate::{ConflictError, LocateError};
use crate::core::collections::{MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer};
use crate::core::realization::TriangulationRealizationValidationError;
use crate::core::simplex::{Simplex, SimplexValidationError};
use crate::core::tds::{
InvariantError, SimplexKey, Tds, TdsConstructionError, TdsError, VertexKey,
};
use crate::core::traits::data_type::DataType;
use crate::core::triangulation::Triangulation;
use crate::core::util::PeriodicFacetKeyDerivationError;
use crate::core::validation::TriangulationValidationError;
use crate::core::vertex::Vertex;
use crate::geometry::kernel::Kernel;
use crate::geometry::point::Point;
use crate::geometry::predicates::Orientation;
use crate::geometry::robust_predicates::robust_orientation;
use crate::geometry::traits::coordinate::{CoordinateValidationError, CoordinateValues};
use crate::topology::traits::topological_space::TopologyKind;
use crate::validation::DelaunayTriangulationValidationError;
use thiserror::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FinalTopologyValidationContext {
ConstructionFinalize,
PeriodicQuotientTopology,
RandomGeneration,
}
impl std::fmt::Display for FinalTopologyValidationContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConstructionFinalize => {
f.write_str("topology validation failed after construction")
}
Self::PeriodicQuotientTopology => {
f.write_str("periodic quotient failed final Levels 1-3 topology validation")
}
Self::RandomGeneration => {
f.write_str("random triangulation failed final Levels 1-3 topology validation")
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum FinalDelaunayValidationContext {
ConstructionFinalize,
PeriodicQuotientDelaunay,
}
impl std::fmt::Display for FinalDelaunayValidationContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ConstructionFinalize => {
f.write_str("Delaunay validation failed after construction")
}
Self::PeriodicQuotientDelaunay => {
f.write_str("periodic quotient failed final Level 5 Delaunay validation")
}
}
}
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum PeriodicQuotientFacetKeyDerivationFailure {
#[error("invalid lifted simplex arity: expected {expected} vertices, got {actual}")]
InvalidLiftedSimplexArity {
expected: usize,
actual: usize,
},
#[error("facet index {facet_index} out of bounds for lifted vertex count {vertex_count}")]
FacetIndexOutOfBounds {
facet_index: usize,
vertex_count: usize,
},
#[error(
"periodic offset component {component} (axis {axis}) is out of encodable range 0..=255"
)]
RelativeOffsetOutOfRange {
axis: usize,
component: i16,
},
}
impl From<PeriodicFacetKeyDerivationError> for PeriodicQuotientFacetKeyDerivationFailure {
fn from(source: PeriodicFacetKeyDerivationError) -> Self {
match source {
PeriodicFacetKeyDerivationError::InvalidLiftedSimplexArity { expected, actual } => {
Self::InvalidLiftedSimplexArity { expected, actual }
}
PeriodicFacetKeyDerivationError::FacetIndexOutOfBounds {
facet_index,
vertex_count,
} => Self::FacetIndexOutOfBounds {
facet_index,
vertex_count,
},
PeriodicFacetKeyDerivationError::RelativeOffsetOutOfRange { axis, component } => {
Self::RelativeOffsetOutOfRange { axis, component }
}
}
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum TriangulationConstructionError {
#[error(transparent)]
Tds(#[from] TdsConstructionError),
#[error("Failed to create simplex during construction: {message}")]
FailedToCreateSimplex {
message: String,
},
#[error("Failed to create periodic quotient simplex during construction: {source}")]
PeriodicQuotientSimplexCreation {
#[source]
source: SimplexValidationError,
},
#[error("Periodic quotient facet-key derivation failed for facet {facet_index}: {reason}")]
PeriodicQuotientFacetKeyDerivation {
facet_index: usize,
#[source]
reason: PeriodicQuotientFacetKeyDerivationFailure,
},
#[error("Cavity filling failed during insertion: {source}")]
InsertionCavityFilling {
#[source]
source: CavityFillingError,
},
#[error("Neighbor wiring failed during insertion: {source}")]
InsertionNeighborWiring {
#[source]
source: NeighborWiringError,
},
#[error("Delaunay repair failed during insertion ({context}): {source}")]
InsertionDelaunayRepair {
context: DelaunayRepairFailureContext,
#[source]
source: Box<DelaunayRepairError>,
},
#[error("Perturbation retry produced invalid coordinates during insertion: {source}")]
InsertionPerturbedCoordinateInvalid {
#[source]
source: CoordinateValidationError,
},
#[error("Geometric orientation canonicalization failed after construction: {source}")]
OrientationCanonicalizationGeometric {
#[source]
source: Box<InsertionError>,
},
#[error("Internal orientation canonicalization failed after construction: {source}")]
OrientationCanonicalizationInternal {
#[source]
source: Box<InsertionError>,
},
#[error("Insufficient vertices for {dimension}D triangulation: {source}")]
InsufficientVertices {
dimension: usize,
#[source]
source: SimplexValidationError,
},
#[error("Geometric degeneracy encountered during construction: {message}")]
GeometricDegeneracy {
message: String,
},
#[error(
"Periodic image-point construction is release-validated only up to {max_validated_dimension}D; {dimension}D scalable quotient construction is tracked by issue #{tracking_issue}"
)]
UnsupportedPeriodicDimension {
dimension: usize,
max_validated_dimension: usize,
tracking_issue: u32,
},
#[error(
"Periodic image-point construction requires periodic facet signatures, but {topology:?} topology does not support them"
)]
PeriodicImageUnsupportedTopology {
topology: TopologyKind,
},
#[error(
"Periodic image-point construction requires a periodic domain, but {topology:?} topology does not expose one"
)]
PeriodicImageMissingDomain {
topology: TopologyKind,
},
#[error(
"Periodic {dimension}D triangulation requires at least {minimum_vertex_count} points, got {actual_vertex_count}"
)]
PeriodicImageInsufficientVertices {
dimension: usize,
minimum_vertex_count: usize,
actual_vertex_count: usize,
},
#[error(
"Periodic image coordinates for canonical vertex {canonical_vertex_index} image {image_index} violated point invariants: {source}"
)]
PeriodicImageCoordinateValidation {
canonical_vertex_index: usize,
image_index: usize,
#[source]
source: CoordinateValidationError,
},
#[error(
"Periodic expanded DT is missing at least one canonical vertex out of {canonical_vertex_count}"
)]
PeriodicImageMissingCanonicalVertices {
canonical_vertex_count: usize,
},
#[error("Periodic image construction failed to canonicalize orientation after build: {source}")]
PeriodicImageOrientationCanonicalization {
#[source]
source: Box<InsertionError>,
},
#[error(
"Periodic image construction failed geometric orientation validation after build: {source}"
)]
PeriodicImageGeometricOrientationValidation {
#[source]
source: Box<TdsError>,
},
#[error("Periodic quotient reconstruction produced no surviving representative simplices")]
PeriodicQuotientEmptyReconstruction,
#[error(
"Periodic quotient candidate extraction found no usable image simplices among {full_simplex_count} full-image simplices for {canonical_vertex_count} canonical vertices"
)]
PeriodicQuotientNoCandidates {
full_simplex_count: usize,
canonical_vertex_count: usize,
},
#[error(
"Periodic quotient selection chose no candidate simplices from {candidate_count} candidates after {search_attempts} attempts"
)]
PeriodicQuotientSelectionEmpty {
candidate_count: usize,
search_attempts: usize,
},
#[error(
"Periodic quotient selection left {boundary_facet_count} boundary facets after {search_attempts} attempts"
)]
PeriodicQuotientSelectionBoundaryFacets {
boundary_facet_count: usize,
search_attempts: usize,
full_vertex_count: usize,
full_simplex_count: usize,
canonical_vertex_count: usize,
candidate_count: usize,
selected_simplex_count: usize,
},
#[error(
"Periodic quotient selection could not reach χ = 0 on T^2; best |χ|={best_abs_chi} after {search_attempts} attempts"
)]
PeriodicQuotientSelectionEulerCharacteristic {
best_abs_chi: i64,
search_attempts: usize,
},
#[error(
"Periodic quotient selection covered only {covered_vertex_count} of {canonical_vertex_count} canonical vertices in {dimension}D"
)]
PeriodicQuotientSelectionIncompleteCoverage {
dimension: usize,
covered_vertex_count: usize,
canonical_vertex_count: usize,
},
#[error(
"Periodic quotient reconstruction over-shared {overloaded_facet_count} facets across {selected_simplex_count} selected simplices"
)]
PeriodicQuotientOverloadedFacets {
overloaded_facet_count: usize,
selected_simplex_count: usize,
},
#[error(
"Periodic quotient facet signature has {occurrence_count} occurrences, expected 1 or 2"
)]
PeriodicQuotientFacetMultiplicity {
occurrence_count: usize,
},
#[error(
"Periodic quotient reconstruction left {unmatched_neighbor_slots} unmatched neighbor slots"
)]
PeriodicQuotientUnmatchedNeighbors {
unmatched_neighbor_slots: usize,
},
#[error("Missing neighbor vector for periodic quotient simplex {simplex_key:?}")]
PeriodicQuotientMissingNeighborVector {
simplex_key: SimplexKey,
},
#[error("Conflict region failed during insertion: {source}")]
InsertionConflictRegion {
#[source]
source: ConflictError,
},
#[error("Point location failed during insertion: {source}")]
InsertionLocation {
#[source]
source: LocateError,
},
#[error(
"Non-manifold topology during insertion: facet {facet_hash:#x} shared by {simplex_count} simplices"
)]
InsertionNonManifoldTopology {
facet_hash: u64,
simplex_count: usize,
},
#[error("Hull extension failed during insertion: {reason}")]
InsertionHullExtension {
#[source]
reason: HullExtensionReason,
},
#[error("Delaunay validation failed during insertion: {source}")]
InsertionDelaunayValidation {
#[source]
source: DelaunayTriangulationValidationError,
},
#[error("Realization validation failed during insertion: {source}")]
InsertionRealizationValidation {
#[source]
source: TriangulationRealizationValidationError,
},
#[error("{context}: {source}")]
InsertionTopologyValidation {
context: InsertionTopologyValidationContext,
#[source]
source: TriangulationValidationError,
},
#[error(
"Local facet repair removal budget exceeded during construction: would remove {attempted} simplices, maximum is {max_simplices_removed}"
)]
LocalRepairBudgetExceeded {
max_simplices_removed: usize,
attempted: usize,
},
#[error("{context}: {source}")]
FinalTopologyValidation {
context: FinalTopologyValidationContext,
#[source]
source: Box<InvariantError>,
},
#[error("{context}: {source}")]
FinalDelaunayValidation {
context: FinalDelaunayValidationContext,
#[source]
source: DelaunayTriangulationValidationError,
},
#[error(
"Duplicate coordinates: vertex with coordinates {coordinates} already exists in the triangulation"
)]
DuplicateCoordinates {
coordinates: CoordinateValues,
},
#[error("Spatial index construction failed during construction: {reason}")]
SpatialIndexConstruction {
#[source]
reason: SpatialIndexConstructionFailure,
},
#[error("Internal inconsistency during construction: {message}")]
InternalInconsistency {
message: String,
},
}
impl<K, U, V, const D: usize> Triangulation<K, U, V, D>
where
K: Kernel<D, Scalar = f64>,
U: DataType,
V: DataType,
{
pub fn build_initial_simplex(
vertices: &[Vertex<U, D>],
) -> Result<Tds<U, V, D>, TriangulationConstructionError> {
if vertices.len() != D + 1 {
return Err(TriangulationConstructionError::InsufficientVertices {
dimension: D,
source: SimplexValidationError::InsufficientVertices {
actual: vertices.len(),
expected: D + 1,
dimension: D,
},
});
}
for vertex in vertices {
vertex.is_valid().map_err(|source| {
TriangulationConstructionError::Tds(TdsConstructionError::ValidationError(
TdsError::InvalidVertex {
vertex_id: vertex.uuid(),
source,
},
))
})?;
}
let points: SmallBuffer<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE> =
vertices.iter().map(|v| *v.point()).collect();
let exact_orientation = robust_orientation(&points[..]).map_err(|e| {
TriangulationConstructionError::FailedToCreateSimplex {
message: format!("Exact orientation test failed: {e}"),
}
})?;
if matches!(exact_orientation, Orientation::DEGENERATE) {
return Err(TriangulationConstructionError::GeometricDegeneracy {
message: format!(
"Degenerate initial simplex: vertices are collinear/coplanar in {}D space. \
The {} input vertices do not span a full {}-dimensional simplex. \
Provide non-degenerate vertices to create a valid triangulation.",
D,
D + 1,
D
),
});
}
let orientation = match exact_orientation {
Orientation::POSITIVE => 1,
Orientation::NEGATIVE => -1,
Orientation::DEGENERATE => {
return Err(TriangulationConstructionError::GeometricDegeneracy {
message: format!("Degenerate initial simplex in {D}D (unreachable)"),
});
}
};
let mut tds = Tds::empty();
let mut vertex_keys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for vertex in vertices {
let vkey = tds.insert_vertex_with_mapping(*vertex)?;
vertex_keys.push(vkey);
}
if orientation < 0 {
if vertex_keys.len() >= 2 {
vertex_keys.swap(0, 1);
} else {
return Err(TriangulationConstructionError::FailedToCreateSimplex {
message: format!(
"Cannot canonicalize orientation for {}D simplex with {} vertex key(s)",
D,
vertex_keys.len(),
),
});
}
}
let simplex = Simplex::try_new(vertex_keys).map_err(|e| {
TriangulationConstructionError::FailedToCreateSimplex {
message: format!("Failed to create initial simplex: {e}"),
}
})?;
let _simplex_key = tds.insert_simplex_with_mapping(simplex)?;
tds.assign_neighbors()
.map_err(TdsConstructionError::ValidationError)?;
tds.assign_incident_simplices()
.map_err(|e| TdsConstructionError::ValidationError(e.into()))?;
Ok(tds)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::simplex::NeighborSlot;
use crate::geometry::kernel::FastKernel;
use crate::geometry::traits::coordinate::{CoordinateValidationError, InvalidCoordinateValue};
use crate::vertex;
use std::assert_matches;
#[test]
fn internal_inconsistency_display() {
let err = TriangulationConstructionError::InternalInconsistency {
message: "missing vertex in lookup table".to_string(),
};
assert_eq!(
err.to_string(),
"Internal inconsistency during construction: missing vertex in lookup table"
);
}
#[test]
fn final_topology_validation_context_display() {
let cases = [
(
FinalTopologyValidationContext::ConstructionFinalize,
"topology validation failed after construction",
),
(
FinalTopologyValidationContext::PeriodicQuotientTopology,
"periodic quotient failed final Levels 1-3 topology validation",
),
(
FinalTopologyValidationContext::RandomGeneration,
"random triangulation failed final Levels 1-3 topology validation",
),
];
for (context, expected) in cases {
assert_eq!(context.to_string(), expected);
}
}
#[test]
fn final_delaunay_validation_context_display() {
assert_eq!(
FinalDelaunayValidationContext::ConstructionFinalize.to_string(),
"Delaunay validation failed after construction"
);
assert_eq!(
FinalDelaunayValidationContext::PeriodicQuotientDelaunay.to_string(),
"periodic quotient failed final Level 5 Delaunay validation"
);
}
#[test]
fn insertion_hull_extension_exposes_typed_source() {
let reason = HullExtensionReason::Tds(TdsError::InconsistentDataStructure {
message: "missing boundary facet".to_string(),
});
let error = TriangulationConstructionError::InsertionHullExtension { reason };
let source = std::error::Error::source(&error)
.and_then(|source| source.downcast_ref::<HullExtensionReason>());
assert_matches!(source, Some(HullExtensionReason::Tds(_)));
}
#[test]
fn insufficient_vertices_exposes_typed_source() {
let error = TriangulationConstructionError::InsufficientVertices {
dimension: 3,
source: SimplexValidationError::InsufficientVertices {
actual: 3,
expected: 4,
dimension: 3,
},
};
let source = std::error::Error::source(&error)
.and_then(|source| source.downcast_ref::<SimplexValidationError>());
assert_matches!(
source,
Some(SimplexValidationError::InsufficientVertices {
actual: 3,
expected: 4,
dimension: 3,
})
);
}
macro_rules! test_build_initial_simplex {
($dim:expr, [$($simplex_coords:expr),+ $(,)?]) => {
pastey::paste! {
#[test]
fn [<build_initial_simplex_ $dim d>]() {
let vertices: Vec<Vertex<(), $dim>> = vec![
$(vertex!($simplex_coords).unwrap()),+
];
let expected_vertices = vertices.len();
assert_eq!(expected_vertices, $dim + 1);
let tds = Triangulation::<FastKernel<f64>, (), (), $dim>::build_initial_simplex(&vertices)
.unwrap();
assert_eq!(tds.number_of_vertices(), expected_vertices);
assert_eq!(tds.number_of_simplices(), 1);
assert_eq!(tds.dim(), $dim as i32);
assert_eq!(tds.vertices().count(), expected_vertices);
let (_, simplex) = tds.simplices().next()
.expect("initial simplex should exist");
assert_eq!(simplex.number_of_vertices(), expected_vertices);
for (_, vertex) in tds.vertices() {
assert!(vertex.incident_simplex().is_some());
}
let neighbors = simplex
.neighbor_slots()
.expect("initial simplex should assign boundary neighbor slots");
assert!(neighbors.iter().all(|slot| *slot == NeighborSlot::Boundary));
}
}
};
}
test_build_initial_simplex!(2, [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
test_build_initial_simplex!(
3,
[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]
]
);
test_build_initial_simplex!(
4,
[
[0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]
]
);
test_build_initial_simplex!(
5,
[
[0.0, 0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 1.0]
]
);
#[test]
fn build_initial_simplex_insufficient_vertices() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
];
let result = Triangulation::<FastKernel<f64>, (), (), 3>::build_initial_simplex(&vertices);
assert_matches!(
result,
Err(TriangulationConstructionError::InsufficientVertices { dimension: 3, .. })
);
}
#[test]
fn build_initial_simplex_too_many_vertices() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
vertex!([0.5, 0.5]).unwrap(),
];
let result = Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices);
assert_matches!(
result,
Err(TriangulationConstructionError::InsufficientVertices { .. })
);
}
macro_rules! test_build_initial_simplex_rejects_non_finite_vertex_coordinate_dimensions {
($($dim:expr),+ $(,)?) => {
pastey::paste! {
$(
#[test]
fn [<build_initial_simplex_rejects_non_finite_vertex_coordinate_ $dim d>]() {
let mut invalid_coords = [0.0_f64; $dim];
invalid_coords[0] = 1.0;
invalid_coords[1] = f64::NAN;
assert_matches!(
Point::<$dim>::try_new(invalid_coords),
Err(CoordinateValidationError::InvalidCoordinate {
coordinate_index: 1,
coordinate_value: InvalidCoordinateValue::Nan,
dimension: $dim,
})
);
}
)+
}
};
}
test_build_initial_simplex_rejects_non_finite_vertex_coordinate_dimensions!(2, 3, 4, 5);
#[test]
fn build_initial_simplex_with_user_data() {
let v1 = vertex!([0.0, 0.0]; data = 42_usize).unwrap();
let v2 = vertex!([1.0, 0.0]; data = 43_usize).unwrap();
let v3 = vertex!([0.0, 1.0]; data = 44_usize).unwrap();
let vertices = vec![v1, v2, v3];
let tds = Triangulation::<FastKernel<f64>, usize, (), 2>::build_initial_simplex(&vertices)
.unwrap();
assert_eq!(tds.number_of_vertices(), 3);
assert_eq!(tds.number_of_simplices(), 1);
let data_values: Vec<_> = tds
.vertices()
.filter_map(|(_, v)| v.data.as_ref())
.copied()
.collect();
assert_eq!(data_values.len(), 3);
assert!(data_values.contains(&42));
assert!(data_values.contains(&43));
assert!(data_values.contains(&44));
}
#[test]
fn build_initial_simplex_rejects_collinear_2d() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([2.0, 0.0]).unwrap(),
];
let result = Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices);
assert_matches!(
result,
Err(TriangulationConstructionError::GeometricDegeneracy { .. })
);
}
#[test]
fn build_initial_simplex_rejects_coplanar_3d() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.5, 0.5, 0.0]).unwrap(),
];
let result = Triangulation::<FastKernel<f64>, (), (), 3>::build_initial_simplex(&vertices);
assert_matches!(
result,
Err(TriangulationConstructionError::GeometricDegeneracy { .. })
);
}
}