use super::{SimplexKey, VertexKey};
use crate::core::algorithms::flips::{FlipError, FlipNeighborWiringError};
use crate::core::facet::FacetError;
use crate::core::realization::TriangulationRealizationValidationError;
use crate::core::simplex::SimplexValidationError;
use crate::core::validation::TriangulationValidationError;
use crate::core::vertex::VertexValidationError;
use crate::validation::DelaunayTriangulationValidationError;
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TriangulationConstructionState {
Incomplete(usize),
Constructed,
}
impl Default for TriangulationConstructionState {
fn default() -> Self {
Self::Incomplete(0)
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum TdsConstructionError {
#[error("Validation error during construction: {0}")]
ValidationError(#[from] TdsError),
#[error("Duplicate UUID: {entity:?} with UUID {uuid} already exists")]
DuplicateUuid {
entity: EntityKind,
uuid: Uuid,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EntityKind {
Vertex,
Simplex,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum GeometricError {
#[error("Degenerate geometric orientation: {message}")]
DegenerateOrientation {
message: String,
},
#[error("Negative geometric orientation: {message}")]
NegativeOrientation {
message: String,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SharedFacetMismatchSide {
SourceFacet,
NeighborFacet,
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum NeighborValidationError {
#[error("Neighbor vector length {actual} != expected {expected} during {context}")]
LengthMismatch {
actual: usize,
expected: usize,
context: String,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) has unassigned neighbor slot at facet {facet_index} during {context}"
)]
UnassignedNeighborSlot {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
context: String,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) has non-periodic self-neighbor at facet {facet_index}"
)]
NonPeriodicSelfNeighbor {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} references missing neighbor {neighbor_key:?} during {context}"
)]
MissingNeighborSimplex {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
neighbor_key: SimplexKey,
context: String,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} references removed neighbor {neighbor_key:?}"
)]
ReferencedRemovedNeighbor {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
neighbor_key: SimplexKey,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} shares {shared_count} vertices with neighbor, expected {expected}"
)]
SharedVertexCountMismatch {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
shared_count: usize,
expected: usize,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) neighbor at facet {facet_index} is opposite {observed_opposite:?}, expected {expected_opposite}"
)]
OppositeVertexMismatch {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
observed_opposite: Option<usize>,
expected_opposite: usize,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} key {facet_key} is missing from facet incidence"
)]
FacetIncidenceMissing {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
facet_key: u64,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} key {facet_key} does not reference the edited facet"
)]
FacetIncidenceDoesNotReferenceSimplex {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
facet_key: u64,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} key {facet_key} is shared by {simplex_count} simplices"
)]
FacetIncidenceMultiplicity {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
facet_key: u64,
simplex_count: usize,
},
#[error(
"Simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index} proposed neighbor {proposed_neighbor:?} does not match expected {expected_neighbor:?}"
)]
NeighborIncidenceMismatch {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
proposed_neighbor: Option<SimplexKey>,
expected_neighbor: Option<SimplexKey>,
},
#[error("Neighbor facet index {facet_index} out of bounds for {slot_count} neighbor slots")]
NeighborSlotOutOfBounds {
facet_index: usize,
slot_count: usize,
},
#[error(
"Could not determine mirror facet during {context}: simplex {simplex_uuid}[{facet_index}] -> neighbor {neighbor_uuid}"
)]
MirrorFacetMissing {
simplex_uuid: Uuid,
facet_index: usize,
neighbor_uuid: Uuid,
context: String,
},
#[error(
"Mirror facet is ambiguous: simplex {simplex_uuid} and neighbor {neighbor_uuid} differ by more than one vertex"
)]
MirrorFacetAmbiguous {
simplex_uuid: Uuid,
neighbor_uuid: Uuid,
},
#[error(
"Mirror facet could not be determined: simplex {simplex_uuid} and neighbor {neighbor_uuid} share all vertices"
)]
MirrorFacetDuplicateSimplices {
simplex_uuid: Uuid,
neighbor_uuid: Uuid,
},
#[error(
"Mirror facet index mismatch: simplex {simplex_uuid}[{facet_index}] -> neighbor {neighbor_uuid}; observed {observed_mirror_index}, expected {expected_mirror_index}"
)]
MirrorFacetIndexMismatch {
simplex_uuid: Uuid,
facet_index: usize,
neighbor_uuid: Uuid,
observed_mirror_index: usize,
expected_mirror_index: usize,
},
#[error(
"Shared facet mismatch ({side:?}): simplex {simplex_uuid}[{facet_index}] and neighbor {neighbor_uuid}[{mirror_index}] are missing vertex {missing_vertex:?}"
)]
SharedFacetMissingVertex {
side: SharedFacetMismatchSide,
simplex_uuid: Uuid,
facet_index: usize,
neighbor_uuid: Uuid,
mirror_index: usize,
missing_vertex: VertexKey,
},
#[error(
"Neighbor back-reference mismatch during {context}: simplex {simplex_uuid}[{facet_index}] -> {neighbor_key:?} should be mirrored by {neighbor_uuid}[{mirror_index}] -> {simplex_key:?}, found {observed:?}"
)]
BackReferenceMismatch {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
neighbor_key: SimplexKey,
neighbor_uuid: Uuid,
mirror_index: usize,
observed: Option<SimplexKey>,
context: String,
},
#[error(
"Neighbor simplex {neighbor_uuid}[{mirror_index}] already references {existing_back_ref:?}; refusing to overwrite with {requested_back_ref:?}"
)]
ExistingBackReferenceConflict {
neighbor_uuid: Uuid,
mirror_index: usize,
existing_back_ref: SimplexKey,
requested_back_ref: SimplexKey,
},
#[error(
"Boundary facet {facet_key} unexpectedly has neighbor {neighbor_key:?} across simplex {simplex_uuid}[{facet_index}]"
)]
BoundaryFacetHasNeighbor {
facet_key: u64,
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
neighbor_key: SimplexKey,
},
#[error(
"Boundary facet {facet_key} has non-periodic self-neighbor across simplex {simplex_uuid}[{facet_index}]"
)]
BoundaryFacetHasNonPeriodicSelfNeighbor {
facet_key: u64,
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
},
#[error(
"Interior facet {facet_key} has inconsistent neighbor pointers: {first_simplex_uuid}[{first_facet_index}] -> {first_neighbor:?}, {second_simplex_uuid}[{second_facet_index}] -> {second_neighbor:?}"
)]
InteriorFacetNeighborMismatch {
facet_key: u64,
first_simplex_key: SimplexKey,
first_simplex_uuid: Uuid,
first_facet_index: usize,
first_neighbor: Option<SimplexKey>,
second_simplex_key: SimplexKey,
second_simplex_uuid: Uuid,
second_facet_index: usize,
second_neighbor: Option<SimplexKey>,
},
#[error(
"Could not build facet order during {context}: simplex {simplex_uuid} (key {simplex_key:?}) facet {facet_index}: {source}"
)]
FacetOrderUnavailable {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
facet_index: usize,
context: String,
#[source]
source: Box<FlipError>,
},
#[error("Flip neighbor wiring failed: {reason}")]
FlipNeighborWiring {
#[source]
reason: Box<FlipNeighborWiringError>,
},
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum TdsError {
#[error("Invalid vertex {vertex_id}: {source}")]
InvalidVertex {
vertex_id: Uuid,
source: VertexValidationError,
},
#[error("Invalid simplex {simplex_id}: {source}")]
InvalidSimplex {
simplex_id: Uuid,
source: SimplexValidationError,
},
#[error("Invalid neighbor relationships: {reason}")]
InvalidNeighbors {
#[source]
reason: NeighborValidationError,
},
#[error(
"Orientation invariant violated between simplices {simplex1_uuid} and {simplex2_uuid}; shared facet orderings {facet_vertices:?} vs {simplex2_facet_vertices:?} (simplex1 facet index {simplex1_facet_index}, simplex2 facet index {simplex2_facet_index}, observed_odd_permutation={observed_odd_permutation}, expected_odd_permutation={expected_odd_permutation})"
)]
OrientationViolation {
simplex1_key: SimplexKey,
simplex1_uuid: Uuid,
simplex2_key: SimplexKey,
simplex2_uuid: Uuid,
simplex1_facet_index: usize,
simplex2_facet_index: usize,
facet_vertices: Vec<VertexKey>,
simplex2_facet_vertices: Vec<VertexKey>,
observed_odd_permutation: bool,
expected_odd_permutation: bool,
},
#[error("Duplicate simplices detected: {message}")]
DuplicateSimplices {
message: String,
},
#[error(
"Duplicate explicit simplices at input indices {existing_simplex_index} and {duplicate_simplex_index} with input vertex indices {vertex_indices:?}"
)]
#[non_exhaustive]
DuplicateExplicitSimplices {
existing_simplex_index: usize,
duplicate_simplex_index: usize,
vertex_indices: Vec<usize>,
},
#[error(
"Facet {facet_key} exceeds incident-simplex limit: observed {attempted_incident_count} incident simplices, max {max_incident_count}; candidate/offending simplex {candidate_simplex_uuid} facet {candidate_facet_index}; other incident simplices {existing_incident_count}"
)]
FacetSharingViolation {
facet_key: u64,
existing_incident_count: usize,
attempted_incident_count: usize,
max_incident_count: usize,
candidate_simplex_uuid: Uuid,
candidate_facet_index: usize,
},
#[error(
"Explicit facet {facet_key} with input vertex indices {facet_vertex_indices:?} exceeds incident-simplex limit: input simplex {candidate_simplex_index} would make {attempted_incident_count} incident simplices, max {max_incident_count}; candidate facet {candidate_facet_index}; existing incident simplices {existing_incident_count}"
)]
#[non_exhaustive]
ExplicitFacetSharingViolation {
facet_key: u64,
facet_vertex_indices: Vec<usize>,
existing_incident_count: usize,
attempted_incident_count: usize,
max_incident_count: usize,
candidate_simplex_index: usize,
candidate_facet_index: usize,
},
#[error("Failed to create simplex: {message}")]
FailedToCreateSimplex {
message: String,
},
#[error("Simplices {simplex1:?} and {simplex2:?} are not neighbors")]
NotNeighbors {
simplex1: Uuid,
simplex2: Uuid,
},
#[error("{entity:?} mapping inconsistency: {message}")]
MappingInconsistency {
entity: EntityKind,
message: String,
},
#[error("Failed to retrieve vertex keys for simplex {simplex_id}: {message}")]
VertexKeyRetrievalFailed {
simplex_id: Uuid,
message: String,
},
#[error("Simplex key {simplex_key:?} not found: {context}")]
SimplexNotFound {
simplex_key: SimplexKey,
context: String,
},
#[error("Vertex key {vertex_key:?} not found: {context}")]
VertexNotFound {
vertex_key: VertexKey,
context: String,
},
#[error("Dimension mismatch: expected {expected}, got {actual} — {context}")]
DimensionMismatch {
expected: usize,
actual: usize,
context: String,
},
#[error("Index out of bounds: index {index}, bound {bound} — {context}")]
IndexOutOfBounds {
index: usize,
bound: usize,
context: String,
},
#[error(
"Vertex-to-simplices index still lists removed simplex {simplex_key:?} for vertex {vertex_key:?} after incidence removal"
)]
RemovedSimplexStillIncident {
vertex_key: VertexKey,
simplex_key: SimplexKey,
},
#[error(
"Vertex-to-simplices index lists simplex {simplex_key:?} for vertex {vertex_key:?}, but the simplex does not contain the vertex"
)]
VertexIncidenceMismatch {
vertex_key: VertexKey,
simplex_key: SimplexKey,
},
#[error("Internal data structure inconsistency: {message}")]
InconsistentDataStructure {
message: String,
},
#[error(transparent)]
Geometric(#[from] GeometricError),
#[error("Facet operation failed: {0}")]
FacetError(#[from] FacetError),
#[error("Duplicate coordinates in simplex {simplex_id}: {message}")]
DuplicateCoordinatesInSimplex {
simplex_id: Uuid,
message: String,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TdsErrorKind {
InvalidVertex,
InvalidSimplex,
InvalidNeighbors,
OrientationViolation,
DuplicateSimplices,
FacetSharingViolation,
FailedToCreateSimplex,
NotNeighbors,
MappingInconsistency,
VertexKeyRetrievalFailed,
SimplexNotFound,
VertexNotFound,
DimensionMismatch,
IndexOutOfBounds,
RemovedSimplexStillIncident,
VertexIncidenceMismatch,
InconsistentDataStructure,
Geometric,
FacetError,
DuplicateCoordinatesInSimplex,
}
impl From<&TdsError> for TdsErrorKind {
fn from(source: &TdsError) -> Self {
match source {
TdsError::InvalidVertex { .. } => Self::InvalidVertex,
TdsError::InvalidSimplex { .. } => Self::InvalidSimplex,
TdsError::InvalidNeighbors { .. } => Self::InvalidNeighbors,
TdsError::OrientationViolation { .. } => Self::OrientationViolation,
TdsError::DuplicateSimplices { .. } | TdsError::DuplicateExplicitSimplices { .. } => {
Self::DuplicateSimplices
}
TdsError::FacetSharingViolation { .. }
| TdsError::ExplicitFacetSharingViolation { .. } => Self::FacetSharingViolation,
TdsError::FailedToCreateSimplex { .. } => Self::FailedToCreateSimplex,
TdsError::NotNeighbors { .. } => Self::NotNeighbors,
TdsError::MappingInconsistency { .. } => Self::MappingInconsistency,
TdsError::VertexKeyRetrievalFailed { .. } => Self::VertexKeyRetrievalFailed,
TdsError::SimplexNotFound { .. } => Self::SimplexNotFound,
TdsError::VertexNotFound { .. } => Self::VertexNotFound,
TdsError::DimensionMismatch { .. } => Self::DimensionMismatch,
TdsError::IndexOutOfBounds { .. } => Self::IndexOutOfBounds,
TdsError::RemovedSimplexStillIncident { .. } => Self::RemovedSimplexStillIncident,
TdsError::VertexIncidenceMismatch { .. } => Self::VertexIncidenceMismatch,
TdsError::InconsistentDataStructure { .. } => Self::InconsistentDataStructure,
TdsError::Geometric(_) => Self::Geometric,
TdsError::FacetError(_) => Self::FacetError,
TdsError::DuplicateCoordinatesInSimplex { .. } => Self::DuplicateCoordinatesInSimplex,
}
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[error(transparent)]
#[must_use]
pub struct TdsMutationError(TdsError);
impl TdsMutationError {
#[must_use]
pub const fn as_tds_error(&self) -> &TdsError {
&self.0
}
#[must_use]
pub fn into_inner(self) -> TdsError {
self.0
}
}
impl From<TdsError> for TdsMutationError {
fn from(err: TdsError) -> Self {
Self(err)
}
}
impl From<TdsMutationError> for TdsError {
fn from(err: TdsMutationError) -> Self {
err.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum InvariantKind {
VertexValidity,
SimplexValidity,
SimplexCoordinateUniqueness,
VertexMappings,
SimplexMappings,
SimplexVertexKeys,
VertexIncidence,
VertexToSimplicesIndex,
DuplicateSimplices,
FacetSharing,
NeighborConsistency,
CoherentOrientation,
Connectedness,
Topology,
Realization,
DelaunayProperty,
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum InvariantError {
#[error(transparent)]
Tds(#[from] TdsError),
#[error(transparent)]
Triangulation(#[from] TriangulationValidationError),
#[error(transparent)]
Realization(#[from] TriangulationRealizationValidationError),
#[error(transparent)]
Delaunay(#[from] DelaunayTriangulationValidationError),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TriangulationValidationErrorKind {
ManifoldFacetMultiplicity,
BoundaryRidgeMultiplicity,
BoundaryFacetInClosedTopology,
PeriodicIdentificationInNonPeriodicTopology,
RidgeNotFound,
RidgeLinkNotManifold,
VertexLinkNotManifold,
NonOrientable,
EulerCharacteristicMismatch,
IsolatedVertex,
Disconnected,
OrientationPromotionNonConvergence,
}
impl From<&TriangulationValidationError> for TriangulationValidationErrorKind {
fn from(source: &TriangulationValidationError) -> Self {
match source {
TriangulationValidationError::ManifoldFacetMultiplicity { .. } => {
Self::ManifoldFacetMultiplicity
}
TriangulationValidationError::BoundaryRidgeMultiplicity { .. } => {
Self::BoundaryRidgeMultiplicity
}
TriangulationValidationError::BoundaryFacetInClosedTopology { .. } => {
Self::BoundaryFacetInClosedTopology
}
TriangulationValidationError::PeriodicIdentificationInNonPeriodicTopology {
..
} => Self::PeriodicIdentificationInNonPeriodicTopology,
TriangulationValidationError::RidgeNotFound { .. } => Self::RidgeNotFound,
TriangulationValidationError::RidgeLinkNotManifold { .. } => Self::RidgeLinkNotManifold,
TriangulationValidationError::VertexLinkNotManifold { .. } => {
Self::VertexLinkNotManifold
}
TriangulationValidationError::NonOrientable { .. } => Self::NonOrientable,
TriangulationValidationError::EulerCharacteristicMismatch { .. } => {
Self::EulerCharacteristicMismatch
}
TriangulationValidationError::IsolatedVertex { .. } => Self::IsolatedVertex,
TriangulationValidationError::Disconnected { .. } => Self::Disconnected,
TriangulationValidationError::OrientationPromotionNonConvergence { .. } => {
Self::OrientationPromotionNonConvergence
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum DelaunayValidationErrorKind {
Tds,
Triangulation,
Realization,
VerificationFailed,
RepairOperationFailed,
}
impl From<&DelaunayTriangulationValidationError> for DelaunayValidationErrorKind {
fn from(source: &DelaunayTriangulationValidationError) -> Self {
match source {
DelaunayTriangulationValidationError::Tds(_) => Self::Tds,
DelaunayTriangulationValidationError::Triangulation(_) => Self::Triangulation,
DelaunayTriangulationValidationError::Realization(_) => Self::Realization,
DelaunayTriangulationValidationError::VerificationFailed { .. } => {
Self::VerificationFailed
}
DelaunayTriangulationValidationError::RepairOperationFailed { .. } => {
Self::RepairOperationFailed
}
}
}
}
#[derive(Clone, Debug)]
pub struct InvariantViolation {
pub kind: InvariantKind,
pub error: InvariantError,
}
#[derive(Clone, Debug)]
pub struct TriangulationValidationReport {
pub violations: Vec<InvariantViolation>,
}
impl TriangulationValidationReport {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.violations.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::algorithms::flips::{DelaunayRepairError, DelaunayRepairPostconditionFailure};
use crate::core::facet::FacetError;
use crate::core::simplex::SimplexValidationError;
use crate::core::util::uuid::UuidValidationError;
use crate::core::validation::TriangulationValidationError;
use crate::core::vertex::VertexValidationError;
use crate::repair::DelaunayRepairOperation;
use crate::topology::characteristics::euler::TopologyClassification;
use crate::topology::traits::topological_space::TopologyKind;
use crate::validation::{DelaunayTriangulationValidationError, DelaunayVerificationError};
use slotmap::KeyData;
use std::{assert_matches, iter};
fn synthetic_delaunay_verification_error(
message: &str,
) -> DelaunayTriangulationValidationError {
let _ = message;
DelaunayTriangulationValidationError::VerificationFailed {
source: DelaunayVerificationError::from(DelaunayRepairError::PostconditionFailed {
reason: Box::new(DelaunayRepairPostconditionFailure::Disconnected {
simplex_count: 1,
}),
})
.into(),
}
}
fn assert_tds_error_kind(source: &TdsError, expected: TdsErrorKind) {
assert_eq!(TdsErrorKind::from(source), expected);
}
#[test]
fn triangulation_construction_state_default_is_empty_incomplete() {
assert_eq!(
TriangulationConstructionState::default(),
TriangulationConstructionState::Incomplete(0)
);
}
#[test]
fn tds_error_kind_from_error_preserves_validation_variants() {
let simplex_key = SimplexKey::from(KeyData::from_ffi(1));
let other_simplex_key = SimplexKey::from(KeyData::from_ffi(2));
let vertex_key = VertexKey::from(KeyData::from_ffi(3));
let uuid = Uuid::new_v4();
assert_tds_error_kind(
&TdsError::InvalidVertex {
vertex_id: uuid,
source: VertexValidationError::InvalidUuid {
source: UuidValidationError::NilUuid,
},
},
TdsErrorKind::InvalidVertex,
);
assert_tds_error_kind(
&TdsError::InvalidSimplex {
simplex_id: uuid,
source: SimplexValidationError::DuplicateVertices,
},
TdsErrorKind::InvalidSimplex,
);
assert_tds_error_kind(
&TdsError::InvalidNeighbors {
reason: NeighborValidationError::NonPeriodicSelfNeighbor {
simplex_key,
simplex_uuid: uuid,
facet_index: 0,
},
},
TdsErrorKind::InvalidNeighbors,
);
assert_tds_error_kind(
&TdsError::OrientationViolation {
simplex1_key: simplex_key,
simplex1_uuid: uuid,
simplex2_key: other_simplex_key,
simplex2_uuid: Uuid::new_v4(),
simplex1_facet_index: 0,
simplex2_facet_index: 1,
facet_vertices: vec![vertex_key],
simplex2_facet_vertices: vec![vertex_key],
observed_odd_permutation: false,
expected_odd_permutation: true,
},
TdsErrorKind::OrientationViolation,
);
assert_tds_error_kind(
&TdsError::Geometric(GeometricError::DegenerateOrientation {
message: "zero determinant".to_string(),
}),
TdsErrorKind::Geometric,
);
assert_tds_error_kind(
&TdsError::FacetError(FacetError::InvalidFacetIndex {
index: 4,
facet_count: 4,
}),
TdsErrorKind::FacetError,
);
assert_tds_error_kind(
&TdsError::DuplicateCoordinatesInSimplex {
simplex_id: uuid,
message: "two vertices share coordinates".to_string(),
},
TdsErrorKind::DuplicateCoordinatesInSimplex,
);
}
#[test]
fn tds_error_kind_from_error_preserves_lookup_and_operation_variants() {
let simplex_key = SimplexKey::from(KeyData::from_ffi(1));
let vertex_key = VertexKey::from(KeyData::from_ffi(3));
let uuid = Uuid::new_v4();
assert_tds_error_kind(
&TdsError::DuplicateSimplices {
message: "duplicate simplex vertex set".to_string(),
},
TdsErrorKind::DuplicateSimplices,
);
assert_tds_error_kind(
&TdsError::FacetSharingViolation {
facet_key: 42,
existing_incident_count: 2,
attempted_incident_count: 3,
max_incident_count: 2,
candidate_simplex_uuid: uuid,
candidate_facet_index: 1,
},
TdsErrorKind::FacetSharingViolation,
);
assert_tds_error_kind(
&TdsError::FailedToCreateSimplex {
message: "simplex validation failed".to_string(),
},
TdsErrorKind::FailedToCreateSimplex,
);
assert_tds_error_kind(
&TdsError::NotNeighbors {
simplex1: uuid,
simplex2: Uuid::new_v4(),
},
TdsErrorKind::NotNeighbors,
);
assert_tds_error_kind(
&TdsError::MappingInconsistency {
entity: EntityKind::Simplex,
message: "uuid mapping was stale".to_string(),
},
TdsErrorKind::MappingInconsistency,
);
assert_tds_error_kind(
&TdsError::VertexKeyRetrievalFailed {
simplex_id: uuid,
message: "simplex vertices unavailable".to_string(),
},
TdsErrorKind::VertexKeyRetrievalFailed,
);
assert_tds_error_kind(
&TdsError::SimplexNotFound {
simplex_key,
context: "simplex lookup".to_string(),
},
TdsErrorKind::SimplexNotFound,
);
assert_tds_error_kind(
&TdsError::VertexNotFound {
vertex_key,
context: "vertex lookup".to_string(),
},
TdsErrorKind::VertexNotFound,
);
assert_tds_error_kind(
&TdsError::DimensionMismatch {
expected: 4,
actual: 3,
context: "simplex arity".to_string(),
},
TdsErrorKind::DimensionMismatch,
);
assert_tds_error_kind(
&TdsError::IndexOutOfBounds {
index: 4,
bound: 4,
context: "facet index".to_string(),
},
TdsErrorKind::IndexOutOfBounds,
);
assert_tds_error_kind(
&TdsError::InconsistentDataStructure {
message: "dangling neighbor".to_string(),
},
TdsErrorKind::InconsistentDataStructure,
);
}
#[test]
fn tds_error_kind_from_error_preserves_explicit_operation_variants() {
assert_tds_error_kind(
&TdsError::DuplicateExplicitSimplices {
existing_simplex_index: 0,
duplicate_simplex_index: 1,
vertex_indices: vec![3],
},
TdsErrorKind::DuplicateSimplices,
);
assert_tds_error_kind(
&TdsError::ExplicitFacetSharingViolation {
facet_key: 42,
facet_vertex_indices: vec![0, 1],
existing_incident_count: 2,
attempted_incident_count: 3,
max_incident_count: 2,
candidate_simplex_index: 2,
candidate_facet_index: 1,
},
TdsErrorKind::FacetSharingViolation,
);
}
#[test]
fn triangulation_validation_error_kind_from_error_preserves_all_variants() {
let vertex_key = VertexKey::from(KeyData::from_ffi(3));
let cases = [
(
TriangulationValidationError::ManifoldFacetMultiplicity {
facet_key: 0xabc,
simplex_count: 3,
},
TriangulationValidationErrorKind::ManifoldFacetMultiplicity,
),
(
TriangulationValidationError::BoundaryRidgeMultiplicity {
ridge_key: 0xdef,
boundary_facet_count: 3,
},
TriangulationValidationErrorKind::BoundaryRidgeMultiplicity,
),
(
TriangulationValidationError::BoundaryFacetInClosedTopology {
topology: TopologyKind::Spherical,
facet_key: 0x111,
simplex_key: SimplexKey::from(KeyData::from_ffi(5)),
simplex_uuid: Uuid::new_v4(),
facet_index: 1,
},
TriangulationValidationErrorKind::BoundaryFacetInClosedTopology,
),
(
TriangulationValidationError::PeriodicIdentificationInNonPeriodicTopology {
topology: TopologyKind::Euclidean,
facet_key: 0x222,
simplex_key: SimplexKey::from(KeyData::from_ffi(6)),
simplex_uuid: Uuid::new_v4(),
facet_index: 2,
},
TriangulationValidationErrorKind::PeriodicIdentificationInNonPeriodicTopology,
),
(
TriangulationValidationError::RidgeNotFound {
ridge_vertices: iter::once(vertex_key).collect(),
},
TriangulationValidationErrorKind::RidgeNotFound,
),
(
TriangulationValidationError::RidgeLinkNotManifold {
ridge_key: 0x123,
link_vertex_count: 4,
link_edge_count: 2,
max_degree: 3,
degree_one_vertices: 1,
connected: false,
},
TriangulationValidationErrorKind::RidgeLinkNotManifold,
),
(
TriangulationValidationError::VertexLinkNotManifold {
vertex_key,
link_vertex_count: 4,
link_simplex_count: 2,
boundary_facet_count: 1,
max_degree: 3,
connected: false,
interior_vertex: true,
},
TriangulationValidationErrorKind::VertexLinkNotManifold,
),
(
TriangulationValidationError::EulerCharacteristicMismatch {
computed: 0,
expected: 1,
classification: TopologyClassification::Ball(3),
},
TriangulationValidationErrorKind::EulerCharacteristicMismatch,
),
(
TriangulationValidationError::IsolatedVertex {
vertex_key,
vertex_uuid: Uuid::new_v4(),
},
TriangulationValidationErrorKind::IsolatedVertex,
),
(
TriangulationValidationError::Disconnected { simplex_count: 2 },
TriangulationValidationErrorKind::Disconnected,
),
(
TriangulationValidationError::OrientationPromotionNonConvergence {
residual_count: 1,
sampled: vec![SimplexKey::from(KeyData::from_ffi(4))],
},
TriangulationValidationErrorKind::OrientationPromotionNonConvergence,
),
];
for (source, expected) in cases {
assert_eq!(TriangulationValidationErrorKind::from(&source), expected);
}
}
#[test]
fn triangulation_validation_error_kind_preserves_non_orientable() {
let source = TriangulationValidationError::NonOrientable {
simplex1_key: SimplexKey::from(KeyData::from_ffi(7)),
simplex1_uuid: Uuid::from_u128(7),
simplex1_facet_index: 1,
simplex2_key: SimplexKey::from(KeyData::from_ffi(8)),
simplex2_uuid: Uuid::from_u128(8),
simplex2_facet_index: 2,
};
assert_eq!(
TriangulationValidationErrorKind::from(&source),
TriangulationValidationErrorKind::NonOrientable
);
}
#[test]
fn delaunay_validation_error_kind_from_error_preserves_all_variants() {
let cases = [
(
DelaunayTriangulationValidationError::from(TdsError::InconsistentDataStructure {
message: "dangling simplex".to_string(),
}),
DelaunayValidationErrorKind::Tds,
),
(
DelaunayTriangulationValidationError::from(
TriangulationValidationError::Disconnected { simplex_count: 2 },
),
DelaunayValidationErrorKind::Triangulation,
),
(
synthetic_delaunay_verification_error("non-Delaunay facet"),
DelaunayValidationErrorKind::VerificationFailed,
),
(
DelaunayTriangulationValidationError::RepairOperationFailed {
operation: DelaunayRepairOperation::VertexRemoval,
source: Box::new(DelaunayRepairError::PostconditionFailed {
reason: Box::new(DelaunayRepairPostconditionFailure::Disconnected {
simplex_count: 1,
}),
}),
},
DelaunayValidationErrorKind::RepairOperationFailed,
),
];
for (source, expected) in cases {
assert_eq!(DelaunayValidationErrorKind::from(&source), expected);
}
}
#[test]
fn test_geometric_error_display() {
let deg = GeometricError::DegenerateOrientation {
message: "det=0".to_string(),
};
assert!(deg.to_string().contains("det=0"));
let neg = GeometricError::NegativeOrientation {
message: "det<0".to_string(),
};
assert!(neg.to_string().contains("det<0"));
}
#[test]
fn test_tds_error_new_variant_display() {
let simplex_key = SimplexKey::from(KeyData::from_ffi(1));
let vertex_key = VertexKey::from(KeyData::from_ffi(2));
let err = TdsError::SimplexNotFound {
simplex_key,
context: "test lookup".to_string(),
};
assert!(err.to_string().contains("not found"));
assert!(err.to_string().contains("test lookup"));
let err = TdsError::VertexNotFound {
vertex_key,
context: "test vertex".to_string(),
};
assert!(err.to_string().contains("not found"));
assert!(err.to_string().contains("test vertex"));
let err = TdsError::DimensionMismatch {
expected: 4,
actual: 3,
context: "simplex check".to_string(),
};
let msg = err.to_string();
assert!(msg.contains('4') && msg.contains('3') && msg.contains("simplex check"));
let err = TdsError::IndexOutOfBounds {
index: 10,
bound: 5,
context: "facet index".to_string(),
};
let msg = err.to_string();
assert!(msg.contains("10") && msg.contains('5') && msg.contains("facet index"));
}
#[test]
fn test_tds_error_geometric_variant_wraps_geometric_error() {
let inner = GeometricError::DegenerateOrientation {
message: "test".to_string(),
};
let err = TdsError::Geometric(inner.clone());
assert!(err.to_string().contains("test"));
assert_eq!(TdsError::from(inner.clone()), TdsError::Geometric(inner));
}
#[test]
fn test_tds_mutation_error_accessors() {
let inner = TdsError::InconsistentDataStructure {
message: "test".to_string(),
};
let mutation = TdsMutationError::from(inner.clone());
assert_eq!(mutation.as_tds_error(), &inner);
let recovered: TdsError = mutation.into_inner();
assert_eq!(recovered, inner);
}
#[test]
fn test_invariant_error_from_tds_and_triangulation() {
let tds_err = TdsError::InconsistentDataStructure {
message: "test".to_string(),
};
let inv = InvariantError::from(tds_err);
assert_matches!(inv, InvariantError::Tds(_));
let tri_err = TriangulationValidationError::EulerCharacteristicMismatch {
computed: 1,
expected: 2,
classification: TopologyClassification::Ball(3),
};
let inv = InvariantError::from(tri_err);
assert_matches!(inv, InvariantError::Triangulation(_));
}
#[test]
fn test_invariant_error_from_delaunay_validation_error() {
let dt_err = synthetic_delaunay_verification_error("test");
let inv = InvariantError::from(dt_err);
assert_matches!(inv, InvariantError::Delaunay(_));
}
#[test]
fn test_invariant_kind_all_variants_are_distinct() {
let kinds = [
InvariantKind::VertexValidity,
InvariantKind::SimplexValidity,
InvariantKind::SimplexCoordinateUniqueness,
InvariantKind::VertexMappings,
InvariantKind::SimplexMappings,
InvariantKind::SimplexVertexKeys,
InvariantKind::VertexIncidence,
InvariantKind::VertexToSimplicesIndex,
InvariantKind::DuplicateSimplices,
InvariantKind::FacetSharing,
InvariantKind::NeighborConsistency,
InvariantKind::CoherentOrientation,
InvariantKind::Connectedness,
InvariantKind::Topology,
InvariantKind::DelaunayProperty,
];
for (i, &a) in kinds.iter().enumerate() {
assert_eq!(a, a);
for &b in &kinds[i + 1..] {
assert_ne!(a, b);
}
}
}
#[test]
fn test_invariant_violation_stores_kind_and_error() {
let violation = InvariantViolation {
kind: InvariantKind::NeighborConsistency,
error: InvariantError::Tds(TdsError::InconsistentDataStructure {
message: "test".to_string(),
}),
};
assert_eq!(violation.kind, InvariantKind::NeighborConsistency);
assert_matches!(violation.error, InvariantError::Tds(_));
}
#[test]
fn test_entity_kind_debug_output() {
assert_eq!(format!("{:?}", EntityKind::Vertex), "Vertex");
assert_eq!(format!("{:?}", EntityKind::Simplex), "Simplex");
assert_ne!(EntityKind::Vertex, EntityKind::Simplex);
}
#[test]
fn test_tds_mutation_error_from_round_trips() {
let original = TdsError::SimplexNotFound {
simplex_key: SimplexKey::from(KeyData::from_ffi(42)),
context: "round trip".to_string(),
};
let mutation = TdsMutationError::from(original.clone());
assert_eq!(mutation.to_string(), original.to_string());
let round_tripped: TdsError = mutation.into();
assert_eq!(round_tripped, original);
}
#[test]
fn test_geometric_error_from_into_tds_error() {
let geo = GeometricError::NegativeOrientation {
message: "det<0".to_string(),
};
let tds_err: TdsError = geo.into();
assert_matches!(
tds_err,
TdsError::Geometric(GeometricError::NegativeOrientation { .. })
);
assert!(tds_err.to_string().contains("det<0"));
}
}