#![forbid(unsafe_code)]
use crate::core::algorithms::flips::DelaunayRepairError;
use crate::core::algorithms::locate::{
ConflictError, LocateError, LocateResult, extract_cavity_boundary,
};
use crate::core::cell::{Cell, CellValidationError};
use crate::core::collections::{
CellKeyBuffer, FastHashMap, FastHashSet, FastHasher, MAX_PRACTICAL_DIMENSION_SIZE, SmallBuffer,
VertexKeyBuffer,
};
use crate::core::facet::{FacetError, FacetHandle};
use crate::core::tds::{
CellKey, DelaunayValidationErrorKind, EntityKind, GeometricError, NeighborValidationError, Tds,
TdsConstructionError, TdsError, TdsErrorKind, TriangulationValidationErrorKind, VertexKey,
};
use crate::core::traits::boundary_analysis::BoundaryAnalysis;
use crate::core::traits::data_type::DataType;
use crate::core::triangulation::TriangulationConstructionError;
use crate::core::triangulation::TriangulationValidationError;
use crate::core::vertex::VertexValidationError;
use crate::geometry::kernel::Kernel;
use crate::geometry::point::Point;
use crate::geometry::predicates::Orientation;
use crate::geometry::robust_predicates::robust_orientation;
use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar};
use crate::triangulation::delaunay::DelaunayTriangulationValidationError;
use std::fmt;
use std::hash::{Hash, Hasher};
pub use crate::core::operations::{InsertionOutcome, InsertionResult, InsertionStatistics};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum HullExtensionReason {
NoVisibleFacets,
InvalidPatch {
details: String,
},
PredicateFailed(CoordinateConversionError),
Tds(TdsError),
Other {
message: String,
},
}
impl fmt::Display for HullExtensionReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoVisibleFacets => f.write_str(
"No visible boundary facets found for exterior vertex (may be coplanar with hull surface)",
),
Self::InvalidPatch { details } => write!(
f,
"Visible boundary facets are not a valid patch: {details}"
),
Self::PredicateFailed(source) => {
write!(f, "Geometric predicate failed: {source}")
}
Self::Tds(source) => write!(f, "TDS error: {source}"),
Self::Other { message } => f.write_str(message),
}
}
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TdsValidationFailure {
#[error("invalid vertex {vertex_id}: {source}")]
InvalidVertex {
vertex_id: uuid::Uuid,
#[source]
source: VertexValidationError,
},
#[error("invalid cell {cell_id}: {source}")]
InvalidCell {
cell_id: uuid::Uuid,
#[source]
source: CellValidationError,
},
#[error("invalid neighbor relationships: {reason}")]
InvalidNeighbors {
#[source]
reason: NeighborValidationError,
},
#[error(
"orientation invariant violated between cells {cell1_uuid} and {cell2_uuid} \
(facet indices {cell1_facet_index}/{cell2_facet_index}, vertex counts \
{facet_vertex_count}/{cell2_facet_vertex_count}, observed odd permutation \
{observed_odd_permutation}, expected {expected_odd_permutation})"
)]
OrientationViolation {
cell1_key: CellKey,
cell1_uuid: uuid::Uuid,
cell2_key: CellKey,
cell2_uuid: uuid::Uuid,
cell1_facet_index: usize,
cell2_facet_index: usize,
facet_vertex_count: usize,
cell2_facet_vertex_count: usize,
observed_odd_permutation: bool,
expected_odd_permutation: bool,
},
#[error("duplicate cells detected: {message}")]
DuplicateCells {
message: String,
},
#[error(
"facet {facet_key} exceeds incident-cell limit: observed {attempted_incident_count} incident cells, max {max_incident_count}; candidate/offending cell {candidate_cell_uuid} facet {candidate_facet_index}; other incident cells {existing_incident_count}"
)]
FacetSharingViolation {
facet_key: u64,
existing_incident_count: usize,
attempted_incident_count: usize,
max_incident_count: usize,
candidate_cell_uuid: uuid::Uuid,
candidate_facet_index: usize,
},
#[error("failed to create cell: {message}")]
FailedToCreateCell {
message: String,
},
#[error("cells {cell1} and {cell2} are not neighbors")]
NotNeighbors {
cell1: uuid::Uuid,
cell2: uuid::Uuid,
},
#[error("{entity:?} mapping inconsistency: {message}")]
MappingInconsistency {
entity: EntityKind,
message: String,
},
#[error("failed to retrieve vertex keys for cell {cell_id}: {message}")]
VertexKeyRetrievalFailed {
cell_id: uuid::Uuid,
message: String,
},
#[error("cell key {cell_key:?} not found: {context}")]
CellNotFound {
cell_key: CellKey,
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("internal data structure inconsistency: {message}")]
InconsistentDataStructure {
message: String,
},
#[error("geometric validation failed: {source}")]
Geometric {
#[source]
source: GeometricError,
},
#[error("facet validation failed: {source}")]
Facet {
#[source]
source: FacetError,
},
#[error("duplicate coordinates in cell {cell_id}: {message}")]
DuplicateCoordinatesInCell {
cell_id: uuid::Uuid,
message: String,
},
}
impl From<TdsError> for TdsValidationFailure {
fn from(source: TdsError) -> Self {
match source {
TdsError::InvalidVertex { vertex_id, source } => {
Self::InvalidVertex { vertex_id, source }
}
TdsError::InvalidCell { cell_id, source } => Self::InvalidCell { cell_id, source },
TdsError::InvalidNeighbors { reason } => Self::InvalidNeighbors { reason },
TdsError::OrientationViolation {
cell1_key,
cell1_uuid,
cell2_key,
cell2_uuid,
cell1_facet_index,
cell2_facet_index,
facet_vertices,
cell2_facet_vertices,
observed_odd_permutation,
expected_odd_permutation,
} => Self::OrientationViolation {
cell1_key,
cell1_uuid,
cell2_key,
cell2_uuid,
cell1_facet_index,
cell2_facet_index,
facet_vertex_count: facet_vertices.len(),
cell2_facet_vertex_count: cell2_facet_vertices.len(),
observed_odd_permutation,
expected_odd_permutation,
},
TdsError::DuplicateCells { message } => Self::DuplicateCells { message },
TdsError::FacetSharingViolation {
facet_key,
existing_incident_count,
attempted_incident_count,
max_incident_count,
candidate_cell_uuid,
candidate_facet_index,
} => Self::FacetSharingViolation {
facet_key,
existing_incident_count,
attempted_incident_count,
max_incident_count,
candidate_cell_uuid,
candidate_facet_index,
},
TdsError::FailedToCreateCell { message } => Self::FailedToCreateCell { message },
TdsError::NotNeighbors { cell1, cell2 } => Self::NotNeighbors { cell1, cell2 },
TdsError::MappingInconsistency { entity, message } => {
Self::MappingInconsistency { entity, message }
}
TdsError::VertexKeyRetrievalFailed { cell_id, message } => {
Self::VertexKeyRetrievalFailed { cell_id, message }
}
TdsError::CellNotFound { cell_key, context } => {
Self::CellNotFound { cell_key, context }
}
TdsError::VertexNotFound {
vertex_key,
context,
} => Self::VertexNotFound {
vertex_key,
context,
},
TdsError::DimensionMismatch {
expected,
actual,
context,
} => Self::DimensionMismatch {
expected,
actual,
context,
},
TdsError::IndexOutOfBounds {
index,
bound,
context,
} => Self::IndexOutOfBounds {
index,
bound,
context,
},
TdsError::InconsistentDataStructure { message } => {
Self::InconsistentDataStructure { message }
}
TdsError::Geometric(source) => Self::Geometric { source },
TdsError::FacetError(source) => Self::Facet { source },
TdsError::DuplicateCoordinatesInCell { cell_id, message } => {
Self::DuplicateCoordinatesInCell { cell_id, message }
}
}
}
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TdsConstructionFailure {
#[error("TDS validation failed during construction: {reason}")]
Validation {
#[source]
reason: TdsValidationFailure,
},
#[error("duplicate UUID during construction: {entity:?} {uuid}")]
DuplicateUuid {
entity: EntityKind,
uuid: uuid::Uuid,
},
}
impl From<TdsConstructionError> for TdsConstructionFailure {
fn from(source: TdsConstructionError) -> Self {
match source {
TdsConstructionError::ValidationError(source) => Self::Validation {
reason: source.into(),
},
TdsConstructionError::DuplicateUuid { entity, uuid } => {
Self::DuplicateUuid { entity, uuid }
}
}
}
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum InitialSimplexConstructionError {
#[error("TDS validation failed while building initial simplex: {source}")]
TdsValidation {
#[source]
source: TdsValidationFailure,
},
#[error("duplicate UUID while building initial simplex: {entity:?} {uuid}")]
DuplicateUuid {
entity: EntityKind,
uuid: uuid::Uuid,
},
#[error("failed to create bootstrap cell: {message}")]
FailedToCreateCell {
message: String,
},
#[error("insufficient vertices for {dimension}D initial simplex: {source}")]
InsufficientVertices {
dimension: usize,
#[source]
source: CellValidationError,
},
#[error("geometric degeneracy while building initial simplex: {message}")]
GeometricDegeneracy {
message: String,
},
#[error("internal inconsistency while building initial simplex: {message}")]
InternalInconsistency {
message: String,
},
#[error("duplicate coordinates while building initial simplex: {coordinates}")]
DuplicateCoordinates {
coordinates: String,
},
#[error(
"unexpected insertion-stage construction error while building initial simplex: {message}"
)]
UnexpectedInsertionStage {
message: String,
},
}
impl From<TdsConstructionError> for InitialSimplexConstructionError {
fn from(source: TdsConstructionError) -> Self {
match source {
TdsConstructionError::ValidationError(source) => Self::TdsValidation {
source: source.into(),
},
TdsConstructionError::DuplicateUuid { entity, uuid } => {
Self::DuplicateUuid { entity, uuid }
}
}
}
}
impl From<TriangulationConstructionError> for InitialSimplexConstructionError {
fn from(source: TriangulationConstructionError) -> Self {
match source {
TriangulationConstructionError::Tds(source) => source.into(),
TriangulationConstructionError::FailedToCreateCell { message } => {
Self::FailedToCreateCell { message }
}
TriangulationConstructionError::InsufficientVertices { dimension, source } => {
Self::InsufficientVertices { dimension, source }
}
TriangulationConstructionError::GeometricDegeneracy { message } => {
Self::GeometricDegeneracy { message }
}
TriangulationConstructionError::InternalInconsistency { message } => {
Self::InternalInconsistency { message }
}
TriangulationConstructionError::DuplicateCoordinates { coordinates } => {
Self::DuplicateCoordinates { coordinates }
}
TriangulationConstructionError::InsertionConflictRegion { source } => {
Self::UnexpectedInsertionStage {
message: source.to_string(),
}
}
TriangulationConstructionError::InsertionLocation { source } => {
Self::UnexpectedInsertionStage {
message: source.to_string(),
}
}
TriangulationConstructionError::InsertionNonManifoldTopology {
facet_hash,
cell_count,
} => Self::UnexpectedInsertionStage {
message: format!(
"facet {facet_hash:#x} shared by {cell_count} cells during initial simplex"
),
},
TriangulationConstructionError::InsertionHullExtension { reason } => {
Self::UnexpectedInsertionStage {
message: reason.to_string(),
}
}
TriangulationConstructionError::InsertionDelaunayValidation { source } => {
Self::UnexpectedInsertionStage {
message: source.to_string(),
}
}
TriangulationConstructionError::InsertionTopologyValidation { source, .. } => {
Self::UnexpectedInsertionStage {
message: source.to_string(),
}
}
TriangulationConstructionError::FinalTopologyValidation { source, .. } => {
Self::UnexpectedInsertionStage {
message: source.to_string(),
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairErrorKind {
NonConvergent,
PostconditionFailed,
VerificationFailed,
OrientationCanonicalizationFailed,
InvalidTopology,
HeuristicRebuildFailed,
Flip,
}
impl From<&DelaunayRepairError> for DelaunayRepairErrorKind {
fn from(source: &DelaunayRepairError) -> Self {
match source {
DelaunayRepairError::NonConvergent { .. } => Self::NonConvergent,
DelaunayRepairError::PostconditionFailed { .. } => Self::PostconditionFailed,
DelaunayRepairError::VerificationFailed { .. } => Self::VerificationFailed,
DelaunayRepairError::OrientationCanonicalizationFailed { .. } => {
Self::OrientationCanonicalizationFailed
}
DelaunayRepairError::InvalidTopology { .. } => Self::InvalidTopology,
DelaunayRepairError::HeuristicRebuildFailed { .. } => Self::HeuristicRebuildFailed,
DelaunayRepairError::Flip(_) => Self::Flip,
}
}
}
#[must_use]
#[derive(Debug, Clone, thiserror::Error)]
#[error("{message}")]
pub struct DelaunayRepairErrorSummary {
pub kind: DelaunayRepairErrorKind,
pub message: String,
}
impl PartialEq for DelaunayRepairErrorSummary {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
impl Eq for DelaunayRepairErrorSummary {}
impl From<&DelaunayRepairError> for DelaunayRepairErrorSummary {
fn from(source: &DelaunayRepairError) -> Self {
Self {
kind: source.into(),
message: source.to_string(),
}
}
}
impl From<DelaunayRepairError> for DelaunayRepairErrorSummary {
fn from(source: DelaunayRepairError) -> Self {
Self::from(&source)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InsertionErrorKind {
ConflictRegion,
Location,
CavityFilling,
NeighborWiring,
NonManifoldTopology,
HullExtension,
DelaunayValidationFailed,
DelaunayRepairFailed,
DuplicateCoordinates,
DuplicateUuid,
TopologyValidation,
TopologyValidationFailed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum InsertionErrorSourceKind {
Tds(TdsErrorKind),
Triangulation(TriangulationValidationErrorKind),
Delaunay(DelaunayValidationErrorKind),
DelaunayRepair(DelaunayRepairErrorKind),
}
#[must_use]
#[derive(Debug, Clone, thiserror::Error)]
#[error("{message}")]
pub struct InsertionErrorSummary {
pub kind: InsertionErrorKind,
pub source_kind: Option<InsertionErrorSourceKind>,
pub retryable: bool,
pub message: String,
}
impl PartialEq for InsertionErrorSummary {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
&& self.source_kind == other.source_kind
&& self.retryable == other.retryable
}
}
impl Eq for InsertionErrorSummary {}
impl InsertionErrorSummary {
#[must_use]
pub const fn is_retryable(&self) -> bool {
self.retryable
}
}
impl From<InsertionError> for InsertionErrorSummary {
fn from(source: InsertionError) -> Self {
let retryable = source.is_retryable();
let kind = match &source {
InsertionError::ConflictRegion(_) => InsertionErrorKind::ConflictRegion,
InsertionError::Location(_) => InsertionErrorKind::Location,
InsertionError::CavityFilling { .. } => InsertionErrorKind::CavityFilling,
InsertionError::NeighborWiring { .. } => InsertionErrorKind::NeighborWiring,
InsertionError::NonManifoldTopology { .. } => InsertionErrorKind::NonManifoldTopology,
InsertionError::HullExtension { .. } => InsertionErrorKind::HullExtension,
InsertionError::DelaunayValidationFailed { .. } => {
InsertionErrorKind::DelaunayValidationFailed
}
InsertionError::DelaunayRepairFailed { .. } => InsertionErrorKind::DelaunayRepairFailed,
InsertionError::DuplicateCoordinates { .. } => InsertionErrorKind::DuplicateCoordinates,
InsertionError::DuplicateUuid { .. } => InsertionErrorKind::DuplicateUuid,
InsertionError::TopologyValidation(_) => InsertionErrorKind::TopologyValidation,
InsertionError::TopologyValidationFailed { .. } => {
InsertionErrorKind::TopologyValidationFailed
}
};
let source_kind = match &source {
InsertionError::DelaunayValidationFailed { source } => {
Some(InsertionErrorSourceKind::Delaunay(source.into()))
}
InsertionError::DelaunayRepairFailed { source, .. } => Some(
InsertionErrorSourceKind::DelaunayRepair(source.as_ref().into()),
),
InsertionError::TopologyValidation(source) => {
Some(InsertionErrorSourceKind::Tds(source.into()))
}
InsertionError::TopologyValidationFailed { source, .. } => {
Some(InsertionErrorSourceKind::Triangulation(source.into()))
}
_ => None,
};
Self {
kind,
source_kind,
retryable,
message: source.to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairFailureContext {
LocalRepairRobustFallback {
initial: DelaunayRepairErrorSummary,
},
LocalRepairNonRecoverable,
OrientationCanonicalization,
LocalRepair,
PostInsertionRepair,
}
impl fmt::Display for DelaunayRepairFailureContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::LocalRepairRobustFallback { initial } => {
f.write_str("local repair failed (")?;
initial.fmt(f)?;
f.write_str("); robust fallback also failed")
}
Self::LocalRepairNonRecoverable => f.write_str("local repair failed (non-recoverable)"),
Self::OrientationCanonicalization => f.write_str("orientation canonicalization"),
Self::LocalRepair => f.write_str("local repair"),
Self::PostInsertionRepair => f.write_str("post-insertion repair"),
}
}
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum NeighborRebuildError {
#[error("neighbor wiring failed while repairing neighbors: {reason}")]
Wiring {
#[source]
reason: NeighborWiringError,
},
#[error(
"non-manifold topology while repairing neighbors: facet {facet_hash:#x} shared by {cell_count} cells"
)]
NonManifoldTopology {
facet_hash: u64,
cell_count: usize,
},
#[error("TDS validation failed while repairing neighbors: {reason}")]
TopologyValidation {
#[source]
reason: TdsValidationFailure,
},
#[error("unexpected neighbor repair error: {source}")]
Unexpected {
#[source]
source: InsertionErrorSummary,
},
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum CavityFillingError {
#[error("boundary facet cell {cell_key:?} not found")]
MissingBoundaryCell {
cell_key: CellKey,
},
#[error("inserted vertex {vertex_key:?} not found in TDS")]
MissingInsertedVertex {
vertex_key: VertexKey,
},
#[error("boundary cell {cell_key:?} has {actual} vertices, expected {expected}")]
WrongCellArity {
cell_key: CellKey,
actual: usize,
expected: usize,
},
#[error(
"facet index {facet_index} out of range for boundary cell {cell_key:?} with {vertex_count} vertices"
)]
InvalidFacetIndex {
cell_key: CellKey,
facet_index: usize,
vertex_count: usize,
},
#[error("failed to create replacement cell: {source}")]
CellCreation {
#[from]
source: CellValidationError,
},
#[error("failed to insert replacement cell: {reason}")]
CellInsertion {
#[source]
reason: TdsConstructionFailure,
},
#[error("failed to build initial simplex: {reason}")]
InitialSimplexConstruction {
#[source]
reason: InitialSimplexConstructionError,
},
#[error("inserted vertex with UUID {uuid} not found in rebuilt TDS")]
RebuiltVertexMissing {
uuid: uuid::Uuid,
},
#[error("empty conflict region for exterior insertion (fallback cell: {fallback_cell:?})")]
EmptyConflictRegion {
fallback_cell: Option<CellKey>,
},
#[error("empty cavity boundary for exterior insertion (fallback cell: {fallback_cell:?})")]
EmptyBoundary {
fallback_cell: Option<CellKey>,
},
#[error("facet sharing invalid after insertion repairs during {stage}")]
InvalidFacetSharingAfterRepair {
stage: CavityRepairStage,
},
#[error("failed to repair neighbors after insertion repairs: {reason}")]
NeighborRebuild {
#[source]
reason: NeighborRebuildError,
},
#[error("failed to convert perturbation scale {value} into scalar type")]
PerturbationScaleConversion {
value: String,
},
#[error("unsupported degenerate insertion location: {location:?}")]
UnsupportedDegenerateLocation {
location: LocateResult,
},
#[error("fan triangulation produced no cells")]
EmptyFanTriangulation,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CavityRepairStage {
PrimaryInsertion,
FanTriangulation,
}
impl fmt::Display for CavityRepairStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PrimaryInsertion => f.write_str("primary insertion"),
Self::FanTriangulation => f.write_str("fan triangulation"),
}
}
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum NeighborWiringError {
#[error("cell {cell_key:?} not found during neighbor wiring")]
MissingCell {
cell_key: CellKey,
},
#[error(
"facet index {facet_index} out of range for cell {cell_key:?} with {vertex_count} vertices"
)]
InvalidFacetIndex {
cell_key: CellKey,
facet_index: usize,
vertex_count: usize,
},
#[error("facet index {facet_index} exceeds compact facet-index maximum {max}")]
FacetIndexOverflow {
facet_index: usize,
max: u8,
},
#[error("cell {cell_key:?} has {found} vertices; expected {expected} for neighbor wiring")]
WrongCellArity {
cell_key: CellKey,
expected: usize,
found: usize,
},
#[error(
"external facet {facet_index} on cell {cell_key:?} with hash {facet_hash:#x} did not match any new-cell facet"
)]
ExternalFacetNotFound {
cell_key: CellKey,
facet_index: u8,
facet_hash: u64,
},
#[error(
"external facet {facet_index} on cell {cell_key:?} with hash {facet_hash:#x} matched {existing_incidents} new-cell incidents"
)]
ExternalFacetAlreadyShared {
cell_key: CellKey,
facet_index: u8,
facet_hash: u64,
existing_incidents: usize,
},
#[error("cell {cell_key:?} has a self-neighbor pointer")]
SelfNeighbor {
cell_key: CellKey,
},
#[error("cell {cell_key:?} has neighbor pointer to missing cell {neighbor_key:?}")]
MissingNeighborTarget {
cell_key: CellKey,
neighbor_key: CellKey,
},
#[error(
"cell {cell_key:?} facet {facet_index} points to {neighbor_key:?}, \
but the mirror facet {mirror_facet_index:?} points back to {neighbor_back:?}"
)]
AsymmetricNeighbor {
cell_key: CellKey,
facet_index: usize,
neighbor_key: CellKey,
mirror_facet_index: Option<usize>,
neighbor_back: Option<CellKey>,
},
#[error("neighbor walk visited {visited} unique cells but triangulation contains {total}")]
NeighborWalkExceededCellCount {
visited: usize,
total: usize,
},
}
#[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum InsertionError {
#[error("Conflict region error: {0}")]
ConflictRegion(#[from] ConflictError),
#[error("Location error: {0}")]
Location(#[from] LocateError),
#[error("Cavity filling failed: {reason}")]
CavityFilling {
#[source]
reason: CavityFillingError,
},
#[error("Neighbor wiring failed: {reason}")]
NeighborWiring {
#[source]
reason: NeighborWiringError,
},
#[error(
"Non-manifold topology: facet {facet_hash:#x} shared by {cell_count} cells (expected ≤2)"
)]
NonManifoldTopology {
facet_hash: u64,
cell_count: usize,
},
#[error("Hull extension failed: {reason}")]
HullExtension {
reason: HullExtensionReason,
},
#[error("Delaunay validation failed: {source}")]
DelaunayValidationFailed {
#[source]
source: DelaunayTriangulationValidationError,
},
#[error("Delaunay repair failed ({context}): {source}")]
DelaunayRepairFailed {
#[source]
source: Box<DelaunayRepairError>,
context: DelaunayRepairFailureContext,
},
#[error(
"Duplicate coordinates: vertex with coordinates {coordinates} already exists in the triangulation"
)]
DuplicateCoordinates {
coordinates: String,
},
#[error("Duplicate UUID: {entity:?} with UUID {uuid} already exists")]
DuplicateUuid {
entity: EntityKind,
uuid: uuid::Uuid,
},
#[error("Topology validation error: {0}")]
TopologyValidation(#[from] TdsError),
#[error("{message}: {source}")]
TopologyValidationFailed {
message: String,
#[source]
source: TriangulationValidationError,
},
}
impl From<CavityFillingError> for InsertionError {
fn from(reason: CavityFillingError) -> Self {
Self::CavityFilling { reason }
}
}
impl From<TdsConstructionError> for CavityFillingError {
fn from(source: TdsConstructionError) -> Self {
Self::CellInsertion {
reason: source.into(),
}
}
}
impl From<TdsConstructionError> for InsertionError {
fn from(source: TdsConstructionError) -> Self {
match source {
TdsConstructionError::ValidationError(source) => Self::TopologyValidation(source),
TdsConstructionError::DuplicateUuid { entity, uuid } => {
Self::DuplicateUuid { entity, uuid }
}
}
}
}
impl From<NeighborWiringError> for InsertionError {
fn from(reason: NeighborWiringError) -> Self {
Self::NeighborWiring { reason }
}
}
impl From<InsertionError> for NeighborRebuildError {
fn from(source: InsertionError) -> Self {
match source {
InsertionError::NeighborWiring { reason } => Self::Wiring { reason },
InsertionError::NonManifoldTopology {
facet_hash,
cell_count,
} => Self::NonManifoldTopology {
facet_hash,
cell_count,
},
InsertionError::TopologyValidation(source) => Self::TopologyValidation {
reason: source.into(),
},
other => Self::Unexpected {
source: other.into(),
},
}
}
}
impl InsertionError {
#[must_use]
pub const fn is_retryable(&self) -> bool {
match self {
Self::NonManifoldTopology { .. } => true,
Self::TopologyValidationFailed { source, .. } => {
Self::is_level3_error_retryable(source)
}
Self::TopologyValidation(tds_err) => Self::is_tds_error_retryable(tds_err),
Self::ConflictRegion(ce) => {
matches!(
ce,
ConflictError::NonManifoldFacet { .. }
| ConflictError::RidgeFan { .. }
| ConflictError::DisconnectedBoundary { .. }
| ConflictError::OpenBoundary { .. }
)
}
Self::HullExtension { reason } => {
matches!(
reason,
HullExtensionReason::NoVisibleFacets | HullExtensionReason::InvalidPatch { .. }
)
}
Self::CavityFilling { reason } => Self::is_cavity_filling_error_retryable(reason),
Self::NeighborWiring { .. }
| Self::Location(_)
| Self::DelaunayValidationFailed { .. }
| Self::DelaunayRepairFailed { .. }
| Self::DuplicateCoordinates { .. }
| Self::DuplicateUuid { .. } => false,
}
}
const fn is_tds_error_retryable(tds_err: &TdsError) -> bool {
matches!(
tds_err,
TdsError::Geometric(_)
| TdsError::OrientationViolation { .. }
| TdsError::FacetSharingViolation { .. }
)
}
const fn is_tds_validation_failure_retryable(err: &TdsValidationFailure) -> bool {
matches!(
err,
TdsValidationFailure::Geometric { .. }
| TdsValidationFailure::OrientationViolation { .. }
| TdsValidationFailure::FacetSharingViolation { .. }
)
}
const fn is_cavity_filling_error_retryable(err: &CavityFillingError) -> bool {
match err {
CavityFillingError::InvalidFacetSharingAfterRepair { .. } => true,
CavityFillingError::CellInsertion { reason } => match reason {
TdsConstructionFailure::Validation { reason } => {
Self::is_tds_validation_failure_retryable(reason)
}
TdsConstructionFailure::DuplicateUuid { .. } => false,
},
CavityFillingError::InitialSimplexConstruction { reason } => match reason {
InitialSimplexConstructionError::TdsValidation { source } => {
Self::is_tds_validation_failure_retryable(source)
}
InitialSimplexConstructionError::GeometricDegeneracy { .. } => true,
InitialSimplexConstructionError::DuplicateUuid { .. }
| InitialSimplexConstructionError::FailedToCreateCell { .. }
| InitialSimplexConstructionError::InsufficientVertices { .. }
| InitialSimplexConstructionError::InternalInconsistency { .. }
| InitialSimplexConstructionError::DuplicateCoordinates { .. }
| InitialSimplexConstructionError::UnexpectedInsertionStage { .. } => false,
},
CavityFillingError::NeighborRebuild { reason } => match reason {
NeighborRebuildError::NonManifoldTopology { .. } => true,
NeighborRebuildError::TopologyValidation { reason } => {
Self::is_tds_validation_failure_retryable(reason)
}
NeighborRebuildError::Unexpected { source } => source.is_retryable(),
NeighborRebuildError::Wiring { .. } => false,
},
CavityFillingError::MissingBoundaryCell { .. }
| CavityFillingError::MissingInsertedVertex { .. }
| CavityFillingError::WrongCellArity { .. }
| CavityFillingError::InvalidFacetIndex { .. }
| CavityFillingError::CellCreation { .. }
| CavityFillingError::RebuiltVertexMissing { .. }
| CavityFillingError::EmptyConflictRegion { .. }
| CavityFillingError::EmptyBoundary { .. }
| CavityFillingError::PerturbationScaleConversion { .. }
| CavityFillingError::UnsupportedDegenerateLocation { .. }
| CavityFillingError::EmptyFanTriangulation => false,
}
}
const fn is_level3_error_retryable(err: &TriangulationValidationError) -> bool {
match err {
TriangulationValidationError::ManifoldFacetMultiplicity { .. }
| TriangulationValidationError::BoundaryRidgeMultiplicity { .. }
| TriangulationValidationError::RidgeLinkNotManifold { .. }
| TriangulationValidationError::VertexLinkNotManifold { .. }
| TriangulationValidationError::IsolatedVertex { .. } => true,
_ => false,
}
}
}
pub fn fill_cavity<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
new_vertex_key: VertexKey,
boundary_facets: &[FacetHandle],
) -> Result<CellKeyBuffer, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
fill_cavity_impl(
tds,
new_vertex_key,
boundary_facets,
CavityInsertionTopology::Checked,
)
}
pub(crate) fn fill_cavity_replacing_cells<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
new_vertex_key: VertexKey,
boundary_facets: &[FacetHandle],
) -> Result<CellKeyBuffer, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
fill_cavity_impl(
tds,
new_vertex_key,
boundary_facets,
CavityInsertionTopology::Prechecked,
)
}
fn fill_cavity_with_prechecked_topology<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
new_vertex_key: VertexKey,
boundary_facets: &[FacetHandle],
) -> Result<CellKeyBuffer, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
fill_cavity_impl(
tds,
new_vertex_key,
boundary_facets,
CavityInsertionTopology::Prechecked,
)
}
#[derive(Clone, Copy)]
enum CavityInsertionTopology {
Checked,
Prechecked,
}
#[expect(
clippy::too_many_lines,
reason = "Cavity filling includes detailed debug instrumentation and error handling"
)]
fn fill_cavity_impl<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
new_vertex_key: VertexKey,
boundary_facets: &[FacetHandle],
insertion_topology: CavityInsertionTopology,
) -> Result<CellKeyBuffer, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if !tds.contains_vertex_key(new_vertex_key) {
return Err(CavityFillingError::MissingInsertedVertex {
vertex_key: new_vertex_key,
}
.into());
}
#[cfg(debug_assertions)]
{
let log_enabled = std::env::var_os("DELAUNAY_DEBUG_CAVITY").is_some();
let mut seen_facets: FastHashMap<u64, Vec<FacetHandle>> = FastHashMap::default();
for facet_handle in boundary_facets {
if let Some(boundary_cell) = tds.cell(facet_handle.cell_key()) {
let facet_idx = usize::from(facet_handle.facet_index());
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vertex_key) in boundary_cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vertex_key);
}
}
facet_vkeys.sort_unstable();
let facet_hash = facet_hash_from_sorted_vertices(&facet_vkeys);
seen_facets
.entry(facet_hash)
.or_default()
.push(*facet_handle);
}
}
let duplicates: Vec<_> = seen_facets
.iter()
.filter(|(_, handles)| handles.len() > 1)
.collect();
if !duplicates.is_empty() {
tracing::warn!(
duplicate_facets = duplicates.len(),
"fill_cavity: duplicate boundary facets will create overlapping cells"
);
for (hash, handles) in &duplicates {
tracing::warn!(
facet_hash = *hash,
instances = handles.len(),
handles = ?handles,
"fill_cavity: duplicate boundary facet"
);
}
} else if log_enabled {
tracing::debug!(
boundary_facets = boundary_facets.len(),
"fill_cavity: no duplicate boundary facet hashes"
);
}
if log_enabled {
let mut ridge_counts: FastHashMap<u64, usize> = FastHashMap::default();
let mut ridge_vertices_map: FastHashMap<u64, VertexKeyBuffer> = FastHashMap::default();
for facet_handle in boundary_facets {
let Some(boundary_cell) = tds.cell(facet_handle.cell_key()) else {
tracing::warn!(
cell_key = ?facet_handle.cell_key(),
"fill_cavity: missing boundary cell while building ridge incidence"
);
continue;
};
let facet_idx = usize::from(facet_handle.facet_index());
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vertex_key) in boundary_cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vertex_key);
}
}
if facet_vkeys.len() < 2 {
continue;
}
for omit in 0..facet_vkeys.len() {
let mut ridge_vertices =
SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (j, &vkey) in facet_vkeys.iter().enumerate() {
if j != omit {
ridge_vertices.push(vkey);
}
}
ridge_vertices.sort_unstable();
let ridge_hash = facet_hash_from_sorted_vertices(&ridge_vertices);
*ridge_counts.entry(ridge_hash).or_insert(0) += 1;
ridge_vertices_map
.entry(ridge_hash)
.or_insert_with(|| ridge_vertices.iter().copied().collect());
}
}
let mut ridge_boundary = 0usize;
let mut ridge_internal = 0usize;
let mut ridge_over_shared = 0usize;
for count in ridge_counts.values() {
match *count {
1 => ridge_boundary += 1,
2 => ridge_internal += 1,
_ => ridge_over_shared += 1,
}
}
tracing::debug!(
boundary_facets = boundary_facets.len(),
ridge_boundary,
ridge_internal,
ridge_over_shared,
"fill_cavity: boundary ridge incidence summary"
);
if ridge_over_shared > 0 {
let mut logged = 0usize;
for (&ridge_hash, &count) in &ridge_counts {
if count <= 2 {
continue;
}
if logged >= 10 {
break;
}
tracing::debug!(
ridge_hash,
ridge_count = count,
ridge_vertices = ?ridge_vertices_map.get(&ridge_hash),
"fill_cavity: ridge shared by >2 boundary facets"
);
logged += 1;
}
}
}
}
let mut new_cells = CellKeyBuffer::new();
for facet_handle in boundary_facets {
let boundary_cell = tds.cell(facet_handle.cell_key()).ok_or_else(|| {
CavityFillingError::MissingBoundaryCell {
cell_key: facet_handle.cell_key(),
}
})?;
if boundary_cell.number_of_vertices() != D + 1 {
return Err(CavityFillingError::WrongCellArity {
cell_key: facet_handle.cell_key(),
actual: boundary_cell.number_of_vertices(),
expected: D + 1,
}
.into());
}
let facet_idx = usize::from(facet_handle.facet_index());
if facet_idx >= boundary_cell.number_of_vertices() {
return Err(CavityFillingError::InvalidFacetIndex {
cell_key: facet_handle.cell_key(),
facet_index: facet_idx,
vertex_count: boundary_cell.number_of_vertices(),
}
.into());
}
let mut new_cell_vertices = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vertex_key) in boundary_cell.vertices().iter().enumerate() {
if i != facet_idx {
new_cell_vertices.push(vertex_key);
}
}
new_cell_vertices.push(new_vertex_key);
let expected_odd_permutation = (facet_idx + D).is_multiple_of(2);
if expected_odd_permutation && D >= 2 {
new_cell_vertices.swap(0, 1);
}
let new_cell = Cell::new(new_cell_vertices, None).map_err(CavityFillingError::from)?;
let cell_key = match insertion_topology {
CavityInsertionTopology::Checked => {
tds.insert_cell_with_mapping_trusted_vertices(new_cell)
}
CavityInsertionTopology::Prechecked => {
tds.insert_cell_with_mapping_prechecked_topology(new_cell)
}
}
.map_err(CavityFillingError::from)?;
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_CAVITY").is_some()
&& let Some(created_cell) = tds.cell(cell_key)
{
let cell_points: SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE> = created_cell
.vertices()
.iter()
.filter_map(|&vk| tds.vertex(vk).map(|v| *v.point()))
.collect();
let orientation: Option<i32> = if cell_points.len() == D + 1 {
match robust_orientation(&cell_points) {
Ok(Orientation::POSITIVE) => Some(1),
Ok(Orientation::NEGATIVE) => Some(-1),
Ok(Orientation::DEGENERATE) => Some(0),
Err(ref e) => {
tracing::warn!(
cell_key = ?cell_key,
vertex_keys = ?created_cell.vertices(),
error = %e,
"fill_cavity: robust_orientation failed for created cell"
);
None
}
}
} else {
tracing::warn!(
cell_key = ?cell_key,
vertex_keys = ?created_cell.vertices(),
actual_len = cell_points.len(),
expected_len = D + 1,
"fill_cavity: incomplete vertex data for orientation (missing vertices)"
);
None
};
tracing::debug!(
cell_key = ?cell_key,
vertex_keys = ?created_cell.vertices(),
orientation = ?orientation,
source_boundary_cell = ?facet_handle.cell_key(),
source_facet_index = usize::from(facet_handle.facet_index()),
"fill_cavity: created cell provenance"
);
}
new_cells.push(cell_key);
}
debug_assert_eq!(
boundary_facets.len(),
new_cells.len(),
"Created {} cells for {} boundary facets (should be 1:1)",
new_cells.len(),
boundary_facets.len()
);
Ok(new_cells)
}
#[expect(
clippy::too_many_lines,
reason = "Neighbor wiring keeps cohesive logic and debug accounting together"
)]
pub fn wire_cavity_neighbors<T, U, V, const D: usize, I>(
tds: &mut Tds<T, U, V, D>,
new_cells: &CellKeyBuffer,
external_facets: I,
conflict_cells: Option<&CellKeyBuffer>,
) -> Result<(), InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
I: IntoIterator<Item = FacetHandle>,
{
type FacetIncidents = SmallBuffer<(CellKey, u8), 2>;
type FacetMap = FastHashMap<u64, FacetIncidents>;
let mut facet_map: FacetMap = FastHashMap::default();
#[cfg(not(debug_assertions))]
let _conflict_cells = conflict_cells;
#[cfg(debug_assertions)]
let log_enabled = std::env::var_os("DELAUNAY_DEBUG_CAVITY").is_some();
#[cfg(debug_assertions)]
let ridge_link_debug = std::env::var_os("DELAUNAY_DEBUG_RIDGE_LINK").is_some();
for &cell_key in new_cells {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let vertex_count = cell.number_of_vertices();
let expected = D + 1;
if vertex_count != expected {
return Err(NeighborWiringError::WrongCellArity {
cell_key,
expected,
found: vertex_count,
}
.into());
}
for facet_idx in 0..vertex_count {
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vkey);
}
}
facet_vkeys.sort_unstable();
let facet_key = facet_hash_from_sorted_vertices(&facet_vkeys);
let facet_idx_u8 =
u8::try_from(facet_idx).map_err(|_| NeighborWiringError::FacetIndexOverflow {
facet_index: facet_idx,
max: u8::MAX,
})?;
facet_map
.entry(facet_key)
.or_default()
.push((cell_key, facet_idx_u8));
}
}
for external in external_facets {
let cell_key = external.cell_key();
let facet_idx = usize::from(external.facet_index());
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
if facet_idx >= cell.number_of_vertices() {
return Err(NeighborWiringError::InvalidFacetIndex {
cell_key,
facet_index: facet_idx,
vertex_count: cell.number_of_vertices(),
}
.into());
}
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vkey);
}
}
facet_vkeys.sort_unstable();
let facet_key = facet_hash_from_sorted_vertices(&facet_vkeys);
let Some(incidents) = facet_map.get_mut(&facet_key) else {
return Err(NeighborWiringError::ExternalFacetNotFound {
cell_key,
facet_index: external.facet_index(),
facet_hash: facet_key,
}
.into());
};
if incidents.len() == 1 {
incidents.push((cell_key, external.facet_index()));
} else {
return Err(NeighborWiringError::ExternalFacetAlreadyShared {
cell_key,
facet_index: external.facet_index(),
facet_hash: facet_key,
existing_incidents: incidents.len(),
}
.into());
}
}
#[cfg(debug_assertions)]
let conflict_set: FastHashSet<CellKey> = conflict_cells
.map(|cells| cells.iter().copied().collect())
.unwrap_or_default();
#[cfg(debug_assertions)]
let new_cells_set: FastHashSet<CellKey> = new_cells.iter().copied().collect();
for (facet_key, cells) in &facet_map {
if cells.len() == 2 {
let (c1, idx1) = cells[0];
let (c2, idx2) = cells[1];
set_neighbor(tds, c1, idx1, Some(c2))?;
set_neighbor(tds, c2, idx2, Some(c1))?;
} else if cells.len() > 2 {
#[cfg(debug_assertions)]
{
let cell_types: Vec<String> = cells
.iter()
.map(|(ck, _)| {
if new_cells_set.contains(ck) {
format!("NEW:{ck:?}")
} else if conflict_set.contains(ck) {
format!("CONFLICT:{ck:?}")
} else {
format!("EXISTING:{ck:?}")
}
})
.collect();
tracing::warn!(
facet_hash = *facet_key,
cell_count = cells.len(),
cell_types = ?cell_types,
"wire_cavity_neighbors: non-manifold facet shared by >2 cells"
);
}
return Err(InsertionError::NonManifoldTopology {
facet_hash: *facet_key,
cell_count: cells.len(),
});
}
}
#[cfg(debug_assertions)]
if log_enabled {
let mut boundary_facets = 0usize;
let mut internal_facets = 0usize;
let mut over_shared_facets = 0usize;
for cells in facet_map.values() {
match cells.len() {
1 => boundary_facets += 1,
2 => internal_facets += 1,
_ => over_shared_facets += 1,
}
}
tracing::debug!(
new_cells = new_cells.len(),
conflict_cells = conflict_cells.map_or(0, CellKeyBuffer::len),
internal_facets,
boundary_facets,
over_shared_facets,
"wire_cavity_neighbors: facet summary"
);
if over_shared_facets > 0 {
let mut logged = 0usize;
for (facet_key, cells) in &facet_map {
if cells.len() <= 2 {
continue;
}
if logged >= 10 {
break;
}
tracing::debug!(
facet_hash = *facet_key,
cell_count = cells.len(),
cells = ?cells,
"wire_cavity_neighbors: facet shared by >2 cells in map"
);
logged += 1;
}
}
}
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_NEIGHBORS").is_some() {
let mut mismatches = 0usize;
for &cell_key in new_cells {
let Some(cell) = tds.cell(cell_key) else {
continue;
};
let Some(neighbors) = cell.neighbors() else {
continue;
};
for (facet_idx, neighbor_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = neighbor_opt else {
continue;
};
let Some(neighbor_cell) = tds.cell(*neighbor_key) else {
continue;
};
let Some(mirror_idx) = cell.mirror_facet_index(facet_idx, neighbor_cell) else {
mismatches += 1;
tracing::warn!(
cell = ?cell_key,
facet_idx,
neighbor = ?neighbor_key,
"wire_cavity_neighbors: missing mirror facet index"
);
continue;
};
let neighbor_back = neighbor_cell
.neighbors()
.and_then(|ns| ns.get(mirror_idx).copied().flatten());
if neighbor_back != Some(cell_key) {
mismatches += 1;
tracing::warn!(
cell = ?cell_key,
facet_idx,
neighbor = ?neighbor_key,
mirror_idx,
neighbor_back = ?neighbor_back,
"wire_cavity_neighbors: asymmetric neighbor pointer"
);
}
}
}
tracing::debug!(
mismatches,
"wire_cavity_neighbors: neighbor symmetry check complete"
);
}
#[cfg(debug_assertions)]
if ridge_link_debug {
let mut total_slots = 0usize;
let mut neighbor_new = 0usize;
let mut neighbor_existing = 0usize;
let mut neighbor_conflict = 0usize;
let mut neighbor_missing = 0usize;
let mut neighbor_none = 0usize;
let mut anomaly_samples: Vec<(CellKey, usize, Option<CellKey>, String)> = Vec::new();
for &cell_key in new_cells {
let Some(cell) = tds.cell(cell_key) else {
continue;
};
let vertex_count = cell.number_of_vertices();
total_slots = total_slots.saturating_add(vertex_count);
let Some(neighbors) = cell.neighbors() else {
neighbor_none = neighbor_none.saturating_add(vertex_count);
continue;
};
for (facet_idx, neighbor_opt) in neighbors.iter().enumerate() {
match neighbor_opt {
None => {
neighbor_none = neighbor_none.saturating_add(1);
}
Some(neighbor_key) => {
if new_cells_set.contains(neighbor_key) {
neighbor_new = neighbor_new.saturating_add(1);
} else if conflict_set.contains(neighbor_key) {
neighbor_conflict = neighbor_conflict.saturating_add(1);
if anomaly_samples.len() < 10 {
anomaly_samples.push((
cell_key,
facet_idx,
Some(*neighbor_key),
"CONFLICT".to_string(),
));
}
} else if tds.contains_cell(*neighbor_key) {
neighbor_existing = neighbor_existing.saturating_add(1);
} else {
neighbor_missing = neighbor_missing.saturating_add(1);
if anomaly_samples.len() < 10 {
anomaly_samples.push((
cell_key,
facet_idx,
Some(*neighbor_key),
"MISSING".to_string(),
));
}
}
}
}
}
}
tracing::debug!(
new_cells = new_cells.len(),
total_slots,
neighbor_new,
neighbor_existing,
neighbor_conflict,
neighbor_missing,
neighbor_none,
"wire_cavity_neighbors: new-cell neighbor classification summary"
);
if neighbor_conflict > 0 || neighbor_missing > 0 {
tracing::warn!(
neighbor_conflict,
neighbor_missing,
anomaly_samples = ?anomaly_samples,
"wire_cavity_neighbors: unexpected neighbor classifications for new cells"
);
}
}
Ok(())
}
pub(crate) fn external_facets_for_boundary<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
internal_cells: &CellKeyBuffer,
boundary_facets: &[FacetHandle],
) -> Result<SmallBuffer<FacetHandle, 64>, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if internal_cells.is_empty() || boundary_facets.is_empty() {
return Ok(SmallBuffer::new());
}
let internal_set: FastHashSet<CellKey> = internal_cells.iter().copied().collect();
let mut boundary_hashes: FastHashSet<u64> = FastHashSet::default();
for &facet in boundary_facets {
let cell_key = facet.cell_key();
let facet_idx = usize::from(facet.facet_index());
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
if facet_idx >= cell.number_of_vertices() {
return Err(NeighborWiringError::InvalidFacetIndex {
cell_key,
facet_index: facet_idx,
vertex_count: cell.number_of_vertices(),
}
.into());
}
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vkey);
}
}
facet_vkeys.sort_unstable();
boundary_hashes.insert(facet_hash_from_sorted_vertices(&facet_vkeys));
}
let mut candidate_cells: FastHashSet<CellKey> = FastHashSet::default();
for &cell_key in internal_cells {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let Some(neighbors) = cell.neighbors() else {
continue;
};
for &neighbor_opt in neighbors {
let Some(neighbor_key) = neighbor_opt else {
continue;
};
if !internal_set.contains(&neighbor_key) {
candidate_cells.insert(neighbor_key);
}
}
}
let mut external_facets: SmallBuffer<FacetHandle, 64> = SmallBuffer::new();
for &cell_key in &candidate_cells {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
for facet_idx in 0..cell.number_of_vertices() {
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vkey);
}
}
facet_vkeys.sort_unstable();
let facet_hash = facet_hash_from_sorted_vertices(&facet_vkeys);
if !boundary_hashes.contains(&facet_hash) {
continue;
}
let facet_idx_u8 =
u8::try_from(facet_idx).map_err(|_| NeighborWiringError::FacetIndexOverflow {
facet_index: facet_idx,
max: u8::MAX,
})?;
external_facets.push(FacetHandle::new(cell_key, facet_idx_u8));
}
}
Ok(external_facets)
}
fn set_neighbor<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
cell_key: CellKey,
facet_idx: u8,
neighbor: Option<CellKey>,
) -> Result<(), InsertionError>
where
U: DataType,
V: DataType,
{
let cell = tds
.cell_mut(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let mut neighbors = cell.neighbors().map_or_else(
|| SmallBuffer::from_elem(None, D + 1),
|n| n.iter().copied().collect(),
);
neighbors[usize::from(facet_idx)] = neighbor;
cell.neighbors = Some(neighbors);
Ok(())
}
fn facet_hash_from_sorted_vertices(sorted_vkeys: &[VertexKey]) -> u64 {
debug_assert!(
sorted_vkeys.windows(2).all(|pair| pair[0] <= pair[1]),
"facet_hash_from_sorted_vertices: input must be sorted"
);
let mut hasher = FastHasher::default();
for &vkey in sorted_vkeys {
vkey.hash(&mut hasher);
}
hasher.finish()
}
fn facet_hash_for_cell<T, U, V, const D: usize>(cell: &Cell<T, U, V, D>, facet_idx: usize) -> u64
where
U: DataType,
V: DataType,
{
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vkey);
}
}
facet_vkeys.sort_unstable();
facet_hash_from_sorted_vertices(&facet_vkeys)
}
fn neighbor_slot_points_across_facet<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
cell: &Cell<T, U, V, D>,
facet_idx: usize,
neighbor_key: CellKey,
) -> bool
where
U: DataType,
V: DataType,
{
tds.cell(neighbor_key)
.is_some_and(|neighbor_cell| cell.mirror_facet_index(facet_idx, neighbor_cell).is_some())
}
#[expect(
clippy::too_many_lines,
reason = "Local neighbor repair keeps affected-set construction, facet indexing, and slot application together"
)]
pub fn repair_neighbor_pointers_local<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
seeds: &[CellKey],
optional_external_cells: Option<&[CellKey]>,
) -> Result<usize, InsertionError>
where
U: DataType,
V: DataType,
{
type FacetIncidents = SmallBuffer<(CellKey, u8), 2>;
let mut affected_cells: FastHashSet<CellKey> = FastHashSet::default();
for &cell_key in seeds {
if tds.contains_cell(cell_key) {
affected_cells.insert(cell_key);
}
}
if let Some(external_cells) = optional_external_cells {
for &cell_key in external_cells {
if tds.contains_cell(cell_key) {
affected_cells.insert(cell_key);
}
}
}
if affected_cells.is_empty() {
return Ok(0);
}
let seed_snapshot: Vec<CellKey> = affected_cells.iter().copied().collect();
for cell_key in seed_snapshot {
let Some(cell) = tds.cell(cell_key) else {
continue;
};
let Some(neighbors) = cell.neighbors() else {
continue;
};
for &neighbor_key in neighbors.iter().flatten() {
if tds.contains_cell(neighbor_key) {
affected_cells.insert(neighbor_key);
}
}
}
let mut facet_map: FastHashMap<u64, FacetIncidents> = FastHashMap::default();
for &cell_key in &affected_cells {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let vertex_count = cell.number_of_vertices();
let expected = D + 1;
if vertex_count != expected {
return Err(NeighborWiringError::WrongCellArity {
cell_key,
expected,
found: vertex_count,
}
.into());
}
for facet_idx in 0..vertex_count {
let facet_hash = facet_hash_for_cell(cell, facet_idx);
let facet_idx_u8 =
u8::try_from(facet_idx).map_err(|_| NeighborWiringError::FacetIndexOverflow {
facet_index: facet_idx,
max: u8::MAX,
})?;
let entry = facet_map.entry(facet_hash).or_default();
entry.push((cell_key, facet_idx_u8));
if entry.len() > 2 {
return Err(InsertionError::NonManifoldTopology {
facet_hash,
cell_count: entry.len(),
});
}
}
}
let mut total_neighbor_slots_fixed = 0usize;
for &cell_key in &affected_cells {
let (vertex_count, old_neighbors, replacement_by_facet, current_usable_by_facet) = {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let vertex_count = cell.number_of_vertices();
let old_neighbors: SmallBuffer<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE> =
cell.neighbors().map_or_else(
|| SmallBuffer::from_elem(None, vertex_count),
|neighbors| {
let mut copied: SmallBuffer<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE> =
neighbors.iter().copied().collect();
copied.resize(vertex_count, None);
copied
},
);
let mut replacement_by_facet =
SmallBuffer::<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
replacement_by_facet.resize(vertex_count, None);
let mut current_usable_by_facet =
SmallBuffer::<bool, MAX_PRACTICAL_DIMENSION_SIZE>::new();
current_usable_by_facet.resize(vertex_count, false);
for facet_idx in 0..vertex_count {
let current_neighbor = old_neighbors.get(facet_idx).copied().flatten();
let current_usable = current_neighbor.is_some_and(|neighbor_key| {
neighbor_slot_points_across_facet(tds, cell, facet_idx, neighbor_key)
});
current_usable_by_facet[facet_idx] = current_usable;
if current_usable {
continue;
}
let facet_hash = facet_hash_for_cell(cell, facet_idx);
let Some(incidents) = facet_map.get(&facet_hash) else {
continue;
};
let [(c1, _), (c2, _)] = incidents.as_slice() else {
continue;
};
replacement_by_facet[facet_idx] = if *c1 == cell_key {
Some(*c2)
} else if *c2 == cell_key {
Some(*c1)
} else {
None
};
}
(
vertex_count,
old_neighbors,
replacement_by_facet,
current_usable_by_facet,
)
};
let mut rebuilt_neighbors = old_neighbors;
let mut changed = false;
for facet_idx in 0..vertex_count {
let current_neighbor = rebuilt_neighbors.get(facet_idx).copied().flatten();
if current_usable_by_facet[facet_idx] {
continue;
}
let replacement = replacement_by_facet[facet_idx];
if current_neighbor != replacement {
rebuilt_neighbors[facet_idx] = replacement;
total_neighbor_slots_fixed = total_neighbor_slots_fixed.saturating_add(1);
changed = true;
}
}
if !changed {
continue;
}
let cell = tds
.cell_mut(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
if rebuilt_neighbors.iter().all(Option::is_none) {
cell.neighbors = None;
} else {
cell.neighbors = Some(rebuilt_neighbors);
}
}
#[cfg(debug_assertions)]
{
validate_no_neighbor_cycles_for_cells(tds, &affected_cells)?;
validate_neighbor_symmetry_for_cells(tds, &affected_cells)?;
}
Ok(total_neighbor_slots_fixed)
}
#[expect(
clippy::too_many_lines,
reason = "Neighbor rebuild keeps facet indexing + wiring + application cohesive; prefer correctness and debuggability"
)]
pub fn repair_neighbor_pointers<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
) -> Result<usize, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
type FacetIncidents = SmallBuffer<(CellKey, u8), 2>;
let cell_keys: Vec<CellKey> = tds.cells().map(|(key, _)| key).collect();
#[cfg(debug_assertions)]
tracing::trace!(
cells = cell_keys.len(),
"repair_neighbor_pointers: rebuilding neighbor pointers"
);
if cell_keys.is_empty() {
return Ok(0);
}
let mut facet_map: FastHashMap<u64, FacetIncidents> = FastHashMap::default();
let mut neighbors_by_cell: FastHashMap<
CellKey,
SmallBuffer<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE>,
> = FastHashMap::default();
for &cell_key in &cell_keys {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let vertex_count = cell.number_of_vertices();
let mut neighbors = SmallBuffer::with_capacity(vertex_count);
neighbors.resize(vertex_count, None);
neighbors_by_cell.insert(cell_key, neighbors);
let mut facet_vkeys = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for facet_idx in 0..vertex_count {
facet_vkeys.clear();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
facet_vkeys.push(vkey);
}
}
facet_vkeys.sort_unstable();
let facet_hash = facet_hash_from_sorted_vertices(&facet_vkeys);
let facet_idx_u8 =
u8::try_from(facet_idx).map_err(|_| NeighborWiringError::FacetIndexOverflow {
facet_index: facet_idx,
max: u8::MAX,
})?;
let entry = facet_map.entry(facet_hash).or_default();
entry.push((cell_key, facet_idx_u8));
if entry.len() > 2 {
return Err(InsertionError::NonManifoldTopology {
facet_hash,
cell_count: entry.len(),
});
}
}
}
for (facet_hash, incidents) in facet_map {
match incidents.as_slice() {
[(c1, i1), (c2, i2)] => {
{
let n1 = neighbors_by_cell
.get_mut(c1)
.ok_or(NeighborWiringError::MissingCell { cell_key: *c1 })?;
n1[usize::from(*i1)] = Some(*c2);
}
{
let n2 = neighbors_by_cell
.get_mut(c2)
.ok_or(NeighborWiringError::MissingCell { cell_key: *c2 })?;
n2[usize::from(*i2)] = Some(*c1);
}
}
[_] | [] => {
}
many => {
return Err(InsertionError::NonManifoldTopology {
facet_hash,
cell_count: many.len(),
});
}
}
}
let mut total_neighbor_slots_fixed: usize = 0;
for (cell_key, rebuilt) in neighbors_by_cell {
let old_neighbors: SmallBuffer<Option<CellKey>, MAX_PRACTICAL_DIMENSION_SIZE> = {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
cell.neighbors().map_or_else(
|| SmallBuffer::from_elem(None, rebuilt.len()),
|ns| ns.iter().copied().collect(),
)
};
total_neighbor_slots_fixed = total_neighbor_slots_fixed.saturating_add(
old_neighbors
.iter()
.zip(rebuilt.iter())
.filter(|(a, b)| a != b)
.count(),
);
let cell = tds
.cell_mut(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
if rebuilt.iter().all(Option::is_none) {
cell.neighbors = None;
} else {
cell.neighbors = Some(rebuilt);
}
}
#[cfg(debug_assertions)]
tracing::trace!(
neighbor_pointers_updated = total_neighbor_slots_fixed,
"repair_neighbor_pointers: neighbor rebuild complete"
);
tds.normalize_coherent_orientation()?;
#[cfg(debug_assertions)]
validate_no_neighbor_cycles(tds)?;
Ok(total_neighbor_slots_fixed)
}
#[cfg(debug_assertions)]
fn validate_no_neighbor_cycles<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
) -> Result<(), InsertionError>
where
U: DataType,
V: DataType,
{
let sample_cells: Vec<CellKey> = tds.cells().map(|(key, _)| key).take(10).collect();
let max_cells = tds.number_of_cells();
for &start_cell in &sample_cells {
let mut visited: FastHashSet<CellKey> = FastHashSet::default();
let mut to_visit = vec![start_cell];
visited.insert(start_cell);
while let Some(current) = to_visit.pop() {
let cell = tds
.cell(current)
.ok_or(NeighborWiringError::MissingCell { cell_key: current })?;
let Some(neighbors) = cell.neighbors() else {
continue;
};
for &neighbor_opt in neighbors {
let Some(neighbor_key) = neighbor_opt else {
continue;
};
if neighbor_key == current {
return Err(NeighborWiringError::SelfNeighbor { cell_key: current }.into());
}
if !tds.contains_cell(neighbor_key) {
return Err(NeighborWiringError::MissingNeighborTarget {
cell_key: current,
neighbor_key,
}
.into());
}
if visited.insert(neighbor_key) {
to_visit.push(neighbor_key);
if visited.len() > max_cells {
return Err(NeighborWiringError::NeighborWalkExceededCellCount {
visited: visited.len(),
total: max_cells,
}
.into());
}
}
}
}
}
tracing::trace!("validate_no_neighbor_cycles: neighbor walk terminated");
Ok(())
}
#[cfg(debug_assertions)]
fn validate_no_neighbor_cycles_for_cells<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
sample_cells: &FastHashSet<CellKey>,
) -> Result<(), InsertionError>
where
U: DataType,
V: DataType,
{
let sample_cells: Vec<CellKey> = sample_cells.iter().copied().take(10).collect();
let max_cells = tds.number_of_cells();
for &start_cell in &sample_cells {
let mut visited: FastHashSet<CellKey> = FastHashSet::default();
let mut to_visit = vec![start_cell];
visited.insert(start_cell);
while let Some(current) = to_visit.pop() {
let cell = tds
.cell(current)
.ok_or(NeighborWiringError::MissingCell { cell_key: current })?;
let Some(neighbors) = cell.neighbors() else {
continue;
};
for &neighbor_opt in neighbors {
let Some(neighbor_key) = neighbor_opt else {
continue;
};
if neighbor_key == current {
return Err(NeighborWiringError::SelfNeighbor { cell_key: current }.into());
}
if !tds.contains_cell(neighbor_key) {
return Err(NeighborWiringError::MissingNeighborTarget {
cell_key: current,
neighbor_key,
}
.into());
}
if visited.insert(neighbor_key) {
to_visit.push(neighbor_key);
if visited.len() > max_cells {
return Err(NeighborWiringError::NeighborWalkExceededCellCount {
visited: visited.len(),
total: max_cells,
}
.into());
}
}
}
}
}
Ok(())
}
#[cfg(debug_assertions)]
fn validate_neighbor_symmetry_for_cells<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
affected_cells: &FastHashSet<CellKey>,
) -> Result<(), InsertionError>
where
U: DataType,
V: DataType,
{
for &cell_key in affected_cells {
let cell = tds
.cell(cell_key)
.ok_or(NeighborWiringError::MissingCell { cell_key })?;
let Some(neighbors) = cell.neighbors() else {
continue;
};
for (facet_idx, &neighbor_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = neighbor_opt else {
continue;
};
if neighbor_key == cell_key {
return Err(NeighborWiringError::SelfNeighbor { cell_key }.into());
}
let Some(neighbor_cell) = tds.cell(neighbor_key) else {
return Err(NeighborWiringError::MissingNeighborTarget {
cell_key,
neighbor_key,
}
.into());
};
let mirror_facet_index = cell.mirror_facet_index(facet_idx, neighbor_cell);
let neighbor_back = mirror_facet_index.and_then(|mirror_idx| {
neighbor_cell
.neighbors()
.and_then(|neighbor_neighbors| neighbor_neighbors.get(mirror_idx))
.copied()
.flatten()
});
if neighbor_back != Some(cell_key) {
return Err(NeighborWiringError::AsymmetricNeighbor {
cell_key,
facet_index: facet_idx,
neighbor_key,
mirror_facet_index,
neighbor_back,
}
.into());
}
}
}
Ok(())
}
pub fn extend_hull<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
new_vertex_key: VertexKey,
point: &Point<K::Scalar, D>,
) -> Result<CellKeyBuffer, InsertionError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if D == 2
&& let Some(edge_facet) = find_boundary_edge_split_facet(tds, point)?
{
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_HULL").is_some() {
tracing::debug!(
point = ?point,
cell_key = ?edge_facet.cell_key(),
facet_index = usize::from(edge_facet.facet_index()),
"extend_hull: 2D boundary-edge split"
);
}
let mut conflict_cells = CellKeyBuffer::new();
conflict_cells.push(edge_facet.cell_key());
let mut boundary_facets = extract_cavity_boundary(tds, &conflict_cells)
.map_err(InsertionError::ConflictRegion)?;
boundary_facets.retain(|facet| {
facet.cell_key() != edge_facet.cell_key()
|| facet.facet_index() != edge_facet.facet_index()
});
if boundary_facets.len() != 2 {
return Err(InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!(
"2D boundary edge split expected 2 facets, got {}",
boundary_facets.len()
),
},
});
}
let external_facets = external_facets_for_boundary(tds, &conflict_cells, &boundary_facets)?;
let new_cells = fill_cavity_replacing_cells(tds, new_vertex_key, &boundary_facets)?;
wire_cavity_neighbors(
tds,
&new_cells,
external_facets.iter().copied(),
Some(&conflict_cells),
)?;
let _ = tds.remove_cells_by_keys(&conflict_cells);
return Ok(new_cells);
}
let visible_facets = match find_visible_boundary_facets(tds, kernel, point) {
Ok(facets) => facets,
Err(err) => {
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_HULL").is_some() {
tracing::debug!(error = ?err, "extend_hull: visibility computation failed");
}
return Err(err);
}
};
#[cfg(debug_assertions)]
if std::env::var_os("DELAUNAY_DEBUG_HULL").is_some() {
let total_boundary = tds.boundary_facets().map_or(0, Iterator::count);
tracing::debug!(
point = ?point,
visible_facets = visible_facets.len(),
total_boundary,
"extend_hull: visibility summary"
);
}
if visible_facets.is_empty() {
return Err(InsertionError::HullExtension {
reason: HullExtensionReason::NoVisibleFacets,
});
}
let new_cells = fill_cavity_with_prechecked_topology(tds, new_vertex_key, &visible_facets)?;
wire_cavity_neighbors(tds, &new_cells, visible_facets.iter().copied(), None)?;
Ok(new_cells)
}
fn find_boundary_edge_split_facet<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
point: &Point<T, D>,
) -> Result<Option<FacetHandle>, InsertionError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D != 2 {
return Ok(None);
}
let mut match_facet: Option<FacetHandle> = None;
let tol = T::default_tolerance();
let boundary_facets = tds
.boundary_facets()
.map_err(|e| InsertionError::HullExtension {
reason: HullExtensionReason::Tds(e),
})?;
for facet_view in boundary_facets {
let cell_key = facet_view.cell_key();
let facet_index = facet_view.facet_index();
let cell = tds
.cell(cell_key)
.ok_or_else(|| InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!("Boundary facet cell {cell_key:?} not found"),
},
})?;
let mut edge_points = SmallBuffer::<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
let mut opposite_point: Option<Point<T, D>> = None;
for (i, &vkey) in cell.vertices().iter().enumerate() {
let vertex = tds
.vertex(vkey)
.ok_or_else(|| InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!("Vertex {vkey:?} not found in TDS"),
},
})?;
if i == usize::from(facet_index) {
opposite_point = Some(*vertex.point());
} else {
edge_points.push(*vertex.point());
}
}
if edge_points.len() != 2 {
continue;
}
let opposite_point = opposite_point.ok_or_else(|| InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!(
"Opposite vertex missing for facet {facet_index} in cell {cell_key:?}"
),
},
})?;
let mut simplex_points = SmallBuffer::<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
simplex_points.extend(edge_points.iter().copied());
simplex_points.push(opposite_point);
let opposite_degenerate = matches!(
robust_orientation(&simplex_points).map_err(|e| InsertionError::HullExtension {
reason: HullExtensionReason::PredicateFailed(e),
})?,
Orientation::DEGENERATE
);
if opposite_degenerate {
continue;
}
let mut edge_line = SmallBuffer::<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
edge_line.extend(edge_points.iter().copied());
edge_line.push(*point);
let is_collinear = matches!(
robust_orientation(&edge_line).map_err(|e| InsertionError::HullExtension {
reason: HullExtensionReason::PredicateFailed(e),
})?,
Orientation::DEGENERATE
);
if !is_collinear {
continue;
}
let p0 = edge_points[0].coords();
let p1 = edge_points[1].coords();
let p = point.coords();
let (min_x, max_x) = if p0[0] <= p1[0] {
(p0[0], p1[0])
} else {
(p1[0], p0[0])
};
let (min_y, max_y) = if p0[1] <= p1[1] {
(p0[1], p1[1])
} else {
(p1[1], p0[1])
};
let on_segment = p[0] >= min_x - tol
&& p[0] <= max_x + tol
&& p[1] >= min_y - tol
&& p[1] <= max_y + tol;
if on_segment {
let handle = FacetHandle::new(cell_key, facet_index);
if match_facet.is_some() {
return Err(InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: "2D boundary edge split matched multiple facets".to_string(),
},
});
}
match_facet = Some(handle);
}
}
Ok(match_facet)
}
#[expect(
clippy::too_many_lines,
reason = "Visibility checks and diagnostic summaries are kept in a single routine"
)]
fn find_visible_boundary_facets<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
point: &Point<K::Scalar, D>,
) -> Result<Vec<FacetHandle>, InsertionError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let mut visible_facets = Vec::new();
#[cfg(debug_assertions)]
let log_enabled = std::env::var_os("DELAUNAY_DEBUG_HULL").is_some();
#[cfg(debug_assertions)]
let detail_enabled = std::env::var_os("DELAUNAY_DEBUG_HULL_DETAIL").is_some();
#[cfg(debug_assertions)]
let track_orientations = detail_enabled || log_enabled;
#[cfg(debug_assertions)]
let mut boundary_facets_count = 0usize;
#[cfg(debug_assertions)]
let mut orientation_opposite_positive = 0usize;
#[cfg(debug_assertions)]
let mut orientation_opposite_negative = 0usize;
#[cfg(debug_assertions)]
let mut orientation_opposite_zero = 0usize;
#[cfg(debug_assertions)]
let mut orientation_point_positive = 0usize;
#[cfg(debug_assertions)]
let mut orientation_point_negative = 0usize;
#[cfg(debug_assertions)]
let mut orientation_point_zero = 0usize;
#[cfg(debug_assertions)]
let mut visible_facets_strict = 0usize;
#[cfg(debug_assertions)]
let mut visible_facets_weak = 0usize;
#[cfg(debug_assertions)]
let mut degenerate_facets: Vec<FacetHandle> = Vec::new();
let boundary_facets = tds
.boundary_facets()
.map_err(|e| InsertionError::HullExtension {
reason: HullExtensionReason::Tds(e),
})?;
for facet_view in boundary_facets {
#[cfg(debug_assertions)]
if track_orientations {
boundary_facets_count += 1;
}
let cell_key = facet_view.cell_key();
let facet_index = facet_view.facet_index();
let cell = tds
.cell(cell_key)
.ok_or_else(|| InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!("Boundary facet cell {cell_key:?} not found"),
},
})?;
let mut simplex_points =
SmallBuffer::<Point<K::Scalar, D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
let mut opposite_point: Option<Point<K::Scalar, D>> = None;
for (i, &vkey) in cell.vertices().iter().enumerate() {
let vertex = tds
.vertex(vkey)
.ok_or_else(|| InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!("Vertex {vkey:?} not found in TDS"),
},
})?;
if i == usize::from(facet_index) {
opposite_point = Some(*vertex.point());
} else {
simplex_points.push(*vertex.point());
}
}
let opposite_point = opposite_point.ok_or_else(|| InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: format!(
"Opposite vertex missing for facet {facet_index} in cell {cell_key:?}"
),
},
})?;
simplex_points.push(opposite_point);
let orientation_with_opposite =
kernel
.orientation(&simplex_points)
.map_err(|e| InsertionError::HullExtension {
reason: HullExtensionReason::PredicateFailed(e),
})?;
let last_index = simplex_points.len() - 1;
simplex_points[last_index] = *point;
let orientation_with_point =
kernel
.orientation(&simplex_points)
.map_err(|e| InsertionError::HullExtension {
reason: HullExtensionReason::PredicateFailed(e),
})?;
#[cfg(debug_assertions)]
if log_enabled && D == 2 && orientation_with_point == 0 {
let p0 = simplex_points[0].coords();
let p1 = simplex_points[1].coords();
let p = point.coords();
let tol = K::Scalar::default_tolerance();
let (min_x, max_x) = if p0[0] <= p1[0] {
(p0[0], p1[0])
} else {
(p1[0], p0[0])
};
let (min_y, max_y) = if p0[1] <= p1[1] {
(p0[1], p1[1])
} else {
(p1[1], p0[1])
};
let min_x = min_x - tol;
let max_x = max_x + tol;
let min_y = min_y - tol;
let max_y = max_y + tol;
let on_segment = p[0] >= min_x && p[0] <= max_x && p[1] >= min_y && p[1] <= max_y;
tracing::debug!(
cell_key = ?cell_key,
facet_index = usize::from(facet_index),
point = ?point,
edge_start = ?p0,
edge_end = ?p1,
on_segment,
"find_visible_boundary_facets: query point coplanar with boundary edge"
);
}
let is_strict_visible = orientation_with_opposite != 0
&& orientation_with_point != 0
&& orientation_with_opposite.signum() != orientation_with_point.signum();
let is_weak_visible = if D == 2 {
false
} else {
orientation_with_opposite != 0 && orientation_with_point == 0
};
let is_visible = is_strict_visible || is_weak_visible;
#[cfg(debug_assertions)]
if track_orientations {
match orientation_with_opposite.cmp(&0) {
std::cmp::Ordering::Greater => orientation_opposite_positive += 1,
std::cmp::Ordering::Less => orientation_opposite_negative += 1,
std::cmp::Ordering::Equal => orientation_opposite_zero += 1,
}
match orientation_with_point.cmp(&0) {
std::cmp::Ordering::Greater => orientation_point_positive += 1,
std::cmp::Ordering::Less => orientation_point_negative += 1,
std::cmp::Ordering::Equal => orientation_point_zero += 1,
}
if is_strict_visible {
visible_facets_strict += 1;
}
if is_weak_visible {
visible_facets_weak += 1;
}
}
#[cfg(debug_assertions)]
if log_enabled && orientation_with_opposite == 0 && degenerate_facets.len() < 10 {
degenerate_facets.push(FacetHandle::new(cell_key, facet_index));
}
#[cfg(debug_assertions)]
if detail_enabled {
tracing::trace!(
cell_key = ?cell_key,
facet_index = usize::from(facet_index),
orientation_with_opposite,
orientation_with_point,
is_strict_visible,
is_weak_visible,
"find_visible_boundary_facets: facet orientation"
);
}
if is_visible {
visible_facets.push(FacetHandle::new(cell_key, facet_index));
}
}
#[cfg(debug_assertions)]
if log_enabled && visible_facets.is_empty() {
tracing::debug!(
point = ?point,
boundary_facets = boundary_facets_count,
visible_facets = visible_facets.len(),
visible_facets_strict,
visible_facets_weak,
orientation_opposite_positive,
orientation_opposite_negative,
orientation_opposite_zero,
orientation_point_positive,
orientation_point_negative,
orientation_point_zero,
degenerate_facets = ?degenerate_facets,
"find_visible_boundary_facets: no visible facets"
);
}
if D >= 2 && !visible_facets.is_empty() {
let mut ridge_to_facets: FastHashMap<u64, Vec<usize>> = FastHashMap::default();
let mut ridge_counts: FastHashMap<u64, usize> = FastHashMap::default();
let mut ridge_vertices_map: FastHashMap<u64, VertexKeyBuffer> = FastHashMap::default();
for (facet_idx, facet_handle) in visible_facets.iter().enumerate() {
let Some(cell) = tds.cell(facet_handle.cell_key()) else {
#[cfg(debug_assertions)]
tracing::warn!(
cell_key = ?facet_handle.cell_key(),
"find_visible_boundary_facets: missing cell while summarizing ridges"
);
continue;
};
let facet_index = usize::from(facet_handle.facet_index());
let mut facet_vertices = SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_index {
facet_vertices.push(vkey);
}
}
if facet_vertices.len() < 2 {
continue;
}
for omit in 0..facet_vertices.len() {
let mut ridge_vertices =
SmallBuffer::<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for (j, &vkey) in facet_vertices.iter().enumerate() {
if j != omit {
ridge_vertices.push(vkey);
}
}
ridge_vertices.sort_unstable();
let ridge_hash = facet_hash_from_sorted_vertices(&ridge_vertices);
ridge_to_facets
.entry(ridge_hash)
.or_default()
.push(facet_idx);
*ridge_counts.entry(ridge_hash).or_insert(0) += 1;
ridge_vertices_map
.entry(ridge_hash)
.or_insert_with(|| ridge_vertices.clone());
}
}
let mut boundary_ridges = 0usize;
#[cfg(debug_assertions)]
let mut internal_ridges = 0usize;
let mut over_shared_ridges = 0usize;
for count in ridge_counts.values() {
match *count {
1 => boundary_ridges += 1,
2 => {
#[cfg(debug_assertions)]
{
internal_ridges += 1;
}
}
_ => over_shared_ridges += 1,
}
}
let mut boundary_components = 0usize;
let mut boundary_subface_nonmanifold = 0usize;
#[cfg(debug_assertions)]
let mut boundary_component_sizes: Vec<usize> = Vec::new();
#[cfg(debug_assertions)]
let mut boundary_degree_min: Option<usize> = None;
#[cfg(debug_assertions)]
let mut boundary_degree_max: Option<usize> = None;
#[cfg(debug_assertions)]
let mut boundary_degree_zero = 0usize;
#[cfg(debug_assertions)]
let mut boundary_degree_one = 0usize;
#[cfg(debug_assertions)]
let mut boundary_degree_two = 0usize;
#[cfg(debug_assertions)]
let mut boundary_degree_over = 0usize;
#[cfg(debug_assertions)]
let want_subface_samples = detail_enabled || log_enabled;
#[cfg(debug_assertions)]
let mut subface_samples: Vec<(u64, usize, Option<VertexKeyBuffer>)> = Vec::new();
if D >= 3 && boundary_ridges > 0 {
let mut boundary_ridge_keys: Vec<u64> = Vec::with_capacity(boundary_ridges);
for (ridge_hash, count) in &ridge_counts {
if *count == 1 {
boundary_ridge_keys.push(*ridge_hash);
}
}
let mut face_to_ridges: FastHashMap<u64, Vec<usize>> = FastHashMap::default();
let mut subface_vertices_map: FastHashMap<u64, VertexKeyBuffer> =
FastHashMap::default();
let mut subface_vertices: VertexKeyBuffer = VertexKeyBuffer::new();
for (idx, ridge_hash) in boundary_ridge_keys.iter().enumerate() {
let Some(ridge_vertices) = ridge_vertices_map.get(ridge_hash) else {
continue;
};
if ridge_vertices.len() < 2 {
continue;
}
for omit in 0..ridge_vertices.len() {
subface_vertices.clear();
for (j, &vk) in ridge_vertices.iter().enumerate() {
if j != omit {
subface_vertices.push(vk);
}
}
subface_vertices.sort_unstable();
let subface_hash = facet_hash_from_sorted_vertices(&subface_vertices);
face_to_ridges.entry(subface_hash).or_default().push(idx);
subface_vertices_map
.entry(subface_hash)
.or_insert_with(|| subface_vertices.iter().copied().collect());
}
}
let mut ridge_adjacency: Vec<Vec<usize>> = vec![Vec::new(); boundary_ridge_keys.len()];
for (subface_hash, ridges) in &face_to_ridges {
if ridges.len() != 2 {
boundary_subface_nonmanifold += 1;
#[cfg(debug_assertions)]
if want_subface_samples && subface_samples.len() < 10 {
subface_samples.push((
*subface_hash,
ridges.len(),
subface_vertices_map.get(subface_hash).cloned(),
));
}
}
#[cfg(not(debug_assertions))]
let _subface_hash = subface_hash;
if ridges.len() < 2 {
continue;
}
for i in 0..ridges.len() {
for j in (i + 1)..ridges.len() {
let a = ridges[i];
let b = ridges[j];
ridge_adjacency[a].push(b);
ridge_adjacency[b].push(a);
}
}
}
#[cfg(debug_assertions)]
for adj in &ridge_adjacency {
let degree = adj.len();
match degree {
0 => boundary_degree_zero += 1,
1 => boundary_degree_one += 1,
2 => boundary_degree_two += 1,
_ => boundary_degree_over += 1,
}
boundary_degree_min =
Some(boundary_degree_min.map_or(degree, |min| min.min(degree)));
boundary_degree_max =
Some(boundary_degree_max.map_or(degree, |max| max.max(degree)));
}
let mut visited = vec![false; boundary_ridge_keys.len()];
for start in 0..boundary_ridge_keys.len() {
if visited[start] {
continue;
}
boundary_components += 1;
let mut stack = vec![start];
visited[start] = true;
#[cfg(debug_assertions)]
let mut component_size = 0usize;
while let Some(r) = stack.pop() {
#[cfg(debug_assertions)]
{
component_size += 1;
}
for &n in &ridge_adjacency[r] {
if !visited[n] {
visited[n] = true;
stack.push(n);
}
}
}
#[cfg(debug_assertions)]
boundary_component_sizes.push(component_size);
}
}
let mut adjacency: Vec<Vec<usize>> = vec![Vec::new(); visible_facets.len()];
for facets in ridge_to_facets.values() {
if facets.len() < 2 {
continue;
}
for i in 0..facets.len() {
for j in (i + 1)..facets.len() {
let a = facets[i];
let b = facets[j];
adjacency[a].push(b);
adjacency[b].push(a);
}
}
}
let mut visited = vec![false; visible_facets.len()];
let mut components = 0usize;
#[cfg(debug_assertions)]
let mut component_sizes: Vec<usize> = Vec::new();
for start in 0..visible_facets.len() {
if visited[start] {
continue;
}
components += 1;
let mut stack = vec![start];
visited[start] = true;
#[cfg(debug_assertions)]
let mut component_size = 0usize;
while let Some(f) = stack.pop() {
#[cfg(debug_assertions)]
{
component_size += 1;
}
for &n in &adjacency[f] {
if !visited[n] {
visited[n] = true;
stack.push(n);
}
}
}
#[cfg(debug_assertions)]
component_sizes.push(component_size);
}
if over_shared_ridges > 0
|| components > 1
|| boundary_ridges == 0
|| (D >= 3 && boundary_components > 1)
|| (D >= 3 && boundary_subface_nonmanifold > 0)
{
#[cfg(debug_assertions)]
if detail_enabled || log_enabled {
let visible_sample = &visible_facets[..visible_facets.len().min(10)];
tracing::debug!(
point = ?point,
visible_facets = visible_facets.len(),
visible_facets_sample = ?visible_sample,
ridge_boundary = boundary_ridges,
ridge_internal = internal_ridges,
ridge_over_shared = over_shared_ridges,
components,
component_sizes = ?component_sizes,
boundary_components,
boundary_component_sizes = ?boundary_component_sizes,
boundary_subface_nonmanifold,
boundary_degree_min,
boundary_degree_max,
boundary_degree_zero,
boundary_degree_one,
boundary_degree_two,
boundary_degree_over,
orientation_opposite_zero,
orientation_point_zero,
degenerate_facets = ?degenerate_facets,
subface_samples = ?subface_samples,
"find_visible_boundary_facets: invalid patch"
);
}
return Err(InsertionError::HullExtension {
reason: HullExtensionReason::InvalidPatch {
details: format!(
"boundary_ridges={boundary_ridges}, ridge_fans={over_shared_ridges}, components={components}, boundary_components={boundary_components}, boundary_subface_nonmanifold={boundary_subface_nonmanifold}",
),
},
});
}
#[cfg(debug_assertions)]
if detail_enabled {
tracing::debug!(
visible_facets = visible_facets.len(),
ridge_boundary = boundary_ridges,
ridge_internal = internal_ridges,
ridge_over_shared = over_shared_ridges,
components,
component_sizes = ?component_sizes,
boundary_components,
boundary_component_sizes = ?boundary_component_sizes,
boundary_subface_nonmanifold,
boundary_degree_min,
boundary_degree_max,
boundary_degree_zero,
boundary_degree_one,
boundary_degree_two,
boundary_degree_over,
orientation_opposite_zero,
orientation_point_zero,
"find_visible_boundary_facets: ridge connectivity summary"
);
}
}
#[cfg(debug_assertions)]
if detail_enabled {
let mixed_orientation =
orientation_opposite_positive > 0 && orientation_opposite_negative > 0;
tracing::debug!(
boundary_facets = boundary_facets_count,
visible_facets = visible_facets.len(),
visible_facets_strict,
visible_facets_weak,
orientation_opposite_positive,
orientation_opposite_negative,
orientation_opposite_zero,
orientation_point_positive,
orientation_point_negative,
orientation_point_zero,
mixed_orientation,
"find_visible_boundary_facets: boundary facet orientation consistency"
);
tracing::debug!(
boundary_facets = boundary_facets_count,
visible_facets = visible_facets.len(),
visible_facets_strict,
visible_facets_weak,
orientation_opposite_positive,
orientation_opposite_negative,
orientation_opposite_zero,
orientation_point_positive,
orientation_point_negative,
orientation_point_zero,
"find_visible_boundary_facets: orientation summary"
);
}
Ok(visible_facets)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::algorithms::flips::{
DelaunayRepairDiagnostics, DelaunayRepairVerificationContext, FlipError, RepairQueueOrder,
};
use crate::core::algorithms::locate::InternalInconsistencySite;
use crate::core::collections::CellKeyBuffer;
use crate::core::tds::GeometricError;
use crate::core::triangulation::TopologyGuarantee;
use crate::geometry::kernel::FastKernel;
use crate::geometry::traits::coordinate::{Coordinate, CoordinateConversionError};
use crate::topology::characteristics::euler::TopologyClassification;
use crate::triangulation::delaunay::DelaunayTriangulation;
use crate::vertex;
use slotmap::KeyData;
fn first_neighbor_pair<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
) -> Option<(CellKey, u8, CellKey, u8)>
where
U: DataType,
V: DataType,
{
for (cell_key, cell) in tds.cells() {
let Some(neighbors) = cell.neighbors() else {
continue;
};
for (facet_idx, &neighbor_opt) in neighbors.iter().enumerate() {
let Some(neighbor_key) = neighbor_opt else {
continue;
};
let Some(neighbor_cell) = tds.cell(neighbor_key) else {
continue;
};
let mirror_idx = cell.mirror_facet_index(facet_idx, neighbor_cell)?;
let facet_idx = u8::try_from(facet_idx).ok()?;
let mirror_idx = u8::try_from(mirror_idx).ok()?;
return Some((cell_key, facet_idx, neighbor_key, mirror_idx));
}
}
None
}
macro_rules! test_fill_cavity {
($dim:literal, $initial_vertices:expr, $new_vertex:expr, $expected_facets:literal) => {
pastey::paste! {
#[test]
fn [<test_fill_cavity_ $dim d>]() {
let vertices = $initial_vertices;
let mut dt = DelaunayTriangulation::<_, (), (), $dim>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let new_vertex = $new_vertex;
let new_vkey = tds.insert_vertex_with_mapping(new_vertex).unwrap();
let cell_key = tds.cell_keys().next().unwrap();
let boundary_facets: Vec<FacetHandle> = (0..=$dim)
.map(|i| FacetHandle::new(cell_key, i))
.collect();
assert_eq!(boundary_facets.len(), $expected_facets);
let new_cells = fill_cavity(tds, new_vkey, &boundary_facets).unwrap();
assert_eq!(new_cells.len(), $expected_facets);
wire_cavity_neighbors(
tds,
&new_cells,
boundary_facets.iter().copied(),
None,
)
.unwrap();
assert!(
tds.is_valid().is_ok(),
"TDS should be valid after cavity filling and neighbor wiring: {:?}",
tds.is_valid().err()
);
for &cell_key in &new_cells {
let cell = tds.cell(cell_key).unwrap();
assert_eq!(
cell.number_of_vertices(),
$dim + 1,
"New cell should have D+1 vertices"
);
}
}
}
};
}
test_fill_cavity!(
2,
vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
],
vertex!([0.5, 0.5]),
3 );
test_fill_cavity!(
3,
vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
],
vertex!([0.25, 0.25, 0.25]),
4 );
test_fill_cavity!(
4,
vec![
vertex!([0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0]),
],
vertex!([0.2, 0.2, 0.2, 0.2]),
5 );
test_fill_cavity!(
5,
vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]),
],
vertex!([0.15, 0.15, 0.15, 0.15, 0.15]),
6 );
#[test]
fn test_fill_cavity_with_invalid_vertex_key() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let invalid_vkey = VertexKey::from(KeyData::from_ffi(u64::MAX));
let cell_key = tds.cell_keys().next().unwrap();
let boundary_facets: Vec<FacetHandle> =
(0..=2).map(|i| FacetHandle::new(cell_key, i)).collect();
let result = fill_cavity(tds, invalid_vkey, &boundary_facets);
assert!(
matches!(
result,
Err(InsertionError::CavityFilling {
reason: CavityFillingError::MissingInsertedVertex { .. },
})
),
"Expected CavityFilling error, got: {result:?}"
);
}
#[test]
fn test_fill_cavity_with_invalid_facet_cell() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap();
let invalid_cell_key = CellKey::from(KeyData::from_ffi(u64::MAX));
let invalid_boundary_facets: Vec<FacetHandle> = (0..=2)
.map(|i| FacetHandle::new(invalid_cell_key, i))
.collect();
let result = fill_cavity(tds, new_vkey, &invalid_boundary_facets);
assert!(result.is_err());
assert!(matches!(
result,
Err(InsertionError::CavityFilling {
reason: CavityFillingError::MissingBoundaryCell { .. },
})
));
}
#[test]
fn test_fill_cavity_with_invalid_facet_index() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap();
let cell_key = tds.cell_keys().next().unwrap();
let original_cell_count = tds.number_of_cells();
let invalid_boundary_facets = vec![FacetHandle::new(cell_key, 3)];
let result = fill_cavity(tds, new_vkey, &invalid_boundary_facets);
assert!(matches!(
result,
Err(InsertionError::CavityFilling {
reason: CavityFillingError::InvalidFacetIndex { .. },
})
));
assert_eq!(
tds.number_of_cells(),
original_cell_count,
"invalid facet index must fail before inserting replacement cells"
);
}
#[test]
fn test_wire_cavity_neighbors_with_invalid_cells() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let mut invalid_cells = CellKeyBuffer::new();
invalid_cells.push(CellKey::from(KeyData::from_ffi(u64::MAX)));
invalid_cells.push(CellKey::from(KeyData::from_ffi(u64::MAX - 1)));
let result = wire_cavity_neighbors(tds, &invalid_cells, [], None);
assert!(result.is_err());
assert!(matches!(
result,
Err(InsertionError::NeighborWiring {
reason: NeighborWiringError::MissingCell { .. },
})
));
}
#[test]
fn test_wire_cavity_neighbors_errors_on_unmatched_external_facet() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v3 = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let v4 = tds.insert_vertex_with_mapping(vertex!([3.0, 2.0])).unwrap();
let v5 = tds.insert_vertex_with_mapping(vertex!([2.0, 3.0])).unwrap();
let new_cell = tds
.insert_cell_with_mapping(Cell::new(vec![v0, v1, v2], None).unwrap())
.unwrap();
let external_cell = tds
.insert_cell_with_mapping(Cell::new(vec![v3, v4, v5], None).unwrap())
.unwrap();
let mut new_cells = CellKeyBuffer::new();
new_cells.push(new_cell);
let err = wire_cavity_neighbors(
&mut tds,
&new_cells,
[FacetHandle::new(external_cell, 0)],
None,
)
.unwrap_err();
assert!(matches!(
err,
InsertionError::NeighborWiring {
reason: NeighborWiringError::ExternalFacetNotFound {
cell_key,
facet_index: 0,
..
},
} if cell_key == external_cell
));
}
#[test]
fn test_wire_cavity_neighbors_errors_on_already_shared_external_facet() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v3 = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]))
.unwrap();
let v4 = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap();
let new_cell_a = tds
.insert_cell_with_mapping(Cell::new(vec![v0, v1, v2], None).unwrap())
.unwrap();
let new_cell_b = tds
.insert_cell_with_mapping(Cell::new(vec![v1, v0, v3], None).unwrap())
.unwrap();
let external_cell = tds
.insert_cell_bypassing_topology_checks_for_test(
Cell::new(vec![v0, v1, v4], None).unwrap(),
)
.unwrap();
let mut new_cells = CellKeyBuffer::new();
new_cells.push(new_cell_a);
new_cells.push(new_cell_b);
let err = wire_cavity_neighbors(
&mut tds,
&new_cells,
[FacetHandle::new(external_cell, 2)],
None,
)
.unwrap_err();
assert!(matches!(
err,
InsertionError::NeighborWiring {
reason: NeighborWiringError::ExternalFacetAlreadyShared {
cell_key,
facet_index: 2,
existing_incidents: 2,
..
},
} if cell_key == external_cell
));
}
#[test]
fn test_external_facets_for_boundary_errors_on_missing_internal_cell() {
let tds: Tds<f64, (), (), 2> = Tds::empty();
let missing_cell = CellKey::from(KeyData::from_ffi(u64::MAX));
let mut internal_cells = CellKeyBuffer::new();
internal_cells.push(missing_cell);
let boundary_facets = [FacetHandle::new(missing_cell, 0)];
let err =
external_facets_for_boundary(&tds, &internal_cells, &boundary_facets).unwrap_err();
assert!(matches!(
err,
InsertionError::NeighborWiring {
reason: NeighborWiringError::MissingCell { cell_key },
} if cell_key == missing_cell
));
}
#[test]
fn test_external_facets_for_boundary_errors_on_missing_neighbor_cell() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let cell_key = tds
.insert_cell_with_mapping(Cell::new(vec![v0, v1, v2], None).unwrap())
.unwrap();
let missing_neighbor = CellKey::from(KeyData::from_ffi(u64::MAX - 1));
tds.cell_mut(cell_key).unwrap().neighbors =
Some(vec![Some(missing_neighbor), None, None].into());
let mut internal_cells = CellKeyBuffer::new();
internal_cells.push(cell_key);
let boundary_facets = [FacetHandle::new(cell_key, 0)];
let err =
external_facets_for_boundary(&tds, &internal_cells, &boundary_facets).unwrap_err();
assert!(matches!(
err,
InsertionError::NeighborWiring {
reason: NeighborWiringError::MissingCell { cell_key },
} if cell_key == missing_neighbor
));
}
#[test]
fn test_external_facets_for_boundary_finds_shared_edge_only() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v0 = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v1 = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v2 = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v3 = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let c1 = tds
.insert_cell_with_mapping(Cell::new(vec![v0, v1, v2], None).unwrap())
.unwrap();
let c2 = tds
.insert_cell_with_mapping(Cell::new(vec![v1, v0, v3], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let mut internal_cells = CellKeyBuffer::new();
internal_cells.push(c1);
let boundary_facets: Vec<FacetHandle> = (0..=2).map(|i| FacetHandle::new(c1, i)).collect();
let external_facets =
external_facets_for_boundary(&tds, &internal_cells, &boundary_facets).unwrap();
assert_eq!(external_facets.len(), 1);
let external = external_facets[0];
assert_eq!(external.cell_key(), c2);
let cell = tds.cell(external.cell_key()).unwrap();
let facet_idx = usize::from(external.facet_index());
let mut edge: SmallBuffer<VertexKey, 2> = SmallBuffer::new();
for (i, &vkey) in cell.vertices().iter().enumerate() {
if i != facet_idx {
edge.push(vkey);
}
}
assert_eq!(edge.len(), 2);
assert!(edge.contains(&v0));
assert!(edge.contains(&v1));
}
#[test]
fn test_fill_cavity_with_empty_boundary_facets() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap();
let empty_facets: Vec<FacetHandle> = vec![];
let result = fill_cavity(tds, new_vkey, &empty_facets);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_fill_cavity_errors_on_boundary_cell_wrong_vertex_count() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let new_vkey = tds.insert_vertex_with_mapping(vertex!([0.5, 0.5])).unwrap();
let cell_key = tds.cell_keys().next().unwrap();
let extra_vkey = tds.cell(cell_key).unwrap().vertices()[0];
tds.cell_mut(cell_key).unwrap().push_vertex_key(extra_vkey);
let boundary_facets = vec![FacetHandle::new(cell_key, 0)];
let err = fill_cavity(tds, new_vkey, &boundary_facets).unwrap_err();
assert!(matches!(
err,
InsertionError::CavityFilling {
reason: CavityFillingError::WrongCellArity { .. },
}
));
}
#[test]
fn test_wire_cavity_neighbors_reports_non_manifold_topology() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]))
.unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let c1 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let c2 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let c3 = tds
.insert_cell_bypassing_topology_checks_for_test(
Cell::new(vec![v_a, v_b, v_e], None).unwrap(),
)
.unwrap();
let mut new_cells = CellKeyBuffer::new();
new_cells.push(c1);
new_cells.push(c2);
new_cells.push(c3);
let err = wire_cavity_neighbors(&mut tds, &new_cells, [], None).unwrap_err();
assert!(matches!(
err,
InsertionError::NonManifoldTopology { cell_count: 3, .. }
));
}
#[test]
fn test_wire_cavity_neighbors_errors_on_wrong_cell_arity() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let cell_key = tds.cell_keys().next().unwrap();
let vkey0 = tds.cell(cell_key).unwrap().vertices()[0];
{
let cell = tds.cell_mut(cell_key).unwrap();
cell.push_vertex_key(vkey0);
}
let mut new_cells = CellKeyBuffer::new();
new_cells.push(cell_key);
let err = wire_cavity_neighbors(tds, &new_cells, [], None).unwrap_err();
assert!(matches!(
err,
InsertionError::NeighborWiring {
reason: NeighborWiringError::WrongCellArity {
cell_key: key,
expected: 3,
found: 4,
},
} if key == cell_key
));
}
#[test]
fn test_neighbor_wiring_error_facet_index_overflow_variant_remains_distinct() {
let err = NeighborWiringError::FacetIndexOverflow {
facet_index: usize::from(u8::MAX) + 1,
max: u8::MAX,
};
assert!(matches!(
err,
NeighborWiringError::FacetIndexOverflow {
facet_index,
max: u8::MAX,
} if facet_index == usize::from(u8::MAX) + 1
));
}
#[test]
fn test_neighbor_wiring_error_wrong_cell_arity_reports_expected_and_found() {
let cell_key = CellKey::from(KeyData::from_ffi(42));
let err = NeighborWiringError::WrongCellArity {
cell_key,
expected: 3,
found: 2,
};
assert!(matches!(
err,
NeighborWiringError::WrongCellArity {
cell_key: key,
expected: 3,
found: 2,
} if key == cell_key
));
assert!(err.to_string().contains("expected 3"));
assert!(err.to_string().contains("has 2 vertices"));
}
#[test]
fn test_neighbor_rebuild_error_unexpected_preserves_source_summary() {
let source = InsertionError::DuplicateCoordinates {
coordinates: "[0.0, 0.0]".to_string(),
};
let err = NeighborRebuildError::from(source.clone());
match err {
NeighborRebuildError::Unexpected { source: summary } => {
assert_eq!(summary.kind, InsertionErrorKind::DuplicateCoordinates);
assert_eq!(summary.source_kind, None);
assert_eq!(summary.message, source.to_string());
}
other => panic!("expected unexpected neighbor repair error, got {other:?}"),
}
}
fn sample_delaunay_repair_diagnostics_for_summary() -> 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_delaunay_repair_error_summary_covers_all_kinds() {
let cases = [
(
DelaunayRepairError::NonConvergent {
max_flips: 42,
diagnostics: Box::new(sample_delaunay_repair_diagnostics_for_summary()),
},
DelaunayRepairErrorKind::NonConvergent,
),
(
DelaunayRepairError::PostconditionFailed {
message: "remaining non-Delaunay facet".to_string(),
},
DelaunayRepairErrorKind::PostconditionFailed,
),
(
DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DegenerateCell),
},
DelaunayRepairErrorKind::VerificationFailed,
),
(
DelaunayRepairError::OrientationCanonicalizationFailed {
message: "orientation pass failed".to_string(),
},
DelaunayRepairErrorKind::OrientationCanonicalizationFailed,
),
(
DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "repair requires PL topology",
},
DelaunayRepairErrorKind::InvalidTopology,
),
(
DelaunayRepairError::HeuristicRebuildFailed {
message: "fallback rebuild failed".to_string(),
},
DelaunayRepairErrorKind::HeuristicRebuildFailed,
),
(
DelaunayRepairError::Flip(FlipError::DegenerateCell),
DelaunayRepairErrorKind::Flip,
),
];
for (source, expected_kind) in cases {
let summary = DelaunayRepairErrorSummary::from(&source);
assert_eq!(summary.kind, expected_kind);
assert_eq!(summary.message, source.to_string());
}
}
#[test]
fn test_insertion_error_summary_preserves_nested_source_kind() {
let cases = [
(
InsertionError::TopologyValidation(TdsError::InconsistentDataStructure {
message: "dangling neighbor".to_string(),
}),
InsertionErrorKind::TopologyValidation,
Some(InsertionErrorSourceKind::Tds(
TdsErrorKind::InconsistentDataStructure,
)),
),
(
InsertionError::TopologyValidationFailed {
message: "post-insertion topology validation failed".to_string(),
source: TriangulationValidationError::Disconnected { cell_count: 2 },
},
InsertionErrorKind::TopologyValidationFailed,
Some(InsertionErrorSourceKind::Triangulation(
TriangulationValidationErrorKind::Disconnected,
)),
),
(
InsertionError::DelaunayValidationFailed {
source: DelaunayTriangulationValidationError::VerificationFailed {
message: "non-Delaunay facet".to_string(),
},
},
InsertionErrorKind::DelaunayValidationFailed,
Some(InsertionErrorSourceKind::Delaunay(
DelaunayValidationErrorKind::VerificationFailed,
)),
),
(
InsertionError::DelaunayRepairFailed {
source: Box::new(DelaunayRepairError::HeuristicRebuildFailed {
message: "rebuild could not restore topology".to_string(),
}),
context: DelaunayRepairFailureContext::LocalRepair,
},
InsertionErrorKind::DelaunayRepairFailed,
Some(InsertionErrorSourceKind::DelaunayRepair(
DelaunayRepairErrorKind::HeuristicRebuildFailed,
)),
),
];
for (source, expected_kind, expected_source_kind) in cases {
let summary = InsertionErrorSummary::from(source.clone());
assert_eq!(summary.kind, expected_kind);
assert_eq!(summary.source_kind, expected_source_kind);
assert_eq!(summary.message, source.to_string());
}
}
#[test]
fn test_insertion_error_summary_retryability_covers_tds_source_kinds() {
let geometric = InsertionErrorSummary {
kind: InsertionErrorKind::TopologyValidation,
source_kind: Some(InsertionErrorSourceKind::Tds(TdsErrorKind::Geometric)),
retryable: true,
message: String::new(),
};
let orientation = InsertionErrorSummary {
kind: InsertionErrorKind::TopologyValidation,
source_kind: Some(InsertionErrorSourceKind::Tds(
TdsErrorKind::OrientationViolation,
)),
retryable: true,
message: String::new(),
};
let structural = InsertionErrorSummary {
kind: InsertionErrorKind::TopologyValidation,
source_kind: Some(InsertionErrorSourceKind::Tds(
TdsErrorKind::InconsistentDataStructure,
)),
retryable: false,
message: String::new(),
};
assert!(geometric.is_retryable());
assert!(orientation.is_retryable());
assert!(!structural.is_retryable());
}
#[test]
fn test_robust_fallback_context_preserves_initial_repair_summary() {
let initial = DelaunayRepairError::PostconditionFailed {
message: "local predicate violation".to_string(),
};
let context = DelaunayRepairFailureContext::LocalRepairRobustFallback {
initial: DelaunayRepairErrorSummary::from(&initial),
};
let msg = context.to_string();
assert!(msg.contains("local repair failed"));
assert!(msg.contains("local predicate violation"));
assert!(msg.contains("robust fallback also failed"));
}
#[test]
fn test_cavity_filling_retryability_inspects_construction_payloads() {
let geometry_failure = TdsValidationFailure::Geometric {
source: GeometricError::DegenerateOrientation {
message: "near-degenerate".to_string(),
},
};
let structural_failure = TdsValidationFailure::InconsistentDataStructure {
message: "dangling link".to_string(),
};
let facet_failure = TdsValidationFailure::FacetSharingViolation {
facet_key: 0x1234,
existing_incident_count: 2,
attempted_incident_count: 3,
max_incident_count: 2,
candidate_cell_uuid: uuid::Uuid::nil(),
candidate_facet_index: 1,
};
let facet_message = facet_failure.to_string();
assert!(facet_message.contains("exceeds incident-cell limit"));
assert!(!facet_message.contains("after inserting candidate cell"));
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::CellInsertion {
reason: TdsConstructionFailure::Validation {
reason: geometry_failure.clone(),
},
},
}
.is_retryable()
);
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::CellInsertion {
reason: TdsConstructionFailure::Validation {
reason: facet_failure,
},
},
}
.is_retryable()
);
assert!(
!InsertionError::CavityFilling {
reason: CavityFillingError::CellInsertion {
reason: TdsConstructionFailure::Validation {
reason: structural_failure,
},
},
}
.is_retryable()
);
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::InitialSimplexConstruction {
reason: InitialSimplexConstructionError::TdsValidation {
source: geometry_failure,
},
},
}
.is_retryable()
);
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::InitialSimplexConstruction {
reason: InitialSimplexConstructionError::GeometricDegeneracy {
message: "coplanar bootstrap".to_string(),
},
},
}
.is_retryable()
);
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::NeighborRebuild {
reason: NeighborRebuildError::from(InsertionError::TopologyValidationFailed {
message: "local topology validation failed".to_string(),
source: TriangulationValidationError::ManifoldFacetMultiplicity {
facet_key: 0x1234,
cell_count: 3,
},
}),
},
}
.is_retryable()
);
assert!(
!InsertionError::CavityFilling {
reason: CavityFillingError::NeighborRebuild {
reason: NeighborRebuildError::from(InsertionError::TopologyValidationFailed {
message: "structural topology validation failed".to_string(),
source: TriangulationValidationError::Disconnected { cell_count: 2 },
}),
},
}
.is_retryable()
);
}
#[test]
#[expect(
clippy::too_many_lines,
reason = "Exhaustive coverage of all InsertionError retryability classifications"
)]
fn test_insertion_error_retryable() {
assert!(
InsertionError::NonManifoldTopology {
facet_hash: 0x12345,
cell_count: 3
}
.is_retryable()
);
assert!(
!InsertionError::TopologyValidation(TdsError::InconsistentDataStructure {
message: "test".to_string()
})
.is_retryable()
);
assert!(
InsertionError::TopologyValidation(TdsError::Geometric(
GeometricError::DegenerateOrientation {
message: "test".to_string()
}
))
.is_retryable()
);
assert!(
InsertionError::TopologyValidation(TdsError::Geometric(
GeometricError::NegativeOrientation {
message: "test".to_string()
}
))
.is_retryable()
);
assert!(
InsertionError::TopologyValidation(TdsError::OrientationViolation {
cell1_key: CellKey::from(KeyData::from_ffi(1)),
cell1_uuid: uuid::Uuid::nil(),
cell2_key: CellKey::from(KeyData::from_ffi(2)),
cell2_uuid: uuid::Uuid::nil(),
cell1_facet_index: 0,
cell2_facet_index: 1,
facet_vertices: vec![],
cell2_facet_vertices: vec![],
observed_odd_permutation: true,
expected_odd_permutation: false,
})
.is_retryable()
);
assert!(
InsertionError::TopologyValidation(TdsError::FacetSharingViolation {
facet_key: 0x1234,
existing_incident_count: 2,
attempted_incident_count: 3,
max_incident_count: 2,
candidate_cell_uuid: uuid::Uuid::nil(),
candidate_facet_index: 1,
})
.is_retryable()
);
assert!(
InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: TriangulationValidationError::IsolatedVertex {
vertex_key: VertexKey::from(KeyData::from_ffi(1)),
vertex_uuid: uuid::Uuid::nil(),
},
}
.is_retryable()
);
assert!(
!InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: TriangulationValidationError::EulerCharacteristicMismatch {
computed: 3,
expected: 2,
classification: TopologyClassification::Ball(3),
},
}
.is_retryable()
);
let geometry_l3 = TriangulationValidationError::ManifoldFacetMultiplicity {
facet_key: 0x12345,
cell_count: 3,
};
assert!(
InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: geometry_l3,
}
.is_retryable()
);
assert!(
InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: TriangulationValidationError::BoundaryRidgeMultiplicity {
ridge_key: 0xab,
boundary_facet_count: 3,
},
}
.is_retryable()
);
assert!(
InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: TriangulationValidationError::RidgeLinkNotManifold {
ridge_key: 0xcd,
link_vertex_count: 4,
link_edge_count: 5,
max_degree: 3,
degree_one_vertices: 1,
connected: false,
},
}
.is_retryable()
);
assert!(
InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: TriangulationValidationError::VertexLinkNotManifold {
vertex_key: VertexKey::from(KeyData::from_ffi(1)),
link_vertex_count: 3,
link_cell_count: 4,
boundary_facet_count: 1,
max_degree: 2,
connected: false,
interior_vertex: true,
},
}
.is_retryable()
);
assert!(
!InsertionError::TopologyValidationFailed {
message: "test".to_string(),
source: TriangulationValidationError::EulerCharacteristicMismatch {
computed: 3,
expected: 2,
classification: TopologyClassification::Ball(3),
},
}
.is_retryable()
);
assert!(
!InsertionError::NeighborWiring {
reason: NeighborWiringError::MissingCell {
cell_key: CellKey::from(KeyData::from_ffi(u64::MAX)),
}
}
.is_retryable()
);
assert!(
InsertionError::ConflictRegion(ConflictError::NonManifoldFacet {
facet_hash: 0x12345_u64,
cell_count: 3,
})
.is_retryable()
);
assert!(
InsertionError::ConflictRegion(ConflictError::RidgeFan {
facet_count: 3,
ridge_vertex_count: 2,
extra_cells: vec![],
})
.is_retryable()
);
assert!(
InsertionError::ConflictRegion(ConflictError::RidgeFan {
facet_count: 3,
ridge_vertex_count: 2,
extra_cells: vec![CellKey::from(KeyData::from_ffi(1))],
})
.is_retryable()
);
assert!(
!InsertionError::ConflictRegion(ConflictError::InvalidStartCell {
cell_key: CellKey::from(KeyData::from_ffi(u64::MAX)),
})
.is_retryable()
);
assert!(
!InsertionError::ConflictRegion(ConflictError::PredicateError {
source: CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: "test".to_string(),
from_type: "f64",
to_type: "f64",
},
})
.is_retryable()
);
assert!(
!InsertionError::ConflictRegion(ConflictError::CellDataAccessFailed {
cell_key: CellKey::from(KeyData::from_ffi(1)),
message: "test".to_string(),
})
.is_retryable()
);
assert!(
!InsertionError::ConflictRegion(ConflictError::InternalInconsistency {
site: InternalInconsistencySite::RidgeFanExtraFacetOutOfBounds {
index: 7,
boundary_facets_len: 5,
extra_facets_len: 3,
},
})
.is_retryable()
);
assert!(
!InsertionError::ConflictRegion(ConflictError::InternalInconsistency {
site: InternalInconsistencySite::OpenBoundaryMissingFirstFacet {
first_facet: 4,
boundary_facets_len: 2,
facet_count: 1,
ridge_vertex_count: 2,
},
})
.is_retryable()
);
assert!(
!InsertionError::ConflictRegion(ConflictError::InternalInconsistency {
site: InternalInconsistencySite::RidgeInfoMissingSecondFacet {
first_facet: 0,
boundary_facets_len: 2,
ridge_vertex_count: 2,
},
})
.is_retryable()
);
assert!(
InsertionError::ConflictRegion(ConflictError::DisconnectedBoundary {
visited: 1,
total: 3,
disconnected_cells: vec![],
})
.is_retryable()
);
assert!(
InsertionError::ConflictRegion(ConflictError::OpenBoundary {
facet_count: 1,
ridge_vertex_count: 2,
open_cell: CellKey::from(KeyData::from_ffi(1)),
})
.is_retryable()
);
assert!(
!InsertionError::DuplicateUuid {
entity: EntityKind::Vertex,
uuid: uuid::Uuid::new_v4(),
}
.is_retryable()
);
assert!(
!InsertionError::DuplicateCoordinates {
coordinates: "0,0,0".to_string()
}
.is_retryable()
);
assert!(
!InsertionError::CavityFilling {
reason: CavityFillingError::EmptyFanTriangulation,
}
.is_retryable()
);
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::InvalidFacetSharingAfterRepair {
stage: CavityRepairStage::PrimaryInsertion,
},
}
.is_retryable()
);
assert!(
InsertionError::CavityFilling {
reason: CavityFillingError::NeighborRebuild {
reason: NeighborRebuildError::NonManifoldTopology {
facet_hash: 0x123,
cell_count: 3,
},
},
}
.is_retryable()
);
assert!(
InsertionError::HullExtension {
reason: HullExtensionReason::NoVisibleFacets
}
.is_retryable()
);
assert!(
InsertionError::HullExtension {
reason: HullExtensionReason::InvalidPatch {
details: "test".to_string(),
}
}
.is_retryable()
);
assert!(
!InsertionError::HullExtension {
reason: HullExtensionReason::Other {
message: "Failed to get boundary facets: test".to_string()
}
}
.is_retryable()
);
assert!(
!InsertionError::HullExtension {
reason: HullExtensionReason::PredicateFailed(
CoordinateConversionError::ConversionFailed {
coordinate_index: 0,
coordinate_value: "test".to_string(),
from_type: "f64",
to_type: "f64",
}
)
}
.is_retryable()
);
assert!(
!InsertionError::HullExtension {
reason: HullExtensionReason::Tds(TdsError::InconsistentDataStructure {
message: "test".to_string(),
})
}
.is_retryable()
);
}
macro_rules! test_repair_neighbors {
($dim:literal, $initial_vertices:expr) => {
pastey::paste! {
#[test]
fn [<test_repair_neighbor_pointers_ $dim d>]() {
let vertices = $initial_vertices;
let mut dt = DelaunayTriangulation::<_, (), (), $dim>::new(&vertices).unwrap();
let tds = dt.tds_mut();
for (_, cell) in tds.cells() {
if let Some(neighbors) = cell.neighbors() {
for &neighbor_opt in neighbors {
if let Some(neighbor_key) = neighbor_opt {
assert!(tds.contains_cell(neighbor_key), "Neighbor should exist");
}
}
}
}
let fixed = repair_neighbor_pointers(tds).unwrap();
assert_eq!(fixed, 0);
for (_, cell) in tds.cells() {
if let Some(neighbors) = cell.neighbors() {
for &neighbor_opt in neighbors {
if let Some(neighbor_key) = neighbor_opt {
assert!(tds.contains_cell(neighbor_key), "Neighbor should still exist after repair");
}
}
}
}
}
}
};
}
test_repair_neighbors!(
2,
vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([0.5, 0.5]),
]
);
test_repair_neighbors!(
3,
vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.25, 0.25, 0.25]),
]
);
test_repair_neighbors!(
4,
vec![
vertex!([0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0]),
vertex!([0.2, 0.2, 0.2, 0.2]),
]
);
test_repair_neighbors!(
5,
vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]),
vertex!([0.15, 0.15, 0.15, 0.15, 0.15]),
]
);
#[test]
fn test_repair_neighbor_pointers_reconstructs_missing_neighbors() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.1]), ];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
tds.clear_all_neighbors();
assert!(tds.cells().all(|(_, c)| c.neighbors().is_none()));
let fixed = repair_neighbor_pointers(tds).unwrap();
assert!(
fixed > 0,
"Expected at least one neighbor pointer to be repaired"
);
let any_internal_neighbor = tds
.cells()
.any(|(_, c)| c.neighbors().is_some_and(|n| n.iter().any(Option::is_some)));
assert!(
any_internal_neighbor,
"Expected at least one internal neighbor after repair"
);
assert!(tds.is_valid().is_ok());
}
macro_rules! test_repair_neighbor_pointers_local_dimensions {
(
$dim:literal,
$initial_vertices:expr,
$shared_facet_vertices:expr,
$opposite_vertices:expr
) => {
pastey::paste! {
#[test]
fn [<test_repair_neighbor_pointers_local_reconstructs_missing_slot_ $dim d>]() {
let vertices = $initial_vertices;
let mut dt = DelaunayTriangulation::<_, (), (), $dim>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let (cell_key, facet_idx, neighbor_key, _) =
first_neighbor_pair(tds).expect("test triangulation should have adjacent cells");
set_neighbor(tds, cell_key, facet_idx, None).unwrap();
let repaired = repair_neighbor_pointers_local(tds, &[cell_key], Some(&[neighbor_key]))
.expect("local repair should reconstruct the missing slot");
assert_eq!(repaired, 1);
let cell = tds.cell(cell_key).unwrap();
assert_eq!(
cell.neighbors()
.and_then(|neighbors| neighbors.get(usize::from(facet_idx)))
.copied()
.flatten(),
Some(neighbor_key)
);
assert!(tds.is_valid().is_ok());
}
#[test]
fn [<test_repair_neighbor_pointers_local_reports_non_manifold_incidence_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let shared_vertices = $shared_facet_vertices;
let opposite_vertices = $opposite_vertices;
let mut shared_keys = Vec::new();
for vertex in shared_vertices {
shared_keys.push(tds.insert_vertex_with_mapping(vertex).unwrap());
}
let mut cell_keys = Vec::new();
for (idx, vertex) in opposite_vertices.into_iter().enumerate() {
let opposite_key = tds.insert_vertex_with_mapping(vertex).unwrap();
let mut vertices = shared_keys.clone();
vertices.push(opposite_key);
let cell = Cell::new(vertices, None).unwrap();
let cell_key = if idx < 2 {
tds.insert_cell_with_mapping(cell)
} else {
tds.insert_cell_bypassing_topology_checks_for_test(cell)
}
.unwrap();
cell_keys.push(cell_key);
}
let err = repair_neighbor_pointers_local(&mut tds, &cell_keys, None).unwrap_err();
assert!(matches!(
err,
InsertionError::NonManifoldTopology { cell_count: 3, .. }
));
}
}
};
}
test_repair_neighbor_pointers_local_dimensions!(
2,
vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.1]),
],
vec![vertex!([0.0, 0.0]), vertex!([1.0, 0.0])],
vec![
vertex!([0.0, 1.0]),
vertex!([0.0, -1.0]),
vertex!([2.0, 0.0]),
]
);
test_repair_neighbor_pointers_local_dimensions!(
3,
vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.25, 0.25, 0.25]),
],
vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
],
vec![
vertex!([0.0, 0.0, 1.0]),
vertex!([0.0, 0.0, -1.0]),
vertex!([1.0, 1.0, 1.0]),
]
);
test_repair_neighbor_pointers_local_dimensions!(
4,
vec![
vertex!([0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0]),
vertex!([0.2, 0.2, 0.2, 0.2]),
],
vec![
vertex!([0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0]),
],
vec![
vertex!([0.0, 0.0, 0.0, 1.0]),
vertex!([0.0, 0.0, 0.0, -1.0]),
vertex!([1.0, 1.0, 1.0, 1.0]),
]
);
test_repair_neighbor_pointers_local_dimensions!(
5,
vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]),
vertex!([0.15, 0.15, 0.15, 0.15, 0.15]),
],
vec![
vertex!([0.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 1.0, 0.0, 0.0]),
vertex!([0.0, 0.0, 0.0, 1.0, 0.0]),
],
vec![
vertex!([0.0, 0.0, 0.0, 0.0, 1.0]),
vertex!([0.0, 0.0, 0.0, 0.0, -1.0]),
vertex!([1.0, 1.0, 1.0, 1.0, 1.0]),
]
);
#[test]
fn test_repair_neighbor_pointers_local_replaces_stale_neighbor_slot() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.1]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let (cell_key, facet_idx, neighbor_key, _) =
first_neighbor_pair(tds).expect("test triangulation should have adjacent cells");
let stale_neighbor = CellKey::from(KeyData::from_ffi(u64::MAX - 7));
assert!(!tds.contains_cell(stale_neighbor));
set_neighbor(tds, cell_key, facet_idx, Some(stale_neighbor)).unwrap();
let repaired = repair_neighbor_pointers_local(tds, &[cell_key], Some(&[neighbor_key]))
.expect("local repair should replace a stale neighbor slot");
assert_eq!(repaired, 1);
let cell = tds.cell(cell_key).unwrap();
assert_eq!(
cell.neighbors()
.and_then(|neighbors| neighbors.get(usize::from(facet_idx)))
.copied()
.flatten(),
Some(neighbor_key)
);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_repair_neighbor_pointers_local_replaces_wrong_live_neighbor_slot() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let v_c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let v_d = tds
.insert_vertex_with_mapping(vertex!([0.0, -1.0]))
.unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap();
let v_f = tds.insert_vertex_with_mapping(vertex!([2.0, 1.0])).unwrap();
let c1 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let c2 = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let wrong_live_neighbor = tds
.insert_cell_with_mapping(Cell::new(vec![v_a, v_e, v_f], None).unwrap())
.unwrap();
let shared_facet_idx = 2usize;
let shared_facet_idx_u8 = u8::try_from(shared_facet_idx).unwrap();
assert!(
tds.cell(c1)
.unwrap()
.mirror_facet_index(shared_facet_idx, tds.cell(wrong_live_neighbor).unwrap())
.is_none()
);
set_neighbor(&mut tds, c1, shared_facet_idx_u8, Some(wrong_live_neighbor)).unwrap();
set_neighbor(&mut tds, c2, shared_facet_idx_u8, Some(c1)).unwrap();
let repaired = repair_neighbor_pointers_local(&mut tds, &[c1], Some(&[c2]))
.expect("local repair should replace a live neighbor across the wrong facet");
assert_eq!(repaired, 1);
let cell = tds.cell(c1).unwrap();
assert_eq!(
cell.neighbors()
.and_then(|neighbors| neighbors.get(shared_facet_idx))
.copied()
.flatten(),
Some(c2)
);
}
#[test]
fn test_repair_neighbor_pointers_local_does_not_scan_unseeded_cells() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.1]),
vertex!([0.5, 0.35]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let (cell_key, facet_idx, _neighbor_key, _) =
first_neighbor_pair(tds).expect("test triangulation should have adjacent cells");
set_neighbor(tds, cell_key, facet_idx, None).unwrap();
let repaired = repair_neighbor_pointers_local(tds, &[], None)
.expect("local repair should ignore unseeded damage");
assert_eq!(repaired, 0);
let cell = tds.cell(cell_key).unwrap();
assert_eq!(
cell.neighbors()
.and_then(|neighbors| neighbors.get(usize::from(facet_idx)))
.copied()
.flatten(),
None
);
assert!(repair_neighbor_pointers(tds).unwrap() > 0);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_extend_hull_adds_cells_for_exterior_vertex() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let kernel = FastKernel::<f64>::new();
let p = Point::new([2.0, 2.0]);
let new_vkey = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let new_cells = extend_hull(tds, &kernel, new_vkey, &p).unwrap();
assert!(!new_cells.is_empty());
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_extend_hull_errors_when_no_visible_facets() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let tds = dt.tds_mut();
let kernel = FastKernel::<f64>::new();
let p = Point::new([0.25, 0.25]); let new_vkey = tds
.insert_vertex_with_mapping(vertex!([0.25, 0.25]))
.unwrap();
let err = extend_hull(tds, &kernel, new_vkey, &p).unwrap_err();
assert!(matches!(
err,
InsertionError::HullExtension {
reason: HullExtensionReason::NoVisibleFacets
}
));
}
#[test]
fn test_find_boundary_edge_split_facet_on_segment_2d() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let point = Point::new([0.5, 0.0]);
let facet = find_boundary_edge_split_facet(dt.tds(), &point).unwrap();
assert!(facet.is_some());
}
#[test]
fn test_find_boundary_edge_split_facet_off_segment_returns_none_2d() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let point = Point::new([2.0, 0.0]);
let facet = find_boundary_edge_split_facet(dt.tds(), &point).unwrap();
assert!(facet.is_none());
}
#[test]
fn test_find_visible_boundary_facets_inside_returns_empty_2d() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let kernel = FastKernel::<f64>::new();
let point = Point::new([0.2, 0.2]);
let visible = find_visible_boundary_facets(dt.tds(), &kernel, &point).unwrap();
assert!(visible.is_empty());
}
#[test]
fn test_find_visible_boundary_facets_outside_returns_non_empty_2d() {
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let dt = DelaunayTriangulation::<_, (), (), 2>::new(&vertices).unwrap();
let kernel = FastKernel::<f64>::new();
let point = Point::new([3.0, 3.0]);
let visible = find_visible_boundary_facets(dt.tds(), &kernel, &point).unwrap();
assert!(!visible.is_empty());
assert!(visible.len() <= 3);
}
#[test]
fn test_set_neighbor_errors_on_missing_cell() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let missing = CellKey::from(KeyData::from_ffi(u64::MAX));
let err = set_neighbor(&mut tds, missing, 0, None).unwrap_err();
assert!(matches!(
err,
InsertionError::NeighborWiring {
reason: NeighborWiringError::MissingCell { .. },
}
));
}
#[test]
fn test_external_facets_for_boundary_empty_inputs_returns_empty() {
let tds: Tds<f64, (), (), 2> = Tds::empty();
let internal_cells = CellKeyBuffer::new();
let boundary_facets: Vec<FacetHandle> = Vec::new();
let external =
external_facets_for_boundary(&tds, &internal_cells, &boundary_facets).unwrap();
assert!(external.is_empty());
}
#[test]
fn test_repair_neighbor_pointers_empty_tds_returns_zero() {
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let fixed = repair_neighbor_pointers(&mut tds).unwrap();
assert_eq!(fixed, 0);
}
}