use crate::core::algorithms::insertion::{InsertionError, InsertionTopologyValidationContext};
use crate::core::collections::{
MAX_PRACTICAL_DIMENSION_SIZE, SimplexKeyBuffer, SimplexKeySet, SmallBuffer,
fast_hash_set_with_capacity,
};
use crate::core::simplex::Simplex;
use crate::core::tds::{SimplexKey, TdsError, VertexKey};
use crate::core::vertex::Vertex;
use crate::geometry::kernel::Kernel;
use crate::geometry::point::Point;
use crate::geometry::predicates::Orientation;
use crate::geometry::robust_predicates::robust_orientation;
use crate::topology::traits::global_topology_model::GlobalTopologyModel;
use crate::triangulation::Triangulation;
use crate::triangulation::realization::{
TriangulationRealizationSimplexDetail, TriangulationRealizationValidationError,
};
use crate::triangulation::validation::TriangulationValidationError;
const POSITIVE_ORIENTATION_PROMOTION_MAX_PASSES: usize = 3;
impl<K, U, V, const D: usize> Triangulation<K, U, V, D>
where
K: Kernel<D, Scalar = f64>,
{
fn orientation_simplex_detail(
&self,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
) -> Result<Box<TriangulationRealizationSimplexDetail>, TdsError> {
let vertex_uuids = simplex
.vertices()
.iter()
.map(|&vertex_key| {
self.tds
.vertex(vertex_key)
.map(Vertex::uuid)
.ok_or_else(|| TdsError::VertexNotFound {
vertex_key,
context: format!("orientation diagnostic for simplex {simplex_key:?}"),
})
})
.collect::<Result<_, _>>()?;
Ok(Box::new(TriangulationRealizationSimplexDetail {
key: simplex_key,
uuid: simplex.uuid(),
vertices: simplex.vertices().iter().copied().collect(),
vertex_uuids,
}))
}
fn collect_simplex_points_for_orientation(
&self,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
purpose: &str,
) -> Result<
SmallBuffer<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE>,
TriangulationRealizationValidationError,
> {
let topology_model = self.global_topology.model();
let periodic_offsets = simplex.periodic_vertex_offsets();
if let Some(offsets) = periodic_offsets {
if 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 {purpose}",
simplex.uuid(),
),
}.into());
}
if !topology_model.supports_periodic_orientation_offsets() {
return Err(TdsError::InconsistentDataStructure {
message: format!(
"Simplex {:?} (key {simplex_key:?}) has periodic offsets (count {}) during {purpose}, but triangulation global topology is {:?} (kind {:?}, allows_boundary: {}, periodic_domain: {:?}); expected periodic-orientation-offset-capable topology",
simplex.uuid(),
offsets.len(),
self.global_topology,
topology_model.kind(),
topology_model.allows_boundary(),
topology_model.periodic_domain(),
),
}.into());
}
}
let mut points: SmallBuffer<Point<D>, MAX_PRACTICAL_DIMENSION_SIZE> =
SmallBuffer::with_capacity(simplex.number_of_vertices());
for (vertex_idx, &vertex_key) in simplex.vertices().iter().enumerate() {
let vertex = self.tds.vertex(vertex_key).ok_or_else(|| {
TdsError::VertexNotFound {
vertex_key,
context: format!(
"referenced by simplex {:?} (key {simplex_key:?}) at position {vertex_idx} during {purpose}",
simplex.uuid(),
),
}
})?;
let periodic_offset = periodic_offsets.map(|offsets| offsets[vertex_idx]);
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,
},
)?;
let lifted_point = Point::try_new(lifted_coords).map_err(|source| {
TriangulationRealizationValidationError::CoordinateValidation {
simplex_key,
simplex_uuid: simplex.uuid(),
vertex_key,
vertex_uuid: vertex.uuid(),
source,
}
})?;
points.push(lifted_point);
}
Ok(points)
}
pub(crate) fn evaluate_simplex_orientation_for_context(
&self,
simplex_key: SimplexKey,
simplex: &Simplex<V, D>,
purpose: &str,
) -> Result<i32, TriangulationRealizationValidationError> {
let points = self.collect_simplex_points_for_orientation(simplex_key, simplex, purpose)?;
match robust_orientation(&points) {
Ok(Orientation::POSITIVE) => Ok(1),
Ok(Orientation::NEGATIVE) => Ok(-1),
Ok(Orientation::DEGENERATE) => Ok(0),
Err(source) => Err(TriangulationRealizationValidationError::PredicateFailed {
simplex_key,
simplex_uuid: simplex.uuid(),
detail: self.orientation_simplex_detail(simplex_key, simplex)?,
source,
}),
}
}
pub(crate) fn validate_geometric_simplex_orientation(
&self,
) -> Result<(), TriangulationRealizationValidationError> {
for (simplex_key, simplex) in self.tds.simplices() {
let orientation = self.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"geometric orientation validation",
)?;
if orientation < 0 {
let vertex_keys: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex.vertices().iter().copied().collect();
let neighbor_keys: SmallBuffer<Option<SimplexKey>, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex
.neighbor_keys()
.map(Iterator::collect)
.unwrap_or_default();
tracing::debug!(
simplex_uuid = %simplex.uuid(),
?simplex_key,
?vertex_keys,
?neighbor_keys,
orientation,
"negative geometric orientation detected during validation",
);
return Err(
TriangulationRealizationValidationError::NegativeSimplexOrientation {
simplex_key,
simplex_uuid: simplex.uuid(),
detail: self.orientation_simplex_detail(simplex_key, simplex)?,
dimension: D,
},
);
}
}
Ok(())
}
pub(crate) fn validate_geometric_simplex_orientation_for_simplices(
&self,
simplices: &[SimplexKey],
) -> Result<(), TriangulationRealizationValidationError> {
for &simplex_key in simplices {
let simplex =
self.tds
.simplex(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "local geometric orientation validation scope".to_string(),
})?;
let orientation = self.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"local geometric orientation validation",
)?;
if orientation < 0 {
let vertex_keys: SmallBuffer<VertexKey, MAX_PRACTICAL_DIMENSION_SIZE> =
simplex.vertices().iter().copied().collect();
tracing::debug!(
simplex_uuid = %simplex.uuid(),
?simplex_key,
?vertex_keys,
orientation,
"negative geometric orientation detected during local validation",
);
return Err(
TriangulationRealizationValidationError::NegativeSimplexOrientation {
simplex_key,
simplex_uuid: simplex.uuid(),
detail: self.orientation_simplex_detail(simplex_key, simplex)?,
dimension: D,
},
);
}
}
Ok(())
}
pub(crate) fn validate_local_orientation_for_simplices(
&self,
simplices: &[SimplexKey],
) -> Result<(), InsertionError> {
self.tds
.validate_coherent_orientation_for_simplices(simplices)?;
self.validate_geometric_simplex_orientation_for_simplices(simplices)
.map_err(|source| InsertionError::OrientationValidationFailed { source })?;
Ok(())
}
fn promote_simplices_to_positive_orientation(&mut self) -> Result<bool, InsertionError> {
let mut negative_simplices = SimplexKeyBuffer::new();
for (simplex_key, simplex) in self.tds.simplices() {
let orientation = self
.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"positive-orientation promotion",
)
.map_err(|source| InsertionError::OrientationValidationFailed { source })?;
if orientation == 0 {
continue;
}
if orientation < 0 {
negative_simplices.push(simplex_key);
}
}
if negative_simplices.is_empty() {
return Ok(false);
}
self.tds.reverse_simplex_orientations(&negative_simplices)?;
Ok(true)
}
fn simplices_require_positive_orientation_promotion(&self) -> Result<bool, InsertionError> {
for (simplex_key, simplex) in self.tds.simplices() {
let orientation = self
.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"positive-orientation convergence check",
)
.map_err(|source| InsertionError::OrientationValidationFailed { source })?;
if orientation == 0 {
continue;
}
if orientation < 0 {
return Ok(true);
}
}
Ok(false)
}
fn canonicalize_component_orientation_signs(&mut self) -> Result<(), InsertionError> {
let simplex_keys: Vec<SimplexKey> = self.tds.simplex_keys().collect();
let mut visited: SimplexKeySet = fast_hash_set_with_capacity(simplex_keys.len());
let mut simplices_to_flip = Vec::new();
for root_simplex_key in simplex_keys {
if !visited.insert(root_simplex_key) {
continue;
}
let mut component = Vec::new();
let mut stack = vec![root_simplex_key];
let mut representative_sign = None;
while let Some(simplex_key) = stack.pop() {
let simplex =
self.tds
.simplex(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "component orientation-sign canonicalization".to_string(),
})?;
let orientation = self
.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"component orientation-sign canonicalization",
)
.map_err(|source| InsertionError::OrientationValidationFailed { source })?;
if representative_sign.is_none() && orientation != 0 {
representative_sign = Some(orientation);
}
component.push(simplex_key);
let Some(neighbors) = simplex.neighbor_keys() else {
continue;
};
for neighbor_key in neighbors.flatten() {
if visited.insert(neighbor_key) {
stack.push(neighbor_key);
}
}
}
if representative_sign == Some(-1) {
simplices_to_flip.extend(component);
}
}
self.tds
.reverse_simplex_orientations(&simplices_to_flip)
.map_err(InsertionError::from)
}
pub(crate) fn normalize_and_promote_positive_orientation(
&mut self,
) -> Result<(), InsertionError> {
self.tds.normalize_coherent_orientation()?;
self.canonicalize_component_orientation_signs()?;
for _ in 0..POSITIVE_ORIENTATION_PROMOTION_MAX_PASSES {
if !self.promote_simplices_to_positive_orientation()? {
break;
}
self.tds.normalize_coherent_orientation()?;
self.canonicalize_component_orientation_signs()?;
}
if self.simplices_require_positive_orientation_promotion()? {
let mut residual_count = 0_usize;
let mut sample_keys: [Option<SimplexKey>; 5] = [None; 5];
for (simplex_key, simplex) in self.tds.simplices() {
let orientation = self
.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"residual negative-orientation sampling",
)
.map_err(|source| InsertionError::OrientationValidationFailed { source })?;
if orientation < 0 {
if residual_count < sample_keys.len() {
sample_keys[residual_count] = Some(simplex_key);
}
residual_count += 1;
}
}
let sampled: Vec<SimplexKey> = sample_keys.into_iter().flatten().collect();
return Err(InsertionError::TopologyValidationFailed {
context: InsertionTopologyValidationContext::PositiveOrientationPromotion,
source: TriangulationValidationError::OrientationPromotionNonConvergence {
residual_count,
sampled,
},
});
}
Ok(())
}
pub(crate) fn canonicalize_positive_orientation_for_simplices(
&mut self,
simplices: &SimplexKeyBuffer,
) -> Result<(), InsertionError> {
#[cfg(debug_assertions)]
let debug_orientation = std::env::var_os("DELAUNAY_DEBUG_ORIENTATION").is_some();
#[cfg(debug_assertions)]
let mut orientation_warn_count = 0_usize;
for &simplex_key in simplices {
let orientation = {
let simplex =
self.tds
.simplex(simplex_key)
.ok_or_else(|| TdsError::SimplexNotFound {
simplex_key,
context: "canonicalizing insertion orientation".to_string(),
})?;
self.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"insertion orientation canonicalization",
)
.map_err(|source| InsertionError::OrientationValidationFailed { source })?
};
if orientation == 0 {
continue;
}
if orientation < 0 {
#[cfg(debug_assertions)]
let pre_swap_vertices = if debug_orientation {
self.tds.simplex(simplex_key).map(|c| c.vertices().to_vec())
} else {
None
};
self.tds
.reverse_simplex_orientations(std::slice::from_ref(&simplex_key))?;
#[cfg(debug_assertions)]
if debug_orientation {
orientation_warn_count += 1;
if orientation_warn_count <= 3 {
let post_orientation = self.tds.simplex(simplex_key).map(|c| {
self.evaluate_simplex_orientation_for_context(
simplex_key,
c,
"orientation swap verification",
)
});
match post_orientation {
Some(Ok(post_o)) => {
tracing::warn!(
simplex_key = ?simplex_key,
pre_swap_vertices = ?pre_swap_vertices,
pre_swap_orientation = orientation,
post_swap_orientation = post_o,
swap_fixed = post_o > 0,
"canonicalize_positive_orientation: negative-orientation simplex swapped"
);
}
Some(Err(ref e)) => {
tracing::warn!(
simplex_key = ?simplex_key,
pre_swap_vertices = ?pre_swap_vertices,
pre_swap_orientation = orientation,
error = %e,
"canonicalize_positive_orientation: post-swap verification failed"
);
}
None => {
tracing::warn!(
simplex_key = ?simplex_key,
pre_swap_vertices = ?pre_swap_vertices,
pre_swap_orientation = orientation,
"canonicalize_positive_orientation: simplex not found after swap"
);
}
}
}
}
}
}
#[cfg(debug_assertions)]
if orientation_warn_count > 3 && debug_orientation {
let suppressed = orientation_warn_count - 3;
tracing::warn!(
total_negative = orientation_warn_count,
suppressed,
"canonicalize_positive_orientation: suppressed {suppressed} additional negative-orientation warnings (see first 3 above)"
);
}
Ok(())
}
pub(crate) fn validate_geometric_nondegeneracy(
&self,
) -> Result<(), TriangulationRealizationValidationError> {
for (simplex_key, simplex) in self.tds.simplices() {
let orientation = self.evaluate_simplex_orientation_for_context(
simplex_key,
simplex,
"geometric nondegeneracy check",
)?;
if orientation == 0 {
return Err(TriangulationRealizationValidationError::DegenerateSimplex {
simplex_key,
simplex_uuid: simplex.uuid(),
detail: self.orientation_simplex_detail(simplex_key, simplex)?,
dimension: D,
});
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::simplex::Simplex;
use crate::core::tds::Tds;
use crate::geometry::kernel::FastKernel;
use crate::topology::traits::global_topology_model::GlobalTopologyModelError;
use crate::topology::traits::topological_space::{
GlobalTopology, ToroidalConstructionMode, ToroidalDomainError,
};
use crate::vertex;
use std::assert_matches;
#[test]
fn negative_oriented_simplex_remains_topologically_valid() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let mut tri =
Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
assert!(tri.is_valid_topology().is_ok());
let simplex_key = tri.tds.simplex_keys().next().unwrap();
tri.tds
.simplex_mut(simplex_key)
.unwrap()
.swap_vertex_slots(0, 1);
assert!(tri.is_valid_topology().is_ok());
let err = tri.validate_geometric_simplex_orientation().unwrap_err();
assert_matches!(
&err,
TriangulationRealizationValidationError::NegativeSimplexOrientation {
simplex_key: key, simplex_uuid, detail, dimension: 2,
} if *key == simplex_key
&& *simplex_uuid == tri.tds.simplex(simplex_key).unwrap().uuid()
&& detail.vertices.as_slice() == tri.tds.simplex(simplex_key).unwrap().vertices()
&& detail.vertex_uuids.len() == 3
);
assert_eq!(
tri.validate_geometric_simplex_orientation_for_simplices(&[simplex_key]),
Err(err.clone())
);
assert_eq!(
tri.validate_local_orientation_for_simplices(&[simplex_key]),
Err(InsertionError::OrientationValidationFailed {
source: err.clone()
})
);
assert!(InsertionError::OrientationValidationFailed { source: err }.is_retryable());
}
#[test]
fn local_geometric_orientation_validation_errors_on_missing_scope_simplex() {
let vertices = vec![
vertex!([0.0, 0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0, 0.0]).unwrap(),
vertex!([0.0, 1.0, 0.0]).unwrap(),
vertex!([0.0, 0.0, 1.0]).unwrap(),
];
let tds =
Triangulation::<FastKernel<f64>, (), (), 3>::build_initial_simplex(&vertices).unwrap();
let mut tri =
Triangulation::<FastKernel<f64>, (), (), 3>::new_with_tds(FastKernel::new(), tds);
let simplex_key = tri.tds.simplex_keys().next().unwrap();
assert_eq!(tri.tds.remove_simplices_by_keys(&[simplex_key]).unwrap(), 1);
match tri.validate_geometric_simplex_orientation_for_simplices(&[simplex_key]) {
Err(TriangulationRealizationValidationError::Tds { source }) => {
assert_matches!(*source, TdsError::SimplexNotFound {
simplex_key: missing_key, ..
} if missing_key == simplex_key);
}
other => panic!("Expected SimplexNotFound, got {other:?}"),
}
}
#[test]
fn simplices_require_positive_orientation_promotion_detects_negative_without_mutating() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
tds.simplex_mut(simplex_key)
.unwrap()
.swap_vertex_slots(0, 1);
let tri = Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
let before: Vec<_> = tri.tds.simplex(simplex_key).unwrap().vertices().to_vec();
assert!(
tri.simplices_require_positive_orientation_promotion()
.unwrap()
);
let after: Vec<_> = tri.tds.simplex(simplex_key).unwrap().vertices().to_vec();
assert_eq!(before, after);
}
#[test]
fn simplices_require_positive_orientation_promotion_false_for_positive_without_mutating() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
let tri = Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
let before: Vec<_> = tri.tds.simplex(simplex_key).unwrap().vertices().to_vec();
assert!(
!tri.simplices_require_positive_orientation_promotion()
.unwrap()
);
let after: Vec<_> = tri.tds.simplex(simplex_key).unwrap().vertices().to_vec();
assert_eq!(before, after);
}
#[test]
fn canonicalize_component_orientation_signs_flips_each_component_independently() {
let mut tds: Tds<(), (), 2> = Tds::empty();
let component_vertices = [
[0.0, 0.0],
[1.0, 0.0],
[0.0, 1.0],
[2.0, 0.0],
[3.0, 0.0],
[2.0, 1.0],
];
let mut vertex_keys = Vec::with_capacity(component_vertices.len());
for coords in component_vertices {
vertex_keys.push(
tds.insert_vertex_with_mapping(vertex!(coords).unwrap())
.unwrap(),
);
}
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![vertex_keys[0], vertex_keys[1], vertex_keys[2]], None)
.unwrap(),
)
.unwrap();
tds.insert_simplex_with_mapping(
Simplex::try_new_with_data(vec![vertex_keys[3], vertex_keys[5], vertex_keys[4]], None)
.unwrap(),
)
.unwrap();
let mut tri =
Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
assert!(
tri.simplices_require_positive_orientation_promotion()
.unwrap()
);
tri.canonicalize_component_orientation_signs().unwrap();
assert!(
!tri.simplices_require_positive_orientation_promotion()
.unwrap()
);
assert!(tri.validate_geometric_simplex_orientation().is_ok());
}
#[test]
fn periodic_geometric_orientation_validation_uses_lifted_coordinates() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([0.8, 0.0]).unwrap(),
vertex!([0.0, 0.8]).unwrap(),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
tds.simplex_mut(simplex_key)
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 0], [0, 0], [1, 0]])
.unwrap();
let mut tri =
Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
tri.global_topology =
GlobalTopology::try_toroidal([1.0, 1.0], ToroidalConstructionMode::PeriodicImagePoint)
.unwrap();
assert!(tri.validate_geometric_simplex_orientation().is_ok());
tri.tds
.simplex_mut(simplex_key)
.unwrap()
.swap_vertex_slots(0, 1);
let err = tri.validate_geometric_simplex_orientation().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::NegativeSimplexOrientation {
simplex_key: key, dimension: 2, ..
} if key == simplex_key
);
}
#[test]
fn periodic_geometric_orientation_validation_requires_toroidal_metadata() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([0.8, 0.0]).unwrap(),
vertex!([0.0, 0.8]).unwrap(),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
tds.simplex_mut(simplex_key)
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 0], [0, 0], [1, 0]])
.unwrap();
let tri = Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
let err = tri.validate_geometric_simplex_orientation().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::Tds { source }
if matches!(source.as_ref(), TdsError::InconsistentDataStructure { message }
if message.contains("has periodic offsets")
&& message.contains("expected periodic-orientation-offset-capable topology")
)
);
}
#[test]
fn periodic_geometric_orientation_validation_rejects_offset_count_mismatch() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([0.8, 0.0]).unwrap(),
vertex!([0.0, 0.8]).unwrap(),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
tds.simplex_mut(simplex_key)
.unwrap()
.periodic_vertex_offsets = Some(vec![[0, 0], [1, 0]].into());
let tri = Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
let err = tri.validate_geometric_simplex_orientation().unwrap_err();
assert_matches!(
err,
TriangulationRealizationValidationError::Tds { source }
if matches!(source.as_ref(), TdsError::DimensionMismatch {
expected: 3,
actual: 2,
..
})
);
}
#[test]
fn periodic_geometric_orientation_rejects_invalid_domain_at_parse_boundary() {
let err = GlobalTopology::<2>::try_toroidal(
[0.0, 1.0],
ToroidalConstructionMode::PeriodicImagePoint,
)
.unwrap_err();
assert_matches!(
err,
ToroidalDomainError::InvalidPeriod { axis: 0, period }
if period.abs() < f64::EPSILON
);
}
#[test]
fn periodic_orientation_preserves_lift_overflow() {
let vertices = [
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let mut tds =
Triangulation::<FastKernel<f64>, (), (), 2>::build_initial_simplex(&vertices).unwrap();
let simplex_key = tds.simplex_keys().next().unwrap();
tds.simplex_mut(simplex_key)
.unwrap()
.set_periodic_vertex_offsets(vec![[0, 2], [0, 0], [0, 0]])
.unwrap();
let mut tri =
Triangulation::<FastKernel<f64>, (), (), 2>::new_with_tds(FastKernel::new(), tds);
tri.global_topology = GlobalTopology::try_toroidal(
[1.0, f64::MAX],
ToroidalConstructionMode::PeriodicImagePoint,
)
.unwrap();
let simplex = tri.tds.simplex(simplex_key).unwrap();
let simplex_uuid = simplex.uuid();
let vertex_key = simplex.vertices()[0];
let vertex_uuid = tri.tds.vertex(vertex_key).unwrap().uuid();
let error = tri
.canonicalize_positive_orientation_for_simplices(&vec![simplex_key].into())
.unwrap_err();
assert!(!error.is_retryable());
assert_matches!(&error, InsertionError::OrientationValidationFailed {
source: TriangulationRealizationValidationError::TopologyLifting {
simplex_key: key, simplex_uuid: uuid, vertex_key: vertex, vertex_uuid: id, ..
},
} if *key == simplex_key && *uuid == simplex_uuid && *vertex == vertex_key && *id == vertex_uuid);
let mut cause: &dyn std::error::Error = &error;
let lift = loop {
if let Some(source) = cause.downcast_ref::<GlobalTopologyModelError>() {
break source;
}
cause = cause
.source()
.expect("orientation failure must retain its typed lift cause");
};
assert_matches!(lift, GlobalTopologyModelError::NonFiniteCoordinate { axis: 1, value } if value.is_infinite());
}
}