#![forbid(unsafe_code)]
use crate::core::algorithms::incremental_insertion::{
CavityFillingError, HullExtensionReason, InsertionError, NeighborWiringError,
TdsConstructionFailure, TdsValidationFailure, external_facets_for_boundary,
wire_cavity_neighbors,
};
use crate::core::algorithms::locate::{ConflictError, LocateError, extract_cavity_boundary};
use crate::core::collections::{
FastHashMap, FastHashSet, FastHasher, MAX_PRACTICAL_DIMENSION_SIZE, PeriodicOffsetBuffer,
SimplexKeyBuffer, SmallBuffer,
};
use crate::core::edge::EdgeKey;
use crate::core::facet::{AllFacetsIter, FacetError, FacetHandle, facet_key_from_vertices};
use crate::core::operations::TopologicalOperation;
use crate::core::simplex::{NeighborSlot, Simplex, SimplexValidationError};
use crate::core::tds::{EntityKind, NeighborValidationError, SimplexKey, Tds, VertexKey};
use crate::core::traits::data_type::DataType;
use crate::core::triangulation::Triangulation;
use crate::core::util::stable_hash_u64_slice;
use crate::core::validation::{TopologyGuarantee, TriangulationValidationError};
use crate::core::vertex::Vertex;
use crate::geometry::kernel::Kernel;
use crate::geometry::point::Point;
#[cfg(debug_assertions)]
use crate::geometry::predicates::simplex_orientation;
use crate::geometry::predicates::{Orientation, simplex_orientation_fast_filter_sign};
use crate::geometry::robust_predicates::robust_orientation;
use crate::geometry::traits::coordinate::{
Coordinate, CoordinateConversionError, CoordinateScalar,
};
use crate::topology::traits::global_topology_model::{
GlobalTopologyModel, GlobalTopologyModelAdapter,
};
use crate::topology::traits::topological_space::GlobalTopology;
use crate::validation::DelaunayTriangulationValidationError;
use slotmap::Key;
use std::borrow::Cow;
use std::collections::VecDeque;
use std::env;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::time::{Duration, Instant};
use thiserror::Error;
type VertexKeyList = SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>;
type RemovedSimplexVertexSnapshot = SmallBuffer<VertexKeyList, MAX_PRACTICAL_DIMENSION_SIZE>;
type ReplacementPeriodicOffsets<const D: usize> =
SmallBuffer<Option<PeriodicOffsetBuffer<D>>, MAX_PRACTICAL_DIMENSION_SIZE>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BistellarFlipKind {
k: usize,
pub d: usize,
}
fn repair_delaunay_with_flips_k2_k3_attempt<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
config: &RepairAttemptConfig,
) -> Result<RepairAttemptOutcome, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
repair_delaunay_with_flips_k2_k3_attempt_timed(tds, kernel, seed_simplices, config, None)
}
#[expect(
clippy::too_many_lines,
reason = "Repair loop contains inline tracing and queue handling for diagnostics"
)]
fn repair_delaunay_with_flips_k2_k3_attempt_timed<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
config: &RepairAttemptConfig,
mut timing: Option<&mut LocalRepairPhaseTiming>,
) -> Result<RepairAttemptOutcome, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if D < 2 {
return Err(FlipError::UnsupportedDimension { dimension: D }.into());
}
if D == 2 {
return repair_delaunay_with_flips_k2_attempt(tds, kernel, seed_simplices, config);
}
let max_flips = config
.max_flips_override
.unwrap_or_else(|| default_max_flips::<D>(tds.number_of_simplices()));
let mut stats = DelaunayRepairStats::default();
let mut diagnostics = RepairDiagnostics::default();
let mut queues = RepairQueues::new();
let mut last_applied_flip: Option<LastAppliedFlip> = None;
let seed_started = timing.is_some().then(Instant::now);
let used_full_reseed = seed_repair_queues(tds, seed_simplices, &mut queues, &mut stats)?;
if let (Some(timing), Some(seed_started)) = (timing.as_deref_mut(), seed_started) {
timing.record_attempt_seed(seed_started.elapsed());
}
let mut touched_simplices = SimplexKeyBuffer::new();
let mut touched_simplex_set = FastHashSet::<SimplexKey>::default();
let mut prefer_secondary = false;
macro_rules! timed_step {
($recorder:ident, $step:expr) => {{
if timing.is_some() {
let started = Instant::now();
let processed = $step?;
if let Some(timing) = timing.as_deref_mut() {
timing.$recorder(started.elapsed());
}
processed
} else {
$step?
}
}};
}
while queues.has_work() {
if prefer_secondary {
let processed_ridge = timed_step!(
record_attempt_ridge,
run_next_ridge_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
);
let processed_edge = !processed_ridge
&& timed_step!(
record_attempt_edge,
run_next_edge_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
);
let processed_triangle = !processed_ridge
&& !processed_edge
&& timed_step!(
record_attempt_triangle,
run_next_triangle_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
);
if processed_ridge || processed_edge || processed_triangle {
prefer_secondary = false;
continue;
}
}
if timed_step!(
record_attempt_facet,
run_next_facet_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
) {
prefer_secondary = true;
continue;
}
let processed_ridge = timed_step!(
record_attempt_ridge,
run_next_ridge_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
);
let processed_edge = !processed_ridge
&& timed_step!(
record_attempt_edge,
run_next_edge_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
);
let processed_triangle = !processed_ridge
&& !processed_edge
&& timed_step!(
record_attempt_triangle,
run_next_triangle_repair_step(
tds,
kernel,
&mut queues,
&mut stats,
max_flips,
config,
&mut diagnostics,
&mut last_applied_flip,
&mut touched_simplices,
&mut touched_simplex_set,
)
);
if processed_ridge || processed_edge || processed_triangle {
prefer_secondary = false;
}
}
if repair_trace_enabled() {
tracing::debug!(
"[repair] attempt={} done: checked={} flips={} max_queue={} ambiguous={} predicate_failures={} cycles={}",
config.attempt,
stats.facets_checked,
stats.flips_performed,
stats.max_queue_len,
diagnostics.ambiguous_predicates,
diagnostics.predicate_failures,
diagnostics.cycle_detections,
);
}
emit_repair_debug_summary("attempt_done", &stats, &diagnostics, config, max_flips);
Ok(RepairAttemptOutcome {
postcondition_required: repair_postcondition_required(&stats, &diagnostics),
stats,
last_applied_flip,
touched_simplices,
used_full_reseed,
})
}
fn snapshot_removed_simplex_vertices<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
removed_simplices: &SimplexKeyBuffer,
) -> Result<RemovedSimplexVertexSnapshot, FlipError>
where
U: DataType,
V: DataType,
{
removed_simplices
.iter()
.copied()
.map(|simplex_key| {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
Ok(simplex.vertices().iter().copied().collect())
})
.collect()
}
#[expect(
clippy::too_many_arguments,
reason = "Flip mutation needs explicit move, cavity, policy, and validation inputs"
)]
#[expect(
clippy::too_many_lines,
reason = "Keep flip construction, validation, and wiring together for clarity"
)]
fn apply_bistellar_flip_with_k<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
k_move: usize,
removed_face_vertices: &[VertexKey],
inserted_face_vertices: &[VertexKey],
removed_simplices: &SimplexKeyBuffer,
direction: FlipDirection,
orientation_policy: ReplacementOrientationPolicy,
validation_scope: FlipValidationScope,
) -> Result<AppliedFlip<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if k_move == 0 || k_move > D + 1 {
return Err(FlipContextError::InvalidMoveSize {
k_move,
dimension: D,
}
.into());
}
let expected_removed_face = D + 2 - k_move;
if removed_face_vertices.len() != expected_removed_face {
return Err(FlipContextError::WrongRemovedFaceArity {
expected: expected_removed_face,
found: removed_face_vertices.len(),
}
.into());
}
if inserted_face_vertices.len() != k_move {
return Err(FlipContextError::WrongInsertedFaceArity {
k_move,
expected: k_move,
found: inserted_face_vertices.len(),
}
.into());
}
if removed_simplices.len() != k_move {
return Err(FlipContextError::WrongRemovedSimplexCount {
expected: k_move,
found: removed_simplices.len(),
}
.into());
}
if removed_face_vertices
.iter()
.any(|v| inserted_face_vertices.contains(v))
{
return Err(FlipContextError::OverlappingFaces.into());
}
debug_assert!(
tds.is_coherently_oriented(),
"TDS coherent orientation invariant violated before bistellar flip (k={k_move}, direction={direction:?})",
);
if k_move >= 2
&& k_move < D
&& let Some(existing_simplex) =
find_simplex_containing_simplex(tds, inserted_face_vertices, removed_simplices)
{
if repair_trace_enabled() || env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() {
tracing::debug!(
"[repair] skip flip: inserted simplex already exists (k={k_move}, inserted_face={inserted_face_vertices:?}, existing_simplex={existing_simplex:?})"
);
}
return Err(FlipError::InsertedSimplexAlreadyExists {
k_move,
simplex_vertices: inserted_face_vertices.iter().copied().collect(),
existing_simplex,
});
}
let mut new_simplices = SimplexKeyBuffer::new();
let mut new_simplex_vertices: SmallBuffer<
SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
MAX_PRACTICAL_DIMENSION_SIZE,
> = SmallBuffer::with_capacity(removed_face_vertices.len());
for &omit in removed_face_vertices {
let mut vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D + 1);
vertices.extend_from_slice(inserted_face_vertices);
for &v in removed_face_vertices {
if v != omit {
vertices.push(v);
}
}
new_simplex_vertices.push(vertices);
}
let boundary_facets = extract_cavity_boundary(tds, removed_simplices).map_err(|source| {
FlipError::from(FlipNeighborWiringError::BoundaryExtraction { source })
})?;
let external_facets = external_facets_for_boundary(tds, removed_simplices, &boundary_facets)
.map_err(FlipNeighborWiringError::from)?;
let topology_index = build_flip_topology_index(
tds,
&new_simplex_vertices,
removed_simplices,
inserted_face_vertices,
);
for vertices in &mut new_simplex_vertices {
if flip_would_duplicate_simplex_any(tds, vertices, &topology_index) {
return Err(FlipError::DuplicateSimplex);
}
if flip_would_create_nonmanifold_facets_any(vertices, &topology_index) {
return Err(FlipError::NonManifoldFacet);
}
let points = vertices_to_points(tds, vertices)?;
match robust_orientation(&points) {
Err(e) => {
return Err(FlipPredicateError::coordinate_conversion(
FlipPredicateOperation::ReplacementSimplexOrientation,
e,
)
.into());
}
Ok(Orientation::DEGENERATE) => {
if env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() {
tracing::debug!(
k_move,
direction = ?direction,
removed_face = ?removed_face_vertices,
inserted_face = ?inserted_face_vertices,
vertices = ?vertices,
"[repair] flip degenerate simplex (exact)"
);
}
return Err(FlipError::DegenerateSimplex);
}
Ok(Orientation::NEGATIVE) => {
vertices.swap(0, 1);
}
Ok(Orientation::POSITIVE) => {}
}
}
let newly_inserted_vertex = if k_move == 1 {
inserted_face_vertices.first().copied()
} else {
None
};
let mut new_simplex_offsets = replacement_simplex_periodic_offsets(
tds,
&new_simplex_vertices,
removed_simplices,
&external_facets,
newly_inserted_vertex,
)?;
orient_replacement_simplices(
tds,
&mut new_simplex_vertices,
&mut new_simplex_offsets,
&external_facets,
)?;
if matches!(
orientation_policy,
ReplacementOrientationPolicy::RequirePositive
) {
validate_replacement_orientation(tds, &new_simplex_vertices)?;
}
let removed_simplex_vertices = snapshot_removed_simplex_vertices(tds, removed_simplices)?;
let mut trial = tds.clone_for_rollback();
for (vertices, periodic_offsets) in new_simplex_vertices.into_iter().zip(new_simplex_offsets) {
let mut simplex = Simplex::new(vertices, None)?;
if let Some(offsets) = periodic_offsets {
simplex.set_periodic_vertex_offsets(offsets)?;
}
let simplex_key = trial
.insert_simplex_with_mapping_prechecked_topology(simplex)
.map_err(|source| FlipMutationError::SimplexInsertion {
source: source.into(),
})?;
new_simplices.push(simplex_key);
}
wire_cavity_neighbors(
&mut trial,
&new_simplices,
external_facets.iter().copied(),
Some(removed_simplices),
)
.map_err(FlipNeighborWiringError::from)?;
trial.remove_simplices_by_keys(removed_simplices);
let validation_result = match validation_scope {
FlipValidationScope::FullTds => trial.is_valid().map_err(TdsValidationFailure::from),
FlipValidationScope::LocalCavity => {
validate_flip_trial_cavity(&trial, &new_simplices, &external_facets, removed_simplices)
}
};
validation_result.map_err(|source| {
FlipError::from(FlipMutationError::TrialValidation {
k_move,
direction,
source,
})
})?;
debug_assert!(
trial.is_coherently_oriented(),
"TDS coherent orientation invariant violated after bistellar flip (k={k_move}, direction={direction:?})",
);
*tds = trial;
Ok(AppliedFlip {
info: FlipInfo {
kind: BistellarFlipKind { k: k_move, d: D },
direction,
removed_simplices: removed_simplices.iter().copied().collect(),
new_simplices,
removed_face_vertices: removed_face_vertices.iter().copied().collect(),
inserted_face_vertices: inserted_face_vertices.iter().copied().collect(),
},
removed_simplex_vertices,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ReplacementOrientationPolicy {
AllowSigned,
RequirePositive,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FlipValidationScope {
FullTds,
LocalCavity,
}
fn validate_flip_trial_cavity<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
new_simplices: &[SimplexKey],
external_facets: &[FacetHandle],
removed_simplices: &[SimplexKey],
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
for &simplex_key in removed_simplices {
if tds.contains_simplex(simplex_key) {
return Err(TdsValidationFailure::InconsistentDataStructure {
message: format!("flip trial still contains removed simplex {simplex_key:?}"),
});
}
if tds.simplex_uuid_from_key(simplex_key).is_some() {
return Err(TdsValidationFailure::MappingInconsistency {
entity: EntityKind::Simplex,
message: format!("flip trial still maps removed simplex key {simplex_key:?}"),
});
}
}
let mut affected_simplices = SimplexKeyBuffer::new();
let mut affected_set = FastHashSet::default();
for &simplex_key in new_simplices {
push_unique_simplex_key(simplex_key, &mut affected_simplices, &mut affected_set);
}
for facet in external_facets {
push_unique_simplex_key(
facet.simplex_key(),
&mut affected_simplices,
&mut affected_set,
);
}
validate_flip_trial_local_facet_sharing(tds, &affected_simplices)?;
for &simplex_key in &affected_simplices {
validate_flip_trial_simplex(tds, simplex_key, removed_simplices)?;
}
Ok(())
}
fn push_unique_simplex_key(
simplex_key: SimplexKey,
simplices: &mut SimplexKeyBuffer,
seen: &mut FastHashSet<SimplexKey>,
) {
if seen.insert(simplex_key) {
simplices.push(simplex_key);
}
}
fn validate_flip_trial_local_facet_sharing<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
affected_simplices: &[SimplexKey],
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
type FacetIncidents = SmallBuffer<(SimplexKey, u8), 2>;
let mut facet_to_simplices: FastHashMap<u64, FacetIncidents> = FastHashMap::default();
for &simplex_key in affected_simplices {
let simplex =
tds.simplex(simplex_key)
.ok_or_else(|| TdsValidationFailure::SimplexNotFound {
simplex_key,
context: "flip trial local facet sharing".to_string(),
})?;
if simplex.number_of_vertices() != D + 1 {
return Err(TdsValidationFailure::DimensionMismatch {
expected: D + 1,
actual: simplex.number_of_vertices(),
context: format!("flip trial simplex {simplex_key:?} arity"),
});
}
for facet_idx in 0..simplex.number_of_vertices() {
let facet_vertices = facet_vertices_from_simplex(simplex, facet_idx);
let facet_idx_u8 =
u8::try_from(facet_idx).map_err(|_| TdsValidationFailure::IndexOutOfBounds {
index: facet_idx,
bound: usize::from(u8::MAX),
context: "flip trial facet index".to_string(),
})?;
facet_to_simplices
.entry(facet_key_from_vertices(&facet_vertices))
.or_default()
.push((simplex_key, facet_idx_u8));
}
}
for (facet_key, incidents) in facet_to_simplices {
match incidents.as_slice() {
[_] => {}
[(simplex_a, facet_a), (simplex_b, facet_b)] => {
validate_flip_trial_mutual_facet_neighbors(
tds,
facet_key,
*simplex_a,
usize::from(*facet_a),
*simplex_b,
usize::from(*facet_b),
)?;
}
_ => {
return Err(TdsValidationFailure::Facet {
source: FacetError::InvalidFacetMultiplicity {
facet_key,
found: incidents.len(),
},
});
}
}
}
Ok(())
}
fn validate_flip_trial_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
removed_simplices: &[SimplexKey],
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
let simplex =
tds.simplex(simplex_key)
.ok_or_else(|| TdsValidationFailure::SimplexNotFound {
simplex_key,
context: "flip trial local simplex validation".to_string(),
})?;
if tds.simplex_uuid_from_key(simplex_key) != Some(simplex.uuid()) {
return Err(TdsValidationFailure::MappingInconsistency {
entity: EntityKind::Simplex,
message: format!(
"missing or inconsistent UUID mapping for flip trial simplex {simplex_key:?}"
),
});
}
if simplex.number_of_vertices() != D + 1 {
return Err(TdsValidationFailure::DimensionMismatch {
expected: D + 1,
actual: simplex.number_of_vertices(),
context: format!("flip trial simplex {simplex_key:?} arity"),
});
}
validate_flip_trial_simplex_vertices(tds, simplex_key, simplex)?;
validate_flip_trial_simplex_neighbors(tds, simplex_key, simplex, removed_simplices)
}
fn validate_flip_trial_simplex_vertices<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
let mut seen_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(simplex.number_of_vertices());
for &vertex_key in simplex.vertices() {
if seen_vertices.contains(&vertex_key) {
return Err(TdsValidationFailure::InconsistentDataStructure {
message: format!(
"flip trial simplex {simplex_key:?} repeats vertex {vertex_key:?}"
),
});
}
seen_vertices.push(vertex_key);
let vertex =
tds.vertex(vertex_key)
.ok_or_else(|| TdsValidationFailure::VertexNotFound {
vertex_key,
context: format!("flip trial simplex {simplex_key:?} vertex reference"),
})?;
if tds.vertex_uuid_from_key(vertex_key) != Some(vertex.uuid()) {
return Err(TdsValidationFailure::MappingInconsistency {
entity: EntityKind::Vertex,
message: format!(
"missing or inconsistent UUID mapping for flip trial vertex {vertex_key:?}"
),
});
}
let Some(incident_simplex_key) = vertex.incident_simplex() else {
continue;
};
let incident_simplex = tds.simplex(incident_simplex_key).ok_or_else(|| {
TdsValidationFailure::SimplexNotFound {
simplex_key: incident_simplex_key,
context: format!("dangling incident_simplex pointer from vertex {vertex_key:?}"),
}
})?;
if !incident_simplex.contains_vertex(vertex_key) {
return Err(TdsValidationFailure::InconsistentDataStructure {
message: format!(
"Vertex {vertex_key:?} incident_simplex {incident_simplex_key:?} does not contain the vertex"
),
});
}
}
Ok(())
}
fn validate_flip_trial_simplex_neighbors<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
removed_simplices: &[SimplexKey],
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
let Some(neighbors) = simplex.neighbor_slots() else {
return Ok(());
};
if neighbors.len() != D + 1 {
return Err(TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::LengthMismatch {
actual: neighbors.len(),
expected: D + 1,
context: "flip trial neighbor validation".to_string(),
},
});
}
for (facet_idx, neighbor_slot) in neighbors.iter().copied().enumerate() {
let neighbor_key = match neighbor_slot {
NeighborSlot::Unassigned => {
return Err(TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::UnassignedNeighborSlot {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
context: "flip trial neighbor validation".to_string(),
},
});
}
NeighborSlot::Boundary => continue,
NeighborSlot::Neighbor(neighbor_key) => neighbor_key,
};
if removed_simplices.contains(&neighbor_key) {
return Err(TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::ReferencedRemovedNeighbor {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_key,
},
});
}
if neighbor_key == simplex_key {
if simplex_allows_periodic_self_neighbor(simplex) {
continue;
}
return Err(TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::NonPeriodicSelfNeighbor {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
},
});
}
let neighbor_simplex =
tds.simplex(neighbor_key)
.ok_or_else(|| TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::MissingNeighborSimplex {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_key,
context: "flip trial neighbor validation".to_string(),
},
})?;
let mirror_idx = simplex
.mirror_facet_index(facet_idx, neighbor_simplex)
.ok_or_else(|| TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::MirrorFacetMissing {
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
neighbor_uuid: neighbor_simplex.uuid(),
context: "flip trial neighbor validation".to_string(),
},
})?;
validate_flip_trial_mutual_facet_neighbors(
tds,
facet_key_from_vertices(&facet_vertices_from_simplex(simplex, facet_idx)),
simplex_key,
facet_idx,
neighbor_key,
mirror_idx,
)?;
validate_flip_trial_neighbor_orientation(
simplex_key,
simplex,
facet_idx,
neighbor_key,
neighbor_simplex,
mirror_idx,
)?;
}
Ok(())
}
fn simplex_allows_periodic_self_neighbor<T, U, V, const D: usize>(
simplex: &Simplex<T, U, V, D>,
) -> bool
where
U: DataType,
V: DataType,
{
let Some(offsets) = simplex.periodic_vertex_offsets() else {
return false;
};
!offsets.is_empty() && offsets.len() == simplex.number_of_vertices()
}
fn validate_flip_trial_mutual_facet_neighbors<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
facet_key: u64,
source_simplex_key: SimplexKey,
source_facet: usize,
target_simplex_key: SimplexKey,
target_facet: usize,
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
let source_simplex =
tds.simplex(source_simplex_key)
.ok_or_else(|| TdsValidationFailure::SimplexNotFound {
simplex_key: source_simplex_key,
context: "flip trial mutual neighbor validation".to_string(),
})?;
let target_simplex =
tds.simplex(target_simplex_key)
.ok_or_else(|| TdsValidationFailure::SimplexNotFound {
simplex_key: target_simplex_key,
context: "flip trial mutual neighbor validation".to_string(),
})?;
let source_neighbor = source_simplex.neighbor_key(source_facet).flatten();
let target_neighbor = target_simplex.neighbor_key(target_facet).flatten();
if source_neighbor != Some(target_simplex_key) || target_neighbor != Some(source_simplex_key) {
return Err(TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::InteriorFacetNeighborMismatch {
facet_key,
first_simplex_key: source_simplex_key,
first_simplex_uuid: source_simplex.uuid(),
first_facet_index: source_facet,
first_neighbor: source_neighbor,
second_simplex_key: target_simplex_key,
second_simplex_uuid: target_simplex.uuid(),
second_facet_index: target_facet,
second_neighbor: target_neighbor,
},
});
}
Ok(())
}
fn validate_flip_trial_neighbor_orientation<T, U, V, const D: usize>(
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
facet_idx: usize,
neighbor_key: SimplexKey,
neighbor_simplex: &Simplex<T, U, V, D>,
mirror_idx: usize,
) -> Result<(), TdsValidationFailure>
where
U: DataType,
V: DataType,
{
let (observed_odd_permutation, expected_odd_permutation, facet_vertex_count, target_count) =
match flip_trial_neighbor_orientation_parity(
simplex_key,
simplex,
facet_idx,
neighbor_key,
neighbor_simplex,
mirror_idx,
) {
Ok(parity) => parity,
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::FacetOrderParityUnavailable,
}) => {
return Err(TdsValidationFailure::InconsistentDataStructure {
message: format!(
"Could not derive facet-order permutation parity between simplices {:?} and {:?}",
simplex.uuid(),
neighbor_simplex.uuid()
),
});
}
Err(err) => {
return Err(TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::FacetOrderUnavailable {
simplex_key,
simplex_uuid: simplex.uuid(),
facet_index: facet_idx,
context: "facet parity in local flip validation".to_string(),
source: Box::new(err),
},
});
}
};
if observed_odd_permutation != expected_odd_permutation {
return Err(TdsValidationFailure::OrientationViolation {
simplex1_key: simplex_key,
simplex1_uuid: simplex.uuid(),
simplex2_key: neighbor_key,
simplex2_uuid: neighbor_simplex.uuid(),
simplex1_facet_index: facet_idx,
simplex2_facet_index: mirror_idx,
facet_vertex_count,
simplex2_facet_vertex_count: target_count,
observed_odd_permutation,
expected_odd_permutation,
});
}
Ok(())
}
fn flip_trial_neighbor_orientation_parity<T, U, V, const D: usize>(
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
facet_idx: usize,
neighbor_key: SimplexKey,
neighbor_simplex: &Simplex<T, U, V, D>,
mirror_idx: usize,
) -> Result<(bool, bool, usize, usize), FlipError>
where
U: DataType,
V: DataType,
{
let expected_odd_permutation = (facet_idx + mirror_idx).is_multiple_of(2);
if simplex.periodic_vertex_offsets().is_some()
|| neighbor_simplex.periodic_vertex_offsets().is_some()
{
let source_offsets = periodic_offsets_or_zero_frame(simplex_key, simplex)?;
let target_offsets = periodic_offsets_or_zero_frame(neighbor_key, neighbor_simplex)?;
let source_order = normalized_facet_order_with_offsets(
simplex_key,
simplex.vertices(),
source_offsets.as_ref(),
facet_idx,
)?;
let target_order = normalized_facet_order_with_offsets(
neighbor_key,
neighbor_simplex.vertices(),
target_offsets.as_ref(),
mirror_idx,
)?;
let observed_odd_permutation = permutation_odd(&source_order, &target_order)
.ok_or(FlipContextError::FacetOrderParityUnavailable)?;
return Ok((
observed_odd_permutation,
expected_odd_permutation,
source_order.len(),
target_order.len(),
));
}
let source_order = facet_order(simplex.vertices(), facet_idx)?;
let target_order = facet_order(neighbor_simplex.vertices(), mirror_idx)?;
let observed_odd_permutation = permutation_odd(&source_order, &target_order)
.ok_or(FlipContextError::FacetOrderParityUnavailable)?;
Ok((
observed_odd_permutation,
expected_odd_permutation,
source_order.len(),
target_order.len(),
))
}
fn find_simplex_containing_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_vertices: &[VertexKey],
removed_simplices: &[SimplexKey],
) -> Option<SimplexKey>
where
U: DataType,
V: DataType,
{
let first = *simplex_vertices.first()?;
let candidates = tds.find_simplices_containing_vertex_by_key(first);
for simplex_key in candidates {
if removed_simplices.contains(&simplex_key) {
continue;
}
let Some(simplex) = tds.simplex(simplex_key) else {
continue;
};
if simplex_vertices
.iter()
.copied()
.all(|vk| simplex.contains_vertex(vk))
{
return Some(simplex_key);
}
}
None
}
fn orient_replacement_simplices<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices: &mut [SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>],
periodic_offsets: &mut [Option<PeriodicOffsetBuffer<D>>],
external_facets: &[FacetHandle],
) -> Result<(), FlipError> {
let mut flips = SmallBuffer::from_elem(None, simplices.len());
if periodic_offsets.len() != simplices.len() {
return Err(FlipContextError::ReplacementPeriodicOffsetCountMismatch {
simplex_count: simplices.len(),
offset_count: periodic_offsets.len(),
}
.into());
}
assign_external_replacement_orientation(
tds,
simplices,
periodic_offsets,
external_facets,
&mut flips,
)?;
loop {
let mut changed = false;
for source_idx in 0..simplices.len() {
for target_idx in (source_idx + 1)..simplices.len() {
let Some((source_facet_idx, target_facet_idx)) =
shared_facet_indices(&simplices[source_idx], &simplices[target_idx])
else {
continue;
};
let coherent = facet_orders_coherent(
&simplices[source_idx],
source_facet_idx,
&simplices[target_idx],
target_facet_idx,
)?;
match (flips[source_idx], flips[target_idx]) {
(Some(source_flip), Some(target_flip)) => {
if target_flip != (source_flip ^ !coherent) {
return Err(
FlipContextError::ConflictingReplacementOrientationBetweenSimplices {
source_simplex_index: source_idx,
target_simplex_index: target_idx,
}
.into(),
);
}
}
(Some(source_flip), None) => {
changed |=
set_flip_assignment(&mut flips, target_idx, source_flip ^ !coherent)?;
}
(None, Some(target_flip)) => {
changed |=
set_flip_assignment(&mut flips, source_idx, target_flip ^ !coherent)?;
}
(None, None) => {}
}
}
}
if flips.iter().all(Option::is_some) {
break;
}
if !changed {
let Some(root_idx) = flips.iter().position(Option::is_none) else {
break;
};
flips[root_idx] = Some(false);
}
}
for ((vertices, offsets), should_flip) in simplices.iter_mut().zip(periodic_offsets).zip(flips)
{
if should_flip.unwrap_or(false) {
if vertices.len() < 2 {
return Err(FlipContextError::ReplacementSimplexTooSmallForOrientationFlip.into());
}
vertices.swap(0, 1);
if let Some(offsets) = offsets {
offsets.swap(0, 1);
}
}
}
Ok(())
}
fn assign_external_replacement_orientation<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices: &[SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>],
periodic_offsets: &[Option<PeriodicOffsetBuffer<D>>],
external_facets: &[FacetHandle],
flips: &mut SmallBuffer<Option<bool>, MAX_PRACTICAL_DIMENSION_SIZE>,
) -> Result<(), FlipError> {
for &external in external_facets {
let external_simplex =
tds.simplex(external.simplex_key())
.ok_or_else(|| FlipError::MissingSimplex {
simplex_key: external.simplex_key(),
})?;
let external_offsets =
periodic_offsets_or_zero_frame(external.simplex_key(), external_simplex)?;
let external_facet_idx = usize::from(external.facet_index());
for (simplex_idx, vertices) in simplices.iter().enumerate() {
let Some(replacement_facet_idx) =
matching_facet_index(external_simplex.vertices(), external_facet_idx, vertices)?
else {
continue;
};
let coherent = if external_simplex.periodic_vertex_offsets().is_some()
|| periodic_offsets[simplex_idx].is_some()
{
let Some(replacement_offsets) = periodic_offsets[simplex_idx].as_deref() else {
return Err(FlipContextError::MissingReplacementPeriodicOffsets {
simplex_index: simplex_idx,
}
.into());
};
facet_orders_coherent_with_periodic_offsets(&PeriodicFacetParityContext {
source_vertices: external_simplex.vertices(),
source_offsets: external_offsets.as_ref(),
source_facet_idx: external_facet_idx,
target_vertices: vertices,
target_offsets: replacement_offsets,
target_facet_idx: replacement_facet_idx,
source_simplex_key: external.simplex_key(),
target_simplex_index: simplex_idx,
})?
} else {
facet_orders_coherent(
external_simplex.vertices(),
external_facet_idx,
vertices,
replacement_facet_idx,
)?
};
set_flip_assignment(flips, simplex_idx, !coherent)?;
}
}
Ok(())
}
fn set_flip_assignment(
assignments: &mut SmallBuffer<Option<bool>, MAX_PRACTICAL_DIMENSION_SIZE>,
simplex_idx: usize,
required: bool,
) -> Result<bool, FlipError> {
if simplex_idx >= assignments.len() {
return Err(FlipContextError::ReplacementOrientationIndexOutOfRange {
simplex_index: simplex_idx,
}
.into());
}
match assignments[simplex_idx] {
Some(existing) if existing != required => Err(
FlipContextError::ConflictingReplacementOrientationForSimplex {
simplex_index: simplex_idx,
}
.into(),
),
Some(_) => Ok(false),
None => {
assignments[simplex_idx] = Some(required);
Ok(true)
}
}
}
fn replacement_simplex_periodic_offsets<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices: &[SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>],
removed_simplices: &[SimplexKey],
external_facets: &[FacetHandle],
newly_inserted_vertex: Option<VertexKey>,
) -> Result<ReplacementPeriodicOffsets<D>, FlipError> {
let source_simplices =
replacement_periodic_source_simplices(removed_simplices, external_facets);
if !replacement_sources_use_periodic_offsets(tds, &source_simplices)? {
return Ok(SmallBuffer::from_elem(None, simplices.len()));
}
let target_simplex_key = *removed_simplices
.first()
.ok_or(FlipContextError::MissingRemovedSimplexFrame)?;
let mut offsets_by_simplex = ReplacementPeriodicOffsets::<D>::with_capacity(simplices.len());
for vertices in simplices {
let mut offsets = PeriodicOffsetBuffer::<D>::with_capacity(vertices.len());
for &vertex_key in vertices {
let offset = if Some(vertex_key) == newly_inserted_vertex
&& !source_simplices_contain_vertex(tds, &source_simplices, vertex_key)?
{
new_vertex_periodic_offset_in_frame(tds, target_simplex_key)?
} else {
periodic_offset_lifted_into_simplex(
tds,
vertex_key,
target_simplex_key,
&source_simplices,
)?
};
offsets.push(offset);
}
offsets_by_simplex.push(Some(offsets));
}
Ok(offsets_by_simplex)
}
fn replacement_periodic_source_simplices(
removed_simplices: &[SimplexKey],
external_facets: &[FacetHandle],
) -> SimplexKeyBuffer {
let mut source_simplices = SimplexKeyBuffer::new();
let mut seen = FastHashSet::default();
for &simplex_key in removed_simplices {
push_unique_simplex_key(simplex_key, &mut source_simplices, &mut seen);
}
for external in external_facets {
push_unique_simplex_key(external.simplex_key(), &mut source_simplices, &mut seen);
}
source_simplices
}
fn replacement_sources_use_periodic_offsets<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
source_simplices: &[SimplexKey],
) -> Result<bool, FlipError> {
let mut uses_periodic_offsets = false;
for &simplex_key in source_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if let Some(offsets) = simplex.periodic_vertex_offsets() {
validate_periodic_offset_len(simplex_key, simplex, offsets)?;
uses_periodic_offsets = true;
}
}
Ok(uses_periodic_offsets)
}
fn source_simplices_contain_vertex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
source_simplices: &[SimplexKey],
vertex_key: VertexKey,
) -> Result<bool, FlipError> {
for &simplex_key in source_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if simplex.contains_vertex(vertex_key) {
return Ok(true);
}
}
Ok(false)
}
fn new_vertex_periodic_offset_in_frame<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
target_simplex_key: SimplexKey,
) -> Result<[i8; D], FlipError> {
let target_simplex = tds
.simplex(target_simplex_key)
.ok_or(FlipError::MissingSimplex {
simplex_key: target_simplex_key,
})?;
let target_offsets = periodic_offsets_or_zero_frame(target_simplex_key, target_simplex)?;
Ok(target_offsets.first().copied().unwrap_or([0_i8; D]))
}
fn matching_facet_index(
source_vertices: &[VertexKey],
source_facet_idx: usize,
target_vertices: &[VertexKey],
) -> Result<Option<usize>, FlipError> {
if source_vertices.len() != target_vertices.len() {
return Ok(None);
}
let source_facet = facet_order(source_vertices, source_facet_idx)?;
if !source_facet
.iter()
.copied()
.all(|vertex| target_vertices.contains(&vertex))
{
return Ok(None);
}
let mut target_facet_idx = None;
for (idx, &vertex) in target_vertices.iter().enumerate() {
if source_facet.contains(&vertex) {
continue;
}
if target_facet_idx.is_some() {
return Ok(None);
}
target_facet_idx = Some(idx);
}
Ok(target_facet_idx)
}
fn shared_facet_indices(
source_vertices: &[VertexKey],
target_vertices: &[VertexKey],
) -> Option<(usize, usize)> {
if source_vertices.len() != target_vertices.len() {
return None;
}
let source_facet_idx = unique_vertex_index(source_vertices, target_vertices)?;
let target_facet_idx = unique_vertex_index(target_vertices, source_vertices)?;
Some((source_facet_idx, target_facet_idx))
}
fn unique_vertex_index(vertices: &[VertexKey], other: &[VertexKey]) -> Option<usize> {
let mut unique_idx = None;
for (idx, &vertex) in vertices.iter().enumerate() {
if other.contains(&vertex) {
continue;
}
if unique_idx.is_some() {
return None;
}
unique_idx = Some(idx);
}
unique_idx
}
fn facet_orders_coherent(
source_vertices: &[VertexKey],
source_facet_idx: usize,
target_vertices: &[VertexKey],
target_facet_idx: usize,
) -> Result<bool, FlipError> {
let source_order = facet_order(source_vertices, source_facet_idx)?;
let target_order = facet_order(target_vertices, target_facet_idx)?;
let observed_odd = permutation_odd(&source_order, &target_order)
.ok_or(FlipContextError::FacetOrderParityUnavailable)?;
let expected_odd = (source_facet_idx + target_facet_idx).is_multiple_of(2);
Ok(observed_odd == expected_odd)
}
struct PeriodicFacetParityContext<'a, const D: usize> {
source_vertices: &'a [VertexKey],
source_offsets: &'a [[i8; D]],
source_facet_idx: usize,
target_vertices: &'a [VertexKey],
target_offsets: &'a [[i8; D]],
target_facet_idx: usize,
source_simplex_key: SimplexKey,
target_simplex_index: usize,
}
fn facet_orders_coherent_with_periodic_offsets<const D: usize>(
context: &PeriodicFacetParityContext<'_, D>,
) -> Result<bool, FlipError> {
if context.source_offsets.len() != context.source_vertices.len() {
return Err(FlipContextError::PeriodicOffsetCountMismatch {
simplex_key: context.source_simplex_key,
offset_count: context.source_offsets.len(),
vertex_count: context.source_vertices.len(),
}
.into());
}
if context.target_offsets.len() != context.target_vertices.len() {
return Err(FlipContextError::ReplacementPeriodicOffsetLengthMismatch {
simplex_index: context.target_simplex_index,
offset_count: context.target_offsets.len(),
vertex_count: context.target_vertices.len(),
}
.into());
}
let source_order = facet_order_with_offsets(
context.source_vertices,
context.source_offsets,
context.source_facet_idx,
)?;
let target_order = facet_order_with_offsets(
context.target_vertices,
context.target_offsets,
context.target_facet_idx,
)?;
let aligned_source_order = align_periodic_facet_order(
&source_order,
&target_order,
context.source_simplex_key,
context.target_simplex_index,
)?;
let observed_odd = permutation_odd(&aligned_source_order, &target_order)
.ok_or(FlipContextError::FacetOrderParityUnavailable)?;
let expected_odd = (context.source_facet_idx + context.target_facet_idx).is_multiple_of(2);
Ok(observed_odd == expected_odd)
}
fn facet_order_with_offsets<const D: usize>(
vertices: &[VertexKey],
offsets: &[[i8; D]],
omit_idx: usize,
) -> Result<SmallBuffer<(VertexKey, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> {
if omit_idx >= vertices.len() {
return Err(FlipContextError::ReplacementFacetIndexOutOfRange {
facet_index: omit_idx,
vertex_count: vertices.len(),
}
.into());
}
let mut order: SmallBuffer<(VertexKey, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len().saturating_sub(1));
for (idx, &vertex) in vertices.iter().enumerate() {
if idx != omit_idx {
order.push((vertex, offsets[idx]));
}
}
Ok(order)
}
fn normalized_facet_order_with_offsets<const D: usize>(
simplex_key: SimplexKey,
vertices: &[VertexKey],
offsets: &[[i8; D]],
omit_idx: usize,
) -> Result<SmallBuffer<(VertexKey, [i16; D]), MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> {
if offsets.len() != vertices.len() {
return Err(FlipContextError::PeriodicOffsetCountMismatch {
simplex_key,
offset_count: offsets.len(),
vertex_count: vertices.len(),
}
.into());
}
if omit_idx >= vertices.len() {
return Err(FlipContextError::ReplacementFacetIndexOutOfRange {
facet_index: omit_idx,
vertex_count: vertices.len(),
}
.into());
}
let mut order: SmallBuffer<(VertexKey, [i16; D]), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len().saturating_sub(1));
for (idx, &vertex) in vertices.iter().enumerate() {
if idx == omit_idx {
continue;
}
let mut offset = [0_i16; D];
for axis in 0..D {
offset[axis] = i16::from(offsets[idx][axis]);
}
order.push((vertex, offset));
}
let mut anchor_key = u64::MAX;
let mut anchor_offset = [0_i16; D];
for (vertex, offset) in &order {
let key_value = (*vertex).data().as_ffi();
if key_value < anchor_key || (key_value == anchor_key && *offset < anchor_offset) {
anchor_key = key_value;
anchor_offset = *offset;
}
}
for (_, offset) in &mut order {
for axis in 0..D {
offset[axis] -= anchor_offset[axis];
}
}
Ok(order)
}
fn align_periodic_facet_order<const D: usize>(
source_order: &[(VertexKey, [i8; D])],
target_order: &[(VertexKey, [i8; D])],
source_simplex_key: SimplexKey,
target_simplex_index: usize,
) -> Result<SmallBuffer<(VertexKey, [i8; D]), MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> {
let mut aligned_order = SmallBuffer::with_capacity(source_order.len());
for &(vertex_key, source_vertex_offset) in source_order {
let mut aligned_offset: Option<[i8; D]> = None;
for &(reference_vertex, source_reference_offset) in source_order {
let Some((_, target_reference_offset)) = target_order
.iter()
.find(|(target_vertex, _)| *target_vertex == reference_vertex)
else {
return Err(FlipContextError::FacetOrderParityUnavailable.into());
};
let candidate_offset = align_periodic_offset(
source_vertex_offset,
source_reference_offset,
*target_reference_offset,
)?;
if let Some(expected_offset) = aligned_offset {
if candidate_offset != expected_offset {
return Err(
FlipContextError::ConflictingReplacementPeriodicFrameTranslation {
vertex_key,
source_simplex_key,
target_simplex_index,
expected_offset: expected_offset.into(),
found_offset: candidate_offset.into(),
}
.into(),
);
}
} else {
aligned_offset = Some(candidate_offset);
}
}
let Some(offset) = aligned_offset else {
return Err(FlipContextError::FacetOrderParityUnavailable.into());
};
aligned_order.push((vertex_key, offset));
}
Ok(aligned_order)
}
fn facet_order(
vertices: &[VertexKey],
omit_idx: usize,
) -> Result<SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError> {
if omit_idx >= vertices.len() {
return Err(FlipContextError::ReplacementFacetIndexOutOfRange {
facet_index: omit_idx,
vertex_count: vertices.len(),
}
.into());
}
let mut order = SmallBuffer::with_capacity(vertices.len().saturating_sub(1));
for (idx, &vertex) in vertices.iter().enumerate() {
if idx != omit_idx {
order.push(vertex);
}
}
Ok(order)
}
fn permutation_odd<Id: PartialEq>(source_order: &[Id], target_order: &[Id]) -> Option<bool> {
if source_order.len() != target_order.len() {
return None;
}
let mut target_positions: SmallBuffer<usize, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(source_order.len());
let mut used_target_indices: SmallBuffer<bool, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::from_elem(false, target_order.len());
for source_vertex in source_order {
let mut matched_target_position = None;
for (target_idx, target_vertex) in target_order.iter().enumerate() {
if target_vertex == source_vertex && !used_target_indices[target_idx] {
matched_target_position = Some(target_idx);
used_target_indices[target_idx] = true;
break;
}
}
target_positions.push(matched_target_position?);
}
let mut inversion_count = 0usize;
for i in 0..target_positions.len() {
for j in (i + 1)..target_positions.len() {
if target_positions[i] > target_positions[j] {
inversion_count += 1;
}
}
}
Some(inversion_count % 2 == 1)
}
fn validate_replacement_orientation<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices: &[SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>],
) -> Result<(), FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
for vertices in simplices {
let points = vertices_to_points(tds, vertices)?;
match robust_orientation(&points) {
Ok(Orientation::POSITIVE) => {}
Ok(Orientation::DEGENERATE) => return Err(FlipError::DegenerateSimplex),
Ok(Orientation::NEGATIVE) => {
return Err(FlipError::NegativeOrientation {
simplex_vertices: vertices.iter().copied().collect(),
});
}
Err(error) => {
return Err(FlipPredicateError::coordinate_conversion(
FlipPredicateOperation::DelaunayRepairReplacementOrientation,
error,
)
.into());
}
}
}
Ok(())
}
fn simplices_containing_vertices<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertices: &[VertexKey],
) -> SimplexKeyBuffer
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut simplices = SimplexKeyBuffer::new();
'simplices: for (simplex_key, simplex) in tds.simplices() {
for &vkey in vertices {
if !simplex.contains_vertex(vkey) {
continue 'simplices;
}
}
simplices.push(simplex_key);
}
simplices
}
fn debug_ridge_context<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
ridge: RidgeHandle,
reported_multiplicity: Option<usize>,
diagnostics: &mut RepairDiagnostics,
last_applied_flip: Option<&LastAppliedFlip>,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if !should_emit_ridge_debug(diagnostics, reported_multiplicity) {
return;
}
let Some(simplex) = tds.simplex(ridge.simplex_key()) else {
tracing::debug!(
ridge = ?ridge,
reported_multiplicity,
"repair: ridge debug skipped (simplex missing)"
);
return;
};
let omit_a = usize::from(ridge.omit_a());
let omit_b = usize::from(ridge.omit_b());
if omit_a >= simplex.number_of_vertices()
|| omit_b >= simplex.number_of_vertices()
|| omit_a == omit_b
{
tracing::debug!(
ridge = ?ridge,
omit_a,
omit_b,
vertex_count = simplex.number_of_vertices(),
reported_multiplicity,
"repair: ridge debug skipped (invalid indices)"
);
return;
}
let ridge_vertices = ridge_vertices_from_simplex(simplex, omit_a, omit_b);
let neighbor_walk =
collect_simplices_around_ridge(tds, ridge.simplex_key(), &ridge_vertices, None)
.map(|simplices| simplices.into_iter().collect::<Vec<_>>());
let global_simplices = simplices_containing_vertices(tds, &ridge_vertices);
let neighbor_snapshot: Option<SmallBuffer<Option<SimplexKey>, MAX_PRACTICAL_DIMENSION_SIZE>> =
simplex.neighbor_keys().map(Iterator::collect);
let global_simplex_details: Vec<String> = global_simplices
.iter()
.copied()
.map(|simplex_key| ridge_incident_simplex_summary(tds, simplex_key, &ridge_vertices))
.collect();
let predecessor_summary =
last_applied_flip.map(|last| predecessor_flip_summary(tds, ridge, &global_simplices, last));
tracing::debug!(
ridge = ?ridge,
ridge_vertices = ?ridge_vertices,
reported_multiplicity,
neighbor_walk = ?neighbor_walk,
global_count = global_simplices.len(),
global_simplices = ?global_simplices,
global_simplex_details = ?global_simplex_details,
predecessor = ?predecessor_summary,
simplex_neighbors = ?neighbor_snapshot,
"repair: ridge adjacency debug snapshot"
);
}
fn ridge_incident_simplex_summary<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
ridge_vertices: &SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
) -> String
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(simplex) = tds.simplex(simplex_key) else {
return format!("{simplex_key:?}: missing");
};
let extras = match simplex_extras_for_ridge(simplex_key, simplex, ridge_vertices) {
Ok(extras) => extras,
Err(err) => return format!("{simplex_key:?}: extras_error={err}"),
};
let ridge_neighbors = ridge_neighbor_simplices_for_simplex(simplex, ridge_vertices);
format!("{simplex_key:?}: extras={extras:?} ridge_neighbors={ridge_neighbors:?}")
}
fn ridge_neighbor_simplices_for_simplex<T, U, V, const D: usize>(
simplex: &Simplex<T, U, V, D>,
ridge_vertices: &SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
) -> SmallBuffer<SimplexKey, 2>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut ridge_neighbors: SmallBuffer<SimplexKey, 2> = SmallBuffer::new();
for (idx, &vertex_key) in simplex.vertices().iter().enumerate() {
if ridge_vertices.contains(&vertex_key) {
continue;
}
if let Some(neighbor_key) = simplex.neighbor_key(idx).flatten() {
ridge_neighbors.push(neighbor_key);
}
}
ridge_neighbors
}
fn predecessor_flip_summary<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
ridge: RidgeHandle,
global_simplices: &[SimplexKey],
last_applied_flip: &LastAppliedFlip,
) -> String
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let global_simplices_in_new: Vec<SimplexKey> = global_simplices
.iter()
.copied()
.filter(|simplex_key| last_applied_flip.new_simplices.contains(simplex_key))
.collect();
let predecessor_new_simplex_vertices: Vec<String> = last_applied_flip
.new_simplices
.iter()
.copied()
.map(|simplex_key| simplex_vertex_summary(tds, simplex_key))
.collect();
format!(
"k={} removed_face={:?} inserted_face={:?} removed_simplices={:?} new_simplices={:?} ridge_simplex_is_new={} global_simplices_in_new={global_simplices_in_new:?} predecessor_new_simplex_vertices={predecessor_new_simplex_vertices:?}",
last_applied_flip.k_move,
last_applied_flip.removed_face_vertices,
last_applied_flip.inserted_face_vertices,
last_applied_flip.removed_simplices,
last_applied_flip.new_simplices,
last_applied_flip
.new_simplices
.contains(&ridge.simplex_key()),
)
}
fn simplex_vertex_summary<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
) -> String
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(simplex) = tds.simplex(simplex_key) else {
return format!("{simplex_key:?}: missing");
};
format!("{simplex_key:?}: vertices={:?}", simplex.vertices())
}
fn debug_postcondition_facet_context<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
facet: FacetHandle,
context: &FlipContext<D, 2>,
diagnostics: &mut RepairDiagnostics,
last_applied_flip: Option<&LastAppliedFlip>,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if !should_emit_postcondition_facet_debug(diagnostics) {
return;
}
let removed_face_details: Vec<_> = context
.removed_face_vertices
.iter()
.filter_map(|&vkey| tds.vertex(vkey).map(|vertex| (vkey, *vertex.point())))
.collect();
let inserted_face_details: Vec<_> = context
.inserted_face_vertices
.iter()
.filter_map(|&vkey| tds.vertex(vkey).map(|vertex| (vkey, *vertex.point())))
.collect();
let incident_simplex_details: Vec<String> = context
.removed_simplices
.iter()
.copied()
.map(|simplex_key| {
facet_incident_simplex_summary(tds, simplex_key, &context.removed_face_vertices)
})
.collect();
let predecessor_summary = last_applied_flip
.map(|last| postcondition_facet_predecessor_summary(tds, &context.removed_simplices, last));
tracing::debug!(
facet = ?facet,
removed_face = ?removed_face_details,
inserted_face = ?inserted_face_details,
incident_simplices = ?context.removed_simplices,
incident_simplex_details = ?incident_simplex_details,
predecessor = ?predecessor_summary,
"repair: postcondition facet debug snapshot"
);
}
fn facet_incident_simplex_summary<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
facet_vertices: &[VertexKey],
) -> String
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(simplex) = tds.simplex(simplex_key) else {
return format!("{simplex_key:?}: missing");
};
let opposite_vertices: Vec<VertexKey> = simplex
.vertices()
.iter()
.copied()
.filter(|vkey| !facet_vertices.contains(vkey))
.collect();
let neighbor_snapshot: Option<SmallBuffer<Option<SimplexKey>, MAX_PRACTICAL_DIMENSION_SIZE>> =
simplex.neighbor_keys().map(Iterator::collect);
format!(
"{simplex_key:?}: vertices={:?} opposite_vertices={opposite_vertices:?} neighbors={neighbor_snapshot:?}",
simplex.vertices()
)
}
fn postcondition_facet_predecessor_summary<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
incident_simplices: &[SimplexKey],
last_applied_flip: &LastAppliedFlip,
) -> String
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let incident_simplices_in_new: Vec<SimplexKey> = incident_simplices
.iter()
.copied()
.filter(|simplex_key| last_applied_flip.new_simplices.contains(simplex_key))
.collect();
let incident_simplices_in_removed: Vec<SimplexKey> = incident_simplices
.iter()
.copied()
.filter(|simplex_key| last_applied_flip.removed_simplices.contains(simplex_key))
.collect();
let predecessor_new_simplex_vertices: Vec<String> = last_applied_flip
.new_simplices
.iter()
.copied()
.map(|simplex_key| simplex_vertex_summary(tds, simplex_key))
.collect();
let predecessor_removed_simplex_vertices: Vec<String> =
last_applied_flip.removed_simplex_vertex_lines();
format!(
"k={} removed_face={:?} inserted_face={:?} removed_simplices={:?} new_simplices={:?} incident_simplices_in_new={incident_simplices_in_new:?} incident_simplices_in_removed={incident_simplices_in_removed:?} predecessor_new_simplex_vertices={predecessor_new_simplex_vertices:?} predecessor_removed_simplex_vertices={predecessor_removed_simplex_vertices:?}",
last_applied_flip.k_move,
last_applied_flip.removed_face_vertices,
last_applied_flip.inserted_face_vertices,
last_applied_flip.removed_simplices,
last_applied_flip.new_simplices,
)
}
fn is_delaunay_violation_k3<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
context: &FlipContext<D, 3>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
) -> Result<bool, FlipError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
delaunay_violation_k3_for_ridge(
tds,
kernel,
topology_model,
&context.removed_face_vertices,
&context.inserted_face_vertices,
&context.removed_simplices,
None,
config,
diagnostics,
)
}
pub(crate) fn apply_bistellar_flip<T, U, V, const D: usize, const K_MOVE: usize>(
tds: &mut Tds<T, U, V, D>,
context: &FlipContext<D, K_MOVE>,
) -> Result<FlipInfo<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
Ok(apply_bistellar_flip_with_k(
tds,
K_MOVE,
&context.removed_face_vertices,
&context.inserted_face_vertices,
&context.removed_simplices,
context.direction,
ReplacementOrientationPolicy::AllowSigned,
FlipValidationScope::FullTds,
)?
.info)
}
pub(crate) fn apply_bistellar_flip_dynamic<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
k_move: usize,
context: &FlipContextDyn<D>,
) -> Result<FlipInfo<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
Ok(apply_bistellar_flip_with_k(
tds,
k_move,
&context.removed_face_vertices,
&context.inserted_face_vertices,
&context.removed_simplices,
context.direction,
ReplacementOrientationPolicy::AllowSigned,
FlipValidationScope::FullTds,
)?
.info)
}
fn apply_delaunay_flip_k2<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
context: &FlipContext<D, 2>,
) -> Result<AppliedFlip<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
apply_bistellar_flip_with_k(
tds,
2,
&context.removed_face_vertices,
&context.inserted_face_vertices,
&context.removed_simplices,
context.direction,
ReplacementOrientationPolicy::RequirePositive,
FlipValidationScope::LocalCavity,
)
}
fn apply_delaunay_flip_k3<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
context: &FlipContext<D, 3>,
) -> Result<AppliedFlip<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
apply_bistellar_flip_with_k(
tds,
3,
&context.removed_face_vertices,
&context.inserted_face_vertices,
&context.removed_simplices,
context.direction,
ReplacementOrientationPolicy::RequirePositive,
FlipValidationScope::LocalCavity,
)
}
fn apply_delaunay_flip_dynamic<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
k_move: usize,
context: &FlipContextDyn<D>,
) -> Result<AppliedFlip<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
apply_bistellar_flip_with_k(
tds,
k_move,
&context.removed_face_vertices,
&context.inserted_face_vertices,
&context.removed_simplices,
context.direction,
ReplacementOrientationPolicy::RequirePositive,
FlipValidationScope::LocalCavity,
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlipDirection {
Forward,
Inverse,
}
impl FlipDirection {
#[must_use]
pub const fn inverse(self) -> Self {
match self {
Self::Forward => Self::Inverse,
Self::Inverse => Self::Forward,
}
}
}
#[derive(Debug, Clone, Copy)]
struct FlipCycleContext<'a> {
signature: u64,
k_move: usize,
direction: FlipDirection,
removed_face_vertices: &'a [VertexKey],
inserted_face_vertices: &'a [VertexKey],
}
impl<'a> FlipCycleContext<'a> {
const fn new(
signature: u64,
k_move: usize,
direction: FlipDirection,
removed_face_vertices: &'a [VertexKey],
inserted_face_vertices: &'a [VertexKey],
) -> Self {
Self {
signature,
k_move,
direction,
removed_face_vertices,
inserted_face_vertices,
}
}
}
fn check_flip_cycle<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
context: FlipCycleContext<'_>,
diagnostics: &mut RepairDiagnostics,
stats: &DelaunayRepairStats,
max_flips: usize,
config: &RepairAttemptConfig,
) -> Result<(), DelaunayRepairError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let repeats = diagnostics
.flip_signature_counts
.get(&context.signature)
.copied()
.unwrap_or(0);
if repeats >= MAX_REPEAT_SIGNATURE {
if repair_trace_enabled() {
let removed_details: Vec<_> = context
.removed_face_vertices
.iter()
.filter_map(|&vkey| tds.vertex(vkey).map(|v| (vkey, *v.point())))
.collect();
let inserted_details: Vec<_> = context
.inserted_face_vertices
.iter()
.filter_map(|&vkey| tds.vertex(vkey).map(|v| (vkey, *v.point())))
.collect();
tracing::debug!(
"[repair] cycle abort signature={} repeats={} flips={} max_flips={} attempt={} order={:?} k={} direction={:?} removed_face={:?} inserted_face={:?}",
context.signature,
repeats,
stats.flips_performed,
max_flips,
config.attempt,
config.queue_order,
context.k_move,
context.direction,
removed_details,
inserted_details,
);
}
diagnostics.record_cycle_abort(context.signature);
return Err(non_convergent_error(max_flips, stats, diagnostics, config));
}
Ok(())
}
fn resolve_facet_handle_for_key<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
handle: FacetHandle,
key: u64,
) -> Option<FacetHandle>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let simplex_key = handle.simplex_key();
let simplex = tds.simplex(simplex_key)?;
let facet_index = usize::from(handle.facet_index());
if facet_index < simplex.number_of_vertices() {
let facet_vertices = facet_vertices_from_simplex(simplex, facet_index);
if facet_key_from_vertices(&facet_vertices) == key {
return Some(handle);
}
}
for candidate_idx in 0..simplex.number_of_vertices() {
let facet_vertices = facet_vertices_from_simplex(simplex, candidate_idx);
if facet_key_from_vertices(&facet_vertices) == key {
let facet_index = u8::try_from(candidate_idx).ok()?;
return Some(FacetHandle::new(simplex_key, facet_index));
}
}
None
}
fn resolve_ridge_handle_for_key<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
handle: RidgeHandle,
key: u64,
) -> Option<RidgeHandle>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 3 {
return None;
}
let simplex_key = handle.simplex_key();
let simplex = tds.simplex(simplex_key)?;
let vertex_count = simplex.number_of_vertices();
let omit_a = usize::from(handle.omit_a());
let omit_b = usize::from(handle.omit_b());
if omit_a < vertex_count && omit_b < vertex_count && omit_a != omit_b {
let ridge_vertices = ridge_vertices_from_simplex(simplex, omit_a, omit_b);
if ridge_vertices.len() == D - 1 && facet_key_from_vertices(&ridge_vertices) == key {
return Some(handle);
}
}
for i in 0..vertex_count {
for j in (i + 1)..vertex_count {
let ridge_vertices = ridge_vertices_from_simplex(simplex, i, j);
if ridge_vertices.len() != D - 1 {
continue;
}
if facet_key_from_vertices(&ridge_vertices) == key {
let omit_a = u8::try_from(i).ok()?;
let omit_b = u8::try_from(j).ok()?;
return Some(RidgeHandle::new(simplex_key, omit_a, omit_b));
}
}
}
None
}
impl BistellarFlipKind {
#[must_use]
pub const fn k(&self) -> usize {
self.k
}
#[must_use]
pub const fn k1(d: usize) -> Self {
Self { k: 1, d }
}
#[must_use]
pub const fn k2(d: usize) -> Self {
Self { k: 2, d }
}
#[must_use]
pub const fn k3(d: usize) -> Self {
Self { k: 3, d }
}
#[must_use]
pub const fn inverse(self) -> Self {
Self {
k: self.d + 2 - self.k,
d: self.d,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ConstK<const K: usize>;
pub trait BistellarMove<const D: usize> {
const K: usize;
}
impl<const D: usize, const K: usize> BistellarMove<D> for ConstK<K> {
const K: usize = K;
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipPredicateOperation {
#[error("replacement-simplex orientation")]
ReplacementSimplexOrientation,
#[error("Delaunay-repair replacement-simplex orientation")]
DelaunayRepairReplacementOrientation,
#[error("degenerate-simplex precheck")]
DegenerateSimplexPrecheck,
#[error("k=2 simplex-A insphere")]
K2SimplexAInSphere,
#[error("k=2 simplex-B insphere")]
K2SimplexBInSphere,
#[error("k=3 simplex insphere")]
K3SimplexInSphere,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipPredicateError {
#[error("{operation} predicate failed: {source}")]
CoordinateConversion {
operation: FlipPredicateOperation,
#[source]
source: CoordinateConversionError,
},
#[error("failed to lift vertex {vertex_key:?} for periodic predicate: {details}")]
PeriodicVertexLift {
vertex_key: VertexKey,
details: String,
},
}
impl FlipPredicateError {
const fn coordinate_conversion(
operation: FlipPredicateOperation,
source: CoordinateConversionError,
) -> Self {
Self::CoordinateConversion { operation, source }
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipContextError {
#[error("k must be in 1..=D+1 (k={k_move}, D={dimension})")]
InvalidMoveSize {
k_move: usize,
dimension: usize,
},
#[error("removed-face must have {expected} vertices, got {found}")]
WrongRemovedFaceArity {
expected: usize,
found: usize,
},
#[error("k={k_move} inserted-face must have {expected} vertices, got {found}")]
WrongInsertedFaceArity {
k_move: usize,
expected: usize,
found: usize,
},
#[error("removed_simplices must have {expected} entries, got {found}")]
WrongRemovedSimplexCount {
expected: usize,
found: usize,
},
#[error("removed-face and inserted-face must be disjoint")]
OverlappingFaces,
#[error(
"replacement periodic offset count {offset_count} does not match replacement simplex count {simplex_count}"
)]
ReplacementPeriodicOffsetCountMismatch {
simplex_count: usize,
offset_count: usize,
},
#[error(
"replacement simplex {simplex_index} is missing periodic offsets for periodic facet parity"
)]
MissingReplacementPeriodicOffsets {
simplex_index: usize,
},
#[error(
"replacement simplex {simplex_index} periodic offset count {offset_count} does not match vertex count {vertex_count}"
)]
ReplacementPeriodicOffsetLengthMismatch {
simplex_index: usize,
offset_count: usize,
vertex_count: usize,
},
#[error(
"conflicting replacement-simplex orientation constraints between local simplices {source_simplex_index} and {target_simplex_index}"
)]
ConflictingReplacementOrientationBetweenSimplices {
source_simplex_index: usize,
target_simplex_index: usize,
},
#[error("replacement simplex needs at least two vertices to flip orientation")]
ReplacementSimplexTooSmallForOrientationFlip,
#[error("replacement orientation index {simplex_index} out of range")]
ReplacementOrientationIndexOutOfRange {
simplex_index: usize,
},
#[error(
"conflicting replacement-simplex orientation constraints for local simplex {simplex_index}"
)]
ConflictingReplacementOrientationForSimplex {
simplex_index: usize,
},
#[error("could not derive replacement facet-order permutation parity")]
FacetOrderParityUnavailable,
#[error(
"facet index {facet_index} out of range for replacement simplex with {vertex_count} vertices"
)]
ReplacementFacetIndexOutOfRange {
facet_index: usize,
vertex_count: usize,
},
#[error("k=2 facet must have {expected} vertices, got {found}")]
K2FacetArity {
expected: usize,
found: usize,
},
#[error("k=2 opposites must be distinct and not in the facet")]
InvalidK2Opposites,
#[error("k=3 ridge must have {expected} vertices, got {found}")]
K3RidgeArity {
expected: usize,
found: usize,
},
#[error(
"conflicting periodic frame translations while aligning vertex {vertex_key:?} from simplex {source_simplex_key:?} into frame {target_simplex_key:?}: expected {expected_offset:?}, got {found_offset:?}"
)]
ConflictingPeriodicFrameTranslation {
vertex_key: VertexKey,
source_simplex_key: SimplexKey,
target_simplex_key: SimplexKey,
expected_offset: Vec<i8>,
found_offset: Vec<i8>,
},
#[error(
"conflicting periodic frame translations while aligning vertex {vertex_key:?} from external simplex {source_simplex_key:?} into replacement simplex {target_simplex_index}: expected {expected_offset:?}, got {found_offset:?}"
)]
ConflictingReplacementPeriodicFrameTranslation {
vertex_key: VertexKey,
source_simplex_key: SimplexKey,
target_simplex_index: usize,
expected_offset: Vec<i8>,
found_offset: Vec<i8>,
},
#[error("cannot align periodic vertex {vertex_key:?} into frame {target_simplex_key:?}")]
PeriodicVertexAlignmentFailed {
vertex_key: VertexKey,
target_simplex_key: SimplexKey,
},
#[error(
"simplex {simplex_key:?} periodic offset count {offset_count} does not match vertex count {vertex_count}"
)]
PeriodicOffsetCountMismatch {
simplex_key: SimplexKey,
offset_count: usize,
vertex_count: usize,
},
#[error("periodic offset subtraction overflow on axis {axis}")]
PeriodicOffsetSubtractionOverflow {
axis: usize,
},
#[error("periodic offset addition overflow on axis {axis}")]
PeriodicOffsetAdditionOverflow {
axis: usize,
},
#[error("inverse flip predicate requires at least one removed simplex frame")]
MissingRemovedSimplexFrame,
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipFailureKind {
#[error("unsupported dimension")]
UnsupportedDimension,
#[error("boundary facet")]
BoundaryFacet,
#[error("missing simplex")]
MissingSimplex,
#[error("missing vertex")]
MissingVertex,
#[error("missing neighbor")]
MissingNeighbor,
#[error("dangling ridge neighbor")]
DanglingRidgeNeighbor,
#[error("invalid facet adjacency")]
InvalidFacetAdjacency,
#[error("invalid facet index")]
InvalidFacetIndex,
#[error("invalid ridge index")]
InvalidRidgeIndex,
#[error("invalid ridge adjacency")]
InvalidRidgeAdjacency,
#[error("invalid ridge multiplicity")]
InvalidRidgeMultiplicity,
#[error("invalid edge multiplicity")]
InvalidEdgeMultiplicity,
#[error("invalid triangle multiplicity")]
InvalidTriangleMultiplicity,
#[error("invalid edge adjacency")]
InvalidEdgeAdjacency,
#[error("invalid triangle adjacency")]
InvalidTriangleAdjacency,
#[error("invalid vertex multiplicity")]
InvalidVertexMultiplicity,
#[error("invalid vertex adjacency")]
InvalidVertexAdjacency,
#[error("invalid flip context")]
InvalidFlipContext,
#[error("predicate failure")]
PredicateFailure,
#[error("degenerate simplex")]
DegenerateSimplex,
#[error("negative orientation")]
NegativeOrientation,
#[error("duplicate simplex")]
DuplicateSimplex,
#[error("non-manifold facet")]
NonManifoldFacet,
#[error("inserted simplex already exists")]
InsertedSimplexAlreadyExists,
#[error("simplex creation")]
SimplexCreation,
#[error("neighbor wiring")]
NeighborWiring,
#[error("trial validation")]
TrialValidation,
#[error("wiring validation")]
WiringValidation,
#[error("Delaunay repair failed")]
DelaunayRepairFailed,
#[error("TDS mutation")]
TdsMutation,
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborCavityFailureKind {
#[error("missing boundary simplex")]
MissingBoundarySimplex,
#[error("missing inserted vertex")]
MissingInsertedVertex,
#[error("wrong simplex arity")]
WrongSimplexArity,
#[error("invalid facet index")]
InvalidFacetIndex,
#[error("simplex creation")]
SimplexCreation,
#[error("simplex insertion")]
SimplexInsertion,
#[error("initial simplex construction")]
InitialSimplexConstruction,
#[error("rebuilt vertex missing")]
RebuiltVertexMissing,
#[error("empty conflict region")]
EmptyConflictRegion,
#[error("empty boundary")]
EmptyBoundary,
#[error("invalid facet sharing after repair")]
InvalidFacetSharingAfterRepair,
#[error("neighbor rebuild")]
NeighborRebuild,
#[error("perturbation scale conversion")]
PerturbationScaleConversion,
#[error("unsupported degenerate location")]
UnsupportedDegenerateLocation,
#[error("empty fan triangulation")]
EmptyFanTriangulation,
}
impl From<&CavityFillingError> for FlipNeighborCavityFailureKind {
fn from(source: &CavityFillingError) -> Self {
match source {
CavityFillingError::MissingBoundarySimplex { .. } => Self::MissingBoundarySimplex,
CavityFillingError::MissingInsertedVertex { .. } => Self::MissingInsertedVertex,
CavityFillingError::WrongSimplexArity { .. } => Self::WrongSimplexArity,
CavityFillingError::InvalidFacetIndex { .. } => Self::InvalidFacetIndex,
CavityFillingError::SimplexCreation { .. } => Self::SimplexCreation,
CavityFillingError::SimplexInsertion { .. } => Self::SimplexInsertion,
CavityFillingError::InitialSimplexConstruction { .. } => {
Self::InitialSimplexConstruction
}
CavityFillingError::RebuiltVertexMissing { .. } => Self::RebuiltVertexMissing,
CavityFillingError::EmptyConflictRegion { .. } => Self::EmptyConflictRegion,
CavityFillingError::EmptyBoundary { .. } => Self::EmptyBoundary,
CavityFillingError::InvalidFacetSharingAfterRepair { .. } => {
Self::InvalidFacetSharingAfterRepair
}
CavityFillingError::NeighborRebuild { .. } => Self::NeighborRebuild,
CavityFillingError::PerturbationScaleConversion { .. } => {
Self::PerturbationScaleConversion
}
CavityFillingError::UnsupportedDegenerateLocation { .. } => {
Self::UnsupportedDegenerateLocation
}
CavityFillingError::EmptyFanTriangulation => Self::EmptyFanTriangulation,
}
}
}
impl From<CavityFillingError> for FlipNeighborCavityFailureKind {
fn from(source: CavityFillingError) -> Self {
Self::from(&source)
}
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborHullExtensionFailureKind {
#[error("no visible facets")]
NoVisibleFacets,
#[error("invalid patch")]
InvalidPatch,
#[error("predicate failed")]
PredicateFailed,
#[error("TDS")]
Tds,
#[error("other")]
Other,
}
impl From<&HullExtensionReason> for FlipNeighborHullExtensionFailureKind {
fn from(source: &HullExtensionReason) -> Self {
match source {
HullExtensionReason::NoVisibleFacets => Self::NoVisibleFacets,
HullExtensionReason::InvalidPatch { .. } => Self::InvalidPatch,
HullExtensionReason::PredicateFailed(_) => Self::PredicateFailed,
HullExtensionReason::Tds(_) => Self::Tds,
HullExtensionReason::Other { .. } => Self::Other,
}
}
}
impl From<HullExtensionReason> for FlipNeighborHullExtensionFailureKind {
fn from(source: HullExtensionReason) -> Self {
Self::from(&source)
}
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborDelaunayValidationFailureKind {
#[error("TDS")]
Tds,
#[error("triangulation")]
Triangulation,
#[error("verification failed")]
VerificationFailed,
#[error("repair failed")]
RepairFailed,
#[error("repair operation failed")]
RepairOperationFailed,
}
impl From<&DelaunayTriangulationValidationError> for FlipNeighborDelaunayValidationFailureKind {
fn from(source: &DelaunayTriangulationValidationError) -> Self {
match source {
DelaunayTriangulationValidationError::Tds(_) => Self::Tds,
DelaunayTriangulationValidationError::Triangulation(_) => Self::Triangulation,
DelaunayTriangulationValidationError::VerificationFailed { .. } => {
Self::VerificationFailed
}
DelaunayTriangulationValidationError::RepairFailed { .. } => Self::RepairFailed,
DelaunayTriangulationValidationError::RepairOperationFailed { .. } => {
Self::RepairOperationFailed
}
}
}
}
impl From<DelaunayTriangulationValidationError> for FlipNeighborDelaunayValidationFailureKind {
fn from(source: DelaunayTriangulationValidationError) -> Self {
Self::from(&source)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FlipNeighborRepairDiagnostics {
pub facets_checked: usize,
pub flips_performed: usize,
pub max_queue_len: usize,
pub ambiguous_predicates: usize,
pub predicate_failures: usize,
pub cycle_detections: usize,
pub attempt: usize,
pub queue_order: RepairQueueOrder,
}
impl fmt::Display for FlipNeighborRepairDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"checked={}, flips={}, max_queue={}, ambiguous={}, predicate_failures={}, cycles={}, attempt={}, order={:?}",
self.facets_checked,
self.flips_performed,
self.max_queue_len,
self.ambiguous_predicates,
self.predicate_failures,
self.cycle_detections,
self.attempt,
self.queue_order
)
}
}
impl From<DelaunayRepairDiagnostics> for FlipNeighborRepairDiagnostics {
fn from(source: DelaunayRepairDiagnostics) -> Self {
Self {
facets_checked: source.facets_checked,
flips_performed: source.flips_performed,
max_queue_len: source.max_queue_len,
ambiguous_predicates: source.ambiguous_predicates,
predicate_failures: source.predicate_failures,
cycle_detections: source.cycle_detections,
attempt: source.attempt,
queue_order: source.queue_order,
}
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborRepairFailure {
#[error("repair did not converge after {max_flips} flips ({diagnostics})")]
NonConvergent {
max_flips: usize,
diagnostics: FlipNeighborRepairDiagnostics,
},
#[error("repair postcondition failed: {message}")]
PostconditionFailed {
message: String,
},
#[error("repair verification failed during {context}: {source_kind}")]
VerificationFailed {
context: DelaunayRepairVerificationContext,
source_kind: FlipFailureKind,
},
#[error("repair orientation canonicalization failed: {message}")]
OrientationCanonicalizationFailed {
message: String,
},
#[error("repair requires {required:?} topology, found {found:?}: {message}")]
InvalidTopology {
required: TopologyGuarantee,
found: TopologyGuarantee,
message: &'static str,
},
#[error("heuristic rebuild failed: {message}")]
HeuristicRebuildFailed {
message: String,
},
#[error("flip error: {source_kind}")]
Flip {
source_kind: FlipFailureKind,
},
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipNeighborWiringError {
#[error("flip boundary extraction failed: {source}")]
BoundaryExtraction {
#[source]
source: ConflictError,
},
#[error("neighbor wiring failed: {source}")]
NeighborWiring {
#[source]
source: NeighborWiringError,
},
#[error("non-manifold topology: facet {facet_hash:#x} shared by {simplex_count} simplices")]
NonManifoldTopology {
facet_hash: u64,
simplex_count: usize,
},
#[error("topology validation failed during neighbor wiring: {source}")]
TopologyValidation {
#[source]
source: TdsValidationFailure,
},
#[error("conflict-region error reached flip neighbor wiring: {source}")]
ConflictRegion {
#[source]
source: ConflictError,
},
#[error("point-location error reached flip neighbor wiring: {source}")]
Location {
#[source]
source: LocateError,
},
#[error("cavity filling error reached flip neighbor wiring: {reason}")]
CavityFilling {
reason: FlipNeighborCavityFailureKind,
},
#[error("hull extension error reached flip neighbor wiring: {reason}")]
HullExtension {
reason: FlipNeighborHullExtensionFailureKind,
},
#[error("Delaunay validation error reached flip neighbor wiring: {reason}")]
DelaunayValidation {
reason: FlipNeighborDelaunayValidationFailureKind,
},
#[error("Delaunay repair error reached flip neighbor wiring: {reason}")]
DelaunayRepair {
#[source]
reason: FlipNeighborRepairFailure,
},
#[error("duplicate coordinates reached flip neighbor wiring: {coordinates}")]
DuplicateCoordinates {
coordinates: String,
},
#[error("duplicate UUID reached flip neighbor wiring: {entity:?} {uuid}")]
DuplicateUuid {
entity: EntityKind,
uuid: uuid::Uuid,
},
#[error("topology validation error reached flip neighbor wiring: {message}: {source}")]
TopologyValidationFailed {
message: String,
#[source]
source: TriangulationValidationError,
},
#[error(
"local repair removal budget reached flip neighbor wiring: attempted {attempted}, max {max_simplices_removed}"
)]
MaxSimplicesRemovedExceeded {
max_simplices_removed: usize,
attempted: usize,
},
}
impl From<InsertionError> for FlipNeighborWiringError {
fn from(source: InsertionError) -> Self {
match source {
InsertionError::NeighborWiring { reason } => Self::NeighborWiring { source: reason },
InsertionError::NonManifoldTopology {
facet_hash,
simplex_count,
} => Self::NonManifoldTopology {
facet_hash,
simplex_count,
},
InsertionError::TopologyValidation(source) => Self::TopologyValidation {
source: source.into(),
},
InsertionError::ConflictRegion(source) => Self::ConflictRegion { source },
InsertionError::Location(source) => Self::Location { source },
InsertionError::CavityFilling { reason } => Self::CavityFilling {
reason: reason.into(),
},
InsertionError::HullExtension { reason } => Self::HullExtension {
reason: reason.into(),
},
InsertionError::DelaunayValidationFailed { source } => Self::DelaunayValidation {
reason: source.into(),
},
InsertionError::DelaunayRepairFailed { source, context: _ } => Self::DelaunayRepair {
reason: FlipNeighborRepairFailure::from(*source),
},
InsertionError::DuplicateCoordinates { coordinates } => {
Self::DuplicateCoordinates { coordinates }
}
InsertionError::DuplicateUuid { entity, uuid } => Self::DuplicateUuid { entity, uuid },
InsertionError::TopologyValidationFailed { message, source } => {
Self::TopologyValidationFailed { message, source }
}
InsertionError::MaxSimplicesRemovedExceeded {
max_simplices_removed,
attempted,
} => Self::MaxSimplicesRemovedExceeded {
max_simplices_removed,
attempted,
},
}
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipMutationError {
#[error("vertex insertion failed: {source}")]
VertexInsertion {
#[source]
source: TdsConstructionFailure,
},
#[error("simplex insertion failed: {source}")]
SimplexInsertion {
#[source]
source: TdsConstructionFailure,
},
#[error(
"trial TDS validation failed after bistellar flip (k={k_move}, direction={direction:?}): {source}"
)]
TrialValidation {
k_move: usize,
direction: FlipDirection,
#[source]
source: TdsValidationFailure,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipEdgeAdjacencyError {
#[error("edge endpoints must be distinct ({vertex_key:?})")]
DuplicateEndpoints {
vertex_key: VertexKey,
},
#[error("simplex {simplex_key:?} does not contain edge vertices {v0:?} and {v1:?}")]
SimplexMissingEdgeVertices {
simplex_key: SimplexKey,
v0: VertexKey,
v1: VertexKey,
},
#[error(
"edge star must have {expected_vertices} distinct opposite vertices each appearing {expected_occurrences} times, found {found_vertices} distinct vertices"
)]
InvalidOppositeVertexIncidence {
expected_vertices: usize,
found_vertices: usize,
expected_occurrences: usize,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipTriangleAdjacencyError {
#[error("simplex {simplex_key:?} does not contain triangle vertices {a:?}, {b:?}, and {c:?}")]
SimplexMissingTriangleVertices {
simplex_key: SimplexKey,
a: VertexKey,
b: VertexKey,
c: VertexKey,
},
#[error(
"triangle star must have {expected_vertices} ridge vertices each appearing {expected_occurrences} times, found {found_vertices} distinct vertices"
)]
InvalidRidgeVertexIncidence {
expected_vertices: usize,
found_vertices: usize,
expected_occurrences: usize,
},
}
#[derive(Clone, Copy, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipVertexAdjacencyError {
#[error("simplex {simplex_key:?} does not contain vertex {vertex_key:?}")]
SimplexMissingVertex {
simplex_key: SimplexKey,
vertex_key: VertexKey,
},
#[error(
"vertex star must have {expected_vertices} link vertices each appearing {expected_occurrences} times, found {found_vertices} distinct vertices"
)]
InvalidLinkVertexIncidence {
expected_vertices: usize,
found_vertices: usize,
expected_occurrences: usize,
},
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum FlipError {
#[error("Bistellar flip not supported for D={dimension}")]
UnsupportedDimension {
dimension: usize,
},
#[error("Facet {facet:?} is on the boundary (no neighbor)")]
BoundaryFacet {
facet: FacetHandle,
},
#[error("Simplex not found: {simplex_key:?}")]
MissingSimplex {
simplex_key: SimplexKey,
},
#[error("Vertex not found: {vertex_key:?}")]
MissingVertex {
vertex_key: VertexKey,
},
#[error("Neighbor simplex {neighbor_key:?} not found for facet {facet:?}")]
MissingNeighbor {
facet: FacetHandle,
neighbor_key: SimplexKey,
},
#[error(
"Ridge adjacency from simplex {simplex_key:?} references missing neighbor {neighbor_key:?}"
)]
DanglingRidgeNeighbor {
simplex_key: SimplexKey,
neighbor_key: SimplexKey,
},
#[error(
"Facet adjacency mismatch between simplex {simplex_key:?} and neighbor {neighbor_key:?}"
)]
InvalidFacetAdjacency {
simplex_key: SimplexKey,
neighbor_key: SimplexKey,
},
#[error(
"Facet index {facet_index} out of bounds for simplex {simplex_key:?} with {vertex_count} vertices"
)]
InvalidFacetIndex {
simplex_key: SimplexKey,
facet_index: u8,
vertex_count: usize,
},
#[error(
"Ridge indices ({omit_a}, {omit_b}) out of bounds for simplex {simplex_key:?} with {vertex_count} vertices"
)]
InvalidRidgeIndex {
simplex_key: SimplexKey,
omit_a: u8,
omit_b: u8,
vertex_count: usize,
},
#[error("Ridge adjacency mismatch for simplex {simplex_key:?}")]
InvalidRidgeAdjacency {
simplex_key: SimplexKey,
},
#[error("Ridge has invalid multiplicity {found}, expected 3")]
InvalidRidgeMultiplicity {
found: usize,
},
#[error("Edge has invalid multiplicity {found}, expected {expected}")]
InvalidEdgeMultiplicity {
found: usize,
expected: usize,
},
#[error("Triangle has invalid multiplicity {found}, expected {expected}")]
InvalidTriangleMultiplicity {
found: usize,
expected: usize,
},
#[error("Edge adjacency mismatch: {reason}")]
InvalidEdgeAdjacency {
reason: FlipEdgeAdjacencyError,
},
#[error("Triangle adjacency mismatch: {reason}")]
InvalidTriangleAdjacency {
reason: FlipTriangleAdjacencyError,
},
#[error("Vertex star has invalid multiplicity {found}, expected {expected}")]
InvalidVertexMultiplicity {
found: usize,
expected: usize,
},
#[error("Vertex adjacency mismatch: {reason}")]
InvalidVertexAdjacency {
reason: FlipVertexAdjacencyError,
},
#[error("Flip context invalid: {reason}")]
InvalidFlipContext {
#[source]
reason: FlipContextError,
},
#[error("Geometric predicate failed: {reason}")]
PredicateFailure {
#[source]
reason: FlipPredicateError,
},
#[error("Flip would create a degenerate simplex (zero orientation)")]
DegenerateSimplex,
#[error(
"Delaunay repair would create a negative-orientation replacement simplex {simplex_vertices:?}"
)]
NegativeOrientation {
simplex_vertices: Vec<VertexKey>,
},
#[error("Flip would create a duplicate simplex")]
DuplicateSimplex,
#[error("Flip would create a non-manifold facet")]
NonManifoldFacet,
#[error(
"Flip would insert simplex that already exists (k={k_move}, simplex={simplex_vertices:?}, existing_simplex={existing_simplex:?})"
)]
InsertedSimplexAlreadyExists {
k_move: usize,
simplex_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
existing_simplex: SimplexKey,
},
#[error(transparent)]
SimplexCreation(#[from] SimplexValidationError),
#[error("Neighbor wiring failed: {reason}")]
NeighborWiring {
#[source]
reason: FlipNeighborWiringError,
},
#[error("TDS mutation failed: {reason}")]
TdsMutation {
#[source]
reason: FlipMutationError,
},
}
impl From<FlipContextError> for FlipError {
fn from(reason: FlipContextError) -> Self {
Self::InvalidFlipContext { reason }
}
}
impl From<FlipPredicateError> for FlipError {
fn from(reason: FlipPredicateError) -> Self {
Self::PredicateFailure { reason }
}
}
impl From<FlipNeighborWiringError> for FlipError {
fn from(reason: FlipNeighborWiringError) -> Self {
Self::NeighborWiring { reason }
}
}
impl From<FlipMutationError> for FlipError {
fn from(reason: FlipMutationError) -> Self {
Self::TdsMutation { reason }
}
}
impl From<&FlipError> for FlipFailureKind {
fn from(source: &FlipError) -> Self {
match source {
FlipError::UnsupportedDimension { .. } => Self::UnsupportedDimension,
FlipError::BoundaryFacet { .. } => Self::BoundaryFacet,
FlipError::MissingSimplex { .. } => Self::MissingSimplex,
FlipError::MissingVertex { .. } => Self::MissingVertex,
FlipError::MissingNeighbor { .. } => Self::MissingNeighbor,
FlipError::DanglingRidgeNeighbor { .. } => Self::DanglingRidgeNeighbor,
FlipError::InvalidFacetAdjacency { .. } => Self::InvalidFacetAdjacency,
FlipError::InvalidFacetIndex { .. } => Self::InvalidFacetIndex,
FlipError::InvalidRidgeIndex { .. } => Self::InvalidRidgeIndex,
FlipError::InvalidRidgeAdjacency { .. } => Self::InvalidRidgeAdjacency,
FlipError::InvalidRidgeMultiplicity { .. } => Self::InvalidRidgeMultiplicity,
FlipError::InvalidEdgeMultiplicity { .. } => Self::InvalidEdgeMultiplicity,
FlipError::InvalidTriangleMultiplicity { .. } => Self::InvalidTriangleMultiplicity,
FlipError::InvalidEdgeAdjacency { .. } => Self::InvalidEdgeAdjacency,
FlipError::InvalidTriangleAdjacency { .. } => Self::InvalidTriangleAdjacency,
FlipError::InvalidVertexMultiplicity { .. } => Self::InvalidVertexMultiplicity,
FlipError::InvalidVertexAdjacency { .. } => Self::InvalidVertexAdjacency,
FlipError::InvalidFlipContext { .. } => Self::InvalidFlipContext,
FlipError::PredicateFailure { .. } => Self::PredicateFailure,
FlipError::DegenerateSimplex => Self::DegenerateSimplex,
FlipError::NegativeOrientation { .. } => Self::NegativeOrientation,
FlipError::DuplicateSimplex => Self::DuplicateSimplex,
FlipError::NonManifoldFacet => Self::NonManifoldFacet,
FlipError::InsertedSimplexAlreadyExists { .. } => Self::InsertedSimplexAlreadyExists,
FlipError::SimplexCreation(_) => Self::SimplexCreation,
FlipError::NeighborWiring { reason } => match reason {
FlipNeighborWiringError::TopologyValidation { .. }
| FlipNeighborWiringError::DelaunayValidation { .. }
| FlipNeighborWiringError::TopologyValidationFailed { .. } => {
Self::WiringValidation
}
FlipNeighborWiringError::DelaunayRepair { .. } => Self::DelaunayRepairFailed,
_ => Self::NeighborWiring,
},
FlipError::TdsMutation {
reason: FlipMutationError::TrialValidation { .. },
} => Self::TrialValidation,
FlipError::TdsMutation { .. } => Self::TdsMutation,
}
}
}
impl From<FlipError> for FlipFailureKind {
fn from(source: FlipError) -> Self {
Self::from(&source)
}
}
#[derive(Debug, Clone)]
pub struct FlipInfo<const D: usize> {
pub kind: BistellarFlipKind,
pub direction: FlipDirection,
pub removed_simplices: SimplexKeyBuffer,
pub new_simplices: SimplexKeyBuffer,
pub removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
pub inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
}
#[derive(Debug, Clone)]
struct AppliedFlip<const D: usize> {
info: FlipInfo<D>,
removed_simplex_vertices: RemovedSimplexVertexSnapshot,
}
#[derive(Debug, Clone)]
pub(crate) struct FlipContext<const D: usize, const K: usize> {
pub removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
pub inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
pub removed_simplices: SimplexKeyBuffer,
pub direction: FlipDirection,
}
#[derive(Debug, Clone)]
pub(crate) struct FlipContextDyn<const D: usize> {
pub removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
pub inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
pub removed_simplices: SimplexKeyBuffer,
pub direction: FlipDirection,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TriangleHandle {
v0: VertexKey,
v1: VertexKey,
v2: VertexKey,
}
impl TriangleHandle {
#[must_use]
pub fn new(a: VertexKey, b: VertexKey, c: VertexKey) -> Self {
let mut verts = [a, b, c];
verts.sort_unstable_by_key(|v| v.data().as_ffi());
Self {
v0: verts[0],
v1: verts[1],
v2: verts[2],
}
}
#[must_use]
pub const fn vertices(self) -> [VertexKey; 3] {
[self.v0, self.v1, self.v2]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RidgeHandle {
simplex_key: SimplexKey,
omit_a: u8,
omit_b: u8,
}
impl RidgeHandle {
#[must_use]
pub const fn new(simplex_key: SimplexKey, omit_a: u8, omit_b: u8) -> Self {
if omit_a <= omit_b {
Self {
simplex_key,
omit_a,
omit_b,
}
} else {
Self {
simplex_key,
omit_a: omit_b,
omit_b: omit_a,
}
}
}
#[must_use]
pub const fn simplex_key(&self) -> SimplexKey {
self.simplex_key
}
#[must_use]
pub const fn omit_a(&self) -> u8 {
self.omit_a
}
#[must_use]
pub const fn omit_b(&self) -> u8 {
self.omit_b
}
}
#[derive(Debug, Clone, Default)]
pub struct DelaunayRepairStats {
pub facets_checked: usize,
pub flips_performed: usize,
pub max_queue_len: usize,
}
#[expect(
clippy::struct_field_names,
reason = "phase timing telemetry keeps units explicit on every exported field"
)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct LocalRepairPhaseTiming {
pub(crate) snapshot_nanos: u64,
pub(crate) attempt_nanos: u64,
pub(crate) attempt_seed_nanos: u64,
pub(crate) attempt_facet_nanos: u64,
pub(crate) attempt_ridge_nanos: u64,
pub(crate) attempt_edge_nanos: u64,
pub(crate) attempt_triangle_nanos: u64,
pub(crate) postcondition_nanos: u64,
pub(crate) restore_nanos: u64,
}
impl LocalRepairPhaseTiming {
fn record_snapshot(&mut self, elapsed: Duration) {
self.snapshot_nanos = self
.snapshot_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_attempt(&mut self, elapsed: Duration) {
self.attempt_nanos = self
.attempt_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_attempt_seed(&mut self, elapsed: Duration) {
self.attempt_seed_nanos = self
.attempt_seed_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_attempt_facet(&mut self, elapsed: Duration) {
self.attempt_facet_nanos = self
.attempt_facet_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_attempt_ridge(&mut self, elapsed: Duration) {
self.attempt_ridge_nanos = self
.attempt_ridge_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_attempt_edge(&mut self, elapsed: Duration) {
self.attempt_edge_nanos = self
.attempt_edge_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_attempt_triangle(&mut self, elapsed: Duration) {
self.attempt_triangle_nanos = self
.attempt_triangle_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_postcondition(&mut self, elapsed: Duration) {
self.postcondition_nanos = self
.postcondition_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
fn record_restore(&mut self, elapsed: Duration) {
self.restore_nanos = self
.restore_nanos
.saturating_add(duration_nanos_saturating(elapsed));
}
}
fn publish_local_repair_phase_timing(
timing: &mut Option<&mut LocalRepairPhaseTiming>,
phase_timing: LocalRepairPhaseTiming,
) {
if let Some(timing) = timing.as_deref_mut() {
*timing = phase_timing;
}
}
#[derive(Debug, Clone)]
pub(crate) struct DelaunayRepairRun {
pub stats: DelaunayRepairStats,
pub touched_simplices: SimplexKeyBuffer,
pub used_full_reseed: bool,
}
#[derive(Debug)]
struct RepairAttemptOutcome {
postcondition_required: bool,
stats: DelaunayRepairStats,
last_applied_flip: Option<LastAppliedFlip>,
touched_simplices: SimplexKeyBuffer,
used_full_reseed: bool,
}
const fn repair_postcondition_required(
stats: &DelaunayRepairStats,
diagnostics: &RepairDiagnostics,
) -> bool {
stats.flips_performed > 0 || diagnostics.saw_applicable_repair_site
}
fn record_touched_simplices(
touched_simplices: &mut SimplexKeyBuffer,
touched_simplex_set: &mut FastHashSet<SimplexKey>,
new_simplices: &[SimplexKey],
) {
for &simplex_key in new_simplices {
if touched_simplex_set.insert(simplex_key) {
touched_simplices.push(simplex_key);
}
}
}
fn local_postcondition_frontier(
seed_simplices: &[SimplexKey],
touched_simplices: &[SimplexKey],
) -> SimplexKeyBuffer {
let mut frontier = SimplexKeyBuffer::new();
let mut seen = FastHashSet::<SimplexKey>::default();
for &simplex_key in seed_simplices.iter().chain(touched_simplices) {
if seen.insert(simplex_key) {
frontier.push(simplex_key);
}
}
frontier
}
fn repair_run_from_attempt(outcome: RepairAttemptOutcome) -> DelaunayRepairRun {
let RepairAttemptOutcome {
stats,
touched_simplices,
used_full_reseed,
..
} = outcome;
DelaunayRepairRun {
stats,
touched_simplices,
used_full_reseed,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RepairQueueOrder {
Fifo,
Lifo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DelaunayRepairDiagnostics {
pub facets_checked: usize,
pub flips_performed: usize,
pub max_queue_len: usize,
pub ambiguous_predicates: usize,
pub ambiguous_predicate_samples: Vec<u64>,
pub predicate_failures: usize,
pub cycle_detections: usize,
pub cycle_signature_samples: Vec<u64>,
pub attempt: usize,
pub queue_order: RepairQueueOrder,
}
impl fmt::Display for DelaunayRepairDiagnostics {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"checked {} facets, ambiguous={}, max_queue={}, flips={}, attempt={}, order={:?}, predicate_failures={}, cycles={}, cycle_samples={:?}",
self.facets_checked,
self.ambiguous_predicates,
self.max_queue_len,
self.flips_performed,
self.attempt,
self.queue_order,
self.predicate_failures,
self.cycle_detections,
self.cycle_signature_samples
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairVerificationContext {
PostRepairVerification,
StrictValidation,
LocalK2DegeneracyVerification,
LocalK2PostconditionVerification,
LocalK3DegeneracyVerification,
LocalK3PostconditionVerification,
LocalInverseK2PostconditionVerification,
LocalInverseK3PostconditionVerification,
}
impl fmt::Display for DelaunayRepairVerificationContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PostRepairVerification => f.write_str("post-repair verification"),
Self::StrictValidation => f.write_str("strict validation"),
Self::LocalK2DegeneracyVerification => f.write_str("local k=2 degeneracy verification"),
Self::LocalK2PostconditionVerification => {
f.write_str("local k=2 postcondition verification")
}
Self::LocalK3DegeneracyVerification => f.write_str("local k=3 degeneracy verification"),
Self::LocalK3PostconditionVerification => {
f.write_str("local k=3 postcondition verification")
}
Self::LocalInverseK2PostconditionVerification => {
f.write_str("local inverse k=2 postcondition verification")
}
Self::LocalInverseK3PostconditionVerification => {
f.write_str("local inverse k=3 postcondition verification")
}
}
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayRepairError {
#[error("Delaunay repair failed to converge after {max_flips} flips ({diagnostics})")]
NonConvergent {
max_flips: usize,
diagnostics: Box<DelaunayRepairDiagnostics>,
},
#[error("Delaunay repair postcondition failed: {message}")]
PostconditionFailed {
message: String,
},
#[error("Delaunay repair verification failed during {context}: {source}")]
VerificationFailed {
context: DelaunayRepairVerificationContext,
#[source]
source: Box<FlipError>,
},
#[error("Delaunay repair orientation canonicalization failed: {message}")]
OrientationCanonicalizationFailed {
message: String,
},
#[error("Delaunay repair requires {required:?} topology, found {found:?}: {message}")]
InvalidTopology {
required: TopologyGuarantee,
found: TopologyGuarantee,
message: &'static str,
},
#[error("Heuristic rebuild failed: {message}")]
HeuristicRebuildFailed {
message: String,
},
#[error("flip error: {source}")]
Flip {
#[source]
source: Box<FlipError>,
},
}
impl From<FlipError> for DelaunayRepairError {
fn from(source: FlipError) -> Self {
Self::Flip {
source: Box::new(source),
}
}
}
impl From<DelaunayRepairError> for FlipNeighborRepairFailure {
fn from(source: DelaunayRepairError) -> Self {
match source {
DelaunayRepairError::NonConvergent {
max_flips,
diagnostics,
} => Self::NonConvergent {
max_flips,
diagnostics: (*diagnostics).into(),
},
DelaunayRepairError::PostconditionFailed { message } => {
Self::PostconditionFailed { message }
}
DelaunayRepairError::VerificationFailed { context, source } => {
Self::VerificationFailed {
context,
source_kind: FlipFailureKind::from(source.as_ref()),
}
}
DelaunayRepairError::OrientationCanonicalizationFailed { message } => {
Self::OrientationCanonicalizationFailed { message }
}
DelaunayRepairError::InvalidTopology {
required,
found,
message,
} => Self::InvalidTopology {
required,
found,
message,
},
DelaunayRepairError::HeuristicRebuildFailed { message } => {
Self::HeuristicRebuildFailed { message }
}
DelaunayRepairError::Flip { source } => Self::Flip {
source_kind: FlipFailureKind::from(source.as_ref()),
},
}
}
}
pub(crate) fn build_k2_flip_context<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
facet: FacetHandle,
) -> Result<FlipContext<D, 2>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 2 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let simplex_a_key = facet.simplex_key();
let simplex_a = tds
.simplex(simplex_a_key)
.ok_or(FlipError::MissingSimplex {
simplex_key: simplex_a_key,
})?;
let facet_index_a = usize::from(facet.facet_index());
let vertex_count = simplex_a.number_of_vertices();
if facet_index_a >= vertex_count {
return Err(FlipError::InvalidFacetIndex {
simplex_key: simplex_a_key,
facet_index: facet.facet_index(),
vertex_count,
});
}
let neighbor_key = simplex_a
.neighbor_key(facet_index_a)
.flatten()
.ok_or(FlipError::BoundaryFacet { facet })?;
let simplex_b = tds
.simplex(neighbor_key)
.ok_or(FlipError::MissingNeighbor {
facet,
neighbor_key,
})?;
let Some(facet_index_b) = simplex_a
.mirror_facet_index(facet_index_a, simplex_b)
.or_else(|| back_reference_facet_index(simplex_a_key, simplex_b))
else {
return Err(FlipError::InvalidFacetAdjacency {
simplex_key: simplex_a_key,
neighbor_key,
});
};
let opposite_a = simplex_a.vertices()[facet_index_a];
let opposite_b = simplex_b.vertices()[facet_index_b];
let shared_facet = facet_vertices_from_simplex(simplex_a, facet_index_a);
if shared_facet.len() != D {
return Err(FlipError::InvalidFacetAdjacency {
simplex_key: simplex_a_key,
neighbor_key,
});
}
if shared_facet.contains(&opposite_a)
|| shared_facet.contains(&opposite_b)
|| opposite_a == opposite_b
{
return Err(FlipError::InvalidFacetAdjacency {
simplex_key: simplex_a_key,
neighbor_key,
});
}
for &v in &shared_facet {
if !simplex_b.contains_vertex(v) {
return Err(FlipError::InvalidFacetAdjacency {
simplex_key: simplex_a_key,
neighbor_key,
});
}
}
let removed_simplices: SimplexKeyBuffer = [simplex_a_key, neighbor_key].into_iter().collect();
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(2);
inserted_face_vertices.push(opposite_a);
inserted_face_vertices.push(opposite_b);
Ok(FlipContext {
removed_face_vertices: shared_facet,
inserted_face_vertices,
removed_simplices,
direction: FlipDirection::Forward,
})
}
fn back_reference_facet_index<T, U, V, const D: usize>(
source_simplex: SimplexKey,
neighbor_simplex: &Simplex<T, U, V, D>,
) -> Option<usize> {
neighbor_simplex
.neighbor_keys()?
.position(|neighbor| neighbor == Some(source_simplex))
}
fn increment_vertex_count(
counts: &mut SmallBuffer<(VertexKey, usize), MAX_PRACTICAL_DIMENSION_SIZE>,
vertex_key: VertexKey,
) {
if let Some((_vertex, count)) = counts
.iter_mut()
.find(|(existing_vertex, _count)| *existing_vertex == vertex_key)
{
*count += 1;
} else {
counts.push((vertex_key, 1));
}
}
pub(crate) fn build_k2_flip_context_from_edge<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
edge: EdgeKey,
) -> Result<FlipContextDyn<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 3 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let (v0, v1) = edge.endpoints();
if v0 == v1 {
return Err(FlipError::InvalidEdgeAdjacency {
reason: FlipEdgeAdjacencyError::DuplicateEndpoints { vertex_key: v0 },
});
}
if tds.vertex(v0).is_none() {
return Err(FlipError::MissingVertex { vertex_key: v0 });
}
if tds.vertex(v1).is_none() {
return Err(FlipError::MissingVertex { vertex_key: v1 });
}
let mut removed_simplices: SimplexKeyBuffer = SimplexKeyBuffer::new();
for simplex_key in tds.find_simplices_containing_vertex(v0) {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if simplex.contains_vertex(v1) {
removed_simplices.push(simplex_key);
}
}
if removed_simplices.len() != D {
return Err(FlipError::InvalidEdgeMultiplicity {
found: removed_simplices.len(),
expected: D,
});
}
let mut counts: SmallBuffer<(VertexKey, usize), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for &simplex_key in &removed_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if !simplex.contains_vertex(v0) || !simplex.contains_vertex(v1) {
return Err(FlipError::InvalidEdgeAdjacency {
reason: FlipEdgeAdjacencyError::SimplexMissingEdgeVertices {
simplex_key,
v0,
v1,
},
});
}
for &vk in simplex.vertices() {
if vk != v0 && vk != v1 {
increment_vertex_count(&mut counts, vk);
}
}
}
if counts.len() != D || !counts.iter().all(|(_vertex, count)| *count == D - 1) {
return Err(FlipError::InvalidEdgeAdjacency {
reason: FlipEdgeAdjacencyError::InvalidOppositeVertexIncidence {
expected_vertices: D,
found_vertices: counts.len(),
expected_occurrences: D - 1,
},
});
}
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
counts.iter().map(|(vertex, _count)| *vertex).collect();
inserted_face_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
let mut removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(2);
removed_face_vertices.push(v0);
removed_face_vertices.push(v1);
Ok(FlipContextDyn {
removed_face_vertices,
inserted_face_vertices,
removed_simplices,
direction: FlipDirection::Inverse,
})
}
fn build_k1_forward_context_from_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
inserted_vertex: VertexKey,
) -> Result<FlipContext<D, 1>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 1 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if tds.vertex(inserted_vertex).is_none() {
return Err(FlipError::MissingVertex {
vertex_key: inserted_vertex,
});
}
let removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex.vertices().iter().copied().collect();
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(1);
inserted_face_vertices.push(inserted_vertex);
let removed_simplices: SimplexKeyBuffer = std::iter::once(simplex_key).collect();
Ok(FlipContext {
removed_face_vertices,
inserted_face_vertices,
removed_simplices,
direction: FlipDirection::Forward,
})
}
pub(crate) fn build_k1_inverse_context<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertex_key: VertexKey,
) -> Result<FlipContextDyn<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 1 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
if tds.vertex(vertex_key).is_none() {
return Err(FlipError::MissingVertex { vertex_key });
}
let removed_simplices = tds.find_simplices_containing_vertex_by_key(vertex_key);
let expected = D + 1;
if removed_simplices.len() != expected {
return Err(FlipError::InvalidVertexMultiplicity {
found: removed_simplices.len(),
expected,
});
}
let mut counts: FastHashMap<VertexKey, usize> = FastHashMap::default();
let mut removed_simplices_buf: SimplexKeyBuffer = SimplexKeyBuffer::new();
for &simplex_key in &removed_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if !simplex.contains_vertex(vertex_key) {
return Err(FlipError::InvalidVertexAdjacency {
reason: FlipVertexAdjacencyError::SimplexMissingVertex {
simplex_key,
vertex_key,
},
});
}
removed_simplices_buf.push(simplex_key);
for &vk in simplex.vertices() {
if vk != vertex_key {
*counts.entry(vk).or_insert(0) += 1;
}
}
}
if counts.len() != expected || !counts.values().all(|&count| count == D) {
return Err(FlipError::InvalidVertexAdjacency {
reason: FlipVertexAdjacencyError::InvalidLinkVertexIncidence {
expected_vertices: expected,
found_vertices: counts.len(),
expected_occurrences: D,
},
});
}
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
counts.keys().copied().collect();
inserted_face_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
let mut removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(1);
removed_face_vertices.push(vertex_key);
Ok(FlipContextDyn {
removed_face_vertices,
inserted_face_vertices,
removed_simplices: removed_simplices_buf,
direction: FlipDirection::Inverse,
})
}
#[inline]
fn source_simplex_is_certified_positive<T, const D: usize>(
source_simplex: Option<SimplexKey>,
points: &[Point<T, D>],
) -> bool
where
T: CoordinateScalar,
{
if source_simplex.is_none() {
return false;
}
let known_positive =
matches!(simplex_orientation_fast_filter_sign(points), Ok(Some(sign)) if sign > 0);
#[cfg(debug_assertions)]
if known_positive {
debug_assert!(
matches!(simplex_orientation(points), Ok(Orientation::POSITIVE)),
"stored source simplices must be positive-oriented before using the insphere fast path"
);
}
known_positive
}
#[expect(
clippy::too_many_arguments,
reason = "local predicate evaluation threads topology, source simplices, and diagnostics explicitly"
)]
#[expect(
clippy::too_many_lines,
reason = "local predicate evaluation keeps frame alignment, diagnostics, and exact predicate calls together"
)]
fn delaunay_violation_k2_for_facet<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
facet_vertices: &[VertexKey],
opposite_a: VertexKey,
opposite_b: VertexKey,
source_simplices: &[SimplexKey],
frame_simplex: Option<SimplexKey>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
) -> Result<bool, FlipError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if facet_vertices.len() != D {
return Err(FlipContextError::K2FacetArity {
expected: D,
found: facet_vertices.len(),
}
.into());
}
if facet_vertices.contains(&opposite_a)
|| facet_vertices.contains(&opposite_b)
|| opposite_a == opposite_b
{
return Err(FlipContextError::InvalidK2Opposites.into());
}
let mut simplex_vertices: [SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>; 2] = [
SmallBuffer::with_capacity(D + 1),
SmallBuffer::with_capacity(D + 1),
];
for vertices in &mut simplex_vertices {
vertices.extend_from_slice(facet_vertices);
}
simplex_vertices[0].push(opposite_a);
simplex_vertices[1].push(opposite_b);
simplex_vertices[0].sort_unstable_by_key(|v| v.data().as_ffi());
simplex_vertices[1].sort_unstable_by_key(|v| v.data().as_ffi());
let (
points_a,
points_b,
opposite_point_a,
opposite_point_b,
positive_oriented_a,
positive_oriented_b,
) = if matches!(topology_model, GlobalTopologyModelAdapter::Euclidean(_)) {
let mut point_cache = EuclideanPointCache::new();
let source_a = matching_source_simplex(tds, &simplex_vertices[0], source_simplices);
let source_b = matching_source_simplex(tds, &simplex_vertices[1], source_simplices);
let points_a = if let Some(source_simplex) = source_a {
let simplex = tds
.simplex(source_simplex)
.ok_or(FlipError::MissingSimplex {
simplex_key: source_simplex,
})?;
point_cache.points_for_vertices(tds, simplex.vertices())?
} else {
point_cache.points_for_vertices(tds, &simplex_vertices[0])?
};
let points_b = if let Some(source_simplex) = source_b {
let simplex = tds
.simplex(source_simplex)
.ok_or(FlipError::MissingSimplex {
simplex_key: source_simplex,
})?;
point_cache.points_for_vertices(tds, simplex.vertices())?
} else {
point_cache.points_for_vertices(tds, &simplex_vertices[1])?
};
let positive_oriented_a = source_simplex_is_certified_positive(source_a, &points_a);
let positive_oriented_b = source_simplex_is_certified_positive(source_b, &points_b);
(
points_a,
points_b,
point_cache.point(tds, opposite_a)?,
point_cache.point(tds, opposite_b)?,
positive_oriented_a,
positive_oriented_b,
)
} else {
let source_a =
matching_source_simplex(tds, &simplex_vertices[0], source_simplices).or(frame_simplex);
let source_b =
matching_source_simplex(tds, &simplex_vertices[1], source_simplices).or(frame_simplex);
(
vertices_to_points_with_optional_lift(
tds,
topology_model,
&simplex_vertices[0],
source_a,
source_simplices,
)?,
vertices_to_points_with_optional_lift(
tds,
topology_model,
&simplex_vertices[1],
source_b,
source_simplices,
)?,
vertex_point_lifted_into_simplex(
tds,
topology_model,
opposite_a,
source_b,
source_simplices,
)?,
vertex_point_lifted_into_simplex(
tds,
topology_model,
opposite_b,
source_a,
source_simplices,
)?,
false,
false,
)
};
let sphere_a = if positive_oriented_a {
kernel.in_sphere_positive_oriented(&points_a, &opposite_point_b)
} else {
kernel.in_sphere(&points_a, &opposite_point_b)
};
let in_a = match sphere_a {
Ok(value) => value,
Err(e) => {
diagnostics.record_predicate_failure();
return Err(FlipPredicateError::coordinate_conversion(
FlipPredicateOperation::K2SimplexAInSphere,
e,
)
.into());
}
};
let sphere_b = if positive_oriented_b {
kernel.in_sphere_positive_oriented(&points_b, &opposite_point_a)
} else {
kernel.in_sphere(&points_b, &opposite_point_a)
};
let in_b = match sphere_b {
Ok(value) => value,
Err(e) => {
diagnostics.record_predicate_failure();
return Err(FlipPredicateError::coordinate_conversion(
FlipPredicateOperation::K2SimplexBInSphere,
e,
)
.into());
}
};
if in_a == 0 {
let key = predicate_key_from_vertices(&simplex_vertices[0], opposite_b);
diagnostics.record_ambiguous(key);
}
if in_b == 0 {
let key = predicate_key_from_vertices(&simplex_vertices[1], opposite_a);
diagnostics.record_ambiguous(key);
}
let violates = in_a > 0 || in_b > 0;
if env::var_os("DELAUNAY_REPAIR_DEBUG_PREDICATES").is_some()
&& (violates || in_a == 0 || in_b == 0)
{
tracing::debug!(
facet_vertices = ?facet_vertices,
opposite_a = ?opposite_a,
opposite_b = ?opposite_b,
in_a,
in_b,
violates,
attempt = config.attempt,
"delaunay_violation_k2_for_facet: insphere classification"
);
}
Ok(violates)
}
fn flip_would_create_degenerate_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
removed_face_vertices: &[VertexKey],
inserted_face_vertices: &[VertexKey],
) -> Result<bool, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
for &omit in removed_face_vertices {
let mut vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D + 1);
vertices.extend_from_slice(inserted_face_vertices);
for &v in removed_face_vertices {
if v != omit {
vertices.push(v);
}
}
let points = vertices_to_points(tds, &vertices)?;
match robust_orientation(&points) {
Err(e) => {
return Err(FlipPredicateError::coordinate_conversion(
FlipPredicateOperation::DegenerateSimplexPrecheck,
e,
)
.into());
}
Ok(Orientation::DEGENERATE) => return Ok(true),
Ok(_) => {}
}
}
Ok(false)
}
fn k2_flip_would_create_degenerate_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
context: &FlipContext<D, 2>,
) -> Result<bool, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if context.inserted_face_vertices.len() != 2 {
return Err(FlipContextError::WrongInsertedFaceArity {
k_move: 2,
expected: 2,
found: context.inserted_face_vertices.len(),
}
.into());
}
flip_would_create_degenerate_simplex(
tds,
&context.removed_face_vertices,
&context.inserted_face_vertices,
)
}
fn is_delaunay_violation_k2<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
context: &FlipContext<D, 2>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
) -> Result<bool, FlipError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if context.inserted_face_vertices.len() != 2 {
return Err(FlipContextError::WrongInsertedFaceArity {
k_move: 2,
expected: 2,
found: context.inserted_face_vertices.len(),
}
.into());
}
let opposite_a = context.inserted_face_vertices[0];
let opposite_b = context.inserted_face_vertices[1];
delaunay_violation_k2_for_facet(
tds,
kernel,
topology_model,
&context.removed_face_vertices,
opposite_a,
opposite_b,
&context.removed_simplices,
None,
config,
diagnostics,
)
}
pub(crate) fn apply_bistellar_flip_k2<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
context: &FlipContext<D, 2>,
) -> Result<FlipInfo<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
apply_bistellar_flip::<T, U, V, D, 2>(tds, context)
}
pub(crate) fn build_k3_flip_context<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
ridge: RidgeHandle,
) -> Result<FlipContext<D, 3>, FlipError>
where
U: DataType,
V: DataType,
{
build_k3_flip_context_with_star_limit(tds, ridge, None)
}
fn build_k3_flip_context_for_repair<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
ridge: RidgeHandle,
) -> Result<FlipContext<D, 3>, FlipError>
where
U: DataType,
V: DataType,
{
build_k3_flip_context_with_star_limit(tds, ridge, Some(3))
}
fn build_k3_flip_context_with_star_limit<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
ridge: RidgeHandle,
max_simplices: Option<usize>,
) -> Result<FlipContext<D, 3>, FlipError>
where
U: DataType,
V: DataType,
{
if D < 3 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let simplex_key = ridge.simplex_key();
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
let vertex_count = simplex.number_of_vertices();
let omit_a = usize::from(ridge.omit_a());
let omit_b = usize::from(ridge.omit_b());
if omit_a >= vertex_count || omit_b >= vertex_count || omit_a == omit_b {
return Err(FlipError::InvalidRidgeIndex {
simplex_key,
omit_a: ridge.omit_a(),
omit_b: ridge.omit_b(),
vertex_count,
});
}
let ridge_vertices = ridge_vertices_from_simplex(simplex, omit_a, omit_b);
if ridge_vertices.len() != D - 1 {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key });
}
let simplices =
collect_simplices_around_ridge(tds, simplex_key, &ridge_vertices, max_simplices)?;
if simplices.len() != 3 {
return Err(FlipError::InvalidRidgeMultiplicity {
found: simplices.len(),
});
}
let mut opposite_counts: SmallBuffer<(VertexKey, u8), 3> = SmallBuffer::new();
let mut extras_per_simplex: SmallBuffer<[VertexKey; 2], 3> = SmallBuffer::new();
for &ck in &simplices {
let simplex = tds
.simplex(ck)
.ok_or(FlipError::MissingSimplex { simplex_key: ck })?;
let extras = simplex_extras_for_ridge(ck, simplex, &ridge_vertices)?;
if extras.len() != 2 {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key: ck });
}
let extras_pair: [VertexKey; 2] = extras
.as_slice()
.try_into()
.map_err(|_| FlipError::InvalidRidgeAdjacency { simplex_key: ck })?;
for &v in &extras_pair {
if let Some((_key, count)) = opposite_counts.iter_mut().find(|(key, _)| *key == v) {
*count += 1;
} else {
opposite_counts.push((v, 1));
}
}
extras_per_simplex.push(extras_pair);
}
if opposite_counts.len() != 3 || !opposite_counts.iter().all(|(_v, count)| *count == 2) {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key });
}
let mut opposite_vertices: SmallBuffer<VertexKey, 3> =
opposite_counts.iter().map(|(v, _count)| *v).collect();
opposite_vertices.sort_unstable();
let opposite_vertices: [VertexKey; 3] = opposite_vertices
.as_slice()
.try_into()
.map_err(|_| FlipError::InvalidRidgeAdjacency { simplex_key })?;
for extras in &extras_per_simplex {
let _missing = missing_opposite_for_simplex(extras, &opposite_vertices)
.ok_or(FlipError::InvalidRidgeAdjacency { simplex_key })?;
}
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(3);
inserted_face_vertices.extend(opposite_vertices);
Ok(FlipContext {
removed_face_vertices: ridge_vertices,
inserted_face_vertices,
removed_simplices: simplices,
direction: FlipDirection::Forward,
})
}
pub(crate) fn build_k3_flip_context_from_triangle<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
triangle: TriangleHandle,
) -> Result<FlipContextDyn<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 4 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let [a, b, c] = triangle.vertices();
if tds.vertex(a).is_none() {
return Err(FlipError::MissingVertex { vertex_key: a });
}
if tds.vertex(b).is_none() {
return Err(FlipError::MissingVertex { vertex_key: b });
}
if tds.vertex(c).is_none() {
return Err(FlipError::MissingVertex { vertex_key: c });
}
let mut removed_simplices: SimplexKeyBuffer = SimplexKeyBuffer::new();
for simplex_key in tds.find_simplices_containing_vertex(a) {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if simplex.contains_vertex(b) && simplex.contains_vertex(c) {
removed_simplices.push(simplex_key);
}
}
let expected = D - 1;
if removed_simplices.len() != expected {
return Err(FlipError::InvalidTriangleMultiplicity {
found: removed_simplices.len(),
expected,
});
}
let mut counts: SmallBuffer<(VertexKey, usize), MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::new();
for &simplex_key in &removed_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if !simplex.contains_vertex(a) || !simplex.contains_vertex(b) || !simplex.contains_vertex(c)
{
return Err(FlipError::InvalidTriangleAdjacency {
reason: FlipTriangleAdjacencyError::SimplexMissingTriangleVertices {
simplex_key,
a,
b,
c,
},
});
}
for &vk in simplex.vertices() {
if vk != a && vk != b && vk != c {
increment_vertex_count(&mut counts, vk);
}
}
}
if counts.len() != expected || !counts.iter().all(|(_vertex, count)| *count == expected - 1) {
return Err(FlipError::InvalidTriangleAdjacency {
reason: FlipTriangleAdjacencyError::InvalidRidgeVertexIncidence {
expected_vertices: expected,
found_vertices: counts.len(),
expected_occurrences: expected - 1,
},
});
}
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
counts.iter().map(|(vertex, _count)| *vertex).collect();
inserted_face_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
let mut removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(3);
removed_face_vertices.push(a);
removed_face_vertices.push(b);
removed_face_vertices.push(c);
Ok(FlipContextDyn {
removed_face_vertices,
inserted_face_vertices,
removed_simplices,
direction: FlipDirection::Inverse,
})
}
#[expect(
clippy::too_many_arguments,
reason = "Local predicate evaluation threads topology, source simplices, and diagnostics explicitly"
)]
fn delaunay_violation_k3_for_ridge<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
ridge_vertices: &[VertexKey],
triangle_vertices: &[VertexKey],
source_simplices: &[SimplexKey],
frame_simplex: Option<SimplexKey>,
_config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
) -> Result<bool, FlipError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if triangle_vertices.len() != 3 {
return Err(FlipContextError::WrongInsertedFaceArity {
k_move: 3,
expected: 3,
found: triangle_vertices.len(),
}
.into());
}
if ridge_vertices.len() != D.saturating_sub(1) {
return Err(FlipContextError::K3RidgeArity {
expected: D.saturating_sub(1),
found: ridge_vertices.len(),
}
.into());
}
let is_euclidean_topology = matches!(topology_model, GlobalTopologyModelAdapter::Euclidean(_));
let mut euclidean_point_cache = EuclideanPointCache::new();
for &missing in triangle_vertices {
let mut simplex_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D + 1);
simplex_vertices.extend_from_slice(ridge_vertices);
for &v in triangle_vertices {
if v != missing {
simplex_vertices.push(v);
}
}
simplex_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
let (points, missing_point, positive_oriented) = if is_euclidean_topology {
let source_simplex = matching_source_simplex(tds, &simplex_vertices, source_simplices);
let points = if let Some(source_simplex) = source_simplex {
let simplex = tds
.simplex(source_simplex)
.ok_or(FlipError::MissingSimplex {
simplex_key: source_simplex,
})?;
euclidean_point_cache.points_for_vertices(tds, simplex.vertices())?
} else {
euclidean_point_cache.points_for_vertices(tds, &simplex_vertices)?
};
let positive_oriented = source_simplex_is_certified_positive(source_simplex, &points);
(
points,
euclidean_point_cache.point(tds, missing)?,
positive_oriented,
)
} else {
let source_simplex =
matching_source_simplex(tds, &simplex_vertices, source_simplices).or(frame_simplex);
(
vertices_to_points_with_optional_lift(
tds,
topology_model,
&simplex_vertices,
source_simplex,
source_simplices,
)?,
vertex_point_lifted_into_simplex(
tds,
topology_model,
missing,
source_simplex,
source_simplices,
)?,
false,
)
};
let in_sphere_result = if positive_oriented {
kernel.in_sphere_positive_oriented(&points, &missing_point)
} else {
kernel.in_sphere(&points, &missing_point)
};
let in_sphere = match in_sphere_result {
Ok(value) => value,
Err(e) => {
diagnostics.record_predicate_failure();
return Err(FlipPredicateError::coordinate_conversion(
FlipPredicateOperation::K3SimplexInSphere,
e,
)
.into());
}
};
if in_sphere == 0 {
let key = predicate_key_from_vertices(&simplex_vertices, missing);
diagnostics.record_ambiguous(key);
}
if in_sphere > 0 {
return Ok(true);
}
}
Ok(false)
}
pub(crate) fn apply_bistellar_flip_k3<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
context: &FlipContext<D, 3>,
) -> Result<FlipInfo<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
apply_bistellar_flip::<T, U, V, D, 3>(tds, context)
}
pub(crate) fn apply_bistellar_flip_k1<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
simplex_key: SimplexKey,
vertex: Vertex<T, U, D>,
) -> Result<FlipInfo<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 1 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let vertex_key = tds.insert_vertex_with_mapping(vertex).map_err(|source| {
FlipMutationError::VertexInsertion {
source: source.into(),
}
})?;
let context = match build_k1_forward_context_from_simplex(tds, simplex_key, vertex_key) {
Ok(ctx) => ctx,
Err(e) => {
let _ = tds.remove_vertex(vertex_key);
return Err(e);
}
};
let result = apply_bistellar_flip::<T, U, V, D, 1>(tds, &context);
if result.is_err() {
let _ = tds.remove_vertex(vertex_key);
}
result
}
pub(crate) fn apply_bistellar_flip_k1_inverse<T, U, V, const D: usize>(
tds: &mut Tds<T, U, V, D>,
vertex_key: VertexKey,
) -> Result<FlipInfo<D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 1 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
let context = build_k1_inverse_context(tds, vertex_key)?;
let info = apply_bistellar_flip_dynamic(tds, D + 1, &context)?;
let _ = tds.remove_vertex(vertex_key);
Ok(info)
}
#[expect(
clippy::too_many_lines,
reason = "Repair loop contains inline tracing and queue handling for diagnostics"
)]
fn repair_delaunay_with_flips_k2_attempt<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
config: &RepairAttemptConfig,
) -> Result<RepairAttemptOutcome, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if D < 2 {
return Err(FlipError::UnsupportedDimension { dimension: D }.into());
}
let max_flips = config
.max_flips_override
.unwrap_or_else(|| default_max_flips::<D>(tds.number_of_simplices()));
let mut stats = DelaunayRepairStats::default();
let mut diagnostics = RepairDiagnostics::default();
let mut queue: VecDeque<(FacetHandle, u64)> = VecDeque::new();
let mut queued: FastHashSet<u64> = FastHashSet::default();
let mut facet_handles: FastHashMap<u64, FacetHandle> = FastHashMap::default();
let mut last_applied_flip: Option<LastAppliedFlip> = None;
let mut touched_simplices = SimplexKeyBuffer::new();
let mut touched_simplex_set = FastHashSet::<SimplexKey>::default();
let used_full_reseed = seed_simplices.is_none();
let topology_model = GlobalTopology::DEFAULT.model();
if let Some(seeds) = seed_simplices {
for &simplex_key in seeds {
enqueue_simplex_facets(
tds,
simplex_key,
&mut queue,
&mut queued,
&mut facet_handles,
&mut stats,
)?;
}
} else {
for facet in AllFacetsIter::new(tds) {
let handle = FacetHandle::new(facet.simplex_key(), facet.facet_index());
enqueue_facet(
tds,
handle,
&mut queue,
&mut queued,
&mut facet_handles,
&mut stats,
);
}
}
if repair_trace_enabled() {
let seed_count = seed_simplices.map_or(0, <[SimplexKey]>::len);
tracing::debug!(
"[repair] attempt={} order={:?} simplices={} max_flips={} seeds={} queues(facet={})",
config.attempt,
config.queue_order,
tds.number_of_simplices(),
max_flips,
seed_count,
queue.len(),
);
}
while let Some((facet, key)) = pop_queue(&mut queue, config.queue_order) {
queued.remove(&key);
let facet = facet_handles.remove(&key).unwrap_or(facet);
let Some(facet) = resolve_facet_handle_for_key(tds, facet, key) else {
continue;
};
stats.facets_checked += 1;
let context = match build_k2_flip_context(tds, facet) {
Ok(ctx) => ctx,
Err(
FlipError::BoundaryFacet { .. }
| FlipError::MissingSimplex { .. }
| FlipError::MissingNeighbor { .. }
| FlipError::InvalidFacetAdjacency { .. }
| FlipError::InvalidFacetIndex { .. },
) => {
continue;
}
Err(e) => return Err(e.into()),
};
let violates = match is_delaunay_violation_k2(
tds,
kernel,
&topology_model,
&context,
config,
&mut diagnostics,
) {
Ok(violates) => violates,
Err(FlipError::PredicateFailure { .. }) => {
continue;
}
Err(e) => return Err(e.into()),
};
if !violates {
continue;
}
diagnostics.record_applicable_repair_site();
let signature = flip_signature(
2,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
);
check_flip_cycle(
tds,
FlipCycleContext::new(
signature,
2,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
),
&mut diagnostics,
&stats,
max_flips,
config,
)?;
if stats.flips_performed >= max_flips {
return Err(non_convergent_error(
max_flips,
&stats,
&diagnostics,
config,
));
}
let applied = match apply_delaunay_flip_k2(tds, &context) {
Ok(applied) => applied,
Err(
err @ (FlipError::DegenerateSimplex
| FlipError::NegativeOrientation { .. }
| FlipError::DuplicateSimplex
| FlipError::NonManifoldFacet
| FlipError::InsertedSimplexAlreadyExists { .. }
| FlipError::SimplexCreation(_)),
) => {
if env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() {
tracing::debug!(
"k=2 flip skipped in repair_delaunay_with_flips_k2_attempt (facet={facet:?}): {err}"
);
}
if repair_trace_enabled() {
tracing::debug!("[repair] skip k=2 flip (facet={facet:?}) reason={err}");
tracing::debug!(
"[repair] skip k=2 flip context removed_face={:?} inserted_face={:?} removed_simplices={:?}",
context.removed_face_vertices,
context.inserted_face_vertices,
context.removed_simplices,
);
}
continue;
}
Err(e) => return Err(e.into()),
};
stats.flips_performed += 1;
diagnostics.record_flip_signature(signature);
last_applied_flip = Some(LastAppliedFlip::from_applied_flip(&applied));
let info = applied.info;
record_touched_simplices(
&mut touched_simplices,
&mut touched_simplex_set,
&info.new_simplices,
);
for &simplex_key in &info.new_simplices {
enqueue_simplex_facets(
tds,
simplex_key,
&mut queue,
&mut queued,
&mut facet_handles,
&mut stats,
)?;
}
}
if repair_trace_enabled() {
tracing::debug!(
"[repair] attempt={} done: checked={} flips={} max_queue={} ambiguous={} predicate_failures={} cycles={}",
config.attempt,
stats.facets_checked,
stats.flips_performed,
stats.max_queue_len,
diagnostics.ambiguous_predicates,
diagnostics.predicate_failures,
diagnostics.cycle_detections,
);
}
emit_repair_debug_summary("attempt_done", &stats, &diagnostics, config, max_flips);
Ok(RepairAttemptOutcome {
postcondition_required: repair_postcondition_required(&stats, &diagnostics),
stats,
last_applied_flip,
touched_simplices,
used_full_reseed,
})
}
pub(crate) fn repair_delaunay_with_flips_k2_k3<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
topology: TopologyGuarantee,
max_flips_override: Option<usize>,
) -> Result<DelaunayRepairStats, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
repair_delaunay_with_flips_k2_k3_run(tds, kernel, seed_simplices, topology, max_flips_override)
.map(|run| run.stats)
}
fn run_full_reseed_retry<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
config: &RepairAttemptConfig,
snapshot: Tds<K::Scalar, U, V, D>,
) -> Result<DelaunayRepairRun, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
*tds = snapshot.clone_for_rollback();
let retry_seed_simplices = None;
let attempt_result = if D == 2 {
repair_delaunay_with_flips_k2_attempt(tds, kernel, retry_seed_simplices, config)
} else {
repair_delaunay_with_flips_k2_k3_attempt(tds, kernel, retry_seed_simplices, config)
};
match attempt_result {
Ok(outcome) => match verify_repair_postcondition(
tds,
kernel,
retry_seed_simplices,
outcome.last_applied_flip.as_ref(),
) {
Ok(()) => Ok(repair_run_from_attempt(outcome)),
Err(err) => {
*tds = snapshot;
Err(err)
}
},
Err(err) => {
*tds = snapshot;
Err(err)
}
}
}
pub(crate) fn repair_delaunay_with_flips_k2_k3_run<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
topology: TopologyGuarantee,
max_flips_override: Option<usize>,
) -> Result<DelaunayRepairRun, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
if D < 2 {
return Err(FlipError::UnsupportedDimension { dimension: D }.into());
}
let operation = TopologicalOperation::FacetFlip;
if !operation.is_admissible_under(topology) {
return Err(DelaunayRepairError::InvalidTopology {
required: operation.required_topology(),
found: topology,
message: "flip-based Delaunay repair requires admissible topology",
});
}
let attempt1 = RepairAttemptConfig {
attempt: 1,
queue_order: RepairQueueOrder::Fifo,
max_flips_override,
};
let attempt2 = RepairAttemptConfig {
attempt: 2,
queue_order: RepairQueueOrder::Lifo,
max_flips_override,
};
let tds_snapshot = tds.clone_for_rollback();
let attempt1_result = if D == 2 {
repair_delaunay_with_flips_k2_attempt(tds, kernel, seed_simplices, &attempt1)
} else {
repair_delaunay_with_flips_k2_k3_attempt(tds, kernel, seed_simplices, &attempt1)
};
match attempt1_result {
Ok(outcome) => {
if verify_repair_postcondition(
tds,
kernel,
seed_simplices,
outcome.last_applied_flip.as_ref(),
)
.is_ok()
{
return Ok(repair_run_from_attempt(outcome));
}
if repair_trace_enabled() {
tracing::debug!(
"[repair] attempt 1 postcondition failed; retrying with LIFO + full reseed"
);
}
run_full_reseed_retry(tds, kernel, &attempt2, tds_snapshot)
}
Err(DelaunayRepairError::NonConvergent { .. }) => {
if repair_trace_enabled() {
tracing::debug!(
"[repair] attempt 1 non-convergent; retrying with LIFO + full reseed"
);
}
run_full_reseed_retry(tds, kernel, &attempt2, tds_snapshot)
}
Err(err) => {
*tds = tds_snapshot;
Err(err)
}
}
}
pub(crate) fn repair_delaunay_local_single_pass<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: &[SimplexKey],
max_flips: usize,
) -> Result<DelaunayRepairStats, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
repair_delaunay_local_single_pass_timed(tds, kernel, seed_simplices, max_flips, None)
}
#[expect(
clippy::too_many_lines,
reason = "bounded two-attempt repair keeps rollback, retry, and postcondition timing together"
)]
pub(crate) fn repair_delaunay_local_single_pass_timed<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: &[SimplexKey],
max_flips: usize,
mut timing: Option<&mut LocalRepairPhaseTiming>,
) -> Result<DelaunayRepairStats, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let mut phase_timing = LocalRepairPhaseTiming::default();
let attempt1 = RepairAttemptConfig {
attempt: 1,
queue_order: RepairQueueOrder::Fifo,
max_flips_override: Some(max_flips),
};
let attempt2 = RepairAttemptConfig {
attempt: 2,
queue_order: RepairQueueOrder::Lifo,
max_flips_override: Some(max_flips),
};
let snapshot_started = Instant::now();
let tds_snapshot = tds.clone_for_rollback();
phase_timing.record_snapshot(snapshot_started.elapsed());
let attempt_started = Instant::now();
let attempt1_result = if D == 2 {
repair_delaunay_with_flips_k2_attempt(tds, kernel, Some(seed_simplices), &attempt1)
} else {
repair_delaunay_with_flips_k2_k3_attempt_timed(
tds,
kernel,
Some(seed_simplices),
&attempt1,
Some(&mut phase_timing),
)
};
phase_timing.record_attempt(attempt_started.elapsed());
match attempt1_result {
Ok(outcome) => {
if !outcome.postcondition_required || D >= 4 {
publish_local_repair_phase_timing(&mut timing, phase_timing);
return Ok(outcome.stats);
}
let postcondition_frontier =
local_postcondition_frontier(seed_simplices, &outcome.touched_simplices);
let postcondition_started = Instant::now();
let postcondition_result = verify_local_repair_postcondition(
tds,
kernel,
&postcondition_frontier,
outcome.last_applied_flip.as_ref(),
);
phase_timing.record_postcondition(postcondition_started.elapsed());
if postcondition_result.is_ok() {
publish_local_repair_phase_timing(&mut timing, phase_timing);
return Ok(outcome.stats);
}
if repair_trace_enabled() {
tracing::debug!("[repair] local attempt 1 postcondition failed; retrying LIFO");
}
}
Err(DelaunayRepairError::NonConvergent { .. }) => {
if repair_trace_enabled() {
tracing::debug!("[repair] local attempt 1 non-convergent; retrying LIFO");
}
}
Err(err) => {
let restore_started = Instant::now();
*tds = tds_snapshot;
phase_timing.record_restore(restore_started.elapsed());
publish_local_repair_phase_timing(&mut timing, phase_timing);
return Err(err);
}
}
let restore_started = Instant::now();
*tds = tds_snapshot.clone_for_rollback();
phase_timing.record_restore(restore_started.elapsed());
let attempt_started = Instant::now();
let attempt2_result = if D == 2 {
repair_delaunay_with_flips_k2_attempt(tds, kernel, Some(seed_simplices), &attempt2)
} else {
repair_delaunay_with_flips_k2_k3_attempt_timed(
tds,
kernel,
Some(seed_simplices),
&attempt2,
Some(&mut phase_timing),
)
};
phase_timing.record_attempt(attempt_started.elapsed());
match attempt2_result {
Ok(outcome) => {
if !outcome.postcondition_required || D >= 4 {
publish_local_repair_phase_timing(&mut timing, phase_timing);
return Ok(outcome.stats);
}
let postcondition_frontier =
local_postcondition_frontier(seed_simplices, &outcome.touched_simplices);
let postcondition_started = Instant::now();
let postcondition_result = verify_local_repair_postcondition(
tds,
kernel,
&postcondition_frontier,
outcome.last_applied_flip.as_ref(),
);
phase_timing.record_postcondition(postcondition_started.elapsed());
match postcondition_result {
Ok(()) => {
publish_local_repair_phase_timing(&mut timing, phase_timing);
Ok(outcome.stats)
}
Err(verifier_err) => {
let restore_started = Instant::now();
*tds = tds_snapshot;
phase_timing.record_restore(restore_started.elapsed());
publish_local_repair_phase_timing(&mut timing, phase_timing);
Err(verifier_err)
}
}
}
Err(err) => {
let restore_started = Instant::now();
*tds = tds_snapshot;
phase_timing.record_restore(restore_started.elapsed());
publish_local_repair_phase_timing(&mut timing, phase_timing);
Err(err)
}
}
}
pub fn verify_delaunay_via_flip_predicates<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
verify_delaunay_with_topology(tds, kernel, GlobalTopology::DEFAULT)
}
pub fn verify_delaunay_for_triangulation<K, U, V, const D: usize>(
triangulation: &Triangulation<K, U, V, D>,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
verify_delaunay_with_topology(
&triangulation.tds,
&triangulation.kernel,
triangulation.global_topology,
)
}
fn verify_delaunay_with_topology<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
global_topology: GlobalTopology<D>,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
verify_repair_postcondition_with_topology(
tds,
kernel,
None,
global_topology,
PostconditionMode::Strict,
None,
ConnectivityPostcondition::Check,
)
}
fn verify_repair_postcondition<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
last_applied_flip: Option<&LastAppliedFlip>,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
verify_repair_postcondition_with_topology(
tds,
kernel,
seed_simplices,
GlobalTopology::DEFAULT,
PostconditionMode::Repair,
last_applied_flip,
ConnectivityPostcondition::Check,
)
}
fn verify_local_repair_postcondition<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: &[SimplexKey],
last_applied_flip: Option<&LastAppliedFlip>,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
verify_repair_postcondition_with_topology(
tds,
kernel,
Some(seed_simplices),
GlobalTopology::DEFAULT,
PostconditionMode::Repair,
last_applied_flip,
ConnectivityPostcondition::Defer,
)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum PostconditionMode {
Repair,
Strict,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ConnectivityPostcondition {
Check,
Defer,
}
fn verification_failed(
context: DelaunayRepairVerificationContext,
source: FlipError,
) -> DelaunayRepairError {
DelaunayRepairError::VerificationFailed {
context,
source: Box::new(source),
}
}
fn verify_repair_postcondition_with_topology<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
global_topology: GlobalTopology<D>,
mode: PostconditionMode,
last_applied_flip: Option<&LastAppliedFlip>,
connectivity: ConnectivityPostcondition,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let topology_model = global_topology.model();
verify_repair_postcondition_locally(
tds,
kernel,
seed_simplices,
&topology_model,
mode,
last_applied_flip,
connectivity,
)
}
fn verify_repair_postcondition_locally<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
seed_simplices: Option<&[SimplexKey]>,
topology_model: &GlobalTopologyModelAdapter<D>,
mode: PostconditionMode,
last_applied_flip: Option<&LastAppliedFlip>,
connectivity: ConnectivityPostcondition,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let config = RepairAttemptConfig {
attempt: 0,
queue_order: RepairQueueOrder::Fifo,
max_flips_override: None,
};
let mut stats = DelaunayRepairStats::default();
let mut diagnostics = RepairDiagnostics::default();
let mut queues = RepairQueues::new();
let _ = seed_repair_queues(tds, seed_simplices, &mut queues, &mut stats)?;
if repair_trace_enabled() {
let seed_count = seed_simplices.map_or(0, <[SimplexKey]>::len);
tracing::debug!(
"[repair] attempt={} order={:?} simplices={} seeds={} queues(facet={}, ridge={}, edge={}, tri={})",
config.attempt,
config.queue_order,
tds.number_of_simplices(),
seed_count,
queues.facet_queue.len(),
queues.ridge_queue.len(),
queues.edge_queue.len(),
queues.triangle_queue.len(),
);
}
verify_postcondition_k2_facets(
tds,
kernel,
topology_model,
&mut queues.facet_queue,
&config,
&mut diagnostics,
mode,
last_applied_flip,
)?;
verify_postcondition_k3_ridges(
tds,
kernel,
topology_model,
&mut queues.ridge_queue,
&config,
&mut diagnostics,
mode,
last_applied_flip,
)?;
verify_postcondition_inverse_k2_edges(
tds,
kernel,
topology_model,
&mut queues.edge_queue,
&config,
&mut diagnostics,
mode,
)?;
verify_postcondition_inverse_k3_triangles(
tds,
kernel,
topology_model,
&mut queues.triangle_queue,
&config,
&mut diagnostics,
mode,
)?;
if connectivity == ConnectivityPostcondition::Check && !tds.is_connected() {
return Err(DelaunayRepairError::PostconditionFailed {
message: format!(
"repair pass disconnected the triangulation \
({} simplices remain); neighbor wiring is incomplete",
tds.number_of_simplices()
),
});
}
Ok(())
}
fn resolve_postcondition_predicate_failure(
mode: PostconditionMode,
context: DelaunayRepairVerificationContext,
error: &FlipError,
) -> Result<(), DelaunayRepairError> {
match mode {
PostconditionMode::Repair => Ok(()),
PostconditionMode::Strict => Err(verification_failed(context, error.clone())),
}
}
#[expect(
clippy::too_many_arguments,
reason = "Postcondition replay threads topology, diagnostics, and predecessor context explicitly"
)]
fn verify_postcondition_k2_facets<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
queue: &mut VecDeque<(FacetHandle, u64)>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
mode: PostconditionMode,
last_applied_flip: Option<&LastAppliedFlip>,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
while let Some((facet, _key)) = pop_queue(queue, config.queue_order) {
let context = match build_k2_flip_context(tds, facet) {
Ok(ctx) => ctx,
Err(
FlipError::BoundaryFacet { .. }
| FlipError::MissingSimplex { .. }
| FlipError::MissingNeighbor { .. }
| FlipError::InvalidFacetAdjacency { .. }
| FlipError::InvalidFacetIndex { .. },
) => {
continue;
}
Err(e) => return Err(e.into()),
};
match is_delaunay_violation_k2(tds, kernel, topology_model, &context, config, diagnostics) {
Ok(true) => {
let flip_degenerate = match k2_flip_would_create_degenerate_simplex(tds, &context) {
Ok(degenerate) => degenerate,
Err(error @ FlipError::PredicateFailure { .. }) => {
resolve_postcondition_predicate_failure(
mode,
DelaunayRepairVerificationContext::LocalK2DegeneracyVerification,
&error,
)?;
continue;
}
Err(e) => {
return Err(verification_failed(
DelaunayRepairVerificationContext::LocalK2DegeneracyVerification,
e,
));
}
};
if flip_degenerate {
if repair_trace_enabled() {
tracing::debug!(
"[repair] postcondition k=2 violation unresolved due to degenerate flip (facet={facet:?})"
);
}
continue;
}
if repair_trace_enabled() {
tracing::debug!(
"[repair] postcondition k=2 violation remains (facet={facet:?})"
);
}
debug_postcondition_facet_context(
tds,
facet,
&context,
diagnostics,
last_applied_flip,
);
let mut message =
format!("local k=2 violation remains after repair (facet={facet:?})");
if env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() {
let removed_details: Vec<_> = context
.removed_face_vertices
.iter()
.filter_map(|&vkey| tds.vertex(vkey).map(|vertex| (vkey, *vertex.point())))
.collect();
let inserted_details: Vec<_> = context
.inserted_face_vertices
.iter()
.filter_map(|&vkey| tds.vertex(vkey).map(|vertex| (vkey, *vertex.point())))
.collect();
message = format!(
"{message}; removed_face={removed_details:?}; inserted_face={inserted_details:?}"
);
}
return Err(DelaunayRepairError::PostconditionFailed { message });
}
Ok(false) => {
}
Err(error @ FlipError::PredicateFailure { .. }) => {
resolve_postcondition_predicate_failure(
mode,
DelaunayRepairVerificationContext::LocalK2PostconditionVerification,
&error,
)?;
}
Err(e) => {
return Err(verification_failed(
DelaunayRepairVerificationContext::LocalK2PostconditionVerification,
e,
));
}
}
}
Ok(())
}
#[expect(
clippy::too_many_arguments,
reason = "Postcondition replay threads topology, diagnostics, and predecessor context explicitly (matches k=2 signature)"
)]
fn verify_postcondition_k3_ridges<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
queue: &mut VecDeque<(RidgeHandle, u64)>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
mode: PostconditionMode,
last_applied_flip: Option<&LastAppliedFlip>,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
while let Some((ridge, _key)) = pop_queue(queue, config.queue_order) {
let context = match build_k3_flip_context(tds, ridge) {
Ok(ctx) => ctx,
Err(
FlipError::InvalidRidgeIndex { .. }
| FlipError::InvalidRidgeAdjacency { .. }
| FlipError::InvalidRidgeMultiplicity { .. }
| FlipError::MissingSimplex { .. },
) => {
continue;
}
Err(e) => return Err(e.into()),
};
match is_delaunay_violation_k3(tds, kernel, topology_model, &context, config, diagnostics) {
Ok(true) => {
let flip_degenerate = match flip_would_create_degenerate_simplex(
tds,
&context.removed_face_vertices,
&context.inserted_face_vertices,
) {
Ok(degenerate) => degenerate,
Err(error @ FlipError::PredicateFailure { .. }) => {
resolve_postcondition_predicate_failure(
mode,
DelaunayRepairVerificationContext::LocalK3DegeneracyVerification,
&error,
)?;
continue;
}
Err(e) => {
return Err(verification_failed(
DelaunayRepairVerificationContext::LocalK3DegeneracyVerification,
e,
));
}
};
if flip_degenerate {
if repair_trace_enabled() {
tracing::debug!(
"[repair] postcondition k=3 violation unresolved due to degenerate flip (ridge={ridge:?})"
);
}
continue;
}
if repair_trace_enabled() {
tracing::debug!(
"[repair] postcondition k=3 violation remains (ridge={ridge:?})"
);
}
if repair_ridge_debug_enabled() {
debug_ridge_context(tds, ridge, None, diagnostics, last_applied_flip);
}
return Err(DelaunayRepairError::PostconditionFailed {
message: format!("local k=3 violation remains after repair (ridge={ridge:?})"),
});
}
Ok(false) => {
}
Err(error @ FlipError::PredicateFailure { .. }) => {
resolve_postcondition_predicate_failure(
mode,
DelaunayRepairVerificationContext::LocalK3PostconditionVerification,
&error,
)?;
}
Err(e) => {
return Err(verification_failed(
DelaunayRepairVerificationContext::LocalK3PostconditionVerification,
e,
));
}
}
}
Ok(())
}
fn verify_postcondition_inverse_k2_edges<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
queue: &mut VecDeque<(EdgeKey, u64)>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
mode: PostconditionMode,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
while let Some((edge, _key)) = pop_queue(queue, config.queue_order) {
let context = match build_k2_flip_context_from_edge(tds, edge) {
Ok(ctx) => ctx,
Err(
FlipError::InvalidEdgeMultiplicity { .. }
| FlipError::InvalidEdgeAdjacency { .. }
| FlipError::MissingSimplex { .. }
| FlipError::MissingVertex { .. },
) => {
continue;
}
Err(e) => return Err(e.into()),
};
if context.removed_face_vertices.len() != 2 {
continue;
}
let opposite_a = context.removed_face_vertices[0];
let opposite_b = context.removed_face_vertices[1];
let frame_simplex = removed_simplex_frame(&context.removed_simplices)?;
let violates = match delaunay_violation_k2_for_facet(
tds,
kernel,
topology_model,
&context.inserted_face_vertices,
opposite_a,
opposite_b,
&context.removed_simplices,
Some(frame_simplex),
config,
diagnostics,
) {
Ok(violates) => violates,
Err(error @ FlipError::PredicateFailure { .. }) => {
resolve_postcondition_predicate_failure(
mode,
DelaunayRepairVerificationContext::LocalInverseK2PostconditionVerification,
&error,
)?;
continue;
}
Err(e) => {
return Err(verification_failed(
DelaunayRepairVerificationContext::LocalInverseK2PostconditionVerification,
e,
));
}
};
if !violates {
if repair_trace_enabled() {
tracing::debug!(
"[repair] postcondition inverse k=2 flip still applicable (edge={edge:?})"
);
}
return Err(DelaunayRepairError::PostconditionFailed {
message: format!(
"local inverse k=2 flip remains applicable after repair (edge={edge:?})"
),
});
}
}
Ok(())
}
fn verify_postcondition_inverse_k3_triangles<K, U, V, const D: usize>(
tds: &Tds<K::Scalar, U, V, D>,
kernel: &K,
topology_model: &GlobalTopologyModelAdapter<D>,
queue: &mut VecDeque<(TriangleHandle, u64)>,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
mode: PostconditionMode,
) -> Result<(), DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
while let Some((triangle, _key)) = pop_queue(queue, config.queue_order) {
let context = match build_k3_flip_context_from_triangle(tds, triangle) {
Ok(ctx) => ctx,
Err(
FlipError::InvalidTriangleMultiplicity { .. }
| FlipError::InvalidTriangleAdjacency { .. }
| FlipError::MissingSimplex { .. }
| FlipError::MissingVertex { .. },
) => {
continue;
}
Err(e) => return Err(e.into()),
};
let frame_simplex = removed_simplex_frame(&context.removed_simplices)?;
let violates = match delaunay_violation_k3_for_ridge(
tds,
kernel,
topology_model,
&context.inserted_face_vertices,
&context.removed_face_vertices,
&context.removed_simplices,
Some(frame_simplex),
config,
diagnostics,
) {
Ok(violates) => violates,
Err(error @ FlipError::PredicateFailure { .. }) => {
resolve_postcondition_predicate_failure(
mode,
DelaunayRepairVerificationContext::LocalInverseK3PostconditionVerification,
&error,
)?;
continue;
}
Err(e) => {
return Err(verification_failed(
DelaunayRepairVerificationContext::LocalInverseK3PostconditionVerification,
e,
));
}
};
if !violates {
if repair_trace_enabled() {
tracing::debug!(
"[repair] postcondition inverse k=3 flip still applicable (triangle={triangle:?})"
);
}
return Err(DelaunayRepairError::PostconditionFailed {
message: format!(
"local inverse k=3 flip remains applicable after repair (triangle={triangle:?})"
),
});
}
}
Ok(())
}
const AMBIGUOUS_SAMPLE_LIMIT: usize = 16;
const CYCLE_SAMPLE_LIMIT: usize = 16;
const FLIP_SIGNATURE_WINDOW: usize = 4096;
const MAX_REPEAT_SIGNATURE: usize = 128;
#[derive(Debug, Default)]
struct RepairDiagnostics {
ambiguous_predicates: usize,
ambiguous_samples: Vec<u64>,
predicate_failures: usize,
cycle_detections: usize,
cycle_samples: Vec<u64>,
inserted_simplex_skips: usize,
inserted_simplex_sample: Option<InsertedSimplexSkipSample>,
invalid_ridge_multiplicity_skips: usize,
invalid_ridge_multiplicity_sample: Option<RidgeMultiplicitySkipSample>,
missing_simplex_skips: usize,
missing_simplex_sample: Option<MissingSimplexSkipSample>,
saw_applicable_repair_site: bool,
flip_signature_window: VecDeque<u64>,
flip_signature_counts: FastHashMap<u64, usize>,
ridge_debug_emitted: usize,
postcondition_facet_debug_emitted: usize,
}
#[derive(Clone, PartialEq, Eq)]
struct InsertedSimplexSkipSample {
location: RepairSkipLocation,
removed_face: VertexKeyList,
inserted_face: VertexKeyList,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct RidgeMultiplicitySkipSample {
ridge: RidgeHandle,
multiplicity: usize,
}
#[derive(Clone, Copy, PartialEq, Eq)]
struct MissingSimplexSkipSample {
location: RepairSkipLocation,
simplex_key: SimplexKey,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RepairSkipLocation {
Edge(EdgeKey),
Facet(FacetHandle),
Ridge(RidgeHandle),
Triangle(TriangleHandle),
}
impl RepairSkipLocation {
fn fmt_label(self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Edge(edge) => write!(f, "edge={edge:?}"),
Self::Facet(facet) => write!(f, "facet={facet:?}"),
Self::Ridge(ridge) => write!(f, "ridge={ridge:?}"),
Self::Triangle(triangle) => write!(f, "triangle={triangle:?}"),
}
}
}
impl fmt::Display for RepairSkipLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_label(f)
}
}
impl fmt::Debug for RepairSkipLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl fmt::Display for InsertedSimplexSkipSample {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.location.fmt_label(f)?;
write!(
f,
" removed_face={:?} inserted_face={:?}",
self.removed_face, self.inserted_face
)
}
}
impl fmt::Debug for InsertedSimplexSkipSample {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.to_string(), f)
}
}
impl fmt::Display for RidgeMultiplicitySkipSample {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ridge={:?} multiplicity={}",
self.ridge, self.multiplicity
)
}
}
impl fmt::Debug for RidgeMultiplicitySkipSample {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.to_string(), f)
}
}
impl fmt::Display for MissingSimplexSkipSample {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.location.fmt_label(f)?;
write!(f, " missing_simplex={:?}", self.simplex_key)
}
}
impl fmt::Debug for MissingSimplexSkipSample {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.to_string(), f)
}
}
fn vertex_key_list(vertices: &[VertexKey]) -> VertexKeyList {
vertices.iter().copied().collect()
}
impl RepairDiagnostics {
fn record_ambiguous(&mut self, key: u64) {
self.ambiguous_predicates += 1;
if self.ambiguous_samples.len() >= AMBIGUOUS_SAMPLE_LIMIT {
return;
}
if !self.ambiguous_samples.contains(&key) {
self.ambiguous_samples.push(key);
}
}
const fn record_predicate_failure(&mut self) {
self.predicate_failures = self.predicate_failures.saturating_add(1);
}
fn record_flip_signature(&mut self, signature: u64) {
let count = self.flip_signature_counts.entry(signature).or_insert(0);
*count = count.saturating_add(1);
if *count > 1 {
self.cycle_detections = self.cycle_detections.saturating_add(1);
if self.cycle_samples.len() < CYCLE_SAMPLE_LIMIT
&& !self.cycle_samples.contains(&signature)
{
self.cycle_samples.push(signature);
}
}
self.flip_signature_window.push_back(signature);
if self.flip_signature_window.len() > FLIP_SIGNATURE_WINDOW
&& let Some(old) = self.flip_signature_window.pop_front()
&& let Some(old_count) = self.flip_signature_counts.get_mut(&old)
{
*old_count = old_count.saturating_sub(1);
if *old_count == 0 {
self.flip_signature_counts.remove(&old);
}
}
}
fn record_cycle_abort(&mut self, signature: u64) {
self.cycle_detections = self.cycle_detections.saturating_add(1);
if self.cycle_samples.len() < CYCLE_SAMPLE_LIMIT && !self.cycle_samples.contains(&signature)
{
self.cycle_samples.push(signature);
}
}
fn record_inserted_simplex_skip(&mut self, sample: InsertedSimplexSkipSample) {
self.inserted_simplex_skips = self.inserted_simplex_skips.saturating_add(1);
if self.inserted_simplex_sample.is_none() {
self.inserted_simplex_sample = Some(sample);
}
}
const fn record_invalid_ridge_multiplicity_skip(
&mut self,
sample: RidgeMultiplicitySkipSample,
) {
self.invalid_ridge_multiplicity_skips =
self.invalid_ridge_multiplicity_skips.saturating_add(1);
if self.invalid_ridge_multiplicity_sample.is_none() {
self.invalid_ridge_multiplicity_sample = Some(sample);
}
}
const fn record_missing_simplex_skip(&mut self, sample: MissingSimplexSkipSample) {
self.missing_simplex_skips = self.missing_simplex_skips.saturating_add(1);
if self.missing_simplex_sample.is_none() {
self.missing_simplex_sample = Some(sample);
}
}
const fn record_applicable_repair_site(&mut self) {
self.saw_applicable_repair_site = true;
}
}
#[derive(Debug, Clone, Copy)]
struct RepairAttemptConfig {
attempt: usize,
queue_order: RepairQueueOrder,
max_flips_override: Option<usize>,
}
fn non_convergent_error(
max_flips: usize,
stats: &DelaunayRepairStats,
diagnostics: &RepairDiagnostics,
config: &RepairAttemptConfig,
) -> DelaunayRepairError {
emit_repair_debug_summary("non_convergent", stats, diagnostics, config, max_flips);
DelaunayRepairError::NonConvergent {
max_flips,
diagnostics: Box::new(DelaunayRepairDiagnostics {
facets_checked: stats.facets_checked,
flips_performed: stats.flips_performed,
max_queue_len: stats.max_queue_len,
ambiguous_predicates: diagnostics.ambiguous_predicates,
ambiguous_predicate_samples: diagnostics.ambiguous_samples.clone(),
predicate_failures: diagnostics.predicate_failures,
cycle_detections: diagnostics.cycle_detections,
cycle_signature_samples: diagnostics.cycle_samples.clone(),
attempt: config.attempt,
queue_order: config.queue_order,
}),
}
}
fn duration_nanos_saturating(duration: Duration) -> u64 {
u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
}
fn emit_repair_debug_summary(
label: &str,
stats: &DelaunayRepairStats,
diagnostics: &RepairDiagnostics,
config: &RepairAttemptConfig,
max_flips: usize,
) {
if env::var_os("DELAUNAY_REPAIR_DEBUG_SUMMARY").is_none() {
return;
}
tracing::debug!(
label = %label,
attempt = config.attempt,
order = ?config.queue_order,
flips = stats.flips_performed,
max_flips,
checked = stats.facets_checked,
max_queue = stats.max_queue_len,
ambiguous = diagnostics.ambiguous_predicates,
predicate_failures = diagnostics.predicate_failures,
cycles = diagnostics.cycle_detections,
inserted_simplex_skips = diagnostics.inserted_simplex_skips,
invalid_ridge_multiplicity_skips = diagnostics.invalid_ridge_multiplicity_skips,
missing_simplex_skips = diagnostics.missing_simplex_skips,
inserted_simplex_sample = ?diagnostics.inserted_simplex_sample,
invalid_ridge_multiplicity_sample = ?diagnostics.invalid_ridge_multiplicity_sample,
missing_simplex_sample = ?diagnostics.missing_simplex_sample,
"repair summary"
);
}
fn pop_queue<T>(queue: &mut VecDeque<T>, order: RepairQueueOrder) -> Option<T> {
match order {
RepairQueueOrder::Fifo => queue.pop_front(),
RepairQueueOrder::Lifo => queue.pop_back(),
}
}
fn predicate_key_from_vertices(simplex_vertices: &[VertexKey], test_vertex: VertexKey) -> u64 {
let mut sorted: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex_vertices.iter().copied().collect();
sorted.sort_unstable();
let mut hasher = FastHasher::default();
for vkey in &sorted {
vkey.hash(&mut hasher);
}
test_vertex.hash(&mut hasher);
hasher.finish()
}
fn flip_signature(
k_move: usize,
direction: FlipDirection,
removed_face_vertices: &[VertexKey],
inserted_face_vertices: &[VertexKey],
) -> u64 {
let mut removed: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
removed_face_vertices.iter().copied().collect();
removed.sort_unstable();
let mut inserted: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
inserted_face_vertices.iter().copied().collect();
inserted.sort_unstable();
let mut hasher = FastHasher::default();
k_move.hash(&mut hasher);
match direction {
FlipDirection::Forward => 0_u8.hash(&mut hasher),
FlipDirection::Inverse => 1_u8.hash(&mut hasher),
}
removed.len().hash(&mut hasher);
for vkey in &removed {
vkey.hash(&mut hasher);
}
inserted.len().hash(&mut hasher);
for vkey in &inserted {
vkey.hash(&mut hasher);
}
hasher.finish()
}
#[derive(Debug, Clone)]
struct LastAppliedFlip {
k_move: usize,
removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
removed_simplices: SimplexKeyBuffer,
new_simplices: SimplexKeyBuffer,
removed_simplex_vertices: RemovedSimplexVertexSnapshot,
}
impl LastAppliedFlip {
fn new(k_move: usize, removed: &[VertexKey], inserted: &[VertexKey]) -> Self {
let mut removed_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
removed.iter().copied().collect();
removed_face_vertices.sort_unstable();
let mut inserted_face_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
inserted.iter().copied().collect();
inserted_face_vertices.sort_unstable();
Self {
k_move,
removed_face_vertices,
inserted_face_vertices,
removed_simplices: SimplexKeyBuffer::new(),
new_simplices: SimplexKeyBuffer::new(),
removed_simplex_vertices: SmallBuffer::new(),
}
}
fn from_applied_flip<const D: usize>(applied: &AppliedFlip<D>) -> Self {
let info = &applied.info;
let mut last = Self::new(
info.kind.k(),
&info.removed_face_vertices,
&info.inserted_face_vertices,
);
last.removed_simplices.clone_from(&info.removed_simplices);
last.new_simplices.clone_from(&info.new_simplices);
last.removed_simplex_vertices
.clone_from(&applied.removed_simplex_vertices);
last
}
fn removed_simplex_vertex_lines(&self) -> Vec<String> {
self.removed_simplices
.iter()
.copied()
.enumerate()
.map(
|(idx, simplex_key)| match self.removed_simplex_vertices.get(idx) {
Some(verts) if !verts.is_empty() => {
format!("{simplex_key:?}: vertices={verts:?}")
}
_ => format!("{simplex_key:?}: missing-snapshot"),
},
)
.collect()
}
}
fn would_immediately_reverse_last_flip<const D: usize>(
last: Option<&LastAppliedFlip>,
k_move: usize,
removed_face_vertices: &[VertexKey],
inserted_face_vertices: &[VertexKey],
) -> bool {
let Some(last_flip) = last else {
return false;
};
if k_move + last_flip.k_move != D + 2 {
return false;
}
let current = LastAppliedFlip::new(k_move, removed_face_vertices, inserted_face_vertices);
current.removed_face_vertices == last_flip.inserted_face_vertices
&& current.inserted_face_vertices == last_flip.removed_face_vertices
}
#[inline]
fn repair_trace_enabled() -> bool {
env::var_os("DELAUNAY_REPAIR_TRACE").is_some()
}
#[inline]
fn repair_ridge_debug_enabled() -> bool {
env::var_os("DELAUNAY_REPAIR_DEBUG_RIDGE").is_some() || repair_trace_enabled()
}
const RIDGE_DEBUG_LIMIT_DEFAULT: usize = 64;
const RIDGE_DEBUG_MIN_MULTIPLICITY_DEFAULT: usize = 0;
fn ridge_debug_limit() -> usize {
env::var("DELAUNAY_REPAIR_DEBUG_RIDGE_LIMIT")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(RIDGE_DEBUG_LIMIT_DEFAULT)
}
fn ridge_debug_min_multiplicity() -> usize {
env::var("DELAUNAY_REPAIR_DEBUG_RIDGE_MIN_MULTIPLICITY")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.unwrap_or(RIDGE_DEBUG_MIN_MULTIPLICITY_DEFAULT)
}
fn should_emit_ridge_debug(
diagnostics: &mut RepairDiagnostics,
reported_multiplicity: Option<usize>,
) -> bool {
let min_multiplicity = ridge_debug_min_multiplicity();
match reported_multiplicity {
Some(found) if found < min_multiplicity => return false,
None if min_multiplicity > 0 => return false,
_ => {}
}
let limit = ridge_debug_limit();
if limit == 0 {
return false;
}
let current = diagnostics.ridge_debug_emitted;
diagnostics.ridge_debug_emitted = diagnostics.ridge_debug_emitted.saturating_add(1);
if current == limit {
tracing::debug!(
"repair: ridge debug output limit reached; suppressing further ridge snapshots"
);
}
current < limit
}
#[inline]
fn postcondition_facet_debug_enabled() -> bool {
env::var_os("DELAUNAY_REPAIR_DEBUG_POSTCONDITION_FACET").is_some()
}
fn should_emit_postcondition_facet_debug(diagnostics: &mut RepairDiagnostics) -> bool {
if !postcondition_facet_debug_enabled() {
return false;
}
let current = diagnostics.postcondition_facet_debug_emitted;
diagnostics.postcondition_facet_debug_emitted = diagnostics
.postcondition_facet_debug_emitted
.saturating_add(1);
current == 0
}
fn default_max_flips<const D: usize>(simplex_count: usize) -> usize {
if D >= 4 {
return simplex_count
.saturating_mul(D.saturating_add(1))
.saturating_mul(4)
.max(4096);
}
let multiplier = match D {
3 => 8,
_ => 4, };
let base = simplex_count
.saturating_mul(D.saturating_add(1))
.saturating_mul(multiplier);
base.max(512)
}
struct RepairQueues {
facet_queue: VecDeque<(FacetHandle, u64)>,
facet_queued: FastHashSet<u64>,
facet_handles: FastHashMap<u64, FacetHandle>,
ridge_queue: VecDeque<(RidgeHandle, u64)>,
ridge_queued: FastHashSet<u64>,
ridge_handles: FastHashMap<u64, RidgeHandle>,
edge_queue: VecDeque<(EdgeKey, u64)>,
edge_queued: FastHashSet<u64>,
triangle_queue: VecDeque<(TriangleHandle, u64)>,
triangle_queued: FastHashSet<u64>,
}
impl RepairQueues {
fn new() -> Self {
Self {
facet_queue: VecDeque::new(),
facet_queued: FastHashSet::default(),
facet_handles: FastHashMap::default(),
ridge_queue: VecDeque::new(),
ridge_queued: FastHashSet::default(),
ridge_handles: FastHashMap::default(),
edge_queue: VecDeque::new(),
edge_queued: FastHashSet::default(),
triangle_queue: VecDeque::new(),
triangle_queued: FastHashSet::default(),
}
}
fn total_len(&self) -> usize {
self.facet_queue.len()
+ self.ridge_queue.len()
+ self.edge_queue.len()
+ self.triangle_queue.len()
}
fn has_work(&self) -> bool {
!self.facet_queue.is_empty()
|| !self.ridge_queue.is_empty()
|| !self.edge_queue.is_empty()
|| !self.triangle_queue.is_empty()
}
}
#[expect(
clippy::too_many_lines,
reason = "seeding logic mirrors runtime queues and stays as one diagnostic flow"
)]
fn seed_repair_queues<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
seed_simplices: Option<&[SimplexKey]>,
queues: &mut RepairQueues,
stats: &mut DelaunayRepairStats,
) -> Result<bool, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if let Some(seeds) = seed_simplices {
let mut present = 0usize;
let mut missing = 0usize;
for &simplex_key in seeds {
if !tds.contains_simplex(simplex_key) {
missing = missing.saturating_add(1);
if repair_trace_enabled() {
tracing::debug!(
"[repair] seed_repair_queues: missing seed simplex={simplex_key:?}"
);
}
continue;
}
present = present.saturating_add(1);
enqueue_simplex_facets(
tds,
simplex_key,
&mut queues.facet_queue,
&mut queues.facet_queued,
&mut queues.facet_handles,
stats,
)?;
enqueue_simplex_ridges(
tds,
simplex_key,
&mut queues.ridge_queue,
&mut queues.ridge_queued,
&mut queues.ridge_handles,
stats,
)?;
enqueue_simplex_edges(
tds,
simplex_key,
&mut queues.edge_queue,
&mut queues.edge_queued,
stats,
);
enqueue_simplex_triangles(
tds,
simplex_key,
&mut queues.triangle_queue,
&mut queues.triangle_queued,
stats,
);
stats.max_queue_len = stats.max_queue_len.max(queues.total_len());
}
if repair_trace_enabled() {
let seed_sample: Vec<SimplexKey> = seeds.iter().copied().take(8).collect();
tracing::debug!(
"[repair] seed_repair_queues: seeds={} present={} missing={}",
seeds.len(),
present,
missing,
);
tracing::debug!("[repair] seed_repair_queues: sample={seed_sample:?}");
}
if present == 0 && !seeds.is_empty() {
if repair_trace_enabled() {
tracing::debug!(
"[repair] seed_repair_queues: all seed simplices stale; falling back to global seeding"
);
}
seed_repair_queues(tds, None, queues, stats)?;
return Ok(true);
}
} else {
for facet in AllFacetsIter::new(tds) {
let handle = FacetHandle::new(facet.simplex_key(), facet.facet_index());
enqueue_facet(
tds,
handle,
&mut queues.facet_queue,
&mut queues.facet_queued,
&mut queues.facet_handles,
stats,
);
}
for (simplex_key, _) in tds.simplices() {
enqueue_simplex_ridges(
tds,
simplex_key,
&mut queues.ridge_queue,
&mut queues.ridge_queued,
&mut queues.ridge_handles,
stats,
)?;
enqueue_simplex_edges(
tds,
simplex_key,
&mut queues.edge_queue,
&mut queues.edge_queued,
stats,
);
enqueue_simplex_triangles(
tds,
simplex_key,
&mut queues.triangle_queue,
&mut queues.triangle_queued,
stats,
);
}
stats.max_queue_len = stats.max_queue_len.max(queues.total_len());
return Ok(true);
}
Ok(false)
}
fn enqueue_new_simplices_for_repair<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
new_simplices: &[SimplexKey],
queues: &mut RepairQueues,
stats: &mut DelaunayRepairStats,
) -> Result<(), FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
for &simplex_key in new_simplices {
enqueue_simplex_facets(
tds,
simplex_key,
&mut queues.facet_queue,
&mut queues.facet_queued,
&mut queues.facet_handles,
stats,
)?;
enqueue_simplex_ridges(
tds,
simplex_key,
&mut queues.ridge_queue,
&mut queues.ridge_queued,
&mut queues.ridge_handles,
stats,
)?;
enqueue_simplex_edges(
tds,
simplex_key,
&mut queues.edge_queue,
&mut queues.edge_queued,
stats,
);
enqueue_simplex_triangles(
tds,
simplex_key,
&mut queues.triangle_queue,
&mut queues.triangle_queued,
stats,
);
stats.max_queue_len = stats.max_queue_len.max(queues.total_len());
}
Ok(())
}
#[expect(
clippy::too_many_arguments,
reason = "Repair step threads queues, diagnostics, and config explicitly"
)]
#[expect(
clippy::too_many_lines,
reason = "Repair step contains inline tracing and queue handling for diagnostics"
)]
fn run_next_ridge_repair_step<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
queues: &mut RepairQueues,
stats: &mut DelaunayRepairStats,
max_flips: usize,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
last_applied_flip: &mut Option<LastAppliedFlip>,
touched_simplices: &mut SimplexKeyBuffer,
touched_simplex_set: &mut FastHashSet<SimplexKey>,
) -> Result<bool, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let Some((ridge, key)) = pop_queue(&mut queues.ridge_queue, config.queue_order) else {
return Ok(false);
};
queues.ridge_queued.remove(&key);
let ridge = queues.ridge_handles.remove(&key).unwrap_or(ridge);
let Some(ridge) = resolve_ridge_handle_for_key(tds, ridge, key) else {
return Ok(true);
};
stats.facets_checked += 1;
let context = match build_k3_flip_context_for_repair(tds, ridge) {
Ok(ctx) => ctx,
Err(
err @ (FlipError::InvalidRidgeIndex { .. }
| FlipError::InvalidRidgeAdjacency { .. }
| FlipError::InvalidRidgeMultiplicity { .. }
| FlipError::MissingSimplex { .. }),
) => {
match &err {
FlipError::InvalidRidgeMultiplicity { found } => {
diagnostics.record_invalid_ridge_multiplicity_skip(
RidgeMultiplicitySkipSample {
ridge,
multiplicity: *found,
},
);
if repair_ridge_debug_enabled() {
debug_ridge_context(
tds,
ridge,
Some(*found),
diagnostics,
last_applied_flip.as_ref(),
);
}
}
FlipError::InvalidRidgeAdjacency { .. } if repair_ridge_debug_enabled() => {
debug_ridge_context(tds, ridge, None, diagnostics, last_applied_flip.as_ref());
}
FlipError::MissingSimplex { simplex_key } => {
diagnostics.record_missing_simplex_skip(MissingSimplexSkipSample {
location: RepairSkipLocation::Ridge(ridge),
simplex_key: *simplex_key,
});
}
_ => {}
}
if repair_trace_enabled() {
tracing::debug!("[repair] skip k=3 ridge (ridge={ridge:?}) reason={err}");
}
return Ok(true);
}
Err(e) => return Err(e.into()),
};
let topology_model = GlobalTopology::DEFAULT.model();
let violates =
match is_delaunay_violation_k3(tds, kernel, &topology_model, &context, config, diagnostics)
{
Ok(violates) => violates,
Err(FlipError::PredicateFailure { .. }) => {
return Ok(true);
}
Err(e) => return Err(e.into()),
};
if !violates {
return Ok(true);
}
diagnostics.record_applicable_repair_site();
if would_immediately_reverse_last_flip::<D>(
last_applied_flip.as_ref(),
3,
&context.removed_face_vertices,
&context.inserted_face_vertices,
) {
if repair_trace_enabled() {
tracing::debug!(
"[repair] skip k=3 flip (ridge={ridge:?}) reason=immediate reverse of prior flip"
);
}
return Ok(true);
}
let signature = flip_signature(
3,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
);
check_flip_cycle(
tds,
FlipCycleContext::new(
signature,
3,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
),
diagnostics,
stats,
max_flips,
config,
)?;
if stats.flips_performed >= max_flips {
return Err(non_convergent_error(max_flips, stats, diagnostics, config));
}
let log_apply_skip = |err: &FlipError| {
if repair_trace_enabled() {
tracing::debug!("[repair] skip k=3 flip (ridge={ridge:?}) reason={err}");
tracing::debug!(
"[repair] skip k=3 flip context removed_face={:?} inserted_face={:?} removed_simplices={:?}",
context.removed_face_vertices,
context.inserted_face_vertices,
context.removed_simplices,
);
}
};
let applied = match apply_delaunay_flip_k3(tds, &context) {
Ok(applied) => applied,
Err(err) if let FlipError::InsertedSimplexAlreadyExists { .. } = &err => {
diagnostics.record_inserted_simplex_skip(InsertedSimplexSkipSample {
location: RepairSkipLocation::Ridge(ridge),
removed_face: vertex_key_list(&context.removed_face_vertices),
inserted_face: vertex_key_list(&context.inserted_face_vertices),
});
log_apply_skip(&err);
return Ok(true);
}
Err(
err @ (FlipError::DegenerateSimplex
| FlipError::NegativeOrientation { .. }
| FlipError::DuplicateSimplex
| FlipError::NonManifoldFacet
| FlipError::SimplexCreation(_)),
) => {
log_apply_skip(&err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
*last_applied_flip = Some(LastAppliedFlip::from_applied_flip(&applied));
let info = applied.info;
if repair_trace_enabled() {
tracing::debug!(
"[repair] apply k=3 flip: kind={:?} direction={:?} removed_face={:?} inserted_face={:?} removed_simplices={:?} new_simplices={:?}",
info.kind,
info.direction,
info.removed_face_vertices,
info.inserted_face_vertices,
info.removed_simplices,
info.new_simplices,
);
}
stats.flips_performed += 1;
diagnostics.record_flip_signature(signature);
record_touched_simplices(touched_simplices, touched_simplex_set, &info.new_simplices);
enqueue_new_simplices_for_repair(tds, &info.new_simplices, queues, stats)?;
Ok(true)
}
#[expect(
clippy::too_many_arguments,
reason = "Repair step threads queues, diagnostics, and config explicitly"
)]
#[expect(
clippy::too_many_lines,
reason = "Repair step contains inline tracing and queue handling for diagnostics"
)]
fn run_next_edge_repair_step<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
queues: &mut RepairQueues,
stats: &mut DelaunayRepairStats,
max_flips: usize,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
last_applied_flip: &mut Option<LastAppliedFlip>,
touched_simplices: &mut SimplexKeyBuffer,
touched_simplex_set: &mut FastHashSet<SimplexKey>,
) -> Result<bool, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let Some((edge, key)) = pop_queue(&mut queues.edge_queue, config.queue_order) else {
return Ok(false);
};
queues.edge_queued.remove(&key);
stats.facets_checked += 1;
let log_build_skip = |err: &FlipError| {
if repair_trace_enabled() {
tracing::debug!("[repair] skip inverse k=2 edge (edge={edge:?}) reason={err}");
}
};
let context = match build_k2_flip_context_from_edge(tds, edge) {
Ok(ctx) => ctx,
Err(ref err) if let FlipError::MissingSimplex { simplex_key } = err => {
diagnostics.record_missing_simplex_skip(MissingSimplexSkipSample {
location: RepairSkipLocation::Edge(edge),
simplex_key: *simplex_key,
});
log_build_skip(err);
return Ok(true);
}
Err(
ref err @ (FlipError::InvalidEdgeMultiplicity { .. }
| FlipError::InvalidEdgeAdjacency { .. }
| FlipError::MissingVertex { .. }),
) => {
log_build_skip(err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
if context.removed_face_vertices.len() != 2 {
return Ok(true);
}
let opposite_a = context.removed_face_vertices[0];
let opposite_b = context.removed_face_vertices[1];
let frame_simplex = removed_simplex_frame(&context.removed_simplices)?;
let violates = match delaunay_violation_k2_for_facet(
tds,
kernel,
&GlobalTopology::DEFAULT.model(),
&context.inserted_face_vertices,
opposite_a,
opposite_b,
&context.removed_simplices,
Some(frame_simplex),
config,
diagnostics,
) {
Ok(violates) => violates,
Err(FlipError::PredicateFailure { .. }) => {
return Ok(true);
}
Err(e) => return Err(e.into()),
};
let allow_exploratory_inverse = config.attempt >= 2;
if violates && !allow_exploratory_inverse {
return Ok(true);
}
diagnostics.record_applicable_repair_site();
if would_immediately_reverse_last_flip::<D>(
last_applied_flip.as_ref(),
D,
&context.removed_face_vertices,
&context.inserted_face_vertices,
) {
if repair_trace_enabled() {
tracing::debug!(
"[repair] skip inverse k=2 flip (edge={edge:?}) reason=immediate reverse of prior flip"
);
}
return Ok(true);
}
let signature = flip_signature(
D,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
);
check_flip_cycle(
tds,
FlipCycleContext::new(
signature,
D,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
),
diagnostics,
stats,
max_flips,
config,
)?;
if stats.flips_performed >= max_flips {
return Err(non_convergent_error(max_flips, stats, diagnostics, config));
}
let log_apply_skip = |err: &FlipError| {
if repair_trace_enabled() {
tracing::debug!("[repair] skip inverse k=2 flip (edge={edge:?}) reason={err}");
tracing::debug!(
"[repair] skip inverse k=2 flip context removed_face={:?} inserted_face={:?} removed_simplices={:?}",
context.removed_face_vertices,
context.inserted_face_vertices,
context.removed_simplices,
);
}
};
let applied = match apply_delaunay_flip_dynamic(tds, D, &context) {
Ok(applied) => applied,
Err(err) if let FlipError::InsertedSimplexAlreadyExists { .. } = &err => {
diagnostics.record_inserted_simplex_skip(InsertedSimplexSkipSample {
location: RepairSkipLocation::Edge(edge),
removed_face: vertex_key_list(&context.removed_face_vertices),
inserted_face: vertex_key_list(&context.inserted_face_vertices),
});
log_apply_skip(&err);
return Ok(true);
}
Err(
err @ (FlipError::DegenerateSimplex
| FlipError::NegativeOrientation { .. }
| FlipError::DuplicateSimplex
| FlipError::NonManifoldFacet
| FlipError::SimplexCreation(_)),
) => {
log_apply_skip(&err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
*last_applied_flip = Some(LastAppliedFlip::from_applied_flip(&applied));
let info = applied.info;
if repair_trace_enabled() {
tracing::debug!(
"[repair] apply inverse k=2 flip: kind={:?} direction={:?} removed_face={:?} inserted_face={:?} removed_simplices={:?} new_simplices={:?}",
info.kind,
info.direction,
info.removed_face_vertices,
info.inserted_face_vertices,
info.removed_simplices,
info.new_simplices,
);
}
stats.flips_performed += 1;
diagnostics.record_flip_signature(signature);
record_touched_simplices(touched_simplices, touched_simplex_set, &info.new_simplices);
enqueue_new_simplices_for_repair(tds, &info.new_simplices, queues, stats)?;
Ok(true)
}
#[expect(
clippy::too_many_arguments,
reason = "Repair step threads queues, diagnostics, and config explicitly"
)]
#[expect(
clippy::too_many_lines,
reason = "Repair step contains inline tracing and queue handling for diagnostics"
)]
fn run_next_triangle_repair_step<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
queues: &mut RepairQueues,
stats: &mut DelaunayRepairStats,
max_flips: usize,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
last_applied_flip: &mut Option<LastAppliedFlip>,
touched_simplices: &mut SimplexKeyBuffer,
touched_simplex_set: &mut FastHashSet<SimplexKey>,
) -> Result<bool, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let Some((triangle, key)) = pop_queue(&mut queues.triangle_queue, config.queue_order) else {
return Ok(false);
};
queues.triangle_queued.remove(&key);
stats.facets_checked += 1;
let log_build_skip = |err: &FlipError| {
if repair_trace_enabled() {
tracing::debug!(
"[repair] skip inverse k=3 triangle (triangle={triangle:?}) reason={err}"
);
}
};
let context = match build_k3_flip_context_from_triangle(tds, triangle) {
Ok(ctx) => ctx,
Err(ref err) if let FlipError::MissingSimplex { simplex_key } = err => {
diagnostics.record_missing_simplex_skip(MissingSimplexSkipSample {
location: RepairSkipLocation::Triangle(triangle),
simplex_key: *simplex_key,
});
log_build_skip(err);
return Ok(true);
}
Err(
ref err @ (FlipError::InvalidTriangleMultiplicity { .. }
| FlipError::InvalidTriangleAdjacency { .. }
| FlipError::MissingVertex { .. }),
) => {
log_build_skip(err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
let frame_simplex = removed_simplex_frame(&context.removed_simplices)?;
let violates = match delaunay_violation_k3_for_ridge(
tds,
kernel,
&GlobalTopology::DEFAULT.model(),
&context.inserted_face_vertices,
&context.removed_face_vertices,
&context.removed_simplices,
Some(frame_simplex),
config,
diagnostics,
) {
Ok(violates) => violates,
Err(FlipError::PredicateFailure { .. }) => {
return Ok(true);
}
Err(e) => return Err(e.into()),
};
if violates {
return Ok(true);
}
diagnostics.record_applicable_repair_site();
if would_immediately_reverse_last_flip::<D>(
last_applied_flip.as_ref(),
D - 1,
&context.removed_face_vertices,
&context.inserted_face_vertices,
) {
if repair_trace_enabled() {
tracing::debug!(
"[repair] skip inverse k=3 flip (triangle={triangle:?}) reason=immediate reverse of prior flip"
);
}
return Ok(true);
}
let signature = flip_signature(
D - 1,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
);
check_flip_cycle(
tds,
FlipCycleContext::new(
signature,
D - 1,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
),
diagnostics,
stats,
max_flips,
config,
)?;
if stats.flips_performed >= max_flips {
return Err(non_convergent_error(max_flips, stats, diagnostics, config));
}
let log_apply_skip = |err: &FlipError| {
if repair_trace_enabled() {
tracing::debug!("[repair] skip inverse k=3 flip (triangle={triangle:?}) reason={err}");
tracing::debug!(
"[repair] skip inverse k=3 flip context removed_face={:?} inserted_face={:?} removed_simplices={:?}",
context.removed_face_vertices,
context.inserted_face_vertices,
context.removed_simplices,
);
}
};
let applied = match apply_delaunay_flip_dynamic(tds, D - 1, &context) {
Ok(applied) => applied,
Err(err) if let FlipError::InsertedSimplexAlreadyExists { .. } = &err => {
diagnostics.record_inserted_simplex_skip(InsertedSimplexSkipSample {
location: RepairSkipLocation::Triangle(triangle),
removed_face: vertex_key_list(&context.removed_face_vertices),
inserted_face: vertex_key_list(&context.inserted_face_vertices),
});
log_apply_skip(&err);
return Ok(true);
}
Err(
err @ (FlipError::DegenerateSimplex
| FlipError::NegativeOrientation { .. }
| FlipError::DuplicateSimplex
| FlipError::NonManifoldFacet
| FlipError::SimplexCreation(_)),
) => {
log_apply_skip(&err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
*last_applied_flip = Some(LastAppliedFlip::from_applied_flip(&applied));
let info = applied.info;
if repair_trace_enabled() {
tracing::debug!(
"[repair] apply inverse k=3 flip: kind={:?} direction={:?} removed_face={:?} inserted_face={:?} removed_simplices={:?} new_simplices={:?}",
info.kind,
info.direction,
info.removed_face_vertices,
info.inserted_face_vertices,
info.removed_simplices,
info.new_simplices,
);
}
stats.flips_performed += 1;
diagnostics.record_flip_signature(signature);
record_touched_simplices(touched_simplices, touched_simplex_set, &info.new_simplices);
enqueue_new_simplices_for_repair(tds, &info.new_simplices, queues, stats)?;
Ok(true)
}
#[expect(
clippy::too_many_arguments,
reason = "Repair step threads queues, diagnostics, and config explicitly"
)]
#[expect(
clippy::too_many_lines,
reason = "Repair step contains inline tracing and queue handling for diagnostics"
)]
fn run_next_facet_repair_step<K, U, V, const D: usize>(
tds: &mut Tds<K::Scalar, U, V, D>,
kernel: &K,
queues: &mut RepairQueues,
stats: &mut DelaunayRepairStats,
max_flips: usize,
config: &RepairAttemptConfig,
diagnostics: &mut RepairDiagnostics,
last_applied_flip: &mut Option<LastAppliedFlip>,
touched_simplices: &mut SimplexKeyBuffer,
touched_simplex_set: &mut FastHashSet<SimplexKey>,
) -> Result<bool, DelaunayRepairError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let Some((facet, key)) = pop_queue(&mut queues.facet_queue, config.queue_order) else {
return Ok(false);
};
queues.facet_queued.remove(&key);
let facet = queues.facet_handles.remove(&key).unwrap_or(facet);
let Some(facet) = resolve_facet_handle_for_key(tds, facet, key) else {
return Ok(true);
};
stats.facets_checked += 1;
let log_build_skip = |err: &FlipError| {
if repair_trace_enabled() {
tracing::debug!("[repair] skip k=2 facet (facet={facet:?}) reason={err}");
}
};
let context = match build_k2_flip_context(tds, facet) {
Ok(ctx) => ctx,
Err(ref err) if let FlipError::MissingSimplex { simplex_key } = err => {
diagnostics.record_missing_simplex_skip(MissingSimplexSkipSample {
location: RepairSkipLocation::Facet(facet),
simplex_key: *simplex_key,
});
log_build_skip(err);
return Ok(true);
}
Err(
ref err @ (FlipError::BoundaryFacet { .. }
| FlipError::MissingNeighbor { .. }
| FlipError::InvalidFacetAdjacency { .. }
| FlipError::InvalidFacetIndex { .. }),
) => {
log_build_skip(err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
let topology_model = GlobalTopology::DEFAULT.model();
let violates =
match is_delaunay_violation_k2(tds, kernel, &topology_model, &context, config, diagnostics)
{
Ok(violates) => violates,
Err(FlipError::PredicateFailure { .. }) => {
return Ok(true);
}
Err(e) => return Err(e.into()),
};
if !violates {
return Ok(true);
}
diagnostics.record_applicable_repair_site();
if would_immediately_reverse_last_flip::<D>(
last_applied_flip.as_ref(),
2,
&context.removed_face_vertices,
&context.inserted_face_vertices,
) {
if repair_trace_enabled() {
tracing::debug!(
"[repair] skip k=2 flip (facet={facet:?}) reason=immediate reverse of prior flip"
);
}
return Ok(true);
}
let signature = flip_signature(
2,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
);
check_flip_cycle(
tds,
FlipCycleContext::new(
signature,
2,
context.direction,
&context.removed_face_vertices,
&context.inserted_face_vertices,
),
diagnostics,
stats,
max_flips,
config,
)?;
if stats.flips_performed >= max_flips {
return Err(non_convergent_error(max_flips, stats, diagnostics, config));
}
let log_apply_skip = |err: &FlipError| {
if env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() {
tracing::debug!(
facet = ?facet,
reason = %err,
removed_face = ?context.removed_face_vertices,
inserted_face = ?context.inserted_face_vertices,
removed_simplices = ?context.removed_simplices,
"[repair] skip k=2 flip"
);
}
if repair_trace_enabled() {
tracing::debug!("[repair] skip k=2 flip (facet={facet:?}) reason={err}");
tracing::debug!(
"[repair] skip k=2 flip context removed_face={:?} inserted_face={:?} removed_simplices={:?}",
context.removed_face_vertices,
context.inserted_face_vertices,
context.removed_simplices,
);
}
};
let applied = match apply_delaunay_flip_k2(tds, &context) {
Ok(applied) => applied,
Err(err) if let FlipError::InsertedSimplexAlreadyExists { .. } = &err => {
diagnostics.record_inserted_simplex_skip(InsertedSimplexSkipSample {
location: RepairSkipLocation::Facet(facet),
removed_face: vertex_key_list(&context.removed_face_vertices),
inserted_face: vertex_key_list(&context.inserted_face_vertices),
});
log_apply_skip(&err);
return Ok(true);
}
Err(
err @ (FlipError::DegenerateSimplex
| FlipError::NegativeOrientation { .. }
| FlipError::DuplicateSimplex
| FlipError::NonManifoldFacet
| FlipError::SimplexCreation(_)),
) => {
log_apply_skip(&err);
return Ok(true);
}
Err(e) => return Err(e.into()),
};
*last_applied_flip = Some(LastAppliedFlip::from_applied_flip(&applied));
let info = applied.info;
if repair_trace_enabled() {
tracing::debug!(
"[repair] apply k=2 flip: kind={:?} direction={:?} removed_face={:?} inserted_face={:?} removed_simplices={:?} new_simplices={:?}",
info.kind,
info.direction,
info.removed_face_vertices,
info.inserted_face_vertices,
info.removed_simplices,
info.new_simplices,
);
}
stats.flips_performed += 1;
diagnostics.record_flip_signature(signature);
record_touched_simplices(touched_simplices, touched_simplex_set, &info.new_simplices);
enqueue_new_simplices_for_repair(tds, &info.new_simplices, queues, stats)?;
Ok(true)
}
fn facet_vertices_from_simplex<T, U, V, const D: usize>(
simplex: &Simplex<T, U, V, D>,
facet_index: usize,
) -> SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>
where
U: DataType,
V: DataType,
{
let mut vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D + 1);
for (i, &vkey) in simplex.vertices().iter().enumerate() {
if i != facet_index {
vertices.push(vkey);
}
}
vertices
}
fn ridge_vertices_from_simplex<T, U, V, const D: usize>(
simplex: &Simplex<T, U, V, D>,
omit_a: usize,
omit_b: usize,
) -> SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>
where
U: DataType,
V: DataType,
{
let mut vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D + 1);
for (i, &vkey) in simplex.vertices().iter().enumerate() {
if i != omit_a && i != omit_b {
vertices.push(vkey);
}
}
vertices
}
fn simplex_extras_for_ridge<T, U, V, const D: usize>(
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
ridge: &SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
) -> Result<SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError>
where
U: DataType,
V: DataType,
{
if !ridge.iter().all(|v| simplex.contains_vertex(*v)) {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key });
}
let mut extras: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(2);
for &vkey in simplex.vertices() {
if !ridge.contains(&vkey) {
extras.push(vkey);
}
}
Ok(extras)
}
fn missing_opposite_for_simplex(
extras: &[VertexKey; 2],
opposites: &[VertexKey; 3],
) -> Option<VertexKey> {
opposites
.iter()
.copied()
.find(|v| *v != extras[0] && *v != extras[1])
}
fn collect_simplices_around_ridge<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
start_simplex: SimplexKey,
ridge: &SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>,
max_simplices: Option<usize>,
) -> Result<SimplexKeyBuffer, FlipError>
where
U: DataType,
V: DataType,
{
let mut queue: SimplexKeyBuffer = SimplexKeyBuffer::new();
let mut visited: SimplexKeyBuffer = SimplexKeyBuffer::new();
let mut simplices: SimplexKeyBuffer = SimplexKeyBuffer::new();
let mut queue_cursor = 0usize;
queue.push(start_simplex);
while queue_cursor < queue.len() {
let simplex_key = queue[queue_cursor];
queue_cursor += 1;
if visited.contains(&simplex_key) {
continue;
}
visited.push(simplex_key);
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
if !ridge.iter().all(|v| simplex.contains_vertex(*v)) {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key });
}
let mut omit_indices: SmallBuffer<usize, 2> = SmallBuffer::with_capacity(2);
for (i, &vkey) in simplex.vertices().iter().enumerate() {
if !ridge.contains(&vkey) {
omit_indices.push(i);
}
}
if omit_indices.len() != 2 {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key });
}
simplices.push(simplex_key);
if max_simplices.is_some_and(|limit| simplices.len() > limit) {
return Ok(simplices);
}
for &omit_idx in &omit_indices {
if let Some(neighbor_key) = simplex.neighbor_key(omit_idx).flatten() {
let Some(neighbor_simplex) = tds.simplex(neighbor_key) else {
return Err(FlipError::DanglingRidgeNeighbor {
simplex_key,
neighbor_key,
});
};
if !ridge.iter().all(|v| neighbor_simplex.contains_vertex(*v)) {
return Err(FlipError::InvalidRidgeAdjacency { simplex_key });
}
queue.push(neighbor_key);
}
}
}
Ok(simplices)
}
fn vertex_point<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertex_key: VertexKey,
) -> Result<Point<T, D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let vertex = tds
.vertex(vertex_key)
.ok_or(FlipError::MissingVertex { vertex_key })?;
Ok(*vertex.point())
}
struct EuclideanPointCache<T, const D: usize> {
points: SmallBuffer<(VertexKey, Point<T, D>), MAX_PRACTICAL_DIMENSION_SIZE>,
}
impl<T, const D: usize> EuclideanPointCache<T, D> {
fn new() -> Self {
Self {
points: SmallBuffer::new(),
}
}
}
impl<T, const D: usize> EuclideanPointCache<T, D>
where
T: CoordinateScalar,
{
fn point<U, V>(
&mut self,
tds: &Tds<T, U, V, D>,
vertex_key: VertexKey,
) -> Result<Point<T, D>, FlipError>
where
U: DataType,
V: DataType,
{
if let Some((_key, point)) = self.points.iter().find(|(key, _point)| *key == vertex_key) {
return Ok(*point);
}
let point = vertex_point(tds, vertex_key)?;
self.points.push((vertex_key, point));
Ok(point)
}
fn points_for_vertices<U, V>(
&mut self,
tds: &Tds<T, U, V, D>,
vertices: &[VertexKey],
) -> Result<SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError>
where
U: DataType,
V: DataType,
{
let mut points: SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len());
for &vertex_key in vertices {
points.push(self.point(tds, vertex_key)?);
}
Ok(points)
}
}
fn vertices_to_points<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertices: &[VertexKey],
) -> Result<SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut points: SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len());
for &vkey in vertices {
points.push(vertex_point(tds, vkey)?);
}
Ok(points)
}
fn vertices_to_points_with_optional_lift<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
topology_model: &GlobalTopologyModelAdapter<D>,
vertices: &[VertexKey],
source_simplex: Option<SimplexKey>,
source_simplices: &[SimplexKey],
) -> Result<SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut points: SmallBuffer<Point<T, D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(vertices.len());
for &vkey in vertices {
points.push(vertex_point_lifted_into_simplex(
tds,
topology_model,
vkey,
source_simplex,
source_simplices,
)?);
}
Ok(points)
}
fn vertex_point_with_optional_lift<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
topology_model: &GlobalTopologyModelAdapter<D>,
vertex_key: VertexKey,
source_simplex: Option<SimplexKey>,
) -> Result<Point<T, D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let periodic_offset = if topology_model.supports_periodic_orientation_offsets() {
match source_simplex {
Some(simplex_key) => periodic_offset_for_simplex_vertex(tds, simplex_key, vertex_key)?,
None => None,
}
} else {
None
};
lift_vertex_point(tds, topology_model, vertex_key, periodic_offset)
}
fn vertex_point_lifted_into_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
topology_model: &GlobalTopologyModelAdapter<D>,
vertex_key: VertexKey,
target_simplex: Option<SimplexKey>,
source_simplices: &[SimplexKey],
) -> Result<Point<T, D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(target_simplex_key) = target_simplex else {
return vertex_point_with_optional_lift(tds, topology_model, vertex_key, None);
};
if !topology_model.supports_periodic_orientation_offsets() {
return lift_vertex_point(tds, topology_model, vertex_key, None);
}
let offset =
periodic_offset_lifted_into_simplex(tds, vertex_key, target_simplex_key, source_simplices)?;
lift_vertex_point(tds, topology_model, vertex_key, Some(offset))
}
fn periodic_offset_lifted_into_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertex_key: VertexKey,
target_simplex_key: SimplexKey,
source_simplices: &[SimplexKey],
) -> Result<[i8; D], FlipError> {
let target_offset = periodic_offset_for_simplex_vertex(tds, target_simplex_key, vertex_key)?;
if let Some(offset) = target_offset {
return Ok(offset);
}
let target_simplex = tds
.simplex(target_simplex_key)
.ok_or(FlipError::MissingSimplex {
simplex_key: target_simplex_key,
})?;
let target_offsets = periodic_offsets_or_zero_frame(target_simplex_key, target_simplex)?;
for &source_simplex_key in source_simplices {
let Some(source_simplex) = tds.simplex(source_simplex_key) else {
continue;
};
if !source_simplex.contains_vertex(vertex_key) {
continue;
}
let source_offsets = periodic_offsets_or_zero_frame(source_simplex_key, source_simplex)?;
let Some(source_vertex_index) = source_simplex
.vertices()
.iter()
.position(|&vkey| vkey == vertex_key)
else {
continue;
};
let shared_indices = shared_vertex_indices(target_simplex, source_simplex);
if shared_indices.is_empty() {
continue;
}
let source_vertex_offset = source_offsets[source_vertex_index];
let mut aligned_offset: Option<[i8; D]> = None;
for (target_shared_index, source_shared_index) in shared_indices {
let target_offset = target_offsets[target_shared_index];
let source_offset = source_offsets[source_shared_index];
let candidate_offset =
align_periodic_offset(source_vertex_offset, source_offset, target_offset)?;
if let Some(expected_offset) = aligned_offset {
if candidate_offset != expected_offset {
return Err(FlipContextError::ConflictingPeriodicFrameTranslation {
vertex_key,
source_simplex_key,
target_simplex_key,
expected_offset: expected_offset.into(),
found_offset: candidate_offset.into(),
}
.into());
}
} else {
aligned_offset = Some(candidate_offset);
}
}
if let Some(offset) = aligned_offset {
return Ok(offset);
}
}
Err(FlipContextError::PeriodicVertexAlignmentFailed {
vertex_key,
target_simplex_key,
}
.into())
}
fn lift_vertex_point<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
topology_model: &GlobalTopologyModelAdapter<D>,
vertex_key: VertexKey,
periodic_offset: Option<[i8; D]>,
) -> Result<Point<T, D>, FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let vertex = tds
.vertex(vertex_key)
.ok_or(FlipError::MissingVertex { vertex_key })?;
let lifted_coords = topology_model
.lift_for_orientation(*vertex.point().coords(), periodic_offset)
.map_err(|source| FlipPredicateError::PeriodicVertexLift {
vertex_key,
details: source.to_string(),
})?;
Ok(Point::new(lifted_coords))
}
fn periodic_offset_for_simplex_vertex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
vertex_key: VertexKey,
) -> Result<Option<[i8; D]>, FlipError> {
let simplex = tds
.simplex(simplex_key)
.ok_or(FlipError::MissingSimplex { simplex_key })?;
let offsets = periodic_offsets_or_zero_frame(simplex_key, simplex)?;
Ok(simplex
.vertices()
.iter()
.position(|&vkey| vkey == vertex_key)
.map(|index| offsets[index]))
}
fn periodic_offsets_or_zero_frame<T, U, V, const D: usize>(
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
) -> Result<Cow<'_, [[i8; D]]>, FlipError> {
let offsets = simplex.periodic_vertex_offsets().map_or_else(
|| Cow::Owned(vec![[0_i8; D]; simplex.number_of_vertices()]),
Cow::Borrowed,
);
validate_periodic_offset_len(simplex_key, simplex, offsets.as_ref())?;
Ok(offsets)
}
fn validate_periodic_offset_len<T, U, V, const D: usize>(
simplex_key: SimplexKey,
simplex: &Simplex<T, U, V, D>,
offsets: &[[i8; D]],
) -> Result<(), FlipError> {
if offsets.len() == simplex.number_of_vertices() {
return Ok(());
}
Err(FlipContextError::PeriodicOffsetCountMismatch {
simplex_key,
offset_count: offsets.len(),
vertex_count: simplex.number_of_vertices(),
}
.into())
}
fn shared_vertex_indices<T, U, V, const D: usize>(
target_simplex: &Simplex<T, U, V, D>,
source_simplex: &Simplex<T, U, V, D>,
) -> SmallBuffer<(usize, usize), MAX_PRACTICAL_DIMENSION_SIZE> {
let mut shared = SmallBuffer::new();
for (target_index, &target_vertex) in target_simplex.vertices().iter().enumerate() {
if let Some(source_index) = source_simplex
.vertices()
.iter()
.position(|&source_vertex| source_vertex == target_vertex)
{
shared.push((target_index, source_index));
}
}
shared
}
fn align_periodic_offset<const D: usize>(
source_vertex_offset: [i8; D],
source_reference_offset: [i8; D],
target_reference_offset: [i8; D],
) -> Result<[i8; D], FlipError> {
let mut aligned = [0_i8; D];
for axis in 0..D {
let delta = target_reference_offset[axis]
.checked_sub(source_reference_offset[axis])
.ok_or(FlipContextError::PeriodicOffsetSubtractionOverflow { axis })?;
aligned[axis] = source_vertex_offset[axis]
.checked_add(delta)
.ok_or(FlipContextError::PeriodicOffsetAdditionOverflow { axis })?;
}
Ok(aligned)
}
fn matching_source_simplex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertices: &[VertexKey],
source_simplices: &[SimplexKey],
) -> Option<SimplexKey>
where
U: DataType,
V: DataType,
{
source_simplices.iter().copied().find(|&simplex_key| {
tds.simplex(simplex_key).is_some_and(|simplex| {
simplex.number_of_vertices() == vertices.len()
&& vertices
.iter()
.all(|&vertex_key| simplex.contains_vertex(vertex_key))
})
})
}
fn removed_simplex_frame(source_simplices: &[SimplexKey]) -> Result<SimplexKey, FlipError> {
source_simplices
.first()
.copied()
.ok_or_else(|| FlipContextError::MissingRemovedSimplexFrame.into())
}
#[derive(Debug, Default)]
struct FlipTopologyIndex {
duplicate_signature_to_simplex: SmallBuffer<(u64, SimplexKey), MAX_PRACTICAL_DIMENSION_SIZE>,
candidate_facet_info: SmallBuffer<
(u64, CandidateFacetInfo),
{ MAX_PRACTICAL_DIMENSION_SIZE * MAX_PRACTICAL_DIMENSION_SIZE },
>,
}
#[derive(Debug, Clone, Copy)]
struct CandidateFacetInfo {
existing_count: u8,
last_simplex: Option<SimplexKey>,
}
fn sorted_vertex_key_values(
vertices: &[VertexKey],
) -> SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> {
let mut key_values: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
vertices.iter().map(|key| key.data().as_ffi()).collect();
key_values.sort_unstable();
key_values
}
fn simplex_signature(vertices: &[VertexKey]) -> u64 {
let key_values = sorted_vertex_key_values(vertices);
stable_hash_u64_slice(&key_values)
}
fn build_flip_topology_index<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
new_simplex_vertices: &[SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE>],
removed_simplices: &[SimplexKey],
inserted_face_vertices: &[VertexKey],
) -> FlipTopologyIndex
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let inserted_values = sorted_vertex_key_values(inserted_face_vertices);
let mut candidate_simplex_signatures: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(new_simplex_vertices.len());
let mut candidate_facet_info: SmallBuffer<
(u64, CandidateFacetInfo),
{ MAX_PRACTICAL_DIMENSION_SIZE * MAX_PRACTICAL_DIMENSION_SIZE },
> = SmallBuffer::new();
for vertices in new_simplex_vertices {
let simplex_values = sorted_vertex_key_values(vertices);
candidate_simplex_signatures.push(stable_hash_u64_slice(&simplex_values));
let mut facet_values: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(simplex_values.len().saturating_sub(1));
for omit_idx in 0..simplex_values.len() {
facet_values.clear();
for (i, &val) in simplex_values.iter().enumerate() {
if i != omit_idx {
facet_values.push(val);
}
}
let facet_hash = stable_hash_u64_slice(&facet_values);
let internal = inserted_values
.iter()
.all(|v| facet_values.binary_search(v).is_ok());
if !internal {
continue;
}
if candidate_facet_info
.iter()
.any(|(hash, _info)| *hash == facet_hash)
{
continue;
}
candidate_facet_info.push((
facet_hash,
CandidateFacetInfo {
existing_count: 0,
last_simplex: None,
},
));
}
}
candidate_facet_info.sort_unstable_by_key(|(hash, _info)| *hash);
let mut duplicate_signature_to_simplex: SmallBuffer<
(u64, SimplexKey),
MAX_PRACTICAL_DIMENSION_SIZE,
> = SmallBuffer::new();
let mut facet_values: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D);
let mut simplex_values: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(D + 1);
for (simplex_key, simplex) in tds.simplices() {
if removed_simplices.contains(&simplex_key) {
continue;
}
if !inserted_face_vertices
.iter()
.all(|v| simplex.contains_vertex(*v))
{
continue;
}
simplex_values.clear();
for key in simplex.vertices() {
simplex_values.push(key.data().as_ffi());
}
simplex_values.sort_unstable();
let signature = stable_hash_u64_slice(&simplex_values);
if candidate_simplex_signatures.contains(&signature)
&& !duplicate_signature_to_simplex
.iter()
.any(|(s, _simplex_key)| *s == signature)
{
duplicate_signature_to_simplex.push((signature, simplex_key));
}
if candidate_facet_info.is_empty() {
continue;
}
for omit_idx in 0..simplex_values.len() {
facet_values.clear();
for (i, &val) in simplex_values.iter().enumerate() {
if i != omit_idx {
facet_values.push(val);
}
}
let facet_hash = stable_hash_u64_slice(&facet_values);
let Ok(idx) =
candidate_facet_info.binary_search_by_key(&facet_hash, |(hash, _info)| *hash)
else {
continue;
};
let info = &mut candidate_facet_info[idx].1;
if info.existing_count < 2 {
info.existing_count += 1;
}
info.last_simplex = Some(simplex_key);
}
}
FlipTopologyIndex {
duplicate_signature_to_simplex,
candidate_facet_info,
}
}
fn flip_would_duplicate_simplex_any<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
vertices: &[VertexKey],
topology: &FlipTopologyIndex,
) -> bool
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let signature = simplex_signature(vertices);
let Some(simplex_key) = topology
.duplicate_signature_to_simplex
.iter()
.find_map(|(s, ck)| (*s == signature).then_some(*ck))
else {
return false;
};
if env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() || repair_trace_enabled() {
let mut target: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
vertices.iter().copied().collect();
target.sort_unstable();
let existing_sorted = tds.simplex(simplex_key).map(|simplex| {
let mut v: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex.vertices().iter().copied().collect();
v.sort_unstable();
v
});
if env::var_os("DELAUNAY_REPAIR_DEBUG_FACETS").is_some() {
tracing::debug!(
"k=2 flip would duplicate existing simplex {simplex_key:?}; target={target:?}; existing={existing_sorted:?}"
);
}
if repair_trace_enabled() {
tracing::debug!(
"[repair] flip would duplicate existing simplex {simplex_key:?}; target={target:?}; existing={existing_sorted:?}"
);
}
}
true
}
fn flip_would_create_nonmanifold_facets_any(
vertices: &[VertexKey],
topology: &FlipTopologyIndex,
) -> bool {
let sorted_values = sorted_vertex_key_values(vertices);
let mut sorted_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
vertices.iter().copied().collect();
sorted_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
let mut facet_values: SmallBuffer<u64, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(sorted_values.len().saturating_sub(1));
let mut facet_vertices: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(sorted_vertices.len().saturating_sub(1));
for omit_idx in 0..sorted_values.len() {
facet_values.clear();
facet_vertices.clear();
for (i, &value) in sorted_values.iter().enumerate() {
if i != omit_idx {
facet_values.push(value);
facet_vertices.push(sorted_vertices[i]);
}
}
let facet_hash = stable_hash_u64_slice(&facet_values);
let Ok(idx) = topology
.candidate_facet_info
.binary_search_by_key(&facet_hash, |(hash, _info)| *hash)
else {
continue;
};
let info = &topology.candidate_facet_info[idx].1;
if info.existing_count > 0 {
if repair_trace_enabled() {
tracing::debug!(
"[repair] flip would create non-manifold internal facet: facet={facet_vertices:?} shared_count={} last_simplex={:?}",
info.existing_count,
info.last_simplex,
);
}
return true;
}
}
false
}
fn enqueue_simplex_facets<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
queue: &mut VecDeque<(FacetHandle, u64)>,
queued: &mut FastHashSet<u64>,
handles: &mut FastHashMap<u64, FacetHandle>,
stats: &mut DelaunayRepairStats,
) -> Result<(), FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(simplex) = tds.simplex(simplex_key) else {
return Ok(());
};
for facet_index in 0..simplex.number_of_vertices() {
let handle = FacetHandle::new(
simplex_key,
u8::try_from(facet_index).map_err(|_| FlipError::InvalidFacetIndex {
simplex_key,
facet_index: u8::MAX,
vertex_count: simplex.number_of_vertices(),
})?,
);
enqueue_facet(tds, handle, queue, queued, handles, stats);
}
Ok(())
}
fn enqueue_facet<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
handle: FacetHandle,
queue: &mut VecDeque<(FacetHandle, u64)>,
queued: &mut FastHashSet<u64>,
handles: &mut FastHashMap<u64, FacetHandle>,
stats: &mut DelaunayRepairStats,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(simplex) = tds.simplex(handle.simplex_key()) else {
return;
};
let facet_index = usize::from(handle.facet_index());
if facet_index >= simplex.number_of_vertices() {
return;
}
let Some(_neighbor_key) = simplex
.neighbor_key(facet_index)
.flatten()
.filter(|&nk| tds.contains_simplex(nk))
else {
return;
};
let facet_vertices = facet_vertices_from_simplex(simplex, facet_index);
let key = facet_key_from_vertices(&facet_vertices);
handles.insert(key, handle);
if queued.insert(key) {
queue.push_back((handle, key));
stats.max_queue_len = stats.max_queue_len.max(queue.len());
}
}
fn enqueue_simplex_edges<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
queue: &mut VecDeque<(EdgeKey, u64)>,
queued: &mut FastHashSet<u64>,
stats: &mut DelaunayRepairStats,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 4 {
return;
}
let Some(simplex) = tds.simplex(simplex_key) else {
return;
};
let vertices = simplex.vertices();
let vertex_count = vertices.len();
for i in 0..vertex_count {
for j in (i + 1)..vertex_count {
let edge = EdgeKey::new(vertices[i], vertices[j]);
enqueue_edge(edge, queue, queued, stats);
}
}
}
fn enqueue_edge(
edge: EdgeKey,
queue: &mut VecDeque<(EdgeKey, u64)>,
queued: &mut FastHashSet<u64>,
stats: &mut DelaunayRepairStats,
) {
let key = facet_key_from_vertices(&[edge.v0(), edge.v1()]);
if queued.insert(key) {
queue.push_back((edge, key));
stats.max_queue_len = stats.max_queue_len.max(queue.len());
}
}
fn enqueue_simplex_triangles<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
queue: &mut VecDeque<(TriangleHandle, u64)>,
queued: &mut FastHashSet<u64>,
stats: &mut DelaunayRepairStats,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 5 {
return;
}
let Some(simplex) = tds.simplex(simplex_key) else {
return;
};
let vertices = simplex.vertices();
let vertex_count = vertices.len();
for i in 0..vertex_count {
for j in (i + 1)..vertex_count {
for k in (j + 1)..vertex_count {
let triangle = TriangleHandle::new(vertices[i], vertices[j], vertices[k]);
enqueue_triangle(triangle, queue, queued, stats);
}
}
}
}
fn enqueue_triangle(
triangle: TriangleHandle,
queue: &mut VecDeque<(TriangleHandle, u64)>,
queued: &mut FastHashSet<u64>,
stats: &mut DelaunayRepairStats,
) {
let vertices = triangle.vertices();
let key = facet_key_from_vertices(&vertices);
if queued.insert(key) {
queue.push_back((triangle, key));
stats.max_queue_len = stats.max_queue_len.max(queue.len());
}
}
fn enqueue_simplex_ridges<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
queue: &mut VecDeque<(RidgeHandle, u64)>,
queued: &mut FastHashSet<u64>,
handles: &mut FastHashMap<u64, RidgeHandle>,
stats: &mut DelaunayRepairStats,
) -> Result<(), FlipError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 3 {
return Ok(());
}
let Some(simplex) = tds.simplex(simplex_key) else {
return Ok(());
};
let vertex_count = simplex.number_of_vertices();
for i in 0..vertex_count {
for j in (i + 1)..vertex_count {
let handle = RidgeHandle::new(
simplex_key,
u8::try_from(i).map_err(|_| FlipError::InvalidRidgeIndex {
simplex_key,
omit_a: u8::MAX,
omit_b: u8::MAX,
vertex_count,
})?,
u8::try_from(j).map_err(|_| FlipError::InvalidRidgeIndex {
simplex_key,
omit_a: u8::MAX,
omit_b: u8::MAX,
vertex_count,
})?,
);
enqueue_ridge(tds, handle, queue, queued, handles, stats);
}
}
Ok(())
}
fn enqueue_ridge<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
handle: RidgeHandle,
queue: &mut VecDeque<(RidgeHandle, u64)>,
queued: &mut FastHashSet<u64>,
handles: &mut FastHashMap<u64, RidgeHandle>,
stats: &mut DelaunayRepairStats,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
if D < 3 {
return;
}
let Some(simplex) = tds.simplex(handle.simplex_key()) else {
return;
};
let vertex_count = simplex.number_of_vertices();
let omit_a = usize::from(handle.omit_a());
let omit_b = usize::from(handle.omit_b());
if omit_a >= vertex_count || omit_b >= vertex_count || omit_a == omit_b {
return;
}
let ridge_vertices = ridge_vertices_from_simplex(simplex, omit_a, omit_b);
if ridge_vertices.len() != D - 1 {
return;
}
let key = facet_key_from_vertices(&ridge_vertices);
handles.insert(key, handle);
if queued.insert(key) {
queue.push_back((handle, key));
stats.max_queue_len = stats.max_queue_len.max(queue.len());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DelaunayTriangulation;
use crate::core::algorithms::incremental_insertion::{
DelaunayRepairFailureContext, repair_neighbor_pointers,
};
use crate::core::algorithms::locate::LocateResult;
use crate::core::collections::Uuid;
use crate::core::validation::TopologyGuarantee;
use crate::geometry::kernel::{AdaptiveKernel, FastKernel};
use crate::repair::DelaunayRepairOperation;
use crate::topology::traits::topological_space::ToroidalConstructionMode;
use crate::vertex;
use approx::assert_relative_eq;
use proptest::prelude::*;
use rand::{RngExt, SeedableRng, rngs::StdRng};
use slotmap::KeyData;
use std::{error::Error as _, iter::once, sync::Once};
fn init_tracing() {
static INIT: Once = Once::new();
INIT.call_once(|| {
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn"));
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_test_writer()
.try_init();
});
}
fn unit_vector<const D: usize>(index: usize) -> [f64; D] {
let mut coords = [0.0; D];
coords[index] = 1.0;
coords
}
fn scaled_unit_vector<const D: usize>(index: usize, scale: f64) -> [f64; D] {
let mut coords = [0.0; D];
coords[index] = scale;
coords
}
fn insert_standard_simplex_vertices<const D: usize>(
tds: &mut Tds<f64, (), (), D>,
) -> Vec<VertexKey> {
let mut vertices = Vec::with_capacity(D + 1);
vertices.push(tds.insert_vertex_with_mapping(vertex!([0.0; D])).unwrap());
for axis in 0..D {
vertices.push(
tds.insert_vertex_with_mapping(vertex!(unit_vector::<D>(axis)))
.unwrap(),
);
}
vertices
}
fn periodic_test_offsets<const D: usize>(len: usize) -> Vec<[i8; D]> {
let mut offsets = Vec::with_capacity(len);
for index in 0..len {
let mut offset = [0_i8; D];
offset[index % D] = i8::try_from(index).expect("test offset index fits in i8");
offsets.push(offset);
}
offsets
}
fn assert_simplex_offsets_by_vertex<const D: usize>(
tds: &Tds<f64, (), (), D>,
simplex_key: SimplexKey,
expected_offsets: &[(VertexKey, [i8; D])],
) {
let simplex = tds.simplex(simplex_key).unwrap();
let offsets = simplex
.periodic_vertex_offsets()
.expect("simplex should carry periodic offsets");
assert_eq!(offsets.len(), simplex.number_of_vertices());
assert_eq!(expected_offsets.len(), simplex.number_of_vertices());
for &(vertex_key, expected_offset) in expected_offsets {
let index = simplex
.vertices()
.iter()
.position(|&candidate| candidate == vertex_key)
.expect("expected vertex should be present in simplex");
assert_eq!(
offsets[index], expected_offset,
"unexpected periodic offset for vertex {vertex_key:?} in simplex {simplex_key:?}"
);
}
}
#[test]
fn test_source_simplex_is_certified_positive_requires_source_and_positive_order() {
let source_simplex = SimplexKey::from(KeyData::from_ffi(42));
let positive = [
Point::new([0.0, 0.0]),
Point::new([1.0, 0.0]),
Point::new([0.0, 1.0]),
];
let negative = [positive[1], positive[0], positive[2]];
assert!(source_simplex_is_certified_positive(
Some(source_simplex),
&positive
));
assert!(!source_simplex_is_certified_positive(None, &positive));
assert!(!source_simplex_is_certified_positive(
Some(source_simplex),
&negative,
));
}
fn vertex_key_buffer(
vertices: &[VertexKey],
) -> SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> {
vertices.iter().copied().collect()
}
fn skewed_point<const D: usize>() -> [f64; D] {
let mut coords = [0.0; D];
for (i, coord) in coords.iter_mut().enumerate().take(D) {
let idx = f64::from(u32::try_from(i + 1).expect("index fits in u32"));
*coord = 0.11 * idx;
}
coords
}
fn translated_scaled_unit_vector<const D: usize>(
index: usize,
offset: f64,
scale: f64,
) -> [f64; D] {
let mut coords = [offset; D];
coords[index] += scale;
coords
}
fn translated_scaled_skewed_point<const D: usize>(offset: f64, scale: f64) -> [f64; D] {
let mut coords = [offset; D];
for (i, coord) in coords.iter_mut().enumerate().take(D) {
let idx = f64::from(u32::try_from(i + 1).expect("index fits in u32"));
*coord += scale * 0.11 * idx;
}
coords
}
fn inserted_face_vertices<const D: usize>(
info: &FlipInfo<D>,
expected: usize,
) -> Result<Vec<VertexKey>, TestCaseError> {
let vertices: Vec<_> = info.inserted_face_vertices.iter().copied().collect();
if vertices.len() != expected {
return Err(TestCaseError::fail(format!(
"flip reported {} inserted-face vertices, expected {expected}",
vertices.len()
)));
}
Ok(vertices)
}
fn to_dynamic<const D: usize, const K: usize>(context: FlipContext<D, K>) -> FlipContextDyn<D> {
FlipContextDyn {
removed_face_vertices: context.removed_face_vertices,
inserted_face_vertices: context.inserted_face_vertices,
removed_simplices: context.removed_simplices,
direction: context.direction,
}
}
macro_rules! gen_removed_simplex_snapshot_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_snapshot_removed_simplex_vertices_captures_vertices_and_reports_missing_simplex_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let vertices = insert_standard_simplex_vertices::<$dim>(&mut tds);
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vertices.clone(), None).unwrap())
.unwrap();
let removed_simplices: SimplexKeyBuffer = std::iter::once(simplex_key).collect();
let snapshot = snapshot_removed_simplex_vertices(&tds, &removed_simplices).unwrap();
assert_eq!(snapshot.len(), 1);
assert_eq!(snapshot[0].iter().copied().collect::<Vec<_>>(), vertices);
let missing_simplex = SimplexKey::from(KeyData::from_ffi(999_999 + $dim));
let missing_simplices: SimplexKeyBuffer = std::iter::once(missing_simplex).collect();
let err = snapshot_removed_simplex_vertices(&tds, &missing_simplices).unwrap_err();
assert!(matches!(
err,
FlipError::MissingSimplex { simplex_key } if simplex_key == missing_simplex
));
}
#[test]
fn [<test_last_applied_flip_preserves_removed_simplex_vertex_snapshots_ $dim d>]() {
let removed_simplex = SimplexKey::from(KeyData::from_ffi(101 + $dim));
let new_simplex = SimplexKey::from(KeyData::from_ffi(102 + $dim));
let v1 = VertexKey::from(KeyData::from_ffi(201 + $dim));
let v2 = VertexKey::from(KeyData::from_ffi(202 + $dim));
let v3 = VertexKey::from(KeyData::from_ffi(203 + $dim));
let v4 = VertexKey::from(KeyData::from_ffi(204 + $dim));
let mut removed_simplex_vertices = RemovedSimplexVertexSnapshot::new();
removed_simplex_vertices.push([v1, v2, v3].into_iter().collect::<VertexKeyList>());
let applied = AppliedFlip::<$dim> {
info: FlipInfo {
kind: BistellarFlipKind::k2($dim),
direction: FlipDirection::Forward,
removed_simplices: std::iter::once(removed_simplex).collect(),
new_simplices: std::iter::once(new_simplex).collect(),
removed_face_vertices: [v3, v1].into_iter().collect(),
inserted_face_vertices: [v4, v2].into_iter().collect(),
},
removed_simplex_vertices,
};
let last = LastAppliedFlip::from_applied_flip(&applied);
assert_eq!(last.k_move, 2);
assert_eq!(
last.removed_face_vertices
.iter()
.copied()
.collect::<Vec<_>>(),
vec![v1, v3]
);
assert_eq!(
last.inserted_face_vertices
.iter()
.copied()
.collect::<Vec<_>>(),
vec![v2, v4]
);
assert_eq!(
last.removed_simplices.iter().copied().collect::<Vec<_>>(),
vec![removed_simplex]
);
assert_eq!(
last.new_simplices.iter().copied().collect::<Vec<_>>(),
vec![new_simplex]
);
let lines = last.removed_simplex_vertex_lines();
assert_eq!(lines.len(), 1);
assert!(lines[0].contains(&format!("{removed_simplex:?}: vertices=")));
assert!(!lines[0].contains("missing-snapshot"));
let mut placeholder = LastAppliedFlip::new(1, &[v1], &[v2]);
placeholder.removed_simplices.push(removed_simplex);
assert_eq!(
placeholder.removed_simplex_vertex_lines(),
vec![format!("{removed_simplex:?}: missing-snapshot")]
);
}
}
};
}
gen_removed_simplex_snapshot_tests!(2);
gen_removed_simplex_snapshot_tests!(3);
gen_removed_simplex_snapshot_tests!(4);
gen_removed_simplex_snapshot_tests!(5);
struct RidgeDiagnosticFixture3d {
tds: Tds<f64, (), (), 3>,
origin_vertex: VertexKey,
x_axis_vertex: VertexKey,
y_axis_vertex: VertexKey,
upper_apex_vertex: VertexKey,
lower_apex_vertex: VertexKey,
upper_tetrahedron: SimplexKey,
lower_neighbor: SimplexKey,
}
impl RidgeDiagnosticFixture3d {
fn new() -> Self {
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let origin_vertex = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let x_axis_vertex = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let y_axis_vertex = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let upper_apex_vertex = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let lower_apex_vertex = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, -1.0]))
.unwrap();
let upper_tetrahedron = tds
.insert_simplex_with_mapping(
Simplex::new(
vec![
origin_vertex,
x_axis_vertex,
y_axis_vertex,
upper_apex_vertex,
],
None,
)
.unwrap(),
)
.unwrap();
let lower_neighbor = tds
.insert_simplex_with_mapping(
Simplex::new(
vec![
origin_vertex,
x_axis_vertex,
y_axis_vertex,
lower_apex_vertex,
],
None,
)
.unwrap(),
)
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
Self {
tds,
origin_vertex,
x_axis_vertex,
y_axis_vertex,
upper_apex_vertex,
lower_apex_vertex,
upper_tetrahedron,
lower_neighbor,
}
}
fn ridge_ab(&self) -> SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> {
[self.origin_vertex, self.x_axis_vertex]
.into_iter()
.collect()
}
fn ridge_handle_abcd(&self) -> RidgeHandle {
RidgeHandle::new(self.upper_tetrahedron, 2, 3)
}
fn last_applied_flip(&self) -> LastAppliedFlip {
let mut removed_simplex_vertices = RemovedSimplexVertexSnapshot::new();
removed_simplex_vertices.push(
[
self.origin_vertex,
self.x_axis_vertex,
self.y_axis_vertex,
self.upper_apex_vertex,
]
.into_iter()
.collect::<VertexKeyList>(),
);
let applied = AppliedFlip::<3> {
info: FlipInfo {
kind: BistellarFlipKind::k2(3),
direction: FlipDirection::Forward,
removed_simplices: std::iter::once(self.upper_tetrahedron).collect(),
new_simplices: std::iter::once(self.lower_neighbor).collect(),
removed_face_vertices: [
self.origin_vertex,
self.x_axis_vertex,
self.y_axis_vertex,
]
.into_iter()
.collect(),
inserted_face_vertices: [self.upper_apex_vertex, self.lower_apex_vertex]
.into_iter()
.collect(),
},
removed_simplex_vertices,
};
LastAppliedFlip::from_applied_flip(&applied)
}
}
#[test]
fn test_ridge_diagnostic_helpers_format_valid_missing_and_invalid_simplices() {
init_tracing();
let fixture = RidgeDiagnosticFixture3d::new();
let ridge = fixture.ridge_ab();
let simplex = fixture.tds.simplex(fixture.upper_tetrahedron).unwrap();
let ridge_neighbors = ridge_neighbor_simplices_for_simplex(simplex, &ridge);
assert!(
ridge_neighbors.contains(&fixture.lower_neighbor),
"shared-face neighbor should be visible from the ridge diagnostics"
);
let incident =
ridge_incident_simplex_summary(&fixture.tds, fixture.upper_tetrahedron, &ridge);
assert!(incident.contains(&format!("{:?}: extras=", fixture.upper_tetrahedron)));
assert!(incident.contains("ridge_neighbors="));
assert!(incident.contains(&format!("{:?}", fixture.lower_neighbor)));
let simplex_summary = simplex_vertex_summary(&fixture.tds, fixture.upper_tetrahedron);
assert!(simplex_summary.contains("vertices="));
let facet_summary = facet_incident_simplex_summary(
&fixture.tds,
fixture.upper_tetrahedron,
&[
fixture.origin_vertex,
fixture.x_axis_vertex,
fixture.y_axis_vertex,
],
);
assert!(facet_summary.contains("opposite_vertices="));
assert!(facet_summary.contains("neighbors="));
let missing_simplex = SimplexKey::from(KeyData::from_ffi(999_901));
assert_eq!(
ridge_incident_simplex_summary(&fixture.tds, missing_simplex, &ridge),
format!("{missing_simplex:?}: missing")
);
assert_eq!(
simplex_vertex_summary(&fixture.tds, missing_simplex),
format!("{missing_simplex:?}: missing")
);
assert_eq!(
facet_incident_simplex_summary(
&fixture.tds,
missing_simplex,
&[fixture.origin_vertex, fixture.x_axis_vertex],
),
format!("{missing_simplex:?}: missing")
);
let missing_vertex = VertexKey::from(KeyData::from_ffi(999_902));
let invalid_ridge: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
[fixture.origin_vertex, missing_vertex]
.into_iter()
.collect();
let invalid_summary =
ridge_incident_simplex_summary(&fixture.tds, fixture.upper_tetrahedron, &invalid_ridge);
assert!(invalid_summary.contains("extras_error="));
}
#[test]
fn test_predecessor_diagnostic_summaries_include_flip_overlap() {
init_tracing();
let fixture = RidgeDiagnosticFixture3d::new();
let last = fixture.last_applied_flip();
let ridge_summary = predecessor_flip_summary(
&fixture.tds,
RidgeHandle::new(fixture.lower_neighbor, 2, 3),
&[fixture.lower_neighbor],
&last,
);
assert!(ridge_summary.contains("ridge_simplex_is_new=true"));
assert!(ridge_summary.contains("global_simplices_in_new"));
assert!(ridge_summary.contains("predecessor_new_simplex_vertices"));
let postcondition_summary = postcondition_facet_predecessor_summary(
&fixture.tds,
&[fixture.upper_tetrahedron, fixture.lower_neighbor],
&last,
);
assert!(postcondition_summary.contains("incident_simplices_in_new"));
assert!(postcondition_summary.contains("incident_simplices_in_removed"));
assert!(postcondition_summary.contains("predecessor_removed_simplex_vertices"));
assert!(!postcondition_summary.contains("missing-snapshot"));
}
#[test]
fn test_debug_ridge_context_exercises_valid_missing_and_invalid_paths() {
init_tracing();
let fixture = RidgeDiagnosticFixture3d::new();
let last = fixture.last_applied_flip();
let mut diagnostics = RepairDiagnostics::default();
debug_ridge_context(
&fixture.tds,
fixture.ridge_handle_abcd(),
Some(2),
&mut diagnostics,
Some(&last),
);
assert_eq!(diagnostics.ridge_debug_emitted, 1);
let missing_simplex = SimplexKey::from(KeyData::from_ffi(999_903));
debug_ridge_context(
&fixture.tds,
RidgeHandle::new(missing_simplex, 0, 1),
None,
&mut diagnostics,
None,
);
assert_eq!(diagnostics.ridge_debug_emitted, 2);
debug_ridge_context(
&fixture.tds,
RidgeHandle::new(fixture.upper_tetrahedron, 0, 0),
None,
&mut diagnostics,
None,
);
assert_eq!(diagnostics.ridge_debug_emitted, 3);
}
#[test]
fn test_ridge_debug_limit_suppresses_after_attempt_budget() {
let mut diagnostics = RepairDiagnostics {
ridge_debug_emitted: RIDGE_DEBUG_LIMIT_DEFAULT,
..RepairDiagnostics::default()
};
assert!(!should_emit_ridge_debug(&mut diagnostics, Some(99)));
assert_eq!(
diagnostics.ridge_debug_emitted,
RIDGE_DEBUG_LIMIT_DEFAULT + 1
);
}
#[test]
fn test_postcondition_facet_debug_context_is_noop_without_env_flag() {
init_tracing();
let fixture = RidgeDiagnosticFixture3d::new();
let last = fixture.last_applied_flip();
let context = FlipContext::<3, 2> {
removed_face_vertices: [
fixture.origin_vertex,
fixture.x_axis_vertex,
fixture.y_axis_vertex,
]
.into_iter()
.collect(),
inserted_face_vertices: [fixture.upper_apex_vertex, fixture.lower_apex_vertex]
.into_iter()
.collect(),
removed_simplices: [fixture.upper_tetrahedron, fixture.lower_neighbor]
.into_iter()
.collect(),
direction: FlipDirection::Forward,
};
let mut diagnostics = RepairDiagnostics::default();
debug_postcondition_facet_context(
&fixture.tds,
FacetHandle::new(fixture.upper_tetrahedron, 3),
&context,
&mut diagnostics,
Some(&last),
);
assert_eq!(diagnostics.postcondition_facet_debug_emitted, 0);
}
fn facet_index_for_edge_2d(
tds: &Tds<f64, (), (), 2>,
simplex_key: SimplexKey,
edge_start: VertexKey,
edge_end: VertexKey,
) -> u8 {
let simplex = tds
.simplex(simplex_key)
.expect("simplex key missing in TDS");
for facet_idx in 0..simplex.number_of_vertices() {
let facet = facet_vertices_from_simplex(simplex, facet_idx);
if facet.len() == 2 && facet.contains(&edge_start) && facet.contains(&edge_end) {
return u8::try_from(facet_idx).expect("facet index fits in u8");
}
}
panic!("edge ({edge_start:?}, {edge_end:?}) not found in simplex {simplex_key:?}");
}
fn facet_index_for_face_3d(
tds: &Tds<f64, (), (), 3>,
simplex_key: SimplexKey,
face_v0: VertexKey,
face_v1: VertexKey,
face_v2: VertexKey,
) -> u8 {
let simplex = tds
.simplex(simplex_key)
.expect("simplex key missing in TDS");
for facet_idx in 0..simplex.number_of_vertices() {
let facet = facet_vertices_from_simplex(simplex, facet_idx);
if facet.len() == 3
&& facet.contains(&face_v0)
&& facet.contains(&face_v1)
&& facet.contains(&face_v2)
{
return u8::try_from(facet_idx).expect("facet index fits in u8");
}
}
panic!("face ({face_v0:?}, {face_v1:?}, {face_v2:?}) not found in simplex {simplex_key:?}");
}
fn assert_context_has_nonzero_robust_orientation(
tds: &Tds<f64, (), (), 2>,
context: &FlipContext<2, 2>,
) {
for &omit in &context.removed_face_vertices {
let mut verts: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(3);
verts.extend_from_slice(&context.inserted_face_vertices);
for &v in &context.removed_face_vertices {
if v != omit {
verts.push(v);
}
}
let points = vertices_to_points(tds, &verts).unwrap();
match robust_orientation(&points) {
Ok(Orientation::POSITIVE | Orientation::NEGATIVE) => {}
other => panic!("robust_orientation must resolve to ±1, got {other:?}"),
}
}
}
#[test]
fn test_resolve_facet_handle_for_key_remaps_after_slot_swap() {
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 simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap())
.unwrap();
let stale_handle = FacetHandle::new(simplex_key, 0);
let stable_key = {
let simplex = tds.simplex(simplex_key).unwrap();
let facet_vertices =
facet_vertices_from_simplex(simplex, usize::from(stale_handle.facet_index()));
facet_key_from_vertices(&facet_vertices)
};
tds.simplex_mut(simplex_key)
.unwrap()
.swap_vertex_slots(0, 1);
let resolved = resolve_facet_handle_for_key(&tds, stale_handle, stable_key)
.expect("facet handle should be recoverable by stable key");
assert_eq!(resolved.simplex_key(), simplex_key);
assert_eq!(usize::from(resolved.facet_index()), 1);
let resolved_key = {
let simplex = tds.simplex(simplex_key).unwrap();
let facet_vertices =
facet_vertices_from_simplex(simplex, usize::from(resolved.facet_index()));
facet_key_from_vertices(&facet_vertices)
};
assert_eq!(resolved_key, stable_key);
}
#[test]
fn test_resolve_ridge_handle_for_key_remaps_after_slot_swap() {
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let v0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let v1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let v2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let v3 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2, v3], None).unwrap())
.unwrap();
let stale_handle = RidgeHandle::new(simplex_key, 0, 1);
let stable_key = {
let simplex = tds.simplex(simplex_key).unwrap();
let ridge_vertices = ridge_vertices_from_simplex(
simplex,
usize::from(stale_handle.omit_a()),
usize::from(stale_handle.omit_b()),
);
facet_key_from_vertices(&ridge_vertices)
};
tds.simplex_mut(simplex_key)
.unwrap()
.swap_vertex_slots(0, 2);
let resolved = resolve_ridge_handle_for_key(&tds, stale_handle, stable_key)
.expect("ridge handle should be recoverable by stable key");
assert_eq!(resolved.simplex_key(), simplex_key);
assert_eq!((resolved.omit_a(), resolved.omit_b()), (1, 2));
let resolved_key = {
let simplex = tds.simplex(simplex_key).unwrap();
let ridge_vertices = ridge_vertices_from_simplex(
simplex,
usize::from(resolved.omit_a()),
usize::from(resolved.omit_b()),
);
facet_key_from_vertices(&ridge_vertices)
};
assert_eq!(resolved_key, stable_key);
}
#[test]
fn test_k2_flip_rewires_external_neighbors_across_cavity_boundary() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_left_bottom = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_right_bottom = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap();
let v_left_top = tds.insert_vertex_with_mapping(vertex!([0.0, 2.0])).unwrap();
let v_right_top = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let v_external = tds
.insert_vertex_with_mapping(vertex!([-1.0, 1.0]))
.unwrap();
let simplex_cavity_left = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_left_bottom, v_right_bottom, v_left_top], None).unwrap(),
)
.unwrap();
let simplex_cavity_right = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_right_bottom, v_left_bottom, v_right_top], None).unwrap(),
)
.unwrap();
let simplex_external_left = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_left_bottom, v_left_top, v_external], None).unwrap(),
)
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
assert!(tds.is_valid().is_ok());
let facet_idx_flip_edge =
facet_index_for_edge_2d(&tds, simplex_cavity_left, v_left_bottom, v_right_bottom);
let ctx = build_k2_flip_context(
&tds,
FacetHandle::new(simplex_cavity_left, facet_idx_flip_edge),
)
.unwrap();
let info = apply_bistellar_flip(&mut tds, &ctx).unwrap();
assert!(!tds.contains_simplex(simplex_cavity_left));
assert!(!tds.contains_simplex(simplex_cavity_right));
assert!(tds.contains_simplex(simplex_external_left));
let facet_idx_glue_edge =
facet_index_for_edge_2d(&tds, simplex_external_left, v_left_bottom, v_left_top);
let external_simplex = tds.simplex(simplex_external_left).unwrap();
let neighbor_key_glue = external_simplex
.neighbor_key(usize::from(facet_idx_glue_edge))
.expect("external neighbors should exist")
.expect("external simplex should have a neighbor across the glue edge after the flip");
assert!(tds.contains_simplex(neighbor_key_glue));
assert!(
info.new_simplices
.iter()
.copied()
.any(|k| k == neighbor_key_glue),
"expected external neighbor across glue edge to be one of the flip-inserted simplices"
);
let neighbor_simplex = tds.simplex(neighbor_key_glue).unwrap();
let mirror_idx = external_simplex
.mirror_facet_index(usize::from(facet_idx_glue_edge), neighbor_simplex)
.expect("mirror facet index should exist");
let neighbor_back = neighbor_simplex.neighbor_key(mirror_idx).flatten();
assert_eq!(neighbor_back, Some(simplex_external_left));
for &simplex_key in &info.new_simplices {
let simplex = tds.simplex(simplex_key).unwrap();
if let Some(ns) = simplex.neighbors() {
for neighbor_key in ns.flatten() {
assert!(
tds.contains_simplex(neighbor_key),
"dangling neighbor pointer from {simplex_key:?} to {neighbor_key:?}"
);
}
}
}
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_k2_flip_preserves_periodic_external_offsets() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let v_left_bottom = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let v_right_bottom = tds.insert_vertex_with_mapping(vertex!([2.0, 0.0])).unwrap();
let v_left_top = tds.insert_vertex_with_mapping(vertex!([0.0, 2.0])).unwrap();
let v_right_top = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let v_external = tds
.insert_vertex_with_mapping(vertex!([-1.0, 1.0]))
.unwrap();
let offset_left_bottom = [0_i8, 0_i8];
let offset_right_bottom = [1_i8, 0_i8];
let offset_left_top = [0_i8, 1_i8];
let offset_right_top = [1_i8, 1_i8];
let offset_external = [0_i8, -1_i8];
let simplex_cavity_left = insert_periodic_simplex_with_offsets(
&mut tds,
vec![v_left_bottom, v_right_bottom, v_left_top],
vec![offset_left_bottom, offset_right_bottom, offset_left_top],
);
let simplex_cavity_right = insert_periodic_simplex_with_offsets(
&mut tds,
vec![v_right_bottom, v_left_bottom, v_right_top],
vec![offset_right_bottom, offset_left_bottom, offset_right_top],
);
let simplex_external_left = insert_periodic_simplex_with_offsets(
&mut tds,
vec![v_left_bottom, v_left_top, v_external],
vec![offset_left_bottom, offset_left_top, offset_external],
);
repair_neighbor_pointers(&mut tds).unwrap();
assert!(tds.is_valid().is_ok());
let facet_idx_flip_edge =
facet_index_for_edge_2d(&tds, simplex_cavity_left, v_left_bottom, v_right_bottom);
let ctx = build_k2_flip_context(
&tds,
FacetHandle::new(simplex_cavity_left, facet_idx_flip_edge),
)
.unwrap();
let info = apply_bistellar_flip_with_k(
&mut tds,
2,
&ctx.removed_face_vertices,
&ctx.inserted_face_vertices,
&ctx.removed_simplices,
ctx.direction,
ReplacementOrientationPolicy::AllowSigned,
FlipValidationScope::LocalCavity,
)
.unwrap()
.info;
assert!(!tds.contains_simplex(simplex_cavity_left));
assert!(!tds.contains_simplex(simplex_cavity_right));
assert!(tds.contains_simplex(simplex_external_left));
let expected_left_replacement = [
(v_left_bottom, offset_left_bottom),
(v_left_top, offset_left_top),
(v_right_top, offset_right_top),
];
let expected_right_replacement = [
(v_right_bottom, offset_right_bottom),
(v_left_top, offset_left_top),
(v_right_top, offset_right_top),
];
for &simplex_key in &info.new_simplices {
let simplex = tds.simplex(simplex_key).unwrap();
let expected = if simplex.contains_vertex(v_left_bottom) {
&expected_left_replacement
} else {
&expected_right_replacement
};
assert_simplex_offsets_by_vertex(&tds, simplex_key, expected);
}
let facet_idx_glue_edge =
facet_index_for_edge_2d(&tds, simplex_external_left, v_left_bottom, v_left_top);
let external_simplex = tds.simplex(simplex_external_left).unwrap();
let neighbor_key_glue = external_simplex
.neighbor_key(usize::from(facet_idx_glue_edge))
.expect("external neighbors should exist")
.expect("external simplex should have a replacement neighbor across the glue edge");
assert!(
info.new_simplices
.iter()
.copied()
.any(|simplex_key| simplex_key == neighbor_key_glue),
"expected periodic external facet to be wired to a flip replacement simplex"
);
assert_simplex_offsets_by_vertex(
&tds,
simplex_external_left,
&[
(v_left_bottom, offset_left_bottom),
(v_left_top, offset_left_top),
(v_external, offset_external),
],
);
assert_simplex_offsets_by_vertex(&tds, neighbor_key_glue, &expected_left_replacement);
assert!(tds.is_valid().is_ok());
}
macro_rules! gen_replacement_orientation_helper_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_orient_replacement_simplices_uses_periodic_external_simplex_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let offsets = periodic_test_offsets::<$dim>($dim + 1);
let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap();
external_simplex.set_periodic_vertex_offsets(offsets.clone()).unwrap();
let external_simplex_key = tds.insert_simplex_with_mapping(external_simplex).unwrap();
let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)];
let mut replacement_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> =
vec![Some(offsets.clone().into())];
orient_replacement_simplices(
&tds,
&mut replacement_simplices,
&mut replacement_offsets,
&[FacetHandle::new(external_simplex_key, 0)],
)
.unwrap();
let mut expected_vertices = simplex_vertices.clone();
expected_vertices.swap(0, 1);
assert_eq!(
replacement_simplices[0].iter().copied().collect::<Vec<_>>(),
expected_vertices,
"periodic external facet parity should flip a same-order replacement simplex"
);
let mut expected_offsets = offsets;
expected_offsets.swap(0, 1);
assert_eq!(
replacement_offsets[0].as_deref(),
Some(expected_offsets.as_slice()),
"periodic offsets should stay aligned with swapped replacement vertices"
);
}
#[test]
fn [<test_orient_replacement_simplices_rejects_conflicting_periodic_external_offsets_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let external_offsets = vec![[0_i8; $dim]; $dim + 1];
let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap();
external_simplex
.set_periodic_vertex_offsets(external_offsets)
.unwrap();
let external_simplex_key = tds.insert_simplex_with_mapping(external_simplex).unwrap();
let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)];
let mut replacement_offsets = vec![[0_i8; $dim]; $dim + 1];
replacement_offsets[1][0] = 1;
let mut replacement_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> =
vec![Some(replacement_offsets.into())];
let result = orient_replacement_simplices(
&tds,
&mut replacement_simplices,
&mut replacement_offsets,
&[FacetHandle::new(external_simplex_key, 0)],
);
assert!(
matches!(
result,
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ConflictingReplacementPeriodicFrameTranslation {
source_simplex_key,
target_simplex_index: 0,
..
}
}) if source_simplex_key == external_simplex_key
),
"conflicting periodic external facet translations should fail before mutation: {result:?}"
);
}
#[test]
fn [<test_orient_replacement_simplices_rejects_periodic_offset_count_mismatch_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)];
let mut replacement_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> = Vec::new();
let result = orient_replacement_simplices(
&tds,
&mut replacement_simplices,
&mut replacement_offsets,
&[],
);
assert!(
matches!(
result,
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ReplacementPeriodicOffsetCountMismatch {
simplex_count: 1,
offset_count: 0,
}
})
),
"replacement offset sidecar length mismatch should fail explicitly: {result:?}"
);
}
#[test]
fn [<test_orient_replacement_simplices_rejects_missing_replacement_periodic_offsets_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap();
external_simplex
.set_periodic_vertex_offsets(vec![[0_i8; $dim]; $dim + 1])
.unwrap();
let external_simplex_key = tds.insert_simplex_with_mapping(external_simplex).unwrap();
let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)];
let mut replacement_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> = vec![None];
let result = orient_replacement_simplices(
&tds,
&mut replacement_simplices,
&mut replacement_offsets,
&[FacetHandle::new(external_simplex_key, 0)],
);
assert!(
matches!(
result,
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::MissingReplacementPeriodicOffsets {
simplex_index: 0,
}
})
),
"periodic external parity should require replacement offsets: {result:?}"
);
}
#[test]
fn [<test_orient_replacement_simplices_rejects_replacement_periodic_offset_length_mismatch_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let mut external_simplex = Simplex::new(simplex_vertices.clone(), None).unwrap();
external_simplex
.set_periodic_vertex_offsets(vec![[0_i8; $dim]; $dim + 1])
.unwrap();
let external_simplex_key = tds.insert_simplex_with_mapping(external_simplex).unwrap();
let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)];
let replacement_offsets = vec![[0_i8; $dim]; $dim];
let mut replacement_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> =
vec![Some(replacement_offsets.into())];
let result = orient_replacement_simplices(
&tds,
&mut replacement_simplices,
&mut replacement_offsets,
&[FacetHandle::new(external_simplex_key, 0)],
);
assert!(
matches!(
result,
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ReplacementPeriodicOffsetLengthMismatch {
simplex_index: 0,
offset_count: $dim,
vertex_count,
}
}) if vertex_count == $dim + 1
),
"replacement periodic offsets should stay slot-aligned with vertices: {result:?}"
);
}
#[test]
fn [<test_orient_replacement_simplices_rejects_missing_external_simplex_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let external_simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(simplex_vertices.clone(), None).unwrap())
.unwrap();
assert_eq!(tds.remove_simplices_by_keys(&[external_simplex_key]), 1);
let mut replacement_simplices = vec![vertex_key_buffer(&simplex_vertices)];
let mut replacement_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> =
vec![None; replacement_simplices.len()];
let result = orient_replacement_simplices(
&tds,
&mut replacement_simplices,
&mut replacement_offsets,
&[FacetHandle::new(external_simplex_key, 0)],
);
assert!(
matches!(
result,
Err(FlipError::MissingSimplex { simplex_key }) if simplex_key == external_simplex_key
),
"missing external simplex should fail explicitly: {result:?}"
);
}
#[test]
fn [<test_replacement_orientation_helpers_cover_error_paths_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let v_square = tds.insert_vertex_with_mapping(vertex!([1.0; $dim])).unwrap();
let v_collinear = tds
.insert_vertex_with_mapping(vertex!(scaled_unit_vector::<$dim>(0, 2.0)))
.unwrap();
let source = vertex_key_buffer(&simplex_vertices);
let mut target_vertices = simplex_vertices[1..].to_vec();
target_vertices.push(v_square);
let target = vertex_key_buffer(&target_vertices);
let mut neighbor_vertices = simplex_vertices[..$dim].to_vec();
neighbor_vertices.push(v_square);
let neighbor = vertex_key_buffer(&neighbor_vertices);
let short = vertex_key_buffer(&simplex_vertices[..$dim]);
let mut two_unique_vertices = simplex_vertices.clone();
two_unique_vertices[1] = v_square;
two_unique_vertices[2] = v_collinear;
let two_unique = vertex_key_buffer(&two_unique_vertices);
let order = facet_order(&source, 1).unwrap();
let expected_order = simplex_vertices
.iter()
.enumerate()
.filter_map(|(idx, &vertex)| (idx != 1).then_some(vertex))
.collect::<Vec<_>>();
assert_eq!(order.iter().copied().collect::<Vec<_>>(), expected_order);
assert!(
matches!(
facet_order(&source, source.len()),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ReplacementFacetIndexOutOfRange {
facet_index,
vertex_count,
}
}) if facet_index == source.len() && vertex_count == source.len()
),
"out-of-range facet indices should be rejected"
);
assert_eq!(matching_facet_index(&source, 0, &target).unwrap(), Some($dim));
assert_eq!(matching_facet_index(&source, 0, &short).unwrap(), None);
assert_eq!(matching_facet_index(&source, 0, &two_unique).unwrap(), None);
assert_eq!(shared_facet_indices(&source, &neighbor), Some(($dim, $dim)));
assert_eq!(shared_facet_indices(&source, &short), None);
assert_eq!(shared_facet_indices(&source, &two_unique), None);
assert!(!facet_orders_coherent(&source, $dim, &neighbor, $dim).unwrap());
assert!(
matches!(
facet_orders_coherent(&source, source.len(), &neighbor, $dim),
Err(FlipError::InvalidFlipContext { .. })
),
"invalid facet-order constraints should surface as invalid context"
);
let mut odd_target_vertices = simplex_vertices.clone();
odd_target_vertices.swap(1, 2);
let odd_target = vertex_key_buffer(&odd_target_vertices);
assert_eq!(permutation_odd(&source, &odd_target), Some(true));
assert_eq!(permutation_odd(&source, &short), None);
assert_eq!(permutation_odd(&source, &neighbor), None);
}
#[test]
fn [<test_set_flip_assignment_rejects_conflicts_and_invalid_indices_ $dim d>]() {
let mut assignments: SmallBuffer<Option<bool>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::from_elem(None, 1);
assert!(set_flip_assignment(&mut assignments, 0, true).unwrap());
assert_eq!(assignments[0], Some(true));
assert!(!set_flip_assignment(&mut assignments, 0, true).unwrap());
assert!(
matches!(
set_flip_assignment(&mut assignments, 0, false),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ConflictingReplacementOrientationForSimplex {
simplex_index: 0,
}
})
),
"conflicting parity assignments should fail"
);
assert!(
matches!(
set_flip_assignment(&mut assignments, 1, false),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ReplacementOrientationIndexOutOfRange {
simplex_index: 1,
}
})
),
"out-of-range parity assignments should fail"
);
}
#[test]
fn [<test_orient_replacement_simplices_aligns_external_and_internal_facets_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let v_square = tds.insert_vertex_with_mapping(vertex!([1.0; $dim])).unwrap();
let external_simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(simplex_vertices.clone(), None).unwrap())
.unwrap();
let mut external_aligned = vec![vertex_key_buffer(&simplex_vertices)];
let mut external_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> =
vec![None; external_aligned.len()];
orient_replacement_simplices(
&tds,
&mut external_aligned,
&mut external_offsets,
&[FacetHandle::new(external_simplex_key, 0)],
)
.unwrap();
let mut expected_external = simplex_vertices.clone();
expected_external.swap(0, 1);
assert_eq!(
external_aligned[0].iter().copied().collect::<Vec<_>>(),
expected_external,
"external facet parity should flip a same-order replacement simplex"
);
let mut adjacent_vertices = simplex_vertices[..$dim].to_vec();
adjacent_vertices.push(v_square);
let mut internally_aligned = vec![
vertex_key_buffer(&simplex_vertices),
vertex_key_buffer(&adjacent_vertices),
];
let mut internal_offsets: Vec<Option<PeriodicOffsetBuffer<$dim>>> =
vec![None; internally_aligned.len()];
orient_replacement_simplices(
&tds,
&mut internally_aligned,
&mut internal_offsets,
&[],
)
.unwrap();
let (source_facet_idx, target_facet_idx) =
shared_facet_indices(&internally_aligned[0], &internally_aligned[1]).unwrap();
assert!(
facet_orders_coherent(
&internally_aligned[0],
source_facet_idx,
&internally_aligned[1],
target_facet_idx,
)
.unwrap(),
"internal shared facets should be coherent after parity propagation"
);
}
#[test]
fn [<test_validate_replacement_orientation_rejects_bad_geometry_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let simplex_vertices = insert_standard_simplex_vertices(&mut tds);
let v_collinear = tds
.insert_vertex_with_mapping(vertex!(scaled_unit_vector::<$dim>(0, 2.0)))
.unwrap();
let mut positive_vertices = simplex_vertices.clone();
if $dim % 2 == 1 {
positive_vertices.swap(1, 2);
}
let positive = vertex_key_buffer(&positive_vertices);
let positive_simplices = vec![positive];
assert!(validate_replacement_orientation(&tds, &positive_simplices).is_ok());
let mut negative_vertices = positive_vertices.clone();
negative_vertices.swap(1, 2);
let negative = vertex_key_buffer(&negative_vertices);
let negative_result = validate_replacement_orientation(&tds, &[negative]);
assert!(
matches!(
negative_result,
Err(FlipError::NegativeOrientation { ref simplex_vertices })
if simplex_vertices == &negative_vertices
),
"negative replacement simplices should fail before mutation: {negative_result:?}"
);
let mut degenerate_vertices = positive_vertices;
degenerate_vertices[$dim] = v_collinear;
let degenerate = vertex_key_buffer(°enerate_vertices);
let degenerate_result = validate_replacement_orientation(&tds, &[degenerate]);
assert!(
matches!(degenerate_result, Err(FlipError::DegenerateSimplex)),
"degenerate replacement simplices should fail before mutation: {degenerate_result:?}"
);
}
}
};
}
gen_replacement_orientation_helper_tests!(2);
gen_replacement_orientation_helper_tests!(3);
gen_replacement_orientation_helper_tests!(4);
gen_replacement_orientation_helper_tests!(5);
#[test]
#[expect(
clippy::too_many_lines,
reason = "Test constructs an explicit k=3 ridge-flip fixture and checks neighbor rewiring"
)]
fn test_k3_flip_rewires_external_neighbors_across_cavity_boundary() {
init_tracing();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let v_edge_start = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let v_edge_end = tds
.insert_vertex_with_mapping(vertex!([2.0, 0.0, 0.0]))
.unwrap();
let v_cycle_0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 2.0, 0.0]))
.unwrap();
let v_cycle_1 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 2.0]))
.unwrap();
let v_cycle_2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 2.0, 2.0]))
.unwrap();
let v_external = tds
.insert_vertex_with_mapping(vertex!([-1.0, 1.0, 1.0]))
.unwrap();
let simplex_around_edge_0 = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_edge_start, v_edge_end, v_cycle_0, v_cycle_1], None).unwrap(),
)
.unwrap();
let simplex_around_edge_1 = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_edge_start, v_edge_end, v_cycle_1, v_cycle_2], None).unwrap(),
)
.unwrap();
let simplex_around_edge_2 = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_edge_start, v_edge_end, v_cycle_2, v_cycle_0], None).unwrap(),
)
.unwrap();
let simplex_external = tds
.insert_simplex_with_mapping(
Simplex::new(vec![v_edge_start, v_cycle_0, v_cycle_1, v_external], None).unwrap(),
)
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
assert!(tds.is_valid().is_ok());
let ridge = RidgeHandle::new(simplex_around_edge_0, 2, 3);
let ctx = build_k3_flip_context(&tds, ridge).unwrap();
assert_eq!(ctx.removed_simplices.len(), 3);
assert!(
ctx.removed_simplices
.iter()
.copied()
.any(|simplex_key| simplex_key == simplex_around_edge_0)
);
assert!(
ctx.removed_simplices
.iter()
.copied()
.any(|simplex_key| simplex_key == simplex_around_edge_1)
);
assert!(
ctx.removed_simplices
.iter()
.copied()
.any(|simplex_key| simplex_key == simplex_around_edge_2)
);
let info = apply_bistellar_flip(&mut tds, &ctx).unwrap();
assert!(!tds.contains_simplex(simplex_around_edge_0));
assert!(!tds.contains_simplex(simplex_around_edge_1));
assert!(!tds.contains_simplex(simplex_around_edge_2));
for &removed_simplex in &info.removed_simplices {
assert!(!tds.contains_simplex(removed_simplex));
}
assert!(tds.contains_simplex(simplex_external));
let glue_face_facet_index =
facet_index_for_face_3d(&tds, simplex_external, v_edge_start, v_cycle_0, v_cycle_1);
let external_simplex = tds.simplex(simplex_external).unwrap();
let glued_neighbor = external_simplex
.neighbor_key(usize::from(glue_face_facet_index))
.expect("external simplex should have neighbors after repair")
.expect("external simplex should have a neighbor across the glue face");
assert!(tds.contains_simplex(glued_neighbor));
assert!(
info.new_simplices
.iter()
.copied()
.any(|simplex_key| simplex_key == glued_neighbor),
"expected glued neighbor to be one of the flip-inserted simplices"
);
let neighbor_simplex = tds.simplex(glued_neighbor).unwrap();
let mirror_idx = external_simplex
.mirror_facet_index(usize::from(glue_face_facet_index), neighbor_simplex)
.expect("mirror facet index should exist");
let neighbor_back = neighbor_simplex.neighbor_key(mirror_idx).flatten();
assert_eq!(neighbor_back, Some(simplex_external));
for &simplex_key in &info.new_simplices {
let simplex = tds.simplex(simplex_key).unwrap();
if let Some(ns) = simplex.neighbors() {
for neighbor_key in ns.flatten() {
assert!(
tds.contains_simplex(neighbor_key),
"dangling neighbor pointer from {simplex_key:?} to {neighbor_key:?}"
);
}
}
}
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_repair_diagnostics_cycle_detection_records_repeats() {
init_tracing();
let mut diagnostics = RepairDiagnostics::default();
diagnostics.record_flip_signature(10);
diagnostics.record_flip_signature(20);
assert_eq!(diagnostics.cycle_detections, 0);
diagnostics.record_flip_signature(10);
assert_eq!(diagnostics.cycle_detections, 1);
assert_eq!(diagnostics.cycle_samples, vec![10]);
diagnostics.record_flip_signature(10);
assert_eq!(diagnostics.cycle_detections, 2);
assert_eq!(diagnostics.cycle_samples, vec![10]);
}
#[test]
fn test_skip_recording_keeps_first_typed_sample() {
let mut diagnostics = RepairDiagnostics::default();
let simplex = SimplexKey::from(KeyData::from_ffi(91));
let missing_simplex = SimplexKey::from(KeyData::from_ffi(92));
let v0 = VertexKey::from(KeyData::from_ffi(101));
let v1 = VertexKey::from(KeyData::from_ffi(102));
let v2 = VertexKey::from(KeyData::from_ffi(103));
let edge = EdgeKey::new(v0, v1);
let facet = FacetHandle::new(simplex, 0);
let ridge = RidgeHandle::new(simplex, 0, 1);
let triangle = TriangleHandle::new(v0, v1, v2);
let first_inserted_sample = InsertedSimplexSkipSample {
location: RepairSkipLocation::Facet(facet),
removed_face: [v0, v1].into_iter().collect(),
inserted_face: std::iter::once(v2).collect(),
};
diagnostics.record_inserted_simplex_skip(first_inserted_sample.clone());
assert_eq!(diagnostics.inserted_simplex_skips, 1);
assert_eq!(
diagnostics.inserted_simplex_sample,
Some(first_inserted_sample.clone())
);
diagnostics.record_inserted_simplex_skip(InsertedSimplexSkipSample {
location: RepairSkipLocation::Edge(edge),
removed_face: std::iter::once(v1).collect(),
inserted_face: [v0, v2].into_iter().collect(),
});
assert_eq!(diagnostics.inserted_simplex_skips, 2);
assert_eq!(
diagnostics.inserted_simplex_sample,
Some(first_inserted_sample)
);
let first_ridge_sample = RidgeMultiplicitySkipSample {
ridge,
multiplicity: 3,
};
diagnostics.record_invalid_ridge_multiplicity_skip(first_ridge_sample);
diagnostics.record_invalid_ridge_multiplicity_skip(RidgeMultiplicitySkipSample {
ridge: RidgeHandle::new(simplex, 1, 2),
multiplicity: 4,
});
assert_eq!(diagnostics.invalid_ridge_multiplicity_skips, 2);
assert_eq!(
diagnostics.invalid_ridge_multiplicity_sample,
Some(first_ridge_sample)
);
let first_missing_sample = MissingSimplexSkipSample {
location: RepairSkipLocation::Triangle(triangle),
simplex_key: missing_simplex,
};
diagnostics.record_missing_simplex_skip(first_missing_sample);
diagnostics.record_missing_simplex_skip(MissingSimplexSkipSample {
location: RepairSkipLocation::Ridge(ridge),
simplex_key: SimplexKey::from(KeyData::from_ffi(93)),
});
assert_eq!(diagnostics.missing_simplex_skips, 2);
assert_eq!(
diagnostics.missing_simplex_sample,
Some(first_missing_sample)
);
}
#[test]
fn test_repair_skip_samples_keep_legacy_debug_shape() {
let simplex = SimplexKey::from(KeyData::from_ffi(91));
let missing_simplex = SimplexKey::from(KeyData::from_ffi(92));
let v0 = VertexKey::from(KeyData::from_ffi(101));
let v1 = VertexKey::from(KeyData::from_ffi(102));
let v2 = VertexKey::from(KeyData::from_ffi(103));
let facet = FacetHandle::new(simplex, 0);
let ridge = RidgeHandle::new(simplex, 0, 1);
let triangle = TriangleHandle::new(v0, v1, v2);
let removed_face: VertexKeyList = [v0, v1].into_iter().collect();
let inserted_face: VertexKeyList = std::iter::once(v2).collect();
let inserted_sample = InsertedSimplexSkipSample {
location: RepairSkipLocation::Facet(facet),
removed_face: removed_face.clone(),
inserted_face: inserted_face.clone(),
};
assert_eq!(
format!("{:?}", Some(inserted_sample)),
format!(
"{:?}",
Some(format!(
"facet={facet:?} removed_face={removed_face:?} inserted_face={inserted_face:?}"
))
)
);
let ridge_sample = RidgeMultiplicitySkipSample {
ridge,
multiplicity: 3,
};
assert_eq!(
format!("{:?}", Some(ridge_sample)),
format!("{:?}", Some(format!("ridge={ridge:?} multiplicity=3")))
);
let missing_sample = MissingSimplexSkipSample {
location: RepairSkipLocation::Triangle(triangle),
simplex_key: missing_simplex,
};
assert_eq!(
format!("{:?}", Some(missing_sample)),
format!(
"{:?}",
Some(format!(
"triangle={triangle:?} missing_simplex={missing_simplex:?}"
))
)
);
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct TopologySnapshot {
vertices: Vec<Uuid>,
simplex_vertices: Vec<Vec<Uuid>>,
simplex_neighbors: Vec<Vec<Option<Uuid>>>,
}
fn snapshot_topology<const D: usize>(tds: &Tds<f64, (), (), D>) -> TopologySnapshot {
let mut vertices: Vec<Uuid> = tds.vertices().map(|(_, vertex)| vertex.uuid()).collect();
vertices.sort();
let mut simplex_vertices: Vec<Vec<Uuid>> = tds
.simplices()
.map(|(_, simplex)| {
let mut uuids: Vec<Uuid> = simplex
.vertices()
.iter()
.map(|&vkey| tds.vertex(vkey).expect("vertex key missing in TDS").uuid())
.collect();
uuids.sort();
uuids
})
.collect();
simplex_vertices.sort();
let simplex_neighbors = snapshot_neighbors(tds);
TopologySnapshot {
vertices,
simplex_vertices,
simplex_neighbors,
}
}
fn snapshot_neighbors<const D: usize>(tds: &Tds<f64, (), (), D>) -> Vec<Vec<Option<Uuid>>> {
let mut simplex_neighbors: Vec<Vec<Option<Uuid>>> = tds
.simplices()
.map(|(_, simplex)| {
let mut neighbors: Vec<Option<Uuid>> = simplex
.neighbors()
.map(|neighbor_keys| {
neighbor_keys
.map(|neighbor| {
neighbor.and_then(|neighbor_key| {
tds.simplex(neighbor_key).map(Simplex::uuid)
})
})
.collect()
})
.unwrap_or_default();
neighbors.sort();
neighbors
})
.collect();
simplex_neighbors.sort();
simplex_neighbors
}
fn snapshot_incidence<const D: usize>(tds: &Tds<f64, (), (), D>) -> Vec<(Uuid, Option<Uuid>)> {
let mut incident_simplices: Vec<(Uuid, Option<Uuid>)> = tds
.vertices()
.map(|(_, vertex)| {
(
vertex.uuid(),
vertex
.incident_simplex()
.and_then(|simplex_key| tds.simplex(simplex_key).map(Simplex::uuid)),
)
})
.collect();
incident_simplices.sort();
incident_simplices
}
fn assert_same_vertex_simplex_topology(actual: &TopologySnapshot, expected: &TopologySnapshot) {
assert_eq!(actual.vertices, expected.vertices);
assert_eq!(actual.simplex_vertices, expected.simplex_vertices);
}
fn insert_translated_simplex<const D: usize>(
tds: &mut Tds<f64, (), (), D>,
offset: f64,
) -> (Vec<VertexKey>, SimplexKey) {
let mut vertices = Vec::with_capacity(D + 1);
vertices.push(
tds.insert_vertex_with_mapping(vertex!([offset; D]))
.unwrap(),
);
for axis in 0..D {
let mut coords = [offset; D];
coords[axis] += 1.0;
vertices.push(tds.insert_vertex_with_mapping(vertex!(coords)).unwrap());
}
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vertices.clone(), None).unwrap())
.unwrap();
(vertices, simplex_key)
}
fn test_flip_trial_validation_rollback_for_dim<const D: usize>() {
let mut tds: Tds<f64, (), (), D> = Tds::empty();
let (_first_vertices, first_simplex) = insert_translated_simplex(&mut tds, 0.0);
let (_second_vertices, second_simplex) = insert_translated_simplex(&mut tds, 10.0);
repair_neighbor_pointers(&mut tds).unwrap();
tds.assign_incident_simplices().unwrap();
let isolated_vertex = tds.insert_vertex_with_mapping(vertex!([20.0; D])).unwrap();
tds.vertex_mut(isolated_vertex)
.unwrap()
.set_incident_simplex(Some(second_simplex));
let before = snapshot_topology(&tds);
let before_incidence = snapshot_incidence(&tds);
let denominator = f64::from(u32::try_from(D + 2).unwrap());
let new_vertex = vertex!([1.0 / denominator; D]);
let result = apply_bistellar_flip_k1(&mut tds, first_simplex, new_vertex);
match result {
Err(FlipError::TdsMutation {
reason: FlipMutationError::TrialValidation { .. },
}) => {}
other => panic!("expected FlipMutationError::TrialValidation, got {other:?}"),
}
assert_eq!(
snapshot_topology(&tds),
before,
"trial.is_valid() failure must leave the original TDS unchanged"
);
assert_eq!(
snapshot_incidence(&tds),
before_incidence,
"trial.is_valid() failure must leave incident_simplex pointers unchanged"
);
}
macro_rules! gen_trial_validation_rollback_tests {
($($dim:literal),+ $(,)?) => {
pastey::paste! {
$(
#[test]
fn [<test_flip_trial_validation_rollback_ $dim d>]() {
test_flip_trial_validation_rollback_for_dim::<$dim>();
}
)+
}
};
}
gen_trial_validation_rollback_tests!(2, 3, 4, 5);
#[test]
fn test_flip_trial_validation_rejects_unassigned_neighbor_slot() {
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 simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vec![v0, v1, v2], None).unwrap())
.unwrap();
tds.assign_neighbors().unwrap();
{
let simplex = tds.simplex_mut(simplex_key).unwrap();
simplex.ensure_neighbors_buffer_mut()[0] = NeighborSlot::Unassigned;
}
let simplex = tds.simplex(simplex_key).unwrap();
let err =
validate_flip_trial_simplex_neighbors(&tds, simplex_key, simplex, &[]).unwrap_err();
assert!(matches!(
err,
TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::UnassignedNeighborSlot { facet_index: 0, .. },
}
));
}
#[expect(
clippy::too_many_lines,
reason = "The property fixture keeps k=2 setup, forward flip, inverse flip, and invariant checks together so failing cases shrink with full context."
)]
fn prop_bistellar_k2_roundtrip_for_dim<const D: usize>(
offset: f64,
scale: f64,
) -> Result<(), TestCaseError> {
init_tracing();
let mut tds: Tds<f64, (), (), D> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(D);
for i in 0..D {
let vertex = tds
.insert_vertex_with_mapping(vertex!(translated_scaled_unit_vector::<D>(
i, offset, scale
)))
.map_err(|err| {
TestCaseError::fail(format!("shared vertex insertion failed: {err:?}"))
})?;
shared_vertices.push(vertex);
}
let opposite_a = tds
.insert_vertex_with_mapping(vertex!([offset; D]))
.map_err(|err| TestCaseError::fail(format!("opposite A insertion failed: {err:?}")))?;
let opposite_b = tds
.insert_vertex_with_mapping(vertex!([offset + scale; D]))
.map_err(|err| TestCaseError::fail(format!("opposite B insertion failed: {err:?}")))?;
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).map_err(
|err| TestCaseError::fail(format!("simplex A creation failed: {err:?}")),
)?)
.map_err(|err| TestCaseError::fail(format!("simplex A insertion failed: {err:?}")))?;
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
tds.insert_simplex_with_mapping(
Simplex::new(vertices_with_second_opposite, None).map_err(|err| {
TestCaseError::fail(format!("simplex B creation failed: {err:?}"))
})?,
)
.map_err(|err| TestCaseError::fail(format!("simplex B insertion failed: {err:?}")))?;
repair_neighbor_pointers(&mut tds)
.map_err(|err| TestCaseError::fail(format!("neighbor repair failed: {err:?}")))?;
let before = snapshot_topology(&tds);
let facet = FacetHandle::new(
simplex_a,
u8::try_from(D).map_err(|err| {
TestCaseError::fail(format!("facet index conversion failed: {err:?}"))
})?,
);
let context = build_k2_flip_context(&tds, facet)
.map_err(|err| TestCaseError::fail(format!("k=2 context build failed: {err:?}")))?;
let info = apply_bistellar_flip_k2(&mut tds, &context)
.map_err(|err| TestCaseError::fail(format!("k=2 flip failed: {err:?}")))?;
tds.is_valid()
.map_err(|err| TestCaseError::fail(format!("post k=2 TDS invalid: {err:?}")))?;
if D == 2 {
let mut inverse_facet: Option<FacetHandle> = None;
for &simplex_key in &info.new_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or_else(|| TestCaseError::fail("new k=2 simplex missing"))?;
if simplex.contains_vertex(opposite_a) && simplex.contains_vertex(opposite_b) {
let facet_index = simplex
.vertices()
.iter()
.position(|&vertex| vertex != opposite_a && vertex != opposite_b)
.ok_or_else(|| TestCaseError::fail("missing inverse k=2 facet vertex"))?;
inverse_facet = Some(FacetHandle::new(
simplex_key,
u8::try_from(facet_index).map_err(|err| {
TestCaseError::fail(format!(
"inverse facet index conversion failed: {err:?}"
))
})?,
));
break;
}
}
let facet =
inverse_facet.ok_or_else(|| TestCaseError::fail("inverse k=2 facet not found"))?;
let context_back = build_k2_flip_context(&tds, facet).map_err(|err| {
TestCaseError::fail(format!("inverse k=2 context build failed: {err:?}"))
})?;
apply_bistellar_flip_k2(&mut tds, &context_back)
.map_err(|err| TestCaseError::fail(format!("inverse k=2 flip failed: {err:?}")))?;
} else {
let inserted = inserted_face_vertices(&info, 2)?;
let edge = match inserted.as_slice() {
[a, b] => EdgeKey::new(*a, *b),
_ => {
return Err(TestCaseError::fail(
"validated k=2 inserted-face arity changed",
));
}
};
let context_back = build_k2_flip_context_from_edge(&tds, edge).map_err(|err| {
TestCaseError::fail(format!("inverse k=2 context build failed: {err:?}"))
})?;
apply_bistellar_flip_dynamic(&mut tds, D, &context_back)
.map_err(|err| TestCaseError::fail(format!("inverse k=2 flip failed: {err:?}")))?;
}
tds.is_valid()
.map_err(|err| TestCaseError::fail(format!("post inverse k=2 TDS invalid: {err:?}")))?;
let after = snapshot_topology(&tds);
prop_assert_eq!(after.vertices, before.vertices);
prop_assert_eq!(after.simplex_vertices, before.simplex_vertices);
Ok(())
}
#[expect(
clippy::too_many_lines,
reason = "The property fixture keeps k=3 setup, forward flip, inverse flip, and invariant checks together so failing cases shrink with full context."
)]
fn prop_bistellar_k3_roundtrip_for_dim<const D: usize>(
offset: f64,
scale: f64,
) -> Result<(), TestCaseError> {
init_tracing();
let ridge_vertex_count = D
.checked_sub(1)
.ok_or_else(|| TestCaseError::fail("k=3 fixture requires D >= 1"))?;
let mut tds: Tds<f64, (), (), D> = Tds::empty();
let mut ridge_vertices = Vec::with_capacity(ridge_vertex_count);
for i in 0..ridge_vertex_count {
let vertex = tds
.insert_vertex_with_mapping(vertex!(translated_scaled_unit_vector::<D>(
i, offset, scale
)))
.map_err(|err| {
TestCaseError::fail(format!("ridge vertex insertion failed: {err:?}"))
})?;
ridge_vertices.push(vertex);
}
let a = tds
.insert_vertex_with_mapping(vertex!([offset; D]))
.map_err(|err| TestCaseError::fail(format!("opposite A insertion failed: {err:?}")))?;
let b = tds
.insert_vertex_with_mapping(vertex!(translated_scaled_unit_vector::<D>(
ridge_vertex_count,
offset,
scale
)))
.map_err(|err| TestCaseError::fail(format!("opposite B insertion failed: {err:?}")))?;
let c = tds
.insert_vertex_with_mapping(vertex!(translated_scaled_skewed_point::<D>(offset, scale)))
.map_err(|err| TestCaseError::fail(format!("opposite C insertion failed: {err:?}")))?;
let mut first_vertices = ridge_vertices.clone();
first_vertices.push(a);
first_vertices.push(b);
let first_simplex = tds
.insert_simplex_with_mapping(Simplex::new(first_vertices, None).map_err(|err| {
TestCaseError::fail(format!("simplex A creation failed: {err:?}"))
})?)
.map_err(|err| TestCaseError::fail(format!("simplex A insertion failed: {err:?}")))?;
let mut second_vertices = ridge_vertices.clone();
second_vertices.push(b);
second_vertices.push(c);
tds.insert_simplex_with_mapping(
Simplex::new(second_vertices, None).map_err(|err| {
TestCaseError::fail(format!("simplex B creation failed: {err:?}"))
})?,
)
.map_err(|err| TestCaseError::fail(format!("simplex B insertion failed: {err:?}")))?;
let mut third_vertices = ridge_vertices.clone();
third_vertices.push(c);
third_vertices.push(a);
tds.insert_simplex_with_mapping(
Simplex::new(third_vertices, None).map_err(|err| {
TestCaseError::fail(format!("simplex C creation failed: {err:?}"))
})?,
)
.map_err(|err| TestCaseError::fail(format!("simplex C insertion failed: {err:?}")))?;
repair_neighbor_pointers(&mut tds)
.map_err(|err| TestCaseError::fail(format!("neighbor repair failed: {err:?}")))?;
let before = snapshot_topology(&tds);
let ridge = RidgeHandle::new(
first_simplex,
u8::try_from(ridge_vertex_count).map_err(|err| {
TestCaseError::fail(format!("ridge index conversion failed: {err:?}"))
})?,
u8::try_from(D).map_err(|err| {
TestCaseError::fail(format!("ridge index conversion failed: {err:?}"))
})?,
);
let context = build_k3_flip_context(&tds, ridge)
.map_err(|err| TestCaseError::fail(format!("k=3 context build failed: {err:?}")))?;
let info = apply_bistellar_flip_k3(&mut tds, &context)
.map_err(|err| TestCaseError::fail(format!("k=3 flip failed: {err:?}")))?;
tds.is_valid()
.map_err(|err| TestCaseError::fail(format!("post k=3 TDS invalid: {err:?}")))?;
if D == 3 {
let mut inverse_facet: Option<FacetHandle> = None;
for &simplex_key in &info.new_simplices {
let simplex = tds
.simplex(simplex_key)
.ok_or_else(|| TestCaseError::fail("new k=3 simplex missing"))?;
if simplex.contains_vertex(a)
&& simplex.contains_vertex(b)
&& simplex.contains_vertex(c)
{
let facet_index = simplex
.vertices()
.iter()
.position(|&vertex| vertex != a && vertex != b && vertex != c)
.ok_or_else(|| TestCaseError::fail("missing inverse k=3 facet vertex"))?;
inverse_facet = Some(FacetHandle::new(
simplex_key,
u8::try_from(facet_index).map_err(|err| {
TestCaseError::fail(format!(
"inverse facet index conversion failed: {err:?}"
))
})?,
));
break;
}
}
let facet =
inverse_facet.ok_or_else(|| TestCaseError::fail("inverse k=3 facet not found"))?;
let context_back = build_k2_flip_context(&tds, facet).map_err(|err| {
TestCaseError::fail(format!("inverse k=3 context build failed: {err:?}"))
})?;
apply_bistellar_flip_k2(&mut tds, &context_back)
.map_err(|err| TestCaseError::fail(format!("inverse k=3 flip failed: {err:?}")))?;
} else {
let inserted = inserted_face_vertices(&info, 3)?;
let triangle = match inserted.as_slice() {
[a, b, c] => TriangleHandle::new(*a, *b, *c),
_ => {
return Err(TestCaseError::fail(
"validated k=3 inserted-face arity changed",
));
}
};
let context_back =
build_k3_flip_context_from_triangle(&tds, triangle).map_err(|err| {
TestCaseError::fail(format!("inverse k=3 context build failed: {err:?}"))
})?;
apply_bistellar_flip_dynamic(&mut tds, ridge_vertex_count, &context_back)
.map_err(|err| TestCaseError::fail(format!("inverse k=3 flip failed: {err:?}")))?;
}
tds.is_valid()
.map_err(|err| TestCaseError::fail(format!("post inverse k=3 TDS invalid: {err:?}")))?;
let after = snapshot_topology(&tds);
prop_assert_eq!(after.vertices, before.vertices);
prop_assert_eq!(after.simplex_vertices, before.simplex_vertices);
Ok(())
}
macro_rules! gen_bistellar_k2_roundtrip_properties {
($($dim:literal),+ $(,)?) => {
pastey::paste! {
$(
proptest! {
#![proptest_config(ProptestConfig::with_cases(16))]
#[test]
fn [<prop_bistellar_k2_roundtrip_ $dim d>](
offset in -2.0_f64..2.0,
scale in 0.5_f64..2.0,
) {
prop_bistellar_k2_roundtrip_for_dim::<$dim>(offset, scale)?;
}
}
)+
}
};
}
#[test]
fn test_bistellar_k2_roundtrip_smoke_2d() {
prop_bistellar_k2_roundtrip_for_dim::<2>(0.25, 1.0).unwrap();
}
#[test]
fn test_bistellar_k2_roundtrip_smoke_4d() {
prop_bistellar_k2_roundtrip_for_dim::<4>(-0.25, 1.25).unwrap();
}
macro_rules! gen_bistellar_k3_roundtrip_properties {
($($dim:literal),+ $(,)?) => {
pastey::paste! {
$(
proptest! {
#![proptest_config(ProptestConfig::with_cases(16))]
#[test]
fn [<prop_bistellar_k3_roundtrip_ $dim d>](
offset in -2.0_f64..2.0,
scale in 0.5_f64..2.0,
) {
prop_bistellar_k3_roundtrip_for_dim::<$dim>(offset, scale)?;
}
}
)+
}
};
}
#[test]
fn test_bistellar_k3_roundtrip_smoke_3d() {
prop_bistellar_k3_roundtrip_for_dim::<3>(0.25, 1.0).unwrap();
}
#[test]
fn test_bistellar_k3_roundtrip_smoke_4d() {
prop_bistellar_k3_roundtrip_for_dim::<4>(-0.25, 1.25).unwrap();
}
gen_bistellar_k2_roundtrip_properties!(2, 3, 4, 5);
gen_bistellar_k3_roundtrip_properties!(3, 4, 5);
macro_rules! test_bistellar_roundtrip_dimension {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_bistellar_k1_roundtrip_ $dim d>]() {
init_tracing();
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let origin = tds.insert_vertex_with_mapping(vertex!([0.0; $dim])).unwrap();
let mut vertices = Vec::with_capacity($dim + 1);
vertices.push(origin);
for i in 0..$dim {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(i)))
.unwrap();
vertices.push(v);
}
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap())
.unwrap();
let before = snapshot_topology(&tds);
let new_vertex = vertex!([0.1; $dim]);
let new_uuid = new_vertex.uuid();
let _info = apply_bistellar_flip_k1(&mut tds, simplex_key, new_vertex)
.unwrap();
assert!(tds.is_valid().is_ok());
let new_key = tds.vertex_key_from_uuid(&new_uuid).unwrap();
let _info_back =
apply_bistellar_flip_k1_inverse(&mut tds, new_key).unwrap();
assert!(tds.is_valid().is_ok());
assert_eq!(snapshot_topology(&tds), before);
}
#[test]
fn [<test_bistellar_k2_roundtrip_ $dim d>]() {
init_tracing();
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let mut shared_vertices = Vec::with_capacity($dim);
for i in 0..$dim {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(i)))
.unwrap();
shared_vertices.push(v);
}
let opposite_a = tds
.insert_vertex_with_mapping(vertex!([0.0; $dim]))
.unwrap();
let opposite_b = tds
.insert_vertex_with_mapping(vertex!([1.0; $dim]))
.unwrap();
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let simplex_a = tds
.insert_simplex_with_mapping(
Simplex::new(vertices_with_first_opposite, None).unwrap(),
)
.unwrap();
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
let _simplex_b = tds
.insert_simplex_with_mapping(
Simplex::new(vertices_with_second_opposite, None).unwrap(),
)
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let before = snapshot_topology(&tds);
let facet = FacetHandle::new(simplex_a, u8::try_from($dim).unwrap());
let context = build_k2_flip_context(&tds, facet).unwrap();
let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
assert!(tds.is_valid().is_ok());
if $dim == 2 {
let mut inverse_facet: Option<FacetHandle> = None;
for &simplex_key in &info.new_simplices {
let simplex = tds.simplex(simplex_key).unwrap();
if simplex.contains_vertex(opposite_a) && simplex.contains_vertex(opposite_b) {
let facet_index = simplex
.vertices()
.iter()
.position(|&v| v != opposite_a && v != opposite_b)
.expect("missing shared vertex for inverse k=2");
inverse_facet = Some(FacetHandle::new(
simplex_key,
u8::try_from(facet_index).unwrap(),
));
break;
}
}
let facet = inverse_facet.expect("inverse k=2 facet not found");
let context_back = build_k2_flip_context(&tds, facet).unwrap();
let _info_back =
apply_bistellar_flip_k2(&mut tds, &context_back).unwrap();
} else {
let edge = EdgeKey::new(opposite_a, opposite_b);
let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap();
let _info_back =
apply_bistellar_flip_dynamic(&mut tds, $dim, &context_back)
.unwrap();
}
assert!(tds.is_valid().is_ok());
let after = snapshot_topology(&tds);
assert_same_vertex_simplex_topology(&after, &before);
}
}
};
($dim:literal, k3) => {
test_bistellar_roundtrip_dimension!($dim);
pastey::paste! {
#[test]
fn [<test_bistellar_k3_roundtrip_ $dim d>]() {
init_tracing();
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let mut ridge_vertices = Vec::with_capacity($dim - 1);
for i in 0..($dim - 1) {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(i)))
.unwrap();
ridge_vertices.push(v);
}
let a = tds
.insert_vertex_with_mapping(vertex!([0.0; $dim]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>($dim - 1)))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!(skewed_point::<$dim>()))
.unwrap();
let mut c1_vertices = ridge_vertices.clone();
c1_vertices.push(a);
c1_vertices.push(b);
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(c1_vertices, None).unwrap())
.unwrap();
let mut c2_vertices = ridge_vertices.clone();
c2_vertices.push(b);
c2_vertices.push(c);
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(c2_vertices, None).unwrap())
.unwrap();
let mut c3_vertices = ridge_vertices.clone();
c3_vertices.push(c);
c3_vertices.push(a);
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(c3_vertices, None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let before = snapshot_topology(&tds);
let ridge = RidgeHandle::new(
c1,
u8::try_from($dim - 1).unwrap(),
u8::try_from($dim).unwrap(),
);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap();
assert!(tds.is_valid().is_ok());
if $dim == 3 {
let mut inverse_facet: Option<FacetHandle> = None;
for &simplex_key in &info.new_simplices {
let simplex = tds.simplex(simplex_key).unwrap();
if simplex.contains_vertex(a)
&& simplex.contains_vertex(b)
&& simplex.contains_vertex(c)
{
let facet_index = simplex
.vertices()
.iter()
.position(|&v| v != a && v != b && v != c)
.expect("missing ridge vertex for inverse k=3");
inverse_facet = Some(FacetHandle::new(
simplex_key,
u8::try_from(facet_index).unwrap(),
));
break;
}
}
let facet = inverse_facet.expect("inverse k=3 facet not found");
let context_back = build_k2_flip_context(&tds, facet).unwrap();
let _info_back =
apply_bistellar_flip_k2(&mut tds, &context_back).unwrap();
} else {
let triangle = TriangleHandle::new(a, b, c);
let context_back =
build_k3_flip_context_from_triangle(&tds, triangle).unwrap();
let _info_back = apply_bistellar_flip_dynamic(
&mut tds,
$dim - 1,
&context_back,
)
.unwrap();
}
assert!(tds.is_valid().is_ok());
let after = snapshot_topology(&tds);
assert_same_vertex_simplex_topology(&after, &before);
}
}
};
}
test_bistellar_roundtrip_dimension!(2);
test_bistellar_roundtrip_dimension!(3, k3);
test_bistellar_roundtrip_dimension!(4, k3);
test_bistellar_roundtrip_dimension!(5, k3);
fn synthetic_vertex_key(index: u64) -> VertexKey {
VertexKey::from(KeyData::from_ffi(index))
}
fn synthetic_simplex_key(index: u64) -> SimplexKey {
SimplexKey::from(KeyData::from_ffi(index))
}
#[test]
fn test_local_postcondition_frontier_deduplicates_seed_and_touched_simplices() {
let seed_a = synthetic_simplex_key(1);
let seed_b = synthetic_simplex_key(2);
let touched_a = synthetic_simplex_key(3);
let frontier = local_postcondition_frontier(
&[seed_a, seed_b, seed_a],
&[seed_b, touched_a, touched_a],
);
assert_eq!(frontier.len(), 3);
assert_eq!(frontier[0], seed_a);
assert_eq!(frontier[1], seed_b);
assert_eq!(frontier[2], touched_a);
}
#[test]
fn test_repair_postcondition_required_tracks_mutation_or_applicable_site() {
let mut stats = DelaunayRepairStats::default();
let mut diagnostics = RepairDiagnostics::default();
assert!(!repair_postcondition_required(&stats, &diagnostics));
diagnostics.record_applicable_repair_site();
assert!(repair_postcondition_required(&stats, &diagnostics));
diagnostics = RepairDiagnostics::default();
stats.flips_performed = 1;
assert!(repair_postcondition_required(&stats, &diagnostics));
}
fn dynamic_flip_rejects_bad_context_for_dimension<const D: usize>() {
init_tracing();
let mut tds: Tds<f64, (), (), D> = Tds::empty();
let vertices = (1..=D + 2)
.map(|index| {
synthetic_vertex_key(
u64::try_from(index).expect("test vertex key index should fit in u64"),
)
})
.collect::<Vec<_>>();
let c0 = synthetic_simplex_key(11);
let c1 = synthetic_simplex_key(12);
let valid_shape = FlipContextDyn {
removed_face_vertices: vertices[..D].iter().copied().collect(),
inserted_face_vertices: vertices[D..D + 2].iter().copied().collect(),
removed_simplices: [c0, c1].into_iter().collect(),
direction: FlipDirection::Forward,
};
assert!(matches!(
apply_bistellar_flip_dynamic(&mut tds, 0, &valid_shape),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::InvalidMoveSize {
k_move: 0,
dimension,
}
}) if dimension == D
));
assert!(matches!(
apply_bistellar_flip_dynamic(&mut tds, D + 2, &valid_shape),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::InvalidMoveSize {
k_move,
dimension,
}
}) if k_move == D + 2 && dimension == D
));
let wrong_removed_face = FlipContextDyn {
removed_face_vertices: vertices[..D - 1].iter().copied().collect(),
..valid_shape.clone()
};
assert!(matches!(
apply_bistellar_flip_dynamic(&mut tds, 2, &wrong_removed_face),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::WrongRemovedFaceArity {
expected,
found,
}
}) if expected == D && found == D - 1
));
let wrong_inserted_face = FlipContextDyn {
inserted_face_vertices: once(vertices[D]).collect(),
..valid_shape.clone()
};
assert!(matches!(
apply_bistellar_flip_dynamic(&mut tds, 2, &wrong_inserted_face),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::WrongInsertedFaceArity {
k_move: 2,
expected: 2,
found: 1,
}
})
));
let wrong_removed_simplices = FlipContextDyn {
removed_simplices: once(c0).collect(),
..valid_shape.clone()
};
assert!(matches!(
apply_bistellar_flip_dynamic(&mut tds, 2, &wrong_removed_simplices),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::WrongRemovedSimplexCount {
expected: 2,
found: 1,
}
})
));
let overlapping_faces = FlipContextDyn {
inserted_face_vertices: [vertices[D - 1], vertices[D]].into_iter().collect(),
..valid_shape
};
assert!(matches!(
apply_bistellar_flip_dynamic(&mut tds, 2, &overlapping_faces),
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::OverlappingFaces,
})
));
assert_eq!(tds.number_of_vertices(), 0);
assert_eq!(tds.number_of_simplices(), 0);
}
macro_rules! gen_dynamic_flip_bad_context_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<dynamic_flip_rejects_bad_context_ $dim d>]() {
dynamic_flip_rejects_bad_context_for_dimension::<$dim>();
}
}
};
}
gen_dynamic_flip_bad_context_tests!(2);
gen_dynamic_flip_bad_context_tests!(3);
gen_dynamic_flip_bad_context_tests!(4);
gen_dynamic_flip_bad_context_tests!(5);
#[test]
fn test_flip_k2_2d_edge_flip() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let d = tds.insert_vertex_with_mapping(vertex!([1.0, 0.2])).unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 2); let context = build_k2_flip_context(&tds, facet).unwrap();
let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
assert_eq!(info.removed_simplices.len(), 2);
assert_eq!(info.new_simplices.len(), 2);
let mut has_cd = false;
for (_, simplex) in tds.simplices() {
let verts = simplex.vertices();
if verts.contains(&c) && verts.contains(&d) {
has_cd = true;
}
}
assert!(has_cd, "Expected flipped diagonal between c and d");
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k2_rejects_duplicate_simplex() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let d = tds.insert_vertex_with_mapping(vertex!([1.0, 0.2])).unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
let _existing = tds
.insert_simplex_with_mapping(Simplex::new(vec![b, c, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 2); let context = build_k2_flip_context(&tds, facet).unwrap();
let result = apply_bistellar_flip_k2(&mut tds, &context);
assert!(matches!(result, Err(FlipError::DuplicateSimplex)));
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k2_rejects_inserting_existing_edge_in_3d() {
init_tracing();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let v_a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let v_b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let v_x = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let v_y = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let v_z = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 1.0]))
.unwrap();
let v_p = tds
.insert_vertex_with_mapping(vertex!([2.0, 0.0, 0.0]))
.unwrap();
let v_q = tds
.insert_vertex_with_mapping(vertex!([2.0, 1.0, 0.0]))
.unwrap();
let simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_x, v_y, v_z], None).unwrap())
.unwrap();
let _simplex_b = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_b, v_x, v_y, v_z], None).unwrap())
.unwrap();
let _edge_witness = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_p, v_q], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
assert!(tds.is_valid().is_ok());
let facet = FacetHandle::new(simplex_a, 0);
let ctx = build_k2_flip_context(&tds, facet).unwrap();
let result = apply_bistellar_flip_k2(&mut tds, &ctx);
assert!(matches!(
result,
Err(FlipError::InsertedSimplexAlreadyExists { .. })
));
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k2_rejects_nonmanifold_internal_facet() {
init_tracing();
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!([1.0, 0.2])).unwrap();
let v_e = tds.insert_vertex_with_mapping(vertex!([2.0, 2.0])).unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_d], None).unwrap())
.unwrap();
let _cd_external = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_c, v_d, v_e], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 2); let context = build_k2_flip_context(&tds, facet).unwrap();
let result = apply_bistellar_flip_k2(&mut tds, &context);
assert!(matches!(result, Err(FlipError::NonManifoldFacet)));
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k2_3d_two_to_three() {
init_tracing();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let v_a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let v_b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let v_c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let v_d = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, 1.0]))
.unwrap();
let v_e = tds
.insert_vertex_with_mapping(vertex!([0.3, -0.1, -0.8]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_d], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_e], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 3); let context = build_k2_flip_context(&tds, facet).unwrap();
let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
assert_eq!(info.new_simplices.len(), 3);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k3_3d_three_to_two() {
init_tracing();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let r0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let r1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, -1.0]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, a, b], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, b, c], None).unwrap())
.unwrap();
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, c, a], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let ridge = RidgeHandle::new(c1, 2, 3);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap();
assert_eq!(info.kind, BistellarFlipKind::k3(3));
assert_eq!(info.removed_simplices.len(), 3);
assert_eq!(info.new_simplices.len(), 2);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k3_4d_three_to_three() {
init_tracing();
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let r0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0]))
.unwrap();
let r2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0]))
.unwrap();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, a, b], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, b, c], None).unwrap())
.unwrap();
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, c, a], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let ridge = RidgeHandle::new(c1, 3, 4);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap();
assert_eq!(info.kind, BistellarFlipKind::k3(4));
assert_eq!(info.removed_simplices.len(), 3);
assert_eq!(info.new_simplices.len(), 3);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k3_5d_three_to_four() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let r0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0]))
.unwrap();
let r3 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0]))
.unwrap();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap())
.unwrap();
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let ridge = RidgeHandle::new(c1, 4, 5);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap();
assert_eq!(info.kind, BistellarFlipKind::k3(5));
assert_eq!(info.removed_simplices.len(), 3);
assert_eq!(info.new_simplices.len(), 4);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k2_boundary_facet_error_2d() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let simplex = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let before = snapshot_topology(&tds);
let facet = FacetHandle::new(simplex, 0);
let err = build_k2_flip_context(&tds, facet).unwrap_err();
assert!(matches!(err, FlipError::BoundaryFacet { .. }));
assert_eq!(snapshot_topology(&tds), before);
}
#[test]
fn test_flip_k3_invalid_ridge_multiplicity_3d() {
init_tracing();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let d = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let simplex = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c, d], None).unwrap())
.unwrap();
let ridge = RidgeHandle::new(simplex, 0, 1);
let err = build_k3_flip_context(&tds, ridge).unwrap_err();
assert!(matches!(
err,
FlipError::InvalidRidgeMultiplicity { found: 1 }
));
}
#[test]
fn test_flip_k3_reports_dangling_ridge_neighbor_3d() {
init_tracing();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let ridge_start = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let ridge_end = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let first_opposite = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let second_opposite = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let dangling_opposite = tds
.insert_vertex_with_mapping(vertex!([1.0, 1.0, 1.0]))
.unwrap();
let simplex = tds
.insert_simplex_with_mapping(
Simplex::new(
vec![ridge_start, ridge_end, first_opposite, second_opposite],
None,
)
.unwrap(),
)
.unwrap();
let dangling_neighbor = tds
.insert_simplex_with_mapping(
Simplex::new(
vec![ridge_start, ridge_end, first_opposite, dangling_opposite],
None,
)
.unwrap(),
)
.unwrap();
assert_eq!(tds.remove_simplices_by_keys(&[dangling_neighbor]), 1);
tds.simplex_mut(simplex)
.expect("test simplex should exist")
.set_neighbors_from_keys([Some(dangling_neighbor), None, None, None])
.unwrap();
let ridge = RidgeHandle::new(simplex, 0, 1);
let err = build_k3_flip_context(&tds, ridge).unwrap_err();
assert_eq!(
err,
FlipError::DanglingRidgeNeighbor {
simplex_key: simplex,
neighbor_key: dangling_neighbor,
}
);
}
#[test]
fn test_flip_k2_inverse_invalid_edge_multiplicity_4d() {
init_tracing();
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(4);
for i in 0..4 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<4>(i)))
.unwrap();
shared_vertices.push(v);
}
let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap();
let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap();
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let _simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap())
.unwrap();
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
let _simplex_b = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap())
.unwrap();
let edge = EdgeKey::new(opposite_a, opposite_b);
let err = build_k2_flip_context_from_edge(&tds, edge).unwrap_err();
assert!(matches!(err, FlipError::InvalidEdgeMultiplicity { .. }));
}
#[test]
fn test_flip_k3_inverse_invalid_triangle_multiplicity_5d() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let origin = tds.insert_vertex_with_mapping(vertex!([0.0; 5])).unwrap();
let mut vertices = Vec::with_capacity(6);
vertices.push(origin);
for i in 0..5 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<5>(i)))
.unwrap();
vertices.push(v);
}
let _simplex = tds
.insert_simplex_with_mapping(Simplex::new(vertices.clone(), None).unwrap())
.unwrap();
let triangle = TriangleHandle::new(vertices[0], vertices[1], vertices[2]);
let err = build_k3_flip_context_from_triangle(&tds, triangle).unwrap_err();
assert!(matches!(
err,
FlipError::InvalidTriangleMultiplicity {
found: 1,
expected: 4,
}
));
}
#[test]
fn test_flip_k1_degenerate_insert_rejected() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let before = snapshot_topology(&tds);
let err = apply_bistellar_flip_k1(&mut tds, simplex_key, vertex!([0.5, 0.0])).unwrap_err();
assert!(matches!(err, FlipError::DegenerateSimplex));
assert_eq!(snapshot_topology(&tds), before);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_dynamic_k2_forward_4d() {
init_tracing();
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(4);
for i in 0..4 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<4>(i)))
.unwrap();
shared_vertices.push(v);
}
let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap();
let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap();
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap())
.unwrap();
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
let _simplex_b = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(simplex_a, 4);
let context = build_k2_flip_context(&tds, facet).unwrap();
let context_dyn = to_dynamic(context);
let info = apply_bistellar_flip_dynamic(&mut tds, 2, &context_dyn).unwrap();
assert_eq!(info.kind, BistellarFlipKind::k2(4));
assert_eq!(info.removed_simplices.len(), 2);
assert_eq!(info.new_simplices.len(), 4);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_dynamic_k3_forward_5d() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let r0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0]))
.unwrap();
let r3 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0]))
.unwrap();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap())
.unwrap();
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let ridge = RidgeHandle::new(c1, 4, 5);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let context_dyn = to_dynamic(context);
let info = apply_bistellar_flip_dynamic(&mut tds, 3, &context_dyn).unwrap();
assert_eq!(info.kind, BistellarFlipKind::k3(5));
assert_eq!(info.removed_simplices.len(), 3);
assert_eq!(info.new_simplices.len(), 4);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_k2_roundtrip_randomized_3d() {
init_tracing();
let mut rng = StdRng::seed_from_u64(0x51f1_7a2b);
for _ in 0..10 {
let mut jitter = |v: [f64; 3]| {
let mut out = v;
for coord in &mut out {
*coord += rng.random_range(-0.03..0.03);
}
out
};
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let v_a = tds
.insert_vertex_with_mapping(vertex!(jitter([0.0, 0.0, 0.0])))
.unwrap();
let v_b = tds
.insert_vertex_with_mapping(vertex!(jitter([1.0, 0.0, 0.0])))
.unwrap();
let v_c = tds
.insert_vertex_with_mapping(vertex!(jitter([0.0, 1.0, 0.0])))
.unwrap();
let v_d = tds
.insert_vertex_with_mapping(vertex!(jitter([0.2, 0.2, 1.0])))
.unwrap();
let v_e = tds
.insert_vertex_with_mapping(vertex!(jitter([0.3, -0.1, -0.8])))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_d], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![v_a, v_b, v_c, v_e], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let before = snapshot_topology(&tds);
let facet = FacetHandle::new(c1, 3);
let context = build_k2_flip_context(&tds, facet).unwrap();
let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
assert!(tds.is_valid().is_ok());
let edge = EdgeKey::new(
info.inserted_face_vertices[0],
info.inserted_face_vertices[1],
);
let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap();
let _info_back = apply_bistellar_flip_dynamic(&mut tds, 3, &context_back).unwrap();
assert!(tds.is_valid().is_ok());
let after = snapshot_topology(&tds);
assert_same_vertex_simplex_topology(&after, &before);
}
}
#[test]
fn test_repair_delaunay_flips_non_delaunay_edge_2d() {
init_tracing();
let kernel = AdaptiveKernel::<f64>::new();
let a_coords = [0.0, 0.0];
let b_coords = [1.0, 1.0];
let c_coords = [1.0, 0.0];
let d_candidates = [[0.0, 1.2], [0.1, 1.1], [0.2, 0.9], [-0.1, 1.3]];
let mut tds = None;
for d_coords in d_candidates {
let mut candidate: Tds<f64, (), (), 2> = Tds::empty();
let a = candidate
.insert_vertex_with_mapping(vertex!(a_coords))
.unwrap();
let b = candidate
.insert_vertex_with_mapping(vertex!(b_coords))
.unwrap();
let c = candidate
.insert_vertex_with_mapping(vertex!(c_coords))
.unwrap();
let d = candidate
.insert_vertex_with_mapping(vertex!(d_coords))
.unwrap();
let _c1 = candidate
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = candidate
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut candidate).unwrap();
if verify_delaunay_via_flip_predicates(&candidate, &kernel).is_err() {
tds = Some(candidate);
break;
}
}
let mut tds = tds.expect("expected a non-Delaunay configuration from candidates");
let stats = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
None,
TopologyGuarantee::PLManifold,
None,
)
.unwrap();
assert!(stats.flips_performed > 0);
assert!(verify_delaunay_via_flip_predicates(&tds, &kernel).is_ok());
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_repair_max_flips_override_caps_repair() {
init_tracing();
let kernel = AdaptiveKernel::<f64>::new();
let d_candidates = [[0.0, 1.2], [0.1, 1.1], [0.2, 0.9], [-0.1, 1.3]];
let mut tds = None;
for d_coords in d_candidates {
let mut candidate: Tds<f64, (), (), 2> = Tds::empty();
let a = candidate
.insert_vertex_with_mapping(vertex!([0.0, 0.0]))
.unwrap();
let b = candidate
.insert_vertex_with_mapping(vertex!([1.0, 1.0]))
.unwrap();
let c = candidate
.insert_vertex_with_mapping(vertex!([1.0, 0.0]))
.unwrap();
let d = candidate
.insert_vertex_with_mapping(vertex!(d_coords))
.unwrap();
let _c1 = candidate
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = candidate
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut candidate).unwrap();
if verify_delaunay_via_flip_predicates(&candidate, &kernel).is_err() {
tds = Some(candidate);
break;
}
}
let mut tds = tds.expect("expected a non-Delaunay configuration from candidates");
let before = snapshot_topology(&tds);
let result = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
None,
TopologyGuarantee::PLManifold,
Some(0),
);
match result {
Err(DelaunayRepairError::NonConvergent { diagnostics, .. }) => {
assert_eq!(
diagnostics.flips_performed, 0,
"max_flips_override=Some(0) should prevent any flips, got: {}",
diagnostics.flips_performed
);
}
other => panic!("expected NonConvergent, got: {other:?}"),
}
assert_eq!(
snapshot_topology(&tds),
before,
"TDS must remain unchanged when max_flips=0 prevents all flips"
);
}
#[test]
#[expect(
clippy::many_single_char_names,
reason = "vertex names a-e mirror standard simplex labelling in geometry tests"
)]
fn test_repair_max_flips_override_caps_repair_3d() {
init_tracing();
let kernel = AdaptiveKernel::<f64>::new();
let mut tds: Tds<f64, (), (), 3> = Tds::empty();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0]))
.unwrap();
let d = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0]))
.unwrap();
let e = tds
.insert_vertex_with_mapping(vertex!([0.3, 0.3, 0.3]))
.unwrap();
let _c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c, d], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c, e], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
assert!(
verify_delaunay_via_flip_predicates(&tds, &kernel).is_err(),
"3D fixture must be non-Delaunay (e inside circumsphere of {{a,b,c,d}})"
);
let before = snapshot_topology(&tds);
let result = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
None,
TopologyGuarantee::PLManifold,
Some(0),
);
match result {
Err(DelaunayRepairError::NonConvergent { diagnostics, .. }) => {
assert_eq!(diagnostics.flips_performed, 0);
}
other => panic!("expected NonConvergent for 3D, got: {other:?}"),
}
assert_eq!(
snapshot_topology(&tds),
before,
"3D TDS must remain unchanged when max_flips=0 prevents all flips"
);
}
#[test]
fn test_verify_delaunay_via_flip_predicates_reports_non_delaunay_2d() {
init_tracing();
let kernel = FastKernel::<f64>::new();
let a_coords = [0.0, 0.0];
let b_coords = [1.0, 1.0];
let c_coords = [1.0, 0.0];
let d_candidates = [[0.0, 1.2], [0.1, 1.1], [0.2, 0.9], [-0.1, 1.3]];
let mut tds = None;
for d_coords in d_candidates {
let mut candidate: Tds<f64, (), (), 2> = Tds::empty();
let a = candidate
.insert_vertex_with_mapping(vertex!(a_coords))
.unwrap();
let b = candidate
.insert_vertex_with_mapping(vertex!(b_coords))
.unwrap();
let c = candidate
.insert_vertex_with_mapping(vertex!(c_coords))
.unwrap();
let d = candidate
.insert_vertex_with_mapping(vertex!(d_coords))
.unwrap();
let _c1 = candidate
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = candidate
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut candidate).unwrap();
if verify_delaunay_via_flip_predicates(&candidate, &kernel).is_err() {
tds = Some(candidate);
break;
}
}
let tds = tds.expect("expected a non-Delaunay configuration from candidates");
let result = verify_delaunay_via_flip_predicates(&tds, &kernel);
assert!(matches!(
result,
Err(DelaunayRepairError::PostconditionFailed { .. })
));
}
#[test]
fn test_repair_delaunay_with_flips_rejects_unsupported_dimension_1d() {
init_tracing();
let mut tds: Tds<f64, (), (), 1> = Tds::empty();
let kernel = AdaptiveKernel::<f64>::new();
let result = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
None,
TopologyGuarantee::PLManifold,
None,
);
assert!(matches!(
result,
Err(DelaunayRepairError::Flip { source })
if matches!(
source.as_ref(),
FlipError::UnsupportedDimension { dimension: 1 }
)
));
}
#[test]
fn test_flip_k2_robust_kernel_near_degenerate_2d() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let d = tds
.insert_vertex_with_mapping(vertex!([1.0, 1e-9]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 2);
let context = build_k2_flip_context(&tds, facet).unwrap();
let _info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_k2_flip_would_create_degenerate_simplex_degenerate() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let d = tds.insert_vertex_with_mapping(vertex!([0.5, 0.0])).unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 2);
let context = build_k2_flip_context(&tds, facet).unwrap();
let degenerate = k2_flip_would_create_degenerate_simplex(&tds, &context).unwrap();
assert!(
degenerate,
"replacement simplices with collinear vertices should be degenerate"
);
}
#[test]
fn test_k2_flip_would_create_degenerate_simplex_nondegenerate() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let d = tds.insert_vertex_with_mapping(vertex!([1.0, 1.0])).unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, d], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(c1, 2);
let context = build_k2_flip_context(&tds, facet).unwrap();
assert_context_has_nonzero_robust_orientation(&tds, &context);
let degenerate = k2_flip_would_create_degenerate_simplex(&tds, &context).unwrap();
assert!(!degenerate);
}
#[test]
fn test_flip_k4_4d_four_to_two() {
init_tracing();
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(4);
for i in 0..4 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<4>(i)))
.unwrap();
shared_vertices.push(v);
}
let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap();
let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap();
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap())
.unwrap();
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
let _simplex_b = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(simplex_a, 4);
let context = build_k2_flip_context(&tds, facet).unwrap();
let _info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
let edge = EdgeKey::new(opposite_a, opposite_b);
let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap();
let info_back = apply_bistellar_flip_dynamic(&mut tds, 4, &context_back).unwrap();
assert_eq!(info_back.kind.k, 4);
assert_eq!(info_back.kind.d, 4);
assert_eq!(info_back.removed_simplices.len(), 4);
assert_eq!(info_back.new_simplices.len(), 2);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k5_4d_five_to_one() {
init_tracing();
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let origin = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap();
let mut vertices = Vec::with_capacity(5);
vertices.push(origin);
for i in 0..4 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<4>(i)))
.unwrap();
vertices.push(v);
}
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap())
.unwrap();
let new_vertex = vertex!([0.1; 4]);
let new_uuid = new_vertex.uuid();
let info = apply_bistellar_flip_k1(&mut tds, simplex_key, new_vertex).unwrap();
assert_eq!(info.kind.k, 1);
assert_eq!(info.new_simplices.len(), 5);
let new_key = tds.vertex_key_from_uuid(&new_uuid).unwrap();
let info_back = apply_bistellar_flip_k1_inverse(&mut tds, new_key).unwrap();
assert_eq!(info_back.kind.k, 5);
assert_eq!(info_back.kind.d, 4);
assert_eq!(info_back.removed_simplices.len(), 5);
assert_eq!(info_back.new_simplices.len(), 1);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k4_5d_four_to_three() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let r0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0]))
.unwrap();
let r3 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0]))
.unwrap();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap())
.unwrap();
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let ridge = RidgeHandle::new(c1, 4, 5);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap();
assert_eq!(info.kind.k, 3);
assert_eq!(info.inserted_face_vertices.len(), 3);
let triangle = TriangleHandle::new(
info.inserted_face_vertices[0],
info.inserted_face_vertices[1],
info.inserted_face_vertices[2],
);
let context_back = build_k3_flip_context_from_triangle(&tds, triangle).unwrap();
let info_back = apply_bistellar_flip_dynamic(&mut tds, 4, &context_back).unwrap();
assert_eq!(info_back.kind.k, 4);
assert_eq!(info_back.kind.d, 5);
assert_eq!(info_back.removed_simplices.len(), 4);
assert_eq!(info_back.new_simplices.len(), 3);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k5_5d_five_to_two() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(5);
for i in 0..5 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<5>(i)))
.unwrap();
shared_vertices.push(v);
}
let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 5])).unwrap();
let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 5])).unwrap();
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap())
.unwrap();
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
let _simplex_b = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(simplex_a, 5);
let context = build_k2_flip_context(&tds, facet).unwrap();
let _info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
let edge = EdgeKey::new(opposite_a, opposite_b);
let context_back = build_k2_flip_context_from_edge(&tds, edge).unwrap();
let info_back = apply_bistellar_flip_dynamic(&mut tds, 5, &context_back).unwrap();
assert_eq!(info_back.kind.k, 5);
assert_eq!(info_back.kind.d, 5);
assert_eq!(info_back.removed_simplices.len(), 5);
assert_eq!(info_back.new_simplices.len(), 2);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k6_5d_six_to_one() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let origin = tds.insert_vertex_with_mapping(vertex!([0.0; 5])).unwrap();
let mut vertices = Vec::with_capacity(6);
vertices.push(origin);
for i in 0..5 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<5>(i)))
.unwrap();
vertices.push(v);
}
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap())
.unwrap();
let new_vertex = vertex!([0.1; 5]);
let new_uuid = new_vertex.uuid();
let info = apply_bistellar_flip_k1(&mut tds, simplex_key, new_vertex).unwrap();
assert_eq!(info.kind.k, 1);
assert_eq!(info.new_simplices.len(), 6);
let new_key = tds.vertex_key_from_uuid(&new_uuid).unwrap();
let info_back = apply_bistellar_flip_k1_inverse(&mut tds, new_key).unwrap();
assert_eq!(info_back.kind.k, 6);
assert_eq!(info_back.kind.d, 5);
assert_eq!(info_back.removed_simplices.len(), 6);
assert_eq!(info_back.new_simplices.len(), 1);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_k1_2d_roundtrip() {
init_tracing();
let mut tds: Tds<f64, (), (), 2> = Tds::empty();
let a = tds.insert_vertex_with_mapping(vertex!([0.0, 0.0])).unwrap();
let b = tds.insert_vertex_with_mapping(vertex!([1.0, 0.0])).unwrap();
let c = tds.insert_vertex_with_mapping(vertex!([0.0, 1.0])).unwrap();
let simplex = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let new_vertex = vertex!([0.2, 0.2]);
let new_uuid = new_vertex.uuid();
let info = apply_bistellar_flip_k1(&mut tds, simplex, new_vertex).unwrap();
assert_eq!(info.kind.k, 1);
assert_eq!(info.kind.d, 2);
assert_eq!(tds.number_of_simplices(), 3);
let new_key = tds.vertex_key_from_uuid(&new_uuid).unwrap();
let info_back = apply_bistellar_flip_k1_inverse(&mut tds, new_key).unwrap();
assert_eq!(info_back.kind.k, 3);
assert_eq!(info_back.kind.d, 2);
assert_eq!(tds.number_of_simplices(), 1);
assert_eq!(tds.number_of_vertices(), 3);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_repair_queue_inverse_k2_smoke_4d() {
init_tracing();
let mut tds: Tds<f64, (), (), 4> = Tds::empty();
let mut shared_vertices = Vec::with_capacity(4);
for i in 0..4 {
let v = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<4>(i)))
.unwrap();
shared_vertices.push(v);
}
let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; 4])).unwrap();
let opposite_b = tds.insert_vertex_with_mapping(vertex!([1.0; 4])).unwrap();
let mut vertices_with_first_opposite = shared_vertices.clone();
vertices_with_first_opposite.push(opposite_a);
let simplex_a = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_first_opposite, None).unwrap())
.unwrap();
let mut vertices_with_second_opposite = shared_vertices.clone();
vertices_with_second_opposite.push(opposite_b);
let _simplex_b = tds
.insert_simplex_with_mapping(Simplex::new(vertices_with_second_opposite, None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let facet = FacetHandle::new(simplex_a, 4);
let context = build_k2_flip_context(&tds, facet).unwrap();
let info = apply_bistellar_flip_k2(&mut tds, &context).unwrap();
let kernel = AdaptiveKernel::<f64>::new();
let seed_simplices: Vec<SimplexKey> = info.new_simplices.iter().copied().collect();
let stats = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
Some(seed_simplices.as_slice()),
TopologyGuarantee::PLManifold,
None,
)
.unwrap();
assert!(stats.facets_checked > 0);
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_repair_queue_inverse_k3_smoke_5d() {
init_tracing();
let mut tds: Tds<f64, (), (), 5> = Tds::empty();
let r0 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r1 = tds
.insert_vertex_with_mapping(vertex!([1.0, 0.0, 0.0, 0.0, 0.0]))
.unwrap();
let r2 = tds
.insert_vertex_with_mapping(vertex!([0.0, 1.0, 0.0, 0.0, 0.0]))
.unwrap();
let r3 = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 1.0, 0.0, 0.0]))
.unwrap();
let a = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 1.0, 0.0]))
.unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!([0.0, 0.0, 0.0, 0.0, 1.0]))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!([0.2, 0.2, 0.2, 0.2, 0.5]))
.unwrap();
let c1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, a, b], None).unwrap())
.unwrap();
let _c2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, b, c], None).unwrap())
.unwrap();
let _c3 = tds
.insert_simplex_with_mapping(Simplex::new(vec![r0, r1, r2, r3, c, a], None).unwrap())
.unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
let ridge = RidgeHandle::new(c1, 4, 5);
let context = build_k3_flip_context(&tds, ridge).unwrap();
let info = apply_bistellar_flip_k3(&mut tds, &context).unwrap();
let kernel = AdaptiveKernel::<f64>::new();
let seed_simplices: Vec<SimplexKey> = info.new_simplices.iter().copied().collect();
let result = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
Some(seed_simplices.as_slice()),
TopologyGuarantee::PLManifold,
None,
);
match result {
Ok(stats) => assert!(stats.facets_checked > 0),
Err(DelaunayRepairError::PostconditionFailed { .. }) => {
}
Err(err) => panic!("unexpected repair failure: {err}"),
}
assert!(tds.is_valid().is_ok());
}
#[test]
fn test_flip_error_partial_eq() {
let unsupported_1 = FlipError::UnsupportedDimension { dimension: 1 };
let unsupported_1_copy = FlipError::UnsupportedDimension { dimension: 1 };
let unsupported_2 = FlipError::UnsupportedDimension { dimension: 2 };
assert_eq!(unsupported_1, unsupported_1_copy);
assert_ne!(unsupported_1, unsupported_2);
assert_ne!(FlipError::DegenerateSimplex, FlipError::DuplicateSimplex);
assert_eq!(FlipError::NonManifoldFacet, FlipError::NonManifoldFacet);
let ridge_4 = FlipError::InvalidRidgeMultiplicity { found: 4 };
let ridge_4_copy = FlipError::InvalidRidgeMultiplicity { found: 4 };
let ridge_5 = FlipError::InvalidRidgeMultiplicity { found: 5 };
assert_eq!(ridge_4, ridge_4_copy);
assert_ne!(ridge_4, ridge_5);
}
fn sample_tds_validation_failure() -> TdsValidationFailure {
TdsValidationFailure::InvalidNeighbors {
reason: NeighborValidationError::Other {
message: "synthetic neighbor mismatch".to_string(),
},
}
}
fn sample_repair_diagnostics() -> DelaunayRepairDiagnostics {
DelaunayRepairDiagnostics {
facets_checked: 7,
flips_performed: 3,
max_queue_len: 5,
ambiguous_predicates: 2,
ambiguous_predicate_samples: vec![11, 13],
predicate_failures: 1,
cycle_detections: 4,
cycle_signature_samples: vec![17, 19],
attempt: 2,
queue_order: RepairQueueOrder::Lifo,
}
}
#[test]
fn test_flip_failure_kind_preserves_nested_validation_and_repair_reasons() {
let trial_error = FlipError::from(FlipMutationError::TrialValidation {
k_move: 2,
direction: FlipDirection::Forward,
source: sample_tds_validation_failure(),
});
assert_eq!(
FlipFailureKind::from(&trial_error),
FlipFailureKind::TrialValidation
);
let wiring_validation = FlipError::from(FlipNeighborWiringError::TopologyValidation {
source: sample_tds_validation_failure(),
});
assert_eq!(
FlipFailureKind::from(&wiring_validation),
FlipFailureKind::WiringValidation
);
let repair_reason =
FlipNeighborRepairFailure::from(DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::PostRepairVerification,
source: Box::new(trial_error),
});
match &repair_reason {
FlipNeighborRepairFailure::VerificationFailed {
context,
source_kind,
} => {
assert_eq!(
*context,
DelaunayRepairVerificationContext::PostRepairVerification
);
assert_eq!(*source_kind, FlipFailureKind::TrialValidation);
}
other => panic!("expected verification failure, got {other:?}"),
}
let flip_reason =
FlipNeighborRepairFailure::from(DelaunayRepairError::from(FlipError::DuplicateSimplex));
assert_eq!(
flip_reason,
FlipNeighborRepairFailure::Flip {
source_kind: FlipFailureKind::DuplicateSimplex,
}
);
let wiring_repair = FlipError::from(FlipNeighborWiringError::DelaunayRepair {
reason: repair_reason,
});
assert_eq!(
FlipFailureKind::from(&wiring_repair),
FlipFailureKind::DelaunayRepairFailed
);
let dangling_ridge_neighbor = FlipError::DanglingRidgeNeighbor {
simplex_key: SimplexKey::from(KeyData::from_ffi(1)),
neighbor_key: SimplexKey::from(KeyData::from_ffi(2)),
};
assert_eq!(
FlipFailureKind::from(&dangling_ridge_neighbor),
FlipFailureKind::DanglingRidgeNeighbor
);
}
#[test]
fn test_flip_neighbor_conversion_kinds_cover_insertion_suberrors() {
let cavity_kind = FlipNeighborCavityFailureKind::from(
&CavityFillingError::UnsupportedDegenerateLocation {
location: LocateResult::Outside,
},
);
assert_eq!(
cavity_kind,
FlipNeighborCavityFailureKind::UnsupportedDegenerateLocation
);
assert_eq!(cavity_kind.to_string(), "unsupported degenerate location");
let hull_kind =
FlipNeighborHullExtensionFailureKind::from(&HullExtensionReason::InvalidPatch {
details: "non-manifold visible patch".to_string(),
});
assert_eq!(
hull_kind,
FlipNeighborHullExtensionFailureKind::InvalidPatch
);
assert_eq!(hull_kind.to_string(), "invalid patch");
let validation_kind = FlipNeighborDelaunayValidationFailureKind::from(
&DelaunayTriangulationValidationError::RepairOperationFailed {
operation: DelaunayRepairOperation::VertexRemoval,
source: Box::new(DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "repair requires PL topology",
}),
},
);
assert_eq!(
validation_kind,
FlipNeighborDelaunayValidationFailureKind::RepairOperationFailed
);
assert_eq!(validation_kind.to_string(), "repair operation failed");
let repair_wiring = FlipNeighborWiringError::from(InsertionError::DelaunayRepairFailed {
source: Box::new(DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "repair requires PL topology",
}),
context: DelaunayRepairFailureContext::PostInsertionRepair,
});
match repair_wiring {
FlipNeighborWiringError::DelaunayRepair {
reason:
FlipNeighborRepairFailure::InvalidTopology {
required,
found,
message,
},
} => {
assert_eq!(required, TopologyGuarantee::PLManifold);
assert_eq!(found, TopologyGuarantee::Pseudomanifold);
assert_eq!(message, "repair requires PL topology");
}
other => panic!("expected preserved Delaunay repair reason, got {other:?}"),
}
let budget_wiring =
FlipNeighborWiringError::from(InsertionError::MaxSimplicesRemovedExceeded {
max_simplices_removed: 2,
attempted: 3,
});
assert_eq!(
budget_wiring,
FlipNeighborWiringError::MaxSimplicesRemovedExceeded {
max_simplices_removed: 2,
attempted: 3,
}
);
}
#[test]
fn test_flip_neighbor_repair_diagnostics_preserve_summary_fields() {
let diagnostics = sample_repair_diagnostics();
let summary = FlipNeighborRepairDiagnostics::from(diagnostics.clone());
assert_eq!(summary.facets_checked, diagnostics.facets_checked);
assert_eq!(summary.flips_performed, diagnostics.flips_performed);
assert_eq!(summary.max_queue_len, diagnostics.max_queue_len);
assert_eq!(
summary.ambiguous_predicates,
diagnostics.ambiguous_predicates
);
assert_eq!(summary.predicate_failures, diagnostics.predicate_failures);
assert_eq!(summary.cycle_detections, diagnostics.cycle_detections);
assert_eq!(summary.attempt, diagnostics.attempt);
assert_eq!(summary.queue_order, diagnostics.queue_order);
assert_eq!(
summary.to_string(),
"checked=7, flips=3, max_queue=5, ambiguous=2, predicate_failures=1, cycles=4, attempt=2, order=Lifo"
);
let non_convergent = FlipNeighborRepairFailure::from(DelaunayRepairError::NonConvergent {
max_flips: 42,
diagnostics: Box::new(diagnostics),
});
match non_convergent {
FlipNeighborRepairFailure::NonConvergent {
max_flips,
diagnostics,
} => {
assert_eq!(max_flips, 42);
assert_eq!(diagnostics.flips_performed, 3);
}
other => panic!("expected non-convergent repair summary, got {other:?}"),
}
}
#[test]
fn test_delaunay_repair_error_partial_eq() {
let post_test = DelaunayRepairError::PostconditionFailed {
message: "test".to_string(),
};
let post_test_copy = DelaunayRepairError::PostconditionFailed {
message: "test".to_string(),
};
let post_other = DelaunayRepairError::PostconditionFailed {
message: "other".to_string(),
};
assert_eq!(post_test, post_test_copy);
assert_ne!(post_test, post_other);
let verification_err = DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DegenerateSimplex),
};
let verification_err_copy = DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DegenerateSimplex),
};
let verification_other = DelaunayRepairError::VerificationFailed {
context: DelaunayRepairVerificationContext::StrictValidation,
source: Box::new(FlipError::DuplicateSimplex),
};
assert_eq!(verification_err, verification_err_copy);
assert_ne!(verification_err, verification_other);
let flip_err = DelaunayRepairError::from(FlipError::DegenerateSimplex);
let flip_err_copy = DelaunayRepairError::from(FlipError::DegenerateSimplex);
let flip_other = DelaunayRepairError::from(FlipError::DuplicateSimplex);
assert_eq!(flip_err, flip_err_copy);
assert_ne!(flip_err, flip_other);
let canonicalization_err = DelaunayRepairError::OrientationCanonicalizationFailed {
message: "test".to_string(),
};
let canonicalization_err_copy = DelaunayRepairError::OrientationCanonicalizationFailed {
message: "test".to_string(),
};
let canonicalization_other = DelaunayRepairError::OrientationCanonicalizationFailed {
message: "other".to_string(),
};
assert_eq!(canonicalization_err, canonicalization_err_copy);
assert_ne!(canonicalization_err, canonicalization_other);
let topo_err = DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "test",
};
let topo_err_copy = DelaunayRepairError::InvalidTopology {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
message: "test",
};
assert_eq!(topo_err, topo_err_copy);
assert_ne!(post_test, topo_err);
assert_ne!(post_test, verification_err);
assert_ne!(post_test, canonicalization_err);
}
#[test]
fn test_delaunay_repair_error_boxes_large_flip_sources() {
assert!(
std::mem::size_of::<DelaunayRepairError>() < std::mem::size_of::<FlipError>(),
"DelaunayRepairError should box large FlipError payloads"
);
let err = DelaunayRepairError::from(FlipError::DegenerateSimplex);
let source = err.source().expect("boxed flip source should be exposed");
let source = source
.downcast_ref::<Box<FlipError>>()
.expect("source should remain a typed boxed FlipError");
assert!(matches!(source.as_ref(), FlipError::DegenerateSimplex));
let DelaunayRepairError::Flip { source } = err else {
panic!("expected boxed flip source");
};
assert!(matches!(source.as_ref(), FlipError::DegenerateSimplex));
}
#[test]
fn test_delaunay_repair_verification_context_display_covers_all_variants() {
let cases = [
(
DelaunayRepairVerificationContext::PostRepairVerification,
"post-repair verification",
),
(
DelaunayRepairVerificationContext::StrictValidation,
"strict validation",
),
(
DelaunayRepairVerificationContext::LocalK2DegeneracyVerification,
"local k=2 degeneracy verification",
),
(
DelaunayRepairVerificationContext::LocalK2PostconditionVerification,
"local k=2 postcondition verification",
),
(
DelaunayRepairVerificationContext::LocalK3DegeneracyVerification,
"local k=3 degeneracy verification",
),
(
DelaunayRepairVerificationContext::LocalK3PostconditionVerification,
"local k=3 postcondition verification",
),
(
DelaunayRepairVerificationContext::LocalInverseK2PostconditionVerification,
"local inverse k=2 postcondition verification",
),
(
DelaunayRepairVerificationContext::LocalInverseK3PostconditionVerification,
"local inverse k=3 postcondition verification",
),
];
for (context, expected_display) in cases {
assert_eq!(context.to_string(), expected_display);
let err = DelaunayRepairError::VerificationFailed {
context,
source: Box::new(FlipError::DegenerateSimplex),
};
match err {
DelaunayRepairError::VerificationFailed {
context: observed, ..
} => assert_eq!(observed, context),
other => panic!("expected verification failure, got {other:?}"),
}
}
}
macro_rules! gen_align_periodic_offset_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_align_periodic_offset_identity_ $dim d>]() {
let mut source_vertex_offset = [0_i8; $dim];
source_vertex_offset[$dim - 1] = 1;
let result = align_periodic_offset(
source_vertex_offset,
[0_i8; $dim],
[0_i8; $dim],
)
.unwrap();
assert_eq!(result, source_vertex_offset);
}
#[test]
fn [<test_align_periodic_offset_shifts_by_delta_ $dim d>]() {
let mut source_vertex_offset = [0_i8; $dim];
source_vertex_offset[0] = 1;
let mut target_reference_offset = [0_i8; $dim];
target_reference_offset[$dim - 1] = 1;
let mut expected = source_vertex_offset;
expected[$dim - 1] = expected[$dim - 1].saturating_add(1);
let result = align_periodic_offset(
source_vertex_offset,
[0_i8; $dim],
target_reference_offset,
)
.unwrap();
assert_eq!(result, expected);
}
#[test]
fn [<test_align_periodic_offset_negative_delta_ $dim d>]() {
let source_vertex_offset = [1_i8; $dim];
let mut source_reference_offset = [0_i8; $dim];
source_reference_offset[0] = 1;
let mut expected = source_vertex_offset;
expected[0] = 0;
let result = align_periodic_offset(
source_vertex_offset,
source_reference_offset,
[0_i8; $dim],
)
.unwrap();
assert_eq!(result, expected);
}
#[test]
fn [<test_align_periodic_offset_subtraction_overflow_ $dim d>]() {
let mut source_reference_offset = [0_i8; $dim];
source_reference_offset[0] = 1;
let mut target_reference_offset = [0_i8; $dim];
target_reference_offset[0] = i8::MIN;
let result = align_periodic_offset(
[0_i8; $dim],
source_reference_offset,
target_reference_offset,
);
assert!(result.is_err());
}
#[test]
fn [<test_align_periodic_offset_addition_overflow_ $dim d>]() {
let mut source_vertex_offset = [0_i8; $dim];
source_vertex_offset[0] = i8::MAX;
let mut target_reference_offset = [0_i8; $dim];
target_reference_offset[0] = 1;
let result = align_periodic_offset(
source_vertex_offset,
[0_i8; $dim],
target_reference_offset,
);
assert!(result.is_err());
}
}
};
}
gen_align_periodic_offset_tests!(2);
gen_align_periodic_offset_tests!(3);
gen_align_periodic_offset_tests!(4);
gen_align_periodic_offset_tests!(5);
fn toroidal_periodic_model<const D: usize>() -> GlobalTopologyModelAdapter<D> {
GlobalTopology::Toroidal {
domain: [1.0; D],
mode: ToroidalConstructionMode::PeriodicImagePoint,
}
.model()
}
fn insert_periodic_simplex_with_lifted_vertex<const D: usize>(
tds: &mut Tds<f64, (), (), D>,
vertices: Vec<VertexKey>,
lifted_vertex: VertexKey,
) -> SimplexKey {
let mut offsets = vec![[0_i8; D]; vertices.len()];
if let Some(index) = vertices.iter().position(|&vkey| vkey == lifted_vertex) {
offsets[index][0] = 1;
}
let mut simplex = Simplex::new(vertices, None).unwrap();
simplex.set_periodic_vertex_offsets(offsets).unwrap();
tds.insert_simplex_with_mapping(simplex).unwrap()
}
fn insert_periodic_simplex_with_offsets<const D: usize>(
tds: &mut Tds<f64, (), (), D>,
vertices: Vec<VertexKey>,
offsets: Vec<[i8; D]>,
) -> SimplexKey {
let mut simplex = Simplex::new(vertices, None).unwrap();
simplex.set_periodic_vertex_offsets(offsets).unwrap();
tds.insert_simplex_with_mapping(simplex).unwrap()
}
fn insert_plain_simplex<const D: usize>(
tds: &mut Tds<f64, (), (), D>,
vertices: Vec<VertexKey>,
) -> SimplexKey {
tds.insert_simplex_with_mapping(Simplex::new(vertices, None).unwrap())
.unwrap()
}
fn periodic_helper_vertices<const D: usize>(
tds: &mut Tds<f64, (), (), D>,
count: usize,
) -> Vec<VertexKey> {
(0..count)
.map(|index| {
let mut coords = [0.0; D];
coords[index % D] =
0.05 * f64::from(u32::try_from(index + 1).expect("test index fits in u32"));
coords[(index + 1) % D] +=
0.01 * f64::from(u32::try_from(index + 2).expect("test index fits in u32"));
tds.insert_vertex_with_mapping(vertex!(coords)).unwrap()
})
.collect()
}
macro_rules! gen_periodic_lift_helper_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_periodic_lift_helpers_use_simplex_offsets_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let lifted_vertex = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(0)))
.unwrap();
let mut simplex_vertices = Vec::with_capacity($dim + 1);
simplex_vertices.push(lifted_vertex);
simplex_vertices.extend(periodic_helper_vertices::<$dim>(&mut tds, $dim));
let mut offsets = vec![[0_i8; $dim]; simplex_vertices.len()];
offsets[0][0] = 1;
let simplex_key =
insert_periodic_simplex_with_offsets(&mut tds, simplex_vertices.clone(), offsets);
let topology_model = toroidal_periodic_model::<$dim>();
let direct = vertex_point_with_optional_lift(
&tds,
&topology_model,
lifted_vertex,
Some(simplex_key),
)
.unwrap();
let mut expected = unit_vector::<$dim>(0);
expected[0] += 1.0;
assert_relative_eq!(direct.coords().as_slice(), expected.as_slice());
let framed = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
lifted_vertex,
Some(simplex_key),
&[],
)
.unwrap();
assert_relative_eq!(framed.coords().as_slice(), expected.as_slice());
let points = vertices_to_points_with_optional_lift(
&tds,
&topology_model,
&[lifted_vertex],
Some(simplex_key),
&[simplex_key],
)
.unwrap();
assert_relative_eq!(points[0].coords().as_slice(), expected.as_slice());
assert_eq!(matching_source_simplex(&tds, &simplex_vertices, &[simplex_key]), Some(simplex_key));
}
#[test]
fn [<test_periodic_lift_treats_missing_source_offsets_as_zero_frame_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let shared_vertex = tds
.insert_vertex_with_mapping(vertex!([0.0; $dim]))
.unwrap();
let lifted_vertex = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(0)))
.unwrap();
let mut target_vertices = Vec::with_capacity($dim + 1);
target_vertices.push(shared_vertex);
target_vertices.extend(periodic_helper_vertices::<$dim>(&mut tds, $dim));
let target_offsets = vec![[0_i8; $dim]; target_vertices.len()];
let target_simplex =
insert_periodic_simplex_with_offsets(&mut tds, target_vertices, target_offsets);
let mut source_vertices = Vec::with_capacity($dim + 1);
source_vertices.push(shared_vertex);
source_vertices.push(lifted_vertex);
source_vertices.extend(periodic_helper_vertices::<$dim>(
&mut tds,
$dim - 1,
));
let source_simplex = insert_plain_simplex(&mut tds, source_vertices);
let topology_model = toroidal_periodic_model::<$dim>();
let result = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
lifted_vertex,
Some(target_simplex),
&[source_simplex],
);
let lifted = result.unwrap();
assert_relative_eq!(
lifted.coords().as_slice(),
unit_vector::<$dim>(0).as_slice()
);
}
#[test]
fn [<test_periodic_lift_rejects_conflicting_shared_translations_ $dim d>]() {
let mut tds: Tds<f64, (), (), $dim> = Tds::empty();
let shared_a = tds
.insert_vertex_with_mapping(vertex!([0.0; $dim]))
.unwrap();
let mut shared_b_coords = [0.0; $dim];
shared_b_coords[0] = 0.2;
let shared_b = tds
.insert_vertex_with_mapping(vertex!(shared_b_coords))
.unwrap();
let lifted_vertex = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<$dim>(0)))
.unwrap();
let mut target_vertices = Vec::with_capacity($dim + 1);
target_vertices.push(shared_a);
target_vertices.push(shared_b);
target_vertices.extend(periodic_helper_vertices::<$dim>(&mut tds, $dim - 1));
let mut target_offsets = vec![[0_i8; $dim]; target_vertices.len()];
target_offsets[1][0] = 1;
let target_simplex =
insert_periodic_simplex_with_offsets(&mut tds, target_vertices, target_offsets);
let mut source_vertices = Vec::with_capacity($dim + 1);
source_vertices.push(shared_a);
source_vertices.push(shared_b);
source_vertices.push(lifted_vertex);
source_vertices.extend(periodic_helper_vertices::<$dim>(
&mut tds,
$dim - 2,
));
let source_offsets = vec![[0_i8; $dim]; source_vertices.len()];
let source_simplex =
insert_periodic_simplex_with_offsets(&mut tds, source_vertices, source_offsets);
let topology_model = toroidal_periodic_model::<$dim>();
let result = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
lifted_vertex,
Some(target_simplex),
&[source_simplex],
);
assert!(
matches!(
result,
Err(FlipError::InvalidFlipContext {
reason: FlipContextError::ConflictingPeriodicFrameTranslation { .. }
})
),
"conflicting shared translations should be rejected: {result:?}"
);
}
#[test]
fn [<test_removed_simplex_frame_requires_source_simplex_ $dim d>]() {
let result = removed_simplex_frame(&[]);
assert!(matches!(result, Err(FlipError::InvalidFlipContext { .. })));
}
}
};
}
gen_periodic_lift_helper_tests!(2);
gen_periodic_lift_helper_tests!(3);
gen_periodic_lift_helper_tests!(4);
gen_periodic_lift_helper_tests!(5);
fn periodic_inverse_k2_fixture<const D: usize>() -> (
Tds<f64, (), (), D>,
Vec<VertexKey>,
VertexKey,
VertexKey,
SimplexKeyBuffer,
) {
let mut tds: Tds<f64, (), (), D> = Tds::empty();
let mut face_vertices = Vec::with_capacity(D);
for axis in 0..D {
face_vertices.push(
tds.insert_vertex_with_mapping(vertex!(unit_vector::<D>(axis)))
.unwrap(),
);
}
let opposite_a = tds.insert_vertex_with_mapping(vertex!([0.0; D])).unwrap();
let opposite_b = tds.insert_vertex_with_mapping(vertex!([0.25; D])).unwrap();
let lifted_vertex = face_vertices[0];
let mut removed_simplices = SimplexKeyBuffer::new();
for skip in 0..D {
let mut vertices = Vec::with_capacity(D + 1);
vertices.push(opposite_a);
vertices.push(opposite_b);
for (index, &vertex) in face_vertices.iter().enumerate() {
if index != skip {
vertices.push(vertex);
}
}
removed_simplices.push(insert_periodic_simplex_with_lifted_vertex(
&mut tds,
vertices,
lifted_vertex,
));
}
(
tds,
face_vertices,
opposite_a,
opposite_b,
removed_simplices,
)
}
fn periodic_inverse_k3_fixture<const D: usize>() -> (
Tds<f64, (), (), D>,
Vec<VertexKey>,
Vec<VertexKey>,
SimplexKeyBuffer,
) {
let mut tds: Tds<f64, (), (), D> = Tds::empty();
let mut ridge_vertices = Vec::with_capacity(D - 1);
for axis in 0..(D - 1) {
ridge_vertices.push(
tds.insert_vertex_with_mapping(vertex!(unit_vector::<D>(axis)))
.unwrap(),
);
}
let a = tds.insert_vertex_with_mapping(vertex!([0.0; D])).unwrap();
let b = tds
.insert_vertex_with_mapping(vertex!(unit_vector::<D>(D - 1)))
.unwrap();
let c = tds
.insert_vertex_with_mapping(vertex!(skewed_point::<D>()))
.unwrap();
let triangle_vertices = vec![a, b, c];
let lifted_vertex = ridge_vertices[0];
let mut removed_simplices = SimplexKeyBuffer::new();
for skip in 0..(D - 1) {
let mut vertices = Vec::with_capacity(D + 1);
vertices.extend_from_slice(&triangle_vertices);
for (index, &vertex) in ridge_vertices.iter().enumerate() {
if index != skip {
vertices.push(vertex);
}
}
removed_simplices.push(insert_periodic_simplex_with_lifted_vertex(
&mut tds,
vertices,
lifted_vertex,
));
}
(tds, ridge_vertices, triangle_vertices, removed_simplices)
}
macro_rules! gen_periodic_inverse_predicate_tests {
($dim:literal) => {
pastey::paste! {
#[test]
fn [<test_periodic_inverse_k2_uses_removed_simplex_frame_ $dim d>]() {
let (tds, face_vertices, opposite_a, opposite_b, removed_simplices) =
periodic_inverse_k2_fixture::<$dim>();
let mut target_simplex_vertices = face_vertices.clone();
target_simplex_vertices.push(opposite_a);
target_simplex_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
assert!(
matching_source_simplex(&tds, &target_simplex_vertices, &removed_simplices)
.is_none(),
"inverse k=2 target simplex should require explicit frame alignment",
);
let topology_model = toroidal_periodic_model::<$dim>();
let frame_simplex = removed_simplex_frame(&removed_simplices).unwrap();
let lifted = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
face_vertices[0],
Some(frame_simplex),
&removed_simplices,
)
.unwrap();
let mut expected = unit_vector::<$dim>(0);
expected[0] += 1.0;
assert_relative_eq!(lifted.coords().as_slice(), expected.as_slice());
let kernel = AdaptiveKernel::<f64>::new();
let config = RepairAttemptConfig {
attempt: 0,
queue_order: RepairQueueOrder::Fifo,
max_flips_override: None,
};
let mut diagnostics = RepairDiagnostics::default();
let result = delaunay_violation_k2_for_facet(
&tds,
&kernel,
&topology_model,
&face_vertices,
opposite_a,
opposite_b,
&removed_simplices,
Some(frame_simplex),
&config,
&mut diagnostics,
);
assert!(result.is_ok(), "inverse k=2 predicate should align periodic frame: {result:?}");
}
#[test]
fn [<test_periodic_inverse_k3_uses_removed_simplex_frame_ $dim d>]() {
let (tds, ridge_vertices, triangle_vertices, removed_simplices) =
periodic_inverse_k3_fixture::<$dim>();
let mut target_simplex_vertices = ridge_vertices.clone();
target_simplex_vertices.extend_from_slice(&triangle_vertices[1..]);
target_simplex_vertices.sort_unstable_by_key(|v| v.data().as_ffi());
assert!(
matching_source_simplex(&tds, &target_simplex_vertices, &removed_simplices)
.is_none(),
"inverse k=3 target simplex should require explicit frame alignment",
);
let topology_model = toroidal_periodic_model::<$dim>();
let frame_simplex = removed_simplex_frame(&removed_simplices).unwrap();
let lifted = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
ridge_vertices[0],
Some(frame_simplex),
&removed_simplices,
)
.unwrap();
let mut expected = unit_vector::<$dim>(0);
expected[0] += 1.0;
assert_relative_eq!(lifted.coords().as_slice(), expected.as_slice());
let kernel = AdaptiveKernel::<f64>::new();
let config = RepairAttemptConfig {
attempt: 0,
queue_order: RepairQueueOrder::Fifo,
max_flips_override: None,
};
let mut diagnostics = RepairDiagnostics::default();
let result = delaunay_violation_k3_for_ridge(
&tds,
&kernel,
&topology_model,
&ridge_vertices,
&triangle_vertices,
&removed_simplices,
Some(frame_simplex),
&config,
&mut diagnostics,
);
assert!(result.is_ok(), "inverse k=3 predicate should align periodic frame: {result:?}");
}
}
};
}
gen_periodic_inverse_predicate_tests!(4);
gen_periodic_inverse_predicate_tests!(5);
#[test]
fn test_non_periodic_lift_ignores_stored_periodic_offsets() {
let (tds, face_vertices, _opposite_a, _opposite_b, removed_simplices) =
periodic_inverse_k2_fixture::<4>();
let lifted_vertex = face_vertices[0];
let source_simplex = removed_simplices
.iter()
.copied()
.find(|&simplex_key| {
tds.simplex(simplex_key)
.is_some_and(|simplex| simplex.contains_vertex(lifted_vertex))
})
.expect("fixture should contain a removed simplex with the lifted vertex");
let topology_model = GlobalTopology::Euclidean.model();
let direct = vertex_point_with_optional_lift(
&tds,
&topology_model,
lifted_vertex,
Some(source_simplex),
)
.unwrap();
assert_relative_eq!(direct.coords().as_slice(), unit_vector::<4>(0).as_slice());
let framed = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
lifted_vertex,
Some(source_simplex),
&removed_simplices,
)
.unwrap();
assert_relative_eq!(framed.coords().as_slice(), unit_vector::<4>(0).as_slice());
}
#[test]
fn test_periodic_inverse_k2_alignment_failure_is_error() {
let (tds, face_vertices, opposite_a, opposite_b, removed_simplices) =
periodic_inverse_k2_fixture::<4>();
let topology_model = toroidal_periodic_model::<4>();
let frame_simplex = removed_simplex_frame(&removed_simplices).unwrap();
let truncated_removed_simplices: SimplexKeyBuffer =
std::iter::once(frame_simplex).collect();
let lift_result = vertex_point_lifted_into_simplex(
&tds,
&topology_model,
face_vertices[0],
Some(frame_simplex),
&truncated_removed_simplices,
);
assert!(matches!(
lift_result,
Err(FlipError::InvalidFlipContext { .. })
));
let kernel = AdaptiveKernel::<f64>::new();
let config = RepairAttemptConfig {
attempt: 0,
queue_order: RepairQueueOrder::Fifo,
max_flips_override: None,
};
let mut diagnostics = RepairDiagnostics::default();
let result = delaunay_violation_k2_for_facet(
&tds,
&kernel,
&topology_model,
&face_vertices,
opposite_a,
opposite_b,
&truncated_removed_simplices,
Some(frame_simplex),
&config,
&mut diagnostics,
);
assert!(
result.is_err(),
"periodic inverse predicate should not fall back to bare coordinates"
);
}
#[test]
fn test_repair_run_full_reseed_preserves_mutation_frontier() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 0.2]),
];
let dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let tds = dt.tds();
let local_simplex = tds.simplex_keys().next().unwrap();
let outcome = RepairAttemptOutcome {
postcondition_required: false,
stats: DelaunayRepairStats::default(),
last_applied_flip: None,
touched_simplices: once(local_simplex).collect(),
used_full_reseed: true,
};
let run = repair_run_from_attempt(outcome);
assert!(run.used_full_reseed);
assert!(
tds.simplex_keys().count() > 1,
"fixture should distinguish local and full frontiers"
);
assert_eq!(run.touched_simplices.len(), 1);
assert_eq!(run.touched_simplices[0], local_simplex);
}
#[test]
fn test_repair_k2_empty_seed_does_not_full_reseed() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 0.2]),
];
let dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let mut tds = dt.tds().clone();
let before = snapshot_topology(&tds);
let kernel = AdaptiveKernel::<f64>::new();
let config = RepairAttemptConfig {
attempt: 1,
queue_order: RepairQueueOrder::Fifo,
max_flips_override: None,
};
let empty_seeds: &[SimplexKey] = &[];
let outcome =
repair_delaunay_with_flips_k2_attempt(&mut tds, &kernel, Some(empty_seeds), &config)
.unwrap();
assert!(!outcome.used_full_reseed);
assert_eq!(outcome.stats.facets_checked, 0);
assert!(outcome.touched_simplices.is_empty());
assert_eq!(snapshot_topology(&tds), before);
}
#[test]
fn test_repair_queue_k2_local_seed() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 0.2]),
];
let dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let mut tds = dt.tds().clone();
let kernel = AdaptiveKernel::<f64>::new();
let seed_simplex = tds.simplex_keys().next().unwrap();
let stats = repair_delaunay_with_flips_k2_k3(
&mut tds,
&kernel,
Some(&[seed_simplex]),
TopologyGuarantee::PLManifold,
None,
)
.unwrap();
assert!(stats.facets_checked > 0);
assert!(tds.is_valid().is_ok());
}
}