#![forbid(unsafe_code)]
pub use crate::core::algorithms::flips::{
BistellarFlipKind, BistellarFlipKindError, BistellarMove, ConstK, DelaunayRepairDiagnostics,
DelaunayRepairError, DelaunayRepairHeuristicRebuildFailure,
DelaunayRepairHeuristicRebuildFailureKind, DelaunayRepairHeuristicVertexContext,
DelaunayRepairOrientationCanonicalizationFailure,
DelaunayRepairOrientationCanonicalizationFailureKind, DelaunayRepairPostconditionFailure,
DelaunayRepairStats, DelaunayRepairVerificationContext, FlipContextError, FlipDirection,
FlipEdgeAdjacencyError, FlipError, FlipFailureKind, FlipFeasibility, FlipInfo,
FlipMutationError, FlipNeighborCavityFailureKind, FlipNeighborDelaunayValidationFailureKind,
FlipNeighborHullExtensionFailureKind, FlipNeighborRepairDiagnostics, FlipNeighborRepairFailure,
FlipNeighborWiringError, FlipOrientationCheckStage, FlipPredicateError, FlipPredicateOperation,
FlipTriangleAdjacencyError, FlipVertexAdjacencyError, RepairQueueOrder, RidgeHandle,
TriangleHandle, TriangleHandleError,
};
pub use crate::tds::{EdgeKey, FacetHandle, SimplexKey, VertexKey};
use crate::core::algorithms::flips::{
apply_bistellar_flip_dynamic_raw, apply_bistellar_flip_k1_inverse_raw,
apply_bistellar_flip_k1_raw, apply_bistellar_flip_raw, build_k2_flip_context,
build_k2_flip_context_from_edge, build_k3_flip_context, build_k3_flip_context_from_triangle,
validate_bistellar_flip_dynamic, validate_bistellar_flip_k1_insert,
validate_bistellar_flip_k1_inverse, validate_bistellar_flip_k2, validate_bistellar_flip_k3,
};
use crate::core::operations::SuspicionFlags;
use crate::core::operations::TopologicalOperation;
use crate::core::traits::data_type::DataType;
use crate::core::vertex::Vertex;
use crate::geometry::kernel::Kernel;
use crate::triangulation::Triangulation;
use crate::triangulation::rollback::TriangulationRollbackTransaction;
fn apply_realized_flip<K, U, V, const D: usize>(
tri: &mut Triangulation<K, U, V, D>,
operation: TopologicalOperation,
apply: impl FnOnce(&mut Triangulation<K, U, V, D>) -> Result<FlipInfo<D>, FlipError>,
) -> Result<FlipInfo<D>, FlipError>
where
K: Kernel<D, Scalar = f64>,
U: DataType,
V: DataType,
{
validate_flip_topology(tri, operation)?;
let mut transaction = TriangulationRollbackTransaction::begin(tri);
let result = apply(transaction.triangulation_mut());
let info = match result {
Ok(info) => info,
Err(error) => {
transaction.rollback();
return Err(error);
}
};
if let Err(error) = transaction
.triangulation_mut()
.normalize_and_promote_positive_orientation()
{
transaction.rollback();
return Err(FlipError::PostconditionRepair {
source: Box::new(error),
});
}
if info.new_simplices.is_empty() {
if let Err(source) = transaction.triangulation_mut().validate() {
transaction.rollback();
return Err(FlipError::InvariantValidation {
source: Box::new(source),
});
}
if let Err(source) = transaction.triangulation_mut().is_valid_realization() {
transaction.rollback();
return Err(FlipError::RealizationValidation {
source: Box::new(source),
});
}
} else {
if let Err(source) = transaction
.triangulation_mut()
.validate_mandatory_mutation_postconditions_for_simplices(&info.new_simplices)
{
transaction.rollback();
return Err(FlipError::InvariantValidation {
source: Box::new(source),
});
}
if let Err(source) = transaction
.triangulation_mut()
.validate_realization_for_simplices(&info.new_simplices)
{
transaction.rollback();
return Err(FlipError::RealizationValidation {
source: Box::new(source),
});
}
let run_global_audit = transaction
.triangulation_mut()
.validation_policy()
.should_validate(SuspicionFlags::default());
if run_global_audit {
if let Err(source) = transaction.triangulation_mut().validate() {
transaction.rollback();
return Err(FlipError::InvariantValidation {
source: Box::new(source),
});
}
if let Err(source) = transaction.triangulation_mut().is_valid_realization() {
transaction.rollback();
return Err(FlipError::RealizationValidation {
source: Box::new(source),
});
}
}
}
transaction.commit();
Ok(info)
}
pub(crate) fn validate_flip_topology<K, U, V, const D: usize>(
tri: &Triangulation<K, U, V, D>,
operation: TopologicalOperation,
) -> Result<(), FlipError> {
let found = tri.topology_guarantee();
if operation.is_admissible_under(found) {
Ok(())
} else {
Err(FlipError::FlipTopologyNotAdmissible {
required: operation.required_topology(),
found,
})
}
}
pub trait BistellarFlips<const D: usize> {
type VertexData;
fn flip_k1_insert(
&mut self,
simplex_key: SimplexKey,
vertex: Vertex<Self::VertexData, D>,
) -> Result<FlipInfo<D>, FlipError>;
fn can_flip_k1_insert(
&self,
simplex_key: SimplexKey,
vertex: &Vertex<Self::VertexData, D>,
) -> Result<FlipFeasibility<D>, FlipError>;
fn flip_k1_remove(&mut self, vertex_key: VertexKey) -> Result<FlipInfo<D>, FlipError>;
fn can_flip_k1_remove(&self, vertex_key: VertexKey) -> Result<FlipFeasibility<D>, FlipError>;
fn flip_k2(&mut self, facet: FacetHandle) -> Result<FlipInfo<D>, FlipError>;
fn can_flip_k2(&self, facet: FacetHandle) -> Result<FlipFeasibility<D>, FlipError>;
fn flip_k3(&mut self, ridge: RidgeHandle) -> Result<FlipInfo<D>, FlipError>;
fn can_flip_k3(&self, ridge: RidgeHandle) -> Result<FlipFeasibility<D>, FlipError>;
fn flip_k2_inverse_from_edge(&mut self, edge: EdgeKey) -> Result<FlipInfo<D>, FlipError>;
fn can_flip_k2_inverse_from_edge(&self, edge: EdgeKey)
-> Result<FlipFeasibility<D>, FlipError>;
fn flip_k3_inverse_from_triangle(
&mut self,
triangle: TriangleHandle,
) -> Result<FlipInfo<D>, FlipError>;
fn can_flip_k3_inverse_from_triangle(
&self,
triangle: TriangleHandle,
) -> Result<FlipFeasibility<D>, FlipError>;
}
impl<K, U, V, const D: usize> BistellarFlips<D> for Triangulation<K, U, V, D>
where
K: Kernel<D, Scalar = f64>,
U: DataType,
V: DataType,
{
type VertexData = U;
fn flip_k1_insert(
&mut self,
simplex_key: SimplexKey,
vertex: Vertex<U, D>,
) -> Result<FlipInfo<D>, FlipError> {
let _ = self.can_flip_k1_insert(simplex_key, &vertex)?;
apply_realized_flip(self, TopologicalOperation::InsertVertex, |tri| {
apply_bistellar_flip_k1_raw(&mut tri.tds, simplex_key, vertex)
})
}
fn can_flip_k1_insert(
&self,
simplex_key: SimplexKey,
vertex: &Vertex<U, D>,
) -> Result<FlipFeasibility<D>, FlipError> {
validate_flip_topology(self, TopologicalOperation::InsertVertex)?;
let topology_model = self.global_topology.model();
validate_bistellar_flip_k1_insert(&self.tds, &topology_model, simplex_key, vertex)
}
fn flip_k1_remove(&mut self, vertex_key: VertexKey) -> Result<FlipInfo<D>, FlipError> {
apply_realized_flip(self, TopologicalOperation::DeleteVertex, |tri| {
apply_bistellar_flip_k1_inverse_raw(&mut tri.tds, vertex_key)
})
}
fn can_flip_k1_remove(&self, vertex_key: VertexKey) -> Result<FlipFeasibility<D>, FlipError> {
validate_flip_topology(self, TopologicalOperation::DeleteVertex)?;
validate_bistellar_flip_k1_inverse(&self.tds, vertex_key)
}
fn flip_k2(&mut self, facet: FacetHandle) -> Result<FlipInfo<D>, FlipError> {
apply_realized_flip(self, TopologicalOperation::FacetFlip, |tri| {
let context = build_k2_flip_context(&tri.tds, facet)?;
apply_bistellar_flip_raw::<U, V, D, 2>(&mut tri.tds, &context)
})
}
fn can_flip_k2(&self, facet: FacetHandle) -> Result<FlipFeasibility<D>, FlipError> {
validate_flip_topology(self, TopologicalOperation::FacetFlip)?;
let context = build_k2_flip_context(&self.tds, facet)?;
validate_bistellar_flip_k2(&self.tds, &context)
}
fn flip_k3(&mut self, ridge: RidgeHandle) -> Result<FlipInfo<D>, FlipError> {
apply_realized_flip(self, TopologicalOperation::CavityFlip, |tri| {
let context = build_k3_flip_context(&tri.tds, ridge)?;
apply_bistellar_flip_raw::<U, V, D, 3>(&mut tri.tds, &context)
})
}
fn can_flip_k3(&self, ridge: RidgeHandle) -> Result<FlipFeasibility<D>, FlipError> {
validate_flip_topology(self, TopologicalOperation::CavityFlip)?;
let context = build_k3_flip_context(&self.tds, ridge)?;
validate_bistellar_flip_k3(&self.tds, &context)
}
fn flip_k2_inverse_from_edge(&mut self, edge: EdgeKey) -> Result<FlipInfo<D>, FlipError> {
apply_realized_flip(self, TopologicalOperation::CavityFlip, |tri| {
let context = build_k2_flip_context_from_edge(&tri.tds, edge)?;
apply_bistellar_flip_dynamic_raw(&mut tri.tds, D, &context)
})
}
fn can_flip_k2_inverse_from_edge(
&self,
edge: EdgeKey,
) -> Result<FlipFeasibility<D>, FlipError> {
validate_flip_topology(self, TopologicalOperation::CavityFlip)?;
let context = build_k2_flip_context_from_edge(&self.tds, edge)?;
validate_bistellar_flip_dynamic(&self.tds, D, &context)
}
fn flip_k3_inverse_from_triangle(
&mut self,
triangle: TriangleHandle,
) -> Result<FlipInfo<D>, FlipError> {
if D < 4 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
apply_realized_flip(self, TopologicalOperation::CavityFlip, |tri| {
let context = build_k3_flip_context_from_triangle(&tri.tds, triangle)?;
let k_move = D
.checked_sub(1)
.ok_or(FlipError::UnsupportedDimension { dimension: D })?;
apply_bistellar_flip_dynamic_raw(&mut tri.tds, k_move, &context)
})
}
fn can_flip_k3_inverse_from_triangle(
&self,
triangle: TriangleHandle,
) -> Result<FlipFeasibility<D>, FlipError> {
if D < 4 {
return Err(FlipError::UnsupportedDimension { dimension: D });
}
validate_flip_topology(self, TopologicalOperation::CavityFlip)?;
let context = build_k3_flip_context_from_triangle(&self.tds, triangle)?;
let k_move = D
.checked_sub(1)
.ok_or(FlipError::UnsupportedDimension { dimension: D })?;
validate_bistellar_flip_dynamic(&self.tds, k_move, &context)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DelaunayTriangulation;
use crate::core::facet::FacetError;
use crate::core::tds::InvariantError;
use crate::triangulation::validation::TopologyConstructionProvenance;
use crate::vertex;
use std::assert_matches;
use crate::TopologyGuarantee;
use crate::core::collections::{SimplexKeyBuffer, SmallBuffer};
use crate::geometry::kernel::{AdaptiveKernel, FastKernel};
use slotmap::KeyData;
#[test]
fn triangulation_flip_k1_insert_and_remove_roundtrip() {
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 dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::builder(&vertices)
.topology_guarantee(TopologyGuarantee::PLManifold)
.build()
.unwrap();
let mut tri = dt.into_triangulation();
let simplex_key = tri.simplices().next().unwrap().0;
let candidate = vertex!([0.25, 0.25, 0.25]).unwrap();
let feasibility = tri
.can_flip_k1_insert(simplex_key, &candidate)
.expect("interior k=1 insertion should pass preflight");
let inserted = tri.flip_k1_insert(simplex_key, candidate).unwrap();
let inserted_vertex = inserted.inserted_face_vertices[0];
assert_eq!(feasibility.kind, inserted.kind);
assert_eq!(feasibility.direction, inserted.direction);
assert_eq!(feasibility.removed_simplices, inserted.removed_simplices);
assert_eq!(
feasibility.removed_face_vertices,
inserted.removed_face_vertices
);
assert!(!inserted.new_simplices.is_empty());
assert!(tri.validate().is_ok());
assert!(tri.validate_realization().is_ok());
let removed = tri.flip_k1_remove(inserted_vertex).unwrap();
assert!(!removed.removed_simplices.is_empty());
assert!(tri.validate().is_ok());
assert!(tri.validate_realization().is_ok());
}
#[test]
fn triangulation_flip_k1_insert_rolls_back_degenerate_insert() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::builder(&vertices)
.topology_guarantee(TopologyGuarantee::PLManifold)
.build()
.unwrap();
let mut tri = dt.into_triangulation();
let simplex_key = tri.simplices().next().unwrap().0;
let before_vertices = tri.tds.number_of_vertices();
let before_simplices = tri.tds.number_of_simplices();
let inserted = vertex!([0.5, 0.0]).unwrap();
let inserted_uuid = inserted.uuid();
let preflight_err = tri.can_flip_k1_insert(simplex_key, &inserted).unwrap_err();
assert_matches!(preflight_err, FlipError::DegenerateSimplex);
assert_eq!(tri.tds.number_of_vertices(), before_vertices);
assert_eq!(tri.tds.number_of_simplices(), before_simplices);
let err = tri.flip_k1_insert(simplex_key, inserted).unwrap_err();
assert_matches!(err, FlipError::DegenerateSimplex);
assert_eq!(tri.tds.number_of_vertices(), before_vertices);
assert_eq!(tri.tds.number_of_simplices(), before_simplices);
assert!(tri.vertex_key_from_uuid(&inserted_uuid).is_none());
assert!(tri.validate().is_ok());
assert!(tri.is_valid_realization().is_ok());
}
#[test]
fn triangulation_flip_k1_insert_rejects_exterior_point_before_mutation() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::builder(&vertices)
.topology_guarantee(TopologyGuarantee::PLManifold)
.build()
.unwrap();
let mut tri = dt.into_triangulation();
let simplex_key = tri.simplices().next().unwrap().0;
let expected_opposite_index = tri
.simplex(simplex_key)
.unwrap()
.vertices()
.iter()
.position(|&vertex_key| *tri.vertex(vertex_key).unwrap().point().coords() == [0.0, 0.0])
.expect("fixture simplex should contain the origin");
let expected_opposite_vertex =
tri.simplex(simplex_key).unwrap().vertices()[expected_opposite_index];
let inserted = vertex!([0.75, 0.75]).unwrap();
let inserted_uuid = inserted.uuid();
let before = tri.tds.clone();
let preflight_err = tri.can_flip_k1_insert(simplex_key, &inserted).unwrap_err();
assert_eq!(
FlipFailureKind::from(&preflight_err),
FlipFailureKind::K1InsertionOutsideSimplex
);
assert_matches!(
preflight_err,
FlipError::K1InsertionOutsideSimplex {
simplex_key: rejected,
opposite_vertex,
opposite_vertex_index,
} if rejected == simplex_key
&& opposite_vertex == expected_opposite_vertex
&& opposite_vertex_index == expected_opposite_index
);
assert_eq!(tri.tds, before);
let commit_err = tri.flip_k1_insert(simplex_key, inserted).unwrap_err();
assert_matches!(
commit_err,
FlipError::K1InsertionOutsideSimplex {
simplex_key: rejected,
opposite_vertex,
opposite_vertex_index,
} if rejected == simplex_key
&& opposite_vertex == expected_opposite_vertex
&& opposite_vertex_index == expected_opposite_index
);
assert_eq!(tri.tds, before);
assert!(tri.vertex_key_from_uuid(&inserted_uuid).is_none());
assert!(tri.validate().is_ok());
assert!(tri.is_valid_realization().is_ok());
}
#[test]
fn realized_flip_transaction_rolls_back_topology_validation_failure() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::builder(&vertices)
.topology_guarantee(TopologyGuarantee::PLManifold)
.build()
.unwrap();
let mut tri = dt.into_triangulation();
tri.validate()
.expect("pre-move topology fixture should be valid");
tri.validate_realization()
.expect("pre-move realization fixture should be valid");
let before = tri.tds.clone();
let owner_before = tri.tds.topology_owner_id();
let generation_before = tri.tds.generation();
let provenance_before = tri.topology_construction_provenance;
let err = apply_realized_flip(&mut tri, TopologicalOperation::InsertVertex, |tri| {
tri.tds
.insert_vertex_with_mapping(vertex!([2.0, 2.0]).unwrap())
.unwrap();
tri.topology_construction_provenance = TopologyConstructionProvenance::Unproven;
Ok(FlipInfo {
kind: BistellarFlipKind::try_k1(2).unwrap(),
direction: FlipDirection::Forward,
removed_simplices: SimplexKeyBuffer::default(),
new_simplices: SimplexKeyBuffer::default(),
removed_face_vertices: SmallBuffer::default(),
inserted_face_vertices: SmallBuffer::default(),
})
})
.unwrap_err();
assert_matches!(
err,
FlipError::InvariantValidation { source }
if matches!(*source, InvariantError::Triangulation { source: _ })
);
assert_eq!(tri.tds, before);
assert_eq!(tri.tds.topology_owner_id(), owner_before);
assert_eq!(tri.tds.generation(), generation_before);
assert_eq!(tri.topology_construction_provenance, provenance_before);
assert!(tri.validate().is_ok());
assert!(tri.validate_realization().is_ok());
}
#[test]
fn flip_k1_insert_requires_explicit_delaunay_demotion() {
let vertices: Vec<Vertex<(), 3>> = 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 dt = DelaunayTriangulation::builder(&vertices).build().unwrap();
let mut tri = dt.into_triangulation();
let simplex_key = tri.simplices().next().unwrap().0;
tri.flip_k1_insert(simplex_key, vertex!([0.2, 0.2, 0.2]).unwrap())
.unwrap();
assert!(tri.validate().is_ok());
assert!(tri.validate_realization().is_ok());
}
#[test]
fn facet_flips_require_a_pl_manifold_proof_before_mutation() {
let vertices = vec![
vertex!([0.0, 0.0]).unwrap(),
vertex!([1.0, 0.0]).unwrap(),
vertex!([0.0, 1.0]).unwrap(),
];
let dt: DelaunayTriangulation<_, (), (), 2> = DelaunayTriangulation::builder(&vertices)
.topology_guarantee(TopologyGuarantee::Pseudomanifold)
.build()
.unwrap();
let mut tri = dt.into_triangulation();
let simplex_key = tri.simplices().next().unwrap().0;
let facet = tri.facet_handle(simplex_key, 0).unwrap();
let before = tri.tds.clone();
let preflight_error = tri.can_flip_k2(facet).unwrap_err();
assert_matches!(
preflight_error,
FlipError::FlipTopologyNotAdmissible {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
}
);
let mutation_error = tri.flip_k2(facet).unwrap_err();
assert_matches!(
mutation_error,
FlipError::FlipTopologyNotAdmissible {
required: TopologyGuarantee::PLManifold,
found: TopologyGuarantee::Pseudomanifold,
}
);
assert_eq!(tri.tds, before);
}
#[test]
fn triangulation_flip_k2_rejects_invalid_facet_index() {
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 dt: DelaunayTriangulation<_, (), (), 3> = DelaunayTriangulation::builder(&vertices)
.topology_guarantee(TopologyGuarantee::PLManifold)
.build()
.unwrap();
let tri = dt.into_triangulation();
let simplex_key = tri.simplices().next().unwrap().0;
let err = tri.facet_handle(simplex_key, u8::MAX).unwrap_err();
assert_matches!(
err,
FacetError::InvalidFacetIndex {
index: u8::MAX,
facet_count: 4,
}
);
}
#[test]
fn triangulation_flip_k3_inverse_rejects_unsupported_dimension() {
let mut tri: Triangulation<AdaptiveKernel<f64>, (), (), 3> =
Triangulation::new_empty(AdaptiveKernel::new());
let a = VertexKey::from(KeyData::from_ffi(1));
let b = VertexKey::from(KeyData::from_ffi(2));
let c = VertexKey::from(KeyData::from_ffi(3));
let err = tri
.flip_k3_inverse_from_triangle(TriangleHandle::try_new(a, b, c).unwrap())
.unwrap_err();
assert_matches!(err, FlipError::UnsupportedDimension { dimension: 3 });
}
#[test]
fn triangulation_can_flip_k3_inverse_prioritizes_unsupported_dimension() {
let tri: Triangulation<AdaptiveKernel<f64>, (), (), 3> =
Triangulation::new_empty(AdaptiveKernel::new());
let a = VertexKey::from(KeyData::from_ffi(1));
let b = VertexKey::from(KeyData::from_ffi(2));
let c = VertexKey::from(KeyData::from_ffi(3));
let error = tri
.can_flip_k3_inverse_from_triangle(TriangleHandle::try_new(a, b, c).unwrap())
.expect_err("3D inverse k=3 feasibility must reject the dimension first");
assert_matches!(error, FlipError::UnsupportedDimension { dimension: 3 });
}
#[test]
fn triangulation_flip_k3_inverse_rejects_zero_dimension_without_underflow() {
let mut tri: Triangulation<FastKernel<f64>, (), (), 0> =
Triangulation::new_empty(FastKernel::new());
let a = VertexKey::from(KeyData::from_ffi(1));
let b = VertexKey::from(KeyData::from_ffi(2));
let c = VertexKey::from(KeyData::from_ffi(3));
let err = tri
.flip_k3_inverse_from_triangle(TriangleHandle::try_new(a, b, c).unwrap())
.unwrap_err();
assert_matches!(err, FlipError::UnsupportedDimension { dimension: 0 });
}
}