#![forbid(unsafe_code)]
use core::ops::ControlFlow;
use crate::core::collections::{
FastHashSet, MAX_PRACTICAL_DIMENSION_SIZE, SimplexVertexKeyBuffer, SimplexVertexUuidBuffer,
SmallBuffer,
};
use crate::core::simplex::Simplex;
use crate::core::tds::{InvariantError, InvariantKind, SimplexKey, Tds, TdsError, VertexKey};
use crate::core::traits::data_type::DataType;
use crate::core::triangulation::Triangulation;
use crate::core::validation::TriangulationValidationError;
use crate::geometry::kernel::Kernel;
use crate::geometry::point::Point;
use crate::geometry::predicates::Orientation;
use crate::geometry::realization::{
LabeledSimplexRealization, LabeledSimplexRealizationError, PeriodicSimplexSpanError,
SimplexIntersectionFailure, axis_aligned_bounding_boxes_overlap, coordinate_range_for_axis,
try_periodic_simplex_span, validate_simplex_realizations_intersect_only_in_shared_faces,
};
use crate::geometry::robust_predicates::robust_orientation;
use crate::geometry::traits::coordinate::{
CoordinateConversionError, CoordinateValidationError, InvalidCoordinateValue,
};
use crate::topology::traits::global_topology_model::{
GlobalTopologyModel, GlobalTopologyModelError,
};
use crate::topology::traits::topological_space::TopologyKind;
use num_traits::ToPrimitive;
use thiserror::Error;
use uuid::Uuid;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TriangulationRealizationSimplexDetail {
pub key: SimplexKey,
pub uuid: Uuid,
pub vertices: SimplexVertexKeyBuffer,
pub vertex_uuids: SimplexVertexUuidBuffer,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TriangulationRealizationSimplexPairDetail {
pub first_simplex: TriangulationRealizationSimplexDetail,
pub second_simplex: TriangulationRealizationSimplexDetail,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TriangulationRealizationIntersectionDetail {
pub first_simplex: TriangulationRealizationSimplexDetail,
pub second_simplex: TriangulationRealizationSimplexDetail,
pub shared_vertices: SimplexVertexKeyBuffer,
pub shared_vertex_uuids: SimplexVertexUuidBuffer,
pub first_only_witness_vertices: SimplexVertexKeyBuffer,
pub first_only_witness_vertex_uuids: SimplexVertexUuidBuffer,
pub second_only_witness_vertices: SimplexVertexKeyBuffer,
pub second_only_witness_vertex_uuids: SimplexVertexUuidBuffer,
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum PeriodicDomainPeriodError {
#[error("non-finite periodic domain period at axis {axis}: {period}")]
NonFinitePeriod {
axis: usize,
period: InvalidCoordinateValue,
},
#[error("non-positive periodic domain period at axis {axis}: {period}")]
NonPositivePeriod {
axis: usize,
period: f64,
},
}
impl From<PeriodicSimplexSpanError> for PeriodicDomainPeriodError {
fn from(source: PeriodicSimplexSpanError) -> Self {
match source {
PeriodicSimplexSpanError::NonFinitePeriod { axis, period } => {
Self::NonFinitePeriod { axis, period }
}
PeriodicSimplexSpanError::NonPositivePeriod { axis, period } => {
Self::NonPositivePeriod { axis, period }
}
}
}
}
#[derive(Clone, Debug, Error, PartialEq)]
#[non_exhaustive]
pub enum TriangulationRealizationValidationError {
#[error(transparent)]
Tds(Box<TdsError>),
#[error(transparent)]
Triangulation(Box<TriangulationValidationError>),
#[error(
"realization validation is unsupported for {topology:?} topology in dimension {dimension}"
)]
UnsupportedTopology {
topology: TopologyKind,
dimension: usize,
},
#[error(
"topology-specific lifting failed for simplex {simplex_uuid} (key {simplex_key:?}), vertex {vertex_key:?}: {source}"
)]
TopologyLifting {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
vertex_key: VertexKey,
vertex_uuid: Uuid,
#[source]
source: GlobalTopologyModelError,
},
#[error(
"simplex {simplex_uuid} (key {simplex_key:?}) has duplicate realization label {vertex_key:?} ({vertex_uuid}) at indices {first_index} and {duplicate_index}"
)]
DuplicateSimplexRealizationLabel {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
vertex_key: VertexKey,
vertex_uuid: Uuid,
first_index: usize,
duplicate_index: usize,
},
#[error("simplex {simplex_uuid} (key {simplex_key:?}) is degenerate in dimension {dimension}")]
DegenerateSimplex {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
dimension: usize,
},
#[error(
"simplex {simplex_uuid} (key {simplex_key:?}) has negative orientation in dimension {dimension}"
)]
NegativeSimplexOrientation {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
dimension: usize,
},
#[error(
"coordinate validation failed for simplex {simplex_uuid} (key {simplex_key:?}), vertex {vertex_key:?}: {source}"
)]
CoordinateValidation {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
vertex_key: VertexKey,
vertex_uuid: Uuid,
#[source]
source: CoordinateValidationError,
},
#[error(
"orientation predicate failed for simplex {simplex_uuid} (key {simplex_key:?}): {source}"
)]
PredicateFailed {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
#[source]
source: CoordinateConversionError,
},
#[error(
"simplex {simplex_uuid} (key {simplex_key:?}) has a singular barycentric basis in dimension {dimension}"
)]
SingularBarycentricBasis {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
dimension: usize,
},
#[error(
"simplices {first_simplex_uuid} (key {first_simplex_key:?}) and {second_simplex_uuid} (key {second_simplex_key:?}) intersect outside their shared face"
)]
SimplexIntersectionOutsideSharedFace {
first_simplex_key: SimplexKey,
first_simplex_uuid: Uuid,
second_simplex_key: SimplexKey,
second_simplex_uuid: Uuid,
detail: Box<TriangulationRealizationIntersectionDetail>,
},
#[error(
"simplex {simplex_uuid} (key {simplex_key:?}) spans {span} along periodic axis {axis}, but the period is {period}"
)]
PeriodicSimplexSpansDomain {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
axis: usize,
span: f64,
period: f64,
},
#[error(
"invalid periodic domain period while validating simplex {simplex_uuid} (key {simplex_key:?}): {source}"
)]
InvalidPeriodicDomainPeriod {
simplex_key: SimplexKey,
simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexDetail>,
#[source]
source: PeriodicDomainPeriodError,
},
#[error(
"periodic translate range for simplices {first_simplex_uuid} (key {first_simplex_key:?}) and {second_simplex_uuid} (key {second_simplex_key:?}) on axis {axis} exceeds i32 shift bounds: lower {lower_bound}, upper {upper_bound}"
)]
PeriodicTranslateRangeOverflow {
first_simplex_key: SimplexKey,
first_simplex_uuid: Uuid,
second_simplex_key: SimplexKey,
second_simplex_uuid: Uuid,
detail: Box<TriangulationRealizationSimplexPairDetail>,
axis: usize,
lower_bound: f64,
upper_bound: f64,
},
#[error("unexpected {kind:?} validation error while validating Level 4 realization: {source}")]
UnexpectedValidationLayer {
kind: InvariantKind,
#[source]
source: Box<InvariantError>,
},
}
impl From<TdsError> for TriangulationRealizationValidationError {
fn from(source: TdsError) -> Self {
Self::Tds(Box::new(source))
}
}
impl From<TriangulationValidationError> for TriangulationRealizationValidationError {
fn from(source: TriangulationValidationError) -> Self {
Self::Triangulation(Box::new(source))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum TriangulationRealizationValidationErrorKind {
Tds,
Triangulation,
UnsupportedTopology,
TopologyLifting,
DuplicateSimplexRealizationLabel,
DegenerateSimplex,
NegativeSimplexOrientation,
CoordinateValidation,
PredicateFailed,
SingularBarycentricBasis,
SimplexIntersectionOutsideSharedFace,
PeriodicSimplexSpansDomain,
InvalidPeriodicDomainPeriod,
PeriodicTranslateRangeOverflow,
UnexpectedValidationLayer,
}
impl From<&TriangulationRealizationValidationError>
for TriangulationRealizationValidationErrorKind
{
fn from(source: &TriangulationRealizationValidationError) -> Self {
match source {
TriangulationRealizationValidationError::Tds(_) => Self::Tds,
TriangulationRealizationValidationError::Triangulation(_) => Self::Triangulation,
TriangulationRealizationValidationError::UnsupportedTopology { .. } => {
Self::UnsupportedTopology
}
TriangulationRealizationValidationError::TopologyLifting { .. } => {
Self::TopologyLifting
}
TriangulationRealizationValidationError::DuplicateSimplexRealizationLabel {
..
} => Self::DuplicateSimplexRealizationLabel,
TriangulationRealizationValidationError::DegenerateSimplex { .. } => {
Self::DegenerateSimplex
}
TriangulationRealizationValidationError::NegativeSimplexOrientation { .. } => {
Self::NegativeSimplexOrientation
}
TriangulationRealizationValidationError::CoordinateValidation { .. } => {
Self::CoordinateValidation
}
TriangulationRealizationValidationError::PredicateFailed { .. } => {
Self::PredicateFailed
}
TriangulationRealizationValidationError::SingularBarycentricBasis { .. } => {
Self::SingularBarycentricBasis
}
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
..
} => Self::SimplexIntersectionOutsideSharedFace,
TriangulationRealizationValidationError::PeriodicSimplexSpansDomain { .. } => {
Self::PeriodicSimplexSpansDomain
}
TriangulationRealizationValidationError::InvalidPeriodicDomainPeriod { .. } => {
Self::InvalidPeriodicDomainPeriod
}
TriangulationRealizationValidationError::PeriodicTranslateRangeOverflow { .. } => {
Self::PeriodicTranslateRangeOverflow
}
TriangulationRealizationValidationError::UnexpectedValidationLayer { .. } => {
Self::UnexpectedValidationLayer
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[must_use]
pub struct TriangulationRealizationValidationReport {
pub number_of_vertices: usize,
pub number_of_simplices: usize,
pub checked_simplices: usize,
pub checked_simplex_pairs: usize,
pub violations: Vec<TriangulationRealizationValidationError>,
}
impl TriangulationRealizationValidationReport {
#[must_use]
pub const fn is_valid(&self) -> bool {
self.violations.is_empty()
}
}
#[derive(Debug)]
struct RealizedSimplex<const D: usize> {
key: SimplexKey,
uuid: Uuid,
vertex_keys: SimplexVertexKeyBuffer,
vertex_uuids: SimplexVertexUuidBuffer,
realization: LabeledSimplexRealization<RealizedVertexIdentity<D>, D>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct RealizedVertexIdentity<const D: usize> {
key: VertexKey,
offset: [i64; D],
}
impl<const D: usize> RealizedVertexIdentity<D> {
fn translated(self, shift: &[i32; D]) -> Self {
Self {
key: self.key,
offset: std::array::from_fn(|axis| self.offset[axis] + i64::from(shift[axis])),
}
}
}
type PeriodicShiftRangeBuffer = SmallBuffer<(i32, i32), MAX_PRACTICAL_DIMENSION_SIZE>;
impl<const D: usize> RealizedSimplex<D> {
fn try_from_simplex<U, V>(
tds: &Tds<U, V, D>,
topology_model: &impl GlobalTopologyModel<D>,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
) -> Result<Self, TriangulationRealizationValidationError> {
let mut vertices = SimplexVertexKeyBuffer::with_capacity(simplex.number_of_vertices());
let mut vertex_uuids = SimplexVertexUuidBuffer::with_capacity(simplex.number_of_vertices());
let mut coords = SmallBuffer::<[f64; D], MAX_PRACTICAL_DIMENSION_SIZE>::with_capacity(
simplex.number_of_vertices(),
);
let periodic_offsets = simplex.periodic_vertex_offsets();
if let Some(offsets) = periodic_offsets
&& offsets.len() != simplex.number_of_vertices()
{
return Err(TdsError::DimensionMismatch {
expected: simplex.number_of_vertices(),
actual: offsets.len(),
context: format!(
"simplex {:?} (key {simplex_key:?}) periodic offset count vs vertex count during realization validation",
simplex.uuid(),
),
}
.into());
}
for (vertex_index, &vertex_key) in simplex.vertices().iter().enumerate() {
let vertex = tds
.vertex(vertex_key)
.ok_or_else(|| TdsError::VertexNotFound {
vertex_key,
context: format!(
"realization validation for simplex {:?} (key {simplex_key:?})",
simplex.uuid()
),
})?;
vertices.push(vertex_key);
vertex_uuids.push(vertex.uuid());
let periodic_offset = periodic_offsets.map(|offsets| offsets[vertex_index]);
let lifted_coords = topology_model
.lift_for_orientation(*vertex.point().coords(), periodic_offset)
.map_err(
|source| TriangulationRealizationValidationError::TopologyLifting {
simplex_key,
simplex_uuid: simplex.uuid(),
vertex_key,
vertex_uuid: vertex.uuid(),
source,
},
)?;
coords.push(lifted_coords);
}
let identities = vertices.iter().enumerate().map(|(vertex_index, &key)| {
let offset = periodic_offsets.map_or([0_i64; D], |offsets| {
std::array::from_fn(|axis| i64::from(offsets[vertex_index][axis]))
});
RealizedVertexIdentity { key, offset }
});
let realization = LabeledSimplexRealization::try_new(identities, coords.iter().copied())
.map_err(|source| {
labeled_simplex_error_to_realization_error(
source,
simplex_key,
simplex,
&vertices,
&vertex_uuids,
)
})?;
Ok(Self {
key: simplex_key,
uuid: simplex.uuid(),
vertex_keys: vertices,
vertex_uuids,
realization,
})
}
fn point_at(
&self,
vertex_index: usize,
) -> Result<Point<D>, TriangulationRealizationValidationError> {
self.realization.point_at(vertex_index).ok_or_else(|| {
TdsError::DimensionMismatch {
expected: self.realization.labels().len(),
actual: vertex_index.saturating_add(1),
context: format!(
"realized simplex {:?} (key {:?}) point index during Level 4 validation",
self.uuid, self.key,
),
}
.into()
})
}
fn point_for_identity(
&self,
identity: RealizedVertexIdentity<D>,
) -> Result<Point<D>, TriangulationRealizationValidationError> {
let vertex_index = self
.realization
.labels()
.iter()
.position(|candidate| *candidate == identity)
.ok_or_else(|| TdsError::VertexNotFound {
vertex_key: identity.key,
context: format!(
"lifted vertex identity ({:?}, offset {:?}) in realized simplex {:?} (key {:?}) facet-side validation",
identity.key, identity.offset, self.uuid, self.key,
),
})?;
self.point_at(vertex_index)
}
fn vertex_uuids_for_keys(&self, vertex_keys: &[VertexKey]) -> SimplexVertexUuidBuffer {
let mut uuids = SimplexVertexUuidBuffer::with_capacity(vertex_keys.len());
uuids.extend(vertex_keys.iter().filter_map(|vertex_key| {
self.vertex_keys
.iter()
.zip(&self.vertex_uuids)
.find_map(|(candidate, &uuid)| (candidate == vertex_key).then_some(uuid))
}));
uuids
}
fn detail(&self) -> TriangulationRealizationSimplexDetail {
TriangulationRealizationSimplexDetail {
key: self.key,
uuid: self.uuid,
vertices: self.vertex_keys.clone(),
vertex_uuids: self.vertex_uuids.clone(),
}
}
}
fn labeled_simplex_error_to_realization_error<V, const D: usize>(
source: LabeledSimplexRealizationError,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
vertex_keys: &SimplexVertexKeyBuffer,
vertex_uuids: &SimplexVertexUuidBuffer,
) -> TriangulationRealizationValidationError {
let (expected, actual) = match source {
LabeledSimplexRealizationError::LabelCoordinateLengthMismatch {
label_count,
coordinate_count,
} => (label_count, coordinate_count),
LabeledSimplexRealizationError::InvalidArity { expected, actual } => (expected, actual),
LabeledSimplexRealizationError::DuplicateLabel {
first_index,
duplicate_index,
} => {
return duplicate_simplex_realization_label_error(
simplex_key,
simplex.uuid(),
vertex_keys,
vertex_uuids,
first_index,
duplicate_index,
"duplicate realization label during realization validation",
);
}
LabeledSimplexRealizationError::NonFiniteCoordinate {
vertex_index,
coordinate_index,
coordinate_value,
} => {
let Some(&vertex_key) = vertex_keys.get(vertex_index) else {
return TdsError::DimensionMismatch {
expected: vertex_keys.len(),
actual: vertex_index.saturating_add(1),
context: format!(
"simplex {:?} (key {simplex_key:?}) finite-coordinate diagnostic vertex index during realization validation",
simplex.uuid(),
),
}
.into();
};
let Some(&vertex_uuid) = vertex_uuids.get(vertex_index) else {
return TdsError::DimensionMismatch {
expected: vertex_uuids.len(),
actual: vertex_index.saturating_add(1),
context: format!(
"simplex {:?} (key {simplex_key:?}) finite-coordinate diagnostic vertex UUID index during realization validation",
simplex.uuid(),
),
}
.into();
};
return TriangulationRealizationValidationError::CoordinateValidation {
simplex_key,
simplex_uuid: simplex.uuid(),
vertex_key,
vertex_uuid,
source: CoordinateValidationError::InvalidCoordinate {
coordinate_index,
coordinate_value,
dimension: D,
},
};
}
LabeledSimplexRealizationError::InvalidPeriodicDomainPeriod { source } => {
return TriangulationRealizationValidationError::InvalidPeriodicDomainPeriod {
simplex_key,
simplex_uuid: simplex.uuid(),
detail: Box::new(TriangulationRealizationSimplexDetail {
key: simplex_key,
uuid: simplex.uuid(),
vertices: vertex_keys.clone(),
vertex_uuids: vertex_uuids.clone(),
}),
source: source.into(),
};
}
};
TdsError::DimensionMismatch {
expected,
actual,
context: format!(
"simplex {:?} (key {simplex_key:?}) arity during realization validation",
simplex.uuid(),
),
}
.into()
}
fn duplicate_simplex_realization_label_error(
simplex_key: SimplexKey,
simplex_uuid: Uuid,
vertex_keys: &SimplexVertexKeyBuffer,
vertex_uuids: &SimplexVertexUuidBuffer,
first_index: usize,
duplicate_index: usize,
context: &'static str,
) -> TriangulationRealizationValidationError {
let Some(&vertex_key) = vertex_keys.get(first_index) else {
return TdsError::DimensionMismatch {
expected: vertex_keys.len(),
actual: first_index.saturating_add(1),
context: format!("{context} for simplex {simplex_uuid} (key {simplex_key:?})"),
}
.into();
};
let Some(&vertex_uuid) = vertex_uuids.get(first_index) else {
return TdsError::DimensionMismatch {
expected: vertex_uuids.len(),
actual: first_index.saturating_add(1),
context: format!(
"{context} vertex UUID for simplex {simplex_uuid} (key {simplex_key:?})"
),
}
.into();
};
TriangulationRealizationValidationError::DuplicateSimplexRealizationLabel {
simplex_key,
simplex_uuid,
detail: Box::new(TriangulationRealizationSimplexDetail {
key: simplex_key,
uuid: simplex_uuid,
vertices: vertex_keys.clone(),
vertex_uuids: vertex_uuids.clone(),
}),
vertex_key,
vertex_uuid,
first_index,
duplicate_index,
}
}
fn labeled_simplex_error_to_realized_simplex_error<const D: usize>(
source: LabeledSimplexRealizationError,
simplex: &RealizedSimplex<D>,
) -> TriangulationRealizationValidationError {
let (expected, actual) = match source {
LabeledSimplexRealizationError::LabelCoordinateLengthMismatch {
label_count,
coordinate_count,
} => (label_count, coordinate_count),
LabeledSimplexRealizationError::InvalidArity { expected, actual } => (expected, actual),
LabeledSimplexRealizationError::DuplicateLabel {
first_index,
duplicate_index,
} => {
return duplicate_simplex_realization_label_error(
simplex.key,
simplex.uuid,
&simplex.vertex_keys,
&simplex.vertex_uuids,
first_index,
duplicate_index,
"duplicate translated realization label during realization validation",
);
}
LabeledSimplexRealizationError::NonFiniteCoordinate {
vertex_index,
coordinate_index,
coordinate_value,
} => {
let Some(&vertex_key) = simplex.vertex_keys.get(vertex_index) else {
return TdsError::DimensionMismatch {
expected: simplex.vertex_keys.len(),
actual: vertex_index.saturating_add(1),
context: format!(
"simplex {:?} (key {:?}) finite-coordinate translated diagnostic vertex index during realization validation",
simplex.uuid, simplex.key,
),
}
.into();
};
let Some(&vertex_uuid) = simplex.vertex_uuids.get(vertex_index) else {
return TdsError::DimensionMismatch {
expected: simplex.vertex_uuids.len(),
actual: vertex_index.saturating_add(1),
context: format!(
"simplex {:?} (key {:?}) finite-coordinate translated diagnostic vertex UUID index during realization validation",
simplex.uuid, simplex.key,
),
}
.into();
};
return TriangulationRealizationValidationError::CoordinateValidation {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
vertex_key,
vertex_uuid,
source: CoordinateValidationError::InvalidCoordinate {
coordinate_index,
coordinate_value,
dimension: D,
},
};
}
LabeledSimplexRealizationError::InvalidPeriodicDomainPeriod { source } => {
return TriangulationRealizationValidationError::InvalidPeriodicDomainPeriod {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
source: source.into(),
};
}
};
TdsError::DimensionMismatch {
expected,
actual,
context: format!(
"simplex {:?} (key {:?}) arity during translated realization validation",
simplex.uuid, simplex.key,
),
}
.into()
}
impl<K, U, V, const D: usize> Triangulation<K, U, V, D> {
pub fn is_valid_realization(&self) -> Result<(), TriangulationRealizationValidationError> {
if let Some(first_violation) = self.realization_diagnostic()? {
return Err(first_violation);
}
Ok(())
}
pub fn realization_diagnostic(
&self,
) -> Result<
Option<TriangulationRealizationValidationError>,
TriangulationRealizationValidationError,
> {
self.first_realization_violation()
}
pub fn realization_report(
&self,
) -> Result<TriangulationRealizationValidationReport, TriangulationRealizationValidationError>
{
let topology_model = self.global_topology.model();
let mut report = TriangulationRealizationValidationReport {
number_of_vertices: self.tds.number_of_vertices(),
number_of_simplices: self.tds.number_of_simplices(),
checked_simplices: 0,
checked_simplex_pairs: 0,
violations: Vec::new(),
};
if !topology_model.supports_affine_chart_realization_validation() {
report.violations.push(
TriangulationRealizationValidationError::UnsupportedTopology {
topology: self.global_topology.kind(),
dimension: D,
},
);
return Ok(report);
}
let simplices = self.collect_realized_simplices()?;
report.checked_simplices = simplices.len();
let periodic_domain = topology_model.periodic_domain();
let periodic_periods = periodic_domain.map(|domain| *domain.periods());
let mut invalid_simplex_keys = FastHashSet::default();
for simplex in &simplices {
if let Err(error) = validate_simplex_orientation(simplex) {
invalid_simplex_keys.insert(simplex.key);
report.violations.push(error);
}
if let Some(domain) = periodic_domain
&& let Err(error) = validate_periodic_simplex_chart(simplex, domain.periods())
{
invalid_simplex_keys.insert(simplex.key);
report.violations.push(error);
}
}
let (checked_simplex_pairs, _) = for_each_candidate_simplex_pair::<D, ()>(
&simplices,
&invalid_simplex_keys,
periodic_periods,
|first, second| {
if let Err(error) =
validate_topology_aware_simplex_pair(first, second, periodic_periods)
{
report.violations.push(error);
}
ControlFlow::Continue(())
},
);
report.checked_simplex_pairs = checked_simplex_pairs;
Ok(report)
}
pub fn validate_realization(&self) -> Result<(), TriangulationRealizationValidationError>
where
K: Kernel<D, Scalar = f64>,
U: DataType,
V: DataType,
{
self.validate().map_err(|error| match error {
InvariantError::Tds(source) => source.into(),
InvariantError::Triangulation(source) => source.into(),
InvariantError::Realization(source) => source,
source @ InvariantError::Delaunay(_) => {
TriangulationRealizationValidationError::UnexpectedValidationLayer {
kind: InvariantKind::DelaunayProperty,
source: Box::new(source),
}
}
})?;
self.is_valid_realization()
}
pub(crate) fn validate_local_realization_orientation(
&self,
simplices: &[SimplexKey],
) -> Result<(), TriangulationRealizationValidationError> {
let topology_model = self.global_topology.model();
if !topology_model.supports_affine_chart_realization_validation() {
return Ok(());
}
let periodic_domain = topology_model.periodic_domain();
for &simplex_key in simplices {
let simplex =
self.tds
.simplex(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "local realization orientation validation".to_string(),
})?;
let realized = RealizedSimplex::try_from_simplex(
&self.tds,
&topology_model,
simplex_key,
simplex,
)?;
validate_simplex_orientation(&realized)?;
if let Some(domain) = periodic_domain {
validate_periodic_simplex_chart(&realized, domain.periods())?;
}
}
Ok(())
}
pub(crate) fn validate_realization_for_simplices(
&self,
local_simplices: &[SimplexKey],
) -> Result<(), TriangulationRealizationValidationError> {
if local_simplices.is_empty() {
return Ok(());
}
let topology_model = self.global_topology.model();
if !topology_model.supports_affine_chart_realization_validation() {
return Err(
TriangulationRealizationValidationError::UnsupportedTopology {
topology: self.global_topology.kind(),
dimension: D,
},
);
}
let mut local_simplex_keys = FastHashSet::default();
local_simplex_keys.reserve(local_simplices.len());
for &simplex_key in local_simplices {
if !self.tds.contains_simplex(simplex_key) {
return Err(TdsError::SimplexNotFound {
simplex_key,
context: "scoped realization validation".to_string(),
}
.into());
}
local_simplex_keys.insert(simplex_key);
}
let simplices = self.collect_realized_simplices()?;
let periodic_domain = topology_model.periodic_domain();
let periodic_periods = periodic_domain.map(|domain| *domain.periods());
for simplex in &simplices {
if !local_simplex_keys.contains(&simplex.key) {
continue;
}
validate_simplex_orientation(simplex)?;
if let Some(domain) = periodic_domain {
validate_periodic_simplex_chart(simplex, domain.periods())?;
}
}
let empty_skip = FastHashSet::default();
let (_, violation) =
for_each_scoped_candidate_simplex_pair::<D, TriangulationRealizationValidationError>(
&simplices,
&empty_skip,
&local_simplex_keys,
periodic_periods,
|first, second| match validate_topology_aware_simplex_pair(
first,
second,
periodic_periods,
) {
Ok(()) => ControlFlow::Continue(()),
Err(error) => ControlFlow::Break(error),
},
);
if let Some(error) = violation {
return Err(error);
}
Ok(())
}
fn collect_realized_simplices(
&self,
) -> Result<Vec<RealizedSimplex<D>>, TriangulationRealizationValidationError> {
let topology_model = self.global_topology.model();
self.tds
.simplices()
.map(|(simplex_key, simplex)| {
RealizedSimplex::try_from_simplex(&self.tds, &topology_model, simplex_key, simplex)
})
.collect()
}
fn first_realization_violation(
&self,
) -> Result<
Option<TriangulationRealizationValidationError>,
TriangulationRealizationValidationError,
> {
let topology_model = self.global_topology.model();
if !topology_model.supports_affine_chart_realization_validation() {
return Ok(Some(
TriangulationRealizationValidationError::UnsupportedTopology {
topology: self.global_topology.kind(),
dimension: D,
},
));
}
let periodic_domain = topology_model.periodic_domain();
let periodic_periods = periodic_domain.map(|domain| *domain.periods());
let mut simplices = Vec::with_capacity(self.tds.number_of_simplices());
for (simplex_key, simplex) in self.tds.simplices() {
let realized = RealizedSimplex::try_from_simplex(
&self.tds,
&topology_model,
simplex_key,
simplex,
)?;
if let Err(error) = validate_simplex_orientation(&realized) {
return Ok(Some(error));
}
if let Some(domain) = periodic_domain
&& let Err(error) = validate_periodic_simplex_chart(&realized, domain.periods())
{
return Ok(Some(error));
}
simplices.push(realized);
}
let empty_skip: FastHashSet<SimplexKey> = FastHashSet::default();
let (_, violation) =
for_each_candidate_simplex_pair::<D, TriangulationRealizationValidationError>(
&simplices,
&empty_skip,
periodic_periods,
|first, second| match validate_topology_aware_simplex_pair(
first,
second,
periodic_periods,
) {
Ok(()) => ControlFlow::Continue(()),
Err(error) => ControlFlow::Break(error),
},
);
Ok(violation)
}
}
fn validate_topology_aware_simplex_pair<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
periodic_periods: Option<[f64; D]>,
) -> Result<(), TriangulationRealizationValidationError> {
let Some(periods) = periodic_periods else {
if bounding_boxes_overlap(first, second) {
if try_validate_full_facet_pair(first, second)? {
return Ok(());
}
validate_simplex_pair_intersection(first, second)?;
}
return Ok(());
};
let shift_ranges = periodic_shift_ranges(first, second, &periods)?;
let mut shift = [0_i32; D];
validate_periodic_translates(first, second, &periods, &shift_ranges, 0, &mut shift)
}
fn try_validate_full_facet_pair<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
) -> Result<bool, TriangulationRealizationValidationError> {
let mut shared = SmallBuffer::<RealizedVertexIdentity<D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
let mut first_only =
SmallBuffer::<RealizedVertexIdentity<D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
let mut second_only =
SmallBuffer::<RealizedVertexIdentity<D>, MAX_PRACTICAL_DIMENSION_SIZE>::new();
for &identity in first.realization.labels() {
if second.realization.labels().contains(&identity) {
shared.push(identity);
} else {
first_only.push(identity);
}
}
for &identity in second.realization.labels() {
if !first.realization.labels().contains(&identity) {
second_only.push(identity);
}
}
if shared.len() != D || first_only.len() != 1 || second_only.len() != 1 {
return Ok(false);
}
let first_orientation = orientation_against_shared_facet(first, &shared, first_only[0])?;
let second_orientation = orientation_against_shared_facet(second, &shared, second_only[0])?;
match (first_orientation, second_orientation) {
(Orientation::POSITIVE, Orientation::NEGATIVE)
| (Orientation::NEGATIVE, Orientation::POSITIVE) => Ok(true),
(
Orientation::POSITIVE | Orientation::NEGATIVE,
Orientation::POSITIVE | Orientation::NEGATIVE,
) => Err(shared_facet_same_side_intersection(
first,
second,
realized_vertex_keys(&shared),
realized_vertex_keys(&first_only),
realized_vertex_keys(&second_only),
)),
(Orientation::DEGENERATE, _) => {
Err(TriangulationRealizationValidationError::DegenerateSimplex {
simplex_key: first.key,
simplex_uuid: first.uuid,
detail: Box::new(first.detail()),
dimension: D,
})
}
(_, Orientation::DEGENERATE) => {
Err(TriangulationRealizationValidationError::DegenerateSimplex {
simplex_key: second.key,
simplex_uuid: second.uuid,
detail: Box::new(second.detail()),
dimension: D,
})
}
}
}
fn orientation_against_shared_facet<const D: usize>(
simplex: &RealizedSimplex<D>,
shared: &[RealizedVertexIdentity<D>],
opposite: RealizedVertexIdentity<D>,
) -> Result<Orientation, TriangulationRealizationValidationError> {
let mut points = SmallBuffer::<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE>::with_capacity(D + 1);
for &identity in shared {
points.push(simplex.point_for_identity(identity)?);
}
points.push(simplex.point_for_identity(opposite)?);
robust_orientation(&points).map_err(|source| {
TriangulationRealizationValidationError::PredicateFailed {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
source,
}
})
}
fn shared_facet_same_side_intersection<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
shared_vertices: SimplexVertexKeyBuffer,
first_only_witness_vertices: SimplexVertexKeyBuffer,
second_only_witness_vertices: SimplexVertexKeyBuffer,
) -> TriangulationRealizationValidationError {
let shared_vertex_uuids = first.vertex_uuids_for_keys(&shared_vertices);
let first_only_witness_vertex_uuids = first.vertex_uuids_for_keys(&first_only_witness_vertices);
let second_only_witness_vertex_uuids =
second.vertex_uuids_for_keys(&second_only_witness_vertices);
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
first_simplex_key: first.key,
first_simplex_uuid: first.uuid,
second_simplex_key: second.key,
second_simplex_uuid: second.uuid,
detail: Box::new(TriangulationRealizationIntersectionDetail {
first_simplex: first.detail(),
second_simplex: second.detail(),
shared_vertices,
shared_vertex_uuids,
first_only_witness_vertices,
first_only_witness_vertex_uuids,
second_only_witness_vertices,
second_only_witness_vertex_uuids,
}),
}
}
fn validate_periodic_translates<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
periods: &[f64; D],
shift_ranges: &[(i32, i32)],
axis: usize,
shift: &mut [i32; D],
) -> Result<(), TriangulationRealizationValidationError> {
if axis == D {
let translated = translated_simplex(second, periods, shift)?;
if bounding_boxes_overlap(first, &translated) {
if try_validate_full_facet_pair(first, &translated)? {
return Ok(());
}
validate_simplex_pair_intersection(first, &translated)?;
}
return Ok(());
}
let (start, end) = shift_ranges[axis];
for value in start..=end {
shift[axis] = value;
validate_periodic_translates(first, second, periods, shift_ranges, axis + 1, shift)?;
}
Ok(())
}
fn periodic_shift_ranges<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
periods: &[f64; D],
) -> Result<PeriodicShiftRangeBuffer, TriangulationRealizationValidationError> {
(0..D)
.map(|axis| {
let (first_min, first_max) = coordinate_range_for_axis(&first.realization, axis)
.expect("axis generated from 0..D must be valid");
let (second_min, second_max) = coordinate_range_for_axis(&second.realization, axis)
.expect("axis generated from 0..D must be valid");
let period = periods[axis];
let lower_bound = ((first_min - second_max) / period).floor();
let upper_bound = ((first_max - second_min) / period).ceil();
let Some(start) = lower_bound.to_i32() else {
return Err(periodic_translate_range_overflow(
first,
second,
axis,
lower_bound,
upper_bound,
));
};
let Some(end) = upper_bound.to_i32() else {
return Err(periodic_translate_range_overflow(
first,
second,
axis,
lower_bound,
upper_bound,
));
};
Ok((start, end))
})
.collect()
}
fn periodic_translate_range_overflow<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
axis: usize,
lower_bound: f64,
upper_bound: f64,
) -> TriangulationRealizationValidationError {
TriangulationRealizationValidationError::PeriodicTranslateRangeOverflow {
first_simplex_key: first.key,
first_simplex_uuid: first.uuid,
second_simplex_key: second.key,
second_simplex_uuid: second.uuid,
detail: Box::new(TriangulationRealizationSimplexPairDetail {
first_simplex: first.detail(),
second_simplex: second.detail(),
}),
axis,
lower_bound,
upper_bound,
}
}
fn translated_simplex<const D: usize>(
simplex: &RealizedSimplex<D>,
periods: &[f64; D],
shift: &[i32; D],
) -> Result<RealizedSimplex<D>, TriangulationRealizationValidationError> {
let translated = simplex
.realization
.try_translated(periods, shift)
.map_err(|source| labeled_simplex_error_to_realized_simplex_error(source, simplex))?;
let labels = translated
.labels()
.iter()
.map(|identity| identity.translated(shift));
let realization =
LabeledSimplexRealization::try_new(labels, translated.coordinates().iter().copied())
.map_err(|source| labeled_simplex_error_to_realized_simplex_error(source, simplex))?;
Ok(RealizedSimplex {
key: simplex.key,
uuid: simplex.uuid,
vertex_keys: simplex.vertex_keys.clone(),
vertex_uuids: simplex.vertex_uuids.clone(),
realization,
})
}
fn validate_periodic_simplex_chart<const D: usize>(
simplex: &RealizedSimplex<D>,
periods: &[f64; D],
) -> Result<(), TriangulationRealizationValidationError> {
let span = try_periodic_simplex_span(&simplex.realization, periods).map_err(|source| {
TriangulationRealizationValidationError::InvalidPeriodicDomainPeriod {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
source: source.into(),
}
})?;
if let Some(span) = span {
return Err(
TriangulationRealizationValidationError::PeriodicSimplexSpansDomain {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
axis: span.axis(),
span: span.span(),
period: span.period(),
},
);
}
Ok(())
}
fn validate_simplex_orientation<const D: usize>(
simplex: &RealizedSimplex<D>,
) -> Result<(), TriangulationRealizationValidationError> {
let points: SmallBuffer<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE> =
(0..simplex.realization.labels().len())
.map(|index| simplex.point_at(index))
.collect::<Result<_, _>>()?;
match robust_orientation(&points) {
Ok(Orientation::POSITIVE) => Ok(()),
Ok(Orientation::NEGATIVE) => Err(
TriangulationRealizationValidationError::NegativeSimplexOrientation {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
dimension: D,
},
),
Ok(Orientation::DEGENERATE) => {
Err(TriangulationRealizationValidationError::DegenerateSimplex {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
dimension: D,
})
}
Err(source) => Err(TriangulationRealizationValidationError::PredicateFailed {
simplex_key: simplex.key,
simplex_uuid: simplex.uuid,
detail: Box::new(simplex.detail()),
source,
}),
}
}
fn bounding_boxes_overlap<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
) -> bool {
axis_aligned_bounding_boxes_overlap(&first.realization, &second.realization)
}
fn validate_simplex_pair_intersection<const D: usize>(
first: &RealizedSimplex<D>,
second: &RealizedSimplex<D>,
) -> Result<(), TriangulationRealizationValidationError> {
match validate_simplex_realizations_intersect_only_in_shared_faces(
&first.realization,
&second.realization,
) {
Ok(()) => Ok(()),
Err(SimplexIntersectionFailure::SingularBarycentricBasis) => Err(
TriangulationRealizationValidationError::SingularBarycentricBasis {
simplex_key: first.key,
simplex_uuid: first.uuid,
detail: Box::new(first.detail()),
dimension: D,
},
),
Err(SimplexIntersectionFailure::IntersectionOutsideSharedFace { witness, .. }) => {
let shared_vertices = realized_vertex_keys(&witness.shared);
let first_only_witness_vertices = realized_vertex_keys(&witness.first_only_witness);
let second_only_witness_vertices = realized_vertex_keys(&witness.second_only_witness);
let shared_vertex_uuids = first.vertex_uuids_for_keys(&shared_vertices);
let first_only_witness_vertex_uuids =
first.vertex_uuids_for_keys(&first_only_witness_vertices);
let second_only_witness_vertex_uuids =
second.vertex_uuids_for_keys(&second_only_witness_vertices);
Err(
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
first_simplex_key: first.key,
first_simplex_uuid: first.uuid,
second_simplex_key: second.key,
second_simplex_uuid: second.uuid,
detail: Box::new(TriangulationRealizationIntersectionDetail {
first_simplex: first.detail(),
second_simplex: second.detail(),
shared_vertices,
shared_vertex_uuids,
first_only_witness_vertices,
first_only_witness_vertex_uuids,
second_only_witness_vertices,
second_only_witness_vertex_uuids,
}),
},
)
}
}
}
fn realized_vertex_keys<const D: usize>(
identities: &[RealizedVertexIdentity<D>],
) -> SimplexVertexKeyBuffer {
identities.iter().map(|identity| identity.key).collect()
}
#[derive(Clone, Copy, Debug)]
struct SimplexBoundingBox<const D: usize> {
simplex_index: usize,
min: [f64; D],
max: [f64; D],
}
impl<const D: usize> SimplexBoundingBox<D> {
fn from_realized(simplex_index: usize, simplex: &RealizedSimplex<D>) -> Self {
let mut min = [f64::INFINITY; D];
let mut max = [f64::NEG_INFINITY; D];
for coords in simplex.realization.coordinates() {
for (axis, &value) in coords.iter().enumerate() {
min[axis] = min[axis].min(value);
max[axis] = max[axis].max(value);
}
}
Self {
simplex_index,
min,
max,
}
}
fn overlaps(&self, other: &Self) -> bool {
(0..D).all(|axis| self.max[axis] >= other.min[axis] && other.max[axis] >= self.min[axis])
}
}
fn widest_extent_axis<const D: usize>(boxes: &[SimplexBoundingBox<D>]) -> usize {
let mut global_min = [f64::INFINITY; D];
let mut global_max = [f64::NEG_INFINITY; D];
for bounding_box in boxes {
for (axis, (&min, &max)) in bounding_box.min.iter().zip(&bounding_box.max).enumerate() {
global_min[axis] = global_min[axis].min(min);
global_max[axis] = global_max[axis].max(max);
}
}
(0..D)
.map(|axis| (axis, global_max[axis] - global_min[axis]))
.max_by(|(_, left), (_, right)| left.total_cmp(right))
.map_or(0, |(axis, _)| axis)
}
fn for_each_candidate_simplex_pair<const D: usize, B>(
simplices: &[RealizedSimplex<D>],
skip: &FastHashSet<SimplexKey>,
periodic_periods: Option<[f64; D]>,
on_pair: impl FnMut(&RealizedSimplex<D>, &RealizedSimplex<D>) -> ControlFlow<B>,
) -> (usize, Option<B>) {
if periodic_periods.is_some() || D == 0 {
return exhaustive_candidate_simplex_pairs(simplices, skip, on_pair);
}
sweep_and_prune_candidate_simplex_pairs(simplices, skip, on_pair)
}
fn for_each_scoped_candidate_simplex_pair<const D: usize, B>(
simplices: &[RealizedSimplex<D>],
skip: &FastHashSet<SimplexKey>,
scope: &FastHashSet<SimplexKey>,
periodic_periods: Option<[f64; D]>,
mut on_pair: impl FnMut(&RealizedSimplex<D>, &RealizedSimplex<D>) -> ControlFlow<B>,
) -> (usize, Option<B>) {
if scope.is_empty() {
return for_each_candidate_simplex_pair(simplices, skip, periodic_periods, on_pair);
}
if periodic_periods.is_some() || D == 0 {
return scoped_exhaustive_candidate_simplex_pairs(simplices, skip, scope, on_pair);
}
sweep_and_prune_candidate_simplex_pairs(simplices, skip, |first, second| {
if scope.contains(&first.key) || scope.contains(&second.key) {
on_pair(first, second)
} else {
ControlFlow::Continue(())
}
})
}
fn exhaustive_candidate_simplex_pairs<const D: usize, B>(
simplices: &[RealizedSimplex<D>],
skip: &FastHashSet<SimplexKey>,
mut on_pair: impl FnMut(&RealizedSimplex<D>, &RealizedSimplex<D>) -> ControlFlow<B>,
) -> (usize, Option<B>) {
let mut examined = 0_usize;
for (first_index, first_simplex) in simplices.iter().enumerate() {
if skip.contains(&first_simplex.key) {
continue;
}
for second_simplex in &simplices[first_index + 1..] {
if skip.contains(&second_simplex.key) {
continue;
}
examined += 1;
if let ControlFlow::Break(value) = on_pair(first_simplex, second_simplex) {
return (examined, Some(value));
}
}
}
(examined, None)
}
fn scoped_exhaustive_candidate_simplex_pairs<const D: usize, B>(
simplices: &[RealizedSimplex<D>],
skip: &FastHashSet<SimplexKey>,
scope: &FastHashSet<SimplexKey>,
mut on_pair: impl FnMut(&RealizedSimplex<D>, &RealizedSimplex<D>) -> ControlFlow<B>,
) -> (usize, Option<B>) {
let mut examined = 0_usize;
for (local_index, local_simplex) in simplices.iter().enumerate() {
if !scope.contains(&local_simplex.key) || skip.contains(&local_simplex.key) {
continue;
}
for (other_index, other_simplex) in simplices.iter().enumerate() {
if other_index == local_index || skip.contains(&other_simplex.key) {
continue;
}
if scope.contains(&other_simplex.key) && other_index < local_index {
continue;
}
examined += 1;
let first_index = local_index.min(other_index);
let second_index = local_index.max(other_index);
if let ControlFlow::Break(value) =
on_pair(&simplices[first_index], &simplices[second_index])
{
return (examined, Some(value));
}
}
}
(examined, None)
}
fn sweep_and_prune_candidate_simplex_pairs<const D: usize, B>(
simplices: &[RealizedSimplex<D>],
skip: &FastHashSet<SimplexKey>,
mut on_pair: impl FnMut(&RealizedSimplex<D>, &RealizedSimplex<D>) -> ControlFlow<B>,
) -> (usize, Option<B>) {
let mut boxes: Vec<SimplexBoundingBox<D>> = simplices
.iter()
.enumerate()
.filter(|(_, simplex)| !skip.contains(&simplex.key))
.map(|(index, simplex)| SimplexBoundingBox::from_realized(index, simplex))
.collect();
if boxes.len() < 2 {
return (0, None);
}
let sweep_axis = widest_extent_axis(&boxes);
boxes.sort_unstable_by(|left, right| left.min[sweep_axis].total_cmp(&right.min[sweep_axis]));
let mut active: Vec<usize> = Vec::new();
let mut examined = 0_usize;
for current in 0..boxes.len() {
let current_min = boxes[current].min[sweep_axis];
active.retain(|&candidate| boxes[candidate].max[sweep_axis] >= current_min);
for &candidate in &active {
if !boxes[candidate].overlaps(&boxes[current]) {
continue;
}
examined += 1;
let first_index = boxes[candidate]
.simplex_index
.min(boxes[current].simplex_index);
let second_index = boxes[candidate]
.simplex_index
.max(boxes[current].simplex_index);
if let ControlFlow::Break(value) =
on_pair(&simplices[first_index], &simplices[second_index])
{
return (examined, Some(value));
}
}
active.push(current);
}
(examined, None)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::builder::DelaunayTriangulationBuilder;
use crate::core::tds::{Tds, TriangulationConstructionState};
use crate::core::triangulation::Triangulation;
use crate::core::vertex::Vertex;
use crate::delaunay_property_validation::DelaunayValidationError;
use crate::geometry::kernel::FastKernel;
use crate::topology::traits::topological_space::{GlobalTopology, ToroidalConstructionMode};
use crate::validation::{DelaunayTriangulationValidationError, DelaunayVerificationError};
use crate::vertex;
use approx::assert_abs_diff_eq;
use std::assert_matches;
fn test_vertex<const D: usize>(coords: [f64; D]) -> Vertex<(), D> {
vertex!(coords).unwrap()
}
fn tds_from_vertices_and_simplices<const D: usize>(
coords: &[[f64; D]],
simplices: &[Vec<usize>],
) -> Tds<(), (), D> {
tds_from_vertices_and_simplices_with_keys(coords, simplices).0
}
fn tds_from_vertices_and_simplices_with_keys<const D: usize>(
coords: &[[f64; D]],
simplices: &[Vec<usize>],
) -> (Tds<(), (), D>, Vec<SimplexKey>) {
let mut tds = Tds::empty();
let vertex_keys: Vec<_> = coords
.iter()
.map(|coords| {
tds.insert_vertex_with_mapping(test_vertex(*coords))
.unwrap()
})
.collect();
let mut simplex_keys = Vec::with_capacity(simplices.len());
for simplex_vertices in simplices {
let vertices: Vec<_> = simplex_vertices
.iter()
.map(|&index| vertex_keys[index])
.collect();
let simplex_key = tds
.insert_simplex_with_mapping(Simplex::try_new_with_data(vertices, None).unwrap())
.unwrap();
simplex_keys.push(simplex_key);
}
tds.construction_state = TriangulationConstructionState::Constructed;
tds.assign_neighbors().unwrap();
tds.assign_incident_simplices().unwrap();
(tds, simplex_keys)
}
fn tri_from_tds<const D: usize>(
tds: Tds<(), (), D>,
) -> Triangulation<FastKernel<f64>, (), (), D> {
Triangulation::new_with_tds(FastKernel::new(), tds)
}
fn tri_from_tds_with_topology<const D: usize>(
tds: Tds<(), (), D>,
global_topology: GlobalTopology<D>,
) -> Triangulation<FastKernel<f64>, (), (), D> {
let mut tri = tri_from_tds(tds);
tri.global_topology = global_topology;
tri
}
fn realization_detail() -> TriangulationRealizationSimplexDetail {
TriangulationRealizationSimplexDetail {
key: SimplexKey::default(),
uuid: Uuid::nil(),
vertices: SimplexVertexKeyBuffer::new(),
vertex_uuids: SimplexVertexUuidBuffer::new(),
}
}
fn realization_pair_detail() -> TriangulationRealizationSimplexPairDetail {
TriangulationRealizationSimplexPairDetail {
first_simplex: realization_detail(),
second_simplex: realization_detail(),
}
}
fn realization_intersection_detail() -> TriangulationRealizationIntersectionDetail {
TriangulationRealizationIntersectionDetail {
first_simplex: realization_detail(),
second_simplex: realization_detail(),
shared_vertices: SimplexVertexKeyBuffer::new(),
shared_vertex_uuids: SimplexVertexUuidBuffer::new(),
first_only_witness_vertices: SimplexVertexKeyBuffer::new(),
first_only_witness_vertex_uuids: SimplexVertexUuidBuffer::new(),
second_only_witness_vertices: SimplexVertexKeyBuffer::new(),
second_only_witness_vertex_uuids: SimplexVertexUuidBuffer::new(),
}
}
fn assert_realization_error_kind(
source: &TriangulationRealizationValidationError,
expected: TriangulationRealizationValidationErrorKind,
) {
assert_eq!(
TriangulationRealizationValidationErrorKind::from(source),
expected
);
}
fn assert_single_simplex_realizes<const D: usize>() {
let mut coords = Vec::with_capacity(D + 1);
coords.push([0.0; D]);
for axis in 0..D {
let mut point = [0.0; D];
point[axis] = 1.0;
coords.push(point);
}
let simplex = (0..=D).collect();
let mut tri = tri_from_tds(tds_from_vertices_and_simplices(&coords, &[simplex]));
tri.normalize_and_promote_positive_orientation()
.expect("fixture orientation should canonicalize");
assert!(tri.is_valid_realization().is_ok());
}
#[test]
fn is_valid_realization_accepts_single_simplex_dimensions_two_through_five() {
assert_single_simplex_realizes::<2>();
assert_single_simplex_realizes::<3>();
assert_single_simplex_realizes::<4>();
assert_single_simplex_realizes::<5>();
}
#[test]
fn validate_realization_accepts_builder_constructed_triangulation() {
let vertices = vec![
test_vertex([0.0, 0.0, 0.0]),
test_vertex([1.0, 0.0, 0.0]),
test_vertex([0.0, 1.0, 0.0]),
test_vertex([0.0, 0.0, 1.0]),
test_vertex([0.25, 0.25, 0.25]),
];
let dt = DelaunayTriangulationBuilder::new(&vertices)
.build()
.unwrap();
assert!(dt.as_triangulation().validate_realization().is_ok());
}
#[test]
fn is_valid_realization_accepts_two_tetrahedra_sharing_a_facet() {
let coords = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, -1.0],
];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 1, 2, 3], vec![0, 2, 1, 4]]);
let mut tri = tri_from_tds(tds);
tri.normalize_and_promote_positive_orientation()
.expect("fixture orientation should canonicalize");
assert!(tri.is_valid_realization().is_ok());
}
#[test]
fn full_facet_shortcut_rejects_same_side_overlap() {
let coords = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.25, 0.25, 0.5],
];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 1, 2, 3], vec![0, 2, 1, 4]]);
let tri = tri_from_tds(tds);
let simplices = tri
.collect_realized_simplices()
.expect("fixture simplices should realize");
let err = validate_topology_aware_simplex_pair(&simplices[0], &simplices[1], None)
.expect_err("same-side simplices must overlap outside their shared facet");
assert_matches!(
err,
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
detail,
..
} if detail.shared_vertices.len() == 3
&& detail.shared_vertex_uuids.len() == 3
&& detail.first_only_witness_vertices.len() == 1
&& detail.first_only_witness_vertex_uuids.len() == 1
&& detail.second_only_witness_vertices.len() == 1
&& detail.second_only_witness_vertex_uuids.len() == 1
);
}
#[test]
fn validate_realization_rejects_degenerate_simplex() {
let coords = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 1, 2]]);
let tri = tri_from_tds(tds);
let diagnostic = tri
.realization_diagnostic()
.unwrap()
.expect("degenerate simplex should produce a diagnostic");
let report_first = tri
.realization_report()
.unwrap()
.violations
.into_iter()
.next()
.expect("degenerate simplex should be the first report violation");
assert_eq!(diagnostic, report_first);
let err = tri.is_valid_realization().unwrap_err();
assert_eq!(err, diagnostic);
assert_matches!(
err,
TriangulationRealizationValidationError::DegenerateSimplex { dimension: 2, .. }
);
}
#[test]
fn negative_orientation_is_level_four_not_level_three() {
let coords = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 2, 1]]);
let tri = tri_from_tds(tds);
tri.is_valid_topology()
.expect("intrinsic topology must not depend on coordinate orientation");
let error = tri
.validate_realization()
.expect_err("negative coordinate orientation must fail Level 4");
assert_matches!(
error,
TriangulationRealizationValidationError::NegativeSimplexOrientation {
dimension: 2,
..
}
);
}
#[test]
fn is_valid_realization_preserves_duplicate_label_detail() {
let coords = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let (mut tds, simplex_keys) =
tds_from_vertices_and_simplices_with_keys(&coords, &[vec![0, 1, 2]]);
let simplex_key = simplex_keys[0];
let (duplicate_key, middle_key, duplicate_uuid) = {
let simplex = tds
.simplex(simplex_key)
.expect("fixture simplex should exist");
let duplicate_key = simplex.vertices()[0];
let middle_key = simplex.vertices()[1];
let duplicate_uuid = tds
.vertex(duplicate_key)
.expect("duplicate fixture vertex should exist")
.uuid();
(duplicate_key, middle_key, duplicate_uuid)
};
{
let simplex = tds
.simplex_mut(simplex_key)
.expect("fixture simplex should be mutable");
simplex.clear_vertex_keys();
simplex.push_vertex_key(duplicate_key);
simplex.push_vertex_key(middle_key);
simplex.push_vertex_key(duplicate_key);
}
let tri = tri_from_tds(tds);
let err = tri.is_valid_realization().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::DuplicateSimplexRealizationLabel {
simplex_key: observed_simplex_key,
vertex_key,
vertex_uuid,
first_index: 0,
duplicate_index: 2,
detail,
..
} if observed_simplex_key == simplex_key
&& vertex_key == duplicate_key
&& vertex_uuid == duplicate_uuid
&& detail.vertices.len() == 3
&& detail.vertex_uuids.len() == 3
);
}
#[test]
fn realization_report_includes_degenerate_simplex_vertices() {
let coords = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 1, 2]]);
let tri = tri_from_tds(tds);
let report = tri
.realization_report()
.expect("realization report should be generated");
assert!(!report.is_valid());
assert_eq!(report.checked_simplices, 1);
assert_matches!(
&report.violations[..],
[TriangulationRealizationValidationError::DegenerateSimplex {
detail,
dimension: 2,
..
}] if detail.vertices.len() == 3 && detail.vertex_uuids.len() == 3
);
}
#[test]
fn is_valid_realization_rejects_nonadjacent_edge_crossing() {
let coords = [[0.0, 0.0], [2.0, 0.0], [0.0, 2.0], [2.0, 2.0], [1.0, -1.0]];
let tds = tds_from_vertices_and_simplices(
&coords,
&[vec![0, 1, 2], vec![2, 1, 3], vec![3, 2, 4]],
);
let tri = tri_from_tds(tds);
let err = tri.is_valid_realization().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace { .. }
);
}
#[test]
fn is_valid_realization_sweep_and_prune_detects_interposed_overlap() {
let coords = [
[0.0, 0.0], [10.0, 0.0], [0.0, 2.0], [3.0, -1.0], [4.0, -1.0], [3.5, -0.5], [4.5, -1.0], [5.5, -1.0], [4.5, 2.0], ];
let tds = tds_from_vertices_and_simplices(
&coords,
&[vec![0, 1, 2], vec![3, 4, 5], vec![6, 7, 8]],
);
let tri = tri_from_tds(tds);
let err = tri.is_valid_realization().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace { .. }
);
}
#[test]
fn realization_report_includes_intersection_witness_vertices() {
let coords = [[0.0, 0.0], [2.0, 0.0], [0.0, 2.0], [2.0, 2.0], [1.0, -1.0]];
let tds = tds_from_vertices_and_simplices(
&coords,
&[vec![0, 1, 2], vec![2, 1, 3], vec![3, 2, 4]],
);
let tri = tri_from_tds(tds);
let report = tri
.realization_report()
.expect("realization report should be generated");
let intersection =
report
.violations
.iter()
.find(|violation| {
matches!(
violation,
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
..
}
)
})
.expect("report should include an illegal simplex intersection");
assert_matches!(
intersection,
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
detail,
..
} if detail.first_simplex.vertices.len() == 3
&& detail.first_simplex.vertex_uuids.len() == 3
&& detail.second_simplex.vertices.len() == 3
&& detail.second_simplex.vertex_uuids.len() == 3
&& !detail.first_only_witness_vertices.is_empty()
&& detail.first_only_witness_vertices.len()
== detail.first_only_witness_vertex_uuids.len()
&& !detail.second_only_witness_vertices.is_empty()
&& detail.second_only_witness_vertices.len()
== detail.second_only_witness_vertex_uuids.len()
);
}
#[test]
fn is_valid_realization_accepts_lifted_toroidal_simplex_chart() {
let coords = [[0.9, 0.1], [0.1, 0.1], [0.9, 0.3]];
let (mut tds, simplex_keys) =
tds_from_vertices_and_simplices_with_keys(&coords, &[vec![0, 1, 2]]);
tds.simplex_mut(simplex_keys[0])
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 0], [1, 0], [0, 0]])
.unwrap();
let tri = tri_from_tds_with_topology(
tds,
GlobalTopology::try_toroidal([1.0, 1.0], ToroidalConstructionMode::PeriodicImagePoint)
.unwrap(),
);
assert!(tri.is_valid_realization().is_ok());
}
#[test]
fn translated_simplex_updates_lifted_vertex_identities() {
let coords = [[0.9, 0.1], [0.1, 0.1], [0.9, 0.3]];
let (mut tds, simplex_keys) =
tds_from_vertices_and_simplices_with_keys(&coords, &[vec![0, 1, 2]]);
tds.simplex_mut(simplex_keys[0])
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 0], [1, 0], [0, 0]])
.unwrap();
let tri = tri_from_tds_with_topology(
tds,
GlobalTopology::try_toroidal([1.0, 1.0], ToroidalConstructionMode::PeriodicImagePoint)
.unwrap(),
);
let realized = tri
.collect_realized_simplices()
.expect("periodic simplex should realize")
.pop()
.expect("fixture should contain one simplex");
let shift = [-1_i32, 2_i32];
let translated = translated_simplex(&realized, &[1.0, 1.0], &shift)
.expect("finite periodic translation should succeed");
for (before, after) in realized
.realization
.labels()
.iter()
.zip(translated.realization.labels())
{
assert_eq!(before.key, after.key);
assert_eq!(
after.offset,
std::array::from_fn(|axis| before.offset[axis] + i64::from(shift[axis])),
);
}
}
#[test]
fn point_for_identity_reports_missing_lifted_offset() {
let coords = [[0.9, 0.1], [0.1, 0.1], [0.9, 0.3]];
let (mut tds, simplex_keys) =
tds_from_vertices_and_simplices_with_keys(&coords, &[vec![0, 1, 2]]);
tds.simplex_mut(simplex_keys[0])
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 0], [1, 0], [0, 0]])
.unwrap();
let tri = tri_from_tds_with_topology(
tds,
GlobalTopology::try_toroidal([1.0, 1.0], ToroidalConstructionMode::PeriodicImagePoint)
.unwrap(),
);
let realized = tri
.collect_realized_simplices()
.expect("periodic simplex should realize")
.pop()
.expect("fixture should contain one simplex");
let missing_identity = realized.realization.labels()[0].translated(&[2, -3]);
let err = realized.point_for_identity(missing_identity).unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::Tds(source)
if matches!(
*source,
TdsError::VertexNotFound { vertex_key, ref context }
if vertex_key == missing_identity.key
&& context.contains(&format!("offset {:?}", missing_identity.offset))
)
);
}
#[test]
fn is_valid_realization_rejects_unsupported_spherical_topology() {
let coords = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 1, 2]]);
let tri = tri_from_tds_with_topology(tds, GlobalTopology::Spherical);
let err = tri.is_valid_realization().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::UnsupportedTopology {
topology: TopologyKind::Spherical,
dimension: 2,
}
);
}
#[test]
fn is_valid_realization_rejects_periodic_simplex_spanning_domain() {
let coords = [[0.0, 0.0], [1.0, 0.0], [0.0, 0.25]];
let tds = tds_from_vertices_and_simplices(&coords, &[vec![0, 1, 2]]);
let tri = tri_from_tds_with_topology(
tds,
GlobalTopology::try_toroidal([1.0, 1.0], ToroidalConstructionMode::PeriodicImagePoint)
.unwrap(),
);
let err = tri.is_valid_realization().unwrap_err();
let (span, period) = match err {
TriangulationRealizationValidationError::PeriodicSimplexSpansDomain {
axis: 0,
span,
period,
..
} => (span, period),
other => panic!("expected periodic simplex span violation, got {other:?}"),
};
assert_abs_diff_eq!(span, 1.0, epsilon = f64::EPSILON);
assert_abs_diff_eq!(period, 1.0, epsilon = f64::EPSILON);
}
#[test]
fn is_valid_realization_rejects_periodic_translate_overlap() {
let coords = [
[0.0, 0.0],
[0.2, 0.0],
[0.0, 0.8],
[0.95, 0.1],
[0.15, 0.1],
[0.95, 0.3],
];
let (mut tds, simplex_keys) =
tds_from_vertices_and_simplices_with_keys(&coords, &[vec![0, 1, 2], vec![3, 4, 5]]);
tds.simplex_mut(simplex_keys[1])
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 0], [1, 0], [0, 0]])
.unwrap();
let tri = tri_from_tds_with_topology(
tds,
GlobalTopology::try_toroidal([1.0, 1.0], ToroidalConstructionMode::PeriodicImagePoint)
.unwrap(),
);
let err = tri.is_valid_realization().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace { .. }
);
}
#[test]
fn realization_error_kind_covers_wrapped_and_topology_variants() {
assert_realization_error_kind(
&TriangulationRealizationValidationError::Tds(Box::new(
TdsError::InconsistentDataStructure {
message: "synthetic TDS failure".to_string(),
},
)),
TriangulationRealizationValidationErrorKind::Tds,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::Triangulation(Box::new(
TriangulationValidationError::Disconnected { simplex_count: 2 },
)),
TriangulationRealizationValidationErrorKind::Triangulation,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::UnsupportedTopology {
topology: TopologyKind::Spherical,
dimension: 2,
},
TriangulationRealizationValidationErrorKind::UnsupportedTopology,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::TopologyLifting {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
vertex_key: VertexKey::default(),
vertex_uuid: Uuid::nil(),
source: GlobalTopologyModelError::NonFiniteCoordinate {
axis: 0,
value: f64::NAN,
},
},
TriangulationRealizationValidationErrorKind::TopologyLifting,
);
}
#[test]
fn realization_error_kind_covers_simplex_geometry_variants() {
assert_realization_error_kind(
&TriangulationRealizationValidationError::DuplicateSimplexRealizationLabel {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
vertex_key: VertexKey::default(),
vertex_uuid: Uuid::nil(),
first_index: 0,
duplicate_index: 2,
},
TriangulationRealizationValidationErrorKind::DuplicateSimplexRealizationLabel,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::DegenerateSimplex {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
dimension: 2,
},
TriangulationRealizationValidationErrorKind::DegenerateSimplex,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::NegativeSimplexOrientation {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
dimension: 2,
},
TriangulationRealizationValidationErrorKind::NegativeSimplexOrientation,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::CoordinateValidation {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
vertex_key: VertexKey::default(),
vertex_uuid: Uuid::nil(),
source: CoordinateValidationError::InvalidCoordinate {
coordinate_index: 0,
coordinate_value: InvalidCoordinateValue::Nan,
dimension: 2,
},
},
TriangulationRealizationValidationErrorKind::CoordinateValidation,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::PredicateFailed {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
source: CoordinateConversionError::NonFiniteValue {
coordinate_index: 0,
coordinate_value: InvalidCoordinateValue::Nan,
},
},
TriangulationRealizationValidationErrorKind::PredicateFailed,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::SingularBarycentricBasis {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
dimension: 2,
},
TriangulationRealizationValidationErrorKind::SingularBarycentricBasis,
);
}
#[test]
fn realization_error_kind_covers_intersection_periodic_and_layer_variants() {
assert_realization_error_kind(
&TriangulationRealizationValidationError::SimplexIntersectionOutsideSharedFace {
first_simplex_key: SimplexKey::default(),
first_simplex_uuid: Uuid::nil(),
second_simplex_key: SimplexKey::default(),
second_simplex_uuid: Uuid::nil(),
detail: Box::new(realization_intersection_detail()),
},
TriangulationRealizationValidationErrorKind::SimplexIntersectionOutsideSharedFace,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::PeriodicSimplexSpansDomain {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
axis: 0,
span: 1.0,
period: 1.0,
},
TriangulationRealizationValidationErrorKind::PeriodicSimplexSpansDomain,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::InvalidPeriodicDomainPeriod {
simplex_key: SimplexKey::default(),
simplex_uuid: Uuid::nil(),
detail: Box::new(realization_detail()),
source: PeriodicDomainPeriodError::NonPositivePeriod {
axis: 0,
period: 0.0,
},
},
TriangulationRealizationValidationErrorKind::InvalidPeriodicDomainPeriod,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::PeriodicTranslateRangeOverflow {
first_simplex_key: SimplexKey::default(),
first_simplex_uuid: Uuid::nil(),
second_simplex_key: SimplexKey::default(),
second_simplex_uuid: Uuid::nil(),
detail: Box::new(realization_pair_detail()),
axis: 0,
lower_bound: f64::from(i32::MIN) - 1.0,
upper_bound: 0.0,
},
TriangulationRealizationValidationErrorKind::PeriodicTranslateRangeOverflow,
);
assert_realization_error_kind(
&TriangulationRealizationValidationError::UnexpectedValidationLayer {
kind: InvariantKind::DelaunayProperty,
source: Box::new(InvariantError::Delaunay(
DelaunayTriangulationValidationError::VerificationFailed {
source: Box::new(DelaunayVerificationError::from(
DelaunayValidationError::TriangulationState {
source: TdsError::InconsistentDataStructure {
message: "synthetic higher-layer failure".to_string(),
},
},
)),
},
)),
},
TriangulationRealizationValidationErrorKind::UnexpectedValidationLayer,
);
}
}