#![forbid(unsafe_code)]
pub use crate::core::algorithms::flips::{DelaunayRepairError, DelaunayRepairStats};
pub use crate::core::algorithms::pl_manifold_repair::{
PlManifoldRepairError, PlManifoldRepairStats,
};
pub use crate::core::cell::CellValidationError;
pub use crate::triangulation::delaunay::DelaunayTriangulationConstructionError;
#[cfg(test)]
use crate::core::algorithms::flips::{DelaunayRepairDiagnostics, RepairQueueOrder};
use crate::core::algorithms::pl_manifold_repair::{
PlManifoldRepairConfig, repair_facet_oversharing,
};
use crate::core::cell::Cell;
use crate::core::collections::{CellVertexUuidBuffer, Entry, FastHashMap};
use crate::core::tds::{CellKey, Tds};
use crate::core::traits::data_type::DataType;
use crate::core::vertex::Vertex;
use crate::geometry::kernel::{ExactPredicates, Kernel};
use crate::geometry::traits::coordinate::CoordinateScalar;
use crate::triangulation::delaunay::{DelaunayRepairHeuristicConfig, DelaunayTriangulation};
use thiserror::Error;
#[cfg(test)]
mod test_hooks {
use super::*;
use std::cell::Cell as ThreadCell;
thread_local! {
static FORCE_DELAUNAY_REPAIR_FAILURE: ThreadCell<bool> = const { ThreadCell::new(false) };
}
pub(super) fn set_force_delaunay_repair_failure(enabled: bool) -> bool {
FORCE_DELAUNAY_REPAIR_FAILURE.with(|flag| {
let prior = flag.get();
flag.set(enabled);
prior
})
}
pub(super) fn restore_force_delaunay_repair_failure(prior: bool) {
FORCE_DELAUNAY_REPAIR_FAILURE.with(|flag| flag.set(prior));
}
pub(super) fn force_delaunay_repair_failure_enabled() -> bool {
FORCE_DELAUNAY_REPAIR_FAILURE.with(ThreadCell::get)
}
pub(super) fn synthetic_repair_error() -> DelaunayRepairError {
DelaunayRepairError::NonConvergent {
max_flips: 0,
diagnostics: Box::new(DelaunayRepairDiagnostics {
facets_checked: 1,
flips_performed: 0,
max_queue_len: 1,
ambiguous_predicates: 0,
ambiguous_predicate_samples: Vec::new(),
predicate_failures: 0,
cycle_detections: 0,
cycle_signature_samples: Vec::new(),
attempt: 1,
queue_order: RepairQueueOrder::Fifo,
}),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DelaunayizeConfig {
pub topology_max_iterations: usize,
pub topology_max_cells_removed: usize,
pub fallback_rebuild: bool,
pub delaunay_max_flips: Option<usize>,
}
impl Default for DelaunayizeConfig {
fn default() -> Self {
Self {
topology_max_iterations: 64,
topology_max_cells_removed: 10_000,
fallback_rebuild: false,
delaunay_max_flips: None,
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct DelaunayizeOutcome<T, U, V, const D: usize> {
pub topology_repair: PlManifoldRepairStats<T, U, V, D>,
pub delaunay_repair: DelaunayRepairStats,
pub used_fallback_rebuild: bool,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum DelaunayizeError {
#[error("Topology repair failed: {source}")]
TopologyRepairFailed {
#[from]
#[source]
source: PlManifoldRepairError,
},
#[error("Topology repair failed ({source}); fallback rebuild also failed: {rebuild_error}")]
TopologyRepairFailedWithRebuild {
#[source]
source: PlManifoldRepairError,
rebuild_error: DelaunayTriangulationConstructionError,
},
#[error(
"Topology repair failed ({source}); fallback rebuild succeeded but cell-data restore failed: {restore_error}"
)]
TopologyRepairFailedWithRebuildRestore {
#[source]
source: PlManifoldRepairError,
restore_error: CellValidationError,
},
#[error("Delaunay repair failed: {source}")]
DelaunayRepairFailed {
#[from]
#[source]
source: DelaunayRepairError,
},
#[error(
"Delaunay repair failed ({source}); fallback cell-data snapshot failed: {snapshot_error}"
)]
DelaunayRepairFailedWithCellDataSnapshot {
#[source]
source: DelaunayRepairError,
snapshot_error: CellValidationError,
},
#[error("Delaunay repair failed ({source}); fallback rebuild also failed: {rebuild_error}")]
DelaunayRepairFailedWithRebuild {
#[source]
source: DelaunayRepairError,
rebuild_error: DelaunayTriangulationConstructionError,
},
#[error(
"Delaunay repair failed ({source}); fallback rebuild succeeded but cell-data restore failed: {restore_error}"
)]
DelaunayRepairFailedWithRebuildRestore {
#[source]
source: DelaunayRepairError,
restore_error: CellValidationError,
},
#[error("Fallback cell-data snapshot failed before repair; no repair attempted: {source}")]
FallbackCellDataSnapshotFailed {
#[from]
#[source]
source: CellValidationError,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum CellDataMatch<V> {
Unique(Option<V>),
Ambiguous,
}
type CellDataByVertexUuids<V> = FastHashMap<CellVertexUuidBuffer, CellDataMatch<V>>;
type FallbackRebuildState<T, U, V, const D: usize> =
(Vec<Vertex<T, U, D>>, CellDataByVertexUuids<V>);
fn snapshot_rebuild_state<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
) -> Result<FallbackRebuildState<T, U, V, D>, CellValidationError>
where
T: CoordinateScalar,
U: DataType,
V: DataType,
{
let vertices = tds
.vertices()
.map(|(_, v)| Vertex::new_with_uuid(*v.point(), v.uuid(), v.data))
.collect::<Vec<_>>();
let cell_data = collect_cell_data(tds)?;
Ok((vertices, cell_data))
}
fn collect_cell_data<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
) -> Result<CellDataByVertexUuids<V>, CellValidationError>
where
U: DataType,
V: DataType,
{
let mut cell_data = FastHashMap::default();
for (_, cell) in tds.cells() {
let vertex_uuids = cell_vertex_uuids(tds, cell)?;
match cell_data.entry(vertex_uuids) {
Entry::Vacant(entry) => {
entry.insert(CellDataMatch::Unique(cell.data().copied()));
}
Entry::Occupied(mut entry) => {
entry.insert(CellDataMatch::Ambiguous);
}
}
}
Ok(cell_data)
}
fn cell_vertex_uuids<T, U, V, const D: usize>(
tds: &Tds<T, U, V, D>,
cell: &Cell<T, U, V, D>,
) -> Result<CellVertexUuidBuffer, CellValidationError>
where
U: DataType,
V: DataType,
{
let mut vertex_uuids = cell
.vertex_uuid_iter(tds)
.collect::<Result<CellVertexUuidBuffer, CellValidationError>>()?;
vertex_uuids.sort_unstable();
Ok(vertex_uuids)
}
fn restore_cell_data<K, U, V, const D: usize>(
rebuilt: &mut DelaunayTriangulation<K, U, V, D>,
original_cell_data: &CellDataByVertexUuids<V>,
) -> Result<(), CellValidationError>
where
K: Kernel<D>,
U: DataType,
V: DataType,
{
let mut assignments: Vec<(CellKey, V)> = Vec::new();
for (cell_key, cell) in rebuilt.cells() {
let vertex_uuids = cell_vertex_uuids(rebuilt.tds(), cell)?;
let Some(CellDataMatch::Unique(Some(data))) = original_cell_data.get(&vertex_uuids) else {
continue;
};
assignments.push((cell_key, *data));
}
for (cell_key, data) in assignments {
rebuilt.set_cell_data(cell_key, Some(data));
}
Ok(())
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
enum FallbackRebuildError {
#[error("fallback rebuild failed: {source}")]
Construction {
#[from]
#[source]
source: DelaunayTriangulationConstructionError,
},
#[error("fallback cell-data restore failed: {source}")]
Restore {
#[from]
#[source]
source: CellValidationError,
},
}
fn topology_rebuild_error(
source: PlManifoldRepairError,
fallback_error: FallbackRebuildError,
) -> DelaunayizeError {
match fallback_error {
FallbackRebuildError::Construction {
source: rebuild_error,
} => DelaunayizeError::TopologyRepairFailedWithRebuild {
source,
rebuild_error,
},
FallbackRebuildError::Restore {
source: restore_error,
} => DelaunayizeError::TopologyRepairFailedWithRebuildRestore {
source,
restore_error,
},
}
}
fn delaunay_rebuild_error(
source: DelaunayRepairError,
fallback_error: FallbackRebuildError,
) -> DelaunayizeError {
match fallback_error {
FallbackRebuildError::Construction {
source: rebuild_error,
} => DelaunayizeError::DelaunayRepairFailedWithRebuild {
source,
rebuild_error,
},
FallbackRebuildError::Restore {
source: restore_error,
} => DelaunayizeError::DelaunayRepairFailedWithRebuildRestore {
source,
restore_error,
},
}
}
fn rebuild_preserving_data<K, U, V, const D: usize>(
kernel: &K,
vertices: &[Vertex<K::Scalar, U, D>],
original_cell_data: &CellDataByVertexUuids<V>,
) -> Result<DelaunayTriangulation<K, U, V, D>, FallbackRebuildError>
where
K: ExactPredicates<D>,
U: DataType,
V: DataType,
{
let mut rebuilt = DelaunayTriangulation::with_kernel(kernel, vertices)?;
restore_cell_data(&mut rebuilt, original_cell_data)?;
Ok(rebuilt)
}
fn run_configured_delaunay_repair<K, U, V, const D: usize>(
dt: &mut DelaunayTriangulation<K, U, V, D>,
config: DelaunayizeConfig,
) -> Result<DelaunayRepairStats, DelaunayRepairError>
where
K: ExactPredicates<D>,
U: DataType,
V: DataType,
{
if let Some(max_flips) = config.delaunay_max_flips {
dt.repair_delaunay_with_flips_advanced(DelaunayRepairHeuristicConfig {
max_flips: Some(max_flips),
..DelaunayRepairHeuristicConfig::default()
})
.map(|outcome| outcome.stats)
} else {
dt.repair_delaunay_with_flips()
}
}
#[expect(
clippy::result_large_err,
reason = "DelaunayizeError preserves typed source and rebuild_error values on the *WithRebuild variants (no boxing) so callers can pattern-match both errors while Error::source exposes the primary repair error; this is a cold error path."
)]
pub fn delaunayize_by_flips<K, U, V, const D: usize>(
dt: &mut DelaunayTriangulation<K, U, V, D>,
config: DelaunayizeConfig,
) -> Result<DelaunayizeOutcome<K::Scalar, U, V, D>, DelaunayizeError>
where
K: ExactPredicates<D>,
U: DataType,
V: DataType,
{
let pl_config = PlManifoldRepairConfig {
max_iterations: config.topology_max_iterations,
max_cells_removed: config.topology_max_cells_removed,
};
let topology_stats = match repair_facet_oversharing(dt.tds_mut_for_repair(), &pl_config) {
Ok(stats) => stats,
Err(topo_err) if config.fallback_rebuild => {
let tds = &dt.as_triangulation().tds;
let (vertices, cell_data) = match snapshot_rebuild_state(tds) {
Ok(state) => state,
Err(snapshot_error) => {
return Err(DelaunayizeError::FallbackCellDataSnapshotFailed {
source: snapshot_error,
});
}
};
match rebuild_preserving_data(&dt.as_triangulation().kernel, &vertices, &cell_data) {
Ok(rebuilt) => {
*dt = rebuilt;
return Ok(DelaunayizeOutcome {
topology_repair: PlManifoldRepairStats::default(),
delaunay_repair: DelaunayRepairStats::default(),
used_fallback_rebuild: true,
});
}
Err(fallback_error) => {
return Err(topology_rebuild_error(topo_err, fallback_error));
}
}
}
Err(topo_err) => return Err(topo_err.into()),
};
#[cfg(test)]
let delaunay_result = if test_hooks::force_delaunay_repair_failure_enabled() {
Err(test_hooks::synthetic_repair_error())
} else {
run_configured_delaunay_repair(dt, config)
};
#[cfg(not(test))]
let delaunay_result = run_configured_delaunay_repair(dt, config);
match delaunay_result {
Ok(delaunay_stats) => Ok(DelaunayizeOutcome {
topology_repair: topology_stats,
delaunay_repair: delaunay_stats,
used_fallback_rebuild: false,
}),
Err(repair_err) => {
if config.fallback_rebuild {
let tds = &dt.as_triangulation().tds;
let (vertices, cell_data) = match snapshot_rebuild_state(tds) {
Ok(state) => state,
Err(snapshot_error) => {
return Err(DelaunayizeError::DelaunayRepairFailedWithCellDataSnapshot {
source: repair_err,
snapshot_error,
});
}
};
match rebuild_preserving_data(&dt.as_triangulation().kernel, &vertices, &cell_data)
{
Ok(rebuilt) => {
*dt = rebuilt;
Ok(DelaunayizeOutcome {
topology_repair: topology_stats,
delaunay_repair: DelaunayRepairStats::default(),
used_fallback_rebuild: true,
})
}
Err(fallback_error) => Err(delaunay_rebuild_error(repair_err, fallback_error)),
}
} else {
Err(DelaunayizeError::from(repair_err))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::{tds::VertexKey, triangulation::TriangulationConstructionError};
use crate::geometry::kernel::AdaptiveKernel;
use crate::geometry::point::Point;
use crate::geometry::traits::coordinate::Coordinate;
use crate::triangulation::builder::DelaunayTriangulationBuilder;
use crate::vertex;
use slotmap::KeyData;
use std::error::Error as StdError;
fn init_tracing() {
let _ = tracing_subscriber::fmt::try_init();
}
fn construction_error() -> DelaunayTriangulationConstructionError {
DelaunayTriangulationConstructionError::from(
TriangulationConstructionError::FailedToCreateCell {
message: "synthetic rebuild degeneracy".to_string(),
},
)
}
fn unit_simplex_vertices<const D: usize>() -> Vec<Vertex<f64, (), D>> {
let mut points = Vec::with_capacity(D + 1);
points.push(Point::new([0.0; D]));
for axis in 0..D {
let mut coords = [0.0; D];
coords[axis] = 1.0;
points.push(Point::new(coords));
}
Vertex::from_points(&points)
}
fn assert_topology_repair_fallback_rebuilds_duplicate_simplex<const D: usize>()
where
AdaptiveKernel<f64>: ExactPredicates<D>,
{
init_tracing();
let vertices = unit_simplex_vertices::<D>();
let mut dt: DelaunayTriangulation<_, (), (), D> =
DelaunayTriangulation::new(&vertices).unwrap();
let (_, existing_cell) = dt.cells().next().unwrap();
let duplicate_vertices = existing_cell.vertices().to_vec();
dt.tri
.tds
.insert_cell_bypassing_topology_checks_for_test(
Cell::new(duplicate_vertices.clone(), None).unwrap(),
)
.unwrap();
dt.tri
.tds
.insert_cell_bypassing_topology_checks_for_test(
Cell::new(duplicate_vertices, None).unwrap(),
)
.unwrap();
let outcome = delaunayize_by_flips(
&mut dt,
DelaunayizeConfig {
topology_max_cells_removed: 0,
fallback_rebuild: true,
..DelaunayizeConfig::default()
},
)
.unwrap();
assert!(outcome.used_fallback_rebuild);
assert!(
!outcome.topology_repair.succeeded,
"fallback rebuild should not mark the failed topology repair as succeeded"
);
assert_eq!(dt.number_of_vertices(), vertices.len());
assert!(dt.validate().is_ok());
}
struct ForceDelaunayRepairFailureGuard {
prior: bool,
}
impl ForceDelaunayRepairFailureGuard {
fn enable() -> Self {
Self {
prior: test_hooks::set_force_delaunay_repair_failure(true),
}
}
}
impl Drop for ForceDelaunayRepairFailureGuard {
fn drop(&mut self) {
test_hooks::restore_force_delaunay_repair_failure(self.prior);
}
}
#[test]
fn test_config_defaults() {
init_tracing();
let config = DelaunayizeConfig::default();
assert_eq!(config.topology_max_iterations, 64);
assert_eq!(config.topology_max_cells_removed, 10_000);
assert!(!config.fallback_rebuild);
assert!(config.delaunay_max_flips.is_none());
}
#[test]
fn test_already_delaunay_3d() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default()).unwrap();
assert!(outcome.topology_repair.succeeded);
assert!(!outcome.used_fallback_rebuild);
assert!(dt.validate().is_ok());
}
#[test]
fn test_already_delaunay_2d() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default()).unwrap();
assert!(outcome.topology_repair.succeeded);
assert!(dt.validate().is_ok());
}
#[test]
fn test_outcome_populated_on_success() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.5, 0.5, 0.5]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(&mut dt, DelaunayizeConfig::default()).unwrap();
assert!(outcome.topology_repair.succeeded);
assert_eq!(outcome.topology_repair.cells_removed, 0);
assert!(!outcome.used_fallback_rebuild);
}
#[test]
fn test_cell_vertex_uuids_missing_vertex() {
let mut tds: Tds<f64, (), i32, 2> = Tds::empty();
let vertex_keys: Vec<_> = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
]
.iter()
.map(|vertex| tds.insert_vertex_with_mapping(*vertex).unwrap())
.collect();
let missing = vertex_keys[0];
let cell = Cell::new(vertex_keys, Some(7)).unwrap();
tds.remove_isolated_vertex(missing);
let err = cell_vertex_uuids(&tds, &cell).unwrap_err();
assert_eq!(err, CellValidationError::VertexKeyNotFound { key: missing });
}
#[test]
fn test_snapshot_error_source() {
let source = CellValidationError::VertexKeyNotFound {
key: VertexKey::from(KeyData::from_ffi(0xBAD)),
};
let err = DelaunayizeError::FallbackCellDataSnapshotFailed {
source: source.clone(),
};
assert_eq!(
err,
DelaunayizeError::FallbackCellDataSnapshotFailed {
source: source.clone()
}
);
assert!(
err.to_string()
.contains("Fallback cell-data snapshot failed")
);
assert!(err.to_string().contains("no repair attempted"));
let error_source = StdError::source(&err).unwrap();
assert_eq!(error_source.to_string(), source.to_string());
}
#[test]
fn test_repair_snapshot_error_source() {
let source = DelaunayRepairError::PostconditionFailed {
message: "synthetic postcondition".to_string(),
};
let snapshot_error = CellValidationError::VertexKeyNotFound {
key: VertexKey::from(KeyData::from_ffi(0xBAD)),
};
let err = DelaunayizeError::DelaunayRepairFailedWithCellDataSnapshot {
source: source.clone(),
snapshot_error: snapshot_error.clone(),
};
assert_eq!(
err,
DelaunayizeError::DelaunayRepairFailedWithCellDataSnapshot {
source: source.clone(),
snapshot_error,
}
);
assert!(
err.to_string()
.contains("fallback cell-data snapshot failed")
);
let error_source = StdError::source(&err).unwrap();
assert_eq!(error_source.to_string(), source.to_string());
}
#[test]
fn test_restore_error_sources() {
let topology_source = PlManifoldRepairError::NoProgress {
over_shared_facets: 2,
iterations: 3,
cells_removed: 4,
};
let delaunay_source = DelaunayRepairError::PostconditionFailed {
message: "synthetic postcondition".to_string(),
};
let restore_error = CellValidationError::VertexKeyNotFound {
key: VertexKey::from(KeyData::from_ffi(0xBAD)),
};
let topology_err = DelaunayizeError::TopologyRepairFailedWithRebuildRestore {
source: topology_source.clone(),
restore_error: restore_error.clone(),
};
let delaunay_err = DelaunayizeError::DelaunayRepairFailedWithRebuildRestore {
source: delaunay_source.clone(),
restore_error,
};
assert!(
topology_err
.to_string()
.contains("cell-data restore failed")
);
assert_eq!(
StdError::source(&topology_err).unwrap().to_string(),
topology_source.to_string()
);
assert!(
delaunay_err
.to_string()
.contains("cell-data restore failed")
);
assert_eq!(
StdError::source(&delaunay_err).unwrap().to_string(),
delaunay_source.to_string()
);
}
#[test]
fn test_topology_rebuild_error_mapping() {
let source = PlManifoldRepairError::NoProgress {
over_shared_facets: 2,
iterations: 3,
cells_removed: 4,
};
let rebuild_error = construction_error();
let restore_error = CellValidationError::VertexKeyNotFound {
key: VertexKey::from(KeyData::from_ffi(0xBAD)),
};
let rebuild_err = topology_rebuild_error(
source.clone(),
FallbackRebuildError::Construction {
source: rebuild_error.clone(),
},
);
assert_eq!(
rebuild_err,
DelaunayizeError::TopologyRepairFailedWithRebuild {
source: source.clone(),
rebuild_error,
}
);
assert!(
rebuild_err
.to_string()
.contains("fallback rebuild also failed")
);
let restore_err = topology_rebuild_error(
source.clone(),
FallbackRebuildError::Restore {
source: restore_error.clone(),
},
);
assert_eq!(
restore_err,
DelaunayizeError::TopologyRepairFailedWithRebuildRestore {
source,
restore_error,
}
);
assert!(
restore_err
.to_string()
.contains("fallback rebuild succeeded but cell-data restore failed")
);
}
#[test]
fn test_delaunay_rebuild_error_mapping() {
let source = DelaunayRepairError::PostconditionFailed {
message: "synthetic postcondition".to_string(),
};
let rebuild_error = construction_error();
let restore_error = CellValidationError::VertexKeyNotFound {
key: VertexKey::from(KeyData::from_ffi(0xBAD)),
};
let rebuild_err = delaunay_rebuild_error(
source.clone(),
FallbackRebuildError::Construction {
source: rebuild_error.clone(),
},
);
assert_eq!(
rebuild_err,
DelaunayizeError::DelaunayRepairFailedWithRebuild {
source: source.clone(),
rebuild_error,
}
);
assert!(
rebuild_err
.to_string()
.contains("fallback rebuild also failed")
);
let restore_err = delaunay_rebuild_error(
source.clone(),
FallbackRebuildError::Restore {
source: restore_error.clone(),
},
);
assert_eq!(
restore_err,
DelaunayizeError::DelaunayRepairFailedWithRebuildRestore {
source,
restore_error,
}
);
assert!(
restore_err
.to_string()
.contains("fallback rebuild succeeded but cell-data restore failed")
);
}
#[test]
fn test_fallback_disabled_by_default() {
init_tracing();
let config = DelaunayizeConfig::default();
assert!(!config.fallback_rebuild);
}
#[test]
fn test_fallback_enabled_on_valid_triangulation() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let config = DelaunayizeConfig {
fallback_rebuild: true,
..DelaunayizeConfig::default()
};
let outcome = delaunayize_by_flips(&mut dt, config).unwrap();
assert!(!outcome.used_fallback_rebuild);
}
#[test]
fn delaunay_repair_fallback_rebuilds_after_unsupported_dimension() {
init_tracing();
let vertices = [vertex!([0.0]), vertex!([1.0])];
let mut dt: DelaunayTriangulation<_, (), (), 1> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome = delaunayize_by_flips(
&mut dt,
DelaunayizeConfig {
fallback_rebuild: true,
..DelaunayizeConfig::default()
},
)
.unwrap();
assert!(outcome.topology_repair.succeeded);
assert_eq!(outcome.topology_repair.cells_removed, 0);
assert_eq!(outcome.delaunay_repair.facets_checked, 0);
assert_eq!(outcome.delaunay_repair.flips_performed, 0);
assert_eq!(outcome.delaunay_repair.max_queue_len, 0);
assert!(outcome.used_fallback_rebuild);
assert_eq!(dt.number_of_vertices(), vertices.len());
assert!(dt.tds().is_valid().is_ok());
}
#[test]
fn delaunay_repair_fallback_rebuilds_supported_2d_after_repair_failure() {
init_tracing();
let vertices = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
vertex!([1.0, 1.0]),
];
let mut dt: DelaunayTriangulation<_, (), (), 2> =
DelaunayTriangulation::new(&vertices).unwrap();
let _guard = ForceDelaunayRepairFailureGuard::enable();
let outcome = delaunayize_by_flips(
&mut dt,
DelaunayizeConfig {
fallback_rebuild: true,
..DelaunayizeConfig::default()
},
)
.unwrap();
assert!(outcome.topology_repair.succeeded);
assert!(outcome.used_fallback_rebuild);
assert_eq!(dt.number_of_vertices(), vertices.len());
assert!(dt.validate().is_ok());
}
#[test]
fn topology_repair_fallback_stats_failed() {
assert_topology_repair_fallback_rebuilds_duplicate_simplex::<2>();
}
macro_rules! gen_topology_repair_fallback_tests {
($($dim:literal),* $(,)?) => {
pastey::paste! {
$(
#[test]
fn [<topology_repair_fallback_rebuilds_duplicate_simplex_ $dim d>]() {
assert_topology_repair_fallback_rebuilds_duplicate_simplex::<$dim>();
}
)*
}
};
}
gen_topology_repair_fallback_tests!(3, 4, 5);
#[test]
fn test_rebuild_restores_cell_data() {
init_tracing();
let vertices = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut dt = DelaunayTriangulationBuilder::new(&vertices)
.build::<i32>()
.unwrap();
let original_cell_key = dt.cells().next().unwrap().0;
dt.set_cell_data(original_cell_key, Some(42));
let tds = &dt.as_triangulation().tds;
let vertices: Vec<_> = tds
.vertices()
.map(|(_, v)| Vertex::new_with_uuid(*v.point(), v.uuid(), v.data))
.collect();
let cell_data = collect_cell_data(tds).unwrap();
let rebuilt =
rebuild_preserving_data(&dt.as_triangulation().kernel, &vertices, &cell_data).unwrap();
let (_, rebuilt_cell) = rebuilt.cells().next().unwrap();
assert_eq!(rebuilt_cell.data(), Some(&42));
assert!(rebuilt.validate().is_ok());
}
#[test]
fn test_rebuild_drops_ambiguous_data() {
init_tracing();
let vertices = [
vertex!([0.0, 0.0]),
vertex!([1.0, 0.0]),
vertex!([0.0, 1.0]),
];
let mut tds: Tds<f64, (), i32, 2> = Tds::empty();
let vertex_keys: Vec<_> = vertices
.iter()
.map(|vertex| tds.insert_vertex_with_mapping(*vertex).unwrap())
.collect();
let duplicate_a = Cell::new(vertex_keys.clone(), Some(42)).unwrap();
let duplicate_b = Cell::new(vertex_keys, Some(42)).unwrap();
tds.insert_cell_bypassing_topology_checks_for_test(duplicate_a)
.unwrap();
tds.insert_cell_bypassing_topology_checks_for_test(duplicate_b)
.unwrap();
let rebuild_vertices: Vec<_> = tds
.vertices()
.map(|(_, v)| Vertex::new_with_uuid(*v.point(), v.uuid(), v.data))
.collect();
let cell_data = collect_cell_data(&tds).unwrap();
let kernel = AdaptiveKernel::new();
let mut rebuilt: DelaunayTriangulation<_, (), i32, 2> =
DelaunayTriangulation::with_kernel(&kernel, &rebuild_vertices).unwrap();
restore_cell_data(&mut rebuilt, &cell_data).unwrap();
let (_, rebuilt_cell) = rebuilt.cells().next().unwrap();
assert_eq!(rebuilt_cell.data(), None);
assert!(rebuilt.validate().is_ok());
}
#[test]
fn test_deterministic_repeated_runs() {
init_tracing();
let vertices = vec![
vertex!([0.0, 0.0, 0.0]),
vertex!([1.0, 0.0, 0.0]),
vertex!([0.0, 1.0, 0.0]),
vertex!([0.0, 0.0, 1.0]),
vertex!([0.5, 0.5, 0.5]),
];
let config = DelaunayizeConfig::default();
let mut dt1: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome1 = delaunayize_by_flips(&mut dt1, config).unwrap();
let mut dt2: DelaunayTriangulation<_, (), (), 3> =
DelaunayTriangulation::new(&vertices).unwrap();
let outcome2 = delaunayize_by_flips(&mut dt2, config).unwrap();
assert_eq!(
outcome1.topology_repair.cells_removed,
outcome2.topology_repair.cells_removed
);
assert_eq!(
outcome1.used_fallback_rebuild,
outcome2.used_fallback_rebuild
);
}
}