#![forbid(unsafe_code)]
use crate::core::collections::ViolationBuffer;
#[cfg(any(test, feature = "diagnostics"))]
use crate::core::collections::{NeighborBuffer, SimplexVertexKeyBuffer};
#[cfg(not(any(test, feature = "diagnostics")))]
use crate::core::simplex::SimplexValidationError;
#[cfg(any(test, feature = "diagnostics"))]
use crate::core::simplex::{NeighborSlot, SimplexValidationError};
use crate::core::tds::{SimplexKey, Tds, TdsError, VertexKey};
use crate::core::traits::data_type::DataType;
use crate::geometry::point::Point;
use crate::geometry::predicates::InSphere;
use crate::geometry::robust_predicates::robust_insphere;
use crate::geometry::traits::coordinate::{CoordinateConversionError, CoordinateScalar};
use smallvec::SmallVec;
use thiserror::Error;
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayValidationError {
#[error(
"Simplex violates Delaunay property: simplex contains vertex that is inside circumsphere"
)]
DelaunayViolation {
simplex_key: SimplexKey,
},
#[error("TDS corruption: {source}")]
TriangulationState {
#[source]
source: TdsError,
},
#[error("Invalid simplex {simplex_key:?}: {source}")]
InvalidSimplex {
simplex_key: SimplexKey,
#[source]
source: SimplexValidationError,
},
#[error(
"Numeric predicate failure while validating Delaunay property for simplex {simplex_key:?}, vertex {vertex_key:?}: {source}"
)]
NumericPredicateError {
simplex_key: SimplexKey,
vertex_key: VertexKey,
#[source]
source: CoordinateConversionError,
},
}
#[cfg(any(test, feature = "diagnostics"))]
#[cfg_attr(docsrs, doc(cfg(feature = "diagnostics")))]
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct DelaunayViolationReport {
pub number_of_vertices: usize,
pub number_of_simplices: usize,
pub checked_simplices: usize,
pub violating_simplices: ViolationBuffer,
pub first_violation: Option<DelaunayViolationDetail>,
}
#[cfg(any(test, feature = "diagnostics"))]
impl DelaunayViolationReport {
#[must_use]
pub fn is_valid(&self) -> bool {
self.violating_simplices.is_empty()
}
}
#[cfg(any(test, feature = "diagnostics"))]
#[cfg_attr(docsrs, doc(cfg(feature = "diagnostics")))]
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct DelaunayViolationDetail {
pub simplex_key: SimplexKey,
pub simplex_vertices: SimplexVertexKeyBuffer,
pub offending_vertex: Option<VertexKey>,
pub neighbor_simplices: NeighborBuffer<NeighborSlot>,
}
fn validate_simplex_delaunay<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
simplex_vertex_points: &mut SmallVec<[Point<T, D>; 8]>,
) -> Result<Option<SimplexKey>, DelaunayValidationError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
Ok(
first_delaunay_violation_witness(tds, simplex_key, simplex_vertex_points)?
.map(|_| simplex_key),
)
}
fn first_delaunay_violation_witness<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
simplex_vertex_points: &mut SmallVec<[Point<T, D>; 8]>,
) -> Result<Option<VertexKey>, DelaunayValidationError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let Some(simplex) = tds.simplex(simplex_key) else {
return Ok(None);
};
simplex
.is_valid()
.map_err(|source| DelaunayValidationError::InvalidSimplex {
simplex_key,
source,
})?;
let simplex_vertex_keys: SmallVec<[VertexKey; 8]> =
simplex.vertices().iter().copied().collect();
simplex_vertex_points.clear();
for &vkey in &simplex_vertex_keys {
let Some(v) = tds.vertex(vkey) else {
return Err(DelaunayValidationError::TriangulationState {
source: TdsError::InconsistentDataStructure {
message: format!(
"Simplex {simplex_key:?} references non-existent vertex {vkey:?}"
),
},
});
};
simplex_vertex_points.push(*v.point());
}
for (test_vkey, test_vertex) in tds.vertices() {
if simplex_vertex_keys.contains(&test_vkey) {
continue;
}
match robust_insphere(simplex_vertex_points, test_vertex.point()) {
Ok(InSphere::INSIDE) => {
if D >= 4 {
let is_artifact = 'artifact: {
let Some(a_neighbors) = simplex.neighbor_keys() else {
break 'artifact false;
};
let mut b_points: SmallVec<[Point<T, D>; 8]> =
SmallVec::with_capacity(D + 1);
for (k, neighbor_opt) in a_neighbors.enumerate() {
let Some(b_key) = neighbor_opt else {
continue;
};
let Some(b_simplex) = tds.simplex(b_key) else {
continue;
};
if !b_simplex.vertices().contains(&test_vkey) {
continue;
}
let apex_a_key = simplex_vertex_keys[k];
let Some(apex_a_v) = tds.vertex(apex_a_key) else {
continue;
};
b_points.clear();
let mut valid = true;
for &bv in b_simplex.vertices() {
let Some(v) = tds.vertex(bv) else {
valid = false;
break;
};
b_points.push(*v.point());
}
if !valid {
continue;
}
if matches!(
robust_insphere(&b_points, apex_a_v.point()),
Ok(InSphere::INSIDE | InSphere::BOUNDARY)
) {
break 'artifact true;
}
}
false
};
if is_artifact {
continue;
}
}
return Ok(Some(test_vkey));
}
Ok(InSphere::BOUNDARY | InSphere::OUTSIDE) => {
}
Err(source) => {
return Err(DelaunayValidationError::NumericPredicateError {
simplex_key,
vertex_key: test_vkey,
source,
});
}
}
}
Ok(None)
}
pub(crate) fn is_delaunay_property_only<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
) -> Result<(), DelaunayValidationError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut simplex_vertex_points: SmallVec<[Point<T, D>; 8]> = SmallVec::with_capacity(D + 1);
for simplex_key in tds.simplex_keys() {
if let Some(violating_simplex) =
validate_simplex_delaunay(tds, simplex_key, &mut simplex_vertex_points)?
{
return Err(DelaunayValidationError::DelaunayViolation {
simplex_key: violating_simplex,
});
}
}
Ok(())
}
pub fn find_delaunay_violations<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices_to_check: Option<&[SimplexKey]>,
) -> Result<ViolationBuffer, DelaunayValidationError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut violating_simplices = ViolationBuffer::new();
let mut simplex_vertex_points: SmallVec<[Point<T, D>; 8]> = SmallVec::with_capacity(D + 1);
#[cfg(any(test, debug_assertions))]
if let Some(keys) = simplices_to_check {
tracing::debug!(
"[Delaunay debug] find_delaunay_violations: checking {} requested simplices",
keys.len()
);
} else {
tracing::debug!("[Delaunay debug] find_delaunay_violations: checking all simplices");
}
#[cfg(any(test, debug_assertions))]
let mut processed_simplices = 0usize;
let mut process_simplex = |simplex_key: SimplexKey| -> Result<(), DelaunayValidationError> {
#[cfg(any(test, debug_assertions))]
{
processed_simplices += 1;
}
if let Some(violating_simplex) =
validate_simplex_delaunay(tds, simplex_key, &mut simplex_vertex_points)?
{
violating_simplices.push(violating_simplex);
}
Ok(())
};
match simplices_to_check {
Some(keys) => {
for &simplex_key in keys {
process_simplex(simplex_key)?;
}
}
None => {
for simplex_key in tds.simplex_keys() {
process_simplex(simplex_key)?;
}
}
}
#[cfg(any(test, debug_assertions))]
tracing::debug!(
"[Delaunay debug] find_delaunay_violations: processed {} simplices, found {} violating simplices",
processed_simplices,
violating_simplices.len()
);
Ok(violating_simplices)
}
#[cfg(any(test, feature = "diagnostics"))]
#[cfg_attr(docsrs, doc(cfg(feature = "diagnostics")))]
pub fn delaunay_violation_report<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices_to_check: Option<&[SimplexKey]>,
) -> Result<DelaunayViolationReport, DelaunayValidationError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let violating_simplices = find_delaunay_violations(tds, simplices_to_check)?;
let checked_simplices =
simplices_to_check.map_or_else(|| tds.number_of_simplices(), <[_]>::len);
let first_violation = violating_simplices
.first()
.and_then(|&simplex_key| build_violation_detail(tds, simplex_key));
Ok(DelaunayViolationReport {
number_of_vertices: tds.number_of_vertices(),
number_of_simplices: tds.number_of_simplices(),
checked_simplices,
violating_simplices,
first_violation,
})
}
#[cfg(any(test, feature = "diagnostics"))]
fn build_violation_detail<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
) -> Option<DelaunayViolationDetail>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let simplex = tds.simplex(simplex_key)?;
let simplex_vertices = simplex.vertices().iter().copied().collect();
let neighbor_simplices = simplex
.neighbor_slots()
.map_or_else(NeighborBuffer::new, |slots| slots.iter().copied().collect());
let offending_vertex = first_offending_vertex(tds, simplex_key);
Some(DelaunayViolationDetail {
simplex_key,
simplex_vertices,
offending_vertex,
neighbor_simplices,
})
}
#[cfg(any(test, feature = "diagnostics"))]
fn first_offending_vertex<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplex_key: SimplexKey,
) -> Option<VertexKey>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let mut simplex_vertex_points: SmallVec<[Point<T, D>; 8]> = SmallVec::with_capacity(D + 1);
first_delaunay_violation_witness(tds, simplex_key, &mut simplex_vertex_points)
.ok()
.flatten()
}
#[cfg(any(test, feature = "diagnostics"))]
#[cfg_attr(docsrs, doc(cfg(feature = "diagnostics")))]
#[expect(
clippy::too_many_lines,
reason = "Debug-only helper with intentionally verbose logging"
)]
pub fn debug_print_first_delaunay_violation<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
simplices_subset: Option<&[SimplexKey]>,
) where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let report = match delaunay_violation_report(tds, simplices_subset) {
Ok(report) => report,
Err(e) => {
tracing::warn!(
"[Delaunay debug] debug_print_first_delaunay_violation: error while finding violations: {e}"
);
return;
}
};
let violations = &report.violating_simplices;
tracing::debug!(
"[Delaunay debug] Triangulation summary: {} vertices, {} simplices, {} checked simplices",
report.number_of_vertices,
report.number_of_simplices,
report.checked_simplices,
);
for (vkey, vertex) in tds.vertices() {
tracing::debug!(
"[Delaunay debug] Vertex {:?}: uuid={}, point={:?}",
vkey,
vertex.uuid(),
vertex.point()
);
}
if violations.is_empty() {
tracing::debug!(
"[Delaunay debug] No Delaunay violations detected for requested simplex subset"
);
return;
}
tracing::debug!(
"[Delaunay debug] Delaunay violations detected in {} simplex(s):",
violations.len()
);
for simplex_key in violations {
match tds.simplex(*simplex_key) {
Some(simplex) => {
tracing::debug!(
"[Delaunay debug] Simplex {:?}: uuid={}, vertices:",
simplex_key,
simplex.uuid()
);
for &vkey in simplex.vertices() {
match tds.vertex(vkey) {
Some(v) => {
tracing::debug!(
"[Delaunay debug] vkey={:?}, uuid={}, point={:?}",
vkey,
v.uuid(),
v.point()
);
}
None => {
tracing::debug!("[Delaunay debug] vkey={vkey:?} (missing in TDS)");
}
}
}
}
None => {
tracing::debug!(
"[Delaunay debug] Simplex {simplex_key:?} not found in TDS during violation dump"
);
}
}
}
let first_simplex_key = violations[0];
let Some(simplex) = tds.simplex(first_simplex_key) else {
tracing::debug!(
"[Delaunay debug] First violating simplex {first_simplex_key:?} not found in TDS"
);
return;
};
let offending = first_offending_vertex(tds, first_simplex_key).and_then(|vkey| {
tds.vertex(vkey)
.map(|vertex| (vkey, *vertex.point()))
.or_else(|| {
tracing::warn!(
"[Delaunay debug] First offending vertex {vkey:?} for simplex {first_simplex_key:?} was not found in TDS",
);
None
})
});
if let Some((off_vkey, off_point)) = offending {
tracing::debug!(
"[Delaunay debug] Offending external vertex: vkey={off_vkey:?}, point={off_point:?}",
);
} else {
tracing::debug!(
"[Delaunay debug] No offending external vertex found for first violating simplex (possible degeneracy or removed vertices)"
);
}
if let Some(neighbors) = simplex.neighbor_slots() {
for (facet_idx, neighbor_slot) in neighbors.iter().copied().enumerate() {
match neighbor_slot {
NeighborSlot::Neighbor(neighbor_key) => {
if let Some(neighbor_simplex) = tds.simplex(neighbor_key) {
tracing::debug!(
"[Delaunay debug] facet {facet_idx}: slot=Assigned, neighbor simplex {neighbor_key:?}, uuid={}",
neighbor_simplex.uuid()
);
} else {
tracing::debug!(
"[Delaunay debug] facet {facet_idx}: slot=Assigned, neighbor simplex {neighbor_key:?} missing from TDS",
);
}
}
NeighborSlot::Boundary => {
tracing::debug!(
"[Delaunay debug] facet {facet_idx}: slot=Boundary (hull facet)"
);
}
NeighborSlot::Unassigned => {
tracing::debug!(
"[Delaunay debug] facet {facet_idx}: slot=Unassigned (missing neighbor wiring)"
);
}
}
}
} else {
tracing::debug!(
"[Delaunay debug] First violating simplex has no neighbors assigned (neighbors() == None)"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::algorithms::incremental_insertion::repair_neighbor_pointers;
use crate::core::simplex::{NeighborSlot, Simplex};
use crate::core::triangulation::Triangulation;
use crate::core::util::make_uuid;
use crate::core::vertex::Vertex;
use crate::geometry::kernel::FastKernel;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::{Coordinate, CoordinateConversionError};
use crate::triangulation::DelaunayTriangulation;
use crate::vertex;
#[test]
fn delaunay_validator_reports_no_violations_for_simple_tetrahedron() {
init_tracing();
println!("Testing Delaunay validator and debug helper on a simple 3D tetrahedron");
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let tds = &dt.as_triangulation().tds;
assert!(
is_delaunay_property_only(tds).is_ok(),
"Simple tetrahedron should satisfy the Delaunay property"
);
let violations = find_delaunay_violations(tds, None).unwrap();
assert!(
violations.is_empty(),
"find_delaunay_violations should report no violating simplices for a tetrahedron"
);
#[cfg(any(test, feature = "diagnostics"))]
debug_print_first_delaunay_violation(tds, None);
}
fn init_tracing() {
static INIT: std::sync::Once = std::sync::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 build_non_delaunay_quad_2d() -> (Tds<f64, (), (), 2>, SimplexKey, SimplexKey) {
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!([0.8, 0.8])).unwrap();
let simplex_1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let simplex_2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, c, d], None).unwrap())
.unwrap();
tds.assign_incident_simplices().unwrap();
repair_neighbor_pointers(&mut tds).unwrap();
(tds, simplex_1, simplex_2)
}
#[test]
fn delaunay_validator_reports_violation_for_non_delaunay_quad_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!([0.8, 0.8])).unwrap();
let simplex_1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let simplex_2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, c, d], None).unwrap())
.unwrap();
tds.assign_incident_simplices().unwrap();
match is_delaunay_property_only(&tds) {
Err(DelaunayValidationError::DelaunayViolation { simplex_key }) => {
assert!(simplex_key == simplex_1 || simplex_key == simplex_2);
}
other => panic!("Expected DelaunayViolation, got {other:?}"),
}
}
#[test]
fn find_delaunay_violations_subset_skips_missing_simplices() {
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!([0.8, 0.8])).unwrap();
let simplex_1 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let _simplex_2 = tds
.insert_simplex_with_mapping(Simplex::new(vec![a, c, d], None).unwrap())
.unwrap();
tds.assign_incident_simplices().unwrap();
let violations =
find_delaunay_violations(&tds, Some(&[simplex_1, SimplexKey::default()])).unwrap();
assert_eq!(violations.len(), 1);
assert!(violations.contains(&simplex_1));
}
#[test]
fn delaunay_validation_handles_empty_tds() {
init_tracing();
let tds: Tds<f64, (), (), 2> = Tds::empty();
assert!(is_delaunay_property_only(&tds).is_ok());
let violations = find_delaunay_violations(&tds, None).unwrap();
assert!(violations.is_empty());
}
#[test]
fn find_delaunay_violations_subset_filters_non_violating_simplex() {
init_tracing();
let (tds, simplex_1, simplex_2) = build_non_delaunay_quad_2d();
let violations = find_delaunay_violations(&tds, None).unwrap();
assert_eq!(violations.len(), 1);
let violating_simplex = violations[0];
let non_violating_simplex = if violating_simplex == simplex_1 {
simplex_2
} else {
simplex_1
};
let subset = find_delaunay_violations(&tds, Some(&[non_violating_simplex])).unwrap();
assert!(subset.is_empty());
}
#[test]
fn delaunay_property_only_handles_non_finite_vertex_without_error() {
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();
tds.insert_simplex_with_mapping(Simplex::new(vec![a, b, c], None).unwrap())
.unwrap();
let invalid_uuid = make_uuid();
let invalid_vk = tds
.insert_vertex_with_mapping(Vertex::new_with_uuid(
Point::new([f64::NAN, 0.0]),
invalid_uuid,
None,
))
.unwrap();
tds.remove_vertex(invalid_vk).unwrap();
assert!(
is_delaunay_property_only(&tds).is_ok(),
"delaunay_property_only_handles_non_finite_vertex_without_error should skip non-finite vertices before validation"
);
}
#[test]
fn numeric_predicate_error_display_includes_context() {
let simplex_key = SimplexKey::from(slotmap::KeyData::from_ffi(1));
let vertex_key = VertexKey::from(slotmap::KeyData::from_ffi(2));
let source = CoordinateConversionError::NonFiniteValue {
coordinate_index: 0,
coordinate_value: "NaN".to_string(),
};
let err = DelaunayValidationError::NumericPredicateError {
simplex_key,
vertex_key,
source,
};
let message = err.to_string();
assert!(message.contains("Numeric predicate failure"));
assert!(message.contains("simplex"));
assert!(message.contains("vertex"));
assert!(message.contains("Non-finite value"));
}
#[test]
fn debug_print_first_delaunay_violation_handles_violations() {
init_tracing();
let (tds, _, _) = build_non_delaunay_quad_2d();
#[cfg(any(test, feature = "diagnostics"))]
debug_print_first_delaunay_violation(&tds, None);
}
#[test]
fn delaunay_violation_report_summarizes_valid_tds() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let dt = DelaunayTriangulation::new(&vertices).unwrap();
let report = delaunay_violation_report(dt.tds(), None).unwrap();
assert!(report.is_valid());
assert_eq!(report.number_of_vertices, 4);
assert_eq!(report.number_of_simplices, 1);
assert_eq!(report.checked_simplices, 1);
assert!(report.first_violation.is_none());
}
#[test]
fn delaunay_violation_report_includes_first_violation_detail() {
init_tracing();
let (tds, simplex_1, simplex_2) = build_non_delaunay_quad_2d();
let report = delaunay_violation_report(&tds, None).unwrap();
assert!(!report.is_valid());
assert_eq!(report.violating_simplices.len(), 1);
let detail = report
.first_violation
.as_ref()
.expect("violating report should include first violation details");
assert!(detail.simplex_key == simplex_1 || detail.simplex_key == simplex_2);
assert_eq!(detail.simplex_vertices.len(), 3);
assert_eq!(detail.neighbor_simplices.len(), 3);
let expected_neighbor = if detail.simplex_key == simplex_1 {
simplex_2
} else {
simplex_1
};
assert!(
detail.neighbor_simplices.iter().copied().any(
|slot| matches!(slot, NeighborSlot::Neighbor(key) if key == expected_neighbor)
),
"violation detail should preserve the assigned neighbor slot"
);
assert!(detail.offending_vertex.is_some());
}
#[test]
fn delaunay_violation_detail_preserves_neighbor_slot_state() {
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();
tds.assign_neighbors().unwrap();
{
let simplex = tds.simplex_mut(simplex_key).unwrap();
simplex.ensure_neighbors_buffer_mut()[1] = NeighborSlot::Unassigned;
}
let detail = build_violation_detail(&tds, simplex_key).unwrap();
assert_eq!(
detail.neighbor_simplices.as_slice(),
&[
NeighborSlot::Boundary,
NeighborSlot::Unassigned,
NeighborSlot::Boundary
]
);
}
#[test]
fn delaunay_violation_report_tracks_requested_subset_size() {
init_tracing();
let (tds, simplex_1, _) = build_non_delaunay_quad_2d();
let report =
delaunay_violation_report(&tds, Some(&[simplex_1, SimplexKey::default()])).unwrap();
assert_eq!(report.checked_simplices, 2);
assert_eq!(report.violating_simplices.as_slice(), &[simplex_1]);
assert_eq!(
report
.first_violation
.as_ref()
.map(|detail| detail.simplex_key),
Some(simplex_1)
);
}
#[test]
fn delaunay_property_only_reports_triangulation_state_on_missing_vertex() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 3>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
let original_vertices = {
let simplex = tds.simplex(simplex_key).unwrap();
simplex.vertices().to_vec()
};
let invalid_vkey = VertexKey::from(slotmap::KeyData::from_ffi(u64::MAX));
{
let simplex = tds.simplex_mut(simplex_key).unwrap();
simplex.clear_vertex_keys();
for (idx, &vkey) in original_vertices.iter().enumerate() {
if idx == 0 {
simplex.push_vertex_key(invalid_vkey);
} else {
simplex.push_vertex_key(vkey);
}
}
}
let err = is_delaunay_property_only(&tds).unwrap_err();
assert!(matches!(
err,
DelaunayValidationError::TriangulationState { .. }
));
}
#[test]
fn is_delaunay_property_only_reports_invalid_simplex() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
let simplex = tds.simplex_mut(simplex_key).unwrap();
simplex.ensure_neighbors_buffer_mut().truncate(2);
let err = is_delaunay_property_only(&tds).unwrap_err();
assert!(matches!(
err,
DelaunayValidationError::InvalidSimplex { .. }
));
}
}