#![forbid(unsafe_code)]
use super::{
CavityFillingError, ConflictError, CoordinateConversionError, CoordinateValidationError,
CoordinateValues, DelaunayTriangulationValidationError, EdgeKey, EntityKind, Error, FacetError,
FacetHandle, FlipDirection, FlipOrientationCheckStage, GlobalTopologyModelError,
HullExtensionReason, InsertionError, InsertionErrorKind, InsertionTopologyValidationContext,
LocateError, MAX_PRACTICAL_DIMENSION_SIZE, NeighborWiringError, RidgeHandle, SimplexKey,
SimplexValidationError, SmallBuffer, SpatialIndexConstructionFailure, TdsConstructionFailure,
TdsMutationError, TdsValidationFailure, TopologyGuarantee, TopologyOwnerId, TriangleHandle,
TriangulationRealizationValidationError, TriangulationValidationError, VertexKey, fmt,
};
use crate::core::tds::InvariantError;
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipPredicateOperation {
#[error("replacement-simplex orientation")]
ReplacementSimplexOrientation,
#[error("Delaunay-repair replacement-simplex orientation")]
DelaunayRepairReplacementOrientation,
#[error("degenerate-simplex precheck")]
DegenerateSimplexPrecheck,
#[error("k=2 simplex-A insphere")]
K2SimplexAInSphere,
#[error("k=2 simplex-B insphere")]
K2SimplexBInSphere,
#[error("k=3 simplex insphere")]
K3SimplexInSphere,
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum FlipPredicateError {
#[error("{operation} predicate failed: {source}")]
CoordinateConversion {
operation: FlipPredicateOperation,
#[source]
source: CoordinateConversionError,
},
#[error("failed to lift vertex {vertex_key:?} for periodic predicate: {source}")]
PeriodicVertexLift {
vertex_key: VertexKey,
#[source]
source: GlobalTopologyModelError,
},
#[error("lifted periodic vertex {vertex_key:?} produced invalid coordinates: {source}")]
PeriodicLiftedPointValidation {
vertex_key: VertexKey,
#[source]
source: CoordinateValidationError,
},
#[error("failed to lift proposed k=1 vertex into simplex {simplex_key:?} frame: {source}")]
K1InsertedVertexLift {
simplex_key: SimplexKey,
#[source]
source: GlobalTopologyModelError,
},
#[error(
"proposed k=1 vertex lifted into simplex {simplex_key:?} frame produced invalid coordinates: {source}"
)]
K1InsertedVertexPointValidation {
simplex_key: SimplexKey,
#[source]
source: CoordinateValidationError,
},
}
impl FlipPredicateError {
pub(super) const fn coordinate_conversion(
operation: FlipPredicateOperation,
source: CoordinateConversionError,
) -> Self {
Self::CoordinateConversion { operation, source }
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipContextError {
#[error("k must be in 1..=D+1 (k={k_move}, D={dimension})")]
InvalidMoveSize {
k_move: usize,
dimension: usize,
},
#[error("removed-face must have {expected} vertices, got {found}")]
WrongRemovedFaceArity {
expected: usize,
found: usize,
},
#[error("k={k_move} inserted-face must have {expected} vertices, got {found}")]
WrongInsertedFaceArity {
k_move: usize,
expected: usize,
found: usize,
},
#[error("removed_simplices must have {expected} entries, got {found}")]
WrongRemovedSimplexCount {
expected: usize,
found: usize,
},
#[error("removed-face and inserted-face must be disjoint")]
OverlappingFaces,
#[error(
"TDS coherent orientation invariant violated during {stage:?} for k={k_move}, direction={direction:?}"
)]
CoherentOrientationViolation {
stage: FlipOrientationCheckStage,
k_move: usize,
direction: FlipDirection,
},
#[error(
"replacement periodic offset count {offset_count} does not match replacement simplex count {simplex_count}"
)]
ReplacementPeriodicOffsetCountMismatch {
simplex_count: usize,
offset_count: usize,
},
#[error(
"replacement simplex {simplex_index} is missing periodic offsets for periodic facet parity"
)]
MissingReplacementPeriodicOffsets {
simplex_index: usize,
},
#[error(
"replacement simplex {simplex_index} periodic offset count {offset_count} does not match vertex count {vertex_count}"
)]
ReplacementPeriodicOffsetLengthMismatch {
simplex_index: usize,
offset_count: usize,
vertex_count: usize,
},
#[error(
"conflicting replacement-simplex orientation constraints between local simplices {source_simplex_index} and {target_simplex_index}"
)]
ConflictingReplacementOrientationBetweenSimplices {
source_simplex_index: usize,
target_simplex_index: usize,
},
#[error("replacement simplex needs at least two vertices to flip orientation")]
ReplacementSimplexTooSmallForOrientationFlip,
#[error("replacement orientation index {simplex_index} out of range")]
ReplacementOrientationIndexOutOfRange {
simplex_index: usize,
},
#[error(
"conflicting replacement-simplex orientation constraints for local simplex {simplex_index}"
)]
ConflictingReplacementOrientationForSimplex {
simplex_index: usize,
},
#[error("could not derive replacement facet-order permutation parity")]
FacetOrderParityUnavailable,
#[error(
"facet index {facet_index} out of range for replacement simplex with {vertex_count} vertices"
)]
ReplacementFacetIndexOutOfRange {
facet_index: usize,
vertex_count: usize,
},
#[error("k=2 facet must have {expected} vertices, got {found}")]
K2FacetArity {
expected: usize,
found: usize,
},
#[error("k=2 opposites must be distinct and not in the facet")]
InvalidK2Opposites,
#[error("k=3 ridge must have {expected} vertices, got {found}")]
K3RidgeArity {
expected: usize,
found: usize,
},
#[error(
"conflicting periodic offsets for vertex {vertex_key:?} in simplex {simplex_key:?}: expected {expected_offset:?}, got {found_offset:?}"
)]
ConflictingPeriodicVertexOffset {
simplex_key: SimplexKey,
vertex_key: VertexKey,
expected_offset: Vec<i8>,
found_offset: Vec<i8>,
},
#[error(
"conflicting periodic frame translations while aligning vertex {vertex_key:?} from simplex {source_simplex_key:?} into frame {target_simplex_key:?}: expected {expected_offset:?}, got {found_offset:?}"
)]
ConflictingPeriodicFrameTranslation {
vertex_key: VertexKey,
source_simplex_key: SimplexKey,
target_simplex_key: SimplexKey,
expected_offset: Vec<i8>,
found_offset: Vec<i8>,
},
#[error(
"conflicting periodic frame translations while aligning vertex {vertex_key:?} from external simplex {source_simplex_key:?} into replacement simplex {target_simplex_index}: expected {expected_offset:?}, got {found_offset:?}"
)]
ConflictingReplacementPeriodicFrameTranslation {
vertex_key: VertexKey,
source_simplex_key: SimplexKey,
target_simplex_index: usize,
expected_offset: Vec<i8>,
found_offset: Vec<i8>,
},
#[error("cannot align periodic vertex {vertex_key:?} into frame {target_simplex_key:?}")]
PeriodicVertexAlignmentFailed {
vertex_key: VertexKey,
target_simplex_key: SimplexKey,
},
#[error(
"simplex {simplex_key:?} periodic offset count {offset_count} does not match vertex count {vertex_count}"
)]
PeriodicOffsetCountMismatch {
simplex_key: SimplexKey,
offset_count: usize,
vertex_count: usize,
},
#[error("periodic offset subtraction overflow on axis {axis}")]
PeriodicOffsetSubtractionOverflow {
axis: usize,
},
#[error("periodic offset addition overflow on axis {axis}")]
PeriodicOffsetAdditionOverflow {
axis: usize,
},
#[error("inverse flip predicate requires at least one removed simplex frame")]
MissingRemovedSimplexFrame,
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipFailureKind {
#[error("wrong topology owner")]
WrongTopologyOwner,
#[error("stale topology proposal")]
StaleTopologyProposal,
#[error("unsupported dimension")]
UnsupportedDimension,
#[error("flip topology not admissible")]
FlipTopologyNotAdmissible,
#[error("boundary facet")]
BoundaryFacet,
#[error("missing simplex")]
MissingSimplex,
#[error("dangling vertex incidence")]
DanglingVertexIncidence,
#[error("missing vertex")]
MissingVertex,
#[error("missing neighbor")]
MissingNeighbor,
#[error("dangling ridge neighbor")]
DanglingRidgeNeighbor,
#[error("invalid facet adjacency")]
InvalidFacetAdjacency,
#[error("invalid facet index")]
InvalidFacetIndex,
#[error("invalid ridge index")]
InvalidRidgeIndex,
#[error("invalid ridge adjacency")]
InvalidRidgeAdjacency,
#[error("invalid ridge multiplicity")]
InvalidRidgeMultiplicity,
#[error("invalid edge multiplicity")]
InvalidEdgeMultiplicity,
#[error("invalid triangle multiplicity")]
InvalidTriangleMultiplicity,
#[error("invalid edge adjacency")]
InvalidEdgeAdjacency,
#[error("invalid triangle adjacency")]
InvalidTriangleAdjacency,
#[error("invalid vertex multiplicity")]
InvalidVertexMultiplicity,
#[error("invalid vertex adjacency")]
InvalidVertexAdjacency,
#[error("invalid flip context")]
InvalidFlipContext,
#[error("predicate failure")]
PredicateFailure,
#[error("degenerate simplex")]
DegenerateSimplex,
#[error("k=1 insertion outside simplex")]
K1InsertionOutsideSimplex,
#[error("negative orientation")]
NegativeOrientation,
#[error("duplicate simplex")]
DuplicateSimplex,
#[error("non-manifold facet")]
NonManifoldFacet,
#[error("inserted simplex already exists")]
InsertedSimplexAlreadyExists,
#[error("facet iteration")]
FacetIteration,
#[error("simplex creation")]
SimplexCreation,
#[error("postcondition orientation repair")]
PostconditionRepair,
#[error("invariant validation")]
InvariantValidation,
#[error("realization validation")]
RealizationValidation,
#[error("neighbor wiring")]
NeighborWiring,
#[error("trial validation")]
TrialValidation,
#[error("wiring validation")]
WiringValidation,
#[error("Delaunay repair failed")]
DelaunayRepairFailed,
#[error("TDS mutation")]
TdsMutation,
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborCavityFailureKind {
#[error("missing boundary simplex")]
MissingBoundarySimplex,
#[error("missing inserted vertex")]
MissingInsertedVertex,
#[error("wrong simplex arity")]
WrongSimplexArity,
#[error("invalid facet index")]
InvalidFacetIndex,
#[error("simplex creation")]
SimplexCreation,
#[error("simplex insertion")]
SimplexInsertion,
#[error("initial simplex construction")]
InitialSimplexConstruction,
#[error("rebuilt vertex missing")]
RebuiltVertexMissing,
#[error("empty conflict region")]
EmptyConflictRegion,
#[error("empty boundary")]
EmptyBoundary,
#[error("invalid facet sharing after repair")]
InvalidFacetSharingAfterRepair,
#[error("boundary simplex count mismatch")]
BoundarySimplexCountMismatch,
#[error("neighbor rebuild")]
NeighborRebuild,
#[error("perturbation scale conversion")]
PerturbationScaleConversion,
#[error("unsupported degenerate location")]
UnsupportedDegenerateLocation,
#[error("empty fan triangulation")]
EmptyFanTriangulation,
#[error("local facet repair quality evaluation")]
LocalFacetQualityEvaluation,
#[error("local facet repair non-finite quality")]
LocalFacetNonFiniteQuality,
}
impl From<&CavityFillingError> for FlipNeighborCavityFailureKind {
fn from(source: &CavityFillingError) -> Self {
match source {
CavityFillingError::MissingBoundarySimplex { .. } => Self::MissingBoundarySimplex,
CavityFillingError::MissingInsertedVertex { .. } => Self::MissingInsertedVertex,
CavityFillingError::WrongSimplexArity { .. } => Self::WrongSimplexArity,
CavityFillingError::InvalidFacetIndex { .. } => Self::InvalidFacetIndex,
CavityFillingError::SimplexCreation { .. } => Self::SimplexCreation,
CavityFillingError::SimplexInsertion { .. } => Self::SimplexInsertion,
CavityFillingError::InitialSimplexConstruction { .. } => {
Self::InitialSimplexConstruction
}
CavityFillingError::RebuiltVertexMissing { .. } => Self::RebuiltVertexMissing,
CavityFillingError::EmptyConflictRegion { .. } => Self::EmptyConflictRegion,
CavityFillingError::EmptyBoundary { .. } => Self::EmptyBoundary,
CavityFillingError::InvalidFacetSharingAfterRepair { .. } => {
Self::InvalidFacetSharingAfterRepair
}
CavityFillingError::BoundarySimplexCountMismatch { .. } => {
Self::BoundarySimplexCountMismatch
}
CavityFillingError::NeighborRebuild { .. } => Self::NeighborRebuild,
CavityFillingError::PerturbationScaleConversion { .. } => {
Self::PerturbationScaleConversion
}
CavityFillingError::UnsupportedDegenerateLocation { .. } => {
Self::UnsupportedDegenerateLocation
}
CavityFillingError::EmptyFanTriangulation => Self::EmptyFanTriangulation,
CavityFillingError::LocalFacetQualityEvaluation { .. } => {
Self::LocalFacetQualityEvaluation
}
CavityFillingError::LocalFacetNonFiniteQuality { .. } => {
Self::LocalFacetNonFiniteQuality
}
}
}
}
impl From<CavityFillingError> for FlipNeighborCavityFailureKind {
fn from(source: CavityFillingError) -> Self {
Self::from(&source)
}
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborHullExtensionFailureKind {
#[error("no visible facets")]
NoVisibleFacets,
#[error("boundary edge split facet count")]
BoundaryEdgeSplitFacetCount,
#[error("multiple boundary edge split facets")]
MultipleBoundaryEdgeSplitFacets,
#[error("disconnected visible patch")]
DisconnectedVisiblePatch,
#[error("predicate failed")]
PredicateFailed,
#[error("TDS")]
Tds,
}
impl From<&HullExtensionReason> for FlipNeighborHullExtensionFailureKind {
fn from(source: &HullExtensionReason) -> Self {
match source {
HullExtensionReason::NoVisibleFacets => Self::NoVisibleFacets,
HullExtensionReason::BoundaryEdgeSplitFacetCount { .. } => {
Self::BoundaryEdgeSplitFacetCount
}
HullExtensionReason::MultipleBoundaryEdgeSplitFacets { .. } => {
Self::MultipleBoundaryEdgeSplitFacets
}
HullExtensionReason::DisconnectedVisiblePatch { .. } => Self::DisconnectedVisiblePatch,
HullExtensionReason::PredicateFailed { source: _ } => Self::PredicateFailed,
HullExtensionReason::Tds { source: _ } => Self::Tds,
}
}
}
impl From<HullExtensionReason> for FlipNeighborHullExtensionFailureKind {
fn from(source: HullExtensionReason) -> Self {
Self::from(&source)
}
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborDelaunayValidationFailureKind {
#[error("TDS")]
Tds,
#[error("triangulation")]
Triangulation,
#[error("realization")]
Realization,
#[error("verification failed")]
VerificationFailed,
#[error("repair operation failed")]
RepairOperationFailed,
}
impl From<&DelaunayTriangulationValidationError> for FlipNeighborDelaunayValidationFailureKind {
fn from(source: &DelaunayTriangulationValidationError) -> Self {
match source {
DelaunayTriangulationValidationError::Tds { source: _ } => Self::Tds,
DelaunayTriangulationValidationError::Triangulation { source: _ } => {
Self::Triangulation
}
DelaunayTriangulationValidationError::Realization { source: _ } => Self::Realization,
DelaunayTriangulationValidationError::VerificationFailed { .. } => {
Self::VerificationFailed
}
DelaunayTriangulationValidationError::RepairOperationFailed { .. } => {
Self::RepairOperationFailed
}
}
}
}
impl From<DelaunayTriangulationValidationError> for FlipNeighborDelaunayValidationFailureKind {
fn from(source: DelaunayTriangulationValidationError) -> Self {
Self::from(&source)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FlipNeighborRepairDiagnostics {
pub facets_checked: usize,
pub flips_performed: usize,
pub max_queue_len: usize,
pub ambiguous_predicates: usize,
pub predicate_failures: usize,
pub cycle_detections: usize,
pub attempt: usize,
pub queue_order: RepairQueueOrder,
}
impl fmt::Display for FlipNeighborRepairDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"checked={}, flips={}, max_queue={}, ambiguous={}, predicate_failures={}, cycles={}, attempt={}, order={:?}",
self.facets_checked,
self.flips_performed,
self.max_queue_len,
self.ambiguous_predicates,
self.predicate_failures,
self.cycle_detections,
self.attempt,
self.queue_order
)
}
}
impl From<DelaunayRepairDiagnostics> for FlipNeighborRepairDiagnostics {
fn from(source: DelaunayRepairDiagnostics) -> Self {
Self {
facets_checked: source.facets_checked,
flips_performed: source.flips_performed,
max_queue_len: source.max_queue_len,
ambiguous_predicates: source.ambiguous_predicates,
predicate_failures: source.predicate_failures,
cycle_detections: source.cycle_detections,
attempt: source.attempt,
queue_order: source.queue_order,
}
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborRepairFailure {
#[error("repair did not converge after {max_flips} flips ({diagnostics})")]
NonConvergent {
max_flips: usize,
diagnostics: FlipNeighborRepairDiagnostics,
},
#[error("repair postcondition failed: {reason}")]
PostconditionFailed {
#[source]
reason: DelaunayRepairPostconditionFailure,
},
#[error("repair verification failed during {context}: {source_kind}")]
VerificationFailed {
context: DelaunayRepairVerificationContext,
source_kind: FlipFailureKind,
},
#[error("repair orientation canonicalization failed: {reason}")]
OrientationCanonicalizationFailed {
reason: DelaunayRepairOrientationCanonicalizationFailureKind,
},
#[error("repair requires {required:?} topology, found {found:?}: {message}")]
InvalidTopology {
required: TopologyGuarantee,
found: TopologyGuarantee,
message: &'static str,
},
#[error("heuristic rebuild failed: {reason}")]
HeuristicRebuildFailed {
reason: DelaunayRepairHeuristicRebuildFailureKind,
},
#[error("flip error: {source_kind}")]
Flip {
source_kind: FlipFailureKind,
},
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum FlipNeighborWiringError {
#[error(
"published {dimension}D owner reached flip neighbor wiring during bootstrap; use its incremental builder API"
)]
PublishedOwnerBootstrapRequiresBuilder {
dimension: usize,
},
#[error("flip boundary extraction failed: {source}")]
BoundaryExtraction {
#[source]
source: ConflictError,
},
#[error("neighbor wiring failed: {source}")]
NeighborWiring {
#[source]
source: NeighborWiringError,
},
#[error("non-manifold topology: facet {facet_hash:#x} shared by {simplex_count} simplices")]
NonManifoldTopology {
facet_hash: u64,
simplex_count: usize,
},
#[error("topology validation failed during neighbor wiring: {source}")]
TopologyValidation {
#[source]
source: TdsValidationFailure,
},
#[error("conflict-region error reached flip neighbor wiring: {source}")]
ConflictRegion {
#[source]
source: ConflictError,
},
#[error("point-location error reached flip neighbor wiring: {source}")]
Location {
#[source]
source: LocateError,
},
#[error("cavity filling error reached flip neighbor wiring: {reason}")]
CavityFilling {
reason: FlipNeighborCavityFailureKind,
},
#[error("hull extension error reached flip neighbor wiring: {reason}")]
HullExtension {
reason: FlipNeighborHullExtensionFailureKind,
},
#[error("Delaunay validation error reached flip neighbor wiring: {reason}")]
DelaunayValidation {
reason: FlipNeighborDelaunayValidationFailureKind,
},
#[error("realization validation error reached flip neighbor wiring: {source}")]
RealizationValidation {
#[source]
source: TriangulationRealizationValidationError,
},
#[error("Delaunay repair error reached flip neighbor wiring: {reason}")]
DelaunayRepair {
#[source]
reason: FlipNeighborRepairFailure,
},
#[error("duplicate coordinates reached flip neighbor wiring: {coordinates}")]
DuplicateCoordinates {
coordinates: CoordinateValues,
},
#[error("duplicate UUID reached flip neighbor wiring: {entity:?} {uuid}")]
DuplicateUuid {
entity: EntityKind,
uuid: uuid::Uuid,
},
#[error("topology validation error reached flip neighbor wiring: {context}: {source}")]
TopologyValidationFailed {
context: InsertionTopologyValidationContext,
#[source]
source: TriangulationValidationError,
},
#[error(
"local repair removal budget reached flip neighbor wiring: attempted {attempted}, max {max_simplices_removed}"
)]
MaxSimplicesRemovedExceeded {
max_simplices_removed: usize,
attempted: usize,
},
#[error("spatial index construction reached flip neighbor wiring: {reason}")]
SpatialIndexConstruction {
#[source]
reason: SpatialIndexConstructionFailure,
},
#[error(
"perturbation retry produced invalid coordinates before flip neighbor wiring: {source}"
)]
PerturbedCoordinateInvalid {
#[source]
source: CoordinateValidationError,
},
}
impl From<InsertionError> for FlipNeighborWiringError {
fn from(source: InsertionError) -> Self {
match source {
InsertionError::PublishedOwnerBootstrapRequiresBuilder { dimension } => {
Self::PublishedOwnerBootstrapRequiresBuilder { dimension }
}
InsertionError::NeighborWiring { reason } => Self::NeighborWiring { source: reason },
InsertionError::NonManifoldTopology {
facet_hash,
simplex_count,
} => Self::NonManifoldTopology {
facet_hash,
simplex_count,
},
InsertionError::TopologyValidation { source } => Self::TopologyValidation {
source: source.into(),
},
InsertionError::ConflictRegion { source } => Self::ConflictRegion { source },
InsertionError::Location { source } => Self::Location { source },
InsertionError::CavityFilling { reason } => Self::CavityFilling {
reason: reason.into(),
},
InsertionError::HullExtension { reason } => Self::HullExtension {
reason: reason.into(),
},
InsertionError::DelaunayValidationFailed { source } => Self::DelaunayValidation {
reason: source.into(),
},
InsertionError::RealizationValidationFailed { source } => {
Self::RealizationValidation { source }
}
InsertionError::DelaunayRepairFailed { source, context: _ } => Self::DelaunayRepair {
reason: FlipNeighborRepairFailure::from(*source),
},
InsertionError::DuplicateCoordinates { coordinates } => {
Self::DuplicateCoordinates { coordinates }
}
InsertionError::DuplicateUuid { entity, uuid } => Self::DuplicateUuid { entity, uuid },
InsertionError::TopologyValidationFailed { context, source } => {
Self::TopologyValidationFailed { context, source }
}
InsertionError::MaxSimplicesRemovedExceeded {
max_simplices_removed,
attempted,
} => Self::MaxSimplicesRemovedExceeded {
max_simplices_removed,
attempted,
},
InsertionError::SpatialIndexConstruction { reason } => {
Self::SpatialIndexConstruction { reason }
}
InsertionError::PerturbedCoordinateInvalid { source } => {
Self::PerturbedCoordinateInvalid { source }
}
}
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum FlipMutationError {
#[error("vertex insertion failed: {source}")]
VertexInsertion {
#[source]
source: TdsConstructionFailure,
},
#[error("vertex removal failed: {source}")]
VertexRemoval {
#[source]
source: TdsMutationError,
},
#[error("simplex insertion failed: {source}")]
SimplexInsertion {
#[source]
source: TdsConstructionFailure,
},
#[error("simplex removal failed: {source}")]
SimplexRemoval {
#[source]
source: TdsMutationError,
},
#[error(
"transactional TDS validation failed after bistellar flip (k={k_move}, direction={direction:?}): {source}"
)]
TrialValidation {
k_move: usize,
direction: FlipDirection,
#[source]
source: TdsValidationFailure,
},
#[error(
"transactional TDS coherent orientation invariant violated during {stage:?} (k={k_move}, direction={direction:?})"
)]
CoherentOrientationViolation {
stage: FlipOrientationCheckStage,
k_move: usize,
direction: FlipDirection,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipEdgeAdjacencyError {
#[error("edge endpoints must be distinct ({vertex_key:?})")]
DuplicateEndpoints {
vertex_key: VertexKey,
},
#[error("simplex {simplex_key:?} does not contain edge vertices {v0:?} and {v1:?}")]
SimplexMissingEdgeVertices {
simplex_key: SimplexKey,
v0: VertexKey,
v1: VertexKey,
},
#[error("vertex incidence index does not list any simplex containing edge {v0:?}-{v1:?}")]
MissingEdgeIncidence {
v0: VertexKey,
v1: VertexKey,
},
#[error("vertex incidence index for {vertex_key:?} is missing simplex {simplex_key:?}")]
MissingVertexIncidence {
vertex_key: VertexKey,
simplex_key: SimplexKey,
},
#[error(
"vertex incidence index for {vertex_key:?} incorrectly references simplex {simplex_key:?}"
)]
VertexIncidenceMismatch {
vertex_key: VertexKey,
simplex_key: SimplexKey,
},
#[error(
"edge star must have {expected_vertices} distinct opposite vertices each appearing {expected_occurrences} times, found {found_vertices} distinct vertices"
)]
InvalidOppositeVertexIncidence {
expected_vertices: usize,
found_vertices: usize,
expected_occurrences: usize,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipTriangleAdjacencyError {
#[error("simplex {simplex_key:?} does not contain triangle vertices {a:?}, {b:?}, and {c:?}")]
SimplexMissingTriangleVertices {
simplex_key: SimplexKey,
a: VertexKey,
b: VertexKey,
c: VertexKey,
},
#[error(
"triangle star must have {expected_vertices} ridge vertices each appearing {expected_occurrences} times, found {found_vertices} distinct vertices"
)]
InvalidRidgeVertexIncidence {
expected_vertices: usize,
found_vertices: usize,
expected_occurrences: usize,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TriangleHandleError {
#[error("triangle vertices must be distinct, got {vertices:?}")]
DuplicateVertices {
vertices: [VertexKey; 3],
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipVertexAdjacencyError {
#[error("simplex {simplex_key:?} does not contain vertex {vertex_key:?}")]
SimplexMissingVertex {
simplex_key: SimplexKey,
vertex_key: VertexKey,
},
#[error(
"vertex star must have {expected_vertices} link vertices each appearing {expected_occurrences} times, found {found_vertices} distinct vertices"
)]
InvalidLinkVertexIncidence {
expected_vertices: usize,
found_vertices: usize,
expected_occurrences: usize,
},
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum FlipError {
#[error("Topology proposal owner mismatch: expected {expected:?}, found {found:?}")]
WrongTopologyOwner {
expected: TopologyOwnerId,
found: TopologyOwnerId,
},
#[error(
"Topology proposal generation {proposal_generation} is stale for current generation {current_generation}"
)]
StaleTopologyProposal {
proposal_generation: u64,
current_generation: u64,
},
#[error("Bistellar flip not supported for D={dimension}")]
UnsupportedDimension {
dimension: usize,
},
#[error("Bistellar flip requires {required:?} topology, found {found:?}")]
FlipTopologyNotAdmissible {
required: TopologyGuarantee,
found: TopologyGuarantee,
},
#[error("Facet {facet:?} is on the boundary (no neighbor)")]
BoundaryFacet {
facet: FacetHandle,
},
#[error("Simplex not found: {simplex_key:?}")]
MissingSimplex {
simplex_key: SimplexKey,
},
#[error("Vertex incidence index for {vertex_key:?} references missing simplex {simplex_key:?}")]
DanglingVertexIncidence {
vertex_key: VertexKey,
simplex_key: SimplexKey,
},
#[error("Vertex not found: {vertex_key:?}")]
MissingVertex {
vertex_key: VertexKey,
},
#[error("Neighbor simplex {neighbor_key:?} not found for facet {facet:?}")]
MissingNeighbor {
facet: FacetHandle,
neighbor_key: SimplexKey,
},
#[error(
"Ridge adjacency from simplex {simplex_key:?} references missing neighbor {neighbor_key:?}"
)]
DanglingRidgeNeighbor {
simplex_key: SimplexKey,
neighbor_key: SimplexKey,
},
#[error(
"Facet adjacency mismatch between simplex {simplex_key:?} and neighbor {neighbor_key:?}"
)]
InvalidFacetAdjacency {
simplex_key: SimplexKey,
neighbor_key: SimplexKey,
},
#[error(
"Facet index {facet_index} out of bounds for simplex {simplex_key:?} with {vertex_count} vertices"
)]
InvalidFacetIndex {
simplex_key: SimplexKey,
facet_index: u8,
vertex_count: usize,
},
#[error(
"Ridge indices ({omit_a}, {omit_b}) out of bounds for simplex {simplex_key:?} with {vertex_count} vertices"
)]
InvalidRidgeIndex {
simplex_key: SimplexKey,
omit_a: u8,
omit_b: u8,
vertex_count: usize,
},
#[error("Ridge adjacency mismatch for simplex {simplex_key:?}")]
InvalidRidgeAdjacency {
simplex_key: SimplexKey,
},
#[error("Ridge has invalid multiplicity {found}, expected 3")]
InvalidRidgeMultiplicity {
found: usize,
},
#[error("Edge has invalid multiplicity {found}, expected {expected}")]
InvalidEdgeMultiplicity {
found: usize,
expected: usize,
},
#[error("Triangle has invalid multiplicity {found}, expected {expected}")]
InvalidTriangleMultiplicity {
found: usize,
expected: usize,
},
#[error("Edge adjacency mismatch: {reason}")]
InvalidEdgeAdjacency {
#[source]
reason: Box<FlipEdgeAdjacencyError>,
},
#[error("Triangle adjacency mismatch: {reason}")]
InvalidTriangleAdjacency {
#[source]
reason: Box<FlipTriangleAdjacencyError>,
},
#[error("Vertex star has invalid multiplicity {found}, expected {expected}")]
InvalidVertexMultiplicity {
found: usize,
expected: usize,
},
#[error("Vertex adjacency mismatch: {reason}")]
InvalidVertexAdjacency {
#[source]
reason: Box<FlipVertexAdjacencyError>,
},
#[error("Flip context invalid: {reason}")]
InvalidFlipContext {
#[source]
reason: Box<FlipContextError>,
},
#[error("Geometric predicate failed: {reason}")]
PredicateFailure {
#[source]
reason: Box<FlipPredicateError>,
},
#[error("Flip would create a degenerate simplex (zero orientation)")]
DegenerateSimplex,
#[error(
"Proposed k=1 vertex lies outside simplex {simplex_key:?} across facet slot {opposite_vertex_index} opposite {opposite_vertex:?}"
)]
K1InsertionOutsideSimplex {
simplex_key: SimplexKey,
opposite_vertex: VertexKey,
opposite_vertex_index: usize,
},
#[error(
"Delaunay repair would create a negative-orientation replacement simplex {simplex_vertices:?}"
)]
NegativeOrientation {
simplex_vertices: Vec<VertexKey>,
},
#[error("Flip would create a duplicate simplex")]
DuplicateSimplex,
#[error("Flip would create a non-manifold facet")]
NonManifoldFacet,
#[error(
"Flip would insert simplex that already exists (k={k_move}, simplex={simplex_vertices:?}, existing_simplex={existing_simplex:?})"
)]
InsertedSimplexAlreadyExists {
k_move: usize,
simplex_vertices: Box<SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>>,
existing_simplex: SimplexKey,
},
#[error("Facet iteration failed: {source}")]
FacetIteration {
#[source]
source: Box<FacetError>,
},
#[error(transparent)]
SimplexCreation {
#[from]
source: Box<SimplexValidationError>,
},
#[error("Flip postcondition orientation repair failed: {source}")]
PostconditionRepair {
#[source]
source: Box<InsertionError>,
},
#[error("Flip postcondition invariant validation failed: {source}")]
InvariantValidation {
#[source]
source: Box<InvariantError>,
},
#[error("Flip postcondition realization validation failed: {source}")]
RealizationValidation {
#[source]
source: Box<TriangulationRealizationValidationError>,
},
#[error("Neighbor wiring failed: {reason}")]
NeighborWiring {
#[source]
reason: Box<FlipNeighborWiringError>,
},
#[error("TDS mutation failed: {reason}")]
TdsMutation {
#[source]
reason: Box<FlipMutationError>,
},
}
impl From<FlipContextError> for FlipError {
fn from(reason: FlipContextError) -> Self {
Self::InvalidFlipContext {
reason: Box::new(reason),
}
}
}
impl From<FlipPredicateError> for FlipError {
fn from(reason: FlipPredicateError) -> Self {
Self::PredicateFailure {
reason: Box::new(reason),
}
}
}
impl From<FlipEdgeAdjacencyError> for FlipError {
fn from(reason: FlipEdgeAdjacencyError) -> Self {
Self::InvalidEdgeAdjacency {
reason: Box::new(reason),
}
}
}
impl From<FlipTriangleAdjacencyError> for FlipError {
fn from(reason: FlipTriangleAdjacencyError) -> Self {
Self::InvalidTriangleAdjacency {
reason: Box::new(reason),
}
}
}
impl From<FlipVertexAdjacencyError> for FlipError {
fn from(reason: FlipVertexAdjacencyError) -> Self {
Self::InvalidVertexAdjacency {
reason: Box::new(reason),
}
}
}
impl From<SimplexValidationError> for FlipError {
fn from(source: SimplexValidationError) -> Self {
Self::SimplexCreation {
source: Box::new(source),
}
}
}
impl From<FacetError> for FlipError {
fn from(source: FacetError) -> Self {
Self::FacetIteration {
source: Box::new(source),
}
}
}
impl From<FlipNeighborWiringError> for FlipError {
fn from(reason: FlipNeighborWiringError) -> Self {
Self::NeighborWiring {
reason: Box::new(reason),
}
}
}
impl From<FlipMutationError> for FlipError {
fn from(reason: FlipMutationError) -> Self {
Self::TdsMutation {
reason: Box::new(reason),
}
}
}
impl From<&FlipError> for FlipFailureKind {
fn from(source: &FlipError) -> Self {
match source {
FlipError::WrongTopologyOwner { .. } => Self::WrongTopologyOwner,
FlipError::StaleTopologyProposal { .. } => Self::StaleTopologyProposal,
FlipError::UnsupportedDimension { .. } => Self::UnsupportedDimension,
FlipError::FlipTopologyNotAdmissible { .. } => Self::FlipTopologyNotAdmissible,
FlipError::BoundaryFacet { .. } => Self::BoundaryFacet,
FlipError::MissingSimplex { .. } => Self::MissingSimplex,
FlipError::DanglingVertexIncidence { .. } => Self::DanglingVertexIncidence,
FlipError::MissingVertex { .. } => Self::MissingVertex,
FlipError::MissingNeighbor { .. } => Self::MissingNeighbor,
FlipError::DanglingRidgeNeighbor { .. } => Self::DanglingRidgeNeighbor,
FlipError::InvalidFacetAdjacency { .. } => Self::InvalidFacetAdjacency,
FlipError::InvalidFacetIndex { .. } => Self::InvalidFacetIndex,
FlipError::InvalidRidgeIndex { .. } => Self::InvalidRidgeIndex,
FlipError::InvalidRidgeAdjacency { .. } => Self::InvalidRidgeAdjacency,
FlipError::InvalidRidgeMultiplicity { .. } => Self::InvalidRidgeMultiplicity,
FlipError::InvalidEdgeMultiplicity { .. } => Self::InvalidEdgeMultiplicity,
FlipError::InvalidTriangleMultiplicity { .. } => Self::InvalidTriangleMultiplicity,
FlipError::InvalidEdgeAdjacency { .. } => Self::InvalidEdgeAdjacency,
FlipError::InvalidTriangleAdjacency { .. } => Self::InvalidTriangleAdjacency,
FlipError::InvalidVertexMultiplicity { .. } => Self::InvalidVertexMultiplicity,
FlipError::InvalidVertexAdjacency { .. } => Self::InvalidVertexAdjacency,
FlipError::InvalidFlipContext { .. } => Self::InvalidFlipContext,
FlipError::PredicateFailure { .. } => Self::PredicateFailure,
FlipError::DegenerateSimplex => Self::DegenerateSimplex,
FlipError::K1InsertionOutsideSimplex { .. } => Self::K1InsertionOutsideSimplex,
FlipError::NegativeOrientation { .. } => Self::NegativeOrientation,
FlipError::DuplicateSimplex => Self::DuplicateSimplex,
FlipError::NonManifoldFacet => Self::NonManifoldFacet,
FlipError::InsertedSimplexAlreadyExists { .. } => Self::InsertedSimplexAlreadyExists,
FlipError::FacetIteration { .. } => Self::FacetIteration,
FlipError::SimplexCreation { source: _ } => Self::SimplexCreation,
FlipError::PostconditionRepair { .. } => Self::PostconditionRepair,
FlipError::InvariantValidation { .. } => Self::InvariantValidation,
FlipError::RealizationValidation { .. } => Self::RealizationValidation,
FlipError::NeighborWiring { reason } => match reason.as_ref() {
FlipNeighborWiringError::TopologyValidation { .. }
| FlipNeighborWiringError::DelaunayValidation { .. }
| FlipNeighborWiringError::RealizationValidation { .. }
| FlipNeighborWiringError::TopologyValidationFailed { .. } => {
Self::WiringValidation
}
FlipNeighborWiringError::DelaunayRepair { .. } => Self::DelaunayRepairFailed,
_ => Self::NeighborWiring,
},
FlipError::TdsMutation { reason }
if matches!(
reason.as_ref(),
FlipMutationError::TrialValidation { .. }
| FlipMutationError::CoherentOrientationViolation { .. }
) =>
{
Self::TrialValidation
}
FlipError::TdsMutation { .. } => Self::TdsMutation,
}
}
}
impl From<FlipError> for FlipFailureKind {
fn from(source: FlipError) -> Self {
Self::from(&source)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepairQueueOrder {
Fifo,
Lifo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DelaunayRepairDiagnostics {
pub facets_checked: usize,
pub flips_performed: usize,
pub max_queue_len: usize,
pub ambiguous_predicates: usize,
pub ambiguous_predicate_samples: Vec<u64>,
pub predicate_failures: usize,
pub cycle_detections: usize,
pub cycle_signature_samples: Vec<u64>,
pub attempt: usize,
pub queue_order: RepairQueueOrder,
}
impl fmt::Display for DelaunayRepairDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"checked {} facets, ambiguous={}, max_queue={}, flips={}, attempt={}, order={:?}, predicate_failures={}, cycles={}, cycle_samples={:?}",
self.facets_checked,
self.ambiguous_predicates,
self.max_queue_len,
self.flips_performed,
self.attempt,
self.queue_order,
self.predicate_failures,
self.cycle_detections,
self.cycle_signature_samples
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairVerificationContext {
PostRepairVerification,
StrictValidation,
LocalK2DegeneracyVerification,
LocalK2PostconditionVerification,
LocalK3DegeneracyVerification,
LocalK3PostconditionVerification,
LocalInverseK2PostconditionVerification,
LocalInverseK3PostconditionVerification,
}
impl fmt::Display for DelaunayRepairVerificationContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PostRepairVerification => f.write_str("post-repair verification"),
Self::StrictValidation => f.write_str("strict validation"),
Self::LocalK2DegeneracyVerification => f.write_str("local k=2 degeneracy verification"),
Self::LocalK2PostconditionVerification => {
f.write_str("local k=2 postcondition verification")
}
Self::LocalK3DegeneracyVerification => f.write_str("local k=3 degeneracy verification"),
Self::LocalK3PostconditionVerification => {
f.write_str("local k=3 postcondition verification")
}
Self::LocalInverseK2PostconditionVerification => {
f.write_str("local inverse k=2 postcondition verification")
}
Self::LocalInverseK3PostconditionVerification => {
f.write_str("local inverse k=3 postcondition verification")
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairPostconditionFailure {
Disconnected {
simplex_count: usize,
},
LocalK2Violation {
facet: FacetHandle,
debug_details: Option<String>,
},
LocalK3Violation {
ridge: RidgeHandle,
},
LocalInverseK2Violation {
edge: EdgeKey,
},
LocalInverseK3Violation {
triangle: TriangleHandle,
},
}
impl fmt::Display for DelaunayRepairPostconditionFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Disconnected { simplex_count } => write!(
f,
"repair pass disconnected the triangulation ({simplex_count} simplices remain); neighbor wiring is incomplete"
),
Self::LocalK2Violation {
facet,
debug_details,
} => {
write!(
f,
"local k=2 violation remains after repair (facet={facet:?})"
)?;
if let Some(details) = debug_details {
write!(f, "; {details}")?;
}
Ok(())
}
Self::LocalK3Violation { ridge } => {
write!(
f,
"local k=3 violation remains after repair (ridge={ridge:?})"
)
}
Self::LocalInverseK2Violation { edge } => {
write!(
f,
"local inverse k=2 flip remains applicable after repair (edge={edge:?})"
)
}
Self::LocalInverseK3Violation { triangle } => write!(
f,
"local inverse k=3 flip remains applicable after repair (triangle={triangle:?})"
),
}
}
}
impl std::error::Error for DelaunayRepairPostconditionFailure {}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum DelaunayRepairOrientationCanonicalizationFailure {
#[error("after flip repair: {source}")]
AfterFlipRepair {
#[source]
source: Box<InsertionError>,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairOrientationCanonicalizationFailureKind {
#[error("after flip repair: {source_kind:?}")]
AfterFlipRepair {
source_kind: InsertionErrorKind,
},
}
impl From<&DelaunayRepairOrientationCanonicalizationFailure>
for DelaunayRepairOrientationCanonicalizationFailureKind
{
fn from(source: &DelaunayRepairOrientationCanonicalizationFailure) -> Self {
match source {
DelaunayRepairOrientationCanonicalizationFailure::AfterFlipRepair { source } => {
Self::AfterFlipRepair {
source_kind: insertion_error_kind(source),
}
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct DelaunayRepairHeuristicVertexContext {
pub index: usize,
pub vertex_uuid: uuid::Uuid,
pub coordinates: CoordinateValues,
}
impl fmt::Display for DelaunayRepairHeuristicVertexContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"idx={} uuid={} coords={}",
self.index, self.vertex_uuid, self.coordinates
)
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum DelaunayRepairHeuristicRebuildFailure {
#[error("heuristic rebuild recursion depth exceeded {max_depth}")]
RecursionDepthExceeded {
max_depth: usize,
},
#[error("primary repair failed ({primary}); robust fallback failed ({robust}); {heuristic}")]
FallbackChainFailed {
#[source]
primary: Box<DelaunayRepairError>,
robust: Box<DelaunayRepairError>,
heuristic: Box<Self>,
},
#[error("heuristic rebuild failed with unexpected repair error: {source}")]
UnexpectedRepairFailure {
#[source]
source: Box<DelaunayRepairError>,
},
#[error("heuristic rebuild made no attempts")]
NoAttempts,
#[error("heuristic rebuild insertion failed at {vertex}: {source}")]
InsertionFailed {
vertex: DelaunayRepairHeuristicVertexContext,
#[source]
source: Box<InsertionError>,
},
#[error("heuristic rebuild repair failed at {vertex}: {source}")]
RepairFailed {
vertex: DelaunayRepairHeuristicVertexContext,
#[source]
source: Box<InsertionError>,
},
#[error("heuristic rebuild Delaunay check failed at {vertex}: {source}")]
DelaunayCheckFailed {
vertex: DelaunayRepairHeuristicVertexContext,
#[source]
source: Box<InsertionError>,
},
#[error("heuristic rebuild skipped vertex at {vertex}: {source}")]
SkippedVertex {
vertex: DelaunayRepairHeuristicVertexContext,
#[source]
source: Box<InsertionError>,
},
#[error(
"attempt {attempt}/{max_attempts} (shuffle_seed={shuffle_seed} perturbation_seed={perturbation_seed}): {source}"
)]
AttemptFailed {
attempt: usize,
max_attempts: usize,
shuffle_seed: u64,
perturbation_seed: u64,
#[source]
source: Box<DelaunayRepairError>,
},
#[error("heuristic rebuild failed after {attempts} attempts: {last_failure}")]
ExhaustedAttempts {
attempts: usize,
#[source]
last_failure: Box<Self>,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairHeuristicRebuildFailureKind {
#[error("recursion depth exceeded")]
RecursionDepthExceeded,
#[error("fallback chain failed")]
FallbackChainFailed,
#[error("unexpected repair failure")]
UnexpectedRepairFailure,
#[error("no attempts")]
NoAttempts,
#[error("insertion failed")]
InsertionFailed,
#[error("repair failed")]
RepairFailed,
#[error("Delaunay check failed")]
DelaunayCheckFailed,
#[error("skipped vertex")]
SkippedVertex,
#[error("attempt failed")]
AttemptFailed,
#[error("attempts exhausted")]
ExhaustedAttempts,
}
impl From<&DelaunayRepairHeuristicRebuildFailure> for DelaunayRepairHeuristicRebuildFailureKind {
fn from(source: &DelaunayRepairHeuristicRebuildFailure) -> Self {
match source {
DelaunayRepairHeuristicRebuildFailure::RecursionDepthExceeded { .. } => {
Self::RecursionDepthExceeded
}
DelaunayRepairHeuristicRebuildFailure::FallbackChainFailed { .. } => {
Self::FallbackChainFailed
}
DelaunayRepairHeuristicRebuildFailure::UnexpectedRepairFailure { .. } => {
Self::UnexpectedRepairFailure
}
DelaunayRepairHeuristicRebuildFailure::NoAttempts => Self::NoAttempts,
DelaunayRepairHeuristicRebuildFailure::InsertionFailed { .. } => Self::InsertionFailed,
DelaunayRepairHeuristicRebuildFailure::RepairFailed { .. } => Self::RepairFailed,
DelaunayRepairHeuristicRebuildFailure::DelaunayCheckFailed { .. } => {
Self::DelaunayCheckFailed
}
DelaunayRepairHeuristicRebuildFailure::SkippedVertex { .. } => Self::SkippedVertex,
DelaunayRepairHeuristicRebuildFailure::AttemptFailed { .. } => Self::AttemptFailed,
DelaunayRepairHeuristicRebuildFailure::ExhaustedAttempts { .. } => {
Self::ExhaustedAttempts
}
}
}
}
pub(super) const fn insertion_error_kind(source: &InsertionError) -> InsertionErrorKind {
match source {
InsertionError::PublishedOwnerBootstrapRequiresBuilder { .. } => {
InsertionErrorKind::PublishedOwnerBootstrapRequiresBuilder
}
InsertionError::ConflictRegion { source: _ } => InsertionErrorKind::ConflictRegion,
InsertionError::Location { source: _ } => InsertionErrorKind::Location,
InsertionError::CavityFilling { .. } => InsertionErrorKind::CavityFilling,
InsertionError::NeighborWiring { .. } => InsertionErrorKind::NeighborWiring,
InsertionError::NonManifoldTopology { .. } => InsertionErrorKind::NonManifoldTopology,
InsertionError::HullExtension { .. } => InsertionErrorKind::HullExtension,
InsertionError::DelaunayValidationFailed { .. } => {
InsertionErrorKind::DelaunayValidationFailed
}
InsertionError::RealizationValidationFailed { .. } => {
InsertionErrorKind::RealizationValidationFailed
}
InsertionError::DelaunayRepairFailed { .. } => InsertionErrorKind::DelaunayRepairFailed,
InsertionError::DuplicateCoordinates { .. } => InsertionErrorKind::DuplicateCoordinates,
InsertionError::DuplicateUuid { .. } => InsertionErrorKind::DuplicateUuid,
InsertionError::TopologyValidation { source: _ } => InsertionErrorKind::TopologyValidation,
InsertionError::TopologyValidationFailed { .. } => {
InsertionErrorKind::TopologyValidationFailed
}
InsertionError::MaxSimplicesRemovedExceeded { .. } => {
InsertionErrorKind::MaxSimplicesRemovedExceeded
}
InsertionError::SpatialIndexConstruction { .. } => {
InsertionErrorKind::SpatialIndexConstruction
}
InsertionError::PerturbedCoordinateInvalid { .. } => {
InsertionErrorKind::PerturbedCoordinateInvalid
}
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum DelaunayRepairError {
#[error("Delaunay repair failed to converge after {max_flips} flips ({diagnostics})")]
NonConvergent {
max_flips: usize,
diagnostics: Box<DelaunayRepairDiagnostics>,
},
#[error("Delaunay repair postcondition failed: {reason}")]
PostconditionFailed {
#[source]
reason: Box<DelaunayRepairPostconditionFailure>,
},
#[error("Delaunay repair verification failed during {context}: {source}")]
VerificationFailed {
context: DelaunayRepairVerificationContext,
#[source]
source: Box<FlipError>,
},
#[error("Delaunay repair orientation canonicalization failed: {reason}")]
OrientationCanonicalizationFailed {
#[source]
reason: Box<DelaunayRepairOrientationCanonicalizationFailure>,
},
#[error("Delaunay repair requires {required:?} topology, found {found:?}: {message}")]
InvalidTopology {
required: TopologyGuarantee,
found: TopologyGuarantee,
message: &'static str,
},
#[error("Heuristic rebuild failed: {reason}")]
HeuristicRebuildFailed {
#[source]
reason: Box<DelaunayRepairHeuristicRebuildFailure>,
},
#[error("flip error: {source}")]
Flip {
#[source]
source: Box<FlipError>,
},
}
impl From<FlipError> for DelaunayRepairError {
fn from(source: FlipError) -> Self {
Self::Flip {
source: Box::new(source),
}
}
}
impl From<FacetError> for DelaunayRepairError {
fn from(source: FacetError) -> Self {
Self::from(FlipError::from(source))
}
}
impl From<DelaunayRepairError> for FlipNeighborRepairFailure {
fn from(source: DelaunayRepairError) -> Self {
match source {
DelaunayRepairError::NonConvergent {
max_flips,
diagnostics,
} => Self::NonConvergent {
max_flips,
diagnostics: (*diagnostics).into(),
},
DelaunayRepairError::PostconditionFailed { reason } => {
Self::PostconditionFailed { reason: *reason }
}
DelaunayRepairError::VerificationFailed { context, source } => {
Self::VerificationFailed {
context,
source_kind: FlipFailureKind::from(source.as_ref()),
}
}
DelaunayRepairError::OrientationCanonicalizationFailed { reason } => {
Self::OrientationCanonicalizationFailed {
reason: reason.as_ref().into(),
}
}
DelaunayRepairError::InvalidTopology {
required,
found,
message,
} => Self::InvalidTopology {
required,
found,
message,
},
DelaunayRepairError::HeuristicRebuildFailed { reason } => {
Self::HeuristicRebuildFailed {
reason: reason.as_ref().into(),
}
}
DelaunayRepairError::Flip { source } => Self::Flip {
source_kind: FlipFailureKind::from(source.as_ref()),
},
}
}
}
#[cfg(test)]
mod tests {
use super::super::*;
use super::*;
use crate::core::algorithms::insertion::DelaunayRepairFailureContext;
use crate::core::algorithms::locate::LocateResult;
use crate::core::collections::{SimplexVertexKeyBuffer, SimplexVertexUuidBuffer, Uuid};
use crate::core::tds::TdsError;
use crate::geometry::traits::coordinate::CoordinateConversionValue;
use crate::repair::DelaunayRepairOperation;
use crate::topology::traits::topological_space::TopologyKind;
use crate::triangulation::realization::TriangulationRealizationSimplexDetail;
use crate::triangulation::validation::TopologyGuarantee;
use slotmap::KeyData;
use std::assert_matches;
use std::{
error::Error as _,
mem::{align_of, size_of},
};
fn sample_heuristic_vertex_context() -> DelaunayRepairHeuristicVertexContext {
DelaunayRepairHeuristicVertexContext {
index: 3,
vertex_uuid: Uuid::nil(),
coordinates: CoordinateValues::from([1.0, 2.0]),
}
}
#[test]
fn test_flip_error_partial_eq() {
let unsupported_1 = FlipError::UnsupportedDimension { dimension: 1 };
let unsupported_1_copy = FlipError::UnsupportedDimension { dimension: 1 };
let unsupported_2 = FlipError::UnsupportedDimension { dimension: 2 };
assert_eq!(unsupported_1, unsupported_1_copy);
assert_ne!(unsupported_1, unsupported_2);
assert_ne!(FlipError::DegenerateSimplex, FlipError::DuplicateSimplex);
assert_eq!(FlipError::NonManifoldFacet, FlipError::NonManifoldFacet);
let ridge_4 = FlipError::InvalidRidgeMultiplicity { found: 4 };
let ridge_4_copy = FlipError::InvalidRidgeMultiplicity { found: 4 };
let ridge_5 = FlipError::InvalidRidgeMultiplicity { found: 5 };
assert_eq!(ridge_4, ridge_4_copy);
assert_ne!(ridge_4, ridge_5);
}
fn sample_tds_validation_failure() -> TdsValidationFailure {
TdsValidationFailure::InconsistentDataStructure {
message: "synthetic neighbor mismatch".to_string(),
}
}
fn sample_repair_diagnostics() -> DelaunayRepairDiagnostics {
DelaunayRepairDiagnostics {
facets_checked: 7,
flips_performed: 3,
max_queue_len: 5,
ambiguous_predicates: 2,
ambiguous_predicate_samples: vec![11, 13],
predicate_failures: 1,
cycle_detections: 4,
cycle_signature_samples: vec![17, 19],
attempt: 2,
queue_order: RepairQueueOrder::Lifo,
}
}
#[test]
fn test_flip_failure_kind_preserves_nested_validation_and_repair_reasons() {
let trial_error = FlipError::from(FlipMutationError::TrialValidation {
k_move: 2,
direction: FlipDirection::Forward,
source: sample_tds_validation_failure(),
});
assert_eq!(
FlipFailureKind::from(&trial_error),
FlipFailureKind::TrialValidation
);
let wiring_validation = FlipError::from(FlipNeighborWiringError::TopologyValidation {
source: sample_tds_validation_failure(),
});
assert_eq!(
FlipFailureKind::from(&wiring_validation),
FlipFailureKind::WiringValidation
);
let repair_reason =
FlipNeighborRepairFailure::from(DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::PostRepairVerification,
source: Box::new(trial_error),
});
match &repair_reason {
FlipNeighborRepairFailure::VerificationFailed {
context,
source_kind,
} => {
assert_eq!(
*context,
DelaunayRepairVerificationContext::PostRepairVerification
);
assert_eq!(*source_kind, FlipFailureKind::TrialValidation);
}
other => panic!("expected verification failure, got {other:?}"),
}
let flip_reason =
FlipNeighborRepairFailure::from(DelaunayRepairError::from(FlipError::DuplicateSimplex));
assert_eq!(
flip_reason,
FlipNeighborRepairFailure::Flip {
source_kind: FlipFailureKind::DuplicateSimplex,
}
);
let wiring_repair = FlipError::from(FlipNeighborWiringError::DelaunayRepair {
reason: repair_reason,
});
assert_eq!(
FlipFailureKind::from(&wiring_repair),
FlipFailureKind::DelaunayRepairFailed
);
let dangling_ridge_neighbor = FlipError::DanglingRidgeNeighbor {
simplex_key: SimplexKey::from(KeyData::from_ffi(1)),
neighbor_key: SimplexKey::from(KeyData::from_ffi(2)),
};
assert_eq!(
FlipFailureKind::from(&dangling_ridge_neighbor),
FlipFailureKind::DanglingRidgeNeighbor
);
let dangling_vertex_incidence = FlipError::DanglingVertexIncidence {
vertex_key: VertexKey::from(KeyData::from_ffi(1)),
simplex_key: SimplexKey::from(KeyData::from_ffi(2)),
};
assert_eq!(
FlipFailureKind::from(&dangling_vertex_incidence),
FlipFailureKind::DanglingVertexIncidence
);
let simplex_creation = FlipError::from(SimplexValidationError::DuplicateVertices);
assert_eq!(
FlipFailureKind::from(&simplex_creation),
FlipFailureKind::SimplexCreation
);
}
fn assert_hull_extension_failure_kind(
source: &HullExtensionReason,
expected: FlipNeighborHullExtensionFailureKind,
expected_display: &str,
) {
let hull_kind = FlipNeighborHullExtensionFailureKind::from(source);
assert_eq!(hull_kind, expected);
assert_eq!(hull_kind.to_string(), expected_display);
}
#[test]
fn test_flip_neighbor_hull_extension_failure_kind_conversions() {
assert_hull_extension_failure_kind(
&HullExtensionReason::BoundaryEdgeSplitFacetCount {
expected: 2,
actual: 1,
},
FlipNeighborHullExtensionFailureKind::BoundaryEdgeSplitFacetCount,
"boundary edge split facet count",
);
assert_hull_extension_failure_kind(
&HullExtensionReason::MultipleBoundaryEdgeSplitFacets {
first: FacetHandle::from_validated(SimplexKey::default(), 0),
second: FacetHandle::from_validated(SimplexKey::default(), 1),
},
FlipNeighborHullExtensionFailureKind::MultipleBoundaryEdgeSplitFacets,
"multiple boundary edge split facets",
);
assert_hull_extension_failure_kind(
&HullExtensionReason::DisconnectedVisiblePatch {
boundary_ridges: 1,
ridge_fans: 0,
components: 2,
boundary_components: 2,
boundary_subface_nonmanifold: 0,
},
FlipNeighborHullExtensionFailureKind::DisconnectedVisiblePatch,
"disconnected visible patch",
);
assert_hull_extension_failure_kind(
&HullExtensionReason::PredicateFailed {
source: CoordinateConversionError::InvalidSimplexPointCount {
actual: 2,
expected: 3,
dimension: 2,
},
},
FlipNeighborHullExtensionFailureKind::PredicateFailed,
"predicate failed",
);
assert_hull_extension_failure_kind(
&HullExtensionReason::Tds {
source: TdsError::InconsistentDataStructure {
message: "missing boundary facet".to_string(),
},
},
FlipNeighborHullExtensionFailureKind::Tds,
"TDS",
);
}
#[expect(
clippy::too_many_lines,
reason = "test keeps the insertion suberror conversion matrix together"
)]
#[test]
fn test_flip_neighbor_conversion_kinds_cover_insertion_suberrors() {
let cavity_kind = FlipNeighborCavityFailureKind::from(
&CavityFillingError::BoundarySimplexCountMismatch {
boundary_facet_count: 3,
new_simplex_count: 2,
},
);
assert_eq!(
cavity_kind,
FlipNeighborCavityFailureKind::BoundarySimplexCountMismatch
);
assert_eq!(cavity_kind.to_string(), "boundary simplex count mismatch");
let cavity_kind = FlipNeighborCavityFailureKind::from(
&CavityFillingError::UnsupportedDegenerateLocation {
location: LocateResult::Outside,
},
);
assert_eq!(
cavity_kind,
FlipNeighborCavityFailureKind::UnsupportedDegenerateLocation
);
assert_eq!(cavity_kind.to_string(), "unsupported degenerate location");
let validation_kind = FlipNeighborDelaunayValidationFailureKind::from(
&DelaunayTriangulationValidationError::RepairOperationFailed {
operation: DelaunayRepairOperation::VertexRemoval,
source: Box::new(DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "repair requires PL topology",
}),
},
);
assert_eq!(
validation_kind,
FlipNeighborDelaunayValidationFailureKind::RepairOperationFailed
);
assert_eq!(validation_kind.to_string(), "repair operation failed");
let validation_cases = [
(
DelaunayTriangulationValidationError::from(TdsError::InconsistentDataStructure {
message: "dangling simplex".to_string(),
}),
FlipNeighborDelaunayValidationFailureKind::Tds,
),
(
DelaunayTriangulationValidationError::from(
TriangulationValidationError::Disconnected { simplex_count: 2 },
),
FlipNeighborDelaunayValidationFailureKind::Triangulation,
),
(
DelaunayTriangulationValidationError::Realization {
source: Box::new(
TriangulationRealizationValidationError::UnsupportedTopology {
topology: TopologyKind::Hyperbolic,
dimension: 2,
},
),
},
FlipNeighborDelaunayValidationFailureKind::Realization,
),
];
for (source, expected) in validation_cases {
assert_eq!(
FlipNeighborDelaunayValidationFailureKind::from(&source),
expected
);
}
let repair_wiring = FlipNeighborWiringError::from(InsertionError::DelaunayRepairFailed {
source: Box::new(DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "repair requires PL topology",
}),
context: DelaunayRepairFailureContext::PostInsertionRepair,
});
match repair_wiring {
FlipNeighborWiringError::DelaunayRepair {
reason:
FlipNeighborRepairFailure::InvalidTopology {
required,
found,
message,
},
} => {
assert_eq!(required, TopologyGuarantee::PLManifold);
assert_eq!(found, TopologyGuarantee::Pseudomanifold);
assert_eq!(message, "repair requires PL topology");
}
other => panic!("expected preserved Delaunay repair reason, got {other:?}"),
}
let budget_wiring =
FlipNeighborWiringError::from(InsertionError::MaxSimplicesRemovedExceeded {
max_simplices_removed: 2,
attempted: 3,
});
assert_eq!(
budget_wiring,
FlipNeighborWiringError::MaxSimplicesRemovedExceeded {
max_simplices_removed: 2,
attempted: 3,
}
);
let spatial_index_wiring =
FlipNeighborWiringError::from(InsertionError::SpatialIndexConstruction {
reason: SpatialIndexConstructionFailure::NonPositiveCellSize {
value: CoordinateConversionValue::from_f64(0.0),
},
});
assert_eq!(
spatial_index_wiring,
FlipNeighborWiringError::SpatialIndexConstruction {
reason: SpatialIndexConstructionFailure::NonPositiveCellSize {
value: CoordinateConversionValue::from_f64(0.0),
},
}
);
}
#[test]
fn flip_neighbor_wiring_classifies_and_preserves_boundary_sources() {
let bootstrap_error =
InsertionError::PublishedOwnerBootstrapRequiresBuilder { dimension: 3 };
assert_eq!(
insertion_error_kind(&bootstrap_error),
InsertionErrorKind::PublishedOwnerBootstrapRequiresBuilder
);
assert_eq!(
FlipNeighborWiringError::from(bootstrap_error),
FlipNeighborWiringError::PublishedOwnerBootstrapRequiresBuilder { dimension: 3 }
);
let topology_error = InsertionError::TopologyValidation {
source: TdsError::InconsistentDataStructure {
message: "broken topology".to_string(),
},
};
assert_eq!(
insertion_error_kind(&topology_error),
InsertionErrorKind::TopologyValidation
);
assert_eq!(
FlipNeighborWiringError::from(topology_error),
FlipNeighborWiringError::TopologyValidation {
source: TdsValidationFailure::InconsistentDataStructure {
message: "broken topology".to_string(),
},
}
);
let simplex_key = SimplexKey::from(KeyData::from_ffi(9_001));
let conflict_source = ConflictError::InvalidStartSimplex { simplex_key };
let conflict_error = InsertionError::ConflictRegion {
source: conflict_source.clone(),
};
assert_eq!(
insertion_error_kind(&conflict_error),
InsertionErrorKind::ConflictRegion
);
assert_eq!(
FlipNeighborWiringError::from(conflict_error),
FlipNeighborWiringError::ConflictRegion {
source: conflict_source,
}
);
let location_source = LocateError::InvalidSimplex { simplex_key };
let location_error = InsertionError::Location {
source: location_source.clone(),
};
assert_eq!(
insertion_error_kind(&location_error),
InsertionErrorKind::Location
);
assert_eq!(
FlipNeighborWiringError::from(location_error),
FlipNeighborWiringError::Location {
source: location_source,
}
);
}
#[test]
fn flip_neighbor_wiring_preserves_realization_validation_source() {
let simplex_key = SimplexKey::from(KeyData::from_ffi(9_101));
let simplex_uuid = Uuid::from_u128(0x9101);
let vertices: SimplexVertexKeyBuffer = [
VertexKey::from(KeyData::from_ffi(9_201)),
VertexKey::from(KeyData::from_ffi(9_202)),
VertexKey::from(KeyData::from_ffi(9_203)),
]
.into_iter()
.collect();
let vertex_uuids: SimplexVertexUuidBuffer = [
Uuid::from_u128(0x9201),
Uuid::from_u128(0x9202),
Uuid::from_u128(0x9203),
]
.into_iter()
.collect();
let realization_source = TriangulationRealizationValidationError::DegenerateSimplex {
simplex_key,
simplex_uuid,
detail: Box::new(TriangulationRealizationSimplexDetail {
key: simplex_key,
uuid: simplex_uuid,
vertices,
vertex_uuids,
}),
dimension: 2,
};
let realization_wiring =
FlipNeighborWiringError::from(InsertionError::RealizationValidationFailed {
source: realization_source.clone(),
});
let FlipNeighborWiringError::RealizationValidation { source } = &realization_wiring else {
panic!("expected preserved realization validation source, got {realization_wiring:?}");
};
assert_eq!(source, &realization_source);
let error_source = realization_wiring
.source()
.and_then(|source| source.downcast_ref::<TriangulationRealizationValidationError>())
.expect("realization validation should remain the typed error source");
assert_eq!(error_source, &realization_source);
assert_eq!(
FlipFailureKind::from(&FlipError::from(realization_wiring)),
FlipFailureKind::WiringValidation
);
let transaction_realization = FlipError::RealizationValidation {
source: Box::new(realization_source.clone()),
};
assert_eq!(
FlipFailureKind::from(&transaction_realization),
FlipFailureKind::RealizationValidation
);
let invariant_validation = FlipError::InvariantValidation {
source: Box::new(InvariantError::Realization {
source: realization_source.clone(),
}),
};
assert_eq!(
FlipFailureKind::from(&invariant_validation),
FlipFailureKind::InvariantValidation
);
let transaction_repair = FlipError::PostconditionRepair {
source: Box::new(InsertionError::RealizationValidationFailed {
source: realization_source,
}),
};
assert_eq!(
FlipFailureKind::from(&transaction_repair),
FlipFailureKind::PostconditionRepair
);
}
#[test]
fn test_flip_neighbor_repair_diagnostics_preserve_summary_fields() {
let diagnostics = sample_repair_diagnostics();
let summary = FlipNeighborRepairDiagnostics::from(diagnostics.clone());
assert_eq!(summary.facets_checked, diagnostics.facets_checked);
assert_eq!(summary.flips_performed, diagnostics.flips_performed);
assert_eq!(summary.max_queue_len, diagnostics.max_queue_len);
assert_eq!(
summary.ambiguous_predicates,
diagnostics.ambiguous_predicates
);
assert_eq!(summary.predicate_failures, diagnostics.predicate_failures);
assert_eq!(summary.cycle_detections, diagnostics.cycle_detections);
assert_eq!(summary.attempt, diagnostics.attempt);
assert_eq!(summary.queue_order, diagnostics.queue_order);
assert_eq!(
summary.to_string(),
"checked=7, flips=3, max_queue=5, ambiguous=2, predicate_failures=1, cycles=4, attempt=2, order=Lifo"
);
let non_convergent = FlipNeighborRepairFailure::from(DelaunayRepairError::NonConvergent {
max_flips: 42,
diagnostics: Box::new(diagnostics),
});
match non_convergent {
FlipNeighborRepairFailure::NonConvergent {
max_flips,
diagnostics,
} => {
assert_eq!(max_flips, 42);
assert_eq!(diagnostics.flips_performed, 3);
}
other => panic!("expected non-convergent repair summary, got {other:?}"),
}
}
#[test]
fn test_delaunay_repair_error_partial_eq() {
let post_test = DelaunayRepairError::PostconditionFailed {
reason: Box::new(DelaunayRepairPostconditionFailure::Disconnected { simplex_count: 1 }),
};
let post_test_copy = DelaunayRepairError::PostconditionFailed {
reason: Box::new(DelaunayRepairPostconditionFailure::Disconnected { simplex_count: 1 }),
};
let post_other = DelaunayRepairError::PostconditionFailed {
reason: Box::new(DelaunayRepairPostconditionFailure::Disconnected { simplex_count: 2 }),
};
assert_eq!(post_test, post_test_copy);
assert_ne!(post_test, post_other);
let verification_err = DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DegenerateSimplex),
};
let verification_err_copy = DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DegenerateSimplex),
};
let verification_other = DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DuplicateSimplex),
};
assert_eq!(verification_err, verification_err_copy);
assert_ne!(verification_err, verification_other);
let flip_err = DelaunayRepairError::from(FlipError::DegenerateSimplex);
let flip_err_copy = DelaunayRepairError::from(FlipError::DegenerateSimplex);
let flip_other = DelaunayRepairError::from(FlipError::DuplicateSimplex);
assert_eq!(flip_err, flip_err_copy);
assert_ne!(flip_err, flip_other);
let canonicalization_err = DelaunayRepairError::OrientationCanonicalizationFailed {
reason: Box::new(
DelaunayRepairOrientationCanonicalizationFailure::AfterFlipRepair {
source: Box::new(InsertionError::DuplicateCoordinates {
coordinates: CoordinateValues::from([0.0, 0.0]),
}),
},
),
};
let canonicalization_err_copy = DelaunayRepairError::OrientationCanonicalizationFailed {
reason: Box::new(
DelaunayRepairOrientationCanonicalizationFailure::AfterFlipRepair {
source: Box::new(InsertionError::DuplicateCoordinates {
coordinates: CoordinateValues::from([0.0, 0.0]),
}),
},
),
};
let canonicalization_other = DelaunayRepairError::OrientationCanonicalizationFailed {
reason: Box::new(
DelaunayRepairOrientationCanonicalizationFailure::AfterFlipRepair {
source: Box::new(InsertionError::DuplicateCoordinates {
coordinates: CoordinateValues::from([1.0, 1.0]),
}),
},
),
};
assert_eq!(canonicalization_err, canonicalization_err_copy);
assert_ne!(canonicalization_err, canonicalization_other);
let topo_err = DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "test",
};
let topo_err_copy = DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "test",
};
assert_eq!(topo_err, topo_err_copy);
assert_ne!(post_test, topo_err);
assert_ne!(post_test, verification_err);
assert_ne!(post_test, canonicalization_err);
}
#[test]
fn test_postcondition_failure_display_covers_variants() {
let simplex = SimplexKey::from(KeyData::from_ffi(91));
let v0 = VertexKey::from(KeyData::from_ffi(101));
let v1 = VertexKey::from(KeyData::from_ffi(102));
let v2 = VertexKey::from(KeyData::from_ffi(103));
let facet = FacetHandle::from_validated(simplex, 0);
let ridge = RidgeHandle::from_validated(simplex, 0, 1);
let edge = EdgeKey::from_validated_endpoints(v0, v1);
let triangle = TriangleHandle::try_new(v0, v1, v2).unwrap();
assert_eq!(
DelaunayRepairPostconditionFailure::Disconnected { simplex_count: 2 }.to_string(),
"repair pass disconnected the triangulation (2 simplices remain); neighbor wiring is incomplete"
);
let k2 = DelaunayRepairPostconditionFailure::LocalK2Violation {
facet,
debug_details: Some("debug facet snapshot".to_string()),
}
.to_string();
assert!(k2.contains("local k=2 violation remains after repair"));
assert!(k2.contains("debug facet snapshot"));
let k3 = DelaunayRepairPostconditionFailure::LocalK3Violation { ridge }.to_string();
assert!(k3.contains("local k=3 violation remains after repair"));
let inverse_k2 =
DelaunayRepairPostconditionFailure::LocalInverseK2Violation { edge }.to_string();
assert!(inverse_k2.contains("local inverse k=2 flip remains applicable after repair"));
let inverse_k3 =
DelaunayRepairPostconditionFailure::LocalInverseK3Violation { triangle }.to_string();
assert!(inverse_k3.contains("local inverse k=3 flip remains applicable after repair"));
}
#[test]
fn test_postcondition_failure_exposes_source() {
let reason = DelaunayRepairPostconditionFailure::Disconnected { simplex_count: 1 };
let repair = DelaunayRepairError::PostconditionFailed {
reason: Box::new(reason.clone()),
};
let source = repair
.source()
.and_then(|source| source.downcast_ref::<Box<DelaunayRepairPostconditionFailure>>());
assert_eq!(source.map(Box::as_ref), Some(&reason));
let neighbor_repair = FlipNeighborRepairFailure::PostconditionFailed { reason };
assert_matches!(
std::error::Error::source(&neighbor_repair)
.and_then(|source| source.downcast_ref::<DelaunayRepairPostconditionFailure>()),
Some(DelaunayRepairPostconditionFailure::Disconnected { simplex_count: 1 })
);
}
#[test]
fn test_heuristic_vertex_context_display() {
let context = sample_heuristic_vertex_context().to_string();
assert!(context.contains("idx=3"));
assert!(context.contains("uuid=00000000-0000-0000-0000-000000000000"));
assert!(context.contains("coords=[1.0, 2.0]"));
}
#[test]
fn test_orientation_failure_kind_conversion() {
let orientation_failure =
DelaunayRepairOrientationCanonicalizationFailure::AfterFlipRepair {
source: Box::new(InsertionError::DuplicateCoordinates {
coordinates: CoordinateValues::from([0.0, 0.0]),
}),
};
assert_eq!(
DelaunayRepairOrientationCanonicalizationFailureKind::from(&orientation_failure),
DelaunayRepairOrientationCanonicalizationFailureKind::AfterFlipRepair {
source_kind: InsertionErrorKind::DuplicateCoordinates,
},
);
let orientation_repair = DelaunayRepairError::OrientationCanonicalizationFailed {
reason: Box::new(orientation_failure),
};
assert_matches!(
FlipNeighborRepairFailure::from(orientation_repair),
FlipNeighborRepairFailure::OrientationCanonicalizationFailed {
reason: DelaunayRepairOrientationCanonicalizationFailureKind::AfterFlipRepair {
source_kind: InsertionErrorKind::DuplicateCoordinates
}
}
);
}
#[test]
fn test_heuristic_rebuild_failure_kind_conversion() {
let insertion_failure = InsertionError::DuplicateCoordinates {
coordinates: CoordinateValues::from([0.0, 0.0]),
};
let repair_source = || DelaunayRepairError::from(FlipError::DegenerateSimplex);
let vertex = sample_heuristic_vertex_context();
let heuristic_cases = [
(
DelaunayRepairHeuristicRebuildFailure::RecursionDepthExceeded { max_depth: 1 },
DelaunayRepairHeuristicRebuildFailureKind::RecursionDepthExceeded,
),
(
DelaunayRepairHeuristicRebuildFailure::FallbackChainFailed {
primary: Box::new(repair_source()),
robust: Box::new(repair_source()),
heuristic: Box::new(DelaunayRepairHeuristicRebuildFailure::NoAttempts),
},
DelaunayRepairHeuristicRebuildFailureKind::FallbackChainFailed,
),
(
DelaunayRepairHeuristicRebuildFailure::UnexpectedRepairFailure {
source: Box::new(repair_source()),
},
DelaunayRepairHeuristicRebuildFailureKind::UnexpectedRepairFailure,
),
(
DelaunayRepairHeuristicRebuildFailure::NoAttempts,
DelaunayRepairHeuristicRebuildFailureKind::NoAttempts,
),
(
DelaunayRepairHeuristicRebuildFailure::InsertionFailed {
vertex: vertex.clone(),
source: Box::new(insertion_failure.clone()),
},
DelaunayRepairHeuristicRebuildFailureKind::InsertionFailed,
),
(
DelaunayRepairHeuristicRebuildFailure::RepairFailed {
vertex: vertex.clone(),
source: Box::new(insertion_failure.clone()),
},
DelaunayRepairHeuristicRebuildFailureKind::RepairFailed,
),
(
DelaunayRepairHeuristicRebuildFailure::DelaunayCheckFailed {
vertex: vertex.clone(),
source: Box::new(insertion_failure.clone()),
},
DelaunayRepairHeuristicRebuildFailureKind::DelaunayCheckFailed,
),
(
DelaunayRepairHeuristicRebuildFailure::SkippedVertex {
vertex,
source: Box::new(insertion_failure),
},
DelaunayRepairHeuristicRebuildFailureKind::SkippedVertex,
),
(
DelaunayRepairHeuristicRebuildFailure::AttemptFailed {
attempt: 1,
max_attempts: 2,
shuffle_seed: 3,
perturbation_seed: 4,
source: Box::new(repair_source()),
},
DelaunayRepairHeuristicRebuildFailureKind::AttemptFailed,
),
(
DelaunayRepairHeuristicRebuildFailure::ExhaustedAttempts {
attempts: 2,
last_failure: Box::new(DelaunayRepairHeuristicRebuildFailure::NoAttempts),
},
DelaunayRepairHeuristicRebuildFailureKind::ExhaustedAttempts,
),
];
for (failure, expected_kind) in heuristic_cases {
assert_eq!(
DelaunayRepairHeuristicRebuildFailureKind::from(&failure),
expected_kind,
);
}
}
#[test]
fn test_flip_neighbor_repair_failure_conversion() {
let heuristic_repair = DelaunayRepairError::HeuristicRebuildFailed {
reason: Box::new(DelaunayRepairHeuristicRebuildFailure::NoAttempts),
};
assert_matches!(
FlipNeighborRepairFailure::from(heuristic_repair),
FlipNeighborRepairFailure::HeuristicRebuildFailed {
reason: DelaunayRepairHeuristicRebuildFailureKind::NoAttempts
}
);
}
#[test]
fn test_delaunay_repair_error_boxes_large_flip_sources() {
assert!(
std::mem::size_of::<DelaunayRepairError>() <= std::mem::size_of::<FlipError>(),
"DelaunayRepairError should box FlipError payloads without exceeding FlipError size"
);
let err = DelaunayRepairError::from(FlipError::DegenerateSimplex);
let source = err.source().expect("boxed flip source should be exposed");
let source = source
.downcast_ref::<Box<FlipError>>()
.expect("source should remain a typed boxed FlipError");
assert_matches!(source.as_ref(), FlipError::DegenerateSimplex);
let DelaunayRepairError::Flip { source } = err else {
panic!("expected boxed flip source");
};
assert_matches!(source.as_ref(), FlipError::DegenerateSimplex);
}
#[test]
fn test_heuristic_exhausted_attempts_exposes_last_failure_source() {
let exhausted = DelaunayRepairHeuristicRebuildFailure::ExhaustedAttempts {
attempts: 6,
last_failure: Box::new(DelaunayRepairHeuristicRebuildFailure::NoAttempts),
};
let source = exhausted
.source()
.expect("exhausted attempts should expose the last failure source")
.downcast_ref::<Box<DelaunayRepairHeuristicRebuildFailure>>()
.expect("source should remain a typed boxed heuristic failure");
assert_matches!(
source.as_ref(),
DelaunayRepairHeuristicRebuildFailure::NoAttempts
);
}
#[test]
fn test_flip_error_boxes_nested_typed_payloads() {
let max_nested_payload_size = [
size_of::<FlipContextError>(),
size_of::<FlipPredicateError>(),
size_of::<FlipEdgeAdjacencyError>(),
size_of::<FlipTriangleAdjacencyError>(),
size_of::<FlipVertexAdjacencyError>(),
size_of::<SimplexValidationError>(),
size_of::<FlipNeighborWiringError>(),
size_of::<FlipMutationError>(),
size_of::<SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>>(),
]
.into_iter()
.max()
.unwrap_or(0);
assert!(
size_of::<FlipError>() < max_nested_payload_size,
"boxed FlipError should stay smaller than its largest nested payload"
);
assert_eq!(align_of::<Result<(), FlipError>>(), align_of::<FlipError>());
assert!(
size_of::<Result<(), FlipError>>() <= size_of::<FlipError>() + size_of::<usize>(),
"Result<(), FlipError> should remain within one machine word of FlipError"
);
let mutation = FlipError::from(FlipMutationError::TrialValidation {
k_move: 2,
direction: FlipDirection::Forward,
source: sample_tds_validation_failure(),
});
let source = mutation
.source()
.expect("boxed mutation source should be exposed")
.downcast_ref::<Box<FlipMutationError>>()
.expect("source should remain a typed boxed FlipMutationError");
assert_matches!(
source.as_ref(),
FlipMutationError::TrialValidation {
k_move: 2,
direction: FlipDirection::Forward,
..
}
);
let FlipError::TdsMutation { reason } = mutation else {
panic!("expected boxed TDS mutation reason");
};
assert_matches!(
reason.as_ref(),
FlipMutationError::TrialValidation {
k_move: 2,
direction: FlipDirection::Forward,
..
}
);
let mut simplex_vertices = SmallBuffer::new();
simplex_vertices.push(VertexKey::from(KeyData::from_ffi(1)));
simplex_vertices.push(VertexKey::from(KeyData::from_ffi(2)));
let duplicate = FlipError::InsertedSimplexAlreadyExists {
k_move: 2,
simplex_vertices: Box::new(simplex_vertices),
existing_simplex: SimplexKey::from(KeyData::from_ffi(3)),
};
let FlipError::InsertedSimplexAlreadyExists {
simplex_vertices, ..
} = duplicate
else {
panic!("expected boxed simplex witness");
};
assert_eq!(simplex_vertices.as_ref().len(), 2);
}
#[test]
fn test_flip_error_boxes_adjacency_payload_sources() {
let edge_vertex = VertexKey::from(KeyData::from_ffi(4));
let edge = FlipError::from(FlipEdgeAdjacencyError::DuplicateEndpoints {
vertex_key: edge_vertex,
});
let source = edge
.source()
.expect("boxed edge-adjacency source should be exposed")
.downcast_ref::<Box<FlipEdgeAdjacencyError>>()
.expect("source should remain a typed boxed FlipEdgeAdjacencyError");
assert_matches!(
source.as_ref(),
FlipEdgeAdjacencyError::DuplicateEndpoints { vertex_key }
if *vertex_key == edge_vertex
);
let FlipError::InvalidEdgeAdjacency { reason } = edge else {
panic!("expected boxed edge-adjacency reason");
};
assert_matches!(
reason.as_ref(),
FlipEdgeAdjacencyError::DuplicateEndpoints { vertex_key }
if *vertex_key == edge_vertex
);
let simplex_key = SimplexKey::from(KeyData::from_ffi(5));
let triangle_a = VertexKey::from(KeyData::from_ffi(6));
let triangle_b = VertexKey::from(KeyData::from_ffi(7));
let triangle_c = VertexKey::from(KeyData::from_ffi(8));
let triangle =
FlipError::from(FlipTriangleAdjacencyError::SimplexMissingTriangleVertices {
simplex_key,
a: triangle_a,
b: triangle_b,
c: triangle_c,
});
let source = triangle
.source()
.expect("boxed triangle-adjacency source should be exposed")
.downcast_ref::<Box<FlipTriangleAdjacencyError>>()
.expect("source should remain a typed boxed FlipTriangleAdjacencyError");
assert_matches!(
source.as_ref(),
FlipTriangleAdjacencyError::SimplexMissingTriangleVertices { a, b, c, .. }
if *a == triangle_a && *b == triangle_b && *c == triangle_c
);
let FlipError::InvalidTriangleAdjacency { reason } = triangle else {
panic!("expected boxed triangle-adjacency reason");
};
assert_matches!(
reason.as_ref(),
FlipTriangleAdjacencyError::SimplexMissingTriangleVertices { a, b, c, .. }
if *a == triangle_a && *b == triangle_b && *c == triangle_c
);
let vertex = FlipError::from(FlipVertexAdjacencyError::SimplexMissingVertex {
simplex_key,
vertex_key: edge_vertex,
});
let source = vertex
.source()
.expect("boxed vertex-adjacency source should be exposed")
.downcast_ref::<Box<FlipVertexAdjacencyError>>()
.expect("source should remain a typed boxed FlipVertexAdjacencyError");
assert_matches!(
source.as_ref(),
FlipVertexAdjacencyError::SimplexMissingVertex { vertex_key, .. }
if *vertex_key == edge_vertex
);
let FlipError::InvalidVertexAdjacency { reason } = vertex else {
panic!("expected boxed vertex-adjacency reason");
};
assert_matches!(
reason.as_ref(),
FlipVertexAdjacencyError::SimplexMissingVertex { vertex_key, .. }
if *vertex_key == edge_vertex
);
}
#[test]
fn test_delaunay_repair_verification_context_display_covers_all_variants() {
let cases = [
(
DelaunayRepairVerificationContext::PostRepairVerification,
"post-repair verification",
),
(
DelaunayRepairVerificationContext::StrictValidation,
"strict validation",
),
(
DelaunayRepairVerificationContext::LocalK2DegeneracyVerification,
"local k=2 degeneracy verification",
),
(
DelaunayRepairVerificationContext::LocalK2PostconditionVerification,
"local k=2 postcondition verification",
),
(
DelaunayRepairVerificationContext::LocalK3DegeneracyVerification,
"local k=3 degeneracy verification",
),
(
DelaunayRepairVerificationContext::LocalK3PostconditionVerification,
"local k=3 postcondition verification",
),
(
DelaunayRepairVerificationContext::LocalInverseK2PostconditionVerification,
"local inverse k=2 postcondition verification",
),
(
DelaunayRepairVerificationContext::LocalInverseK3PostconditionVerification,
"local inverse k=3 postcondition verification",
),
];
for (context, expected_display) in cases {
assert_eq!(context.to_string(), expected_display);
let err = DelaunayRepairError::VerificationFailed {
context,
source: Box::new(FlipError::DegenerateSimplex),
};
match err {
DelaunayRepairError::VerificationFailed {
context: observed, ..
} => assert_eq!(observed, context),
other => panic!("expected verification failure, got {other:?}"),
}
}
}
}